diff --git a/config/shared/errors/errors.go b/config/shared/errors/errors.go index 9fa1645a7..9058e0b80 100644 --- a/config/shared/errors/errors.go +++ b/config/shared/errors/errors.go @@ -83,6 +83,7 @@ var ( ErrDuplicateLabels = errors.New("cannot use the same partition label twice") ErrInvalidProxy = errors.New("proxies must be http(s)") ErrInsecureProxy = errors.New("insecure plaintext HTTP proxy specified for HTTPS resources") + ErrPathConflictsSystemd = errors.New("path conflicts with systemd unit or dropin") // Systemd section errors ErrInvalidSystemdExt = errors.New("invalid systemd unit extension") diff --git a/config/v3_0/types/config.go b/config/v3_0/types/config.go index a98062a1b..1ac295948 100644 --- a/config/v3_0/types/config.go +++ b/config/v3_0/types/config.go @@ -15,7 +15,12 @@ package types import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/go-semver/semver" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" ) var ( @@ -24,3 +29,36 @@ var ( Minor: 0, } ) + +func (cfg Config) Validate(c path.ContextPath) (r report.Report) { + systemdPath := "/etc/systemd/system/" + unitPaths := map[string]struct{}{} + for _, unit := range cfg.Systemd.Units { + if !util.NilOrEmpty(unit.Contents) { + pathString := systemdPath + unit.Name + unitPaths[pathString] = struct{}{} + } + for _, dropin := range unit.Dropins { + if !util.NilOrEmpty(dropin.Contents) { + pathString := systemdPath + unit.Name + ".d/" + dropin.Name + unitPaths[pathString] = struct{}{} + } + } + } + for i, f := range cfg.Storage.Files { + if _, exists := unitPaths[f.Path]; exists { + r.AddOnError(c.Append("storage", "files", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, d := range cfg.Storage.Directories { + if _, exists := unitPaths[d.Path]; exists { + r.AddOnError(c.Append("storage", "directories", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, l := range cfg.Storage.Links { + if _, exists := unitPaths[l.Path]; exists { + r.AddOnError(c.Append("storage", "links", i, "path"), errors.ErrPathConflictsSystemd) + } + } + return +} diff --git a/config/v3_0/types/config_test.go b/config/v3_0/types/config_test.go new file mode 100644 index 000000000..da8cfa26c --- /dev/null +++ b/config/v3_0/types/config_test.go @@ -0,0 +1,260 @@ +// Copyright 2020 Red Hat, 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 ( + "reflect" + "testing" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func TestConfigValidation(t *testing.T) { + tests := []struct { + in Config + out error + at path.ContextPath + }{ + // test 0: file conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 1: file conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 2: directory conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 3: directory conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 4: link conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: "/qux.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 5: link conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + LinkEmbedded1: LinkEmbedded1{Target: "/qux.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 6: non-conflicting scenarios + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/bar.service.d/baz.conf"}, + }, + { + Node: Node{Path: "/etc/systemd/system/bar.service"}, + }, + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/qux.conf"}, + }, + }, + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/qux.service"}, + LinkEmbedded1: LinkEmbedded1{Target: "/qux.conf"}, + }, + { + Node: Node{Path: "/etc/systemd/system/quux.service.d/foo.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: "/foo.conf"}, + }, + }, + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/quux.service.d"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + { + Name: "bar.service", + Dropins: []Dropin{ + { + Name: "baz.conf", + }, + }, + Enabled: util.BoolToPtr(true), + }, + { + Name: "qux.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + }, + }, + }, + { + Name: "quux.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + }, + } + for i, test := range tests { + r := test.in.Validate(path.New("json")) + expected := report.Report{} + expected.AddOnError(test.at, test.out) + if !reflect.DeepEqual(expected, r) { + t.Errorf("#%d: bad error: expected : %v, got %v", i, expected, r) + } + } +} diff --git a/config/v3_1/types/config.go b/config/v3_1/types/config.go index 23ba8dd18..3cebde7fb 100644 --- a/config/v3_1/types/config.go +++ b/config/v3_1/types/config.go @@ -15,7 +15,12 @@ package types import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/go-semver/semver" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" ) var ( @@ -24,3 +29,36 @@ var ( Minor: 1, } ) + +func (cfg Config) Validate(c path.ContextPath) (r report.Report) { + systemdPath := "/etc/systemd/system/" + unitPaths := map[string]struct{}{} + for _, unit := range cfg.Systemd.Units { + if !util.NilOrEmpty(unit.Contents) { + pathString := systemdPath + unit.Name + unitPaths[pathString] = struct{}{} + } + for _, dropin := range unit.Dropins { + if !util.NilOrEmpty(dropin.Contents) { + pathString := systemdPath + unit.Name + ".d/" + dropin.Name + unitPaths[pathString] = struct{}{} + } + } + } + for i, f := range cfg.Storage.Files { + if _, exists := unitPaths[f.Path]; exists { + r.AddOnError(c.Append("storage", "files", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, d := range cfg.Storage.Directories { + if _, exists := unitPaths[d.Path]; exists { + r.AddOnError(c.Append("storage", "directories", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, l := range cfg.Storage.Links { + if _, exists := unitPaths[l.Path]; exists { + r.AddOnError(c.Append("storage", "links", i, "path"), errors.ErrPathConflictsSystemd) + } + } + return +} diff --git a/config/v3_1/types/config_test.go b/config/v3_1/types/config_test.go new file mode 100644 index 000000000..da8cfa26c --- /dev/null +++ b/config/v3_1/types/config_test.go @@ -0,0 +1,260 @@ +// Copyright 2020 Red Hat, 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 ( + "reflect" + "testing" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func TestConfigValidation(t *testing.T) { + tests := []struct { + in Config + out error + at path.ContextPath + }{ + // test 0: file conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 1: file conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 2: directory conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 3: directory conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 4: link conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: "/qux.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 5: link conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + LinkEmbedded1: LinkEmbedded1{Target: "/qux.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 6: non-conflicting scenarios + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/bar.service.d/baz.conf"}, + }, + { + Node: Node{Path: "/etc/systemd/system/bar.service"}, + }, + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/qux.conf"}, + }, + }, + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/qux.service"}, + LinkEmbedded1: LinkEmbedded1{Target: "/qux.conf"}, + }, + { + Node: Node{Path: "/etc/systemd/system/quux.service.d/foo.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: "/foo.conf"}, + }, + }, + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/quux.service.d"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + { + Name: "bar.service", + Dropins: []Dropin{ + { + Name: "baz.conf", + }, + }, + Enabled: util.BoolToPtr(true), + }, + { + Name: "qux.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + }, + }, + }, + { + Name: "quux.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + }, + } + for i, test := range tests { + r := test.in.Validate(path.New("json")) + expected := report.Report{} + expected.AddOnError(test.at, test.out) + if !reflect.DeepEqual(expected, r) { + t.Errorf("#%d: bad error: expected : %v, got %v", i, expected, r) + } + } +} diff --git a/config/v3_2/types/config.go b/config/v3_2/types/config.go index 4b18d5378..0e2fc3703 100644 --- a/config/v3_2/types/config.go +++ b/config/v3_2/types/config.go @@ -15,7 +15,12 @@ package types import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/go-semver/semver" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" ) var ( @@ -24,3 +29,36 @@ var ( Minor: 2, } ) + +func (cfg Config) Validate(c path.ContextPath) (r report.Report) { + systemdPath := "/etc/systemd/system/" + unitPaths := map[string]struct{}{} + for _, unit := range cfg.Systemd.Units { + if !util.NilOrEmpty(unit.Contents) { + pathString := systemdPath + unit.Name + unitPaths[pathString] = struct{}{} + } + for _, dropin := range unit.Dropins { + if !util.NilOrEmpty(dropin.Contents) { + pathString := systemdPath + unit.Name + ".d/" + dropin.Name + unitPaths[pathString] = struct{}{} + } + } + } + for i, f := range cfg.Storage.Files { + if _, exists := unitPaths[f.Path]; exists { + r.AddOnError(c.Append("storage", "files", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, d := range cfg.Storage.Directories { + if _, exists := unitPaths[d.Path]; exists { + r.AddOnError(c.Append("storage", "directories", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, l := range cfg.Storage.Links { + if _, exists := unitPaths[l.Path]; exists { + r.AddOnError(c.Append("storage", "links", i, "path"), errors.ErrPathConflictsSystemd) + } + } + return +} diff --git a/config/v3_2/types/config_test.go b/config/v3_2/types/config_test.go new file mode 100644 index 000000000..da8cfa26c --- /dev/null +++ b/config/v3_2/types/config_test.go @@ -0,0 +1,260 @@ +// Copyright 2020 Red Hat, 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 ( + "reflect" + "testing" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func TestConfigValidation(t *testing.T) { + tests := []struct { + in Config + out error + at path.ContextPath + }{ + // test 0: file conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 1: file conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 2: directory conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 3: directory conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 4: link conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: "/qux.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 5: link conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + LinkEmbedded1: LinkEmbedded1{Target: "/qux.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 6: non-conflicting scenarios + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/bar.service.d/baz.conf"}, + }, + { + Node: Node{Path: "/etc/systemd/system/bar.service"}, + }, + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/qux.conf"}, + }, + }, + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/qux.service"}, + LinkEmbedded1: LinkEmbedded1{Target: "/qux.conf"}, + }, + { + Node: Node{Path: "/etc/systemd/system/quux.service.d/foo.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: "/foo.conf"}, + }, + }, + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/quux.service.d"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + { + Name: "bar.service", + Dropins: []Dropin{ + { + Name: "baz.conf", + }, + }, + Enabled: util.BoolToPtr(true), + }, + { + Name: "qux.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + }, + }, + }, + { + Name: "quux.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + }, + } + for i, test := range tests { + r := test.in.Validate(path.New("json")) + expected := report.Report{} + expected.AddOnError(test.at, test.out) + if !reflect.DeepEqual(expected, r) { + t.Errorf("#%d: bad error: expected : %v, got %v", i, expected, r) + } + } +} diff --git a/config/v3_3/types/config.go b/config/v3_3/types/config.go index fdccc1949..9158e7f01 100644 --- a/config/v3_3/types/config.go +++ b/config/v3_3/types/config.go @@ -15,7 +15,12 @@ package types import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/go-semver/semver" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" ) var ( @@ -24,3 +29,36 @@ var ( Minor: 3, } ) + +func (cfg Config) Validate(c path.ContextPath) (r report.Report) { + systemdPath := "/etc/systemd/system/" + unitPaths := map[string]struct{}{} + for _, unit := range cfg.Systemd.Units { + if !util.NilOrEmpty(unit.Contents) { + pathString := systemdPath + unit.Name + unitPaths[pathString] = struct{}{} + } + for _, dropin := range unit.Dropins { + if !util.NilOrEmpty(dropin.Contents) { + pathString := systemdPath + unit.Name + ".d/" + dropin.Name + unitPaths[pathString] = struct{}{} + } + } + } + for i, f := range cfg.Storage.Files { + if _, exists := unitPaths[f.Path]; exists { + r.AddOnError(c.Append("storage", "files", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, d := range cfg.Storage.Directories { + if _, exists := unitPaths[d.Path]; exists { + r.AddOnError(c.Append("storage", "directories", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, l := range cfg.Storage.Links { + if _, exists := unitPaths[l.Path]; exists { + r.AddOnError(c.Append("storage", "links", i, "path"), errors.ErrPathConflictsSystemd) + } + } + return +} diff --git a/config/v3_3/types/config_test.go b/config/v3_3/types/config_test.go new file mode 100644 index 000000000..3d82627b2 --- /dev/null +++ b/config/v3_3/types/config_test.go @@ -0,0 +1,260 @@ +// Copyright 2020 Red Hat, 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 ( + "reflect" + "testing" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func TestConfigValidation(t *testing.T) { + tests := []struct { + in Config + out error + at path.ContextPath + }{ + // test 0: file conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 1: file conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 2: directory conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 3: directory conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 4: link conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: util.StrToPtr("/qux.conf")}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 5: link conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + LinkEmbedded1: LinkEmbedded1{Target: util.StrToPtr("/qux.conf")}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 6: non-conflicting scenarios + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/bar.service.d/baz.conf"}, + }, + { + Node: Node{Path: "/etc/systemd/system/bar.service"}, + }, + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/qux.conf"}, + }, + }, + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/qux.service"}, + LinkEmbedded1: LinkEmbedded1{Target: util.StrToPtr("/qux.conf")}, + }, + { + Node: Node{Path: "/etc/systemd/system/quux.service.d/foo.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: util.StrToPtr("/foo.conf")}, + }, + }, + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/quux.service.d"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + { + Name: "bar.service", + Dropins: []Dropin{ + { + Name: "baz.conf", + }, + }, + Enabled: util.BoolToPtr(true), + }, + { + Name: "qux.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + }, + }, + }, + { + Name: "quux.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + }, + } + for i, test := range tests { + r := test.in.Validate(path.New("json")) + expected := report.Report{} + expected.AddOnError(test.at, test.out) + if !reflect.DeepEqual(expected, r) { + t.Errorf("#%d: bad error: expected : %v, got %v", i, expected, r) + } + } +} diff --git a/config/v3_4_experimental/types/config.go b/config/v3_4_experimental/types/config.go index e77181931..119efbdeb 100644 --- a/config/v3_4_experimental/types/config.go +++ b/config/v3_4_experimental/types/config.go @@ -15,7 +15,12 @@ package types import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/go-semver/semver" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" ) var ( @@ -25,3 +30,36 @@ var ( PreRelease: "experimental", } ) + +func (cfg Config) Validate(c path.ContextPath) (r report.Report) { + systemdPath := "/etc/systemd/system/" + unitPaths := map[string]struct{}{} + for _, unit := range cfg.Systemd.Units { + if !util.NilOrEmpty(unit.Contents) { + pathString := systemdPath + unit.Name + unitPaths[pathString] = struct{}{} + } + for _, dropin := range unit.Dropins { + if !util.NilOrEmpty(dropin.Contents) { + pathString := systemdPath + unit.Name + ".d/" + dropin.Name + unitPaths[pathString] = struct{}{} + } + } + } + for i, f := range cfg.Storage.Files { + if _, exists := unitPaths[f.Path]; exists { + r.AddOnError(c.Append("storage", "files", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, d := range cfg.Storage.Directories { + if _, exists := unitPaths[d.Path]; exists { + r.AddOnError(c.Append("storage", "directories", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, l := range cfg.Storage.Links { + if _, exists := unitPaths[l.Path]; exists { + r.AddOnError(c.Append("storage", "links", i, "path"), errors.ErrPathConflictsSystemd) + } + } + return +} diff --git a/config/v3_4_experimental/types/config_test.go b/config/v3_4_experimental/types/config_test.go new file mode 100644 index 000000000..3d82627b2 --- /dev/null +++ b/config/v3_4_experimental/types/config_test.go @@ -0,0 +1,260 @@ +// Copyright 2020 Red Hat, 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 ( + "reflect" + "testing" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func TestConfigValidation(t *testing.T) { + tests := []struct { + in Config + out error + at path.ContextPath + }{ + // test 0: file conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 1: file conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "files", 0, "path"), + }, + // test 2: directory conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 3: directory conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "directories", 0, "path"), + }, + // test 4: link conflicts with systemd dropin file, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/bar.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: util.StrToPtr("/qux.conf")}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Bar"), + }, + }, + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 5: link conflicts with systemd unit, error + { + in: Config{ + Storage: Storage{ + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/foo.service"}, + LinkEmbedded1: LinkEmbedded1{Target: util.StrToPtr("/qux.conf")}, + }, + }, + }, + Systemd: Systemd{ + []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + out: errors.ErrPathConflictsSystemd, + at: path.New("json", "storage", "links", 0, "path"), + }, + // test 6: non-conflicting scenarios + { + in: Config{ + Storage: Storage{ + Files: []File{ + { + Node: Node{Path: "/etc/systemd/system/bar.service.d/baz.conf"}, + }, + { + Node: Node{Path: "/etc/systemd/system/bar.service"}, + }, + { + Node: Node{Path: "/etc/systemd/system/foo.service.d/qux.conf"}, + }, + }, + Links: []Link{ + { + Node: Node{Path: "/etc/systemd/system/qux.service"}, + LinkEmbedded1: LinkEmbedded1{Target: util.StrToPtr("/qux.conf")}, + }, + { + Node: Node{Path: "/etc/systemd/system/quux.service.d/foo.conf"}, + LinkEmbedded1: LinkEmbedded1{Target: util.StrToPtr("/foo.conf")}, + }, + }, + Directories: []Directory{ + { + Node: Node{Path: "/etc/systemd/system/quux.service.d"}, + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "foo.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + { + Name: "bar.service", + Dropins: []Dropin{ + { + Name: "baz.conf", + }, + }, + Enabled: util.BoolToPtr(true), + }, + { + Name: "qux.service", + Dropins: []Dropin{ + { + Name: "bar.conf", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + }, + }, + }, + { + Name: "quux.service", + Contents: util.StrToPtr("[Foo]\nQux=Baz"), + Enabled: util.BoolToPtr(true), + }, + }, + }, + }, + }, + } + for i, test := range tests { + r := test.in.Validate(path.New("json")) + expected := report.Report{} + expected.AddOnError(test.at, test.out) + if !reflect.DeepEqual(expected, r) { + t.Errorf("#%d: bad error: expected : %v, got %v", i, expected, r) + } + } +} diff --git a/docs/release-notes.md b/docs/release-notes.md index 17b2a41fb..21a439592 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -21,6 +21,7 @@ nav_order: 9 - Warn if `user`/`group` specified for hard link - Install ignition-apply in `/usr/libexec` - Convert `NEWS` to Markdown and move to docs site +- Fail if files/links/dirs conflict with systemd units or dropins ### Bug fixes