Merge pull request #632 from dghubble/update-ct-and-ignition

glide.yaml: Update ct and Ignition
This commit is contained in:
Dalton Hubble
2017-08-08 13:55:21 -07:00
committed by GitHub
111 changed files with 4093 additions and 713 deletions

15
glide.lock generated
View File

@@ -1,5 +1,5 @@
hash: 7de5ab95677974311285feaa83e24f127bbb4c64a68740bab24d71f491e8b689
updated: 2017-05-24T15:28:05.291154327-07:00
hash: 9c2d24d482e4b591552c20d251b4620bdc2a12256564f8f18d7cb30b797bfd33
updated: 2017-08-08T13:27:35.465951914-07:00
imports:
- name: github.com/ajeddeloh/go-json
version: 73d058cf8437a1989030afe571eeab9f90eebbbd
@@ -10,11 +10,14 @@ imports:
- name: github.com/camlistore/camlistore
version: 9106ce829629773474c689b34aacd7d3aaa99426
- name: github.com/coreos/container-linux-config-transpiler
version: 12554ca0a5ce8ea4a6c594242ccb23d8b9bff493
version: d42f09a1374bc318d853b53e5a31148db68a4e2a
subpackages:
- config
- config/astyaml
- config/platform
- config/templating
- config/types
- config/types/util
- name: github.com/coreos/coreos-cloudinit
version: 5be99bf577f2768193c7fb587ef5a8806c1503cf
subpackages:
@@ -29,7 +32,7 @@ imports:
- journal
- unit
- name: github.com/coreos/ignition
version: d75d0aa3bf307f0954ce4ea8cac56dacec8d16ce
version: 11813c57bc05f30644bbae7891ae30a4a62e0b33
subpackages:
- config
- config/types
@@ -37,6 +40,8 @@ imports:
- config/v1/types
- config/v2_0
- config/v2_0/types
- config/v2_1
- config/v2_1/types
- config/validate
- config/validate/astjson
- config/validate/report
@@ -98,7 +103,7 @@ imports:
- internal/timeseries
- trace
- name: golang.org/x/sys
version: d4feaf1a7e61e1d9e79e6c4e76c6349e9cab0a03
version: 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
subpackages:
- unix
- name: google.golang.org/grpc

View File

@@ -19,13 +19,13 @@ import:
- transport
# Container Linux Config Transpiler and Ignition
- package: github.com/coreos/container-linux-config-transpiler
version: v0.2.2
version: v0.4.2
subpackages:
- config
- config/types
- config/templating
- package: github.com/coreos/ignition
version: d75d0aa3bf307f0954ce4ea8cac56dacec8d16ce
version: v0.17.2
subpackages:
- config
- config/types

View File

