Vendor Go module dependencies

This commit is contained in:
Dalton Hubble
2019-03-10 23:59:34 -07:00
parent a7c13fbe5b
commit 5bfcb24003
108 changed files with 3811 additions and 2448 deletions

View File

@@ -21,7 +21,7 @@ import (
"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_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
@@ -62,13 +62,13 @@ func Parse(data []byte) (types.Config, astnode.AstNode, report.Report) {
return cfg, root, r
}
// ConvertAs2_0 will convert a golang struct representing a Container Linux
// Convert 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
// Convert 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 astnode.AstNode) (ignTypes.Config, report.Report) {
func Convert(in types.Config, p string, ast astnode.AstNode) (ignTypes.Config, report.Report) {
if !platform.IsSupportedPlatform(p) {
r := report.Report{}
r.Add(report.Entry{
@@ -77,5 +77,5 @@ func ConvertAs2_0(in types.Config, p string, ast astnode.AstNode) (ignTypes.Conf
})
return ignTypes.Config{}, r
}
return types.ConvertAs2_0(in, p, ast)
return types.Convert(in, p, ast)
}

View File

@@ -23,6 +23,7 @@ const (
OpenStackMetadata = "openstack-metadata"
VagrantVirtualbox = "vagrant-virtualbox"
CloudStackConfigDrive = "cloudstack-configdrive"
Custom = "custom"
)
var Platforms = []string{
@@ -34,6 +35,7 @@ var Platforms = []string{
OpenStackMetadata,
VagrantVirtualbox,
CloudStackConfigDrive,
Custom,
}
func IsSupportedPlatform(platform string) bool {

View File

@@ -76,6 +76,13 @@ var platformTemplatingMap = map[string]map[string]string{
platform.CloudStackConfigDrive: {
fieldHostname: "CLOUDSTACK_LOCAL_HOSTNAME",
},
platform.Custom: {
fieldHostname: "COREOS_CUSTOM_HOSTNAME",
fieldV4Private: "COREOS_CUSTOM_PRIVATE_IPV4",
fieldV4Public: "COREOS_CUSTOM_PUBLIC_IPV4",
fieldV6Private: "COREOS_CUSTOM_PRIVATE_IPV6",
fieldV6Public: "COREOS_CUSTOM_PUBLIC_IPV6",
},
}
// HasTemplating returns whether or not any of the environment variables present

View File

@@ -19,12 +19,13 @@ import (
"fmt"
"reflect"
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/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"
iutil "github.com/coreos/container-linux-config-transpiler/internal/util"
"github.com/coreos/ignition/config/validate/astnode"
)
@@ -36,18 +37,19 @@ var (
)
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, p string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, p string) (ignTypes.Config, report.Report, astnode.AstNode) {
if p == platform.OpenStackMetadata || p == platform.CloudStackConfigDrive {
out.Systemd.Units = append(out.Systemd.Units, ignTypes.Unit{
Name: "coreos-metadata.service",
Dropins: []ignTypes.Dropin{{
Dropins: []ignTypes.SystemdDropin{{
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.Unit{
Name: "coreos-metadata-sshkeys@.service",
Dropins: []ignTypes.Dropin{{
Name: "coreos-metadata-sshkeys@.service",
Enabled: iutil.BoolToPtr(true),
Dropins: []ignTypes.SystemdDropin{{
Name: "20-clct-provider-override.conf",
Contents: fmt.Sprintf("[Service]\nEnvironment=COREOS_METADATA_OPT_PROVIDER=--provider=%s", p),
}},
@@ -111,6 +113,9 @@ func getArgs(format, tagName string, e interface{}) []string {
vars = append(vars, getCliArgs(val)...)
} else {
key := et.Field(i).Tag.Get(tagName)
if ev.Field(i).Kind() == reflect.Ptr {
val = reflect.Indirect(ev.Field(i)).Interface()
}
if _, ok := val.(string); ok {
// to handle whitespace characters
val = fmt.Sprintf("%q", val)

View File

@@ -17,7 +17,7 @@ package types
import (
"net/url"
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -38,6 +38,7 @@ type Config struct {
type Ignition struct {
Config IgnitionConfig `yaml:"config"`
Timeouts Timeouts `yaml:"timeouts"`
Security Security `yaml:"security"`
}
type IgnitionConfig struct {
@@ -56,7 +57,7 @@ type Timeouts struct {
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
r := report.Report{}
out.Ignition.Timeouts.HTTPResponseHeaders = in.Ignition.Timeouts.HTTPResponseHeaders
out.Ignition.Timeouts.HTTPTotal = in.Ignition.Timeouts.HTTPTotal

View File

@@ -18,21 +18,21 @@ import (
"reflect"
"github.com/coreos/container-linux-config-transpiler/config/astyaml"
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
type converterFor2_0 func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode)
type converter func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode)
var convertersFor2_0 []converterFor2_0
var converters []converter
func register2_0(f converterFor2_0) {
convertersFor2_0 = append(convertersFor2_0, f)
func register(f converter) {
converters = append(converters, f)
}
func ConvertAs2_0(in Config, platform string, ast astnode.AstNode) (ignTypes.Config, report.Report) {
func Convert(in Config, platform string, ast astnode.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)
@@ -43,13 +43,13 @@ func ConvertAs2_0(in Config, platform string, ast astnode.AstNode) (ignTypes.Con
out := ignTypes.Config{
Ignition: ignTypes.Ignition{
Version: "2.1.0",
Version: "2.2.0",
},
}
r := report.Report{}
for _, convert := range convertersFor2_0 {
for _, convert := range converters {
var subReport report.Report
out, subReport, ast = convert(in, ast, out, platform)
r.Merge(subReport)

View File

@@ -18,7 +18,7 @@ import (
"fmt"
"github.com/alecthomas/units"
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -52,7 +52,7 @@ type Partition struct {
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
r := report.Report{}
for disk_idx, disk := range in.Storage.Disks {
newDisk := ignTypes.Disk{

View File

@@ -18,7 +18,7 @@ import (
"fmt"
"strings"
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -28,13 +28,13 @@ type Docker struct {
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.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.Unit{
Name: "docker.service",
Enable: true,
Dropins: []ignTypes.Dropin{{
Dropins: []ignTypes.SystemdDropin{{
Name: "20-clct-docker.conf",
Contents: contents,
}},

View File

@@ -19,7 +19,7 @@ import (
"fmt"
"github.com/coreos/go-semver/semver"
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -55,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 > 2:
case v.Major == 3 && v.Minor > 3:
return report.ReportFromError(EtcdMinorVersionTooNew, report.EntryWarning)
case v.Major > 3:
return report.ReportFromError(EtcdMajorVersionTooNew, report.EntryError)
@@ -108,18 +108,25 @@ func (etcd *Etcd) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
etcd.Options = o
} else if version.Major == 3 && version.Minor >= 2 {
} else if version.Major == 3 && version.Minor == 2 {
o := Etcd3_2{}
if err := unmarshal(&o); err != nil {
return err
}
etcd.Options = o
} else if version.Major == 3 && version.Minor >= 3 {
o := Etcd3_3{}
if err := unmarshal(&o); err != nil {
return err
}
etcd.Options = o
}
return nil
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
if in.Etcd != nil {
contents, err := etcdContents(*in.Etcd, platform)
if err != nil {
@@ -128,7 +135,7 @@ func init() {
out.Systemd.Units = append(out.Systemd.Units, ignTypes.Unit{
Name: "etcd-member.service",
Enable: true,
Dropins: []ignTypes.Dropin{{
Dropins: []ignTypes.SystemdDropin{{
Name: "20-clct-etcd-member.conf",
Contents: contents,
}},
@@ -155,192 +162,257 @@ func etcdContents(etcd Etcd, platform string) (string, error) {
}
type Etcd3_0 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"`
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"`
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"`
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"`
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"`
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"`
}
type Etcd3_1 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"`
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"`
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"`
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"`
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"`
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 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"`
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 Etcd3_3 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"`
QuotaBackendBytes *int `yaml:"quota_backend_bytes" cli:"quota-backend-bytes"`
MaxTxnOps *int `yaml:"max_txn_ops" cli:"max-txn-ops"`
MaxRequestBytes *int `yaml:"max_request_bytes" cli:"max-request-bytes"`
GrpcKeepaliveMinTime *string `yaml:"grpc_keepalive_min_time" cli:"grpc-keepalive-min-time"`
GrpcKeepaliveInterval *string `yaml:"grpc_keepalive_interval" cli:"grpc-keepalive-interval"`
GrpcKeepaliveTimeout *string `yaml:"grpc_keepalive_timeout" cli:"grpc-keepalive-timeout"`
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 *string `yaml:"auto_compaction_retention" cli:"auto-compaction-retention"`
AutoCompactionMode *string `yaml:"auto_compaction_mode" cli:"auto-compaction-mode"`
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"`
ClientCrlFile *string `yaml:"client_crl_file" cli:"client-crl-file"`
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"`
PeerCrlFile *string `yaml:"peer_crl_file" cli:"peer-crl-file"`
PeerTrustedCaFile *string `yaml:"peer_trusted_ca_file" cli:"peer-trusted-ca-file"`
PeerAutoTls *bool `yaml:"peer_auto_tls" cli:"peer-auto-tls"`
PeerCertAllowedCn *string `yaml:"peer_cert_allowed_cn" cli:"peer-cert-allowed-cn"`
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"`
ListenMetricsUrls *string `yaml:"listen_metrics_urls" cli:"listen-metrics-urls"`
ExperimentalInitialCorruptCheck *bool `yaml:"experimental_initial_corrupt_check" cli:"experimental-initial-corrupt-check"`
ExperimentalCorruptCheckTime *string `yaml:"experimental_corrupt_check_time" cli:"experimental-corrupt-check-time"`
ExperimentalEnableV2V3 *string `yaml:"experimental_enable_v2v3" cli:"experimental-enable-v2v3"`
}
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"`
CertFile string `yaml:"cert_file" cli:"cert-file"`
ClientCertAuth bool `yaml:"client_cert_auth" cli:"client-cert-auth"`
CorsOrigins string `yaml:"cors" cli:"cors"`
DataDir string `yaml:"data_dir" cli:"data-dir"`
Debug bool `yaml:"debug" cli:"debug"`
Discovery string `yaml:"discovery" cli:"discovery"`
DiscoveryFallback string `yaml:"discovery_fallback" cli:"discovery-fallback"`
DiscoverySRV string `yaml:"discovery_srv" cli:"discovery-srv"`
DiscoveryProxy string `yaml:"discovery_proxy" cli:"discovery-proxy"`
ElectionTimeout int `yaml:"election_timeout" cli:"election-timeout"`
EnablePprof bool `yaml:"enable_pprof" cli:"enable-pprof"`
ForceNewCluster bool `yaml:"force_new_cluster" cli:"force-new-cluster"`
HeartbeatInterval int `yaml:"heartbeat_interval" cli:"heartbeat-interval"`
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"`
KeyFile string `yaml:"key_file" cli:"key-file"`
ListenClientURLs string `yaml:"listen_client_urls" cli:"listen-client-urls"`
ListenPeerURLs string `yaml:"listen_peer_urls" cli:"listen-peer-urls"`
LogPackageLevels string `yaml:"log_package_levels" cli:"log-package-levels"`
MaxSnapshots int `yaml:"max_snapshots" cli:"max-snapshots"`
MaxWALs int `yaml:"max_wals" cli:"max-wals"`
Name string `yaml:"name" cli:"name"`
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"`
Proxy string `yaml:"proxy" cli:"proxy" valid:"^(on|off|readonly)$"`
ProxyDialTimeout int `yaml:"proxy_dial_timeout" cli:"proxy-dial-timeout"`
ProxyFailureWait int `yaml:"proxy_failure_wait" cli:"proxy-failure-wait"`
ProxyReadTimeout int `yaml:"proxy_read_timeout" cli:"proxy-read-timeout"`
ProxyRefreshInterval int `yaml:"proxy_refresh_interval" cli:"proxy-refresh-interval"`
ProxyWriteTimeout int `yaml:"proxy_write_timeout" cli:"proxy-write-timeout"`
SnapshotCount int `yaml:"snapshot_count" cli:"snapshot-count"`
StrictReconfigCheck bool `yaml:"strict_reconfig_check" cli:"strict-reconfig-check"`
TrustedCAFile string `yaml:"trusted_ca_file" cli:"trusted-ca-file"`
WalDir string `yaml:"wal_dir" cli:"wal-dir"`
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"`
CertFile *string `yaml:"cert_file" cli:"cert-file"`
ClientCertAuth *bool `yaml:"client_cert_auth" cli:"client-cert-auth"`
CorsOrigins *string `yaml:"cors" cli:"cors"`
DataDir *string `yaml:"data_dir" cli:"data-dir"`
Debug *bool `yaml:"debug" cli:"debug"`
Discovery *string `yaml:"discovery" cli:"discovery"`
DiscoveryFallback *string `yaml:"discovery_fallback" cli:"discovery-fallback"`
DiscoverySRV *string `yaml:"discovery_srv" cli:"discovery-srv"`
DiscoveryProxy *string `yaml:"discovery_proxy" cli:"discovery-proxy"`
ElectionTimeout *int `yaml:"election_timeout" cli:"election-timeout"`
EnablePprof *bool `yaml:"enable_pprof" cli:"enable-pprof"`
ForceNewCluster *bool `yaml:"force_new_cluster" cli:"force-new-cluster"`
HeartbeatInterval *int `yaml:"heartbeat_interval" cli:"heartbeat-interval"`
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"`
KeyFile *string `yaml:"key_file" cli:"key-file"`
ListenClientURLs *string `yaml:"listen_client_urls" cli:"listen-client-urls"`
ListenPeerURLs *string `yaml:"listen_peer_urls" cli:"listen-peer-urls"`
LogPackageLevels *string `yaml:"log_package_levels" cli:"log-package-levels"`
MaxSnapshots *int `yaml:"max_snapshots" cli:"max-snapshots"`
MaxWALs *int `yaml:"max_wals" cli:"max-wals"`
Name *string `yaml:"name" cli:"name"`
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"`
Proxy *string `yaml:"proxy" cli:"proxy" valid:"^(on|off|readonly)$"`
ProxyDialTimeout *int `yaml:"proxy_dial_timeout" cli:"proxy-dial-timeout"`
ProxyFailureWait *int `yaml:"proxy_failure_wait" cli:"proxy-failure-wait"`
ProxyReadTimeout *int `yaml:"proxy_read_timeout" cli:"proxy-read-timeout"`
ProxyRefreshInterval *int `yaml:"proxy_refresh_interval" cli:"proxy-refresh-interval"`
ProxyWriteTimeout *int `yaml:"proxy_write_timeout" cli:"proxy-write-timeout"`
SnapshotCount *int `yaml:"snapshot_count" cli:"snapshot-count"`
StrictReconfigCheck *bool `yaml:"strict_reconfig_check" cli:"strict-reconfig-check"`
TrustedCAFile *string `yaml:"trusted_ca_file" cli:"trusted-ca-file"`
WalDir *string `yaml:"wal_dir" cli:"wal-dir"`
}

View File

@@ -15,16 +15,32 @@
package types
import (
"errors"
"flag"
"fmt"
"io/ioutil"
"net/url"
"path"
"github.com/coreos/container-linux-config-transpiler/config/astyaml"
"github.com/coreos/container-linux-config-transpiler/internal/util"
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
var (
DefaultFileMode = 0644
DefaultDirMode = 0755
WarningUnsetFileMode = fmt.Errorf("mode unspecified for file, defaulting to %#o", DefaultFileMode)
WarningUnsetDirMode = fmt.Errorf("mode unspecified for directory, defaulting to %#o", DefaultDirMode)
ErrTooManyFileSources = errors.New("only one of the following can be set: local, inline, remote.url")
)
type FileUser struct {
Id *int `yaml:"id"`
Name string `yaml:"name"`
@@ -38,15 +54,18 @@ type FileGroup struct {
type File struct {
Filesystem string `yaml:"filesystem"`
Path string `yaml:"path"`
User FileUser `yaml:"user"`
Group FileGroup `yaml:"group"`
Mode int `yaml:"mode"`
User *FileUser `yaml:"user"`
Group *FileGroup `yaml:"group"`
Mode *int `yaml:"mode"`
Contents FileContents `yaml:"contents"`
Overwrite *bool `yaml:"overwrite"`
Append bool `yaml:"append"`
}
type FileContents struct {
Remote Remote `yaml:"remote"`
Inline string `yaml:"inline"`
Local string `yaml:"local"`
}
type Remote struct {
@@ -56,45 +75,87 @@ type Remote struct {
}
type Directory struct {
Filesystem string `yaml:"filesystem"`
Path string `yaml:"path"`
User FileUser `yaml:"user"`
Group FileGroup `yaml:"group"`
Mode int `yaml:"mode"`
Filesystem string `yaml:"filesystem"`
Path string `yaml:"path"`
User *FileUser `yaml:"user"`
Group *FileGroup `yaml:"group"`
Mode *int `yaml:"mode"`
Overwrite *bool `yaml:"overwrite"`
}
type Link struct {
Filesystem string `yaml:"filesystem"`
Path string `yaml:"path"`
User FileUser `yaml:"user"`
Group FileGroup `yaml:"group"`
Hard bool `yaml:"hard"`
Target string `yaml:"target"`
Filesystem string `yaml:"filesystem"`
Path string `yaml:"path"`
User *FileUser `yaml:"user"`
Group *FileGroup `yaml:"group"`
Hard bool `yaml:"hard"`
Target string `yaml:"target"`
Overwrite *bool `yaml:"overwrite"`
}
func (f File) ValidateMode() report.Report {
if f.Mode == nil {
return report.ReportFromError(WarningUnsetFileMode, report.EntryWarning)
}
return report.Report{}
}
func (d Directory) ValidateMode() report.Report {
if d.Mode == nil {
return report.ReportFromError(WarningUnsetDirMode, report.EntryWarning)
}
return report.Report{}
}
func (fc FileContents) Validate() report.Report {
i := 0
if fc.Remote.Url != "" {
i++
}
if fc.Inline != "" {
i++
}
if fc.Local != "" {
i++
}
if i > 1 {
return report.ReportFromError(ErrTooManyFileSources, report.EntryError)
}
return report.Report{}
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
r := report.Report{}
files_node, _ := getNodeChildPath(ast, "storage", "files")
for i, file := range in.Storage.Files {
if file.Mode == nil {
file.Mode = util.IntToPtr(DefaultFileMode)
}
file_node, _ := getNodeChild(files_node, i)
newFile := ignTypes.File{
Node: ignTypes.Node{
Filesystem: file.Filesystem,
Path: file.Path,
User: ignTypes.NodeUser{
ID: file.User.Id,
Name: file.User.Name,
},
Group: ignTypes.NodeGroup{
ID: file.Group.Id,
Name: file.Group.Name,
},
Overwrite: file.Overwrite,
},
FileEmbedded1: ignTypes.FileEmbedded1{
Mode: file.Mode,
Mode: file.Mode,
Append: file.Append,
},
}
if file.User != nil {
newFile.User = &ignTypes.NodeUser{
ID: file.User.Id,
Name: file.User.Name,
}
}
if file.Group != nil {
newFile.Group = &ignTypes.NodeGroup{
ID: file.Group.Id,
Name: file.Group.Name,
}
}
if file.Contents.Inline != "" {
newFile.Contents = ignTypes.FileContents{
@@ -105,6 +166,42 @@ func init() {
}
}
if file.Contents.Local != "" {
// The provided local file path is relative to the value of the
// --files-dir flag.
filesDir := flag.Lookup("files-dir")
if filesDir == nil || filesDir.Value.String() == "" {
err := errors.New("local files require setting the --files-dir flag to the directory that contains the file")
flagReport := report.ReportFromError(err, report.EntryError)
if n, err := getNodeChildPath(file_node, "contents", "local"); err == nil {
line, col, _ := n.ValueLineCol(nil)
flagReport.AddPosition(line, col, "")
}
r.Merge(flagReport)
continue
}
localPath := path.Join(filesDir.Value.String(), file.Contents.Local)
contents, err := ioutil.ReadFile(localPath)
if err != nil {
// If the file could not be read, record error and continue.
convertReport := report.ReportFromError(err, report.EntryError)
if n, err := getNodeChildPath(file_node, "contents", "local"); err == nil {
line, col, _ := n.ValueLineCol(nil)
convertReport.AddPosition(line, col, "")
}
r.Merge(convertReport)
continue
}
// Include the contents of the local file as if it were provided inline.
newFile.Contents = ignTypes.FileContents{
Source: (&url.URL{
Scheme: "data",
Opaque: "," + dataurl.Escape(contents),
}).String(),
}
}
if file.Contents.Remote.Url != "" {
source, err := url.Parse(file.Contents.Remote.Url)
if err != nil {
@@ -114,6 +211,7 @@ func init() {
line, col, _ := n.ValueLineCol(nil)
convertReport.AddPosition(line, col, "")
}
r.Merge(convertReport)
continue
}
@@ -145,43 +243,58 @@ func init() {
out.Storage.Files = append(out.Storage.Files, newFile)
}
for _, dir := range in.Storage.Directories {
out.Storage.Directories = append(out.Storage.Directories, ignTypes.Directory{
if dir.Mode == nil {
dir.Mode = util.IntToPtr(DefaultDirMode)
}
newDir := ignTypes.Directory{
Node: ignTypes.Node{
Filesystem: dir.Filesystem,
Path: dir.Path,
User: ignTypes.NodeUser{
ID: dir.User.Id,
Name: dir.User.Name,
},
Group: ignTypes.NodeGroup{
ID: dir.Group.Id,
Name: dir.Group.Name,
},
Overwrite: dir.Overwrite,
},
DirectoryEmbedded1: ignTypes.DirectoryEmbedded1{
Mode: dir.Mode,
},
})
}
if dir.User != nil {
newDir.User = &ignTypes.NodeUser{
ID: dir.User.Id,
Name: dir.User.Name,
}
}
if dir.Group != nil {
newDir.Group = &ignTypes.NodeGroup{
ID: dir.Group.Id,
Name: dir.Group.Name,
}
}
out.Storage.Directories = append(out.Storage.Directories, newDir)
}
for _, link := range in.Storage.Links {
out.Storage.Links = append(out.Storage.Links, ignTypes.Link{
newLink := ignTypes.Link{
Node: ignTypes.Node{
Filesystem: link.Filesystem,
Path: link.Path,
User: ignTypes.NodeUser{
ID: link.User.Id,
Name: link.User.Name,
},
Group: ignTypes.NodeGroup{
ID: link.Group.Id,
Name: link.Group.Name,
},
Overwrite: link.Overwrite,
},
LinkEmbedded1: ignTypes.LinkEmbedded1{
Hard: link.Hard,
Target: link.Target,
},
})
}
if link.User != nil {
newLink.User = &ignTypes.NodeUser{
ID: link.User.Id,
Name: link.User.Name,
}
}
if link.Group != nil {
newLink.Group = &ignTypes.NodeGroup{
ID: link.Group.Id,
Name: link.Group.Name,
}
}
out.Storage.Links = append(out.Storage.Links, newLink)
}
return out, r, ast
})

View File

@@ -15,7 +15,7 @@
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -42,7 +42,7 @@ type Create struct {
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
r := report.Report{}
for _, filesystem := range in.Storage.Filesystems {
newFilesystem := ignTypes.Filesystem{

View File

@@ -6,7 +6,7 @@ import (
"fmt"
"github.com/coreos/go-semver/semver"
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -71,7 +71,7 @@ func (fv FlannelVersion) String() string {
func (f *Flannel) Validate() report.Report {
switch o := f.Options.(type) {
case Flannel0_7:
if o.KubeSubnetMgr && f.NetworkConfig != "" {
if o.KubeSubnetMgr != nil && *o.KubeSubnetMgr && f.NetworkConfig != "" {
return report.ReportFromError(ErrNetConfigProvidedAndKubeMgrSet, report.EntryError)
}
}
@@ -115,7 +115,7 @@ func (flannel *Flannel) UnmarshalYAML(unmarshal func(interface{}) error) error {
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
if in.Flannel != nil {
contents, err := flannelContents(*in.Flannel, platform)
if err != nil {
@@ -124,7 +124,7 @@ func init() {
out.Systemd.Units = append(out.Systemd.Units, ignTypes.Unit{
Name: "flanneld.service",
Enable: true,
Dropins: []ignTypes.Dropin{{
Dropins: []ignTypes.SystemdDropin{{
Name: "20-clct-flannel.conf",
Contents: contents,
}},
@@ -149,10 +149,10 @@ func flannelContents(flannel Flannel, platform string) (string, error) {
if flannel.NetworkConfig != "" {
pre := "ExecStartPre=/usr/bin/etcdctl"
var endpoints string
var etcdCAFile string
var etcdCertFile string
var etcdKeyFile string
var endpoints *string
var etcdCAFile *string
var etcdCertFile *string
var etcdKeyFile *string
switch o := flannel.Options.(type) {
case Flannel0_7:
endpoints = o.EtcdEndpoints
@@ -170,17 +170,17 @@ func flannelContents(flannel Flannel, platform string) (string, error) {
etcdCertFile = o.EtcdCertFile
etcdKeyFile = o.EtcdKeyFile
}
if endpoints != "" {
pre += fmt.Sprintf(" --endpoints=%q", endpoints)
if endpoints != nil {
pre += fmt.Sprintf(" --endpoints=%q", *endpoints)
}
if etcdCAFile != "" {
pre += fmt.Sprintf(" --ca-file=%q", etcdCAFile)
if etcdCAFile != nil {
pre += fmt.Sprintf(" --ca-file=%q", *etcdCAFile)
}
if etcdCertFile != "" {
pre += fmt.Sprintf(" --cert-file=%q", etcdCertFile)
if etcdCertFile != nil {
pre += fmt.Sprintf(" --cert-file=%q", *etcdCertFile)
}
if etcdKeyFile != "" {
pre += fmt.Sprintf(" --key-file=%q", etcdKeyFile)
if etcdKeyFile != nil {
pre += fmt.Sprintf(" --key-file=%q", *etcdKeyFile)
}
pre += fmt.Sprintf(" set /coreos.com/network/config %q", flannel.NetworkConfig)
unit.Service.Add(pre)
@@ -192,42 +192,42 @@ func flannelContents(flannel Flannel, platform string) (string, error) {
// Flannel0_7 represents flannel options for version 0.7.x. Don't embed Flannel0_6 because
// the yaml parser doesn't handle embedded structs
type Flannel0_7 struct {
EtcdUsername string `yaml:"etcd_username" cli:"etcd-username"`
EtcdPassword string `yaml:"etcd_password" cli:"etcd-password"`
EtcdEndpoints string `yaml:"etcd_endpoints" cli:"etcd-endpoints"`
EtcdCAFile string `yaml:"etcd_cafile" cli:"etcd-cafile"`
EtcdCertFile string `yaml:"etcd_certfile" cli:"etcd-certfile"`
EtcdKeyFile string `yaml:"etcd_keyfile" cli:"etcd-keyfile"`
EtcdPrefix string `yaml:"etcd_prefix" cli:"etcd-prefix"`
IPMasq string `yaml:"ip_masq" cli:"ip-masq"`
SubnetFile string `yaml:"subnet_file" cli:"subnet-file"`
Iface string `yaml:"interface" cli:"iface"`
PublicIP string `yaml:"public_ip" cli:"public-ip"`
KubeSubnetMgr bool `yaml:"kube_subnet_mgr" cli:"kube-subnet-mgr"`
EtcdUsername *string `yaml:"etcd_username" cli:"etcd-username"`
EtcdPassword *string `yaml:"etcd_password" cli:"etcd-password"`
EtcdEndpoints *string `yaml:"etcd_endpoints" cli:"etcd-endpoints"`
EtcdCAFile *string `yaml:"etcd_cafile" cli:"etcd-cafile"`
EtcdCertFile *string `yaml:"etcd_certfile" cli:"etcd-certfile"`
EtcdKeyFile *string `yaml:"etcd_keyfile" cli:"etcd-keyfile"`
EtcdPrefix *string `yaml:"etcd_prefix" cli:"etcd-prefix"`
IPMasq *string `yaml:"ip_masq" cli:"ip-masq"`
SubnetFile *string `yaml:"subnet_file" cli:"subnet-file"`
Iface *string `yaml:"interface" cli:"iface"`
PublicIP *string `yaml:"public_ip" cli:"public-ip"`
KubeSubnetMgr *bool `yaml:"kube_subnet_mgr" cli:"kube-subnet-mgr"`
}
type Flannel0_6 struct {
EtcdUsername string `yaml:"etcd_username" cli:"etcd-username"`
EtcdPassword string `yaml:"etcd_password" cli:"etcd-password"`
EtcdEndpoints string `yaml:"etcd_endpoints" cli:"etcd-endpoints"`
EtcdCAFile string `yaml:"etcd_cafile" cli:"etcd-cafile"`
EtcdCertFile string `yaml:"etcd_certfile" cli:"etcd-certfile"`
EtcdKeyFile string `yaml:"etcd_keyfile" cli:"etcd-keyfile"`
EtcdPrefix string `yaml:"etcd_prefix" cli:"etcd-prefix"`
IPMasq string `yaml:"ip_masq" cli:"ip-masq"`
SubnetFile string `yaml:"subnet_file" cli:"subnet-file"`
Iface string `yaml:"interface" cli:"iface"`
PublicIP string `yaml:"public_ip" cli:"public-ip"`
EtcdUsername *string `yaml:"etcd_username" cli:"etcd-username"`
EtcdPassword *string `yaml:"etcd_password" cli:"etcd-password"`
EtcdEndpoints *string `yaml:"etcd_endpoints" cli:"etcd-endpoints"`
EtcdCAFile *string `yaml:"etcd_cafile" cli:"etcd-cafile"`
EtcdCertFile *string `yaml:"etcd_certfile" cli:"etcd-certfile"`
EtcdKeyFile *string `yaml:"etcd_keyfile" cli:"etcd-keyfile"`
EtcdPrefix *string `yaml:"etcd_prefix" cli:"etcd-prefix"`
IPMasq *string `yaml:"ip_masq" cli:"ip-masq"`
SubnetFile *string `yaml:"subnet_file" cli:"subnet-file"`
Iface *string `yaml:"interface" cli:"iface"`
PublicIP *string `yaml:"public_ip" cli:"public-ip"`
}
type Flannel0_5 struct {
EtcdEndpoints string `yaml:"etcd_endpoints" cli:"etcd-endpoints"`
EtcdCAFile string `yaml:"etcd_cafile" cli:"etcd-cafile"`
EtcdCertFile string `yaml:"etcd_certfile" cli:"etcd-certfile"`
EtcdKeyFile string `yaml:"etcd_keyfile" cli:"etcd-keyfile"`
EtcdPrefix string `yaml:"etcd_prefix" cli:"etcd-prefix"`
IPMasq string `yaml:"ip_masq" cli:"ip-masq"`
SubnetFile string `yaml:"subnet_file" cli:"subnet-file"`
Iface string `yaml:"interface" cli:"iface"`
PublicIP string `yaml:"public_ip" cli:"public-ip"`
EtcdEndpoints *string `yaml:"etcd_endpoints" cli:"etcd-endpoints"`
EtcdCAFile *string `yaml:"etcd_cafile" cli:"etcd-cafile"`
EtcdCertFile *string `yaml:"etcd_certfile" cli:"etcd-certfile"`
EtcdKeyFile *string `yaml:"etcd_keyfile" cli:"etcd-keyfile"`
EtcdPrefix *string `yaml:"etcd_prefix" cli:"etcd-prefix"`
IPMasq *string `yaml:"ip_masq" cli:"ip-masq"`
SubnetFile *string `yaml:"subnet_file" cli:"subnet-file"`
Iface *string `yaml:"interface" cli:"iface"`
PublicIP *string `yaml:"public_ip" cli:"public-ip"`
}

View File

@@ -33,29 +33,36 @@ var (
)
type Locksmith struct {
RebootStrategy string `yaml:"reboot_strategy" locksmith:"REBOOT_STRATEGY"`
WindowStart string `yaml:"window_start" locksmith:"LOCKSMITHD_REBOOT_WINDOW_START"`
WindowLength string `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"`
RebootStrategy *string `yaml:"reboot_strategy" locksmith:"REBOOT_STRATEGY"`
WindowStart *string `yaml:"window_start" locksmith:"LOCKSMITHD_REBOOT_WINDOW_START"`
WindowLength *string `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=%v", "locksmith", l)
}
func nilOrEmpty(s *string) bool {
return s == nil || *s == ""
}
func (l Locksmith) Validate() report.Report {
if (l.WindowStart != "" && l.WindowLength == "") || (l.WindowStart == "" && l.WindowLength != "") {
if (!nilOrEmpty(l.WindowStart) && nilOrEmpty(l.WindowLength)) || (nilOrEmpty(l.WindowStart) && !nilOrEmpty(l.WindowLength)) {
return report.ReportFromError(ErrMissingStartOrLength, report.EntryError)
}
return report.Report{}
}
func (l Locksmith) ValidateRebootStrategy() report.Report {
switch strings.ToLower(l.RebootStrategy) {
if nilOrEmpty(l.RebootStrategy) {
return report.Report{}
}
switch strings.ToLower(*l.RebootStrategy) {
case "reboot", "etcd-lock", "off":
return report.Report{}
default:
@@ -64,16 +71,16 @@ func (l Locksmith) ValidateRebootStrategy() report.Report {
}
func (l Locksmith) ValidateWindowStart() report.Report {
if l.WindowStart == "" {
if nilOrEmpty(l.WindowStart) {
return report.Report{}
}
var day string
var t string
_, err := fmt.Sscanf(l.WindowStart, "%s %s", &day, &t)
_, err := fmt.Sscanf(*l.WindowStart, "%s %s", &day, &t)
if err != nil {
day = "not-present"
t = l.WindowStart
t = *l.WindowStart
}
switch strings.ToLower(day) {
@@ -92,10 +99,10 @@ func (l Locksmith) ValidateWindowStart() report.Report {
}
func (l Locksmith) ValidateWindowLength() report.Report {
if l.WindowLength == "" {
if nilOrEmpty(l.WindowLength) {
return report.Report{}
}
_, err := time.ParseDuration(l.WindowLength)
_, err := time.ParseDuration(*l.WindowLength)
if err != nil {
return report.ReportFromError(ErrParsingLength, report.EntryError)
}

View File

@@ -15,7 +15,7 @@
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -25,17 +25,30 @@ type Networkd struct {
}
type NetworkdUnit struct {
Name string `yaml:"name"`
Contents string `yaml:"contents"`
Dropins []NetworkdUnitDropIn `yaml:"dropins"`
}
type NetworkdUnitDropIn struct {
Name string `yaml:"name"`
Contents string `yaml:"contents"`
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
for _, unit := range in.Networkd.Units {
out.Networkd.Units = append(out.Networkd.Units, ignTypes.Networkdunit{
newUnit := ignTypes.Networkdunit{
Name: unit.Name,
Contents: unit.Contents,
})
}
for _, dropIn := range unit.Dropins {
newUnit.Dropins = append(newUnit.Dropins, ignTypes.NetworkdDropin{
Name: dropIn.Name,
Contents: dropIn.Contents,
})
}
out.Networkd.Units = append(out.Networkd.Units, newUnit)
}
return out, report.Report{}, ast
})

View File

@@ -15,7 +15,7 @@
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -63,7 +63,7 @@ type Group struct {
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
for _, user := range in.Passwd.Users {
newUser := ignTypes.PasswdUser{
Name: user.Name,
@@ -74,7 +74,7 @@ func init() {
HomeDir: user.HomeDir,
NoCreateHome: user.NoCreateHome,
PrimaryGroup: user.PrimaryGroup,
Groups: convertStringSliceIntoTypesPasswdUserGroupSlice(user.Groups),
Groups: convertStringSliceIntoTypesGroupSlice(user.Groups),
NoUserGroup: user.NoUserGroup,
System: user.System,
NoLogInit: user.NoLogInit,
@@ -130,10 +130,10 @@ func convertStringSliceIntoTypesUsercreateGroupSlice(ss []string) []ignTypes.Use
}
// golang--
func convertStringSliceIntoTypesPasswdUserGroupSlice(ss []string) []ignTypes.PasswdUserGroup {
var res []ignTypes.PasswdUserGroup
func convertStringSliceIntoTypesGroupSlice(ss []string) []ignTypes.Group {
var res []ignTypes.Group
for _, s := range ss {
res = append(res, ignTypes.PasswdUserGroup(s))
res = append(res, ignTypes.Group(s))
}
return res
}

View File

@@ -15,7 +15,7 @@
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -25,16 +25,18 @@ type Raid struct {
Level string `yaml:"level"`
Devices []string `yaml:"devices"`
Spares int `yaml:"spares"`
Options []string `yaml:"options"`
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
for _, array := range in.Storage.Arrays {
newArray := ignTypes.Raid{
Name: array.Name,
Level: array.Level,
Spares: array.Spares,
Devices: convertStringSliceToTypesDeviceSlice(array.Devices),
Options: convertStringSiceToTypesRaidOptionSlice(array.Options),
}
out.Storage.Raid = append(out.Storage.Raid, newArray)
@@ -51,3 +53,12 @@ func convertStringSliceToTypesDeviceSlice(ss []string) []ignTypes.Device {
}
return res
}
// golang--
func convertStringSiceToTypesRaidOptionSlice(ss []string) []ignTypes.RaidOption {
var res []ignTypes.RaidOption
for _, s := range ss {
res = append(res, ignTypes.RaidOption(s))
}
return res
}

View File

@@ -0,0 +1,46 @@
// Copyright 2018 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 (
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
type Security struct {
TLS TLS `yaml:"tls"`
}
type TLS struct {
CertificateAuthorities []CaReference `yaml:"certificate_authorities"`
}
type CaReference struct {
Source string `yaml:"source"`
Verification Verification `yaml:"verification"`
}
func init() {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
for _, ca := range in.Ignition.Security.TLS.CertificateAuthorities {
out.Ignition.Security.TLS.CertificateAuthorities = append(out.Ignition.Security.TLS.CertificateAuthorities, ignTypes.CaReference{
Source: ca.Source,
Verification: convertVerification(ca.Verification),
})
}
return out, report.Report{}, ast
})
}

View File

@@ -15,7 +15,7 @@
package types
import (
ignTypes "github.com/coreos/ignition/config/v2_1/types"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
)
@@ -39,7 +39,7 @@ type SystemdUnitDropIn struct {
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
for _, unit := range in.Systemd.Units {
newUnit := ignTypes.Unit{
Name: unit.Name,
@@ -50,7 +50,7 @@ func init() {
}
for _, dropIn := range unit.Dropins {
newUnit.Dropins = append(newUnit.Dropins, ignTypes.Dropin{
newUnit.Dropins = append(newUnit.Dropins, ignTypes.SystemdDropin{
Name: dropIn.Name,
Contents: dropIn.Contents,
})

View File

@@ -20,7 +20,9 @@ import (
"net/url"
"strings"
ignTypes "github.com/coreos/ignition/config/v2_1/types"
"github.com/coreos/container-linux-config-transpiler/internal/util"
ignTypes "github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate/astnode"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
@@ -59,7 +61,7 @@ func (s UpdateServer) Validate() report.Report {
}
func init() {
register2_0(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
register(func(in Config, ast astnode.AstNode, out ignTypes.Config, platform string) (ignTypes.Config, report.Report, astnode.AstNode) {
var contents string
if in.Update != nil {
if in.Update.Group != "" {
@@ -82,7 +84,7 @@ func init() {
Path: "/etc/coreos/update.conf",
},
FileEmbedded1: ignTypes.FileEmbedded1{
Mode: 0644,
Mode: util.IntToPtr(0644),
Contents: ignTypes.FileContents{
Source: (&url.URL{
Scheme: "data",

View File

@@ -0,0 +1,27 @@
// 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 util
func IntToPtr(i int) *int {
return &i
}
func BoolToPtr(b bool) *bool {
return &b
}
func StringToPtr(s string) *string {
return &s
}

View File

@@ -1,72 +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 (
"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

@@ -1,215 +0,0 @@
// 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 config
import (
"bytes"
"errors"
"reflect"
"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")
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) {
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
}
version, err := Version(rawConfig)
if err != nil && err != ErrVersionIndeterminable {
// If we can't determine the version, its probably invalid json and we want to fall through
// to handle that in ParseFromLatest, since it will generate the highlight string and report
// line and column.
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.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:
// It's not empty, it's not a cloud config, and it's not a script, but
// we can't determine the version of it (or it's 0.0.0, hooray golang
// zero values). We know it's not valid, but try to parse it anyway with
// ParseFromLatest to generate any parse errors.
config, rep, err := ParseFromLatest(rawConfig)
if err != nil || rep.IsFatal() {
return config, rep, err
}
// Somehow parsing succeeded without any errors or fatal reports. This
// should be an impossible state, but since we're in this part of the
// switch statement we know at the very least the version is invalid, so
// return that.
return types.Config{}, report.Report{}, ErrUnknownVersion
}
}
func ParseFromLatest(rawConfig []byte) (types.Config, report.Report, error) {
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 ParseFromV1(rawConfig []byte) (types.Config, error) {
config, err := v1.Parse(rawConfig)
if err != nil {
return types.Config{}, err
}
return TranslateFromV1(config), nil
}
func ParseFromV2_0(rawConfig []byte) (types.Config, report.Report, error) {
cfg, report, err := v2_0.Parse(rawConfig)
if err != nil {
return types.Config{}, report, err
}
return TranslateFromV2_0(cfg), report, err
}
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 *string `json:"version"`
} `json:"ignition"`
}
if json.Unmarshal(rawConfig, &composite) == nil {
if composite.Ignition.Version != nil {
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 semver.Version{Major: int64(*composite.Version)}, nil
}
}
return semver.Version{}, ErrVersionIndeterminable
}
func isEmpty(userdata []byte) bool {
return len(userdata) == 0
}

View File

@@ -0,0 +1,106 @@
// Copyright 2018 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 errors includes errors that are used in multiple config versions
package errors
import (
"errors"
"fmt"
)
var (
// Parsing / general errors
ErrInvalid = errors.New("config is not valid")
ErrCloudConfig = errors.New("not a config (found coreos-cloudconfig)")
ErrEmpty = errors.New("not a config (empty)")
ErrUnknownVersion = errors.New("unsupported config version")
ErrScript = errors.New("not a config (found coreos-cloudinit script)")
ErrDeprecated = errors.New("config format deprecated")
ErrCompressionInvalid = errors.New("invalid compression method")
// Ignition section errors
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)")
// Storage section errors
ErrPermissionsUnset = errors.New("permissions unset, defaulting to 0000")
ErrDiskDeviceRequired = errors.New("disk device is required")
ErrPartitionNumbersCollide = errors.New("partition numbers collide")
ErrPartitionsOverlap = errors.New("partitions overlap")
ErrPartitionsMisaligned = errors.New("partitions misaligned")
ErrAppendAndOverwrite = errors.New("cannot set both append and overwrite to true")
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")
ErrFileIllegalMode = errors.New("illegal file mode")
ErrNoFilesystem = errors.New("no filesystem specified")
ErrBothIDAndNameSet = errors.New("cannot set both id and name")
ErrLabelTooLong = errors.New("partition labels may not exceed 36 characters")
ErrDoesntMatchGUIDRegex = errors.New("doesn't match the form \"01234567-89AB-CDEF-EDCB-A98765432101\"")
ErrLabelContainsColon = errors.New("partition label will be truncated to text before the colon")
ErrPathRelative = errors.New("path not absolute")
ErrSparesUnsupportedForLevel = errors.New("spares unsupported for arrays with a level greater than 0")
ErrUnrecognizedRaidLevel = errors.New("unrecognized raid level")
ErrShouldNotExistWithOthers = errors.New("shouldExist specified false with other options also specified")
ErrZeroesWithShouldNotExist = errors.New("shouldExist is false for a partition and other partition(s) has start or size 0")
ErrPartitionsUnitsMismatch = errors.New("cannot mix MBs and sectors within a disk")
ErrSizeDeprecated = errors.New("size is deprecated; use sizeMB instead")
ErrStartDeprecated = errors.New("start is deprecated; use startMB instead")
// Passwd section errors
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")
// Systemd and Networkd section errors
ErrInvalidSystemdExt = errors.New("invalid systemd unit extension")
ErrInvalidSystemdDropinExt = errors.New("invalid systemd drop-in extension")
ErrInvalidNetworkdExt = errors.New("invalid networkd unit extension")
ErrInvalidNetworkdDropinExt = errors.New("invalid networkd drop-in extension")
// Misc errors
ErrInvalidScheme = errors.New("invalid url scheme")
ErrInvalidUrl = errors.New("unable to parse url")
ErrHashMalformed = errors.New("malformed hash specifier")
ErrHashWrongSize = errors.New("incorrect size for hash sum")
ErrHashUnrecognized = errors.New("unrecognized hash function")
ErrEngineConfiguration = errors.New("engine incorrectly configured")
// AWS S3 specific errors
ErrInvalidS3ObjectVersionId = errors.New("invalid S3 object VersionId")
)
// NewNoInstallSectionError produces an error indicating the given unit, named
// name, is missing an Install section.
func NewNoInstallSectionError(name string) error {
return fmt.Errorf("unit %q is enabled, but has no install section so enable does nothing", name)
}

View File

@@ -0,0 +1,54 @@
// Copyright 2018 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 validations contains validations shared between multiple config
// versions.
package validations
import (
"github.com/coreos/go-systemd/unit"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
// ValidateInstallSection is a helper to validate a given unit
func ValidateInstallSection(name string, enabled bool, contentsEmpty bool, contentSections []*unit.UnitOption) report.Report {
if !enabled {
// install sections don't matter for not-enabled units
return report.Report{}
}
if contentsEmpty {
// install sections don't matter if it has no contents, e.g. it's being masked or just has dropins or such
return report.Report{}
}
if contentSections == nil {
// Should only happen if the unit could not be parsed, at which point an
// error is probably already in the report so we don't need to double-up on
// errors + warnings.
return report.Report{}
}
for _, section := range contentSections {
if section.Section == "Install" {
return report.Report{}
}
}
return report.Report{
Entries: []report.Entry{{
Message: errors.NewNoInstallSectionError(name).Error(),
Kind: report.EntryWarning,
}},
}
}

View File

@@ -1,734 +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 (
"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: v2_0.MaxVersion.String(),
},
}
for _, oldDisk := range old.Storage.Disks {
disk := types.Disk{
Device: string(oldDisk.Device),
WipeTable: oldDisk.WipeTable,
}
for _, oldPartition := range oldDisk.Partitions {
disk.Partitions = append(disk.Partitions, types.Partition{
Label: string(oldPartition.Label),
Number: oldPartition.Number,
Size: int(oldPartition.Size),
Start: int(oldPartition.Start),
TypeGUID: string(oldPartition.TypeGUID),
})
}
config.Storage.Disks = append(config.Storage.Disks, disk)
}
for _, oldArray := range old.Storage.Arrays {
array := types.Raid{
Name: oldArray.Name,
Level: oldArray.Level,
Spares: oldArray.Spares,
}
for _, oldDevice := range oldArray.Devices {
array.Devices = append(array.Devices, types.Device(oldDevice))
}
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.Mount{
Device: string(oldFilesystem.Device),
Format: string(oldFilesystem.Format),
},
}
if oldFilesystem.Create != nil {
filesystem.Mount.Create = &types.Create{
Force: oldFilesystem.Create.Force,
Options: translateV1MkfsOptionsToV2_2OptionSlice(oldFilesystem.Create.Options),
}
}
config.Storage.Filesystems = append(config.Storage.Filesystems, filesystem)
for _, oldFile := range oldFilesystem.Files {
file := types.File{
Node: types.Node{
Filesystem: filesystem.Name,
Path: string(oldFile.Path),
User: types.NodeUser{ID: intToPtr(oldFile.Uid)},
Group: types.NodeGroup{ID: intToPtr(oldFile.Gid)},
},
FileEmbedded1: types.FileEmbedded1{
Mode: int(oldFile.Mode),
Contents: types.FileContents{
Source: (&url.URL{
Scheme: "data",
Opaque: "," + dataurl.EscapeString(oldFile.Contents),
}).String(),
},
},
}
config.Storage.Files = append(config.Storage.Files, file)
}
}
for _, oldUnit := range old.Systemd.Units {
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.Dropin{
Name: string(oldDropIn.Name),
Contents: oldDropIn.Contents,
})
}
config.Systemd.Units = append(config.Systemd.Units, unit)
}
for _, oldUnit := range old.Networkd.Units {
config.Networkd.Units = append(config.Networkd.Units, types.Networkdunit{
Name: string(oldUnit.Name),
Contents: oldUnit.Contents,
})
}
for _, oldUser := range old.Passwd.Users {
user := types.PasswdUser{
Name: oldUser.Name,
PasswordHash: strToPtr(oldUser.PasswordHash),
SSHAuthorizedKeys: translateStringSliceToV2_2SSHAuthorizedKeySlice(oldUser.SSHAuthorizedKeys),
}
if oldUser.Create != nil {
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: translateStringSliceToV2_2UsercreateGroupSlice(oldUser.Create.Groups),
NoUserGroup: oldUser.Create.NoUserGroup,
System: oldUser.Create.System,
NoLogInit: oldUser.Create.NoLogInit,
Shell: oldUser.Create.Shell,
}
}
config.Passwd.Users = append(config.Passwd.Users, user)
}
for _, oldGroup := range old.Passwd.Groups {
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: gid,
PasswordHash: oldGroup.PasswordHash,
System: oldGroup.System,
})
}
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 {
// .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: old.Source.String(),
Verification: translateVerification(old.Verification),
}
}
config := types.Config{
Ignition: types.Ignition{
Version: types.MaxVersion.String(),
},
}
if old.Ignition.Config.Replace != nil {
ref := translateConfigReference(*old.Ignition.Config.Replace)
config.Ignition.Config.Replace = &ref
}
for _, oldAppend := range old.Ignition.Config.Append {
config.Ignition.Config.Append =
append(config.Ignition.Config.Append, translateConfigReference(oldAppend))
}
for _, oldDisk := range old.Storage.Disks {
disk := types.Disk{
Device: string(oldDisk.Device),
WipeTable: oldDisk.WipeTable,
}
for _, oldPartition := range oldDisk.Partitions {
disk.Partitions = append(disk.Partitions, types.Partition{
Label: string(oldPartition.Label),
Number: oldPartition.Number,
Size: int(oldPartition.Size),
Start: int(oldPartition.Start),
TypeGUID: string(oldPartition.TypeGUID),
})
}
config.Storage.Disks = append(config.Storage.Disks, disk)
}
for _, oldArray := range old.Storage.Arrays {
array := types.Raid{
Name: oldArray.Name,
Level: oldArray.Level,
Spares: oldArray.Spares,
}
for _, oldDevice := range oldArray.Devices {
array.Devices = append(array.Devices, types.Device(oldDevice))
}
config.Storage.Raid = append(config.Storage.Raid, array)
}
for _, oldFilesystem := range old.Storage.Filesystems {
filesystem := types.Filesystem{
Name: oldFilesystem.Name,
}
if oldFilesystem.Mount != nil {
filesystem.Mount = &types.Mount{
Device: string(oldFilesystem.Mount.Device),
Format: string(oldFilesystem.Mount.Format),
}
if oldFilesystem.Mount.Create != nil {
filesystem.Mount.Create = &types.Create{
Force: oldFilesystem.Mount.Create.Force,
Options: translateV2_0MkfsOptionsToV2_2OptionSlice(oldFilesystem.Mount.Create.Options),
}
}
}
if oldFilesystem.Path != nil {
p := string(*oldFilesystem.Path)
filesystem.Path = &p
}
config.Storage.Filesystems = append(config.Storage.Filesystems, filesystem)
}
for _, oldFile := range old.Storage.Files {
file := types.File{
Node: types.Node{
Filesystem: oldFile.Filesystem,
Path: string(oldFile.Path),
User: types.NodeUser{ID: intToPtr(oldFile.User.Id)},
Group: types.NodeGroup{ID: intToPtr(oldFile.Group.Id)},
},
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),
},
},
}
config.Storage.Files = append(config.Storage.Files, file)
}
for _, oldUnit := range old.Systemd.Units {
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.Dropin{
Name: string(oldDropIn.Name),
Contents: oldDropIn.Contents,
})
}
config.Systemd.Units = append(config.Systemd.Units, unit)
}
for _, oldUnit := range old.Networkd.Units {
config.Networkd.Units = append(config.Networkd.Units, types.Networkdunit{
Name: string(oldUnit.Name),
Contents: oldUnit.Contents,
})
}
for _, oldUser := range old.Passwd.Users {
user := types.PasswdUser{
Name: oldUser.Name,
PasswordHash: strToPtr(oldUser.PasswordHash),
SSHAuthorizedKeys: translateStringSliceToV2_2SSHAuthorizedKeySlice(oldUser.SSHAuthorizedKeys),
}
if oldUser.Create != nil {
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: translateStringSliceToV2_2UsercreateGroupSlice(oldUser.Create.Groups),
NoUserGroup: oldUser.Create.NoUserGroup,
System: oldUser.Create.System,
NoLogInit: oldUser.Create.NoLogInit,
Shell: oldUser.Create.Shell,
}
}
config.Passwd.Users = append(config.Passwd.Users, user)
}
for _, oldGroup := range old.Passwd.Groups {
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: g,
PasswordHash: oldGroup.PasswordHash,
System: oldGroup.System,
})
}
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

@@ -1,82 +0,0 @@
// 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

@@ -0,0 +1,34 @@
// Copyright 2018 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
func IntToPtr(x int) *int {
return &x
}
func StrToPtr(s string) *string {
if s == "" {
return nil
}
return &s
}
func StrToPtrStrict(s string) *string {
return &s
}
func BoolToPtr(b bool) *bool {
return &b
}

View File

@@ -0,0 +1,74 @@
// Copyright 2018 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
import (
"bytes"
"errors"
configErrors "github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/v2_4_experimental/types"
"github.com/coreos/ignition/config/validate/report"
json "github.com/ajeddeloh/go-json"
"go4.org/errorutil"
)
var (
ErrValidConfig = errors.New("HandleParseErrors called with a valid config")
)
// HandleParseErrors will attempt to unmarshal an invalid rawConfig into the
// latest config struct, so as to generate a report.Report from the errors. It
// will always return an error. This is called after config/v* parse functions
// chain has failed to parse a config.
func HandleParseErrors(rawConfig []byte) (report.Report, error) {
config := types.Config{}
err := json.Unmarshal(rawConfig, &config)
if err == nil {
return report.Report{}, ErrValidConfig
}
// 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 report.Report{
Entries: []report.Entry{{
Kind: report.EntryError,
Message: serr.Error(),
Line: line,
Column: col,
Highlight: highlight,
}},
},
configErrors.ErrInvalid
}
if terr, ok := err.(*json.UnmarshalTypeError); ok {
line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), terr.Offset)
return report.Report{
Entries: []report.Entry{{
Kind: report.EntryError,
Message: terr.Error(),
Line: line,
Column: col,
Highlight: highlight,
}},
},
configErrors.ErrInvalid
}
return report.ReportFromError(err, report.EntryError), err
}

View File

@@ -15,41 +15,43 @@
package v1
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/util"
"github.com/coreos/ignition/config/v1/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
"go4.org/errorutil"
json "github.com/ajeddeloh/go-json"
)
var (
ErrVersion = errors.New("incorrect config version")
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)")
)
func Parse(rawConfig []byte) (config types.Config, err error) {
if err = json.Unmarshal(rawConfig, &config); err == nil {
if config.Version != types.Version {
err = ErrVersion
}
} else if isEmpty(rawConfig) {
err = ErrEmpty
func Parse(rawConfig []byte) (types.Config, report.Report, error) {
if isEmpty(rawConfig) {
return types.Config{}, report.Report{}, errors.ErrEmpty
} else if isCloudConfig(rawConfig) {
err = ErrCloudConfig
return types.Config{}, report.Report{}, errors.ErrCloudConfig
} else if isScript(rawConfig) {
err = ErrScript
}
if serr, ok := err.(*json.SyntaxError); ok {
line, col, highlight := errorutil.HighlightBytePosition(bytes.NewReader(rawConfig), serr.Offset)
err = fmt.Errorf("error at line %d, column %d\n%s%v", line, col, highlight, err)
return types.Config{}, report.Report{}, errors.ErrScript
}
return
var err error
var config types.Config
err = json.Unmarshal(rawConfig, &config)
if err != nil {
rpt, err := util.HandleParseErrors(rawConfig)
// HandleParseErrors always returns an error
return types.Config{}, rpt, err
}
if config.Version != types.Version {
return types.Config{}, report.Report{}, errors.ErrUnknownVersion
}
rpt := validate.ValidateConfig(rawConfig, config)
if rpt.IsFatal() {
return types.Config{}, rpt, errors.ErrInvalid
}
return config, rpt, nil
}
func isEmpty(userdata []byte) bool {

View File

@@ -14,14 +14,18 @@
package types
import (
"reflect"
)
import "github.com/coreos/go-semver/semver"
const (
Version = 1
)
var (
MaxVersion = semver.Version{
Major: Version,
}
)
type Config struct {
Version int `json:"ignitionVersion"`
Storage Storage `json:"storage,omitempty"`
@@ -29,45 +33,3 @@ type Config struct {
Networkd Networkd `json:"networkd,omitempty"`
Passwd Passwd `json:"passwd,omitempty"`
}
func (c Config) AssertValid() error {
return assertStructValid(reflect.ValueOf(c))
}
func assertValid(vObj reflect.Value) error {
if !vObj.IsValid() {
return nil
}
if obj, ok := vObj.Interface().(interface {
AssertValid() error
}); ok && !(vObj.Kind() == reflect.Ptr && vObj.IsNil()) {
if err := obj.AssertValid(); err != nil {
return err
}
}
switch vObj.Kind() {
case reflect.Ptr:
return assertValid(vObj.Elem())
case reflect.Struct:
return assertStructValid(vObj)
case reflect.Slice:
for i := 0; i < vObj.Len(); i++ {
if err := assertValid(vObj.Index(i)); err != nil {
return err
}
}
}
return nil
}
func assertStructValid(vObj reflect.Value) error {
for i := 0; i < vObj.Type().NumField(); i++ {
if err := assertValid(vObj.Field(i)); err != nil {
return err
}
}
return nil
}

View File

@@ -15,8 +15,8 @@
package types
import (
"encoding/json"
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
type Disk struct {
@@ -24,32 +24,35 @@ type Disk struct {
WipeTable bool `json:"wipeTable,omitempty"`
Partitions []Partition `json:"partitions,omitempty"`
}
type disk Disk
func (n *Disk) UnmarshalJSON(data []byte) error {
tn := disk(*n)
if err := json.Unmarshal(data, &tn); err != nil {
return err
}
*n = Disk(tn)
return n.AssertValid()
}
func (n Disk) AssertValid() error {
func (n Disk) Validate() report.Report {
r := report.Report{}
if len(n.Device) == 0 {
return fmt.Errorf("disk device is required")
r.Add(report.Entry{
Kind: report.EntryError,
Message: errors.ErrDiskDeviceRequired.Error(),
})
}
if n.partitionNumbersCollide() {
return fmt.Errorf("disk %q: partition numbers collide", n.Device)
r.Add(report.Entry{
Kind: report.EntryError,
Message: errors.ErrPartitionNumbersCollide.Error(),
})
}
if n.partitionsOverlap() {
return fmt.Errorf("disk %q: partitions overlap", n.Device)
r.Add(report.Entry{
Kind: report.EntryError,
Message: errors.ErrPartitionsOverlap.Error(),
})
}
if n.partitionsMisaligned() {
return fmt.Errorf("disk %q: partitions misaligned", n.Device)
r.Add(report.Entry{
Kind: report.EntryError,
Message: errors.ErrPartitionsMisaligned.Error(),
})
}
// Disks which get to this point will likely succeed in sgdisk
return nil
return r
}
// partitionNumbersCollide returns true if partition numbers in n.Partitions are not unique.

View File

@@ -15,13 +15,10 @@
package types
import (
"encoding/json"
"errors"
"os"
)
var (
ErrFileIllegalMode = errors.New("illegal file mode")
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
type FileMode os.FileMode
@@ -33,20 +30,10 @@ type File struct {
Uid int `json:"uid,omitempty"`
Gid int `json:"gid,omitempty"`
}
type fileMode FileMode
func (m *FileMode) UnmarshalJSON(data []byte) error {
tm := fileMode(*m)
if err := json.Unmarshal(data, &tm); err != nil {
return err
}
*m = FileMode(tm)
return m.AssertValid()
}
func (m FileMode) AssertValid() error {
func (m FileMode) Validate() report.Report {
if (m &^ 07777) != 0 {
return ErrFileIllegalMode
return report.ReportFromError(errors.ErrFileIllegalMode, report.EntryError)
}
return nil
return report.Report{}
}

View File

@@ -15,12 +15,8 @@
package types
import (
"encoding/json"
"errors"
)
var (
ErrFilesystemInvalidFormat = errors.New("invalid filesystem format")
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
type Filesystem struct {
@@ -34,60 +30,16 @@ type FilesystemCreate struct {
Force bool `json:"force,omitempty"`
Options MkfsOptions `json:"options,omitempty"`
}
type filesystem Filesystem
func (f *Filesystem) UnmarshalJSON(data []byte) error {
tf := filesystem(*f)
if err := json.Unmarshal(data, &tf); err != nil {
return err
}
*f = Filesystem(tf)
return f.AssertValid()
}
func (f Filesystem) AssertValid() error {
if err := f.Device.AssertValid(); err != nil {
return err
}
if err := f.Format.AssertValid(); err != nil {
return err
}
return nil
}
type FilesystemFormat string
type filesystemFormat FilesystemFormat
func (f *FilesystemFormat) UnmarshalJSON(data []byte) error {
tf := filesystemFormat(*f)
if err := json.Unmarshal(data, &tf); err != nil {
return err
}
*f = FilesystemFormat(tf)
return f.AssertValid()
}
func (f FilesystemFormat) AssertValid() error {
func (f FilesystemFormat) Validate() report.Report {
switch f {
case "ext4", "btrfs", "xfs":
return nil
return report.Report{}
default:
return ErrFilesystemInvalidFormat
return report.ReportFromError(errors.ErrFilesystemInvalidFormat, report.EntryError)
}
}
type MkfsOptions []string
type mkfsOptions MkfsOptions
func (o *MkfsOptions) UnmarshalJSON(data []byte) error {
to := mkfsOptions(*o)
if err := json.Unmarshal(data, &to); err != nil {
return err
}
*o = MkfsOptions(to)
return o.AssertValid()
}
func (o MkfsOptions) AssertValid() error {
return nil
}

View File

@@ -15,9 +15,11 @@
package types
import (
"encoding/json"
"fmt"
"regexp"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
type Partition struct {
@@ -29,59 +31,30 @@ type Partition struct {
}
type PartitionLabel string
type partitionLabel PartitionLabel
func (n *PartitionLabel) UnmarshalJSON(data []byte) error {
tn := partitionLabel(*n)
if err := json.Unmarshal(data, &tn); err != nil {
return err
}
*n = PartitionLabel(tn)
return n.AssertValid()
}
func (n PartitionLabel) AssertValid() error {
func (n PartitionLabel) Validate() 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 fmt.Errorf("partition labels may not exceed 36 characters")
return report.ReportFromError(errors.ErrLabelTooLong, report.EntryError)
}
return nil
return report.Report{}
}
type PartitionDimension uint64
func (n *PartitionDimension) UnmarshalJSON(data []byte) error {
var pd uint64
if err := json.Unmarshal(data, &pd); err != nil {
return err
}
*n = PartitionDimension(pd)
return nil
}
type PartitionTypeGUID string
type partitionTypeGUID PartitionTypeGUID
func (d *PartitionTypeGUID) UnmarshalJSON(data []byte) error {
td := partitionTypeGUID(*d)
if err := json.Unmarshal(data, &td); err != nil {
return err
}
*d = PartitionTypeGUID(td)
return d.AssertValid()
}
func (d PartitionTypeGUID) AssertValid() error {
func (d PartitionTypeGUID) Validate() report.Report {
ok, err := regexp.MatchString("^(|[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$", string(d))
if err != nil {
return fmt.Errorf("error matching type-guid regexp: %v", err)
return report.ReportFromError(fmt.Errorf("error matching type-guid regexp: %v", err), report.EntryError)
}
if !ok {
return fmt.Errorf(`partition type-guid must have the form "01234567-89AB-CDEF-EDCB-A98765432101", got: %q`, string(d))
return report.ReportFromError(fmt.Errorf(`partition type-guid must have the form "01234567-89AB-CDEF-EDCB-A98765432101", got: %q`, string(d)), report.EntryError)
}
return nil
return report.Report{}
}

View File

@@ -15,29 +15,17 @@
package types
import (
"encoding/json"
"errors"
"path"
)
var (
ErrPathRelative = errors.New("path not absolute")
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
type Path string
func (d *Path) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
*d = Path(s)
return d.AssertValid()
}
func (d Path) AssertValid() error {
func (d Path) Validate() report.Report {
if !path.IsAbs(string(d)) {
return ErrPathRelative
return report.ReportFromError(errors.ErrPathRelative, report.EntryError)
}
return nil
return report.Report{}
}

View File

@@ -15,8 +15,8 @@
package types
import (
"encoding/json"
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
type Raid struct {
@@ -25,22 +25,12 @@ type Raid struct {
Devices []Path `json:"devices,omitempty"`
Spares int `json:"spares,omitempty"`
}
type raid Raid
func (n *Raid) UnmarshalJSON(data []byte) error {
tn := raid(*n)
if err := json.Unmarshal(data, &tn); err != nil {
return err
}
*n = Raid(tn)
return n.AssertValid()
}
func (n Raid) AssertValid() error {
func (n Raid) Validate() report.Report {
switch n.Level {
case "linear", "raid0", "0", "stripe":
if n.Spares != 0 {
return fmt.Errorf("spares unsupported for %q arrays", n.Level)
return report.ReportFromError(errors.ErrSparesUnsupportedForLevel, report.EntryError)
}
case "raid1", "1", "mirror":
case "raid4", "4":
@@ -48,7 +38,7 @@ func (n Raid) AssertValid() error {
case "raid6", "6":
case "raid10", "10":
default:
return fmt.Errorf("unrecognized raid level: %q", n.Level)
return report.ReportFromError(errors.ErrUnrecognizedRaidLevel, report.EntryError)
}
return nil
return report.Report{}
}

View File

@@ -15,9 +15,10 @@
package types
import (
"encoding/json"
"errors"
"path"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
type SystemdUnit struct {
@@ -34,44 +35,24 @@ type SystemdUnitDropIn struct {
}
type SystemdUnitName string
type systemdUnitName SystemdUnitName
func (n *SystemdUnitName) UnmarshalJSON(data []byte) error {
tn := systemdUnitName(*n)
if err := json.Unmarshal(data, &tn); err != nil {
return err
}
*n = SystemdUnitName(tn)
return n.AssertValid()
}
func (n SystemdUnitName) AssertValid() error {
func (n SystemdUnitName) Validate() report.Report {
switch path.Ext(string(n)) {
case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope":
return nil
return report.Report{}
default:
return errors.New("invalid systemd unit extension")
return report.ReportFromError(errors.ErrInvalidSystemdExt, report.EntryError)
}
}
type SystemdUnitDropInName string
type systemdUnitDropInName SystemdUnitDropInName
func (n *SystemdUnitDropInName) UnmarshalJSON(data []byte) error {
tn := systemdUnitDropInName(*n)
if err := json.Unmarshal(data, &tn); err != nil {
return err
}
*n = SystemdUnitDropInName(tn)
return n.AssertValid()
}
func (n SystemdUnitDropInName) AssertValid() error {
func (n SystemdUnitDropInName) Validate() report.Report {
switch path.Ext(string(n)) {
case ".conf":
return nil
return report.Report{}
default:
return errors.New("invalid systemd unit drop-in extension")
return report.ReportFromError(errors.ErrInvalidSystemdDropinExt, report.EntryError)
}
}
@@ -81,22 +62,12 @@ type NetworkdUnit struct {
}
type NetworkdUnitName string
type networkdUnitName NetworkdUnitName
func (n *NetworkdUnitName) UnmarshalJSON(data []byte) error {
tn := networkdUnitName(*n)
if err := json.Unmarshal(data, &tn); err != nil {
return err
}
*n = NetworkdUnitName(tn)
return n.AssertValid()
}
func (n NetworkdUnitName) AssertValid() error {
func (n NetworkdUnitName) Validate() report.Report {
switch path.Ext(string(n)) {
case ".link", ".netdev", ".network":
return nil
return report.Report{}
default:
return errors.New("invalid networkd unit extension")
return report.ReportFromError(errors.ErrInvalidNetworkdExt, report.EntryError)
}
}

View File

@@ -1,73 +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 v2_0
import (
"reflect"
"github.com/coreos/ignition/config/v2_0/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. 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".
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).Tag.Get("merge") {
case "old":
vfRes.Set(vfOld)
continue
case "new":
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

@@ -15,104 +15,54 @@
package v2_0
import (
"bytes"
"errors"
"reflect"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/v1"
"github.com/coreos/ignition/config/v2_0/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")
"github.com/coreos/go-semver/semver"
)
// 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) {
if isEmpty(rawConfig) {
return types.Config{}, report.Report{}, ErrEmpty
return types.Config{}, report.Report{}, errors.ErrEmpty
} else if isCloudConfig(rawConfig) {
return types.Config{}, report.Report{}, ErrCloudConfig
return types.Config{}, report.Report{}, errors.ErrCloudConfig
} else if isScript(rawConfig) {
return types.Config{}, report.Report{}, ErrScript
return types.Config{}, report.Report{}, errors.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()
if versionReport.IsFatal() {
return types.Config{}, versionReport, ErrInvalid
err = json.Unmarshal(rawConfig, &config)
if err != nil || semver.Version(config.Ignition.Version).LessThan(types.MaxVersion) {
// We can fail unmarshaling if it's an older config. Attempt to parse
// it as such.
config, rpt, err := v1.Parse(rawConfig)
if err != nil {
return types.Config{}, rpt, err
}
rpt.Merge(report.ReportFromError(errors.ErrDeprecated, report.EntryDeprecated))
return TranslateFromV1(config), rpt, err
}
// 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 semver.Version(config.Ignition.Version) != types.MaxVersion {
return types.Config{}, report.Report{}, errors.ErrUnknownVersion
}
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
rpt := validate.ValidateConfig(rawConfig, config)
if rpt.IsFatal() {
return types.Config{}, rpt, errors.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
return config, rpt, nil
}
func isEmpty(userdata []byte) bool {

View File

@@ -0,0 +1,173 @@
// Copyright 2018 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_0
import (
"fmt"
v1 "github.com/coreos/ignition/config/v1/types"
"github.com/coreos/ignition/config/v2_0/types"
"github.com/vincent-petithory/dataurl"
)
func TranslateFromV1(old v1.Config) types.Config {
config := types.Config{
Ignition: types.Ignition{
Version: types.IgnitionVersion(types.MaxVersion),
},
}
for _, oldDisk := range old.Storage.Disks {
disk := types.Disk{
Device: types.Path(oldDisk.Device),
WipeTable: oldDisk.WipeTable,
}
for _, oldPartition := range oldDisk.Partitions {
disk.Partitions = append(disk.Partitions, types.Partition{
Label: types.PartitionLabel(oldPartition.Label),
Number: oldPartition.Number,
Size: types.PartitionDimension(oldPartition.Size),
Start: types.PartitionDimension(oldPartition.Start),
TypeGUID: types.PartitionTypeGUID(oldPartition.TypeGUID),
})
}
config.Storage.Disks = append(config.Storage.Disks, disk)
}
for _, oldArray := range old.Storage.Arrays {
array := types.Raid{
Name: oldArray.Name,
Level: oldArray.Level,
Spares: oldArray.Spares,
}
for _, oldDevice := range oldArray.Devices {
array.Devices = append(array.Devices, types.Path(oldDevice))
}
config.Storage.Arrays = append(config.Storage.Arrays, 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),
},
}
if oldFilesystem.Create != nil {
filesystem.Mount.Create = &types.FilesystemCreate{
Force: oldFilesystem.Create.Force,
Options: types.MkfsOptions(oldFilesystem.Create.Options),
}
}
config.Storage.Filesystems = append(config.Storage.Filesystems, filesystem)
for _, oldFile := range oldFilesystem.Files {
file := types.File{
Filesystem: filesystem.Name,
Path: types.Path(oldFile.Path),
User: types.FileUser{Id: oldFile.Uid},
Group: types.FileGroup{Id: oldFile.Gid},
Mode: types.FileMode(oldFile.Mode),
Contents: types.FileContents{
Source: types.Url{
Scheme: "data",
Opaque: "," + dataurl.EscapeString(oldFile.Contents),
},
},
}
config.Storage.Files = append(config.Storage.Files, file)
}
}
for _, oldUnit := range old.Systemd.Units {
unit := types.SystemdUnit{
Name: types.SystemdUnitName(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),
Contents: oldDropIn.Contents,
})
}
config.Systemd.Units = append(config.Systemd.Units, unit)
}
for _, oldUnit := range old.Networkd.Units {
config.Networkd.Units = append(config.Networkd.Units, types.NetworkdUnit{
Name: types.NetworkdUnitName(oldUnit.Name),
Contents: oldUnit.Contents,
})
}
for _, oldUser := range old.Passwd.Users {
user := types.User{
Name: oldUser.Name,
PasswordHash: oldUser.PasswordHash,
SSHAuthorizedKeys: oldUser.SSHAuthorizedKeys,
}
if oldUser.Create != nil {
var uid *uint
if oldUser.Create.Uid != nil {
tmp := uint(*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,
NoUserGroup: oldUser.Create.NoUserGroup,
System: oldUser.Create.System,
NoLogInit: oldUser.Create.NoLogInit,
Shell: oldUser.Create.Shell,
}
}
config.Passwd.Users = append(config.Passwd.Users, user)
}
for _, oldGroup := range old.Passwd.Groups {
var gid *uint
if oldGroup.Gid != nil {
tmp := uint(*oldGroup.Gid)
gid = &tmp
}
config.Passwd.Groups = append(config.Passwd.Groups, types.Group{
Name: oldGroup.Name,
Gid: gid,
PasswordHash: oldGroup.PasswordHash,
System: oldGroup.System,
})
}
return config
}

View File

@@ -15,22 +15,17 @@
package types
import (
"errors"
"github.com/coreos/ignition/config/shared/errors"
"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)
return report.ReportFromError(errors.ErrCompressionInvalid, report.EntryError)
}
return report.Report{}
}

View File

@@ -15,8 +15,7 @@
package types
import (
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -30,25 +29,25 @@ func (n Disk) Validate() report.Report {
r := report.Report{}
if len(n.Device) == 0 {
r.Add(report.Entry{
Message: "disk device is required",
Message: errors.ErrDiskDeviceRequired.Error(),
Kind: report.EntryError,
})
}
if n.partitionNumbersCollide() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partition numbers collide", n.Device),
Message: errors.ErrPartitionNumbersCollide.Error(),
Kind: report.EntryError,
})
}
if n.partitionsOverlap() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions overlap", n.Device),
Message: errors.ErrPartitionsOverlap.Error(),
Kind: report.EntryError,
})
}
if n.partitionsMisaligned() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions misaligned", n.Device),
Message: errors.ErrPartitionsMisaligned.Error(),
Kind: report.EntryError,
})
}

View File

@@ -15,17 +15,12 @@
package types
import (
"errors"
"os"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrFileIllegalMode = errors.New("illegal file mode")
ErrNoFilesystem = errors.New("no filesystem specified")
)
type File struct {
Filesystem string `json:"filesystem,omitempty"`
Path Path `json:"path,omitempty"`
@@ -37,7 +32,7 @@ type File struct {
func (f File) Validate() report.Report {
if f.Filesystem == "" {
return report.ReportFromError(ErrNoFilesystem, report.EntryError)
return report.ReportFromError(errors.ErrNoFilesystem, report.EntryError)
}
return report.Report{}
}
@@ -60,7 +55,7 @@ type FileMode os.FileMode
func (m FileMode) Validate() report.Report {
if (m &^ 07777) != 0 {
return report.ReportFromError(ErrFileIllegalMode, report.EntryError)
return report.ReportFromError(errors.ErrFileIllegalMode, report.EntryError)
}
return report.Report{}
}

View File

@@ -15,17 +15,10 @@
package types
import (
"errors"
"github.com/coreos/ignition/config/shared/errors"
"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")
)
type Filesystem struct {
Name string `json:"name,omitempty"`
Mount *FilesystemMount `json:"mount,omitempty"`
@@ -45,10 +38,10 @@ type FilesystemCreate struct {
func (f Filesystem) Validate() report.Report {
if f.Mount == nil && f.Path == nil {
return report.ReportFromError(ErrFilesystemNoMountPath, report.EntryError)
return report.ReportFromError(errors.ErrFilesystemNoMountPath, report.EntryError)
}
if f.Mount != nil && f.Path != nil {
return report.ReportFromError(ErrFilesystemMountAndPath, report.EntryError)
return report.ReportFromError(errors.ErrFilesystemMountAndPath, report.EntryError)
}
return report.Report{}
}
@@ -60,7 +53,7 @@ func (f FilesystemFormat) Validate() report.Report {
case "ext4", "btrfs", "xfs":
return report.Report{}
default:
return report.ReportFromError(ErrFilesystemInvalidFormat, report.EntryError)
return report.ReportFromError(errors.ErrFilesystemInvalidFormat, report.EntryError)
}
}

View File

@@ -18,18 +18,12 @@ import (
"crypto"
"encoding/hex"
"encoding/json"
"errors"
"strings"
"github.com/coreos/ignition/config/shared/errors"
"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")
)
type Hash struct {
Function string
Sum string
@@ -43,7 +37,7 @@ func (h *Hash) UnmarshalJSON(data []byte) error {
parts := strings.SplitN(th, "-", 2)
if len(parts) != 2 {
return ErrHashMalformed
return errors.ErrHashMalformed
}
h.Function = parts[0]
@@ -67,11 +61,11 @@ func (h Hash) Validate() report.Report {
case "sha512":
hash = crypto.SHA512
default:
return report.ReportFromError(ErrHashUnrecognized, report.EntryError)
return report.ReportFromError(errors.ErrHashUnrecognized, report.EntryError)
}
if len(h.Sum) != hex.EncodedLen(hash.Size()) {
return report.ReportFromError(ErrHashWrongSize, report.EntryError)
return report.ReportFromError(errors.ErrHashWrongSize, report.EntryError)
}
return report.Report{}

View File

@@ -16,18 +16,13 @@ package types
import (
"encoding/json"
"errors"
"github.com/coreos/go-semver/semver"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrOldVersion = errors.New("incorrect config version (too old)")
ErrNewVersion = errors.New("incorrect config version (too new)")
)
type Ignition struct {
Version IgnitionVersion `json:"version,omitempty" merge:"old"`
Config IgnitionConfig `json:"config,omitempty" merge:"new"`
@@ -60,10 +55,10 @@ func (v IgnitionVersion) MarshalJSON() ([]byte, error) {
func (v IgnitionVersion) Validate() report.Report {
if MaxVersion.Major > v.Major {
return report.ReportFromError(ErrOldVersion, report.EntryError)
return report.ReportFromError(errors.ErrOldVersion, report.EntryError)
}
if MaxVersion.LessThan(semver.Version(v)) {
return report.ReportFromError(ErrNewVersion, report.EntryError)
return report.ReportFromError(errors.ErrNewVersion, report.EntryError)
}
return report.Report{}
}

View File

@@ -17,7 +17,9 @@ package types
import (
"fmt"
"regexp"
"strings"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -38,7 +40,10 @@ func (n PartitionLabel) Validate() report.Report {
// 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)
return report.ReportFromError(errors.ErrLabelTooLong, report.EntryError)
}
if strings.Contains(string(n), ":") {
return report.ReportFromError(errors.ErrLabelContainsColon, report.EntryWarning)
}
return report.Report{}
}
@@ -53,7 +58,7 @@ func (d PartitionTypeGUID) Validate() report.Report {
return report.ReportFromError(fmt.Errorf("error matching type-guid regexp: %v", err), 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.ReportFromError(errors.ErrDoesntMatchGUIDRegex, report.EntryError)
}
return report.Report{}
}

View File

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

View File

@@ -15,8 +15,7 @@
package types
import (
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -31,7 +30,7 @@ func (n Raid) Validate() 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)
return report.ReportFromError(errors.ErrSparesUnsupportedForLevel, report.EntryError)
}
case "raid1", "1", "mirror":
case "raid4", "4":
@@ -39,7 +38,7 @@ 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)
return report.ReportFromError(errors.ErrUnrecognizedRaidLevel, report.EntryError)
}
return report.Report{}
}

View File

@@ -15,13 +15,14 @@
package types
import (
"bytes"
"errors"
"fmt"
"path"
"strings"
"github.com/coreos/go-systemd/unit"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/shared/validations"
"github.com/coreos/ignition/config/validate/report"
)
@@ -34,11 +35,15 @@ type SystemdUnit struct {
}
func (u SystemdUnit) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
r := report.Report{}
opts, err := validateUnitContent(u.Contents)
if err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
r.Merge(validations.ValidateInstallSection(string(u.Name), u.Enable, u.Contents == "", opts))
return r
}
type SystemdUnitDropIn struct {
@@ -47,7 +52,7 @@ type SystemdUnitDropIn struct {
}
func (u SystemdUnitDropIn) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
if _, err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
}
@@ -61,7 +66,7 @@ func (n SystemdUnitName) Validate() report.Report {
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)
return report.ReportFromError(errors.ErrInvalidSystemdExt, report.EntryError)
}
}
@@ -72,7 +77,7 @@ func (n SystemdUnitDropInName) Validate() report.Report {
case ".conf":
return report.Report{}
default:
return report.ReportFromError(errors.New("invalid systemd unit drop-in extension"), report.EntryError)
return report.ReportFromError(errors.ErrInvalidSystemdDropinExt, report.EntryError)
}
}
@@ -82,7 +87,7 @@ type NetworkdUnit struct {
}
func (u NetworkdUnit) Validate() report.Report {
if err := validateUnitContent(u.Contents); err != nil {
if _, err := validateUnitContent(u.Contents); err != nil {
return report.ReportFromError(err, report.EntryError)
}
@@ -96,16 +101,15 @@ func (n NetworkdUnitName) Validate() report.Report {
case ".link", ".netdev", ".network":
return report.Report{}
default:
return report.ReportFromError(errors.New("invalid networkd unit extension"), report.EntryError)
return report.ReportFromError(errors.ErrInvalidNetworkdExt, report.EntryError)
}
}
func validateUnitContent(content string) error {
c := bytes.NewBufferString(content)
_, err := unit.Deserialize(c)
func validateUnitContent(content string) ([]*unit.UnitOption, error) {
c := strings.NewReader(content)
opts, err := unit.Deserialize(c)
if err != nil {
return fmt.Errorf("invalid unit content: %s", err)
return nil, fmt.Errorf("invalid unit content: %s", err)
}
return nil
return opts, nil
}

View File

@@ -16,15 +16,12 @@ package types
import (
"encoding/json"
"errors"
"net/url"
"github.com/coreos/ignition/config/validate/report"
"github.com/vincent-petithory/dataurl"
)
var (
ErrInvalidScheme = errors.New("invalid url scheme")
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
type Url url.URL
@@ -37,7 +34,7 @@ func (u *Url) UnmarshalJSON(data []byte) error {
pu, err := url.Parse(tu)
if err != nil {
return err
return errors.ErrInvalidUrl
}
*u = Url(*pu)
@@ -67,6 +64,6 @@ func (u Url) Validate() report.Report {
}
return report.Report{}
default:
return report.ReportFromError(ErrInvalidScheme, report.EntryError)
return report.ReportFromError(errors.ErrInvalidScheme, report.EntryError)
}
}

View File

@@ -1,72 +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 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

@@ -15,102 +15,52 @@
package v2_1
import (
"bytes"
"errors"
"reflect"
"github.com/coreos/go-semver/semver"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/v2_0"
"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
return types.Config{}, report.Report{}, errors.ErrEmpty
} else if isCloudConfig(rawConfig) {
return types.Config{}, report.Report{}, ErrCloudConfig
return types.Config{}, report.Report{}, errors.ErrCloudConfig
} else if isScript(rawConfig) {
return types.Config{}, report.Report{}, ErrScript
return types.Config{}, report.Report{}, errors.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
err = json.Unmarshal(rawConfig, &config)
version, semverErr := semver.NewVersion(config.Ignition.Version)
if err != nil || semverErr != nil || version.LessThan(types.MaxVersion) {
// We can fail unmarshaling if it's an older config. Attempt to parse
// it as such.
config, rpt, err := v2_0.Parse(rawConfig)
if err != nil {
return types.Config{}, rpt, err
}
return TranslateFromV2_0(config), rpt, err
}
// 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 *version != types.MaxVersion {
return types.Config{}, report.Report{}, errors.ErrUnknownVersion
}
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
rpt := validate.ValidateConfig(rawConfig, config)
if rpt.IsFatal() {
return types.Config{}, rpt, errors.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
return config, rpt, nil
}
func isEmpty(userdata []byte) bool {

View File

@@ -0,0 +1,236 @@
// Copyright 2018 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 (
"strings"
"github.com/coreos/ignition/config/util"
v2_0 "github.com/coreos/ignition/config/v2_0/types"
"github.com/coreos/ignition/config/v2_1/types"
)
// golang--
func translateV2_0MkfsOptionsTov2_1OptionSlice(opts v2_0.MkfsOptions) []types.CreateOption {
newOpts := make([]types.CreateOption, len(opts))
for i, o := range opts {
newOpts[i] = types.CreateOption(o)
}
return newOpts
}
// golang--
func translateStringSliceTov2_1SSHAuthorizedKeySlice(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_1UsercreateGroupSlice(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 {
// .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: old.Source.String(),
Verification: translateVerification(old.Verification),
}
}
config := types.Config{
Ignition: types.Ignition{
Version: types.MaxVersion.String(),
},
}
if old.Ignition.Config.Replace != nil {
ref := translateConfigReference(*old.Ignition.Config.Replace)
config.Ignition.Config.Replace = &ref
}
for _, oldAppend := range old.Ignition.Config.Append {
config.Ignition.Config.Append =
append(config.Ignition.Config.Append, translateConfigReference(oldAppend))
}
for _, oldDisk := range old.Storage.Disks {
disk := types.Disk{
Device: string(oldDisk.Device),
WipeTable: oldDisk.WipeTable,
}
for _, oldPartition := range oldDisk.Partitions {
disk.Partitions = append(disk.Partitions, types.Partition{
Label: string(oldPartition.Label),
Number: oldPartition.Number,
Size: int(oldPartition.Size),
Start: int(oldPartition.Start),
TypeGUID: string(oldPartition.TypeGUID),
})
}
config.Storage.Disks = append(config.Storage.Disks, disk)
}
for _, oldArray := range old.Storage.Arrays {
array := types.Raid{
Name: oldArray.Name,
Level: oldArray.Level,
Spares: oldArray.Spares,
}
for _, oldDevice := range oldArray.Devices {
array.Devices = append(array.Devices, types.Device(oldDevice))
}
config.Storage.Raid = append(config.Storage.Raid, array)
}
for _, oldFilesystem := range old.Storage.Filesystems {
filesystem := types.Filesystem{
Name: oldFilesystem.Name,
}
if oldFilesystem.Mount != nil {
filesystem.Mount = &types.Mount{
Device: string(oldFilesystem.Mount.Device),
Format: string(oldFilesystem.Mount.Format),
}
if oldFilesystem.Mount.Create != nil {
filesystem.Mount.Create = &types.Create{
Force: oldFilesystem.Mount.Create.Force,
Options: translateV2_0MkfsOptionsTov2_1OptionSlice(oldFilesystem.Mount.Create.Options),
}
}
}
if oldFilesystem.Path != nil {
p := string(*oldFilesystem.Path)
filesystem.Path = &p
}
config.Storage.Filesystems = append(config.Storage.Filesystems, filesystem)
}
for _, oldFile := range old.Storage.Files {
file := types.File{
Node: types.Node{
Filesystem: oldFile.Filesystem,
Path: string(oldFile.Path),
User: types.NodeUser{ID: util.IntToPtr(oldFile.User.Id)},
Group: types.NodeGroup{ID: util.IntToPtr(oldFile.Group.Id)},
},
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),
},
},
}
config.Storage.Files = append(config.Storage.Files, file)
}
for _, oldUnit := range old.Systemd.Units {
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.Dropin{
Name: string(oldDropIn.Name),
Contents: oldDropIn.Contents,
})
}
config.Systemd.Units = append(config.Systemd.Units, unit)
}
for _, oldUnit := range old.Networkd.Units {
config.Networkd.Units = append(config.Networkd.Units, types.Networkdunit{
Name: string(oldUnit.Name),
Contents: oldUnit.Contents,
})
}
for _, oldUser := range old.Passwd.Users {
user := types.PasswdUser{
Name: oldUser.Name,
PasswordHash: util.StrToPtr(oldUser.PasswordHash),
SSHAuthorizedKeys: translateStringSliceTov2_1SSHAuthorizedKeySlice(oldUser.SSHAuthorizedKeys),
}
if oldUser.Create != nil {
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: translateStringSliceTov2_1UsercreateGroupSlice(oldUser.Create.Groups),
NoUserGroup: oldUser.Create.NoUserGroup,
System: oldUser.Create.System,
NoLogInit: oldUser.Create.NoLogInit,
Shell: oldUser.Create.Shell,
}
}
config.Passwd.Users = append(config.Passwd.Users, user)
}
for _, oldGroup := range old.Passwd.Groups {
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: g,
PasswordHash: oldGroup.PasswordHash,
System: oldGroup.System,
})
}
return config
}

View File

@@ -15,8 +15,7 @@
package types
import (
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -26,7 +25,7 @@ func (n Disk) Validate() report.Report {
func (n Disk) ValidateDevice() report.Report {
if len(n.Device) == 0 {
return report.ReportFromError(fmt.Errorf("disk device is required"), report.EntryError)
return report.ReportFromError(errors.ErrDiskDeviceRequired, report.EntryError)
}
if err := validatePath(string(n.Device)); err != nil {
return report.ReportFromError(err, report.EntryError)
@@ -38,19 +37,19 @@ 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),
Message: errors.ErrPartitionNumbersCollide.Error(),
Kind: report.EntryError,
})
}
if n.partitionsOverlap() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions overlap", n.Device),
Message: errors.ErrPartitionsOverlap.Error(),
Kind: report.EntryError,
})
}
if n.partitionsMisaligned() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions misaligned", n.Device),
Message: errors.ErrPartitionsMisaligned.Error(),
Kind: report.EntryError,
})
}

View File

@@ -15,16 +15,10 @@
package types
import (
"errors"
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"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 {
@@ -42,7 +36,7 @@ func (fc FileContents) ValidateCompression() report.Report {
case "", "gzip":
default:
r.Add(report.Entry{
Message: ErrCompressionInvalid.Error(),
Message: errors.ErrCompressionInvalid.Error(),
Kind: report.EntryError,
})
}
@@ -54,7 +48,7 @@ func (fc FileContents) ValidateSource() report.Report {
err := validateURL(fc.Source)
if err != nil {
r.Add(report.Entry{
Message: fmt.Sprintf("invalid url %q: %v", fc.Source, err),
Message: err.Error(),
Kind: report.EntryError,
})
}

View File

@@ -15,56 +15,40 @@
package types
import (
"errors"
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"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(),
Message: errors.ErrFilesystemNoMountPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount != nil {
if f.Path != nil {
r.Add(report.Entry{
Message: ErrFilesystemMountAndPath.Error(),
Message: errors.ErrFilesystemMountAndPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount.Create != nil {
if f.Mount.WipeFilesystem {
r.Add(report.Entry{
Message: ErrUsedCreateAndWipeFilesystem.Error(),
Message: errors.ErrUsedCreateAndWipeFilesystem.Error(),
Kind: report.EntryError,
})
}
if len(f.Mount.Options) > 0 {
r.Add(report.Entry{
Message: ErrUsedCreateAndMountOpts.Error(),
Message: errors.ErrUsedCreateAndMountOpts.Error(),
Kind: report.EntryError,
})
}
r.Add(report.Entry{
Message: ErrWarningCreateDeprecated.Error(),
Message: errors.ErrWarningCreateDeprecated.Error(),
Kind: report.EntryWarning,
})
}
@@ -76,7 +60,7 @@ 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),
Message: errors.ErrPathRelative.Error(),
Kind: report.EntryError,
})
}
@@ -89,7 +73,7 @@ func (m Mount) Validate() report.Report {
case "ext4", "btrfs", "xfs", "swap", "vfat":
default:
r.Add(report.Entry{
Message: ErrFilesystemInvalidFormat.Error(),
Message: errors.ErrFilesystemInvalidFormat.Error(),
Kind: report.EntryError,
})
}
@@ -117,7 +101,7 @@ func (m Mount) ValidateLabel() report.Report {
if len(*m.Label) > 16 {
// source: man mkfs.ext4
r.Add(report.Entry{
Message: ErrExt4LabelTooLong.Error(),
Message: errors.ErrExt4LabelTooLong.Error(),
Kind: report.EntryError,
})
}
@@ -125,7 +109,7 @@ func (m Mount) ValidateLabel() report.Report {
if len(*m.Label) > 256 {
// source: man mkfs.btrfs
r.Add(report.Entry{
Message: ErrBtrfsLabelTooLong.Error(),
Message: errors.ErrBtrfsLabelTooLong.Error(),
Kind: report.EntryError,
})
}
@@ -133,7 +117,7 @@ func (m Mount) ValidateLabel() report.Report {
if len(*m.Label) > 12 {
// source: man mkfs.xfs
r.Add(report.Entry{
Message: ErrXfsLabelTooLong.Error(),
Message: errors.ErrXfsLabelTooLong.Error(),
Kind: report.EntryError,
})
}
@@ -143,7 +127,7 @@ func (m Mount) ValidateLabel() report.Report {
// 15 characters, so let's enforce that.
if len(*m.Label) > 15 {
r.Add(report.Entry{
Message: ErrSwapLabelTooLong.Error(),
Message: errors.ErrSwapLabelTooLong.Error(),
Kind: report.EntryError,
})
}
@@ -151,7 +135,7 @@ func (m Mount) ValidateLabel() report.Report {
if len(*m.Label) > 11 {
// source: man mkfs.fat
r.Add(report.Entry{
Message: ErrVfatLabelTooLong.Error(),
Message: errors.ErrVfatLabelTooLong.Error(),
Kind: report.EntryError,
})
}

View File

@@ -15,19 +15,12 @@
package types
import (
"errors"
"github.com/coreos/go-semver/semver"
"github.com/coreos/ignition/config/shared/errors"
"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)
@@ -47,13 +40,13 @@ func (v Ignition) Semver() (*semver.Version, error) {
func (v Ignition) Validate() report.Report {
tv, err := v.Semver()
if err != nil {
return report.ReportFromError(ErrInvalidVersion, report.EntryError)
return report.ReportFromError(errors.ErrInvalidVersion, report.EntryError)
}
if MaxVersion.Major > tv.Major {
return report.ReportFromError(ErrOldVersion, report.EntryError)
return report.ReportFromError(errors.ErrOldVersion, report.EntryError)
}
if MaxVersion.LessThan(*tv) {
return report.ReportFromError(ErrNewVersion, report.EntryError)
return report.ReportFromError(errors.ErrNewVersion, report.EntryError)
}
return report.Report{}
}

View File

@@ -15,16 +15,12 @@
package types
import (
"errors"
)
var (
ErrFileIllegalMode = errors.New("illegal file mode")
"github.com/coreos/ignition/config/shared/errors"
)
func validateMode(m int) error {
if m < 0 || m > 07777 {
return ErrFileIllegalMode
return errors.ErrFileIllegalMode
}
return nil
}

View File

@@ -15,22 +15,17 @@
package types
import (
"errors"
"path/filepath"
"github.com/coreos/ignition/config/shared/errors"
"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(),
Message: errors.ErrNoFilesystem.Error(),
Kind: report.EntryError,
})
}
@@ -60,7 +55,7 @@ func (nu NodeUser) Validate() report.Report {
r := report.Report{}
if nu.ID != nil && nu.Name != "" {
r.Add(report.Entry{
Message: ErrBothIDAndNameSet.Error(),
Message: errors.ErrBothIDAndNameSet.Error(),
Kind: report.EntryError,
})
}
@@ -70,7 +65,7 @@ func (ng NodeGroup) Validate() report.Report {
r := report.Report{}
if ng.ID != nil && ng.Name != "" {
r.Add(report.Entry{
Message: ErrBothIDAndNameSet.Error(),
Message: errors.ErrBothIDAndNameSet.Error(),
Kind: report.EntryError,
})
}

View File

@@ -15,10 +15,11 @@
package types
import (
"errors"
"fmt"
"regexp"
"strings"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -26,11 +27,6 @@ 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:
@@ -40,10 +36,18 @@ func (p Partition) ValidateLabel() report.Report {
// with udev naming /dev/disk/by-partlabel/*.
if len(p.Label) > 36 {
r.Add(report.Entry{
Message: ErrLabelTooLong.Error(),
Message: errors.ErrLabelTooLong.Error(),
Kind: report.EntryError,
})
}
// sgdisk uses colons for delimitting compound arguments and does not allow escaping them.
if strings.Contains(p.Label, ":") {
r.Add(report.Entry{
Message: errors.ErrLabelContainsColon.Error(),
Kind: report.EntryWarning,
})
}
return r
}
@@ -65,7 +69,7 @@ func validateGUID(guid string) report.Report {
})
} else if !ok {
r.Add(report.Entry{
Message: ErrDoesntMatchGUIDRegex.Error(),
Message: errors.ErrDoesntMatchGUIDRegex.Error(),
Kind: report.EntryError,
})
}

View File

@@ -15,30 +15,15 @@
package types
import (
"errors"
"github.com/coreos/ignition/config/shared/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(),
Message: errors.ErrPasswdCreateDeprecated.Error(),
Kind: report.EntryWarning,
})
addErr := func(err error) {
@@ -48,34 +33,34 @@ func (p PasswdUser) Validate() report.Report {
})
}
if p.Gecos != "" {
addErr(ErrPasswdCreateAndGecos)
addErr(errors.ErrPasswdCreateAndGecos)
}
if len(p.Groups) > 0 {
addErr(ErrPasswdCreateAndGroups)
addErr(errors.ErrPasswdCreateAndGroups)
}
if p.HomeDir != "" {
addErr(ErrPasswdCreateAndHomeDir)
addErr(errors.ErrPasswdCreateAndHomeDir)
}
if p.NoCreateHome {
addErr(ErrPasswdCreateAndNoCreateHome)
addErr(errors.ErrPasswdCreateAndNoCreateHome)
}
if p.NoLogInit {
addErr(ErrPasswdCreateAndNoLogInit)
addErr(errors.ErrPasswdCreateAndNoLogInit)
}
if p.NoUserGroup {
addErr(ErrPasswdCreateAndNoUserGroup)
addErr(errors.ErrPasswdCreateAndNoUserGroup)
}
if p.PrimaryGroup != "" {
addErr(ErrPasswdCreateAndPrimaryGroup)
addErr(errors.ErrPasswdCreateAndPrimaryGroup)
}
if p.Shell != "" {
addErr(ErrPasswdCreateAndShell)
addErr(errors.ErrPasswdCreateAndShell)
}
if p.System {
addErr(ErrPasswdCreateAndSystem)
addErr(errors.ErrPasswdCreateAndSystem)
}
if p.UID != nil {
addErr(ErrPasswdCreateAndUID)
addErr(errors.ErrPasswdCreateAndUID)
}
}
return r

View File

@@ -15,17 +15,14 @@
package types
import (
"errors"
"path"
)
var (
ErrPathRelative = errors.New("path not absolute")
"github.com/coreos/ignition/config/shared/errors"
)
func validatePath(p string) error {
if !path.IsAbs(p) {
return ErrPathRelative
return errors.ErrPathRelative
}
return nil
}

View File

@@ -15,8 +15,7 @@
package types
import (
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -26,7 +25,7 @@ func (n Raid) ValidateLevel() report.Report {
case "linear", "raid0", "0", "stripe":
if n.Spares != 0 {
r.Add(report.Entry{
Message: fmt.Sprintf("spares unsupported for %q arrays", n.Level),
Message: errors.ErrSparesUnsupportedForLevel.Error(),
Kind: report.EntryError,
})
}
@@ -37,7 +36,7 @@ func (n Raid) ValidateLevel() report.Report {
case "raid10", "10":
default:
r.Add(report.Entry{
Message: fmt.Sprintf("unrecognized raid level: %q", n.Level),
Message: errors.ErrUnrecognizedRaidLevel.Error(),
Kind: report.EntryError,
})
}
@@ -49,7 +48,7 @@ func (n Raid) ValidateDevices() 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),
Message: errors.ErrPathRelative.Error(),
Kind: report.EntryError,
})
}

View File

@@ -15,29 +15,30 @@
package types
import (
"bytes"
"errors"
"fmt"
"path"
"strings"
"github.com/coreos/go-systemd/unit"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/shared/validations"
"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 {
opts, err := validateUnitContent(u.Contents)
if err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
isEnabled := u.Enable || (u.Enabled != nil && *u.Enabled)
r.Merge(validations.ValidateInstallSection(u.Name, isEnabled, u.Contents == "", opts))
return r
}
@@ -47,7 +48,7 @@ func (u Unit) ValidateName() report.Report {
case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope":
default:
r.Add(report.Entry{
Message: ErrInvalidSystemdExt.Error(),
Message: errors.ErrInvalidSystemdExt.Error(),
Kind: report.EntryError,
})
}
@@ -57,7 +58,7 @@ func (u Unit) ValidateName() report.Report {
func (d Dropin) Validate() report.Report {
r := report.Report{}
if err := validateUnitContent(d.Contents); err != nil {
if _, err := validateUnitContent(d.Contents); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
@@ -68,7 +69,7 @@ func (d Dropin) Validate() report.Report {
case ".conf":
default:
r.Add(report.Entry{
Message: fmt.Sprintf("invalid systemd unit drop-in extension: %q", path.Ext(d.Name)),
Message: errors.ErrInvalidSystemdDropinExt.Error(),
Kind: report.EntryError,
})
}
@@ -79,7 +80,7 @@ func (d Dropin) Validate() report.Report {
func (u Networkdunit) Validate() report.Report {
r := report.Report{}
if err := validateUnitContent(u.Contents); err != nil {
if _, err := validateUnitContent(u.Contents); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
@@ -90,7 +91,7 @@ func (u Networkdunit) Validate() report.Report {
case ".link", ".netdev", ".network":
default:
r.Add(report.Entry{
Message: ErrInvalidNetworkdExt.Error(),
Message: errors.ErrInvalidNetworkdExt.Error(),
Kind: report.EntryError,
})
}
@@ -98,12 +99,11 @@ func (u Networkdunit) Validate() report.Report {
return r
}
func validateUnitContent(content string) error {
c := bytes.NewBufferString(content)
_, err := unit.Deserialize(c)
func validateUnitContent(content string) ([]*unit.UnitOption, error) {
c := strings.NewReader(content)
opts, err := unit.Deserialize(c)
if err != nil {
return fmt.Errorf("invalid unit content: %s", err)
return nil, fmt.Errorf("invalid unit content: %s", err)
}
return nil
return opts, nil
}

View File

@@ -15,16 +15,12 @@
package types
import (
"errors"
"net/url"
"github.com/coreos/ignition/config/shared/errors"
"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 == "" {
@@ -32,11 +28,18 @@ func validateURL(s string) error {
}
u, err := url.Parse(s)
if err != nil {
return err
return errors.ErrInvalidUrl
}
switch u.Scheme {
case "http", "https", "oem", "tftp", "s3":
case "http", "https", "oem", "tftp":
return nil
case "s3":
if v, ok := u.Query()["versionId"]; ok {
if len(v) == 0 || v[0] == "" {
return errors.ErrInvalidS3ObjectVersionId
}
}
return nil
case "data":
if _, err := dataurl.DecodeString(s); err != nil {
@@ -44,6 +47,6 @@ func validateURL(s string) error {
}
return nil
default:
return ErrInvalidScheme
return errors.ErrInvalidScheme
}
}

View File

@@ -17,18 +17,12 @@ package types
import (
"crypto"
"encoding/hex"
"errors"
"strings"
"github.com/coreos/ignition/config/shared/errors"
"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) {
@@ -38,7 +32,7 @@ func (v Verification) HashParts() (string, string, error) {
}
parts := strings.SplitN(*v.Hash, "-", 2)
if len(parts) != 2 {
return "", "", ErrHashMalformed
return "", "", errors.ErrHashMalformed
}
return parts[0], parts[1], nil
@@ -66,7 +60,7 @@ func (v Verification) Validate() report.Report {
hash = crypto.SHA512
default:
r.Add(report.Entry{
Message: ErrHashUnrecognized.Error(),
Message: errors.ErrHashUnrecognized.Error(),
Kind: report.EntryError,
})
return r
@@ -74,7 +68,7 @@ func (v Verification) Validate() report.Report {
if len(sum) != hex.EncodedLen(hash.Size()) {
r.Add(report.Entry{
Message: ErrHashWrongSize.Error(),
Message: errors.ErrHashWrongSize.Error(),
Kind: report.EntryError,
})
}

View File

@@ -14,7 +14,7 @@
// These functions are copied from github.com/coreos/coreos-cloudinit/config.
package config
package v2_2
import (
"bytes"

View File

@@ -0,0 +1,71 @@
// 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_2
import (
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/v2_1"
"github.com/coreos/ignition/config/v2_2/types"
"github.com/coreos/ignition/config/validate"
"github.com/coreos/ignition/config/validate/report"
json "github.com/ajeddeloh/go-json"
"github.com/coreos/go-semver/semver"
)
// Parse parses the raw config into a types.Config struct and generates a report of any
// errors, warnings, info, and deprecations it encountered. Unlike config.Parse,
// it does not attempt to translate the config.
func Parse(rawConfig []byte) (types.Config, report.Report, error) {
if isEmpty(rawConfig) {
return types.Config{}, report.Report{}, errors.ErrEmpty
} else if isCloudConfig(rawConfig) {
return types.Config{}, report.Report{}, errors.ErrCloudConfig
} else if isScript(rawConfig) {
return types.Config{}, report.Report{}, errors.ErrScript
}
var err error
var config types.Config
err = json.Unmarshal(rawConfig, &config)
version, semverErr := semver.NewVersion(config.Ignition.Version)
if err != nil || semverErr != nil || version.LessThan(types.MaxVersion) {
// We can fail unmarshaling if it's an older config. Attempt to parse
// it as such.
config, rpt, err := v2_1.Parse(rawConfig)
if err != nil {
return types.Config{}, rpt, err
}
return TranslateFromV2_1(config), rpt, err
}
if *version != types.MaxVersion {
return types.Config{}, report.Report{}, errors.ErrUnknownVersion
}
rpt := validate.ValidateConfig(rawConfig, config)
if rpt.IsFatal() {
return types.Config{}, rpt, errors.ErrInvalid
}
return config, rpt, nil
}
func isEmpty(userdata []byte) bool {
return len(userdata) == 0
}

View File

@@ -0,0 +1,354 @@
// Copyright 2018 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_2
import (
"github.com/coreos/ignition/config/util"
v2_1 "github.com/coreos/ignition/config/v2_1/types"
"github.com/coreos/ignition/config/v2_2/types"
)
// 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_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.Group {
var res []types.Group
for _, g := range old {
res = append(res, types.Group(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: util.IntToPtr(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: util.IntToPtr(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.SystemdDropin {
var res []types.SystemdDropin
for _, x := range old {
res = append(res, types.SystemdDropin{
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

@@ -1,4 +1,4 @@
// Copyright 2017 CoreOS, Inc.
// Copyright 2018 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,21 +15,13 @@
package types
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,
})
}
func (c CaReference) ValidateSource() report.Report {
err := validateURL(c.Source)
if err != nil {
return report.ReportFromError(err, report.EntryError)
}
return r
return report.Report{}
}

View File

@@ -0,0 +1,91 @@
// 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: 2,
}
)
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 checkNodeFilesystems(node Node, filesystems map[string]struct{}, nodeType string) report.Report {
r := report.Report{}
if node.Filesystem == "" {
// Filesystem was not specified. This is an error, but its handled in types.File's Validate, not here
return r
}
_, ok := filesystems[node.Filesystem]
if !ok {
r.Add(report.Entry{
Kind: report.EntryWarning,
Message: fmt.Sprintf("%v %q references nonexistent filesystem %q. (This is ok if it is defined in a referenced config)",
nodeType, node.Path, node.Filesystem),
})
}
return r
}
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 {
r.Merge(checkNodeFilesystems(file.Node, filesystems, "File"))
}
for _, link := range cfg.Storage.Links {
r.Merge(checkNodeFilesystems(link.Node, filesystems, "Link"))
}
for _, dir := range cfg.Storage.Directories {
r.Merge(checkNodeFilesystems(dir.Node, filesystems, "Directory"))
}
}
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

@@ -15,6 +15,7 @@
package types
import (
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -26,5 +27,11 @@ func (d Directory) ValidateMode() report.Report {
Kind: report.EntryError,
})
}
if d.Mode == nil {
r.Add(report.Entry{
Message: errors.ErrPermissionsUnset.Error(),
Kind: report.EntryWarning,
})
}
return r
}

View File

@@ -15,8 +15,7 @@
package types
import (
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -26,7 +25,7 @@ func (n Disk) Validate() report.Report {
func (n Disk) ValidateDevice() report.Report {
if len(n.Device) == 0 {
return report.ReportFromError(fmt.Errorf("disk device is required"), report.EntryError)
return report.ReportFromError(errors.ErrDiskDeviceRequired, report.EntryError)
}
if err := validatePath(string(n.Device)); err != nil {
return report.ReportFromError(err, report.EntryError)
@@ -38,19 +37,19 @@ 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),
Message: errors.ErrPartitionNumbersCollide.Error(),
Kind: report.EntryError,
})
}
if n.partitionsOverlap() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions overlap", n.Device),
Message: errors.ErrPartitionsOverlap.Error(),
Kind: report.EntryError,
})
}
if n.partitionsMisaligned() {
r.Add(report.Entry{
Message: fmt.Sprintf("disk %q: partitions misaligned", n.Device),
Message: errors.ErrPartitionsMisaligned.Error(),
Kind: report.EntryError,
})
}

View File

@@ -0,0 +1,69 @@
// 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 (
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
func (f File) Validate() report.Report {
if f.Overwrite != nil && *f.Overwrite && f.Append {
return report.ReportFromError(errors.ErrAppendAndOverwrite, report.EntryError)
}
return report.Report{}
}
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,
})
}
if f.Mode == nil {
r.Add(report.Entry{
Message: errors.ErrPermissionsUnset.Error(),
Kind: report.EntryWarning,
})
}
return r
}
func (fc FileContents) ValidateCompression() report.Report {
r := report.Report{}
switch fc.Compression {
case "", "gzip":
default:
r.Add(report.Entry{
Message: errors.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: err.Error(),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -0,0 +1,144 @@
// 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 (
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
func (f Filesystem) Validate() report.Report {
r := report.Report{}
if f.Mount == nil && f.Path == nil {
r.Add(report.Entry{
Message: errors.ErrFilesystemNoMountPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount != nil {
if f.Path != nil {
r.Add(report.Entry{
Message: errors.ErrFilesystemMountAndPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount.Create != nil {
if f.Mount.WipeFilesystem {
r.Add(report.Entry{
Message: errors.ErrUsedCreateAndWipeFilesystem.Error(),
Kind: report.EntryError,
})
}
if len(f.Mount.Options) > 0 {
r.Add(report.Entry{
Message: errors.ErrUsedCreateAndMountOpts.Error(),
Kind: report.EntryError,
})
}
r.Add(report.Entry{
Message: errors.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: errors.ErrPathRelative.Error(),
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: errors.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: errors.ErrExt4LabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "btrfs":
if len(*m.Label) > 256 {
// source: man mkfs.btrfs
r.Add(report.Entry{
Message: errors.ErrBtrfsLabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "xfs":
if len(*m.Label) > 12 {
// source: man mkfs.xfs
r.Add(report.Entry{
Message: errors.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: errors.ErrSwapLabelTooLong.Error(),
Kind: report.EntryError,
})
}
case "vfat":
if len(*m.Label) > 11 {
// source: man mkfs.fat
r.Add(report.Entry{
Message: errors.ErrVfatLabelTooLong.Error(),
Kind: report.EntryError,
})
}
}
return r
}

View File

@@ -15,19 +15,12 @@
package types
import (
"errors"
"github.com/coreos/go-semver/semver"
"github.com/coreos/ignition/config/shared/errors"
"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)
@@ -47,13 +40,13 @@ func (v Ignition) Semver() (*semver.Version, error) {
func (v Ignition) Validate() report.Report {
tv, err := v.Semver()
if err != nil {
return report.ReportFromError(ErrInvalidVersion, report.EntryError)
return report.ReportFromError(errors.ErrInvalidVersion, report.EntryError)
}
if MaxVersion.Major > tv.Major {
return report.ReportFromError(ErrOldVersion, report.EntryError)
return report.ReportFromError(errors.ErrOldVersion, report.EntryError)
}
if MaxVersion.LessThan(*tv) {
return report.ReportFromError(ErrNewVersion, report.EntryError)
return report.ReportFromError(errors.ErrNewVersion, report.EntryError)
}
return report.Report{}
}

View File

@@ -15,16 +15,12 @@
package types
import (
"errors"
"github.com/coreos/ignition/config/shared/errors"
)
var (
ErrFileIllegalMode = errors.New("illegal file mode")
)
func validateMode(m int) error {
if m < 0 || m > 07777 {
return ErrFileIllegalMode
func validateMode(m *int) error {
if m != nil && (*m < 0 || *m > 07777) {
return errors.ErrFileIllegalMode
}
return nil
}

View File

@@ -15,22 +15,17 @@
package types
import (
"errors"
"path/filepath"
"github.com/coreos/ignition/config/shared/errors"
"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(),
Message: errors.ErrNoFilesystem.Error(),
Kind: report.EntryError,
})
}
@@ -60,7 +55,7 @@ func (nu NodeUser) Validate() report.Report {
r := report.Report{}
if nu.ID != nil && nu.Name != "" {
r.Add(report.Entry{
Message: ErrBothIDAndNameSet.Error(),
Message: errors.ErrBothIDAndNameSet.Error(),
Kind: report.EntryError,
})
}
@@ -70,7 +65,7 @@ func (ng NodeGroup) Validate() report.Report {
r := report.Report{}
if ng.ID != nil && ng.Name != "" {
r.Add(report.Entry{
Message: ErrBothIDAndNameSet.Error(),
Message: errors.ErrBothIDAndNameSet.Error(),
Kind: report.EntryError,
})
}

View File

@@ -15,10 +15,11 @@
package types
import (
"errors"
"fmt"
"regexp"
"strings"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -26,11 +27,6 @@ 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:
@@ -40,10 +36,18 @@ func (p Partition) ValidateLabel() report.Report {
// with udev naming /dev/disk/by-partlabel/*.
if len(p.Label) > 36 {
r.Add(report.Entry{
Message: ErrLabelTooLong.Error(),
Message: errors.ErrLabelTooLong.Error(),
Kind: report.EntryError,
})
}
// sgdisk uses colons for delimitting compound arguments and does not allow escaping them.
if strings.Contains(p.Label, ":") {
r.Add(report.Entry{
Message: errors.ErrLabelContainsColon.Error(),
Kind: report.EntryWarning,
})
}
return r
}
@@ -65,7 +69,7 @@ func validateGUID(guid string) report.Report {
})
} else if !ok {
r.Add(report.Entry{
Message: ErrDoesntMatchGUIDRegex.Error(),
Message: errors.ErrDoesntMatchGUIDRegex.Error(),
Kind: report.EntryError,
})
}

View File

@@ -0,0 +1,67 @@
// 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 (
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
func (p PasswdUser) Validate() report.Report {
r := report.Report{}
if p.Create != nil {
r.Add(report.Entry{
Message: errors.ErrPasswdCreateDeprecated.Error(),
Kind: report.EntryWarning,
})
addErr := func(err error) {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
if p.Gecos != "" {
addErr(errors.ErrPasswdCreateAndGecos)
}
if len(p.Groups) > 0 {
addErr(errors.ErrPasswdCreateAndGroups)
}
if p.HomeDir != "" {
addErr(errors.ErrPasswdCreateAndHomeDir)
}
if p.NoCreateHome {
addErr(errors.ErrPasswdCreateAndNoCreateHome)
}
if p.NoLogInit {
addErr(errors.ErrPasswdCreateAndNoLogInit)
}
if p.NoUserGroup {
addErr(errors.ErrPasswdCreateAndNoUserGroup)
}
if p.PrimaryGroup != "" {
addErr(errors.ErrPasswdCreateAndPrimaryGroup)
}
if p.Shell != "" {
addErr(errors.ErrPasswdCreateAndShell)
}
if p.System {
addErr(errors.ErrPasswdCreateAndSystem)
}
if p.UID != nil {
addErr(errors.ErrPasswdCreateAndUID)
}
}
return r
}

View File

@@ -15,17 +15,14 @@
package types
import (
"errors"
"path"
)
var (
ErrPathRelative = errors.New("path not absolute")
"github.com/coreos/ignition/config/shared/errors"
)
func validatePath(p string) error {
if !path.IsAbs(p) {
return ErrPathRelative
return errors.ErrPathRelative
}
return nil
}

View File

@@ -15,8 +15,7 @@
package types
import (
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
@@ -26,7 +25,7 @@ func (n Raid) ValidateLevel() report.Report {
case "linear", "raid0", "0", "stripe":
if n.Spares != 0 {
r.Add(report.Entry{
Message: fmt.Sprintf("spares unsupported for %q arrays", n.Level),
Message: errors.ErrSparesUnsupportedForLevel.Error(),
Kind: report.EntryError,
})
}
@@ -37,7 +36,7 @@ func (n Raid) ValidateLevel() report.Report {
case "raid10", "10":
default:
r.Add(report.Entry{
Message: fmt.Sprintf("unrecognized raid level: %q", n.Level),
Message: errors.ErrUnrecognizedRaidLevel.Error(),
Kind: report.EntryError,
})
}
@@ -49,7 +48,7 @@ func (n Raid) ValidateDevices() 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),
Message: errors.ErrPathRelative.Error(),
Kind: report.EntryError,
})
}

View File

@@ -2,6 +2,11 @@ package types
// generated by "schematyper --package=types schema/ignition.json -o config/types/schema.go --root-type=Config" -- DO NOT EDIT
type CaReference struct {
Source string `json:"source,omitempty"`
Verification Verification `json:"verification,omitempty"`
}
type Config struct {
Ignition Ignition `json:"ignition"`
Networkd Networkd `json:"networkd,omitempty"`
@@ -30,7 +35,7 @@ type Directory struct {
}
type DirectoryEmbedded1 struct {
Mode int `json:"mode,omitempty"`
Mode *int `json:"mode,omitempty"`
}
type Disk struct {
@@ -39,11 +44,6 @@ type Disk struct {
WipeTable bool `json:"wipeTable,omitempty"`
}
type Dropin struct {
Contents string `json:"contents,omitempty"`
Name string `json:"name,omitempty"`
}
type File struct {
Node
FileEmbedded1
@@ -56,8 +56,9 @@ type FileContents struct {
}
type FileEmbedded1 struct {
Append bool `json:"append,omitempty"`
Contents FileContents `json:"contents,omitempty"`
Mode int `json:"mode,omitempty"`
Mode *int `json:"mode,omitempty"`
}
type Filesystem struct {
@@ -66,8 +67,11 @@ type Filesystem struct {
Path *string `json:"path,omitempty"`
}
type Group string
type Ignition struct {
Config IgnitionConfig `json:"config,omitempty"`
Security Security `json:"security,omitempty"`
Timeouts Timeouts `json:"timeouts,omitempty"`
Version string `json:"version,omitempty"`
}
@@ -103,16 +107,23 @@ type Networkd struct {
Units []Networkdunit `json:"units,omitempty"`
}
type Networkdunit struct {
type NetworkdDropin struct {
Contents string `json:"contents,omitempty"`
Name string `json:"name,omitempty"`
}
type Networkdunit struct {
Contents string `json:"contents,omitempty"`
Dropins []NetworkdDropin `json:"dropins,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"`
Filesystem string `json:"filesystem,omitempty"`
Group *NodeGroup `json:"group,omitempty"`
Overwrite *bool `json:"overwrite,omitempty"`
Path string `json:"path,omitempty"`
User *NodeUser `json:"user,omitempty"`
}
type NodeGroup struct {
@@ -149,7 +160,7 @@ type PasswdGroup struct {
type PasswdUser struct {
Create *Usercreate `json:"create,omitempty"`
Gecos string `json:"gecos,omitempty"`
Groups []PasswdUserGroup `json:"groups,omitempty"`
Groups []Group `json:"groups,omitempty"`
HomeDir string `json:"homeDir,omitempty"`
Name string `json:"name,omitempty"`
NoCreateHome bool `json:"noCreateHome,omitempty"`
@@ -163,17 +174,22 @@ type PasswdUser struct {
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"`
Devices []Device `json:"devices,omitempty"`
Level string `json:"level,omitempty"`
Name string `json:"name,omitempty"`
Options []RaidOption `json:"options,omitempty"`
Spares int `json:"spares,omitempty"`
}
type RaidOption string
type SSHAuthorizedKey string
type Security struct {
TLS TLS `json:"tls,omitempty"`
}
type Storage struct {
Directories []Directory `json:"directories,omitempty"`
Disks []Disk `json:"disks,omitempty"`
@@ -187,18 +203,27 @@ type Systemd struct {
Units []Unit `json:"units,omitempty"`
}
type SystemdDropin struct {
Contents string `json:"contents,omitempty"`
Name string `json:"name,omitempty"`
}
type TLS struct {
CertificateAuthorities []CaReference `json:"certificateAuthorities,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"`
Contents string `json:"contents,omitempty"`
Dropins []SystemdDropin `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 {

View File

@@ -15,29 +15,30 @@
package types
import (
"bytes"
"errors"
"fmt"
"path"
"strings"
"github.com/coreos/go-systemd/unit"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/shared/validations"
"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 {
opts, err := validateUnitContent(u.Contents)
if err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
isEnabled := u.Enable || (u.Enabled != nil && *u.Enabled)
r.Merge(validations.ValidateInstallSection(u.Name, isEnabled, u.Contents == "", opts))
return r
}
@@ -47,17 +48,17 @@ func (u Unit) ValidateName() report.Report {
case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope":
default:
r.Add(report.Entry{
Message: ErrInvalidSystemdExt.Error(),
Message: errors.ErrInvalidSystemdExt.Error(),
Kind: report.EntryError,
})
}
return r
}
func (d Dropin) Validate() report.Report {
func (d SystemdDropin) Validate() report.Report {
r := report.Report{}
if err := validateUnitContent(d.Contents); err != nil {
if _, err := validateUnitContent(d.Contents); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
@@ -68,7 +69,7 @@ func (d Dropin) Validate() report.Report {
case ".conf":
default:
r.Add(report.Entry{
Message: fmt.Sprintf("invalid systemd unit drop-in extension: %q", path.Ext(d.Name)),
Message: errors.ErrInvalidSystemdDropinExt.Error(),
Kind: report.EntryError,
})
}
@@ -79,7 +80,7 @@ func (d Dropin) Validate() report.Report {
func (u Networkdunit) Validate() report.Report {
r := report.Report{}
if err := validateUnitContent(u.Contents); err != nil {
if _, err := validateUnitContent(u.Contents); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
@@ -90,7 +91,7 @@ func (u Networkdunit) Validate() report.Report {
case ".link", ".netdev", ".network":
default:
r.Add(report.Entry{
Message: ErrInvalidNetworkdExt.Error(),
Message: errors.ErrInvalidNetworkdExt.Error(),
Kind: report.EntryError,
})
}
@@ -98,12 +99,33 @@ func (u Networkdunit) Validate() report.Report {
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)
func (d NetworkdDropin) Validate() report.Report {
r := report.Report{}
if _, err := validateUnitContent(d.Contents); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
return nil
switch path.Ext(d.Name) {
case ".conf":
default:
r.Add(report.Entry{
Message: errors.ErrInvalidNetworkdDropinExt.Error(),
Kind: report.EntryError,
})
}
return r
}
func validateUnitContent(content string) ([]*unit.UnitOption, error) {
c := strings.NewReader(content)
opts, err := unit.Deserialize(c)
if err != nil {
return nil, fmt.Errorf("invalid unit content: %s", err)
}
return opts, nil
}

View File

@@ -15,14 +15,11 @@
package types
import (
"errors"
"net/url"
"github.com/vincent-petithory/dataurl"
)
var (
ErrInvalidScheme = errors.New("invalid url scheme")
"github.com/coreos/ignition/config/shared/errors"
)
func validateURL(s string) error {
@@ -32,11 +29,18 @@ func validateURL(s string) error {
}
u, err := url.Parse(s)
if err != nil {
return err
return errors.ErrInvalidUrl
}
switch u.Scheme {
case "http", "https", "oem", "tftp", "s3":
case "http", "https", "oem", "tftp":
return nil
case "s3":
if v, ok := u.Query()["versionId"]; ok {
if len(v) == 0 || v[0] == "" {
return errors.ErrInvalidS3ObjectVersionId
}
}
return nil
case "data":
if _, err := dataurl.DecodeString(s); err != nil {
@@ -44,6 +48,6 @@ func validateURL(s string) error {
}
return nil
default:
return ErrInvalidScheme
return errors.ErrInvalidScheme
}
}

View File

@@ -17,18 +17,12 @@ package types
import (
"crypto"
"encoding/hex"
"errors"
"strings"
"github.com/coreos/ignition/config/shared/errors"
"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) {
@@ -38,7 +32,7 @@ func (v Verification) HashParts() (string, string, error) {
}
parts := strings.SplitN(*v.Hash, "-", 2)
if len(parts) != 2 {
return "", "", ErrHashMalformed
return "", "", errors.ErrHashMalformed
}
return parts[0], parts[1], nil
@@ -66,7 +60,7 @@ func (v Verification) Validate() report.Report {
hash = crypto.SHA512
default:
r.Add(report.Entry{
Message: ErrHashUnrecognized.Error(),
Message: errors.ErrHashUnrecognized.Error(),
Kind: report.EntryError,
})
return r
@@ -74,7 +68,7 @@ func (v Verification) Validate() report.Report {
if len(sum) != hex.EncodedLen(hash.Size()) {
r.Add(report.Entry{
Message: ErrHashWrongSize.Error(),
Message: errors.ErrHashWrongSize.Error(),
Kind: report.EntryError,
})
}

View File

@@ -0,0 +1,27 @@
// Copyright 2018 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 (
"github.com/coreos/ignition/config/validate/report"
)
func (c CaReference) ValidateSource() report.Report {
err := validateURL(c.Source)
if err != nil {
return report.ReportFromError(err, report.EntryError)
}
return report.Report{}
}

View File

@@ -25,7 +25,7 @@ import (
var (
MaxVersion = semver.Version{
Major: 2,
Minor: 2,
Minor: 4,
PreRelease: "experimental",
}
)

View File

@@ -15,21 +15,23 @@
package types
import (
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
func (s Link) Validate() report.Report {
func (d Directory) ValidateMode() 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,
})
}
if err := validateMode(d.Mode); err != nil {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
if d.Mode == nil {
r.Add(report.Entry{
Message: errors.ErrPermissionsUnset.Error(),
Kind: report.EntryWarning,
})
}
return r
}

View File

@@ -0,0 +1,164 @@
// 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 (
"github.com/coreos/ignition/config/shared/errors"
"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(errors.ErrDiskDeviceRequired, 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: errors.ErrPartitionNumbersCollide.Error(),
Kind: report.EntryError,
})
}
if n.partitionsOverlap() {
r.Add(report.Entry{
Message: errors.ErrPartitionsOverlap.Error(),
Kind: report.EntryError,
})
}
if n.partitionsMisaligned() {
r.Add(report.Entry{
Message: errors.ErrPartitionsMisaligned.Error(),
Kind: report.EntryError,
})
}
if n.partitionsMixZeroesAndNonexistence() {
r.Add(report.Entry{
Message: errors.ErrZeroesWithShouldNotExist.Error(),
Kind: report.EntryError,
})
}
if n.partitionsUnitsMismatch() {
r.Add(report.Entry{
Message: errors.ErrPartitionsUnitsMismatch.Error(),
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 {
if p.Number != 0 {
// a number of 0 means next available number, multiple devices can specify this
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. Only used by partitionsOverlap. Requires non-nil Start and Size.
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 == nil || p.Size == nil || *p.Start == 0 {
continue
}
for _, o := range n.Partitions {
if o.Start == nil || o.Size == nil || 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 != nil && ((*p.Start & (2048 - 1)) != 0) {
return true
}
}
return false
}
func (n Disk) partitionsMixZeroesAndNonexistence() bool {
hasZero := false
hasShouldNotExist := false
for _, p := range n.Partitions {
hasShouldNotExist = hasShouldNotExist || (p.ShouldExist != nil && !*p.ShouldExist)
hasZero = hasZero || (p.Number == 0)
}
return hasZero && hasShouldNotExist
}
func (n Disk) partitionsUnitsMismatch() bool {
partsInMb := false
partsNotInMb := false
for _, p := range n.Partitions {
if p.Size != nil || p.Start != nil {
partsNotInMb = true
}
if p.SizeMiB != nil || p.StartMiB != nil {
partsInMb = true
}
}
return partsInMb && partsNotInMb
}

View File

@@ -15,15 +15,18 @@
package types
import (
"errors"
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
var (
ErrCompressionInvalid = errors.New("invalid compression method")
)
func (f File) Validate() report.Report {
if f.Overwrite != nil && *f.Overwrite && f.Append {
return report.ReportFromError(errors.ErrAppendAndOverwrite, report.EntryError)
}
return report.Report{}
}
func (f File) ValidateMode() report.Report {
r := report.Report{}
@@ -33,6 +36,12 @@ func (f File) ValidateMode() report.Report {
Kind: report.EntryError,
})
}
if f.Mode == nil {
r.Add(report.Entry{
Message: errors.ErrPermissionsUnset.Error(),
Kind: report.EntryWarning,
})
}
return r
}
@@ -42,7 +51,7 @@ func (fc FileContents) ValidateCompression() report.Report {
case "", "gzip":
default:
r.Add(report.Entry{
Message: ErrCompressionInvalid.Error(),
Message: errors.ErrCompressionInvalid.Error(),
Kind: report.EntryError,
})
}

View File

@@ -15,56 +15,42 @@
package types
import (
"errors"
"fmt"
"github.com/coreos/ignition/config/shared/errors"
"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(),
Message: errors.ErrFilesystemNoMountPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount != nil {
if f.Path != nil {
r.Add(report.Entry{
Message: ErrFilesystemMountAndPath.Error(),
Message: errors.ErrFilesystemMountAndPath.Error(),
Kind: report.EntryError,
})
}
if f.Mount.Create != nil {
if f.Mount.WipeFilesystem {
r.Add(report.Entry{
Message: ErrUsedCreateAndWipeFilesystem.Error(),
Message: errors.ErrUsedCreateAndWipeFilesystem.Error(),
Kind: report.EntryError,
})
}
if len(f.Mount.Options) > 0 {
r.Add(report.Entry{
Message: ErrUsedCreateAndMountOpts.Error(),
Message: errors.ErrUsedCreateAndMountOpts.Error(),
Kind: report.EntryError,
})
}
r.Add(report.Entry{
Message: ErrWarningCreateDeprecated.Error(),
Message: errors.ErrWarningCreateDeprecated.Error(),
Kind: report.EntryWarning,
})
}
@@ -89,7 +75,7 @@ func (m Mount) Validate() report.Report {
case "ext4", "btrfs", "xfs", "swap", "vfat":
default:
r.Add(report.Entry{
Message: ErrFilesystemInvalidFormat.Error(),
Message: errors.ErrFilesystemInvalidFormat.Error(),
Kind: report.EntryError,
})
}
@@ -117,7 +103,7 @@ func (m Mount) ValidateLabel() report.Report {
if len(*m.Label) > 16 {
// source: man mkfs.ext4
r.Add(report.Entry{
Message: ErrExt4LabelTooLong.Error(),
Message: errors.ErrExt4LabelTooLong.Error(),
Kind: report.EntryError,
})
}
@@ -125,7 +111,7 @@ func (m Mount) ValidateLabel() report.Report {
if len(*m.Label) > 256 {
// source: man mkfs.btrfs
r.Add(report.Entry{
Message: ErrBtrfsLabelTooLong.Error(),
Message: errors.ErrBtrfsLabelTooLong.Error(),
Kind: report.EntryError,
})
}
@@ -133,7 +119,7 @@ func (m Mount) ValidateLabel() report.Report {
if len(*m.Label) > 12 {
// source: man mkfs.xfs
r.Add(report.Entry{
Message: ErrXfsLabelTooLong.Error(),
Message: errors.ErrXfsLabelTooLong.Error(),
Kind: report.EntryError,
})
}
@@ -143,7 +129,7 @@ func (m Mount) ValidateLabel() report.Report {
// 15 characters, so let's enforce that.
if len(*m.Label) > 15 {
r.Add(report.Entry{
Message: ErrSwapLabelTooLong.Error(),
Message: errors.ErrSwapLabelTooLong.Error(),
Kind: report.EntryError,
})
}
@@ -151,7 +137,7 @@ func (m Mount) ValidateLabel() report.Report {
if len(*m.Label) > 11 {
// source: man mkfs.fat
r.Add(report.Entry{
Message: ErrVfatLabelTooLong.Error(),
Message: errors.ErrVfatLabelTooLong.Error(),
Kind: report.EntryError,
})
}

View File

@@ -0,0 +1,52 @@
// 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 (
"github.com/coreos/go-semver/semver"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
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(errors.ErrInvalidVersion, report.EntryError)
}
if MaxVersion.Major > tv.Major {
return report.ReportFromError(errors.ErrOldVersion, report.EntryError)
}
if MaxVersion.LessThan(*tv) {
return report.ReportFromError(errors.ErrNewVersion, report.EntryError)
}
return report.Report{}
}

View File

@@ -0,0 +1,26 @@
// 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 (
"github.com/coreos/ignition/config/shared/errors"
)
func validateMode(m *int) error {
if m != nil && (*m < 0 || *m > 07777) {
return errors.ErrFileIllegalMode
}
return nil
}

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 (
"path/filepath"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
func (n Node) ValidateFilesystem() report.Report {
r := report.Report{}
if n.Filesystem == "" {
r.Add(report.Entry{
Message: errors.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: errors.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: errors.ErrBothIDAndNameSet.Error(),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -0,0 +1,112 @@
// 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"
"regexp"
"strings"
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
const (
guidRegexStr = "^(|[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$"
)
func (p Partition) Validate() report.Report {
r := report.Report{}
if (p.Start != nil || p.Size != nil) && (p.StartMiB != nil || p.SizeMiB != nil) {
r.Add(report.Entry{
Message: errors.ErrPartitionsUnitsMismatch.Error(),
Kind: report.EntryError,
})
}
if p.ShouldExist != nil && !*p.ShouldExist &&
(p.Label != nil || p.TypeGUID != "" || p.GUID != "" || p.Start != nil || p.Size != nil) {
r.Add(report.Entry{
Message: errors.ErrShouldNotExistWithOthers.Error(),
Kind: report.EntryError,
})
}
return r
}
func (p Partition) ValidateSize() report.Report {
if p.Size != nil {
return report.ReportFromError(errors.ErrSizeDeprecated, report.EntryDeprecated)
}
return report.Report{}
}
func (p Partition) ValidateStart() report.Report {
if p.Start != nil {
return report.ReportFromError(errors.ErrStartDeprecated, report.EntryDeprecated)
}
return report.Report{}
}
func (p Partition) ValidateLabel() report.Report {
r := report.Report{}
if p.Label == nil {
return r
}
// 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: errors.ErrLabelTooLong.Error(),
Kind: report.EntryError,
})
}
// sgdisk uses colons for delimitting compound arguments and does not allow escaping them.
if strings.Contains(*p.Label, ":") {
r.Add(report.Entry{
Message: errors.ErrLabelContainsColon.Error(),
Kind: report.EntryWarning,
})
}
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: errors.ErrDoesntMatchGUIDRegex.Error(),
Kind: report.EntryError,
})
}
return r
}

View File

@@ -0,0 +1,67 @@
// 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 (
"github.com/coreos/ignition/config/shared/errors"
"github.com/coreos/ignition/config/validate/report"
)
func (p PasswdUser) Validate() report.Report {
r := report.Report{}
if p.Create != nil {
r.Add(report.Entry{
Message: errors.ErrPasswdCreateDeprecated.Error(),
Kind: report.EntryWarning,
})
addErr := func(err error) {
r.Add(report.Entry{
Message: err.Error(),
Kind: report.EntryError,
})
}
if p.Gecos != "" {
addErr(errors.ErrPasswdCreateAndGecos)
}
if len(p.Groups) > 0 {
addErr(errors.ErrPasswdCreateAndGroups)
}
if p.HomeDir != "" {
addErr(errors.ErrPasswdCreateAndHomeDir)
}
if p.NoCreateHome {
addErr(errors.ErrPasswdCreateAndNoCreateHome)
}
if p.NoLogInit {
addErr(errors.ErrPasswdCreateAndNoLogInit)
}
if p.NoUserGroup {
addErr(errors.ErrPasswdCreateAndNoUserGroup)
}
if p.PrimaryGroup != "" {
addErr(errors.ErrPasswdCreateAndPrimaryGroup)
}
if p.Shell != "" {
addErr(errors.ErrPasswdCreateAndShell)
}
if p.System {
addErr(errors.ErrPasswdCreateAndSystem)
}
if p.UID != nil {
addErr(errors.ErrPasswdCreateAndUID)
}
}
return r
}

View File

@@ -0,0 +1,28 @@
// 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 (
"path"
"github.com/coreos/ignition/config/shared/errors"
)
func validatePath(p string) error {
if !path.IsAbs(p) {
return errors.ErrPathRelative
}
return nil
}

View File

@@ -0,0 +1,57 @@
// 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 (
"github.com/coreos/ignition/config/shared/errors"
"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: errors.ErrSparesUnsupportedForLevel.Error(),
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: errors.ErrUnrecognizedRaidLevel.Error(),
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: errors.ErrPathRelative.Error(),
Kind: report.EntryError,
})
}
}
return r
}

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