@@ -89,7 +89,7 @@ func (s *Server) ignitionHandler(core server.Server) ContextHandler {
}
// Parse bytes into a Container Linux Config
config, report := ct.Parse(buf.Bytes())
config, ast, report := ct.Parse(buf.Bytes())
if report.IsFatal() {
s.logger.Errorf("error parsing Container Linux config: %s", report.String())
http.NotFound(w, req)
@@ -97,7 +97,7 @@ func (s *Server) ignitionHandler(core server.Server) ContextHandler {
}
// Convert Container Linux Config into an Ignition Config
ign, report := ct.ConvertAs2_0(config, "")
ign, report := ct.ConvertAs2_0(config, "", ast)
if report.IsFatal() {
s.logger.Errorf("error converting Container Linux config: %s", report.String())
http.NotFound(w, req)

View File

@@ -1,90 +0,0 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"errors"
"io"
yaml "github.com/ajeddeloh/yaml"
"github.com/coreos/ignition/config/validate"
)
var (
ErrNotDocumentNode = errors.New("Can only convert from document node")
)
type yamlNode struct {
key yaml.Node
yaml.Node
}
func fromYamlDocumentNode(n yaml.Node) (yamlNode, error) {
if n.Kind != yaml.DocumentNode {
return yamlNode{}, ErrNotDocumentNode
}
return yamlNode{
key: n,
Node: *n.Children[0],
}, nil
}
func (n yamlNode) ValueLineCol(source io.ReadSeeker) (int, int, string) {
return n.Line, n.Column, ""
}
func (n yamlNode) KeyLineCol(source io.ReadSeeker) (int, int, string) {
return n.key.Line, n.key.Column, ""
}
func (n yamlNode) LiteralValue() interface{} {
return n.Value
}
func (n yamlNode) SliceChild(index int) (validate.AstNode, bool) {
if n.Kind != yaml.SequenceNode {
return nil, false
}
if index >= len(n.Children) {
return nil, false
}
return yamlNode{
key: yaml.Node{},
Node: *n.Children[index],
}, true
}
func (n yamlNode) KeyValueMap() (map[string]validate.AstNode, bool) {
if n.Kind != yaml.MappingNode {
return nil, false
}
kvmap := map[string]validate.AstNode{}
for i := 0; i < len(n.Children); i += 2 {
key := *n.Children[i]
value := *n.Children[i+1]
kvmap[key.Value] = yamlNode{
key: key,
Node: value,
}
}
return kvmap, true
}
func (n yamlNode) Tag() string {
return "yaml"
}

View File

@@ -0,0 +1,134 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package astyaml
import (
"errors"
"io"
"strings"
yaml "github.com/ajeddeloh/yaml"
"github.com/coreos/ignition/config/validate"
)
var (
ErrNotDocumentNode = errors.New("Can only convert from document node")
ErrNotMappingNode = errors.New("Tried to change the key of a node which is not a mapping node")
ErrKeyNotFound = errors.New("Key to be replaced not found")
)
type YamlNode struct {
tag string
key yaml.Node
yaml.Node
}
func FromYamlDocumentNode(n yaml.Node) (YamlNode, error) {
if n.Kind != yaml.DocumentNode {
return YamlNode{}, ErrNotDocumentNode
}
return YamlNode{
key: n,
tag: "yaml",
Node: *n.Children[0],
}, nil
}
func (n YamlNode) ValueLineCol(source io.ReadSeeker) (int, int, string) {
return n.Line + 1, n.Column + 1, ""
}
func (n YamlNode) KeyLineCol(source io.ReadSeeker) (int, int, string) {
return n.key.Line + 1, n.key.Column + 1, ""
}
func (n YamlNode) LiteralValue() interface{} {
return n.Value
}
func (n YamlNode) SliceChild(index int) (validate.AstNode, bool) {
if n.Kind != yaml.SequenceNode {
return nil, false
}
if index >= len(n.Children) {
return nil, false
}
return YamlNode{
key: yaml.Node{},
tag: n.tag,
Node: *n.Children[index],
}, true
}
func (n YamlNode) KeyValueMap() (map[string]validate.AstNode, bool) {
if n.Kind != yaml.MappingNode {
return nil, false
}
kvmap := map[string]validate.AstNode{}
for i := 0; i < len(n.Children); i += 2 {
key := *n.Children[i]
if n.tag == "json" {
key.Value = getIgnKeyName(key.Value)
}
value := *n.Children[i+1]
kvmap[key.Value] = YamlNode{
key: key,
tag: n.tag,
Node: value,
}
}
return kvmap, true
}
// ChangeKey replaces the oldkey with a new key/value pair. Useful for patching
// up a tree parsed from yaml but then used for validating an ignition structure
func (n *YamlNode) ChangeKey(oldKeyName, newKeyName string, newValue YamlNode) error {
if n.Kind != yaml.MappingNode {
return ErrNotMappingNode
}
for i := 0; i < len(n.Children); i += 2 {
key := n.Children[i]
if key.Value == oldKeyName {
//key.Value = newKeyName
(*n.Children[i]).Value = newKeyName
*n.Children[i+1] = newValue.Node
return nil
}
}
return ErrKeyNotFound
}
// getIgnKeyName converts a snake_case (used by clct) to a camelCase (used by
// ignition)
func getIgnKeyName(keyname string) string {
words := strings.Split(keyname, "_")
for i, word := range words[1:] {
words[i+1] = strings.Title(word)
}
return strings.Join(words, "")
}
func (n YamlNode) Tag() string {
return n.tag
}
// ChangeTreeTag changes the value Tag() returns to newTag
func (n *YamlNode) ChangeTreeTag(newTag string) {
n.tag = newTag
}

View File

@@ -18,21 +18,27 @@ import (
"reflect"
yaml "github.com/ajeddeloh/yaml"
"github.com/coreos/container-linux-config-transpiler/config/astyaml"
"github.com/coreos/container-linux-config-transpiler/config/platform"
"github.com/coreos/container-linux-config-transpiler/config/types"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
func Parse(data []byte) (types.Config, report.Report) {
// Parse will convert a byte slice containing a Container Linux Config into a
// golang struct representing the config, the parse tree from parsing the yaml
// and a report of any warnings or errors that occurred during the parsing.
func Parse(data []byte) (types.Config, validate.AstNode, report.Report) {
var cfg types.Config
var r report.Report
if err := yaml.Unmarshal(data, &cfg); err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError)
return types.Config{}, nil, report.ReportFromError(err, report.EntryError)
}
nodes := yaml.UnmarshalToNode(data)
var root validate.AstNode
if nodes == nil {
r.Add(report.Entry{
Kind: report.EntryWarning,
@@ -40,20 +46,35 @@ func Parse(data []byte) (types.Config, report.Report) {
})
r.Merge(validate.ValidateWithoutSource(reflect.ValueOf(cfg)))
} else {
root, err := fromYamlDocumentNode(*nodes)
var err error
root, err = astyaml.FromYamlDocumentNode(*nodes)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError)
return types.Config{}, nil, report.ReportFromError(err, report.EntryError)
}
r.Merge(validate.Validate(reflect.ValueOf(cfg), root, nil))
r.Merge(validate.Validate(reflect.ValueOf(cfg), root, nil, true))
}
if r.IsFatal() {
return types.Config{}, r
return types.Config{}, nil, r
}
return cfg, r
return cfg, root, r
}
func ConvertAs2_0(in types.Config, platform string) (ignTypes.Config, report.Report) {
return types.ConvertAs2_0(in, platform)
// ConvertAs2_0 will convert a golang struct representing a Container Linux
// Config into an Ignition Config, and a report of any warnings or errors. It
// takes the parse tree from parsing the Container Linux config as well.
// ConvertAs2_0 also accepts a platform string, which can either be one of the
// platform strings defined in config/templating/templating.go or an empty
// string if [dynamic data](doc/dynamic-data.md) isn't used.
func ConvertAs2_0(in types.Config, p string, ast validate.AstNode) (ignTypes.Config, report.Report) {
if !platform.IsSupportedPlatform(p) {
r := report.Report{}
r.Add(report.Entry{
Kind: report.EntryError,
Message: "unsupported platform",
})
return ignTypes.Config{}, r
}
return types.ConvertAs2_0(in, p, ast)
}

View File

@@ -0,0 +1,44 @@
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package platform
const (
Azure = "azure"
DO = "digitalocean"
EC2 = "ec2"
GCE = "gce"
Packet = "packet"
OpenStackMetadata = "openstack-metadata"
VagrantVirtualbox = "vagrant-virtualbox"
)
var Platforms = []string{
Azure,
DO,
EC2,
GCE,
Packet,
OpenStackMetadata,
VagrantVirtualbox,
}
func IsSupportedPlatform(platform string) bool {
for _, supportedPlatform := range Platforms {
if supportedPlatform == platform {
return true
}
}
return platform == ""
}

View File

@@ -17,6 +17,8 @@ package templating
import (
"fmt"
"strings"
"github.com/coreos/container-linux-config-transpiler/config/platform"
)
var (
@@ -24,24 +26,6 @@ var (
ErrUnknownField = fmt.Errorf("unknown field")
)
const (
PlatformAzure = "azure"
PlatformDO = "digitalocean"
PlatformEC2 = "ec2"
PlatformGCE = "gce"
PlatformPacket = "packet"
PlatformOpenStackMetadata = "openstack-metadata"
)
var Platforms = []string{
PlatformAzure,
PlatformDO,
PlatformEC2,
PlatformGCE,
PlatformPacket,
PlatformOpenStackMetadata,
}
const (
fieldHostname = "HOSTNAME"
fieldV4Private = "PRIVATE_IPV4"
@@ -51,12 +35,12 @@ const (
)
var platformTemplatingMap = map[string]map[string]string{
PlatformAzure: {
platform.Azure: {
// TODO: is this right?
fieldV4Private: "COREOS_AZURE_IPV4_DYNAMIC",
fieldV4Public: "COREOS_AZURE_IPV4_VIRTUAL",
},
PlatformDO: {
platform.DO: {
// TODO: unused: COREOS_DIGITALOCEAN_IPV4_ANCHOR_0
fieldHostname: "COREOS_DIGITALOCEAN_HOSTNAME",
fieldV4Private: "COREOS_DIGITALOCEAN_IPV4_PRIVATE_0",
@@ -64,27 +48,31 @@ var platformTemplatingMap = map[string]map[string]string{
fieldV6Private: "COREOS_DIGITALOCEAN_IPV6_PRIVATE_0",
fieldV6Public: "COREOS_DIGITALOCEAN_IPV6_PUBLIC_0",
},
PlatformEC2: {
platform.EC2: {
fieldHostname: "COREOS_EC2_HOSTNAME",
fieldV4Private: "COREOS_EC2_IPV4_LOCAL",
fieldV4Public: "COREOS_EC2_IPV4_PUBLIC",
},
PlatformGCE: {
platform.GCE: {
fieldHostname: "COREOS_GCE_HOSTNAME",
fieldV4Private: "COREOS_GCE_IP_EXTERNAL_0",
fieldV4Public: "COREOS_GCE_IP_LOCAL_0",
fieldV4Private: "COREOS_GCE_IP_LOCAL_0",
fieldV4Public: "COREOS_GCE_IP_EXTERNAL_0",
},
PlatformPacket: {
platform.Packet: {
fieldHostname: "COREOS_PACKET_HOSTNAME",
fieldV4Private: "COREOS_PACKET_IPV4_PRIVATE_0",
fieldV4Public: "COREOS_PACKET_IPV4_PUBLIC_0",
fieldV6Public: "COREOS_PACKET_IPV6_PUBLIC_0",
},
PlatformOpenStackMetadata: {
platform.OpenStackMetadata: {
fieldHostname: "COREOS_OPENSTACK_HOSTNAME",
fieldV4Private: "COREOS_OPENSTACK_IPV4_LOCAL",
fieldV4Public: "COREOS_OPENSTACK_IPV4_PUBLIC",
},
platform.VagrantVirtualbox: {
fieldHostname: "COREOS_VAGRANT_VIRTUALBOX_HOSTNAME",
fieldV4Private: "COREOS_VAGRANT_VIRTUALBOX_PRIVATE_IPV4",
},
}
// HasTemplating returns whether or not any of the environment variables present

View File

@@ -15,16 +15,48 @@
package types
import (
"errors"
"fmt"
"reflect"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate/report"
"github.com/coreos/container-linux-config-transpiler/config/platform"
"github.com/coreos/container-linux-config-transpiler/config/templating"
"github.com/coreos/container-linux-config-transpiler/config/types/util"
"github.com/coreos/ignition/config/validate"
)
var (
ErrPlatformUnspecified = fmt.Errorf("platform must be specified to use templating")
ErrInvalidKey = errors.New("Key is invalid (wrong type or not found")
ErrNilNode = errors.New("Ast node is nil")
ErrKeyNotFound = errors.New("Key not found")
)
func init() {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, p string) (ignTypes.Config, report.Report, validate.AstNode) {
if p == platform.OpenStackMetadata {
out.Systemd.Units = append(out.Systemd.Units, ignTypes.SystemdUnit{
Name: "coreos-metadata.service",
DropIns: []ignTypes.SystemdUnitDropIn{{
Name: "20-clct-provider-override.conf",
Contents: fmt.Sprintf("[Service]\nEnvironment=COREOS_METADATA_OPT_PROVIDER=--provider=%s", p),
}},
})
out.Systemd.Units = append(out.Systemd.Units, ignTypes.SystemdUnit{
Name: "coreos-metadata-sshkeys@.service",
DropIns: []ignTypes.SystemdUnitDropIn{{
Name: "20-clct-provider-override.conf",
Contents: fmt.Sprintf("[Service]\nEnvironment=COREOS_METADATA_OPT_PROVIDER=--provider=%s", p),
}},
})
}
return out, report.Report{}, ast
})
}
func isZero(v interface{}) bool {
if v == nil {
return true
@@ -36,37 +68,36 @@ func isZero(v interface{}) bool {
// assembleUnit will assemble the contents of a systemd unit dropin that will
// have the given environment variables, and call the given exec line with the
// provided args prepended to it
func assembleUnit(exec string, args, vars []string, platform string) (string, error) {
func assembleUnit(exec string, args, vars []string, p string) (util.SystemdUnit, error) {
hasTemplating := templating.HasTemplating(args)
var out string
out := util.NewSystemdUnit()
if hasTemplating {
if platform == "" {
return "", ErrPlatformUnspecified
if p == "" {
return util.SystemdUnit{}, ErrPlatformUnspecified
}
out = "[Unit]\nRequires=coreos-metadata.service\nAfter=coreos-metadata.service\n\n[Service]\nEnvironmentFile=/run/metadata/coreos\n"
out.Unit.Add("Requires=coreos-metadata.service")
out.Unit.Add("After=coreos-metadata.service")
out.Service.Add("EnvironmentFile=/run/metadata/coreos")
var err error
args, err = templating.PerformTemplating(platform, args)
args, err = templating.PerformTemplating(p, args)
if err != nil {
return "", err
return util.SystemdUnit{}, err
}
} else {
out = "[Service]\n"
}
for _, v := range vars {
out += fmt.Sprintf("Environment=\"%s\"\n", v)
out.Service.Add(fmt.Sprintf("Environment=\"%s\"", v))
}
for _, a := range args {
exec += fmt.Sprintf(" \\\n %s", a)
}
out += "ExecStart=\n"
out += fmt.Sprintf("ExecStart=%s", exec)
out.Service.Add("ExecStart=")
out.Service.Add(fmt.Sprintf("ExecStart=%s", exec))
return out, nil
}
// getCliArgs builds a list of --ARG=VAL from a struct with cli: tags on its members.
func getCliArgs(e interface{}) []string {
func getArgs(format, tagName string, e interface{}) []string {
if e == nil {
return nil
}
@@ -79,11 +110,58 @@ func getCliArgs(e interface{}) []string {
if et.Field(i).Anonymous {
vars = append(vars, getCliArgs(val)...)
} else {
key := et.Field(i).Tag.Get("cli")
vars = append(vars, fmt.Sprintf("--%s=%q", key, val))
key := et.Field(i).Tag.Get(tagName)
if _, ok := val.(string); ok {
// to handle whitespace characters
val = fmt.Sprintf("%q", val)
}
vars = append(vars, fmt.Sprintf(format, key, val))
}
}
}
return vars
}
// getCliArgs builds a list of --ARG=VAL from a struct with cli: tags on its members.
func getCliArgs(e interface{}) []string {
return getArgs("--%s=%v", "cli", e)
}
// Get returns the value for key, where key is an int or string and n is a
// sequence node or mapping node, respectively.
func getNodeChild(n validate.AstNode, key interface{}) (validate.AstNode, error) {
if n == nil {
return nil, ErrNilNode
}
switch k := key.(type) {
case int:
if child, ok := n.SliceChild(k); ok {
return child, nil
}
case string:
kvmap, ok := n.KeyValueMap()
if !ok {
return nil, ErrInvalidKey
}
if v, ok := kvmap[k]; ok {
return v, nil
}
default:
return nil, ErrInvalidKey
}
// not found
return nil, ErrKeyNotFound
}
// GetPath works like calling Get() repeatly with each argument.
func getNodeChildPath(n validate.AstNode, key ...interface{}) (validate.AstNode, error) {
if len(key) == 0 {
return n, nil
}
next, err := getNodeChild(n, key[0])
if err != nil {
return nil, err
}
return getNodeChildPath(next, key[1:]...)
}

View File

@@ -18,6 +18,7 @@ import (
"net/url"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -49,36 +50,48 @@ type ConfigReference struct {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, ref := range in.Ignition.Config.Append {
newRef, err := convertConfigReference(ref)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
r := report.Report{}
cfgNode, _ := getNodeChildPath(ast, "ignition", "config", "append")
for i, ref := range in.Ignition.Config.Append {
tmp, _ := getNodeChild(cfgNode, i)
newRef, convertReport := convertConfigReference(ref, tmp)
r.Merge(convertReport)
if convertReport.IsFatal() {
// don't add to the output if invalid
continue
}
out.Ignition.Config.Append = append(out.Ignition.Config.Append, newRef)
}
cfgNode, _ = getNodeChildPath(ast, "ignition", "config", "replace")
if in.Ignition.Config.Replace != nil {
newRef, err := convertConfigReference(*in.Ignition.Config.Replace)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
newRef, convertReport := convertConfigReference(*in.Ignition.Config.Replace, cfgNode)
r.Merge(convertReport)
if convertReport.IsFatal() {
// don't add to the output if invalid
return out, r, ast
}
out.Ignition.Config.Replace = &newRef
}
return out, report.Report{}
return out, r, ast
})
}
func convertConfigReference(in ConfigReference) (ignTypes.ConfigReference, error) {
func convertConfigReference(in ConfigReference, ast validate.AstNode) (ignTypes.ConfigReference, report.Report) {
source, err := url.Parse(in.Source)
if err != nil {
return ignTypes.ConfigReference{}, err
r := report.ReportFromError(err, report.EntryError)
if n, err := getNodeChild(ast, "source"); err == nil {
r.AddPosition(n.ValueLineCol(nil))
}
return ignTypes.ConfigReference{}, r
}
return ignTypes.ConfigReference{
Source: ignTypes.Url(*source),
Verification: convertVerification(in.Verification),
}, nil
}, report.Report{}
}
func convertVerification(in Verification) ignTypes.Verification {

View File

@@ -17,12 +17,13 @@ package types
import (
"reflect"
"github.com/coreos/container-linux-config-transpiler/config/astyaml"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
type converterFor2_0 func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report)
type converterFor2_0 func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode)
var convertersFor2_0 []converterFor2_0
@@ -30,7 +31,15 @@ func register2_0(f converterFor2_0) {
convertersFor2_0 = append(convertersFor2_0, f)
}
func ConvertAs2_0(in Config, platform string) (ignTypes.Config, report.Report) {
func ConvertAs2_0(in Config, platform string, ast validate.AstNode) (ignTypes.Config, report.Report) {
// convert our tree from having yaml tags to having json tags, so when Validate() is
// called on the tree, it can find the keys in the ignition structs (which are denoted
// by `json` tags)
if asYamlNode, ok := ast.(astyaml.YamlNode); ok {
asYamlNode.ChangeTreeTag("json")
ast = asYamlNode
}
out := ignTypes.Config{
Ignition: ignTypes.Ignition{
Version: ignTypes.IgnitionVersion{Major: 2, Minor: 0},
@@ -41,14 +50,14 @@ func ConvertAs2_0(in Config, platform string) (ignTypes.Config, report.Report) {
for _, convert := range convertersFor2_0 {
var subReport report.Report
out, subReport = convert(in, out, platform)
out, subReport, ast = convert(in, ast, out, platform)
r.Merge(subReport)
}
if r.IsFatal() {
return ignTypes.Config{}, r
}
validationReport := validate.ValidateWithoutSource(reflect.ValueOf(out))
validationReport := validate.Validate(reflect.ValueOf(out), ast, nil, false)
r.Merge(validationReport)
if r.IsFatal() {
return ignTypes.Config{}, r

View File

@@ -19,6 +19,7 @@ import (
"github.com/alecthomas/units"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -41,35 +42,49 @@ type Partition struct {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, disk := range in.Storage.Disks {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
r := report.Report{}
for disk_idx, disk := range in.Storage.Disks {
newDisk := ignTypes.Disk{
Device: ignTypes.Path(disk.Device),
WipeTable: disk.WipeTable,
}
for _, partition := range disk.Partitions {
for part_idx, partition := range disk.Partitions {
size, err := convertPartitionDimension(partition.Size)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
convertReport := report.ReportFromError(err, report.EntryError)
if sub_node, err := getNodeChildPath(ast, "storage", "disks", disk_idx, "partitions", part_idx, "size"); err == nil {
convertReport.AddPosition(sub_node.ValueLineCol(nil))
}
r.Merge(convertReport)
// dont add invalid partitions
continue
}
start, err := convertPartitionDimension(partition.Start)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
convertReport := report.ReportFromError(err, report.EntryError)
if sub_node, err := getNodeChildPath(ast, "storage", "disks", disk_idx, "partitions", part_idx, "start"); err == nil {
convertReport.AddPosition(sub_node.ValueLineCol(nil))
}
r.Merge(convertReport)
// dont add invalid partitions
continue
}
newDisk.Partitions = append(newDisk.Partitions, ignTypes.Partition{
newPart := ignTypes.Partition{
Label: ignTypes.PartitionLabel(partition.Label),
Number: partition.Number,
Size: size,
Start: start,
TypeGUID: ignTypes.PartitionTypeGUID(partition.TypeGUID),
})
}
newDisk.Partitions = append(newDisk.Partitions, newPart)
}
out.Storage.Disks = append(out.Storage.Disks, newDisk)
}
return out, report.Report{}
return out, r, ast
})
}

View File

@@ -19,6 +19,7 @@ import (
"strings"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -27,7 +28,7 @@ type Docker struct {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
if in.Docker != nil {
contents := fmt.Sprintf("[Service]\nEnvironment=\"DOCKER_OPTS=%s\"", strings.Join(in.Docker.Flags, " "))
out.Systemd.Units = append(out.Systemd.Units, ignTypes.SystemdUnit{
@@ -39,6 +40,6 @@ func init() {
}},
})
}
return out, report.Report{}
return out, report.Report{}, ast
})
}

View File

@@ -20,6 +20,7 @@ import (
"github.com/coreos/go-semver/semver"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -54,7 +55,7 @@ func (e EtcdVersion) Validate() report.Report {
return report.ReportFromError(EtcdVersionTooOld, report.EntryError)
case v.Major == 2 && v.Minor > 3:
fallthrough
case v.Major == 3 && v.Minor > 1:
case v.Major == 3 && v.Minor > 2:
return report.ReportFromError(EtcdMinorVersionTooNew, report.EntryWarning)
case v.Major > 3:
return report.ReportFromError(EtcdMajorVersionTooNew, report.EntryError)
@@ -101,22 +102,28 @@ func (etcd *Etcd) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
etcd.Options = o
} else if version.Major == 3 && version.Minor >= 1 {
} else if version.Major == 3 && version.Minor == 1 {
o := Etcd3_1{}
if err := unmarshal(&o); err != nil {
return err
}
etcd.Options = o
} else if version.Major == 3 && version.Minor >= 2 {
o := Etcd3_2{}
if err := unmarshal(&o); err != nil {
return err
}
etcd.Options = o
}
return nil
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
if in.Etcd != nil {
contents, err := etcdContents(*in.Etcd, platform)
if err != nil {
return ignTypes.Config{}, report.ReportFromError(err, report.EntryError)
return ignTypes.Config{}, report.ReportFromError(err, report.EntryError), ast
}
out.Systemd.Units = append(out.Systemd.Units, ignTypes.SystemdUnit{
Name: "etcd-member.service",
@@ -127,7 +134,7 @@ func init() {
}},
})
}
return out, report.Report{}
return out, report.Report{}, ast
})
}
@@ -139,7 +146,12 @@ func etcdContents(etcd Etcd, platform string) (string, error) {
vars = []string{fmt.Sprintf("ETCD_IMAGE_TAG=v%s", etcd.Version)}
}
return assembleUnit("/usr/lib/coreos/etcd-wrapper $ETCD_OPTS", args, vars, platform)
unit, err := assembleUnit("/usr/lib/coreos/etcd-wrapper $ETCD_OPTS", args, vars, platform)
if err != nil {
return "", err
}
return unit.String(), nil
}
type Etcd3_0 struct {
@@ -149,6 +161,7 @@ type Etcd3_0 struct {
SnapshotCount int `yaml:"snapshot_count" cli:"snapshot-count"`
HeartbeatInterval int `yaml:"heartbeat_interval" cli:"heartbeat-interval"`
ElectionTimeout int `yaml:"election_timeout" cli:"election-timeout"`
EnablePprof bool `yaml:"enable_pprof" cli:"enable-pprof"`
ListenPeerUrls string `yaml:"listen_peer_urls" cli:"listen-peer-urls"`
ListenClientUrls string `yaml:"listen_client_urls" cli:"listen-client-urls"`
MaxSnapshots int `yaml:"max_snapshots" cli:"max-snapshots"`
@@ -195,6 +208,7 @@ type Etcd3_1 struct {
SnapshotCount int `yaml:"snapshot_count" cli:"snapshot-count"`
HeartbeatInterval int `yaml:"heartbeat_interval" cli:"heartbeat-interval"`
ElectionTimeout int `yaml:"election_timeout" cli:"election-timeout"`
EnablePprof bool `yaml:"enable_pprof" cli:"enable-pprof"`
ListenPeerUrls string `yaml:"listen_peer_urls" cli:"listen-peer-urls"`
ListenClientUrls string `yaml:"listen_client_urls" cli:"listen-client-urls"`
MaxSnapshots int `yaml:"max_snapshots" cli:"max-snapshots"`
@@ -236,6 +250,57 @@ type Etcd3_1 struct {
LogOutput string `yaml:"log_output" cli:"log-output"`
}
type Etcd3_2 struct {
Name string `yaml:"name" cli:"name"`
DataDir string `yaml:"data_dir" cli:"data-dir"`
WalDir string `yaml:"wal_dir" cli:"wal-dir"`
SnapshotCount int `yaml:"snapshot_count" cli:"snapshot-count"`
HeartbeatInterval int `yaml:"heartbeat_interval" cli:"heartbeat-interval"`
ElectionTimeout int `yaml:"election_timeout" cli:"election-timeout"`
EnablePprof bool `yaml:"enable_pprof" cli:"enable-pprof"`
EnableV2 bool `yaml:"enable_v2" cli:"enable-v2"`
ListenPeerUrls string `yaml:"listen_peer_urls" cli:"listen-peer-urls"`
ListenClientUrls string `yaml:"listen_client_urls" cli:"listen-client-urls"`
MaxSnapshots int `yaml:"max_snapshots" cli:"max-snapshots"`
MaxWals int `yaml:"max_wals" cli:"max-wals"`
Cors string `yaml:"cors" cli:"cors"`
InitialAdvertisePeerUrls string `yaml:"initial_advertise_peer_urls" cli:"initial-advertise-peer-urls"`
InitialCluster string `yaml:"initial_cluster" cli:"initial-cluster"`
InitialClusterState string `yaml:"initial_cluster_state" cli:"initial-cluster-state"`
InitialClusterToken string `yaml:"initial_cluster_token" cli:"initial-cluster-token"`
AdvertiseClientUrls string `yaml:"advertise_client_urls" cli:"advertise-client-urls"`
Discovery string `yaml:"discovery" cli:"discovery"`
DiscoverySrv string `yaml:"discovery_srv" cli:"discovery-srv"`
DiscoveryFallback string `yaml:"discovery_fallback" cli:"discovery-fallback"`
DiscoveryProxy string `yaml:"discovery_proxy" cli:"discovery-proxy"`
StrictReconfigCheck bool `yaml:"strict_reconfig_check" cli:"strict-reconfig-check"`
AutoCompactionRetention int `yaml:"auto_compaction_retention" cli:"auto-compaction-retention"`
Proxy string `yaml:"proxy" cli:"proxy"`
ProxyFailureWait int `yaml:"proxy_failure_wait" cli:"proxy-failure-wait"`
ProxyRefreshInterval int `yaml:"proxy_refresh_interval" cli:"proxy-refresh-interval"`
ProxyDialTimeout int `yaml:"proxy_dial_timeout" cli:"proxy-dial-timeout"`
ProxyWriteTimeout int `yaml:"proxy_write_timeout" cli:"proxy-write-timeout"`
ProxyReadTimeout int `yaml:"proxy_read_timeout" cli:"proxy-read-timeout"`
CaFile string `yaml:"ca_file" cli:"ca-file" deprecated:"ca_file obsoleted by trusted_ca_file and client_cert_auth"`
CertFile string `yaml:"cert_file" cli:"cert-file"`
KeyFile string `yaml:"key_file" cli:"key-file"`
ClientCertAuth bool `yaml:"client_cert_auth" cli:"client-cert-auth"`
TrustedCaFile string `yaml:"trusted_ca_file" cli:"trusted-ca-file"`
AutoTls bool `yaml:"auto_tls" cli:"auto-tls"`
PeerCaFile string `yaml:"peer_ca_file" cli:"peer-ca-file" deprecated:"peer_ca_file obsoleted peer_trusted_ca_file and peer_client_cert_auth"`
PeerCertFile string `yaml:"peer_cert_file" cli:"peer-cert-file"`
PeerKeyFile string `yaml:"peer_key_file" cli:"peer-key-file"`
PeerClientCertAuth bool `yaml:"peer_client_cert_auth" cli:"peer-client-cert-auth"`
PeerTrustedCaFile string `yaml:"peer_trusted_ca_file" cli:"peer-trusted-ca-file"`
PeerAutoTls bool `yaml:"peer_auto_tls" cli:"peer-auto-tls"`
AuthToken string `yaml:"auth_token" cli:"auth-token"`
Debug bool `yaml:"debug" cli:"debug"`
LogPackageLevels string `yaml:"log_package_levels" cli:"log-package-levels"`
ForceNewCluster bool `yaml:"force_new_cluster" cli:"force-new-cluster"`
Metrics string `yaml:"metrics" cli:"metrics"`
LogOutput string `yaml:"log_output" cli:"log-output"`
}
type Etcd2 struct {
AdvertiseClientURLs string `yaml:"advertise_client_urls" cli:"advertise-client-urls"`
CAFile string `yaml:"ca_file" cli:"ca-file" deprecated:"ca_file obsoleted by trusted_ca_file and client_cert_auth"`

View File

@@ -17,7 +17,10 @@ package types
import (
"net/url"
"github.com/coreos/container-linux-config-transpiler/config/astyaml"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
@@ -51,8 +54,11 @@ type FileGroup struct {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
for _, file := range in.Storage.Files {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
r := report.Report{}
files_node, _ := getNodeChildPath(ast, "storage", "files")
for i, file := range in.Storage.Files {
file_node, _ := getNodeChild(files_node, i)
newFile := ignTypes.File{
Filesystem: file.Filesystem,
Path: ignTypes.Path(file.Path),
@@ -73,10 +79,29 @@ func init() {
if file.Contents.Remote.Url != "" {
source, err := url.Parse(file.Contents.Remote.Url)
if err != nil {
return out, report.ReportFromError(err, report.EntryError)
// if invalid, record error and continue
convertReport := report.ReportFromError(err, report.EntryError)
if n, err := getNodeChildPath(file_node, "contents", "remote", "url"); err != nil {
line, col, _ := n.ValueLineCol(nil)
convertReport.AddPosition(line, col, "")
}
continue
}
// patch the yaml tree to look like the ignition tree by making contents
// the remote section and changing the name from url -> source
asYamlNode, ok := file_node.(astyaml.YamlNode)
if ok {
newContents, _ := getNodeChildPath(file_node, "contents", "remote")
newContentsAsYaml := newContents.(astyaml.YamlNode)
asYamlNode.ChangeKey("contents", "contents", newContentsAsYaml)
url, _ := getNodeChild(newContents.(astyaml.YamlNode), "url")
newContentsAsYaml.ChangeKey("url", "source", url.(astyaml.YamlNode))
}
newFile.Contents = ignTypes.FileContents{Source: ignTypes.Url(*source)}
}
if newFile.Contents == (ignTypes.FileContents{}) {
@@ -93,6 +118,6 @@ func init() {
out.Storage.Files = append(out.Storage.Files, newFile)
}
return out, report.Report{}
return out, r, ast
})
}

View File

@@ -16,6 +16,7 @@ package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -37,7 +38,7 @@ type Create struct {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
for _, filesystem := range in.Storage.Filesystems {
newFilesystem := ignTypes.Filesystem{
Name: filesystem.Name,
@@ -66,6 +67,6 @@ func init() {
out.Storage.Filesystems = append(out.Storage.Filesystems, newFilesystem)
}
return out, report.Report{}
return out, report.Report{}, ast
})
}

View File

@@ -1,23 +1,28 @@
package types
import (
"encoding/json"
"errors"
"fmt"
"github.com/coreos/go-semver/semver"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrFlannelTooOld = errors.New("invalid flannel version (too old)")
ErrFlannelMinorTooNew = errors.New("flannel minor version too new. Only options available in the previous minor version will be supported")
OldestFlannelVersion = *semver.New("0.5.0")
FlannelDefaultVersion = *semver.New("0.6.0")
ErrFlannelTooOld = errors.New("invalid flannel version (too old)")
ErrFlannelMinorTooNew = errors.New("flannel minor version too new. Only options available in the previous minor version will be supported")
ErrNetConfigInvalidJSON = errors.New("flannel network config doesn't appear to be valid JSON")
ErrNetConfigProvidedAndKubeMgrSet = errors.New("flannel network config cannot be provided if kube_subnet_mgr is set")
OldestFlannelVersion = *semver.New("0.5.0")
FlannelDefaultVersion = *semver.New("0.6.0")
)
type Flannel struct {
Version *FlannelVersion `yaml:"version"`
Version *FlannelVersion `yaml:"version"`
NetworkConfig NetworkConfig `yaml:"network_config"`
Options
}
@@ -25,6 +30,20 @@ type flannelCommon Flannel
type FlannelVersion semver.Version
type NetworkConfig string
func (nc NetworkConfig) Validate() report.Report {
if nc == "" {
return report.Report{}
}
tmp := make(map[string]interface{})
err := json.Unmarshal([]byte(nc), &tmp)
if err != nil {
return report.ReportFromError(ErrNetConfigInvalidJSON, report.EntryError)
}
return report.Report{}
}
func (v *FlannelVersion) UnmarshalYAML(unmarshal func(interface{}) error) error {
t := semver.Version(*v)
if err := unmarshal(&t); err != nil {
@@ -49,6 +68,16 @@ func (fv FlannelVersion) String() string {
return semver.Version(fv).String()
}
func (f *Flannel) Validate() report.Report {
switch o := f.Options.(type) {
case Flannel0_7:
if o.KubeSubnetMgr && f.NetworkConfig != "" {
return report.ReportFromError(ErrNetConfigProvidedAndKubeMgrSet, report.EntryError)
}
}
return report.Report{}
}
func (flannel *Flannel) UnmarshalYAML(unmarshal func(interface{}) error) error {
t := flannelCommon(*flannel)
if err := unmarshal(&t); err != nil {
@@ -86,11 +115,11 @@ func (flannel *Flannel) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
if in.Flannel != nil {
contents, err := flannelContents(*in.Flannel, platform)
if err != nil {
return ignTypes.Config{}, report.ReportFromError(err, report.EntryError)
return ignTypes.Config{}, report.ReportFromError(err, report.EntryError), ast
}
out.Systemd.Units = append(out.Systemd.Units, ignTypes.SystemdUnit{
Name: "flanneld.service",
@@ -101,16 +130,63 @@ func init() {
}},
})
}
return out, report.Report{}
return out, report.Report{}, ast
})
}
// flannelContents creates the string containing the systemd drop in for flannel
func flannelContents(flannel Flannel, platform string) (string, error) {
args := getCliArgs(flannel.Options)
vars := []string{fmt.Sprintf("FLANNEL_IMAGE_TAG=v%s", flannel.Version)}
var vars []string
if flannel.Version != nil {
vars = []string{fmt.Sprintf("FLANNEL_IMAGE_TAG=v%s", flannel.Version)}
}
return assembleUnit("/usr/lib/coreos/flannel-wrapper $FLANNEL_OPTS", args, vars, platform)
unit, err := assembleUnit("/usr/lib/coreos/flannel-wrapper $FLANNEL_OPTS", args, vars, platform)
if err != nil {
return "", err
}
if flannel.NetworkConfig != "" {
pre := "ExecStartPre=/usr/bin/etcdctl"
var endpoints string
var etcdCAFile string
var etcdCertFile string
var etcdKeyFile string
switch o := flannel.Options.(type) {
case Flannel0_7:
endpoints = o.EtcdEndpoints
etcdCAFile = o.EtcdCAFile
etcdCertFile = o.EtcdCertFile
etcdKeyFile = o.EtcdKeyFile
case Flannel0_6:
endpoints = o.EtcdEndpoints
etcdCAFile = o.EtcdCAFile
etcdCertFile = o.EtcdCertFile
etcdKeyFile = o.EtcdKeyFile
case Flannel0_5:
endpoints = o.EtcdEndpoints
etcdCAFile = o.EtcdCAFile
etcdCertFile = o.EtcdCertFile
etcdKeyFile = o.EtcdKeyFile
}
if endpoints != "" {
pre += fmt.Sprintf(" --endpoints=%q", endpoints)
}
if etcdCAFile != "" {
pre += fmt.Sprintf(" --ca-file=%q", etcdCAFile)
}
if etcdCertFile != "" {
pre += fmt.Sprintf(" --cert-file=%q", etcdCertFile)
}
if etcdKeyFile != "" {
pre += fmt.Sprintf(" --key-file=%q", etcdKeyFile)
}
pre += fmt.Sprintf(" set /coreos.com/network/config %q", flannel.NetworkConfig)
unit.Service.Add(pre)
}
return unit.String(), nil
}
// Flannel0_7 represents flannel options for version 0.7.x. Don't embed Flannel0_6 because

View File

@@ -16,20 +16,47 @@ package types
import (
"errors"
"fmt"
"strings"
"time"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrUnknownStrategy = errors.New("unknown reboot strategy")
ErrMissingStartOrLength = errors.New("window-start and window-length must both be specified")
ErrUnknownStrategy = errors.New("unknown reboot strategy")
ErrParsingWindowStart = errors.New("couldn't parse window start")
ErrUnknownDay = errors.New("unknown day in window start")
ErrParsingWindow = errors.New("couldn't parse window start")
ErrParsingLength = errors.New("couldn't parse window length")
)
type Locksmith struct {
RebootStrategy RebootStrategy `yaml:"reboot_strategy"`
RebootStrategy RebootStrategy `yaml:"reboot_strategy" locksmith:"REBOOT_STRATEGY"`
WindowStart WindowStart `yaml:"window_start" locksmith:"LOCKSMITHD_REBOOT_WINDOW_START"`
WindowLength WindowLength `yaml:"window_length" locksmith:"LOCKSMITHD_REBOOT_WINDOW_LENGTH"`
Group string `yaml:"group" locksmith:"LOCKSMITHD_GROUP"`
EtcdEndpoints string `yaml:"etcd_endpoints" locksmith:"LOCKSMITHD_ENDPOINT"`
EtcdCAFile string `yaml:"etcd_cafile" locksmith:"LOCKSMITHD_ETCD_CAFILE"`
EtcdCertFile string `yaml:"etcd_certfile" locksmith:"LOCKSMITHD_ETCD_CERTFILE"`
EtcdKeyFile string `yaml:"etcd_keyfile" locksmith:"LOCKSMITHD_ETCD_KEYFILE"`
}
func (l Locksmith) configLines() []string {
return getArgs("%s=%q", "locksmith", l)
}
type RebootStrategy string
type WindowStart string
type WindowLength string
func (l Locksmith) Validate() report.Report {
if (l.WindowStart != "" && l.WindowLength == "") || (l.WindowStart == "" && l.WindowLength != "") {
return report.ReportFromError(ErrMissingStartOrLength, report.EntryError)
}
return report.Report{}
}
func (r RebootStrategy) Validate() report.Report {
switch strings.ToLower(string(r)) {
@@ -39,3 +66,42 @@ func (r RebootStrategy) Validate() report.Report {
return report.ReportFromError(ErrUnknownStrategy, report.EntryError)
}
}
func (s WindowStart) Validate() report.Report {
if s == "" {
return report.Report{}
}
var day string
var t string
_, err := fmt.Sscanf(string(s), "%s %s", &day, &t)
if err != nil {
day = "not-present"
t = string(s)
}
switch strings.ToLower(day) {
case "sun", "mon", "tue", "wed", "thu", "fri", "sat", "not-present":
break
default:
return report.ReportFromError(ErrUnknownDay, report.EntryError)
}
_, err = time.Parse("15:04", t)
if err != nil {
return report.ReportFromError(ErrParsingWindow, report.EntryError)
}
return report.Report{}
}
func (l WindowLength) Validate() report.Report {
if l == "" {
return report.Report{}
}
_, err := time.ParseDuration(string(l))
if err != nil {
return report.ReportFromError(ErrParsingLength, report.EntryError)
}
return report.Report{}
}

View File

@@ -16,6 +16,7 @@ package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -29,13 +30,13 @@ type NetworkdUnit struct {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
for _, unit := range in.Networkd.Units {
out.Networkd.Units = append(out.Networkd.Units, ignTypes.NetworkdUnit{
Name: ignTypes.NetworkdUnitName(unit.Name),
Contents: unit.Contents,
})
}
return out, report.Report{}
return out, report.Report{}, ast
})
}

View File

@@ -16,6 +16,7 @@ package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -52,8 +53,11 @@ type Group struct {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
for _, user := range in.Passwd.Users {
if user.Name != "core" && user.Create == nil {
user.Create = &UserCreate{}
}
newUser := ignTypes.User{
Name: user.Name,
PasswordHash: user.PasswordHash,
@@ -86,6 +90,6 @@ func init() {
System: group.System,
})
}
return out, report.Report{}
return out, report.Report{}, ast
})
}

View File

@@ -16,6 +16,7 @@ package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -27,7 +28,7 @@ type Raid struct {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
for _, array := range in.Storage.Arrays {
newArray := ignTypes.Raid{
Name: array.Name,
@@ -41,6 +42,6 @@ func init() {
out.Storage.Arrays = append(out.Storage.Arrays, newArray)
}
return out, report.Report{}
return out, report.Report{}, ast
})
}

View File

@@ -16,6 +16,7 @@ package types
import (
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
)
@@ -37,7 +38,7 @@ type SystemdUnitDropIn struct {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
for _, unit := range in.Systemd.Units {
newUnit := ignTypes.SystemdUnit{
Name: ignTypes.SystemdUnitName(unit.Name),
@@ -55,6 +56,6 @@ func init() {
out.Systemd.Units = append(out.Systemd.Units, newUnit)
}
return out, report.Report{}
return out, report.Report{}, ast
})
}

View File

@@ -21,6 +21,7 @@ import (
"strings"
ignTypes "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
@@ -58,7 +59,7 @@ func (s UpdateServer) Validate() report.Report {
}
func init() {
register2_0(func(in Config, out ignTypes.Config, platform string) (ignTypes.Config, report.Report) {
register2_0(func(in Config, ast validate.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, validate.AstNode) {
var contents string
if in.Update != nil {
if in.Update.Group != "" {
@@ -69,8 +70,9 @@ func init() {
}
}
if in.Locksmith != nil {
if in.Locksmith.RebootStrategy != "" {
contents += fmt.Sprintf("\nREBOOT_STRATEGY=%s", strings.ToLower(string(in.Locksmith.RebootStrategy)))
lines := in.Locksmith.configLines()
if len(lines) > 0 {
contents += "\n" + strings.Join(lines, "\n")
}
}
if contents != "" {
@@ -86,6 +88,6 @@ func init() {
},
})
}
return out, report.Report{}
return out, report.Report{}, ast
})
}

View File

@@ -0,0 +1,62 @@
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package util
type SystemdUnit struct {
Unit *UnitSection
Service *UnitSection
Install *UnitSection
}
func NewSystemdUnit() SystemdUnit {
return SystemdUnit{
Unit: &UnitSection{},
Service: &UnitSection{},
Install: &UnitSection{},
}
}
type UnitSection []string
func (u *UnitSection) Add(line string) {
*u = append(*u, line)
}
func (s SystemdUnit) String() string {
res := ""
type section struct {
name string
contents []string
}
for _, sec := range []section{
{"Unit", *s.Unit},
{"Service", *s.Service},
{"Install", *s.Install},
} {
if len(sec.contents) == 0 {
continue
}
if res != "" {
res += "\n\n"
}
res += "[" + sec.name + "]"
for _, line := range sec.contents {
res += "\n" + line
}
}
return res
}

View File

@@ -36,11 +36,10 @@ func Append(oldConfig, newConfig types.Config) types.Config {
// appendStruct is an internal helper function to AppendConfig. Given two values
// of structures (assumed to be the same type), recursively iterate over every
// field in the struct, appending slices, recursively appending structs, and
// overwriting old values with the new for all other types. Individual fields
// are able to override their merge strategy using the "merge" tag. Accepted
// values are "new" or "old": "new" uses the new value, "old" uses the old
// value. These are currently only used for "ignition.config" and
// "ignition.version".
// overwriting old values with the new for all other types. Some individual
// struct fields have alternate merge strategies, determined by the field name.
// Currently these fields are "ignition.version", which uses the old value, and
// "ignition.config" which uses the new value.
func appendStruct(vOld, vNew reflect.Value) reflect.Value {
tOld := vOld.Type()
vRes := reflect.New(tOld)
@@ -50,11 +49,11 @@ func appendStruct(vOld, vNew reflect.Value) reflect.Value {
vfNew := vNew.Field(i)
vfRes := vRes.Elem().Field(i)
switch tOld.Field(i).Tag.Get("merge") {
case "old":
switch tOld.Field(i).Name {
case "Version":
vfRes.Set(vfOld)
continue
case "new":
case "Config":
vfRes.Set(vfNew)
continue
}

View File

@@ -22,55 +22,69 @@ import (
"github.com/coreos/ignition/config/types"
"github.com/coreos/ignition/config/v1"
"github.com/coreos/ignition/config/v2_0"
"github.com/coreos/ignition/config/v2_1"
"github.com/coreos/ignition/config/validate"
astjson "github.com/coreos/ignition/config/validate/astjson"
"github.com/coreos/ignition/config/validate/report"
json "github.com/ajeddeloh/go-json"
"github.com/coreos/go-semver/semver"
"go4.org/errorutil"
)
var (
ErrCloudConfig = errors.New("not a config (found coreos-cloudconfig)")
ErrEmpty = errors.New("not a config (empty)")
ErrScript = errors.New("not a config (found coreos-cloudinit script)")
ErrDeprecated = errors.New("config format deprecated")
ErrInvalid = errors.New("config is not valid")
ErrCloudConfig = errors.New("not a config (found coreos-cloudconfig)")
ErrEmpty = errors.New("not a config (empty)")
ErrScript = errors.New("not a config (found coreos-cloudinit script)")
ErrDeprecated = errors.New("config format deprecated")
ErrInvalid = errors.New("config is not valid")
ErrUnknownVersion = errors.New("unsupported config version")
ErrVersionIndeterminable = errors.New("unable to determine version")
)
// Parse parses the raw config into a types.Config struct and generates a report of any
// errors, warnings, info, and deprecations it encountered
func Parse(rawConfig []byte) (types.Config, report.Report, error) {
switch version(rawConfig) {
case types.IgnitionVersion{Major: 1}:
version, err := Version(rawConfig)
if err != nil && err != ErrVersionIndeterminable {
// If we can't determine the version, ignore this error so that in the
// default case of the switch statement we can check for empty configs,
// cloud configs, and other such things.
return types.Config{}, report.ReportFromError(err, report.EntryError), err
}
switch version {
case semver.Version{Major: 1}:
config, err := ParseFromV1(rawConfig)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError), err
}
return config, report.ReportFromError(ErrDeprecated, report.EntryDeprecated), nil
case types.IgnitionVersion{Major: 2, Minor: 0}:
case types.MaxVersion:
return ParseFromLatest(rawConfig)
case semver.Version{Major: 2, Minor: 1}:
return ParseFromV2_1(rawConfig)
case semver.Version{Major: 2, Minor: 0}:
return ParseFromV2_0(rawConfig)
default:
return ParseFromLatest(rawConfig)
if isEmpty(rawConfig) {
return types.Config{}, report.Report{}, ErrEmpty
} else if isCloudConfig(rawConfig) {
return types.Config{}, report.Report{}, ErrCloudConfig
} else if isScript(rawConfig) {
return types.Config{}, report.Report{}, ErrScript
}
return types.Config{}, report.Report{}, ErrUnknownVersion
}
}
func ParseFromLatest(rawConfig []byte) (types.Config, report.Report, error) {
if isEmpty(rawConfig) {
return types.Config{}, report.Report{}, ErrEmpty
} else if isCloudConfig(rawConfig) {
return types.Config{}, report.Report{}, ErrCloudConfig
} else if isScript(rawConfig) {
return types.Config{}, report.Report{}, ErrScript
}
var err error
var config types.Config
// These errors are fatal and the config should not be further validated
if err = json.Unmarshal(rawConfig, &config); err == nil {
versionReport := config.Ignition.Version.Validate()
versionReport := config.Ignition.Validate()
if versionReport.IsFatal() {
return types.Config{}, versionReport, ErrInvalid
}
@@ -123,7 +137,7 @@ func ParseFromLatest(rawConfig []byte) (types.Config, report.Report, error) {
})
r.Merge(validate.ValidateWithoutSource(configValue))
} else {
r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig)))
r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig), true))
}
if r.IsFatal() {
@@ -151,23 +165,36 @@ func ParseFromV2_0(rawConfig []byte) (types.Config, report.Report, error) {
return TranslateFromV2_0(cfg), report, err
}
func version(rawConfig []byte) types.IgnitionVersion {
func ParseFromV2_1(rawConfig []byte) (types.Config, report.Report, error) {
cfg, report, err := v2_1.Parse(rawConfig)
if err != nil {
return types.Config{}, report, err
}
return TranslateFromV2_1(cfg), report, err
}
func Version(rawConfig []byte) (semver.Version, error) {
var composite struct {
Version *int `json:"ignitionVersion"`
Ignition struct {
Version *types.IgnitionVersion `json:"version"`
Version *string `json:"version"`
} `json:"ignition"`
}
if json.Unmarshal(rawConfig, &composite) == nil {
if composite.Ignition.Version != nil {
return *composite.Ignition.Version
v, err := types.Ignition{Version: *composite.Ignition.Version}.Semver()
if err != nil {
return semver.Version{}, err
}
return *v, nil
} else if composite.Version != nil {
return types.IgnitionVersion{Major: int64(*composite.Version)}
return semver.Version{Major: int64(*composite.Version)}, nil
}
}
return types.IgnitionVersion{}
return semver.Version{}, ErrVersionIndeterminable
}
func isEmpty(userdata []byte) bool {

View File

@@ -16,34 +16,52 @@ package config
import (
"fmt"
"net/url"
"strings"
"github.com/coreos/ignition/config/types"
v1 "github.com/coreos/ignition/config/v1/types"
v2_0 "github.com/coreos/ignition/config/v2_0/types"
v2_1 "github.com/coreos/ignition/config/v2_1/types"
"github.com/vincent-petithory/dataurl"
)
func intToPtr(x int) *int {
return &x
}
func strToPtr(s string) *string {
if s == "" {
return nil
}
return &s
}
func boolToPtr(b bool) *bool {
return &b
}
func TranslateFromV1(old v1.Config) types.Config {
config := types.Config{
Ignition: types.Ignition{
Version: types.IgnitionVersion(v2_0.MaxVersion),
Version: v2_0.MaxVersion.String(),
},
}
for _, oldDisk := range old.Storage.Disks {
disk := types.Disk{
Device: types.Path(oldDisk.Device),
Device: string(oldDisk.Device),
WipeTable: oldDisk.WipeTable,
}
for _, oldPartition := range oldDisk.Partitions {
disk.Partitions = append(disk.Partitions, types.Partition{
Label: types.PartitionLabel(oldPartition.Label),
Label: string(oldPartition.Label),
Number: oldPartition.Number,
Size: types.PartitionDimension(oldPartition.Size),
Start: types.PartitionDimension(oldPartition.Start),
TypeGUID: types.PartitionTypeGUID(oldPartition.TypeGUID),
Size: int(oldPartition.Size),
Start: int(oldPartition.Start),
TypeGUID: string(oldPartition.TypeGUID),
})
}
@@ -58,25 +76,25 @@ func TranslateFromV1(old v1.Config) types.Config {
}
for _, oldDevice := range oldArray.Devices {
array.Devices = append(array.Devices, types.Path(oldDevice))
array.Devices = append(array.Devices, types.Device(oldDevice))
}
config.Storage.Arrays = append(config.Storage.Arrays, array)
config.Storage.Raid = append(config.Storage.Raid, array)
}
for i, oldFilesystem := range old.Storage.Filesystems {
filesystem := types.Filesystem{
Name: fmt.Sprintf("_translate-filesystem-%d", i),
Mount: &types.FilesystemMount{
Device: types.Path(oldFilesystem.Device),
Format: types.FilesystemFormat(oldFilesystem.Format),
Mount: &types.Mount{
Device: string(oldFilesystem.Device),
Format: string(oldFilesystem.Format),
},
}
if oldFilesystem.Create != nil {
filesystem.Mount.Create = &types.FilesystemCreate{
filesystem.Mount.Create = &types.Create{
Force: oldFilesystem.Create.Force,
Options: types.MkfsOptions(oldFilesystem.Create.Options),
Options: translateV1MkfsOptionsToV2_2OptionSlice(oldFilesystem.Create.Options),
}
}
@@ -86,15 +104,17 @@ func TranslateFromV1(old v1.Config) types.Config {
file := types.File{
Node: types.Node{
Filesystem: filesystem.Name,
Path: types.Path(oldFile.Path),
Mode: types.NodeMode(oldFile.Mode),
User: types.NodeUser{Id: oldFile.Uid},
Group: types.NodeGroup{Id: oldFile.Gid},
Path: string(oldFile.Path),
User: types.NodeUser{ID: intToPtr(oldFile.Uid)},
Group: types.NodeGroup{ID: intToPtr(oldFile.Gid)},
},
Contents: types.FileContents{
Source: types.Url{
Scheme: "data",
Opaque: "," + dataurl.EscapeString(oldFile.Contents),
FileEmbedded1: types.FileEmbedded1{
Mode: int(oldFile.Mode),
Contents: types.FileContents{
Source: (&url.URL{
Scheme: "data",
Opaque: "," + dataurl.EscapeString(oldFile.Contents),
}).String(),
},
},
}
@@ -104,16 +124,16 @@ func TranslateFromV1(old v1.Config) types.Config {
}
for _, oldUnit := range old.Systemd.Units {
unit := types.SystemdUnit{
Name: types.SystemdUnitName(oldUnit.Name),
unit := types.Unit{
Name: string(oldUnit.Name),
Enable: oldUnit.Enable,
Mask: oldUnit.Mask,
Contents: oldUnit.Contents,
}
for _, oldDropIn := range oldUnit.DropIns {
unit.DropIns = append(unit.DropIns, types.SystemdUnitDropIn{
Name: types.SystemdUnitDropInName(oldDropIn.Name),
unit.Dropins = append(unit.Dropins, types.Dropin{
Name: string(oldDropIn.Name),
Contents: oldDropIn.Contents,
})
}
@@ -122,27 +142,33 @@ func TranslateFromV1(old v1.Config) types.Config {
}
for _, oldUnit := range old.Networkd.Units {
config.Networkd.Units = append(config.Networkd.Units, types.NetworkdUnit{
Name: types.NetworkdUnitName(oldUnit.Name),
config.Networkd.Units = append(config.Networkd.Units, types.Networkdunit{
Name: string(oldUnit.Name),
Contents: oldUnit.Contents,
})
}
for _, oldUser := range old.Passwd.Users {
user := types.User{
user := types.PasswdUser{
Name: oldUser.Name,
PasswordHash: oldUser.PasswordHash,
SSHAuthorizedKeys: oldUser.SSHAuthorizedKeys,
PasswordHash: strToPtr(oldUser.PasswordHash),
SSHAuthorizedKeys: translateStringSliceToV2_2SSHAuthorizedKeySlice(oldUser.SSHAuthorizedKeys),
}
if oldUser.Create != nil {
user.Create = &types.UserCreate{
Uid: oldUser.Create.Uid,
GECOS: oldUser.Create.GECOS,
Homedir: oldUser.Create.Homedir,
var uid *int
if oldUser.Create.Uid != nil {
tmp := int(*oldUser.Create.Uid)
uid = &tmp
}
user.Create = &types.Usercreate{
UID: uid,
Gecos: oldUser.Create.GECOS,
HomeDir: oldUser.Create.Homedir,
NoCreateHome: oldUser.Create.NoCreateHome,
PrimaryGroup: oldUser.Create.PrimaryGroup,
Groups: oldUser.Create.Groups,
Groups: translateStringSliceToV2_2UsercreateGroupSlice(oldUser.Create.Groups),
NoUserGroup: oldUser.Create.NoUserGroup,
System: oldUser.Create.System,
NoLogInit: oldUser.Create.NoLogInit,
@@ -154,9 +180,14 @@ func TranslateFromV1(old v1.Config) types.Config {
}
for _, oldGroup := range old.Passwd.Groups {
config.Passwd.Groups = append(config.Passwd.Groups, types.Group{
var gid *int
if oldGroup.Gid != nil {
tmp := int(*oldGroup.Gid)
gid = &tmp
}
config.Passwd.Groups = append(config.Passwd.Groups, types.PasswdGroup{
Name: oldGroup.Name,
Gid: oldGroup.Gid,
Gid: gid,
PasswordHash: oldGroup.PasswordHash,
System: oldGroup.System,
})
@@ -165,25 +196,53 @@ func TranslateFromV1(old v1.Config) types.Config {
return config
}
// golang--
func translateV1MkfsOptionsToV2_2OptionSlice(opts v1.MkfsOptions) []types.CreateOption {
newOpts := make([]types.CreateOption, len(opts))
for i, o := range opts {
newOpts[i] = types.CreateOption(o)
}
return newOpts
}
// golang--
func translateStringSliceToV2_2SSHAuthorizedKeySlice(keys []string) []types.SSHAuthorizedKey {
newKeys := make([]types.SSHAuthorizedKey, len(keys))
for i, k := range keys {
newKeys[i] = types.SSHAuthorizedKey(k)
}
return newKeys
}
// golang--
func translateStringSliceToV2_2UsercreateGroupSlice(groups []string) []types.UsercreateGroup {
var newGroups []types.UsercreateGroup
for _, g := range groups {
newGroups = append(newGroups, types.UsercreateGroup(g))
}
return newGroups
}
func TranslateFromV2_0(old v2_0.Config) types.Config {
translateVerification := func(old v2_0.Verification) types.Verification {
var ver types.Verification
if old.Hash != nil {
h := types.Hash(*old.Hash)
// .String() here is a wrapper around MarshalJSON, which will put the hash in quotes
h := strings.Trim(old.Hash.String(), "\"")
ver.Hash = &h
}
return ver
}
translateConfigReference := func(old v2_0.ConfigReference) types.ConfigReference {
return types.ConfigReference{
Source: types.Url(old.Source),
Source: old.Source.String(),
Verification: translateVerification(old.Verification),
}
}
config := types.Config{
Ignition: types.Ignition{
Version: types.IgnitionVersion(types.MaxVersion),
Version: types.MaxVersion.String(),
},
}
@@ -199,17 +258,17 @@ func TranslateFromV2_0(old v2_0.Config) types.Config {
for _, oldDisk := range old.Storage.Disks {
disk := types.Disk{
Device: types.Path(oldDisk.Device),
Device: string(oldDisk.Device),
WipeTable: oldDisk.WipeTable,
}
for _, oldPartition := range oldDisk.Partitions {
disk.Partitions = append(disk.Partitions, types.Partition{
Label: types.PartitionLabel(oldPartition.Label),
Label: string(oldPartition.Label),
Number: oldPartition.Number,
Size: types.PartitionDimension(oldPartition.Size),
Start: types.PartitionDimension(oldPartition.Start),
TypeGUID: types.PartitionTypeGUID(oldPartition.TypeGUID),
Size: int(oldPartition.Size),
Start: int(oldPartition.Start),
TypeGUID: string(oldPartition.TypeGUID),
})
}
@@ -224,10 +283,10 @@ func TranslateFromV2_0(old v2_0.Config) types.Config {
}
for _, oldDevice := range oldArray.Devices {
array.Devices = append(array.Devices, types.Path(oldDevice))
array.Devices = append(array.Devices, types.Device(oldDevice))
}
config.Storage.Arrays = append(config.Storage.Arrays, array)
config.Storage.Raid = append(config.Storage.Raid, array)
}
for _, oldFilesystem := range old.Storage.Filesystems {
@@ -236,22 +295,22 @@ func TranslateFromV2_0(old v2_0.Config) types.Config {
}
if oldFilesystem.Mount != nil {
filesystem.Mount = &types.FilesystemMount{
Device: types.Path(oldFilesystem.Mount.Device),
Format: types.FilesystemFormat(oldFilesystem.Mount.Format),
filesystem.Mount = &types.Mount{
Device: string(oldFilesystem.Mount.Device),
Format: string(oldFilesystem.Mount.Format),
}
if oldFilesystem.Mount.Create != nil {
filesystem.Mount.Create = &types.FilesystemCreate{
filesystem.Mount.Create = &types.Create{
Force: oldFilesystem.Mount.Create.Force,
Options: types.MkfsOptions(oldFilesystem.Mount.Create.Options),
Options: translateV2_0MkfsOptionsToV2_2OptionSlice(oldFilesystem.Mount.Create.Options),
}
}
}
if oldFilesystem.Path != nil {
path := types.Path(*oldFilesystem.Path)
filesystem.Path = &path
p := string(*oldFilesystem.Path)
filesystem.Path = &p
}
config.Storage.Filesystems = append(config.Storage.Filesystems, filesystem)
@@ -261,15 +320,17 @@ func TranslateFromV2_0(old v2_0.Config) types.Config {
file := types.File{
Node: types.Node{
Filesystem: oldFile.Filesystem,
Path: types.Path(oldFile.Path),
Mode: types.NodeMode(oldFile.Mode),
User: types.NodeUser{Id: oldFile.User.Id},
Group: types.NodeGroup{Id: oldFile.Group.Id},
Path: string(oldFile.Path),
User: types.NodeUser{ID: intToPtr(oldFile.User.Id)},
Group: types.NodeGroup{ID: intToPtr(oldFile.Group.Id)},
},
Contents: types.FileContents{
Compression: types.Compression(oldFile.Contents.Compression),
Source: types.Url(oldFile.Contents.Source),
Verification: translateVerification(oldFile.Contents.Verification),
FileEmbedded1: types.FileEmbedded1{
Mode: int(oldFile.Mode),
Contents: types.FileContents{
Compression: string(oldFile.Contents.Compression),
Source: oldFile.Contents.Source.String(),
Verification: translateVerification(oldFile.Contents.Verification),
},
},
}
@@ -277,16 +338,16 @@ func TranslateFromV2_0(old v2_0.Config) types.Config {
}
for _, oldUnit := range old.Systemd.Units {
unit := types.SystemdUnit{
Name: types.SystemdUnitName(oldUnit.Name),
unit := types.Unit{
Name: string(oldUnit.Name),
Enable: oldUnit.Enable,
Mask: oldUnit.Mask,
Contents: oldUnit.Contents,
}
for _, oldDropIn := range oldUnit.DropIns {
unit.DropIns = append(unit.DropIns, types.SystemdUnitDropIn{
Name: types.SystemdUnitDropInName(oldDropIn.Name),
unit.Dropins = append(unit.Dropins, types.Dropin{
Name: string(oldDropIn.Name),
Contents: oldDropIn.Contents,
})
}
@@ -295,27 +356,32 @@ func TranslateFromV2_0(old v2_0.Config) types.Config {
}
for _, oldUnit := range old.Networkd.Units {
config.Networkd.Units = append(config.Networkd.Units, types.NetworkdUnit{
Name: types.NetworkdUnitName(oldUnit.Name),
config.Networkd.Units = append(config.Networkd.Units, types.Networkdunit{
Name: string(oldUnit.Name),
Contents: oldUnit.Contents,
})
}
for _, oldUser := range old.Passwd.Users {
user := types.User{
user := types.PasswdUser{
Name: oldUser.Name,
PasswordHash: oldUser.PasswordHash,
SSHAuthorizedKeys: oldUser.SSHAuthorizedKeys,
PasswordHash: strToPtr(oldUser.PasswordHash),
SSHAuthorizedKeys: translateStringSliceToV2_2SSHAuthorizedKeySlice(oldUser.SSHAuthorizedKeys),
}
if oldUser.Create != nil {
user.Create = &types.UserCreate{
Uid: oldUser.Create.Uid,
GECOS: oldUser.Create.GECOS,
Homedir: oldUser.Create.Homedir,
var u *int
if oldUser.Create.Uid != nil {
tmp := int(*oldUser.Create.Uid)
u = &tmp
}
user.Create = &types.Usercreate{
UID: u,
Gecos: oldUser.Create.GECOS,
HomeDir: oldUser.Create.Homedir,
NoCreateHome: oldUser.Create.NoCreateHome,
PrimaryGroup: oldUser.Create.PrimaryGroup,
Groups: oldUser.Create.Groups,
Groups: translateStringSliceToV2_2UsercreateGroupSlice(oldUser.Create.Groups),
NoUserGroup: oldUser.Create.NoUserGroup,
System: oldUser.Create.System,
NoLogInit: oldUser.Create.NoLogInit,
@@ -327,9 +393,14 @@ func TranslateFromV2_0(old v2_0.Config) types.Config {
}
for _, oldGroup := range old.Passwd.Groups {
config.Passwd.Groups = append(config.Passwd.Groups, types.Group{
var g *int
if oldGroup.Gid != nil {
tmp := int(*oldGroup.Gid)
g = &tmp
}
config.Passwd.Groups = append(config.Passwd.Groups, types.PasswdGroup{
Name: oldGroup.Name,
Gid: oldGroup.Gid,
Gid: g,
PasswordHash: oldGroup.PasswordHash,
System: oldGroup.System,
})
@@ -337,3 +408,327 @@ func TranslateFromV2_0(old v2_0.Config) types.Config {
return config
}
// golang--
func translateV2_0MkfsOptionsToV2_2OptionSlice(opts v2_0.MkfsOptions) []types.CreateOption {
newOpts := make([]types.CreateOption, len(opts))
for i, o := range opts {
newOpts[i] = types.CreateOption(o)
}
return newOpts
}
func TranslateFromV2_1(old v2_1.Config) types.Config {
translateConfigReference := func(old *v2_1.ConfigReference) *types.ConfigReference {
if old == nil {
return nil
}
return &types.ConfigReference{
Source: old.Source,
Verification: types.Verification{
Hash: old.Verification.Hash,
},
}
}
translateConfigReferenceSlice := func(old []v2_1.ConfigReference) []types.ConfigReference {
var res []types.ConfigReference
for _, c := range old {
res = append(res, *translateConfigReference(&c))
}
return res
}
translateNetworkdUnitSlice := func(old []v2_1.Networkdunit) []types.Networkdunit {
var res []types.Networkdunit
for _, u := range old {
res = append(res, types.Networkdunit{
Contents: u.Contents,
Name: u.Name,
})
}
return res
}
translatePasswdGroupSlice := func(old []v2_1.PasswdGroup) []types.PasswdGroup {
var res []types.PasswdGroup
for _, g := range old {
res = append(res, types.PasswdGroup{
Gid: g.Gid,
Name: g.Name,
PasswordHash: g.PasswordHash,
System: g.System,
})
}
return res
}
translatePasswdUsercreateGroupSlice := func(old []v2_1.UsercreateGroup) []types.UsercreateGroup {
var res []types.UsercreateGroup
for _, g := range old {
res = append(res, types.UsercreateGroup(g))
}
return res
}
translatePasswdUsercreate := func(old *v2_1.Usercreate) *types.Usercreate {
if old == nil {
return nil
}
return &types.Usercreate{
Gecos: old.Gecos,
Groups: translatePasswdUsercreateGroupSlice(old.Groups),
HomeDir: old.HomeDir,
NoCreateHome: old.NoCreateHome,
NoLogInit: old.NoLogInit,
NoUserGroup: old.NoUserGroup,
PrimaryGroup: old.PrimaryGroup,
Shell: old.Shell,
System: old.System,
UID: old.UID,
}
}
translatePasswdUserGroupSlice := func(old []v2_1.PasswdUserGroup) []types.PasswdUserGroup {
var res []types.PasswdUserGroup
for _, g := range old {
res = append(res, types.PasswdUserGroup(g))
}
return res
}
translatePasswdSSHAuthorizedKeySlice := func(old []v2_1.SSHAuthorizedKey) []types.SSHAuthorizedKey {
res := make([]types.SSHAuthorizedKey, len(old))
for i, k := range old {
res[i] = types.SSHAuthorizedKey(k)
}
return res
}
translatePasswdUserSlice := func(old []v2_1.PasswdUser) []types.PasswdUser {
var res []types.PasswdUser
for _, u := range old {
res = append(res, types.PasswdUser{
Create: translatePasswdUsercreate(u.Create),
Gecos: u.Gecos,
Groups: translatePasswdUserGroupSlice(u.Groups),
HomeDir: u.HomeDir,
Name: u.Name,
NoCreateHome: u.NoCreateHome,
NoLogInit: u.NoLogInit,
NoUserGroup: u.NoUserGroup,
PasswordHash: u.PasswordHash,
PrimaryGroup: u.PrimaryGroup,
SSHAuthorizedKeys: translatePasswdSSHAuthorizedKeySlice(u.SSHAuthorizedKeys),
Shell: u.Shell,
System: u.System,
UID: u.UID,
})
}
return res
}
translateNodeGroup := func(old v2_1.NodeGroup) types.NodeGroup {
return types.NodeGroup{
ID: old.ID,
Name: old.Name,
}
}
translateNodeUser := func(old v2_1.NodeUser) types.NodeUser {
return types.NodeUser{
ID: old.ID,
Name: old.Name,
}
}
translateNode := func(old v2_1.Node) types.Node {
return types.Node{
Filesystem: old.Filesystem,
Group: translateNodeGroup(old.Group),
Path: old.Path,
User: translateNodeUser(old.User),
}
}
translateDirectorySlice := func(old []v2_1.Directory) []types.Directory {
var res []types.Directory
for _, x := range old {
res = append(res, types.Directory{
Node: translateNode(x.Node),
DirectoryEmbedded1: types.DirectoryEmbedded1{
Mode: x.DirectoryEmbedded1.Mode,
},
})
}
return res
}
translatePartitionSlice := func(old []v2_1.Partition) []types.Partition {
var res []types.Partition
for _, x := range old {
res = append(res, types.Partition{
GUID: x.GUID,
Label: x.Label,
Number: x.Number,
Size: x.Size,
Start: x.Start,
TypeGUID: x.TypeGUID,
})
}
return res
}
translateDiskSlice := func(old []v2_1.Disk) []types.Disk {
var res []types.Disk
for _, x := range old {
res = append(res, types.Disk{
Device: x.Device,
Partitions: translatePartitionSlice(x.Partitions),
WipeTable: x.WipeTable,
})
}
return res
}
translateFileSlice := func(old []v2_1.File) []types.File {
var res []types.File
for _, x := range old {
res = append(res, types.File{
Node: translateNode(x.Node),
FileEmbedded1: types.FileEmbedded1{
Contents: types.FileContents{
Compression: x.Contents.Compression,
Source: x.Contents.Source,
Verification: types.Verification{
Hash: x.Contents.Verification.Hash,
},
},
Mode: x.Mode,
},
})
}
return res
}
translateMountCreateOptionSlice := func(old []v2_1.CreateOption) []types.CreateOption {
var res []types.CreateOption
for _, x := range old {
res = append(res, types.CreateOption(x))
}
return res
}
translateMountCreate := func(old *v2_1.Create) *types.Create {
if old == nil {
return nil
}
return &types.Create{
Force: old.Force,
Options: translateMountCreateOptionSlice(old.Options),
}
}
translateMountOptionSlice := func(old []v2_1.MountOption) []types.MountOption {
var res []types.MountOption
for _, x := range old {
res = append(res, types.MountOption(x))
}
return res
}
translateMount := func(old *v2_1.Mount) *types.Mount {
if old == nil {
return nil
}
return &types.Mount{
Create: translateMountCreate(old.Create),
Device: old.Device,
Format: old.Format,
Label: old.Label,
Options: translateMountOptionSlice(old.Options),
UUID: old.UUID,
WipeFilesystem: old.WipeFilesystem,
}
}
translateFilesystemSlice := func(old []v2_1.Filesystem) []types.Filesystem {
var res []types.Filesystem
for _, x := range old {
res = append(res, types.Filesystem{
Mount: translateMount(x.Mount),
Name: x.Name,
Path: x.Path,
})
}
return res
}
translateLinkSlice := func(old []v2_1.Link) []types.Link {
var res []types.Link
for _, x := range old {
res = append(res, types.Link{
Node: translateNode(x.Node),
LinkEmbedded1: types.LinkEmbedded1{
Hard: x.Hard,
Target: x.Target,
},
})
}
return res
}
translateDeviceSlice := func(old []v2_1.Device) []types.Device {
var res []types.Device
for _, x := range old {
res = append(res, types.Device(x))
}
return res
}
translateRaidSlice := func(old []v2_1.Raid) []types.Raid {
var res []types.Raid
for _, x := range old {
res = append(res, types.Raid{
Devices: translateDeviceSlice(x.Devices),
Level: x.Level,
Name: x.Name,
Spares: x.Spares,
})
}
return res
}
translateSystemdDropinSlice := func(old []v2_1.Dropin) []types.Dropin {
var res []types.Dropin
for _, x := range old {
res = append(res, types.Dropin{
Contents: x.Contents,
Name: x.Name,
})
}
return res
}
translateSystemdUnitSlice := func(old []v2_1.Unit) []types.Unit {
var res []types.Unit
for _, x := range old {
res = append(res, types.Unit{
Contents: x.Contents,
Dropins: translateSystemdDropinSlice(x.Dropins),
Enable: x.Enable,
Enabled: x.Enabled,
Mask: x.Mask,
Name: x.Name,
})
}
return res
}
config := types.Config{
Ignition: types.Ignition{
Version: types.MaxVersion.String(),
Timeouts: types.Timeouts{
HTTPResponseHeaders: old.Ignition.Timeouts.HTTPResponseHeaders,
HTTPTotal: old.Ignition.Timeouts.HTTPTotal,
},
Config: types.IgnitionConfig{
Replace: translateConfigReference(old.Ignition.Config.Replace),
Append: translateConfigReferenceSlice(old.Ignition.Config.Append),
},
},
Networkd: types.Networkd{
Units: translateNetworkdUnitSlice(old.Networkd.Units),
},
Passwd: types.Passwd{
Groups: translatePasswdGroupSlice(old.Passwd.Groups),
Users: translatePasswdUserSlice(old.Passwd.Users),
},
Storage: types.Storage{
Directories: translateDirectorySlice(old.Storage.Directories),
Disks: translateDiskSlice(old.Storage.Disks),
Files: translateFileSlice(old.Storage.Files),
Filesystems: translateFilesystemSlice(old.Storage.Filesystems),
Links: translateLinkSlice(old.Storage.Links),
Raid: translateRaidSlice(old.Storage.Raid),
},
Systemd: types.Systemd{
Units: translateSystemdUnitSlice(old.Systemd.Units),
},
}
return config
}

View File

@@ -25,19 +25,11 @@ import (
var (
MaxVersion = semver.Version{
Major: 2,
Minor: 1,
Minor: 2,
PreRelease: "experimental",
}
)
type Config struct {
Ignition Ignition `json:"ignition"`
Storage Storage `json:"storage,omitempty"`
Systemd Systemd `json:"systemd,omitempty"`
Networkd Networkd `json:"networkd,omitempty"`
Passwd Passwd `json:"passwd,omitempty"`
}
func (c Config) Validate() report.Report {
r := report.Report{}
rules := []rule{

View File

@@ -1,4 +1,4 @@
// Copyright 2016 CoreOS, Inc.
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,15 +15,16 @@
package types
import (
"path"
"github.com/coreos/ignition/config/validate/report"
)
type Directory Node
func (d *Directory) Depth() int {
count := 0
for p := path.Clean(string(d.Path)); p != "/"; count++ {
p = path.Dir(p)
func (d Directory) ValidateMode() report.Report {
r := report.Report{}
if err := validateMode(d.Mode); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return count
return r
}

View File

@@ -20,20 +20,22 @@ import (
"github.com/coreos/ignition/config/validate/report"
)
type Disk struct {
Device Path `json:"device,omitempty"`
WipeTable bool `json:"wipeTable,omitempty"`
Partitions []Partition `json:"partitions,omitempty"`
func (n Disk) Validate() report.Report {
return report.Report{}
}
func (n Disk) Validate() report.Report {
r := report.Report{}
func (n Disk) ValidateDevice() report.Report {
if len(n.Device) == 0 {
r.Add(report.Entry{
Message: "disk device is required",
Kind: report.EntryError,
})
return report.ReportFromError(fmt.Errorf("disk device is required"), report.EntryError)
}
if err := validatePath(string(n.Device)); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
func (n Disk) ValidatePartitions() report.Report {
r := report.Report{}
if n.partitionNumbersCollide() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partition numbers collide", n.Device),
@@ -72,7 +74,7 @@ func (n Disk) partitionNumbersCollide() bool {
}
// end returns the last sector of a partition.
func (p Partition) end() PartitionDimension {
func (p Partition) end() int {
if p.Size == 0 {
// a size of 0 means "fill available", just return the start as the end for those.
return p.Start

View File

@@ -14,14 +14,49 @@
package types
// File represents regular files
type File struct {
Node
Contents FileContents `json:"contents,omitempty"`
import (
"errors"
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrCompressionInvalid = errors.New("invalid compression method")
)
func (f File) ValidateMode() report.Report {
r := report.Report{}
if err := validateMode(f.Mode); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return r
}
type FileContents struct {
Compression Compression `json:"compression,omitempty"`
Source Url `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
func (fc FileContents) ValidateCompression() report.Report {
r := report.Report{}
switch fc.Compression {
case "", "gzip":
default:
r.Add(report.Entry{
Message: ErrCompressionInvalid.Error(),
Kind: report.EntryError,
})
}
return r
}
func (fc FileContents) ValidateSource() report.Report {
r := report.Report{}
err := validateURL(fc.Source)
if err != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("invalid url %q: %v", fc.Source, err),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -16,52 +16,145 @@ package types
import (
"errors"
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrFilesystemInvalidFormat = errors.New("invalid filesystem format")
ErrFilesystemNoMountPath = errors.New("filesystem is missing mount or path")
ErrFilesystemMountAndPath = errors.New("filesystem has both mount and path defined")
ErrFilesystemInvalidFormat = errors.New("invalid filesystem format")
ErrFilesystemNoMountPath = errors.New("filesystem is missing mount or path")
ErrFilesystemMountAndPath = errors.New("filesystem has both mount and path defined")
ErrUsedCreateAndMountOpts = errors.New("cannot use both create object and mount-level options field")
ErrUsedCreateAndWipeFilesystem = errors.New("cannot use both create object and wipeFilesystem field")
ErrWarningCreateDeprecated = errors.New("the create object has been deprecated in favor of mount-level options")
ErrExt4LabelTooLong = errors.New("filesystem labels cannot be longer than 16 characters when using ext4")
ErrBtrfsLabelTooLong = errors.New("filesystem labels cannot be longer than 256 characters when using btrfs")
ErrXfsLabelTooLong = errors.New("filesystem labels cannot be longer than 12 characters when using xfs")
ErrSwapLabelTooLong = errors.New("filesystem labels cannot be longer than 15 characters when using swap")
ErrVfatLabelTooLong = errors.New("filesystem labels cannot be longer than 11 characters when using vfat")
)
type Filesystem struct {
Name string `json:"name,omitempty"`
Mount *FilesystemMount `json:"mount,omitempty"`
Path *Path `json:"path,omitempty"`
}
type FilesystemMount struct {
Device Path `json:"device,omitempty"`
Format FilesystemFormat `json:"format,omitempty"`
Create *FilesystemCreate `json:"create,omitempty"`
}
type FilesystemCreate struct {
Force bool `json:"force,omitempty"`
Options MkfsOptions `json:"options,omitempty"`
}
func (f Filesystem) Validate() report.Report {
r := report.Report{}
if f.Mount == nil && f.Path == nil {
return report.ReportFromError(ErrFilesystemNoMountPath, report.EntryError)
r.Add(report.Entry{
Message: ErrFilesystemNoMountPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount != nil && f.Path != nil {
return report.ReportFromError(ErrFilesystemMountAndPath, report.EntryError)
if f.Mount != nil {
if f.Path != nil {
r.Add(report.Entry{
Message: ErrFilesystemMountAndPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount.Create != nil {
if f.Mount.WipeFilesystem {
r.Add(report.Entry{
Message: ErrUsedCreateAndWipeFilesystem.Error(),
Kind: report.EntryError,
})
}
if len(f.Mount.Options) > 0 {
r.Add(report.Entry{
Message: ErrUsedCreateAndMountOpts.Error(),
Kind: report.EntryError,
})
}
r.Add(report.Entry{
Message: ErrWarningCreateDeprecated.Error(),
Kind: report.EntryWarning,
})
}
}
return report.Report{}
return r
}
type FilesystemFormat string
func (f Filesystem) ValidatePath() report.Report {
r := report.Report{}
if f.Path != nil && validatePath(*f.Path) != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("filesystem %q: path not absolute", f.Name),
Kind: report.EntryError,
})
}
return r
}
func (f FilesystemFormat) Validate() report.Report {
switch f {
case "ext4", "btrfs", "xfs":
return report.Report{}
func (m Mount) Validate() report.Report {
r := report.Report{}
switch m.Format {
case "ext4", "btrfs", "xfs", "swap", "vfat":
default:
return report.ReportFromError(ErrFilesystemInvalidFormat, report.EntryError)
r.Add(report.Entry{
Message: ErrFilesystemInvalidFormat.Error(),
Kind: report.EntryError,
})
}
return r
}
type MkfsOptions []string
func (m Mount) ValidateDevice() report.Report {
r := report.Report{}
if err := validatePath(m.Device); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return r
}
func (m Mount) ValidateLabel() report.Report {
r := report.Report{}
if m.Label == nil {
return r
}
switch m.Format {
case "ext4":
if len(*m.Label) > 16 {
// source: man mkfs.ext4
r.Add(report.Entry{
Message: ErrExt4LabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "btrfs":
if len(*m.Label) > 256 {
// source: man mkfs.btrfs
r.Add(report.Entry{
Message: ErrBtrfsLabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "xfs":
if len(*m.Label) > 12 {
// source: man mkfs.xfs
r.Add(report.Entry{
Message: ErrXfsLabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "swap":
// mkswap's man page does not state a limit on label size, but through
// experimentation it appears that mkswap will truncate long labels to
// 15 characters, so let's enforce that.
if len(*m.Label) > 15 {
r.Add(report.Entry{
Message: ErrSwapLabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "vfat":
if len(*m.Label) > 11 {
// source: man mkfs.fat
r.Add(report.Entry{
Message: ErrVfatLabelTooLong.Error(),
Kind: report.EntryError,
})
}
}
return r
}

View File

@@ -15,7 +15,6 @@
package types
import (
"encoding/json"
"errors"
"github.com/coreos/go-semver/semver"
@@ -24,46 +23,36 @@ import (
)
var (
ErrOldVersion = errors.New("incorrect config version (too old)")
ErrNewVersion = errors.New("incorrect config version (too new)")
ErrOldVersion = errors.New("incorrect config version (too old)")
ErrNewVersion = errors.New("incorrect config version (too new)")
ErrInvalidVersion = errors.New("invalid config version (couldn't parse)")
)
type Ignition struct {
Version IgnitionVersion `json:"version,omitempty" merge:"old"`
Config IgnitionConfig `json:"config,omitempty" merge:"new"`
Timeouts Timeouts `json:"timeouts,omitempty" merge:"new"`
}
type IgnitionConfig struct {
Append []ConfigReference `json:"append,omitempty"`
Replace *ConfigReference `json:"replace,omitempty"`
}
type ConfigReference struct {
Source Url `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
}
type IgnitionVersion semver.Version
func (v *IgnitionVersion) UnmarshalJSON(data []byte) error {
tv := semver.Version(*v)
if err := json.Unmarshal(data, &tv); err != nil {
return err
func (c ConfigReference) ValidateSource() report.Report {
r := report.Report{}
err := validateURL(c.Source)
if err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
*v = IgnitionVersion(tv)
return nil
return r
}
func (v IgnitionVersion) MarshalJSON() ([]byte, error) {
return semver.Version(v).MarshalJSON()
func (v Ignition) Semver() (*semver.Version, error) {
return semver.NewVersion(v.Version)
}
func (v IgnitionVersion) Validate() report.Report {
if MaxVersion.Major > v.Major {
func (v Ignition) Validate() report.Report {
tv, err := v.Semver()
if err != nil {
return report.ReportFromError(ErrInvalidVersion, report.EntryError)
}
if MaxVersion.Major > tv.Major {
return report.ReportFromError(ErrOldVersion, report.EntryError)
}
if MaxVersion.LessThan(semver.Version(v)) {
if MaxVersion.LessThan(*tv) {
return report.ReportFromError(ErrNewVersion, report.EntryError)
}
return report.Report{}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 CoreOS, Inc.
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -15,22 +15,21 @@
package types
import (
"errors"
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrCompressionInvalid = errors.New("invalid compression method")
)
type Compression string
func (c Compression) Validate() report.Report {
switch c {
case "", "gzip":
default:
return report.ReportFromError(ErrCompressionInvalid, report.EntryError)
func (s Link) Validate() report.Report {
r := report.Report{}
if !s.Hard {
err := validatePath(s.Target)
if err != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("problem with target path %q: %v", s.Target, err),
Kind: report.EntryError,
})
}
}
return report.Report{}
return r
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 CoreOS, Inc.
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,6 +14,17 @@
package types
type Networkd struct {
Units []NetworkdUnit `json:"units,omitempty"`
import (
"errors"
)
var (
ErrFileIllegalMode = errors.New("illegal file mode")
)
func validateMode(m int) error {
if m < 0 || m > 07777 {
return ErrFileIllegalMode
}
return nil
}

View File

@@ -16,45 +16,63 @@ package types
import (
"errors"
"os"
"path/filepath"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrNoFilesystem = errors.New("no filesystem specified")
ErrFileIllegalMode = errors.New("illegal file mode")
ErrNoFilesystem = errors.New("no filesystem specified")
ErrBothIDAndNameSet = errors.New("cannot set both id and name")
)
// Node represents all common info for files (special types, e.g. directories, included).
type Node struct {
Filesystem string `json:"filesystem,omitempty"`
Path Path `json:"path,omitempty"`
Mode NodeMode `json:"mode,omitempty"`
User NodeUser `json:"user,omitempty"`
Group NodeGroup `json:"group,omitempty"`
}
type NodeUser struct {
Id int `json:"id,omitempty"`
}
type NodeGroup struct {
Id int `json:"id,omitempty"`
}
func (n Node) Validate() report.Report {
func (n Node) ValidateFilesystem() report.Report {
r := report.Report{}
if n.Filesystem == "" {
return report.ReportFromError(ErrNoFilesystem, report.EntryError)
r.Add(report.Entry{
Message: ErrNoFilesystem.Error(),
Kind: report.EntryError,
})
}
return report.Report{}
return r
}
type NodeMode os.FileMode
func (m NodeMode) Validate() report.Report {
if (m &^ 07777) != 0 {
return report.ReportFromError(ErrFileIllegalMode, report.EntryError)
func (n Node) ValidatePath() report.Report {
r := report.Report{}
if err := validatePath(n.Path); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return report.Report{}
return r
}
func (n Node) Depth() int {
count := 0
for p := filepath.Clean(string(n.Path)); p != "/"; count++ {
p = filepath.Dir(p)
}
return count
}
func (nu NodeUser) Validate() report.Report {
r := report.Report{}
if nu.ID != nil && nu.Name != "" {
r.Add(report.Entry{
Message: ErrBothIDAndNameSet.Error(),
Kind: report.EntryError,
})
}
return r
}
func (ng NodeGroup) Validate() report.Report {
r := report.Report{}
if ng.ID != nil && ng.Name != "" {
r.Add(report.Entry{
Message: ErrBothIDAndNameSet.Error(),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -15,45 +15,59 @@
package types
import (
"errors"
"fmt"
"regexp"
"github.com/coreos/ignition/config/validate/report"
)
type Partition struct {
Label PartitionLabel `json:"label,omitempty"`
Number int `json:"number"`
Size PartitionDimension `json:"size"`
Start PartitionDimension `json:"start"`
TypeGUID PartitionTypeGUID `json:"typeGuid,omitempty"`
}
const (
guidRegexStr = "^(|[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$"
)
type PartitionLabel string
var (
ErrLabelTooLong = errors.New("partition labels may not exceed 36 characters")
ErrDoesntMatchGUIDRegex = errors.New("doesn't match the form \"01234567-89AB-CDEF-EDCB-A98765432101\"")
)
func (n PartitionLabel) Validate() report.Report {
func (p Partition) ValidateLabel() report.Report {
r := report.Report{}
// http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries:
// 56 (0x38) 72 bytes Partition name (36 UTF-16LE code units)
// XXX(vc): note GPT calls it a name, we're using label for consistency
// with udev naming /dev/disk/by-partlabel/*.
if len(string(n)) > 36 {
return report.ReportFromError(fmt.Errorf("partition labels may not exceed 36 characters"), report.EntryError)
if len(p.Label) > 36 {
r.Add(report.Entry{
Message: ErrLabelTooLong.Error(),
Kind: report.EntryError,
})
}
return report.Report{}
return r
}
type PartitionDimension uint64
func (p Partition) ValidateTypeGUID() report.Report {
return validateGUID(p.TypeGUID)
}
type PartitionTypeGUID string
func (p Partition) ValidateGUID() report.Report {
return validateGUID(p.GUID)
}
func (d PartitionTypeGUID) Validate() report.Report {
ok, err := regexp.MatchString("^(|[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$", string(d))
func validateGUID(guid string) report.Report {
r := report.Report{}
ok, err := regexp.MatchString(guidRegexStr, guid)
if err != nil {
return report.ReportFromError(fmt.Errorf("error matching type-guid regexp: %v", err), report.EntryError)
r.Add(report.Entry{
Message: fmt.Sprintf("error matching guid regexp: %v", err),
Kind: report.EntryError,
})
} else if !ok {
r.Add(report.Entry{
Message: ErrDoesntMatchGUIDRegex.Error(),
Kind: report.EntryError,
})
}
if !ok {
return report.ReportFromError(fmt.Errorf(`partition type-guid must have the form "01234567-89AB-CDEF-EDCB-A98765432101", got: %q`, string(d)), report.EntryError)
}
return report.Report{}
return r
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 CoreOS, Inc.
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,7 +14,69 @@
package types
type Passwd struct {
Users []User `json:"users,omitempty"`
Groups []Group `json:"groups,omitempty"`
import (
"errors"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrPasswdCreateDeprecated = errors.New("the create object has been deprecated in favor of user-level options")
ErrPasswdCreateAndGecos = errors.New("cannot use both the create object and the user-level gecos field")
ErrPasswdCreateAndGroups = errors.New("cannot use both the create object and the user-level groups field")
ErrPasswdCreateAndHomeDir = errors.New("cannot use both the create object and the user-level homeDir field")
ErrPasswdCreateAndNoCreateHome = errors.New("cannot use both the create object and the user-level noCreateHome field")
ErrPasswdCreateAndNoLogInit = errors.New("cannot use both the create object and the user-level noLogInit field")
ErrPasswdCreateAndNoUserGroup = errors.New("cannot use both the create object and the user-level noUserGroup field")
ErrPasswdCreateAndPrimaryGroup = errors.New("cannot use both the create object and the user-level primaryGroup field")
ErrPasswdCreateAndShell = errors.New("cannot use both the create object and the user-level shell field")
ErrPasswdCreateAndSystem = errors.New("cannot use both the create object and the user-level system field")
ErrPasswdCreateAndUID = errors.New("cannot use both the create object and the user-level uid field")
)
func (p PasswdUser) Validate() report.Report {
r := report.Report{}
if p.Create != nil {
r.Add(report.Entry{
Message: ErrPasswdCreateDeprecated.Error(),
Kind: report.EntryWarning,
})
addErr := func(err error) {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
if p.Gecos != "" {
addErr(ErrPasswdCreateAndGecos)
}
if len(p.Groups) > 0 {
addErr(ErrPasswdCreateAndGroups)
}
if p.HomeDir != "" {
addErr(ErrPasswdCreateAndHomeDir)
}
if p.NoCreateHome {
addErr(ErrPasswdCreateAndNoCreateHome)
}
if p.NoLogInit {
addErr(ErrPasswdCreateAndNoLogInit)
}
if p.NoUserGroup {
addErr(ErrPasswdCreateAndNoUserGroup)
}
if p.PrimaryGroup != "" {
addErr(ErrPasswdCreateAndPrimaryGroup)
}
if p.Shell != "" {
addErr(ErrPasswdCreateAndShell)
}
if p.System {
addErr(ErrPasswdCreateAndSystem)
}
if p.UID != nil {
addErr(ErrPasswdCreateAndUID)
}
}
return r
}

View File

@@ -17,23 +17,15 @@ package types
import (
"errors"
"path"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrPathRelative = errors.New("path not absolute")
)
type Path string
func (p Path) MarshalJSON() ([]byte, error) {
return []byte(`"` + string(p) + `"`), nil
}
func (p Path) Validate() report.Report {
if !path.IsAbs(string(p)) {
return report.ReportFromError(ErrPathRelative, report.EntryError)
func validatePath(p string) error {
if !path.IsAbs(p) {
return ErrPathRelative
}
return report.Report{}
return nil
}

View File

@@ -20,18 +20,15 @@ import (
"github.com/coreos/ignition/config/validate/report"
)
type Raid struct {
Name string `json:"name"`
Level string `json:"level"`
Devices []Path `json:"devices,omitempty"`
Spares int `json:"spares,omitempty"`
}
func (n Raid) Validate() report.Report {
func (n Raid) ValidateLevel() report.Report {
r := report.Report{}
switch n.Level {
case "linear", "raid0", "0", "stripe":
if n.Spares != 0 {
return report.ReportFromError(fmt.Errorf("spares unsupported for %q arrays", n.Level), report.EntryError)
r.Add(report.Entry{
Message: fmt.Sprintf("spares unsupported for %q arrays", n.Level),
Kind: report.EntryError,
})
}
case "raid1", "1", "mirror":
case "raid4", "4":
@@ -39,7 +36,23 @@ func (n Raid) Validate() report.Report {
case "raid6", "6":
case "raid10", "10":
default:
return report.ReportFromError(fmt.Errorf("unrecognized raid level: %q", n.Level), report.EntryError)
r.Add(report.Entry{
Message: fmt.Sprintf("unrecognized raid level: %q", n.Level),
Kind: report.EntryError,
})
}
return report.Report{}
return r
}
func (n Raid) ValidateDevices() report.Report {
r := report.Report{}
for _, d := range n.Devices {
if err := validatePath(string(d)); err != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("array %q: device path not absolute: %q", n.Name, d),
Kind: report.EntryError,
})
}
}
return r
}

View File

@@ -0,0 +1,221 @@
package types
// generated by "schematyper --package=types schema/ignition.json -o config/types/schema.go --root-type=Config" -- DO NOT EDIT
type Config struct {
Ignition Ignition `json:"ignition"`
Networkd Networkd `json:"networkd,omitempty"`
Passwd Passwd `json:"passwd,omitempty"`
Storage Storage `json:"storage,omitempty"`
Systemd Systemd `json:"systemd,omitempty"`
}
type ConfigReference struct {
Source string `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
}
type Create struct {
Force bool `json:"force,omitempty"`
Options []CreateOption `json:"options,omitempty"`
}
type CreateOption string
type Device string
type Directory struct {
Node
DirectoryEmbedded1
}
type DirectoryEmbedded1 struct {
Mode int `json:"mode,omitempty"`
}
type Disk struct {
Device string `json:"device,omitempty"`
Partitions []Partition `json:"partitions,omitempty"`
WipeTable bool `json:"wipeTable,omitempty"`
}
type Dropin struct {
Contents string `json:"contents,omitempty"`
Name string `json:"name,omitempty"`
}
type File struct {
Node
FileEmbedded1
}
type FileContents struct {
Compression string `json:"compression,omitempty"`
Source string `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
}
type FileEmbedded1 struct {
Contents FileContents `json:"contents,omitempty"`
Mode int `json:"mode,omitempty"`
}
type Filesystem struct {
Mount *Mount `json:"mount,omitempty"`
Name string `json:"name,omitempty"`
Path *string `json:"path,omitempty"`
}
type Ignition struct {
Config IgnitionConfig `json:"config,omitempty"`
Timeouts Timeouts `json:"timeouts,omitempty"`
Version string `json:"version,omitempty"`
}
type IgnitionConfig struct {
Append []ConfigReference `json:"append,omitempty"`
Replace *ConfigReference `json:"replace,omitempty"`
}
type Link struct {
Node
LinkEmbedded1
}
type LinkEmbedded1 struct {
Hard bool `json:"hard,omitempty"`
Target string `json:"target,omitempty"`
}
type Mount struct {
Create *Create `json:"create,omitempty"`
Device string `json:"device,omitempty"`
Format string `json:"format,omitempty"`
Label *string `json:"label,omitempty"`
Options []MountOption `json:"options,omitempty"`
UUID *string `json:"uuid,omitempty"`
WipeFilesystem bool `json:"wipeFilesystem,omitempty"`
}
type MountOption string
type Networkd struct {
Units []Networkdunit `json:"units,omitempty"`
}
type Networkdunit struct {
Contents string `json:"contents,omitempty"`
Name string `json:"name,omitempty"`
}
type Node struct {
Filesystem string `json:"filesystem,omitempty"`
Group NodeGroup `json:"group,omitempty"`
Path string `json:"path,omitempty"`
User NodeUser `json:"user,omitempty"`
}
type NodeGroup struct {
ID *int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type NodeUser struct {
ID *int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type Partition struct {
GUID string `json:"guid,omitempty"`
Label string `json:"label,omitempty"`
Number int `json:"number,omitempty"`
Size int `json:"size,omitempty"`
Start int `json:"start,omitempty"`
TypeGUID string `json:"typeGuid,omitempty"`
}
type Passwd struct {
Groups []PasswdGroup `json:"groups,omitempty"`
Users []PasswdUser `json:"users,omitempty"`
}
type PasswdGroup struct {
Gid *int `json:"gid,omitempty"`
Name string `json:"name,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
System bool `json:"system,omitempty"`
}
type PasswdUser struct {
Create *Usercreate `json:"create,omitempty"`
Gecos string `json:"gecos,omitempty"`
Groups []PasswdUserGroup `json:"groups,omitempty"`
HomeDir string `json:"homeDir,omitempty"`
Name string `json:"name,omitempty"`
NoCreateHome bool `json:"noCreateHome,omitempty"`
NoLogInit bool `json:"noLogInit,omitempty"`
NoUserGroup bool `json:"noUserGroup,omitempty"`
PasswordHash *string `json:"passwordHash,omitempty"`
PrimaryGroup string `json:"primaryGroup,omitempty"`
SSHAuthorizedKeys []SSHAuthorizedKey `json:"sshAuthorizedKeys,omitempty"`
Shell string `json:"shell,omitempty"`
System bool `json:"system,omitempty"`
UID *int `json:"uid,omitempty"`
}
type PasswdUserGroup string
type Raid struct {
Devices []Device `json:"devices,omitempty"`
Level string `json:"level,omitempty"`
Name string `json:"name,omitempty"`
Spares int `json:"spares,omitempty"`
}
type SSHAuthorizedKey string
type Storage struct {
Directories []Directory `json:"directories,omitempty"`
Disks []Disk `json:"disks,omitempty"`
Files []File `json:"files,omitempty"`
Filesystems []Filesystem `json:"filesystems,omitempty"`
Links []Link `json:"links,omitempty"`
Raid []Raid `json:"raid,omitempty"`
}
type Systemd struct {
Units []Unit `json:"units,omitempty"`
}
type Timeouts struct {
HTTPResponseHeaders *int `json:"httpResponseHeaders,omitempty"`
HTTPTotal *int `json:"httpTotal,omitempty"`
}
type Unit struct {
Contents string `json:"contents,omitempty"`
Dropins []Dropin `json:"dropins,omitempty"`
Enable bool `json:"enable,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
Mask bool `json:"mask,omitempty"`
Name string `json:"name,omitempty"`
}
type Usercreate struct {
Gecos string `json:"gecos,omitempty"`
Groups []UsercreateGroup `json:"groups,omitempty"`
HomeDir string `json:"homeDir,omitempty"`
NoCreateHome bool `json:"noCreateHome,omitempty"`
NoLogInit bool `json:"noLogInit,omitempty"`
NoUserGroup bool `json:"noUserGroup,omitempty"`
PrimaryGroup string `json:"primaryGroup,omitempty"`
Shell string `json:"shell,omitempty"`
System bool `json:"system,omitempty"`
UID *int `json:"uid,omitempty"`
}
type UsercreateGroup string
type Verification struct {
Hash *string `json:"hash,omitempty"`
}

View File

@@ -25,79 +25,77 @@ import (
"github.com/coreos/ignition/config/validate/report"
)
type SystemdUnit struct {
Name SystemdUnitName `json:"name,omitempty"`
Enable bool `json:"enable,omitempty"`
Mask bool `json:"mask,omitempty"`
Contents string `json:"contents,omitempty"`
DropIns []SystemdUnitDropIn `json:"dropins,omitempty"`
}
var (
ErrInvalidSystemdExt = errors.New("invalid systemd unit extension")
ErrInvalidNetworkdExt = errors.New("invalid networkd unit extension")
)
func (u SystemdUnit) Validate() report.Report {
func (u Unit) ValidateContents() report.Report {
r := report.Report{}
if err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return report.Report{}
return r
}
type SystemdUnitDropIn struct {
Name SystemdUnitDropInName `json:"name,omitempty"`
Contents string `json:"contents,omitempty"`
}
func (u SystemdUnitDropIn) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
type SystemdUnitName string
func (n SystemdUnitName) Validate() report.Report {
switch path.Ext(string(n)) {
func (u Unit) ValidateName() report.Report {
r := report.Report{}
switch path.Ext(u.Name) {
case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope":
return report.Report{}
default:
return report.ReportFromError(errors.New("invalid systemd unit extension"), report.EntryError)
r.Add(report.Entry{
Message: ErrInvalidSystemdExt.Error(),
Kind: report.EntryError,
})
}
return r
}
type SystemdUnitDropInName string
func (d Dropin) Validate() report.Report {
r := report.Report{}
func (n SystemdUnitDropInName) Validate() report.Report {
switch path.Ext(string(n)) {
if err := validateUnitContent(d.Contents); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
switch path.Ext(d.Name) {
case ".conf":
return report.Report{}
default:
return report.ReportFromError(errors.New("invalid systemd unit drop-in extension"), report.EntryError)
r.Add(report.Entry{
Message: fmt.Sprintf("invalid systemd unit drop-in extension: %q", path.Ext(d.Name)),
Kind: report.EntryError,
})
}
return r
}
type NetworkdUnit struct {
Name NetworkdUnitName `json:"name,omitempty"`
Contents string `json:"contents,omitempty"`
}
func (u Networkdunit) Validate() report.Report {
r := report.Report{}
func (u NetworkdUnit) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return report.Report{}
}
type NetworkdUnitName string
func (n NetworkdUnitName) Validate() report.Report {
switch path.Ext(string(n)) {
switch path.Ext(u.Name) {
case ".link", ".netdev", ".network":
return report.Report{}
default:
return report.ReportFromError(errors.New("invalid networkd unit extension"), report.EntryError)
r.Add(report.Entry{
Message: ErrInvalidNetworkdExt.Error(),
Kind: report.EntryError,
})
}
return r
}
func validateUnitContent(content string) error {

View File

@@ -15,11 +15,9 @@
package types
import (
"encoding/json"
"errors"
"net/url"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
@@ -27,46 +25,25 @@ var (
ErrInvalidScheme = errors.New("invalid url scheme")
)
type Url url.URL
func (u *Url) UnmarshalJSON(data []byte) error {
var tu string
if err := json.Unmarshal(data, &tu); err != nil {
return err
func validateURL(s string) error {
// Empty url is valid, indicates an empty file
if s == "" {
return nil
}
pu, err := url.Parse(tu)
u, err := url.Parse(s)
if err != nil {
return err
}
*u = Url(*pu)
return nil
}
func (u Url) MarshalJSON() ([]byte, error) {
return []byte(`"` + u.String() + `"`), nil
}
func (u Url) String() string {
tu := url.URL(u)
return (&tu).String()
}
func (u Url) Validate() report.Report {
// Empty url is valid, indicates an empty file
if u.String() == "" {
return report.Report{}
}
switch url.URL(u).Scheme {
case "http", "https", "oem":
return report.Report{}
switch u.Scheme {
case "http", "https", "oem", "tftp", "s3":
return nil
case "data":
if _, err := dataurl.DecodeString(u.String()); err != nil {
return report.ReportFromError(err, report.EntryError)
if _, err := dataurl.DecodeString(s); err != nil {
return err
}
return report.Report{}
return nil
default:
return report.ReportFromError(ErrInvalidScheme, report.EntryError)
return ErrInvalidScheme
}
}

View File

@@ -1,35 +0,0 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
type User struct {
Name string `json:"name,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
SSHAuthorizedKeys []string `json:"sshAuthorizedKeys,omitempty"`
Create *UserCreate `json:"create,omitempty"`
}
type UserCreate struct {
Uid *uint `json:"uid,omitempty"`
GECOS string `json:"gecos,omitempty"`
Homedir string `json:"homeDir,omitempty"`
NoCreateHome bool `json:"noCreateHome,omitempty"`
PrimaryGroup string `json:"primaryGroup,omitempty"`
Groups []string `json:"groups,omitempty"`
NoUserGroup bool `json:"noUserGroup,omitempty"`
System bool `json:"system,omitempty"`
NoLogInit bool `json:"noLogInit,omitempty"`
Shell string `json:"shell,omitempty"`
}

View File

@@ -14,6 +14,70 @@
package types
type Verification struct {
Hash *Hash `json:"hash,omitempty"`
import (
"crypto"
"encoding/hex"
"errors"
"strings"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrHashMalformed = errors.New("malformed hash specifier")
ErrHashWrongSize = errors.New("incorrect size for hash sum")
ErrHashUnrecognized = errors.New("unrecognized hash function")
)
// HashParts will return the sum and function (in that order) of the hash stored
// in this Verification, or an error if there is an issue during parsing.
func (v Verification) HashParts() (string, string, error) {
if v.Hash == nil {
// The hash can be nil
return "", "", nil
}
parts := strings.SplitN(*v.Hash, "-", 2)
if len(parts) != 2 {
return "", "", ErrHashMalformed
}
return parts[0], parts[1], nil
}
func (v Verification) Validate() report.Report {
r := report.Report{}
if v.Hash == nil {
// The hash can be nil
return r
}
function, sum, err := v.HashParts()
if err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
return r
}
var hash crypto.Hash
switch function {
case "sha512":
hash = crypto.SHA512
default:
r.Add(report.Entry{
Message: ErrHashUnrecognized.Error(),
Kind: report.EntryError,
})
return r
}
if len(sum) != hex.EncodedLen(hash.Size()) {
r.Add(report.Entry{
Message: ErrHashWrongSize.Error(),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -105,7 +105,7 @@ func Parse(rawConfig []byte) (types.Config, report.Report, error) {
})
r.Merge(validate.ValidateWithoutSource(configValue))
} else {
r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig)))
r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig), true))
}
if r.IsFatal() {

View File

@@ -56,6 +56,11 @@ func (h Hash) MarshalJSON() ([]byte, error) {
return []byte(`"` + h.Function + "-" + h.Sum + `"`), nil
}
func (h Hash) String() string {
bytes, _ := h.MarshalJSON()
return string(bytes)
}
func (h Hash) Validate() report.Report {
var hash crypto.Hash
switch h.Function {

View File

@@ -0,0 +1,72 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v2_1
import (
"reflect"
"github.com/coreos/ignition/config/types"
)
// Append appends newConfig to oldConfig and returns the result. Appending one
// config to another is accomplished by iterating over every field in the
// config structure, appending slices, recursively appending structs, and
// overwriting old values with new values for all other types.
func Append(oldConfig, newConfig types.Config) types.Config {
vOld := reflect.ValueOf(oldConfig)
vNew := reflect.ValueOf(newConfig)
vResult := appendStruct(vOld, vNew)
return vResult.Interface().(types.Config)
}
// appendStruct is an internal helper function to AppendConfig. Given two values
// of structures (assumed to be the same type), recursively iterate over every
// field in the struct, appending slices, recursively appending structs, and
// overwriting old values with the new for all other types. Some individual
// struct fields have alternate merge strategies, determined by the field name.
// Currently these fields are "ignition.version", which uses the old value, and
// "ignition.config" which uses the new value.
func appendStruct(vOld, vNew reflect.Value) reflect.Value {
tOld := vOld.Type()
vRes := reflect.New(tOld)
for i := 0; i < tOld.NumField(); i++ {
vfOld := vOld.Field(i)
vfNew := vNew.Field(i)
vfRes := vRes.Elem().Field(i)
switch tOld.Field(i).Name {
case "Version":
vfRes.Set(vfOld)
continue
case "Config":
vfRes.Set(vfNew)
continue
}
switch vfOld.Type().Kind() {
case reflect.Struct:
vfRes.Set(appendStruct(vfOld, vfNew))
case reflect.Slice:
vfRes.Set(reflect.AppendSlice(vfOld, vfNew))
default:
vfRes.Set(vfNew)
}
}
return vRes.Elem()
}

View File

@@ -0,0 +1,53 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// These functions are copied from github.com/coreos/coreos-cloudinit/config.
package v2_1
import (
"bytes"
"compress/gzip"
"io/ioutil"
"strings"
"unicode"
)
func isCloudConfig(userdata []byte) bool {
header := strings.SplitN(string(decompressIfGzipped(userdata)), "\n", 2)[0]
// Trim trailing whitespaces
header = strings.TrimRightFunc(header, unicode.IsSpace)
return (header == "#cloud-config")
}
func isScript(userdata []byte) bool {
header := strings.SplitN(string(decompressIfGzipped(userdata)), "\n", 2)[0]
return strings.HasPrefix(header, "#!")
}
func decompressIfGzipped(data []byte) []byte {
if reader, err := gzip.NewReader(bytes.NewReader(data)); err == nil {
uncompressedData, err := ioutil.ReadAll(reader)
reader.Close()
if err == nil {
return uncompressedData
} else {
return data
}
} else {
return data
}
}

118
vendor/github.com/coreos/ignition/config/v2_1/config.go generated vendored Normal file
View File

@@ -0,0 +1,118 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v2_1
import (
"bytes"
"errors"
"reflect"
"github.com/coreos/ignition/config/v2_1/types"
"github.com/coreos/ignition/config/validate"
astjson "github.com/coreos/ignition/config/validate/astjson"
"github.com/coreos/ignition/config/validate/report"
json "github.com/ajeddeloh/go-json"
"go4.org/errorutil"
)
var (
ErrCloudConfig = errors.New("not a config (found coreos-cloudconfig)")
ErrEmpty = errors.New("not a config (empty)")
ErrScript = errors.New("not a config (found coreos-cloudinit script)")
ErrDeprecated = errors.New("config format deprecated")
ErrInvalid = errors.New("config is not valid")
)
func Parse(rawConfig []byte) (types.Config, report.Report, error) {
if isEmpty(rawConfig) {
return types.Config{}, report.Report{}, ErrEmpty
} else if isCloudConfig(rawConfig) {
return types.Config{}, report.Report{}, ErrCloudConfig
} else if isScript(rawConfig) {
return types.Config{}, report.Report{}, ErrScript
}
var err error
var config types.Config
// These errors are fatal and the config should not be further validated
if err = json.Unmarshal(rawConfig, &config); err == nil {
versionReport := config.Ignition.Validate()
if versionReport.IsFatal() {
return types.Config{}, versionReport, ErrInvalid
}
}
// Handle json syntax and type errors first, since they are fatal but have offset info
if serr, ok := err.(*json.SyntaxError); ok {
line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), serr.Offset)
return types.Config{},
report.Report{
Entries: []report.Entry{{
Kind: report.EntryError,
Message: serr.Error(),
Line: line,
Column: col,
Highlight: highlight,
}},
},
ErrInvalid
}
if terr, ok := err.(*json.UnmarshalTypeError); ok {
line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), terr.Offset)
return types.Config{},
report.Report{
Entries: []report.Entry{{
Kind: report.EntryError,
Message: terr.Error(),
Line: line,
Column: col,
Highlight: highlight,
}},
},
ErrInvalid
}
// Handle other fatal errors (i.e. invalid version)
if err != nil {
return types.Config{}, report.ReportFromError(err, report.EntryError), err
}
// Unmarshal again to a json.Node to get offset information for building a report
var ast json.Node
var r report.Report
configValue := reflect.ValueOf(config)
if err := json.Unmarshal(rawConfig, &ast); err != nil {
r.Add(report.Entry{
Kind: report.EntryWarning,
Message: "Ignition could not unmarshal your config for reporting line numbers. This should never happen. Please file a bug.",
})
r.Merge(validate.ValidateWithoutSource(configValue))
} else {
r.Merge(validate.Validate(configValue, astjson.FromJsonRoot(ast), bytes.NewReader(rawConfig), true))
}
if r.IsFatal() {
return types.Config{}, r, ErrInvalid
}
return config, r, nil
}
func isEmpty(userdata []byte) bool {
return len(userdata) == 0
}

View File

@@ -0,0 +1,79 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"fmt"
"github.com/coreos/go-semver/semver"
"github.com/coreos/ignition/config/validate/report"
)
var (
MaxVersion = semver.Version{
Major: 2,
Minor: 1,
}
)
func (c Config) Validate() report.Report {
r := report.Report{}
rules := []rule{
checkFilesFilesystems,
checkDuplicateFilesystems,
}
for _, rule := range rules {
rule(c, &r)
}
return r
}
type rule func(cfg Config, report *report.Report)
func checkFilesFilesystems(cfg Config, r *report.Report) {
filesystems := map[string]struct{}{"root": {}}
for _, filesystem := range cfg.Storage.Filesystems {
filesystems[filesystem.Name] = struct{}{}
}
for _, file := range cfg.Storage.Files {
if file.Filesystem == "" {
// Filesystem was not specified. This is an error, but its handled in types.File's Validate, not here
continue
}
_, ok := filesystems[file.Filesystem]
if !ok {
r.Add(report.Entry{
Kind: report.EntryWarning,
Message: fmt.Sprintf("File %q references nonexistent filesystem %q. (This is ok if it is defined in a referenced config)",
file.Path, file.Filesystem),
})
}
}
}
func checkDuplicateFilesystems(cfg Config, r *report.Report) {
filesystems := map[string]struct{}{"root": {}}
for _, filesystem := range cfg.Storage.Filesystems {
if _, ok := filesystems[filesystem.Name]; ok {
r.Add(report.Entry{
Kind: report.EntryWarning,
Message: fmt.Sprintf("Filesystem %q shadows exising filesystem definition", filesystem.Name),
})
}
filesystems[filesystem.Name] = struct{}{}
}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 CoreOS, Inc.
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,9 +14,17 @@
package types
type Group struct {
Name string `json:"name,omitempty"`
Gid *uint `json:"gid,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
System bool `json:"system,omitempty"`
import (
"github.com/coreos/ignition/config/validate/report"
)
func (d Directory) ValidateMode() report.Report {
r := report.Report{}
if err := validateMode(d.Mode); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -0,0 +1,126 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
func (n Disk) Validate() report.Report {
return report.Report{}
}
func (n Disk) ValidateDevice() report.Report {
if len(n.Device) == 0 {
return report.ReportFromError(fmt.Errorf("disk device is required"), report.EntryError)
}
if err := validatePath(string(n.Device)); err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}
func (n Disk) ValidatePartitions() report.Report {
r := report.Report{}
if n.partitionNumbersCollide() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partition numbers collide", n.Device),
Kind: report.EntryError,
})
}
if n.partitionsOverlap() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions overlap", n.Device),
Kind: report.EntryError,
})
}
if n.partitionsMisaligned() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions misaligned", n.Device),
Kind: report.EntryError,
})
}
// Disks which have no errors at this point will likely succeed in sgdisk
return r
}
// partitionNumbersCollide returns true if partition numbers in n.Partitions are not unique.
func (n Disk) partitionNumbersCollide() bool {
m := map[int][]Partition{}
for _, p := range n.Partitions {
m[p.Number] = append(m[p.Number], p)
}
for _, n := range m {
if len(n) > 1 {
// TODO(vc): return information describing the collision for logging
return true
}
}
return false
}
// end returns the last sector of a partition.
func (p Partition) end() int {
if p.Size == 0 {
// a size of 0 means "fill available", just return the start as the end for those.
return p.Start
}
return p.Start + p.Size - 1
}
// partitionsOverlap returns true if any explicitly dimensioned partitions overlap
func (n Disk) partitionsOverlap() bool {
for _, p := range n.Partitions {
// Starts of 0 are placed by sgdisk into the "largest available block" at that time.
// We aren't going to check those for overlap since we don't have the disk geometry.
if p.Start == 0 {
continue
}
for _, o := range n.Partitions {
if p == o || o.Start == 0 {
continue
}
// is p.Start within o?
if p.Start >= o.Start && p.Start <= o.end() {
return true
}
// is p.end() within o?
if p.end() >= o.Start && p.end() <= o.end() {
return true
}
// do p.Start and p.end() straddle o?
if p.Start < o.Start && p.end() > o.end() {
return true
}
}
}
return false
}
// partitionsMisaligned returns true if any of the partitions don't start on a 2048-sector (1MiB) boundary.
func (n Disk) partitionsMisaligned() bool {
for _, p := range n.Partitions {
if (p.Start & (2048 - 1)) != 0 {
return true
}
}
return false
}

View File

@@ -0,0 +1,62 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrCompressionInvalid = errors.New("invalid compression method")
)
func (f File) ValidateMode() report.Report {
r := report.Report{}
if err := validateMode(f.Mode); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return r
}
func (fc FileContents) ValidateCompression() report.Report {
r := report.Report{}
switch fc.Compression {
case "", "gzip":
default:
r.Add(report.Entry{
Message: ErrCompressionInvalid.Error(),
Kind: report.EntryError,
})
}
return r
}
func (fc FileContents) ValidateSource() report.Report {
r := report.Report{}
err := validateURL(fc.Source)
if err != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("invalid url %q: %v", fc.Source, err),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -0,0 +1,160 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrFilesystemInvalidFormat = errors.New("invalid filesystem format")
ErrFilesystemNoMountPath = errors.New("filesystem is missing mount or path")
ErrFilesystemMountAndPath = errors.New("filesystem has both mount and path defined")
ErrUsedCreateAndMountOpts = errors.New("cannot use both create object and mount-level options field")
ErrUsedCreateAndWipeFilesystem = errors.New("cannot use both create object and wipeFilesystem field")
ErrWarningCreateDeprecated = errors.New("the create object has been deprecated in favor of mount-level options")
ErrExt4LabelTooLong = errors.New("filesystem labels cannot be longer than 16 characters when using ext4")
ErrBtrfsLabelTooLong = errors.New("filesystem labels cannot be longer than 256 characters when using btrfs")
ErrXfsLabelTooLong = errors.New("filesystem labels cannot be longer than 12 characters when using xfs")
ErrSwapLabelTooLong = errors.New("filesystem labels cannot be longer than 15 characters when using swap")
ErrVfatLabelTooLong = errors.New("filesystem labels cannot be longer than 11 characters when using vfat")
)
func (f Filesystem) Validate() report.Report {
r := report.Report{}
if f.Mount == nil && f.Path == nil {
r.Add(report.Entry{
Message: ErrFilesystemNoMountPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount != nil {
if f.Path != nil {
r.Add(report.Entry{
Message: ErrFilesystemMountAndPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount.Create != nil {
if f.Mount.WipeFilesystem {
r.Add(report.Entry{
Message: ErrUsedCreateAndWipeFilesystem.Error(),
Kind: report.EntryError,
})
}
if len(f.Mount.Options) > 0 {
r.Add(report.Entry{
Message: ErrUsedCreateAndMountOpts.Error(),
Kind: report.EntryError,
})
}
r.Add(report.Entry{
Message: ErrWarningCreateDeprecated.Error(),
Kind: report.EntryWarning,
})
}
}
return r
}
func (f Filesystem) ValidatePath() report.Report {
r := report.Report{}
if f.Path != nil && validatePath(*f.Path) != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("filesystem %q: path not absolute", f.Name),
Kind: report.EntryError,
})
}
return r
}
func (m Mount) Validate() report.Report {
r := report.Report{}
switch m.Format {
case "ext4", "btrfs", "xfs", "swap", "vfat":
default:
r.Add(report.Entry{
Message: ErrFilesystemInvalidFormat.Error(),
Kind: report.EntryError,
})
}
return r
}
func (m Mount) ValidateDevice() report.Report {
r := report.Report{}
if err := validatePath(m.Device); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return r
}
func (m Mount) ValidateLabel() report.Report {
r := report.Report{}
if m.Label == nil {
return r
}
switch m.Format {
case "ext4":
if len(*m.Label) > 16 {
// source: man mkfs.ext4
r.Add(report.Entry{
Message: ErrExt4LabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "btrfs":
if len(*m.Label) > 256 {
// source: man mkfs.btrfs
r.Add(report.Entry{
Message: ErrBtrfsLabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "xfs":
if len(*m.Label) > 12 {
// source: man mkfs.xfs
r.Add(report.Entry{
Message: ErrXfsLabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "swap":
// mkswap's man page does not state a limit on label size, but through
// experimentation it appears that mkswap will truncate long labels to
// 15 characters, so let's enforce that.
if len(*m.Label) > 15 {
r.Add(report.Entry{
Message: ErrSwapLabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "vfat":
if len(*m.Label) > 11 {
// source: man mkfs.fat
r.Add(report.Entry{
Message: ErrVfatLabelTooLong.Error(),
Kind: report.EntryError,
})
}
}
return r
}

View File

@@ -0,0 +1,59 @@
// Copyright 2015 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"github.com/coreos/go-semver/semver"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrOldVersion = errors.New("incorrect config version (too old)")
ErrNewVersion = errors.New("incorrect config version (too new)")
ErrInvalidVersion = errors.New("invalid config version (couldn't parse)")
)
func (c ConfigReference) ValidateSource() report.Report {
r := report.Report{}
err := validateURL(c.Source)
if err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return r
}
func (v Ignition) Semver() (*semver.Version, error) {
return semver.NewVersion(v.Version)
}
func (v Ignition) Validate() report.Report {
tv, err := v.Semver()
if err != nil {
return report.ReportFromError(ErrInvalidVersion, report.EntryError)
}
if MaxVersion.Major > tv.Major {
return report.ReportFromError(ErrOldVersion, report.EntryError)
}
if MaxVersion.LessThan(*tv) {
return report.ReportFromError(ErrNewVersion, report.EntryError)
}
return report.Report{}
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 CoreOS, Inc.
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,10 +14,22 @@
package types
type Storage struct {
Disks []Disk `json:"disks,omitempty"`
Arrays []Raid `json:"raid,omitempty"`
Filesystems []Filesystem `json:"filesystems,omitempty"`
Files []File `json:"files,omitempty"`
Directories []Directory `json:"directories,omitempty"`
import (
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
func (s Link) Validate() report.Report {
r := report.Report{}
if !s.Hard {
err := validatePath(s.Target)
if err != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("problem with target path %q: %v", s.Target, err),
Kind: report.EntryError,
})
}
}
return r
}

View File

@@ -1,4 +1,4 @@
// Copyright 2016 CoreOS, Inc.
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -14,6 +14,17 @@
package types
type Systemd struct {
Units []SystemdUnit `json:"units,omitempty"`
import (
"errors"
)
var (
ErrFileIllegalMode = errors.New("illegal file mode")
)
func validateMode(m int) error {
if m < 0 || m > 07777 {
return ErrFileIllegalMode
}
return nil
}

View File

@@ -0,0 +1,78 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"path/filepath"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrNoFilesystem = errors.New("no filesystem specified")
ErrBothIDAndNameSet = errors.New("cannot set both id and name")
)
func (n Node) ValidateFilesystem() report.Report {
r := report.Report{}
if n.Filesystem == "" {
r.Add(report.Entry{
Message: ErrNoFilesystem.Error(),
Kind: report.EntryError,
})
}
return r
}
func (n Node) ValidatePath() report.Report {
r := report.Report{}
if err := validatePath(n.Path); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return r
}
func (n Node) Depth() int {
count := 0
for p := filepath.Clean(string(n.Path)); p != "/"; count++ {
p = filepath.Dir(p)
}
return count
}
func (nu NodeUser) Validate() report.Report {
r := report.Report{}
if nu.ID != nil && nu.Name != "" {
r.Add(report.Entry{
Message: ErrBothIDAndNameSet.Error(),
Kind: report.EntryError,
})
}
return r
}
func (ng NodeGroup) Validate() report.Report {
r := report.Report{}
if ng.ID != nil && ng.Name != "" {
r.Add(report.Entry{
Message: ErrBothIDAndNameSet.Error(),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -0,0 +1,73 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"fmt"
"regexp"
"github.com/coreos/ignition/config/validate/report"
)
const (
guidRegexStr = "^(|[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$"
)
var (
ErrLabelTooLong = errors.New("partition labels may not exceed 36 characters")
ErrDoesntMatchGUIDRegex = errors.New("doesn't match the form \"01234567-89AB-CDEF-EDCB-A98765432101\"")
)
func (p Partition) ValidateLabel() report.Report {
r := report.Report{}
// http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries:
// 56 (0x38) 72 bytes Partition name (36 UTF-16LE code units)
// XXX(vc): note GPT calls it a name, we're using label for consistency
// with udev naming /dev/disk/by-partlabel/*.
if len(p.Label) > 36 {
r.Add(report.Entry{
Message: ErrLabelTooLong.Error(),
Kind: report.EntryError,
})
}
return r
}
func (p Partition) ValidateTypeGUID() report.Report {
return validateGUID(p.TypeGUID)
}
func (p Partition) ValidateGUID() report.Report {
return validateGUID(p.GUID)
}
func validateGUID(guid string) report.Report {
r := report.Report{}
ok, err := regexp.MatchString(guidRegexStr, guid)
if err != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("error matching guid regexp: %v", err),
Kind: report.EntryError,
})
} else if !ok {
r.Add(report.Entry{
Message: ErrDoesntMatchGUIDRegex.Error(),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -0,0 +1,82 @@
// Copyright 2017 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrPasswdCreateDeprecated = errors.New("the create object has been deprecated in favor of user-level options")
ErrPasswdCreateAndGecos = errors.New("cannot use both the create object and the user-level gecos field")
ErrPasswdCreateAndGroups = errors.New("cannot use both the create object and the user-level groups field")
ErrPasswdCreateAndHomeDir = errors.New("cannot use both the create object and the user-level homeDir field")
ErrPasswdCreateAndNoCreateHome = errors.New("cannot use both the create object and the user-level noCreateHome field")
ErrPasswdCreateAndNoLogInit = errors.New("cannot use both the create object and the user-level noLogInit field")
ErrPasswdCreateAndNoUserGroup = errors.New("cannot use both the create object and the user-level noUserGroup field")
ErrPasswdCreateAndPrimaryGroup = errors.New("cannot use both the create object and the user-level primaryGroup field")
ErrPasswdCreateAndShell = errors.New("cannot use both the create object and the user-level shell field")
ErrPasswdCreateAndSystem = errors.New("cannot use both the create object and the user-level system field")
ErrPasswdCreateAndUID = errors.New("cannot use both the create object and the user-level uid field")
)
func (p PasswdUser) Validate() report.Report {
r := report.Report{}
if p.Create != nil {
r.Add(report.Entry{
Message: ErrPasswdCreateDeprecated.Error(),
Kind: report.EntryWarning,
})
addErr := func(err error) {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
if p.Gecos != "" {
addErr(ErrPasswdCreateAndGecos)
}
if len(p.Groups) > 0 {
addErr(ErrPasswdCreateAndGroups)
}
if p.HomeDir != "" {
addErr(ErrPasswdCreateAndHomeDir)
}
if p.NoCreateHome {
addErr(ErrPasswdCreateAndNoCreateHome)
}
if p.NoLogInit {
addErr(ErrPasswdCreateAndNoLogInit)
}
if p.NoUserGroup {
addErr(ErrPasswdCreateAndNoUserGroup)
}
if p.PrimaryGroup != "" {
addErr(ErrPasswdCreateAndPrimaryGroup)
}
if p.Shell != "" {
addErr(ErrPasswdCreateAndShell)
}
if p.System {
addErr(ErrPasswdCreateAndSystem)
}
if p.UID != nil {
addErr(ErrPasswdCreateAndUID)
}
}
return r
}

View File

@@ -14,7 +14,18 @@
package types
type Timeouts struct {
HttpResponseHeaders *int `json:"httpResponseHeaders,omitempty"`
HttpTotal *int `json:"httpTotal,omitempty"`
import (
"errors"
"path"
)
var (
ErrPathRelative = errors.New("path not absolute")
)
func validatePath(p string) error {
if !path.IsAbs(p) {
return ErrPathRelative
}
return nil
}

View File

@@ -0,0 +1,58 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"fmt"
"github.com/coreos/ignition/config/validate/report"
)
func (n Raid) ValidateLevel() report.Report {
r := report.Report{}
switch n.Level {
case "linear", "raid0", "0", "stripe":
if n.Spares != 0 {
r.Add(report.Entry{
Message: fmt.Sprintf("spares unsupported for %q arrays", n.Level),
Kind: report.EntryError,
})
}
case "raid1", "1", "mirror":
case "raid4", "4":
case "raid5", "5":
case "raid6", "6":
case "raid10", "10":
default:
r.Add(report.Entry{
Message: fmt.Sprintf("unrecognized raid level: %q", n.Level),
Kind: report.EntryError,
})
}
return r
}
func (n Raid) ValidateDevices() report.Report {
r := report.Report{}
for _, d := range n.Devices {
if err := validatePath(string(d)); err != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("array %q: device path not absolute: %q", n.Name, d),
Kind: report.EntryError,
})
}
}
return r
}

View File

@@ -0,0 +1,221 @@
package types
// generated by "schematyper --package=types schema/ignition.json -o config/types/schema.go --root-type=Config" -- DO NOT EDIT
type Config struct {
Ignition Ignition `json:"ignition"`
Networkd Networkd `json:"networkd,omitempty"`
Passwd Passwd `json:"passwd,omitempty"`
Storage Storage `json:"storage,omitempty"`
Systemd Systemd `json:"systemd,omitempty"`
}
type ConfigReference struct {
Source string `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
}
type Create struct {
Force bool `json:"force,omitempty"`
Options []CreateOption `json:"options,omitempty"`
}
type CreateOption string
type Device string
type Directory struct {
Node
DirectoryEmbedded1
}
type DirectoryEmbedded1 struct {
Mode int `json:"mode,omitempty"`
}
type Disk struct {
Device string `json:"device,omitempty"`
Partitions []Partition `json:"partitions,omitempty"`
WipeTable bool `json:"wipeTable,omitempty"`
}
type Dropin struct {
Contents string `json:"contents,omitempty"`
Name string `json:"name,omitempty"`
}
type File struct {
Node
FileEmbedded1
}
type FileContents struct {
Compression string `json:"compression,omitempty"`
Source string `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
}
type FileEmbedded1 struct {
Contents FileContents `json:"contents,omitempty"`
Mode int `json:"mode,omitempty"`
}
type Filesystem struct {
Mount *Mount `json:"mount,omitempty"`
Name string `json:"name,omitempty"`
Path *string `json:"path,omitempty"`
}
type Ignition struct {
Config IgnitionConfig `json:"config,omitempty"`
Timeouts Timeouts `json:"timeouts,omitempty"`
Version string `json:"version,omitempty"`
}
type IgnitionConfig struct {
Append []ConfigReference `json:"append,omitempty"`
Replace *ConfigReference `json:"replace,omitempty"`
}
type Link struct {
Node
LinkEmbedded1
}
type LinkEmbedded1 struct {
Hard bool `json:"hard,omitempty"`
Target string `json:"target,omitempty"`
}
type Mount struct {
Create *Create `json:"create,omitempty"`
Device string `json:"device,omitempty"`
Format string `json:"format,omitempty"`
Label *string `json:"label,omitempty"`
Options []MountOption `json:"options,omitempty"`
UUID *string `json:"uuid,omitempty"`
WipeFilesystem bool `json:"wipeFilesystem,omitempty"`
}
type MountOption string
type Networkd struct {
Units []Networkdunit `json:"units,omitempty"`
}
type Networkdunit struct {
Contents string `json:"contents,omitempty"`
Name string `json:"name,omitempty"`
}
type Node struct {
Filesystem string `json:"filesystem,omitempty"`
Group NodeGroup `json:"group,omitempty"`
Path string `json:"path,omitempty"`
User NodeUser `json:"user,omitempty"`
}
type NodeGroup struct {
ID *int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type NodeUser struct {
ID *int `json:"id,omitempty"`
Name string `json:"name,omitempty"`
}
type Partition struct {
GUID string `json:"guid,omitempty"`
Label string `json:"label,omitempty"`
Number int `json:"number,omitempty"`
Size int `json:"size,omitempty"`
Start int `json:"start,omitempty"`
TypeGUID string `json:"typeGuid,omitempty"`
}
type Passwd struct {
Groups []PasswdGroup `json:"groups,omitempty"`
Users []PasswdUser `json:"users,omitempty"`
}
type PasswdGroup struct {
Gid *int `json:"gid,omitempty"`
Name string `json:"name,omitempty"`
PasswordHash string `json:"passwordHash,omitempty"`
System bool `json:"system,omitempty"`
}
type PasswdUser struct {
Create *Usercreate `json:"create,omitempty"`
Gecos string `json:"gecos,omitempty"`
Groups []PasswdUserGroup `json:"groups,omitempty"`
HomeDir string `json:"homeDir,omitempty"`
Name string `json:"name,omitempty"`
NoCreateHome bool `json:"noCreateHome,omitempty"`
NoLogInit bool `json:"noLogInit,omitempty"`
NoUserGroup bool `json:"noUserGroup,omitempty"`
PasswordHash *string `json:"passwordHash,omitempty"`
PrimaryGroup string `json:"primaryGroup,omitempty"`
SSHAuthorizedKeys []SSHAuthorizedKey `json:"sshAuthorizedKeys,omitempty"`
Shell string `json:"shell,omitempty"`
System bool `json:"system,omitempty"`
UID *int `json:"uid,omitempty"`
}
type PasswdUserGroup string
type Raid struct {
Devices []Device `json:"devices,omitempty"`
Level string `json:"level,omitempty"`
Name string `json:"name,omitempty"`
Spares int `json:"spares,omitempty"`
}
type SSHAuthorizedKey string
type Storage struct {
Directories []Directory `json:"directories,omitempty"`
Disks []Disk `json:"disks,omitempty"`
Files []File `json:"files,omitempty"`
Filesystems []Filesystem `json:"filesystems,omitempty"`
Links []Link `json:"links,omitempty"`
Raid []Raid `json:"raid,omitempty"`
}
type Systemd struct {
Units []Unit `json:"units,omitempty"`
}
type Timeouts struct {
HTTPResponseHeaders *int `json:"httpResponseHeaders,omitempty"`
HTTPTotal *int `json:"httpTotal,omitempty"`
}
type Unit struct {
Contents string `json:"contents,omitempty"`
Dropins []Dropin `json:"dropins,omitempty"`
Enable bool `json:"enable,omitempty"`
Enabled *bool `json:"enabled,omitempty"`
Mask bool `json:"mask,omitempty"`
Name string `json:"name,omitempty"`
}
type Usercreate struct {
Gecos string `json:"gecos,omitempty"`
Groups []UsercreateGroup `json:"groups,omitempty"`
HomeDir string `json:"homeDir,omitempty"`
NoCreateHome bool `json:"noCreateHome,omitempty"`
NoLogInit bool `json:"noLogInit,omitempty"`
NoUserGroup bool `json:"noUserGroup,omitempty"`
PrimaryGroup string `json:"primaryGroup,omitempty"`
Shell string `json:"shell,omitempty"`
System bool `json:"system,omitempty"`
UID *int `json:"uid,omitempty"`
}
type UsercreateGroup string
type Verification struct {
Hash *string `json:"hash,omitempty"`
}

View File

@@ -0,0 +1,109 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"bytes"
"errors"
"fmt"
"path"
"github.com/coreos/go-systemd/unit"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrInvalidSystemdExt = errors.New("invalid systemd unit extension")
ErrInvalidNetworkdExt = errors.New("invalid networkd unit extension")
)
func (u Unit) ValidateContents() report.Report {
r := report.Report{}
if err := validateUnitContent(u.Contents); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return r
}
func (u Unit) ValidateName() report.Report {
r := report.Report{}
switch path.Ext(u.Name) {
case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope":
default:
r.Add(report.Entry{
Message: ErrInvalidSystemdExt.Error(),
Kind: report.EntryError,
})
}
return r
}
func (d Dropin) Validate() report.Report {
r := report.Report{}
if err := validateUnitContent(d.Contents); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
switch path.Ext(d.Name) {
case ".conf":
default:
r.Add(report.Entry{
Message: fmt.Sprintf("invalid systemd unit drop-in extension: %q", path.Ext(d.Name)),
Kind: report.EntryError,
})
}
return r
}
func (u Networkdunit) Validate() report.Report {
r := report.Report{}
if err := validateUnitContent(u.Contents); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
switch path.Ext(u.Name) {
case ".link", ".netdev", ".network":
default:
r.Add(report.Entry{
Message: ErrInvalidNetworkdExt.Error(),
Kind: report.EntryError,
})
}
return r
}
func validateUnitContent(content string) error {
c := bytes.NewBufferString(content)
_, err := unit.Deserialize(c)
if err != nil {
return fmt.Errorf("invalid unit content: %s", err)
}
return nil
}

View File

@@ -0,0 +1,49 @@
// Copyright 2016 CoreOS, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package types
import (
"errors"
"net/url"
"github.com/vincent-petithory/dataurl"
)
var (
ErrInvalidScheme = errors.New("invalid url scheme")
)
func validateURL(s string) error {
// Empty url is valid, indicates an empty file
if s == "" {
return nil
}
u, err := url.Parse(s)
if err != nil {
return err
}
switch u.Scheme {
case "http", "https", "oem", "tftp", "s3":
return nil
case "data":
if _, err := dataurl.DecodeString(s); err != nil {
return err
}
return nil
default:
return ErrInvalidScheme
}
}

View File

@@ -17,7 +17,6 @@ package types
import (
"crypto"
"encoding/hex"
"encoding/json"
"errors"
"strings"
@@ -30,44 +29,55 @@ var (
ErrHashUnrecognized = errors.New("unrecognized hash function")
)
type Hash struct {
Function string
Sum string
}
func (h *Hash) UnmarshalJSON(data []byte) error {
var th string
if err := json.Unmarshal(data, &th); err != nil {
return err
// HashParts will return the sum and function (in that order) of the hash stored
// in this Verification, or an error if there is an issue during parsing.
func (v Verification) HashParts() (string, string, error) {
if v.Hash == nil {
// The hash can be nil
return "", "", nil
}
parts := strings.SplitN(th, "-", 2)
parts := strings.SplitN(*v.Hash, "-", 2)
if len(parts) != 2 {
return ErrHashMalformed
return "", "", ErrHashMalformed
}
h.Function = parts[0]
h.Sum = parts[1]
return nil
return parts[0], parts[1], nil
}
func (h Hash) MarshalJSON() ([]byte, error) {
return []byte(`"` + h.Function + "-" + h.Sum + `"`), nil
}
func (v Verification) Validate() report.Report {
r := report.Report{}
func (h Hash) Validate() report.Report {
if v.Hash == nil {
// The hash can be nil
return r
}
function, sum, err := v.HashParts()
if err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
return r
}
var hash crypto.Hash
switch h.Function {
switch function {
case "sha512":
hash = crypto.SHA512
default:
return report.ReportFromError(ErrHashUnrecognized, report.EntryError)
r.Add(report.Entry{
Message: ErrHashUnrecognized.Error(),
Kind: report.EntryError,
})
return r
}
if len(h.Sum) != hex.EncodedLen(hash.Size()) {
return report.ReportFromError(ErrHashWrongSize, report.EntryError)
if len(sum) != hex.EncodedLen(hash.Size()) {
r.Add(report.Entry{
Message: ErrHashWrongSize.Error(),
Kind: report.EntryError,
})
}
return report.Report{}
return r
}

View File

@@ -54,8 +54,9 @@ type AstNode interface {
}
// Validate walks down a struct tree calling Validate on every node that implements it, building
// A report of all the errors, warnings, info, and deprecations it encounters
func Validate(vObj reflect.Value, ast AstNode, source io.ReadSeeker) (r report.Report) {
// A report of all the errors, warnings, info, and deprecations it encounters. If checkUnusedKeys
// is true, Validate will generate warnings for unused keys in the ast, otherwise it will not.
func Validate(vObj reflect.Value, ast AstNode, source io.ReadSeeker, checkUnusedKeys bool) (r report.Report) {
if !vObj.IsValid() {
return
}
@@ -85,11 +86,11 @@ func Validate(vObj reflect.Value, ast AstNode, source io.ReadSeeker) (r report.R
switch vObj.Kind() {
case reflect.Ptr:
sub_report := Validate(vObj.Elem(), ast, source)
sub_report := Validate(vObj.Elem(), ast, source, checkUnusedKeys)
sub_report.AddPosition(line, col, "")
r.Merge(sub_report)
case reflect.Struct:
sub_report := validateStruct(vObj, ast, source)
sub_report := validateStruct(vObj, ast, source, checkUnusedKeys)
sub_report.AddPosition(line, col, "")
r.Merge(sub_report)
case reflect.Slice:
@@ -100,7 +101,7 @@ func Validate(vObj reflect.Value, ast AstNode, source io.ReadSeeker) (r report.R
sub_node = n
}
}
sub_report := Validate(vObj.Index(i), sub_node, source)
sub_report := Validate(vObj.Index(i), sub_node, source, checkUnusedKeys)
sub_report.AddPosition(line, col, "")
r.Merge(sub_report)
}
@@ -109,7 +110,7 @@ func Validate(vObj reflect.Value, ast AstNode, source io.ReadSeeker) (r report.R
}
func ValidateWithoutSource(cfg reflect.Value) (report report.Report) {
return Validate(cfg, nil, nil)
return Validate(cfg, nil, nil, false)
}
type field struct {
@@ -137,7 +138,7 @@ func getFields(vObj reflect.Value) []field {
return ret
}
func validateStruct(vObj reflect.Value, ast AstNode, source io.ReadSeeker) report.Report {
func validateStruct(vObj reflect.Value, ast AstNode, source io.ReadSeeker, checkUnusedKeys bool) report.Report {
r := report.Report{}
// isFromObject will be true if this struct was unmarshalled from a JSON object.
@@ -173,17 +174,28 @@ func validateStruct(vObj reflect.Value, ast AstNode, source io.ReadSeeker) repor
src = source
}
}
sub_report := Validate(f.Value, sub_node, src)
// Default to deepest node if the node's type isn't an object,
// such as when a json string actually unmarshal to structs (like with version)
line, col := 0, 0
if ast != nil {
line, col, _ = ast.ValueLineCol(src)
}
// If there's a Validate<Name> func for the given field, call it
funct := vObj.MethodByName("Validate" + f.Type.Name)
if funct.IsValid() {
res := funct.Call(nil)
sub_report := res[0].Interface().(report.Report)
sub_report.AddPosition(line, col, "")
r.Merge(sub_report)
}
sub_report := Validate(f.Value, sub_node, src, checkUnusedKeys)
sub_report.AddPosition(line, col, "")
r.Merge(sub_report)
}
if !isFromObject {
if !isFromObject || !checkUnusedKeys {
// If this struct was not unmarshalled from a JSON object, there cannot be unused keys.
return r
}

View File

@@ -144,6 +144,7 @@ func getAttrList(path string, attrList attrList, attrBuf []byte, options uint) (
uintptr(options),
0,
)
use(unsafe.Pointer(_p0))
if e1 != 0 {
return nil, e1
}
@@ -196,6 +197,7 @@ func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
bufsize = unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
}
r0, _, e1 := Syscall(SYS_GETFSSTAT64, uintptr(_p0), bufsize, uintptr(flags))
use(unsafe.Pointer(_p0))
n = int(r0)
if e1 != 0 {
err = e1

View File

@@ -109,6 +109,7 @@ func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
bufsize = unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
}
r0, _, e1 := Syscall(SYS_GETFSSTAT, uintptr(_p0), bufsize, uintptr(flags))
use(unsafe.Pointer(_p0))
n = int(r0)
if e1 != 0 {
err = e1

View File

@@ -129,6 +129,7 @@ func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
bufsize = unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
}
r0, _, e1 := Syscall(SYS_GETFSSTAT, uintptr(_p0), bufsize, uintptr(flags))
use(unsafe.Pointer(_p0))
n = int(r0)
if e1 != 0 {
err = e1

View File

@@ -60,6 +60,15 @@ func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
return openat(dirfd, path, flags|O_LARGEFILE, mode)
}
//sys ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error)
func Ppoll(fds []PollFd, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
if len(fds) == 0 {
return ppoll(nil, 0, timeout, sigmask)
}
return ppoll(&fds[0], len(fds), timeout, sigmask)
}
//sys readlinkat(dirfd int, path string, buf []byte) (n int, err error)
func Readlink(path string, buf []byte) (n int, err error) {
@@ -1043,8 +1052,6 @@ func Munmap(b []byte) (err error) {
// Newfstatat
// Nfsservctl
// Personality
// Poll
// Ppoll
// Pselect6
// Ptrace
// Putpmsg

View File

@@ -388,3 +388,12 @@ func (msghdr *Msghdr) SetControllen(length int) {
func (cmsg *Cmsghdr) SetLen(length int) {
cmsg.Len = uint32(length)
}
//sys poll(fds *PollFd, nfds int, timeout int) (n int, err error)
func Poll(fds []PollFd, timeout int) (n int, err error) {
if len(fds) == 0 {
return poll(nil, 0, timeout)
}
return poll(&fds[0], len(fds), timeout)
}

View File

@@ -146,3 +146,12 @@ func (msghdr *Msghdr) SetControllen(length int) {
func (cmsg *Cmsghdr) SetLen(length int) {
cmsg.Len = uint64(length)
}
//sys poll(fds *PollFd, nfds int, timeout int) (n int, err error)
func Poll(fds []PollFd, timeout int) (n int, err error) {
if len(fds) == 0 {
return poll(nil, 0, timeout)
}
return poll(&fds[0], len(fds), timeout)
}

View File

@@ -252,3 +252,12 @@ func (msghdr *Msghdr) SetControllen(length int) {
func (cmsg *Cmsghdr) SetLen(length int) {
cmsg.Len = uint32(length)
}
//sys poll(fds *PollFd, nfds int, timeout int) (n int, err error)
func Poll(fds []PollFd, timeout int) (n int, err error) {
if len(fds) == 0 {
return poll(nil, 0, timeout)
}
return poll(&fds[0], len(fds), timeout)
}

View File

@@ -178,3 +178,15 @@ const (
SYS_EPOLL_CREATE = 1042
SYS_EPOLL_WAIT = 1069
)
func Poll(fds []PollFd, timeout int) (n int, err error) {
var ts *Timespec
if timeout >= 0 {
ts = new(Timespec)
*ts = NsecToTimespec(int64(timeout) * 1e6)
}
if len(fds) == 0 {
return ppoll(nil, 0, ts, nil)
}
return ppoll(&fds[0], len(fds), ts, nil)
}

View File

@@ -204,3 +204,12 @@ func (msghdr *Msghdr) SetControllen(length int) {
func (cmsg *Cmsghdr) SetLen(length int) {
cmsg.Len = uint64(length)
}
//sys poll(fds *PollFd, nfds int, timeout int) (n int, err error)
func Poll(fds []PollFd, timeout int) (n int, err error) {
if len(fds) == 0 {
return poll(nil, 0, timeout)
}
return poll(&fds[0], len(fds), timeout)
}

View File

@@ -124,3 +124,12 @@ func Pipe2(p []int, flags int) (err error) {
p[1] = int(pp[1])
return
}
//sys poll(fds *PollFd, nfds int, timeout int) (n int, err error)
func Poll(fds []PollFd, timeout int) (n int, err error) {
if len(fds) == 0 {
return poll(nil, 0, timeout)
}
return poll(&fds[0], len(fds), timeout)
}

View File

@@ -318,3 +318,12 @@ func Shutdown(s, how int) error {
}
return nil
}
//sys poll(fds *PollFd, nfds int, timeout int) (n int, err error)
func Poll(fds []PollFd, timeout int) (n int, err error) {
if len(fds) == 0 {
return poll(nil, 0, timeout)
}
return poll(&fds[0], len(fds), timeout)
}

View File

@@ -111,6 +111,7 @@ func Getfsstat(buf []Statfs_t, flags int) (n int, err error) {
bufsize = unsafe.Sizeof(Statfs_t{}) * uintptr(len(buf))
}
r0, _, e1 := Syscall(SYS_GETFSSTAT, uintptr(_p0), bufsize, uintptr(flags))
use(unsafe.Pointer(_p0))
n = int(r0)
if e1 != 0 {
err = e1

View File

@@ -72,18 +72,20 @@ func ParseDirent(buf []byte, max int, names []string) (consumed int, count int,
return origlen - len(buf), count, names
}
func pipe() (r uintptr, w uintptr, err uintptr)
//sysnb pipe(p *[2]_C_int) (n int, err error)
func Pipe(p []int) (err error) {
if len(p) != 2 {
return EINVAL
}
r0, w0, e1 := pipe()
if e1 != 0 {
err = syscall.Errno(e1)
var pp [2]_C_int
n, err := pipe(&pp)
if n != 0 {
return err
}
p[0], p[1] = int(r0), int(w0)
return
p[0] = int(pp[0])
p[1] = int(pp[1])
return nil
}
func (sa *SockaddrInet4) sockaddr() (unsafe.Pointer, _Socklen, error) {
@@ -269,24 +271,34 @@ func (w WaitStatus) StopSignal() syscall.Signal {
func (w WaitStatus) TrapCause() int { return -1 }
func wait4(pid uintptr, wstatus *WaitStatus, options uintptr, rusage *Rusage) (wpid uintptr, err uintptr)
//sys wait4(pid int32, statusp *_C_int, options int, rusage *Rusage) (wpid int32, err error)
func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (wpid int, err error) {
r0, e1 := wait4(uintptr(pid), wstatus, uintptr(options), rusage)
if e1 != 0 {
err = syscall.Errno(e1)
func Wait4(pid int, wstatus *WaitStatus, options int, rusage *Rusage) (int, error) {
var status _C_int
rpid, err := wait4(int32(pid), &status, options, rusage)
wpid := int(rpid)
if wpid == -1 {
return wpid, err
}
return int(r0), err
if wstatus != nil {
*wstatus = WaitStatus(status)
}
return wpid, nil
}
func gethostname() (name string, err uintptr)
//sys gethostname(buf []byte) (n int, err error)
func Gethostname() (name string, err error) {
name, e1 := gethostname()
if e1 != 0 {
err = syscall.Errno(e1)
var buf [MaxHostNameLen]byte
n, err := gethostname(buf[:])
if n != 0 {
return "", err
}
return name, err
n = clen(buf[:])
if n < 1 {
return "", EFAULT
}
return string(buf[:n]), nil
}
//sys utimes(path string, times *[2]Timeval) (err error)

View File

@@ -24,6 +24,7 @@ package unix
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netpacket/packet.h>
#include <poll.h>
#include <signal.h>
#include <stdio.h>
#include <sys/epoll.h>
@@ -430,6 +431,20 @@ const (
AT_SYMLINK_NOFOLLOW = C.AT_SYMLINK_NOFOLLOW
)
type PollFd C.struct_pollfd
const (
POLLIN = C.POLLIN
POLLPRI = C.POLLPRI
POLLOUT = C.POLLOUT
POLLRDHUP = C.POLLRDHUP
POLLERR = C.POLLERR
POLLHUP = C.POLLHUP
POLLNVAL = C.POLLNVAL
)
type Sigset_t C.sigset_t
// Terminal handling
type Termios C.termios_t

View File

@@ -22,6 +22,7 @@ package unix
#define __USE_LEGACY_PROTOTYPES__ // iovec
#include <dirent.h>
#include <fcntl.h>
#include <netdb.h>
#include <limits.h>
#include <signal.h>
#include <termios.h>
@@ -81,6 +82,7 @@ const (
sizeofLong = C.sizeof_long
sizeofLongLong = C.sizeof_longlong
PathMax = C.PATH_MAX
MaxHostNameLen = C.MAXHOSTNAMELEN
)
// Basic types

View File

@@ -216,6 +216,7 @@ const (
CLONE_FILES = 0x400
CLONE_FS = 0x200
CLONE_IO = 0x80000000
CLONE_NEWCGROUP = 0x2000000
CLONE_NEWIPC = 0x8000000
CLONE_NEWNET = 0x40000000
CLONE_NEWNS = 0x20000

View File

@@ -216,6 +216,7 @@ const (
CLONE_FILES = 0x400
CLONE_FS = 0x200
CLONE_IO = 0x80000000
CLONE_NEWCGROUP = 0x2000000
CLONE_NEWIPC = 0x8000000
CLONE_NEWNET = 0x40000000
CLONE_NEWNS = 0x20000

View File

@@ -212,6 +212,7 @@ const (
CLONE_FILES = 0x400
CLONE_FS = 0x200
CLONE_IO = 0x80000000
CLONE_NEWCGROUP = 0x2000000
CLONE_NEWIPC = 0x8000000
CLONE_NEWNET = 0x40000000
CLONE_NEWNS = 0x20000

View File

@@ -222,6 +222,7 @@ const (
CLONE_FILES = 0x400
CLONE_FS = 0x200
CLONE_IO = 0x80000000
CLONE_NEWCGROUP = 0x2000000
CLONE_NEWIPC = 0x8000000
CLONE_NEWNET = 0x40000000
CLONE_NEWNS = 0x20000

View File

@@ -221,6 +221,7 @@ const (
CLONE_FILES = 0x400
CLONE_FS = 0x200
CLONE_IO = 0x80000000
CLONE_NEWCGROUP = 0x2000000
CLONE_NEWIPC = 0x8000000
CLONE_NEWNET = 0x40000000
CLONE_NEWNS = 0x20000

View File

@@ -221,6 +221,7 @@ const (
CLONE_FILES = 0x400
CLONE_FS = 0x200
CLONE_IO = 0x80000000
CLONE_NEWCGROUP = 0x2000000
CLONE_NEWIPC = 0x8000000
CLONE_NEWNET = 0x40000000
CLONE_NEWNS = 0x20000

View File

@@ -223,6 +223,7 @@ const (
CLONE_FILES = 0x400
CLONE_FS = 0x200
CLONE_IO = 0x80000000
CLONE_NEWCGROUP = 0x2000000
CLONE_NEWIPC = 0x8000000
CLONE_NEWNET = 0x40000000
CLONE_NEWNS = 0x20000

View File

@@ -222,6 +222,7 @@ const (
CLONE_FILES = 0x400
CLONE_FS = 0x200
CLONE_IO = 0x80000000
CLONE_NEWCGROUP = 0x2000000
CLONE_NEWIPC = 0x8000000
CLONE_NEWNET = 0x40000000
CLONE_NEWNS = 0x20000

View File

@@ -53,6 +53,17 @@ func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func readlinkat(dirfd int, path string, buf []byte) (n int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
@@ -1636,3 +1647,14 @@ func Utime(path string, buf *Utimbuf) (err error) {
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func poll(fds *PollFd, nfds int, timeout int) (n int, err error) {
r0, _, e1 := Syscall(SYS_POLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(timeout))
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@@ -53,6 +53,17 @@ func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func readlinkat(dirfd int, path string, buf []byte) (n int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
@@ -1830,3 +1841,14 @@ func pipe2(p *[2]_C_int, flags int) (err error) {
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func poll(fds *PollFd, nfds int, timeout int) (n int, err error) {
r0, _, e1 := Syscall(SYS_POLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(timeout))
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@@ -53,6 +53,17 @@ func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func readlinkat(dirfd int, path string, buf []byte) (n int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
@@ -1737,3 +1748,14 @@ func setrlimit(resource int, rlim *rlimit32) (err error) {
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func poll(fds *PollFd, nfds int, timeout int) (n int, err error) {
r0, _, e1 := Syscall(SYS_POLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(timeout))
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@@ -53,6 +53,17 @@ func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func readlinkat(dirfd int, path string, buf []byte) (n int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)

View File

@@ -53,6 +53,17 @@ func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func readlinkat(dirfd int, path string, buf []byte) (n int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
@@ -1779,3 +1790,14 @@ func stat(path string, st *stat_t) (err error) {
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func poll(fds *PollFd, nfds int, timeout int) (n int, err error) {
r0, _, e1 := Syscall(SYS_POLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(timeout))
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@@ -53,6 +53,17 @@ func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func readlinkat(dirfd int, path string, buf []byte) (n int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
@@ -1779,3 +1790,14 @@ func stat(path string, st *stat_t) (err error) {
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func poll(fds *PollFd, nfds int, timeout int) (n int, err error) {
r0, _, e1 := Syscall(SYS_POLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(timeout))
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@@ -53,6 +53,17 @@ func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func readlinkat(dirfd int, path string, buf []byte) (n int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
@@ -1841,3 +1852,14 @@ func pipe2(p *[2]_C_int, flags int) (err error) {
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func poll(fds *PollFd, nfds int, timeout int) (n int, err error) {
r0, _, e1 := Syscall(SYS_POLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(timeout))
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@@ -53,6 +53,17 @@ func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func readlinkat(dirfd int, path string, buf []byte) (n int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
@@ -1841,3 +1852,14 @@ func pipe2(p *[2]_C_int, flags int) (err error) {
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func poll(fds *PollFd, nfds int, timeout int) (n int, err error) {
r0, _, e1 := Syscall(SYS_POLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(timeout))
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}

View File

@@ -53,6 +53,17 @@ func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
r0, _, e1 := Syscall6(SYS_PPOLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(unsafe.Pointer(timeout)), uintptr(unsafe.Pointer(sigmask)), 0, 0)
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func readlinkat(dirfd int, path string, buf []byte) (n int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
@@ -1621,3 +1632,14 @@ func pipe2(p *[2]_C_int, flags int) (err error) {
}
return
}
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
func poll(fds *PollFd, nfds int, timeout int) (n int, err error) {
r0, _, e1 := Syscall(SYS_POLL, uintptr(unsafe.Pointer(fds)), uintptr(nfds), uintptr(timeout))
n = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}

Some files were not shown because too many files have changed in this diff Show More