From bb6a09a8b51043eb5a422e06c62e87b913247afe Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Sat, 11 Mar 2023 13:06:57 -0500 Subject: [PATCH 1/3] openshift/v4_13_exp: rename to v4_14_exp --- config/openshift/{v4_13_exp => v4_14_exp}/result/schema.go | 0 config/openshift/{v4_13_exp => v4_14_exp}/schema.go | 0 config/openshift/{v4_13_exp => v4_14_exp}/translate.go | 0 config/openshift/{v4_13_exp => v4_14_exp}/translate_test.go | 0 config/openshift/{v4_13_exp => v4_14_exp}/validate.go | 0 config/openshift/{v4_13_exp => v4_14_exp}/validate_test.go | 0 ...onfig-openshift-v4_13-exp.md => config-openshift-v4_14-exp.md} | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename config/openshift/{v4_13_exp => v4_14_exp}/result/schema.go (100%) rename config/openshift/{v4_13_exp => v4_14_exp}/schema.go (100%) rename config/openshift/{v4_13_exp => v4_14_exp}/translate.go (100%) rename config/openshift/{v4_13_exp => v4_14_exp}/translate_test.go (100%) rename config/openshift/{v4_13_exp => v4_14_exp}/validate.go (100%) rename config/openshift/{v4_13_exp => v4_14_exp}/validate_test.go (100%) rename docs/{config-openshift-v4_13-exp.md => config-openshift-v4_14-exp.md} (100%) diff --git a/config/openshift/v4_13_exp/result/schema.go b/config/openshift/v4_14_exp/result/schema.go similarity index 100% rename from config/openshift/v4_13_exp/result/schema.go rename to config/openshift/v4_14_exp/result/schema.go diff --git a/config/openshift/v4_13_exp/schema.go b/config/openshift/v4_14_exp/schema.go similarity index 100% rename from config/openshift/v4_13_exp/schema.go rename to config/openshift/v4_14_exp/schema.go diff --git a/config/openshift/v4_13_exp/translate.go b/config/openshift/v4_14_exp/translate.go similarity index 100% rename from config/openshift/v4_13_exp/translate.go rename to config/openshift/v4_14_exp/translate.go diff --git a/config/openshift/v4_13_exp/translate_test.go b/config/openshift/v4_14_exp/translate_test.go similarity index 100% rename from config/openshift/v4_13_exp/translate_test.go rename to config/openshift/v4_14_exp/translate_test.go diff --git a/config/openshift/v4_13_exp/validate.go b/config/openshift/v4_14_exp/validate.go similarity index 100% rename from config/openshift/v4_13_exp/validate.go rename to config/openshift/v4_14_exp/validate.go diff --git a/config/openshift/v4_13_exp/validate_test.go b/config/openshift/v4_14_exp/validate_test.go similarity index 100% rename from config/openshift/v4_13_exp/validate_test.go rename to config/openshift/v4_14_exp/validate_test.go diff --git a/docs/config-openshift-v4_13-exp.md b/docs/config-openshift-v4_14-exp.md similarity index 100% rename from docs/config-openshift-v4_13-exp.md rename to docs/config-openshift-v4_14-exp.md From c20feb129a6e39397fec4af92d0bc48ae1991f3f Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Sat, 11 Mar 2023 13:23:39 -0500 Subject: [PATCH 2/3] openshift/v4_13: copy from v4_14_exp --- config/openshift/v4_13/result/schema.go | 48 ++ config/openshift/v4_13/schema.go | 39 ++ config/openshift/v4_13/translate.go | 321 +++++++++++++ config/openshift/v4_13/translate_test.go | 577 +++++++++++++++++++++++ config/openshift/v4_13/validate.go | 43 ++ config/openshift/v4_13/validate_test.go | 249 ++++++++++ docs/config-openshift-v4_13.md | 175 +++++++ 7 files changed, 1452 insertions(+) create mode 100644 config/openshift/v4_13/result/schema.go create mode 100644 config/openshift/v4_13/schema.go create mode 100644 config/openshift/v4_13/translate.go create mode 100644 config/openshift/v4_13/translate_test.go create mode 100644 config/openshift/v4_13/validate.go create mode 100644 config/openshift/v4_13/validate_test.go create mode 100644 docs/config-openshift-v4_13.md diff --git a/config/openshift/v4_13/result/schema.go b/config/openshift/v4_13/result/schema.go new file mode 100644 index 00000000..ad5abd8e --- /dev/null +++ b/config/openshift/v4_13/result/schema.go @@ -0,0 +1,48 @@ +// Copyright 2021 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 result + +import ( + "github.com/coreos/ignition/v2/config/v3_4/types" +) + +const ( + MC_API_VERSION = "machineconfiguration.openshift.io/v1" + MC_KIND = "MachineConfig" +) + +// We round-trip through JSON because Ignition uses `json` struct tags, +// so all struct tags need to be `json` even though we're ultimately +// writing YAML. + +type MachineConfig struct { + ApiVersion string `json:"apiVersion"` + Kind string `json:"kind"` + Metadata Metadata `json:"metadata"` + Spec Spec `json:"spec"` +} + +type Metadata struct { + Name string `json:"name"` + Labels map[string]string `json:"labels,omitempty"` +} + +type Spec struct { + Config types.Config `json:"config"` + KernelArguments []string `json:"kernelArguments,omitempty"` + Extensions []string `json:"extensions,omitempty"` + FIPS *bool `json:"fips,omitempty"` + KernelType *string `json:"kernelType,omitempty"` +} diff --git a/config/openshift/v4_13/schema.go b/config/openshift/v4_13/schema.go new file mode 100644 index 00000000..ab204d39 --- /dev/null +++ b/config/openshift/v4_13/schema.go @@ -0,0 +1,39 @@ +// 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 v4_13_exp + +import ( + fcos "github.com/coreos/butane/config/fcos/v1_5_exp" +) + +const ROLE_LABEL_KEY = "machineconfiguration.openshift.io/role" + +type Config struct { + fcos.Config `yaml:",inline"` + Metadata Metadata `yaml:"metadata"` + OpenShift OpenShift `yaml:"openshift"` +} + +type Metadata struct { + Name string `yaml:"name"` + Labels map[string]string `yaml:"labels,omitempty"` +} + +type OpenShift struct { + KernelArguments []string `yaml:"kernel_arguments"` + Extensions []string `yaml:"extensions"` + FIPS *bool `yaml:"fips"` + KernelType *string `yaml:"kernel_type"` +} diff --git a/config/openshift/v4_13/translate.go b/config/openshift/v4_13/translate.go new file mode 100644 index 00000000..4f951b49 --- /dev/null +++ b/config/openshift/v4_13/translate.go @@ -0,0 +1,321 @@ +// 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 v4_13_exp + +import ( + "net/url" + "reflect" + "strings" + + "github.com/coreos/butane/config/common" + "github.com/coreos/butane/config/openshift/v4_13_exp/result" + cutil "github.com/coreos/butane/config/util" + "github.com/coreos/butane/translate" + + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/ignition/v2/config/v3_4/types" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +const ( + // FIPS 140-2 doesn't allow the default XTS mode + fipsCipherOption = types.LuksOption("--cipher") + fipsCipherShortOption = types.LuksOption("-c") + fipsCipherArgument = types.LuksOption("aes-cbc-essiv:sha256") +) + +// ToMachineConfig4_13Unvalidated translates the config to a MachineConfig. It also +// returns the set of translations it did so paths in the resultant config +// can be tracked back to their source in the source config. No config +// validation is performed on input or output. +func (c Config) ToMachineConfig4_13Unvalidated(options common.TranslateOptions) (result.MachineConfig, translate.TranslationSet, report.Report) { + cfg, ts, r := c.Config.ToIgn3_4Unvalidated(options) + if r.IsFatal() { + return result.MachineConfig{}, ts, r + } + ts = translateUserGrubCfg(&cfg, &ts) + + // wrap + ts = ts.PrefixPaths(path.New("yaml"), path.New("json", "spec", "config")) + mc := result.MachineConfig{ + ApiVersion: result.MC_API_VERSION, + Kind: result.MC_KIND, + Metadata: result.Metadata{ + Name: c.Metadata.Name, + Labels: make(map[string]string), + }, + Spec: result.Spec{ + Config: cfg, + }, + } + ts.AddTranslation(path.New("yaml", "version"), path.New("json", "apiVersion")) + ts.AddTranslation(path.New("yaml", "version"), path.New("json", "kind")) + ts.AddTranslation(path.New("yaml", "metadata"), path.New("json", "metadata")) + ts.AddTranslation(path.New("yaml", "metadata", "name"), path.New("json", "metadata", "name")) + ts.AddTranslation(path.New("yaml", "metadata", "labels"), path.New("json", "metadata", "labels")) + ts.AddTranslation(path.New("yaml", "version"), path.New("json", "spec")) + ts.AddTranslation(path.New("yaml"), path.New("json", "spec", "config")) + for k, v := range c.Metadata.Labels { + mc.Metadata.Labels[k] = v + ts.AddTranslation(path.New("yaml", "metadata", "labels", k), path.New("json", "metadata", "labels", k)) + } + + // translate OpenShift fields + tr := translate.NewTranslator("yaml", "json", options) + from := &c.OpenShift + to := &mc.Spec + ts2, r2 := translate.Prefixed(tr, "extensions", &from.Extensions, &to.Extensions) + translate.MergeP(tr, ts2, &r2, "fips", &from.FIPS, &to.FIPS) + translate.MergeP2(tr, ts2, &r2, "kernel_arguments", &from.KernelArguments, "kernelArguments", &to.KernelArguments) + translate.MergeP2(tr, ts2, &r2, "kernel_type", &from.KernelType, "kernelType", &to.KernelType) + ts.MergeP2("openshift", "spec", ts2) + r.Merge(r2) + + // apply FIPS options to LUKS volumes + ts.Merge(addLuksFipsOptions(&mc)) + + // finally, check the fully desugared config for RHCOS and MCO support + r.Merge(validateRHCOSSupport(mc, ts)) + r.Merge(validateMCOSupport(mc, ts)) + + return mc, ts, r +} + +// ToMachineConfig4_13 translates the config to a MachineConfig. It returns a +// report of any errors or warnings in the source and resultant config. If +// the report has fatal errors or it encounters other problems translating, +// an error is returned. +func (c Config) ToMachineConfig4_13(options common.TranslateOptions) (result.MachineConfig, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToMachineConfig4_13Unvalidated", options) + return cfg.(result.MachineConfig), r, err +} + +// ToIgn3_4Unvalidated translates the config to an Ignition config. It also +// returns the set of translations it did so paths in the resultant config +// can be tracked back to their source in the source config. No config +// validation is performed on input or output. +func (c Config) ToIgn3_4Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { + mc, ts, r := c.ToMachineConfig4_13Unvalidated(options) + cfg := mc.Spec.Config + + // report warnings if there are any non-empty fields in Spec (other + // than the Ignition config itself) that we're ignoring + mc.Spec.Config = types.Config{} + warnings := translate.PrefixReport(cutil.CheckForElidedFields(mc.Spec), "spec") + // translate from json space into yaml space + r.Merge(cutil.TranslateReportPaths(warnings, ts)) + + ts = ts.Descend(path.New("json", "spec", "config")) + return cfg, ts, r +} + +// ToIgn3_4 translates the config to an Ignition config. It returns a +// report of any errors or warnings in the source and resultant config. If +// the report has fatal errors or it encounters other problems translating, +// an error is returned. +func (c Config) ToIgn3_4(options common.TranslateOptions) (types.Config, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToIgn3_4Unvalidated", options) + return cfg.(types.Config), r, err +} + +// ToConfigBytes translates from a v4.13 Butane config to a v4.13 MachineConfig or a v3.4.0 Ignition config. It returns a report of any errors or +// warnings in the source and resultant config. If the report has fatal errors or it encounters other problems +// translating, an error is returned. +func ToConfigBytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { + if options.Raw { + return cutil.TranslateBytes(input, &Config{}, "ToIgn3_4", options) + } else { + return cutil.TranslateBytesYAML(input, &Config{}, "ToMachineConfig4_13", options) + } +} + +func addLuksFipsOptions(mc *result.MachineConfig) translate.TranslationSet { + ts := translate.NewTranslationSet("yaml", "json") + if !util.IsTrue(mc.Spec.FIPS) { + return ts + } + +OUTER: + for i := range mc.Spec.Config.Storage.Luks { + luks := &mc.Spec.Config.Storage.Luks[i] + // Only add options if the user hasn't already specified + // a cipher option. Do this in-place, since config merging + // doesn't support conditional logic. + for _, option := range luks.Options { + if option == fipsCipherOption || + strings.HasPrefix(string(option), string(fipsCipherOption)+"=") || + option == fipsCipherShortOption { + continue OUTER + } + } + for j := 0; j < 2; j++ { + ts.AddTranslation(path.New("yaml", "openshift", "fips"), path.New("json", "spec", "config", "storage", "luks", i, "options", len(luks.Options)+j)) + } + if len(luks.Options) == 0 { + ts.AddTranslation(path.New("yaml", "openshift", "fips"), path.New("json", "spec", "config", "storage", "luks", i, "options")) + } + luks.Options = append(luks.Options, fipsCipherOption, fipsCipherArgument) + } + return ts +} + +// Error on fields that are rejected by RHCOS. +// +// Some of these fields may have been generated by sugar (e.g. +// boot_device.luks), so we work in JSON (output) space and then translate +// paths back to YAML (input) space. That's also the reason we do these +// checks after translation, rather than during validation. +func validateRHCOSSupport(mc result.MachineConfig, ts translate.TranslationSet) report.Report { + var r report.Report + for i, fs := range mc.Spec.Config.Storage.Filesystems { + if fs.Format != nil && *fs.Format == "btrfs" { + // we don't ship mkfs.btrfs + r.AddOnError(path.New("json", "spec", "config", "storage", "filesystems", i, "format"), common.ErrBtrfsSupport) + } + } + return cutil.TranslateReportPaths(r, ts) +} + +// Error on fields that are rejected outright by the MCO, or that are +// unsupported by the MCO and we want to discourage. +// +// https://github.com/openshift/machine-config-operator/blob/d6dabadeca05/MachineConfigDaemon.md#supported-vs-unsupported-ignition-config-changes +// +// Some of these fields may have been generated by sugar (e.g. storage.trees), +// so we work in JSON (output) space and then translate paths back to YAML +// (input) space. That's also the reason we do these checks after +// translation, rather than during validation. +func validateMCOSupport(mc result.MachineConfig, ts translate.TranslationSet) report.Report { + // Error classes for the purposes of this function: + // + // UNPARSABLE - Cannot be rendered into a config by the MCC. If + // present in MC, MCC will mark the pool degraded. We reject these. + // + // FORBIDDEN - Not supported by the MCD. If present in MC, MCD will + // mark the node degraded. We reject these. + // + // REDUNDANT - Feature is also provided by a MachineConfig-specific + // field with different semantics. To reduce confusion, disable + // this implementation. + // + // IMMUTABLE - Permitted in MC, passed through to Ignition, but not + // supported by the MCD. MCD will mark the node degraded if the + // field changes after the node is provisioned. We reject these + // outright to discourage their use. + // + // TRIPWIRE - A subset of fields in the containing struct are + // supported by the MCD. If the struct contents change after the node + // is provisioned, and the struct contains unsupported fields, MCD + // will mark the node degraded, even if the change only affects + // supported fields. We reject these. + + var r report.Report + for i, fs := range mc.Spec.Config.Storage.Filesystems { + if fs.Format != nil && *fs.Format == "none" { + // UNPARSABLE + r.AddOnError(path.New("json", "spec", "config", "storage", "filesystems", i, "format"), common.ErrFilesystemNoneSupport) + } + } + for i := range mc.Spec.Config.Storage.Directories { + // IMMUTABLE + r.AddOnError(path.New("json", "spec", "config", "storage", "directories", i), common.ErrDirectorySupport) + } + for i, file := range mc.Spec.Config.Storage.Files { + if len(file.Append) > 0 { + // FORBIDDEN + r.AddOnError(path.New("json", "spec", "config", "storage", "files", i, "append"), common.ErrFileAppendSupport) + } + if file.Contents.Source != nil { + fileSource, err := url.Parse(*file.Contents.Source) + // parse errors will be caught by normal config validation + if err == nil && fileSource.Scheme != "data" { + // FORBIDDEN + r.AddOnError(path.New("json", "spec", "config", "storage", "files", i, "contents", "source"), common.ErrFileSchemeSupport) + } + } + if file.Mode != nil && *file.Mode & ^0777 != 0 { + // UNPARSABLE + r.AddOnError(path.New("json", "spec", "config", "storage", "files", i, "mode"), common.ErrFileSpecialModeSupport) + } + } + for i := range mc.Spec.Config.Storage.Links { + // IMMUTABLE + // If you change this to be less restrictive without adding + // link support in the MCO, consider what should happen if + // the user specifies a storage.tree that includes symlinks. + r.AddOnError(path.New("json", "spec", "config", "storage", "links", i), common.ErrLinkSupport) + } + for i := range mc.Spec.Config.Passwd.Groups { + // IMMUTABLE + r.AddOnError(path.New("json", "spec", "config", "passwd", "groups", i), common.ErrGroupSupport) + } + for i, user := range mc.Spec.Config.Passwd.Users { + if user.Name == "core" { + // PasswordHash and SSHAuthorizedKeys are managed; other fields are not + v := reflect.ValueOf(user) + t := v.Type() + for j := 0; j < v.NumField(); j++ { + fv := v.Field(j) + ft := t.Field(j) + switch ft.Name { + case "Name", "PasswordHash", "SSHAuthorizedKeys": + continue + default: + if fv.IsValid() && !fv.IsZero() { + tag := strings.Split(ft.Tag.Get("json"), ",")[0] + // TRIPWIRE + r.AddOnError(path.New("json", "spec", "config", "passwd", "users", i, tag), common.ErrUserFieldSupport) + } + } + } + } else { + // TRIPWIRE + r.AddOnError(path.New("json", "spec", "config", "passwd", "users", i), common.ErrUserNameSupport) + } + } + for i := range mc.Spec.Config.KernelArguments.ShouldExist { + // UNPARSABLE, REDUNDANT + r.AddOnError(path.New("json", "spec", "config", "kernelArguments", "shouldExist", i), common.ErrKernelArgumentSupport) + } + for i := range mc.Spec.Config.KernelArguments.ShouldNotExist { + // UNPARSABLE, REDUNDANT + r.AddOnError(path.New("json", "spec", "config", "kernelArguments", "shouldNotExist", i), common.ErrKernelArgumentSupport) + } + return cutil.TranslateReportPaths(r, ts) +} + +// fcos config generates a user.cfg file using append; however, OpenShift config +// does not support append (since MCO does not support it). Let change the file to use contents +func translateUserGrubCfg(config *types.Config, ts *translate.TranslationSet) translate.TranslationSet { + newMappings := translate.NewTranslationSet("json", "json") + for i, file := range config.Storage.Files { + if file.Path == "/boot/grub2/user.cfg" { + if len(file.Append) != 1 { + // The number of append objects was different from expected, this file + // was created by the user and not via butane GRUB sugar + return *ts + } + fromPath := path.New("json", "storage", "files", i, "append", 0) + translatedPath := path.New("json", "storage", "files", i, "contents") + config.Storage.Files[i].FileEmbedded1.Contents = file.Append[0] + config.Storage.Files[i].FileEmbedded1.Append = nil + newMappings.AddFromCommonObject(fromPath, translatedPath, config.Storage.Files[i].FileEmbedded1.Contents) + + return ts.Map(newMappings) + } + } + return *ts +} diff --git a/config/openshift/v4_13/translate_test.go b/config/openshift/v4_13/translate_test.go new file mode 100644 index 00000000..77ea7da4 --- /dev/null +++ b/config/openshift/v4_13/translate_test.go @@ -0,0 +1,577 @@ +// Copyright 2021 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 v4_13_exp + +import ( + "fmt" + "testing" + + baseutil "github.com/coreos/butane/base/util" + base "github.com/coreos/butane/base/v0_5_exp" + "github.com/coreos/butane/config/common" + fcos "github.com/coreos/butane/config/fcos/v1_5_exp" + "github.com/coreos/butane/config/openshift/v4_13_exp/result" + "github.com/coreos/butane/translate" + + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/ignition/v2/config/v3_4/types" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" + "github.com/stretchr/testify/assert" +) + +// TestElidedFieldWarning tests that we warn when transpiling fields to an +// Ignition config that can't be represented in an Ignition config. +func TestElidedFieldWarning(t *testing.T) { + in := Config{ + Metadata: Metadata{ + Name: "z", + }, + OpenShift: OpenShift{ + KernelArguments: []string{"a", "b"}, + FIPS: util.BoolToPtr(true), + KernelType: util.StrToPtr("realtime"), + }, + } + + var expected report.Report + expected.AddOnWarn(path.New("yaml", "openshift", "kernel_arguments"), common.ErrFieldElided) + expected.AddOnWarn(path.New("yaml", "openshift", "fips"), common.ErrFieldElided) + expected.AddOnWarn(path.New("yaml", "openshift", "kernel_type"), common.ErrFieldElided) + + _, _, r := in.ToIgn3_4Unvalidated(common.TranslateOptions{}) + assert.Equal(t, expected, r, "report mismatch") +} + +func TestTranslateConfig(t *testing.T) { + tests := []struct { + in Config + out result.MachineConfig + exceptions []translate.Translation + }{ + // empty-ish config + { + Config{ + Metadata: Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + }, + result.MachineConfig{ + ApiVersion: result.MC_API_VERSION, + Kind: result.MC_KIND, + Metadata: result.Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + Spec: result.Spec{ + Config: types.Config{ + Ignition: types.Ignition{ + Version: "3.4.0", + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "apiVersion")}, + {From: path.New("yaml", "version"), To: path.New("json", "kind")}, + {From: path.New("yaml", "version"), To: path.New("json", "spec")}, + {From: path.New("yaml"), To: path.New("json", "spec", "config")}, + {From: path.New("yaml", "ignition"), To: path.New("json", "spec", "config", "ignition")}, + {From: path.New("yaml", "version"), To: path.New("json", "spec", "config", "ignition", "version")}, + }, + }, + // FIPS + { + Config{ + Metadata: Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + OpenShift: OpenShift{ + FIPS: util.BoolToPtr(true), + }, + Config: fcos.Config{ + Config: base.Config{ + Storage: base.Storage{ + Luks: []base.Luks{ + { + Name: "a", + }, + { + Name: "b", + Options: []string{"b", "b"}, + }, + { + Name: "c", + Options: []string{"c", "--cipher", "c"}, + }, + { + Name: "d", + Options: []string{"--cipher=z"}, + }, + { + Name: "e", + Options: []string{"-c", "z"}, + }, + { + Name: "f", + Options: []string{"--ciphertext"}, + }, + }, + }, + }, + BootDevice: fcos.BootDevice{ + Luks: fcos.BootDeviceLuks{ + Tpm2: util.BoolToPtr(true), + }, + }, + }, + }, + result.MachineConfig{ + ApiVersion: result.MC_API_VERSION, + Kind: result.MC_KIND, + Metadata: result.Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + Spec: result.Spec{ + Config: types.Config{ + Ignition: types.Ignition{ + Version: "3.4.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/mapper/root", + Format: util.StrToPtr("xfs"), + Label: util.StrToPtr("root"), + WipeFilesystem: util.BoolToPtr(true), + }, + }, + Luks: []types.Luks{ + { + Name: "root", + Device: util.StrToPtr("/dev/disk/by-partlabel/root"), + Label: util.StrToPtr("luks-root"), + WipeVolume: util.BoolToPtr(true), + Options: []types.LuksOption{fipsCipherOption, fipsCipherArgument}, + Clevis: types.Clevis{ + Tpm2: util.BoolToPtr(true), + }, + }, + { + Name: "a", + Options: []types.LuksOption{fipsCipherOption, fipsCipherArgument}, + }, + { + Name: "b", + Options: []types.LuksOption{"b", "b", fipsCipherOption, fipsCipherArgument}, + }, + { + Name: "c", + Options: []types.LuksOption{"c", "--cipher", "c"}, + }, + { + Name: "d", + Options: []types.LuksOption{"--cipher=z"}, + }, + { + Name: "e", + Options: []types.LuksOption{"-c", "z"}, + }, + { + Name: "f", + Options: []types.LuksOption{"--ciphertext", fipsCipherOption, fipsCipherArgument}, + }, + }, + }, + }, + FIPS: util.BoolToPtr(true), + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "apiVersion")}, + {From: path.New("yaml", "version"), To: path.New("json", "kind")}, + {From: path.New("yaml", "version"), To: path.New("json", "spec")}, + {From: path.New("yaml"), To: path.New("json", "spec", "config")}, + {From: path.New("yaml", "ignition"), To: path.New("json", "spec", "config", "ignition")}, + {From: path.New("yaml", "version"), To: path.New("json", "spec", "config", "ignition", "version")}, + {From: path.New("yaml", "boot_device", "luks", "tpm2"), To: path.New("json", "spec", "config", "storage", "luks", 0, "clevis", "tpm2")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "spec", "config", "storage", "luks", 0, "clevis")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "spec", "config", "storage", "luks", 0, "device")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "spec", "config", "storage", "luks", 0, "label")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "spec", "config", "storage", "luks", 0, "name")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "spec", "config", "storage", "luks", 0, "wipeVolume")}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 0, "options", 0)}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 0, "options", 1)}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 0, "options")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "spec", "config", "storage", "luks", 0)}, + {From: path.New("yaml", "storage", "luks", 0, "name"), To: path.New("json", "spec", "config", "storage", "luks", 1, "name")}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 1, "options", 0)}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 1, "options", 1)}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 1, "options")}, + {From: path.New("yaml", "storage", "luks", 0), To: path.New("json", "spec", "config", "storage", "luks", 1)}, + {From: path.New("yaml", "storage", "luks", 1, "name"), To: path.New("json", "spec", "config", "storage", "luks", 2, "name")}, + {From: path.New("yaml", "storage", "luks", 1, "options", 0), To: path.New("json", "spec", "config", "storage", "luks", 2, "options", 0)}, + {From: path.New("yaml", "storage", "luks", 1, "options", 1), To: path.New("json", "spec", "config", "storage", "luks", 2, "options", 1)}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 2, "options", 2)}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 2, "options", 3)}, + {From: path.New("yaml", "storage", "luks", 1, "options"), To: path.New("json", "spec", "config", "storage", "luks", 2, "options")}, + {From: path.New("yaml", "storage", "luks", 1), To: path.New("json", "spec", "config", "storage", "luks", 2)}, + {From: path.New("yaml", "storage", "luks", 2, "name"), To: path.New("json", "spec", "config", "storage", "luks", 3, "name")}, + {From: path.New("yaml", "storage", "luks", 2, "options", 0), To: path.New("json", "spec", "config", "storage", "luks", 3, "options", 0)}, + {From: path.New("yaml", "storage", "luks", 2, "options", 1), To: path.New("json", "spec", "config", "storage", "luks", 3, "options", 1)}, + {From: path.New("yaml", "storage", "luks", 2, "options", 2), To: path.New("json", "spec", "config", "storage", "luks", 3, "options", 2)}, + {From: path.New("yaml", "storage", "luks", 2, "options"), To: path.New("json", "spec", "config", "storage", "luks", 3, "options")}, + {From: path.New("yaml", "storage", "luks", 2), To: path.New("json", "spec", "config", "storage", "luks", 3)}, + {From: path.New("yaml", "storage", "luks", 3, "name"), To: path.New("json", "spec", "config", "storage", "luks", 4, "name")}, + {From: path.New("yaml", "storage", "luks", 3, "options", 0), To: path.New("json", "spec", "config", "storage", "luks", 4, "options", 0)}, + {From: path.New("yaml", "storage", "luks", 3, "options"), To: path.New("json", "spec", "config", "storage", "luks", 4, "options")}, + {From: path.New("yaml", "storage", "luks", 3), To: path.New("json", "spec", "config", "storage", "luks", 4)}, + {From: path.New("yaml", "storage", "luks", 4, "name"), To: path.New("json", "spec", "config", "storage", "luks", 5, "name")}, + {From: path.New("yaml", "storage", "luks", 4, "options", 0), To: path.New("json", "spec", "config", "storage", "luks", 5, "options", 0)}, + {From: path.New("yaml", "storage", "luks", 4, "options", 1), To: path.New("json", "spec", "config", "storage", "luks", 5, "options", 1)}, + {From: path.New("yaml", "storage", "luks", 4, "options"), To: path.New("json", "spec", "config", "storage", "luks", 5, "options")}, + {From: path.New("yaml", "storage", "luks", 4), To: path.New("json", "spec", "config", "storage", "luks", 5)}, + {From: path.New("yaml", "storage", "luks", 5, "name"), To: path.New("json", "spec", "config", "storage", "luks", 6, "name")}, + {From: path.New("yaml", "storage", "luks", 5, "options", 0), To: path.New("json", "spec", "config", "storage", "luks", 6, "options", 0)}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 6, "options", 1)}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "config", "storage", "luks", 6, "options", 2)}, + {From: path.New("yaml", "storage", "luks", 5, "options"), To: path.New("json", "spec", "config", "storage", "luks", 6, "options")}, + {From: path.New("yaml", "storage", "luks", 5), To: path.New("json", "spec", "config", "storage", "luks", 6)}, + {From: path.New("yaml", "storage", "luks"), To: path.New("json", "spec", "config", "storage", "luks")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "label")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "spec", "config", "storage", "filesystems", 0)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "spec", "config", "storage", "filesystems")}, + {From: path.New("yaml", "storage"), To: path.New("json", "spec", "config", "storage")}, + {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "fips")}, + }, + }, + // Test Grub config + { + Config{ + Metadata: Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + Config: fcos.Config{ + Grub: fcos.Grub{ + Users: []fcos.GrubUser{ + { + Name: "root", + PasswordHash: util.StrToPtr("grub.pbkdf2.sha512.10000.874A958E526409..."), + }, + }, + }, + }, + }, + result.MachineConfig{ + ApiVersion: result.MC_API_VERSION, + Kind: result.MC_KIND, + Metadata: result.Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + Spec: result.Spec{ + Config: types.Config{ + Ignition: types.Ignition{ + Version: "3.4.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-label/boot", + Format: util.StrToPtr("ext4"), + Path: util.StrToPtr("/boot"), + }, + }, + Files: []types.File{ + { + Node: types.Node{ + Path: "/boot/grub2/user.cfg", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,%23%20Generated%20by%20Butane%0A%0Aset%20superusers%3D%22root%22%0Apassword_pbkdf2%20root%20grub.pbkdf2.sha512.10000.874A958E526409...%0A"), + Compression: util.StrToPtr(""), + }, + }, + }, + }, + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "apiVersion")}, + {From: path.New("yaml", "version"), To: path.New("json", "kind")}, + {From: path.New("yaml", "version"), To: path.New("json", "spec")}, + {From: path.New("yaml"), To: path.New("json", "spec", "config")}, + {From: path.New("yaml", "ignition"), To: path.New("json", "spec", "config", "ignition")}, + {From: path.New("yaml", "version"), To: path.New("json", "spec", "config", "ignition", "version")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems", 0)}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "path")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0)}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "path")}, + // "append" field is a remnant of translations performed in fcos config + // TODO: add a delete function to translation.TranslationSet and delete "append" translation + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "append")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "contents")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "contents", "source")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "contents", "compression")}, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := test.in.ToMachineConfig4_13Unvalidated(common.TranslateOptions{}) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, report.Report{}, r, "non-empty report") + baseutil.VerifyTranslations(t, translations, test.exceptions) + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// Test post-translation validation of RHCOS/MCO support for Ignition config fields. +func TestValidateSupport(t *testing.T) { + type entry struct { + kind report.EntryKind + err error + path path.ContextPath + } + tests := []struct { + in Config + entries []entry + }{ + // empty-ish config + { + Config{ + Metadata: Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + }, + []entry{}, + }, + // core user with only accepted fields + { + Config{ + Metadata: Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + Config: fcos.Config{ + Config: base.Config{ + Passwd: base.Passwd{ + Users: []base.PasswdUser{ + { + Name: "core", + PasswordHash: util.StrToPtr("corned beef"), + SSHAuthorizedKeys: []base.SSHAuthorizedKey{"value"}, + }, + }, + }, + }, + }, + }, + []entry{}, + }, + // valid data URL + { + Config{ + Metadata: Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + Config: fcos.Config{ + Config: base.Config{ + Storage: base.Storage{ + Files: []base.File{ + { + Path: "/f", + Contents: base.Resource{ + Source: util.StrToPtr("data:,foo"), + }, + }, + }, + }, + }, + }, + }, + []entry{}, + }, + // all the warnings/errors + { + Config{ + Metadata: Metadata{ + Name: "z", + Labels: map[string]string{ + ROLE_LABEL_KEY: "z", + }, + }, + Config: fcos.Config{ + Config: base.Config{ + Storage: base.Storage{ + Files: []base.File{ + { + Path: "/f", + }, + { + Path: "/g", + Append: []base.Resource{ + { + Inline: util.StrToPtr("z"), + }, + }, + }, + { + Path: "/h", + Contents: base.Resource{ + Source: util.StrToPtr("https://example.com/"), + }, + Mode: util.IntToPtr(04755), + }, + }, + Filesystems: []base.Filesystem{ + { + Device: "/dev/vda4", + Format: util.StrToPtr("btrfs"), + }, + { + Device: "/dev/vda5", + Format: util.StrToPtr("none"), + }, + }, + Directories: []base.Directory{ + { + Path: "/d", + }, + }, + Links: []base.Link{ + { + Path: "/l", + Target: util.StrToPtr("/t"), + }, + }, + }, + Passwd: base.Passwd{ + Users: []base.PasswdUser{ + { + Name: "core", + Gecos: util.StrToPtr("mercury delay line"), + Groups: []base.Group{ + "z", + }, + HomeDir: util.StrToPtr("/home/drum"), + NoCreateHome: util.BoolToPtr(true), + NoLogInit: util.BoolToPtr(true), + NoUserGroup: util.BoolToPtr(true), + PasswordHash: util.StrToPtr("corned beef"), + PrimaryGroup: util.StrToPtr("wheel"), + SSHAuthorizedKeys: []base.SSHAuthorizedKey{"value"}, + Shell: util.StrToPtr("/bin/tcsh"), + ShouldExist: util.BoolToPtr(false), + System: util.BoolToPtr(true), + UID: util.IntToPtr(42), + }, + { + Name: "bovik", + }, + }, + Groups: []base.PasswdGroup{ + { + Name: "mock", + }, + }, + }, + KernelArguments: base.KernelArguments{ + ShouldExist: []base.KernelArgument{ + "foo", + }, + ShouldNotExist: []base.KernelArgument{ + "bar", + }, + }, + }, + }, + }, + []entry{ + {report.Error, common.ErrBtrfsSupport, path.New("yaml", "storage", "filesystems", 0, "format")}, + {report.Error, common.ErrFilesystemNoneSupport, path.New("yaml", "storage", "filesystems", 1, "format")}, + {report.Error, common.ErrDirectorySupport, path.New("yaml", "storage", "directories", 0)}, + {report.Error, common.ErrFileAppendSupport, path.New("yaml", "storage", "files", 1, "append")}, + {report.Error, common.ErrFileSchemeSupport, path.New("yaml", "storage", "files", 2, "contents", "source")}, + {report.Error, common.ErrFileSpecialModeSupport, path.New("yaml", "storage", "files", 2, "mode")}, + {report.Error, common.ErrLinkSupport, path.New("yaml", "storage", "links", 0)}, + {report.Error, common.ErrGroupSupport, path.New("yaml", "passwd", "groups", 0)}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "gecos")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "groups")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "home_dir")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "no_create_home")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "no_log_init")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "no_user_group")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "primary_group")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "shell")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "should_exist")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "system")}, + {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "uid")}, + {report.Error, common.ErrUserNameSupport, path.New("yaml", "passwd", "users", 1)}, + {report.Error, common.ErrKernelArgumentSupport, path.New("yaml", "kernel_arguments", "should_exist", 0)}, + {report.Error, common.ErrKernelArgumentSupport, path.New("yaml", "kernel_arguments", "should_not_exist", 0)}, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + var expectedReport report.Report + for _, entry := range test.entries { + expectedReport.AddOn(entry.path, entry.err, entry.kind) + } + actual, translations, r := test.in.ToMachineConfig4_13Unvalidated(common.TranslateOptions{}) + assert.Equal(t, expectedReport, r, "report mismatch") + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} diff --git a/config/openshift/v4_13/validate.go b/config/openshift/v4_13/validate.go new file mode 100644 index 00000000..74f0955d --- /dev/null +++ b/config/openshift/v4_13/validate.go @@ -0,0 +1,43 @@ +// Copyright 2021 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 v4_13_exp + +import ( + "github.com/coreos/butane/config/common" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (m Metadata) Validate(c path.ContextPath) (r report.Report) { + if m.Name == "" { + r.AddOnError(c.Append("name"), common.ErrNameRequired) + } + if m.Labels[ROLE_LABEL_KEY] == "" { + r.AddOnError(c.Append("labels", ROLE_LABEL_KEY), common.ErrRoleRequired) + } + return +} + +func (os OpenShift) Validate(c path.ContextPath) (r report.Report) { + if os.KernelType != nil { + switch *os.KernelType { + case "", "default", "realtime": + default: + r.AddOnError(c.Append("kernel_type"), common.ErrInvalidKernelType) + } + } + return +} diff --git a/config/openshift/v4_13/validate_test.go b/config/openshift/v4_13/validate_test.go new file mode 100644 index 00000000..7e293283 --- /dev/null +++ b/config/openshift/v4_13/validate_test.go @@ -0,0 +1,249 @@ +// Copyright 2021 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 v4_13_exp + +import ( + "fmt" + "testing" + + "github.com/coreos/butane/config/common" + + "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" + "github.com/stretchr/testify/assert" +) + +func TestValidateMetadata(t *testing.T) { + tests := []struct { + in Metadata + out error + errPath path.ContextPath + }{ + // missing name + { + Metadata{ + Labels: map[string]string{ + ROLE_LABEL_KEY: "r", + }, + }, + common.ErrNameRequired, + path.New("yaml", "name"), + }, + // missing role + { + Metadata{ + Name: "n", + }, + common.ErrRoleRequired, + path.New("yaml", "labels", ROLE_LABEL_KEY), + }, + // empty role + { + Metadata{ + Name: "n", + Labels: map[string]string{ + ROLE_LABEL_KEY: "", + }, + }, + common.ErrRoleRequired, + path.New("yaml", "labels", ROLE_LABEL_KEY), + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + expected := report.Report{} + expected.AddOnError(test.errPath, test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} + +func TestValidateOpenShift(t *testing.T) { + tests := []struct { + in OpenShift + out error + errPath path.ContextPath + }{ + // empty struct + { + OpenShift{}, + nil, + path.New("yaml"), + }, + // bad kernel type + { + OpenShift{ + KernelType: util.StrToPtr("hurd"), + }, + common.ErrInvalidKernelType, + path.New("yaml", "kernel_type"), + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + expected := report.Report{} + expected.AddOnError(test.errPath, test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} + +// TestReportCorrelation tests that errors are correctly correlated to their source lines +func TestReportCorrelation(t *testing.T) { + tests := []struct { + in string + message string + line int64 + }{ + // Butane unused key check + { + ` + metadata: + name: something + labels: + machineconfiguration.openshift.io/role: r + storage: + files: + - path: /z + q: z`, + "Unused key q", + 9, + }, + // Butane YAML validation error + { + ` + metadata: + name: something + labels: + machineconfiguration.openshift.io/role: r + storage: + files: + - path: /z + contents: + source: https://example.com + inline: z`, + common.ErrTooManyResourceSources.Error(), + 10, + }, + // Butane YAML validation warning + { + ` + metadata: + name: something + labels: + machineconfiguration.openshift.io/role: r + storage: + files: + - path: /z + mode: 444`, + common.ErrDecimalMode.Error(), + 9, + }, + // Butane translation error + { + ` + metadata: + name: something + labels: + machineconfiguration.openshift.io/role: r + storage: + files: + - path: /z + contents: + local: z`, + common.ErrNoFilesDir.Error(), + 10, + }, + // Ignition validation error, leaf node + { + ` + metadata: + name: something + labels: + machineconfiguration.openshift.io/role: r + storage: + files: + - path: z`, + errors.ErrPathRelative.Error(), + 8, + }, + // Ignition validation error, partition + { + ` + metadata: + name: something + labels: + machineconfiguration.openshift.io/role: r + storage: + disks: + - device: /dev/z + partitions: + - start_mib: 5`, + errors.ErrNeedLabelOrNumber.Error(), + 10, + }, + // Ignition validation error, partition list + { + ` + metadata: + name: something + labels: + machineconfiguration.openshift.io/role: r + storage: + disks: + - device: /dev/z + partitions: + - number: 1 + should_exist: false + - label: z`, + errors.ErrZeroesWithShouldNotExist.Error(), + 10, + }, + // Ignition duplicate key check, paths + { + ` + metadata: + name: something + labels: + machineconfiguration.openshift.io/role: r + storage: + files: + - path: /z + - path: /z`, + errors.ErrDuplicate.Error(), + 9, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + for _, raw := range []bool{false, true} { + _, r, _ := ToConfigBytes([]byte(test.in), common.TranslateBytesOptions{ + Raw: raw, + }) + assert.Len(t, r.Entries, 1, "unexpected report length, raw %v", raw) + assert.Equal(t, test.message, r.Entries[0].Message, "bad error, raw %v", raw) + assert.NotNil(t, r.Entries[0].Marker.StartP, "marker start is nil, raw %v", raw) + assert.Equal(t, test.line, r.Entries[0].Marker.StartP.Line, "incorrect error line, raw %v", raw) + } + }) + } +} diff --git a/docs/config-openshift-v4_13.md b/docs/config-openshift-v4_13.md new file mode 100644 index 00000000..712a6c83 --- /dev/null +++ b/docs/config-openshift-v4_13.md @@ -0,0 +1,175 @@ +--- +title: OpenShift v4.13.0-experimental +parent: Configuration specifications +nav_order: 150 +--- + +# OpenShift Specification v4.13.0-experimental + +**Note: This configuration is experimental and has not been stabilized. It is subject to change without warning or announcement.** + +The OpenShift configuration is a YAML document conforming to the following specification, with **_italicized_** entries being optional: + +* **variant** (string): used to differentiate configs for different operating systems. Must be `openshift` for this specification. +* **version** (string): the semantic version of the spec for this document. This document is for version `4.13.0-experimental` and generates Ignition configs with version `3.4.0`. +* **metadata** (object): metadata about the generated MachineConfig resource. Respected when rendering to a MachineConfig, ignored when rendering directly to an Ignition config. + * **name** (string): a unique [name][k8s-names] for this MachineConfig resource. + * **labels** (object): string key/value pairs to apply as [Kubernetes labels][k8s-labels] to this MachineConfig resource. `machineconfiguration.openshift.io/role` is required. +* **_ignition_** (object): metadata about the configuration itself. + * **_config_** (objects): options related to the configuration. + * **_merge_** (list of objects): a list of the configs to be merged to the current config. + * **_source_** (string): the URL of the config. Supported schemes are `http`, `https`, `s3`, `arn`, `gs`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. + * **_inline_** (string): the contents of the config. Mutually exclusive with `source` and `local`. + * **_local_** (string): a local path to the contents of the config, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `source` and `inline`. + * **_compression_** (string): the type of compression used on the config (null or gzip). Compression cannot be used with S3. + * **_http_headers_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only. + * **name** (string): the header name. + * **_value_** (string): the header contents. + * **_verification_** (object): options related to the verification of the config. + * **_hash_** (string): the hash of the config, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed config. + * **_replace_** (object): the config that will replace the current. + * **_source_** (string): the URL of the config. Supported schemes are `http`, `https`, `s3`, `arn`, `gs`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. + * **_inline_** (string): the contents of the config. Mutually exclusive with `source` and `local`. + * **_local_** (string): a local path to the contents of the config, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `source` and `inline`. + * **_compression_** (string): the type of compression used on the config (null or gzip). Compression cannot be used with S3. + * **_http_headers_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only. + * **name** (string): the header name. + * **_value_** (string): the header contents. + * **_verification_** (object): options related to the verification of the config. + * **_hash_** (string): the hash of the config, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed config. + * **_timeouts_** (object): options relating to `http` timeouts when fetching files over `http` or `https`. + * **_http_response_headers_** (integer) the time to wait (in seconds) for the server's response headers (but not the body) after making a request. 0 indicates no timeout. Default is 10 seconds. + * **_http_total_** (integer) the time limit (in seconds) for the operation (connection, request, and response), including retries. 0 indicates no timeout. Default is 0. + * **_security_** (object): options relating to network security. + * **_tls_** (object): options relating to TLS when fetching resources over `https`. + * **_certificate_authorities_** (list of objects): the list of additional certificate authorities (in addition to the system authorities) to be used for TLS verification when fetching over `https`. All certificate authorities must have a unique `source`, `inline`, or `local`. + * **_source_** (string): the URL of the certificate bundle (in PEM format). With Ignition ≥ 2.4.0, the bundle can contain multiple concatenated certificates. Supported schemes are `http`, `https`, `s3`, `arn`, `gs`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. + * **_inline_** (string): the contents of the certificate bundle (in PEM format). With Ignition ≥ 2.4.0, the bundle can contain multiple concatenated certificates. Mutually exclusive with `source` and `local`. + * **_local_** (string): a local path to the contents of the certificate bundle (in PEM format), relative to the directory specified by the `--files-dir` command-line argument. With Ignition ≥ 2.4.0, the bundle can contain multiple concatenated certificates. Mutually exclusive with `source` and `inline`. + * **_compression_** (string): the type of compression used on the certificate (null or gzip). Compression cannot be used with S3. + * **_http_headers_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only. + * **name** (string): the header name. + * **_value_** (string): the header contents. + * **_verification_** (object): options related to the verification of the certificate. + * **_hash_** (string): the hash of the certificate, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed certificate. + * **_proxy_** (object): options relating to setting an `HTTP(S)` proxy when fetching resources. + * **_http_proxy_** (string): will be used as the proxy URL for HTTP requests and HTTPS requests unless overridden by `https_proxy` or `no_proxy`. + * **_https_proxy_** (string): will be used as the proxy URL for HTTPS requests unless overridden by `no_proxy`. + * **_no_proxy_** (list of strings): specifies a list of strings to hosts that should be excluded from proxying. Each value is represented by an `IP address prefix (1.2.3.4)`, `an IP address prefix in CIDR notation (1.2.3.4/8)`, `a domain name`, or `a special DNS label (*)`. An IP address prefix and domain name can also include a literal port number `(1.2.3.4:80)`. A domain name matches that name and all subdomains. A domain name with a leading `.` matches subdomains only. For example `foo.com` matches `foo.com` and `bar.foo.com`; `.y.com` matches `x.y.com` but not `y.com`. A single asterisk `(*)` indicates that no proxying should be done. +* **_storage_** (object): describes the desired state of the system's storage devices. + * **_disks_** (list of objects): the list of disks to be configured and their options. Every entry must have a unique `device`. + * **device** (string): the absolute path to the device. Devices are typically referenced by the `/dev/disk/by-*` symlinks. + * **_wipe_table_** (boolean): whether or not the partition tables shall be wiped. When true, the partition tables are erased before any further manipulation. Otherwise, the existing entries are left intact. + * **_partitions_** (list of objects): the list of partitions and their configuration for this particular disk. Every partition must have a unique `number`, or if 0 is specified, a unique `label`. + * **_label_** (string): the PARTLABEL for the partition. + * **_number_** (integer): the partition number, which dictates its position in the partition table (one-indexed). If zero, use the next available partition slot. + * **_size_mib_** (integer): the size of the partition (in mebibytes). If zero, the partition will be made as large as possible. + * **_start_mib_** (integer): the start of the partition (in mebibytes). If zero, the partition will be positioned at the start of the largest block available. + * **_type_guid_** (string): the GPT [partition type GUID][part-types]. If omitted, the default will be 0FC63DAF-8483-4772-8E79-3D69D8477DE4 (Linux filesystem data). + * **_guid_** (string): the GPT unique partition GUID. + * **_wipe_partition_entry_** (boolean) if true, Ignition will clobber an existing partition if it does not match the config. If false (default), Ignition will fail instead. + * **_should_exist_** (boolean) whether or not the partition with the specified `number` should exist. If omitted, it defaults to true. If false Ignition will either delete the specified partition or fail, depending on `wipePartitionEntry`. If false `number` must be specified and non-zero and `label`, `start`, `size`, `guid`, and `typeGuid` must all be omitted. + * **_resize_** (boolean) whether or not the existing partition should be resized. If omitted, it defaults to false. If true, Ignition will resize an existing partition if it matches the config in all respects except the partition size. + * **_raid_** (list of objects): the list of RAID arrays to be configured. Every RAID array must have a unique `name`. + * **name** (string): the name to use for the resulting md device. + * **level** (string): the redundancy level of the array (e.g. linear, raid1, raid5, etc.). + * **devices** (list of strings): the list of devices (referenced by their absolute path) in the array. + * **_spares_** (integer): the number of spares (if applicable) in the array. + * **_options_** (list of strings): any additional options to be passed to mdadm. + * **_filesystems_** (list of objects): the list of filesystems to be configured. `device` and `format` need to be specified. Every filesystem must have a unique `device`. + * **device** (string): the absolute path to the device. Devices are typically referenced by the `/dev/disk/by-*` symlinks. + * **format** (string): the filesystem format (ext4, xfs, vfat, or swap). + * **_path_** (string): the mount-point of the filesystem while Ignition is running relative to where the root filesystem will be mounted. This is not necessarily the same as where it should be mounted in the real root, but it is encouraged to make it the same. + * **_wipe_filesystem_** (boolean): whether or not to wipe the device before filesystem creation, see [the documentation on filesystems](https://coreos.github.io/ignition/operator-notes/#filesystem-reuse-semantics) for more information. Defaults to false. + * **_label_** (string): the label of the filesystem. + * **_uuid_** (string): the uuid of the filesystem. + * **_options_** (list of strings): any additional options to be passed to the format-specific mkfs utility. + * **_mount_options_** (list of strings): any special options to be passed to the mount command. + * **_with_mount_unit_** (bool): whether to additionally generate a generic mount unit for this filesystem or a swap unit for this swap area. If a more specific unit is needed, a custom one can be specified in the `systemd.units` section. The unit will be named with the [escaped][systemd-escape] version of the `path` or `device`, depending on the unit type. If your filesystem is located on a Tang-backed LUKS device, the unit will automatically require network access if you specify the device as `/dev/mapper/` or `/dev/disk/by-id/dm-name-`. + * **_files_** (list of objects): the list of files to be written. Every file, directory and link must have a unique `path`. + * **path** (string): the absolute path to the file. + * **_overwrite_** (boolean): whether to delete preexisting nodes at the path. `contents` must be specified if `overwrite` is true. Defaults to false. + * **_contents_** (object): options related to the contents of the file. + * **_compression_** (string): the type of compression used on the contents (null or gzip). Compression cannot be used with S3. + * **_source_** (string): the URL of the file contents. Only the [`data`][rfc2397] scheme is supported. If source is omitted and a regular file already exists at the path, Ignition will do nothing. If source is omitted and no file exists, an empty file will be created. Mutually exclusive with `inline` and `local`. + * **_inline_** (string): the contents of the file. Mutually exclusive with `source` and `local`. + * **_local_** (string): a local path to the contents of the file, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `source` and `inline`. + * **_verification_** (object): options related to the verification of the file contents. + * **_hash_** (string): the hash of the contents, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed contents. + * **_mode_** (integer): the file's permission mode. Setuid/setgid/sticky bits are not supported. If not specified, the permission mode for files defaults to 0644 or the existing file's permissions if `overwrite` is false, `contents` is unspecified, and a file already exists at the path. + * **_user_** (object): specifies the file's owner. + * **_id_** (integer): the user ID of the owner. + * **_name_** (string): the user name of the owner. + * **_group_** (object): specifies the file's group. + * **_id_** (integer): the group ID of the group. + * **_name_** (string): the group name of the group. + * **_luks_** (list of objects): the list of luks devices to be created. Every device must have a unique `name`. + * **name** (string): the name of the luks device. + * **device** (string): the absolute path to the device. Devices are typically referenced by the `/dev/disk/by-*` symlinks. + * **_key_file_** (string): options related to the contents of the key file. + * **_compression_** (string): the type of compression used on the contents (null or gzip). Compression cannot be used with S3. + * **_source_** (string): the URL of the key file contents. Supported schemes are `http`, `https`, `tftp`, `s3`, `arn`, `gs`, and [`data`][rfc2397]. When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. + * **_inline_** (string): the contents of the key file. Mutually exclusive with `source` and `local`. + * **_local_** (string): a local path to the contents of the key file, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `source` and `inline`. + * **_http_headers_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only. + * **name** (string): the header name. + * **_value_** (string): the header contents. + * **_verification_** (object): options related to the verification of the file contents. + * **_hash_** (string): the hash of the contents, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed contents. + * **_label_** (string): the label of the luks device. + * **_uuid_** (string): the uuid of the luks device. + * **_options_** (list of strings): any additional options to be passed to `cryptsetup luksFormat`. + * **_discard_** (boolean): whether to issue discard commands to the underlying block device when blocks are freed. Enabling this improves performance and device longevity on SSDs and space utilization on thinly provisioned SAN devices, but leaks information about which disk blocks contain data. If omitted, it defaults to false. + * **_open_options_** (list of strings): any additional options to be passed to `cryptsetup luksOpen`. Supported options will be persistently written to the luks volume. + * **_wipe_volume_** (boolean): whether or not to wipe the device before volume creation, see [the Ignition documentation on filesystems](https://coreos.github.io/ignition/operator-notes/#filesystem-reuse-semantics) for more information. + * **_clevis_** (object): describes the clevis configuration for the luks device. + * **_tang_** (list of objects): describes a tang server. Every server must have a unique `url`. + * **url** (string): url of the tang server. + * **thumbprint** (string): thumbprint of a trusted signing key. + * **advertisement** (string): the advertisement JSON. If not specified, the advertisement is fetched from the tang server during provisioning. + * **_tpm2_** (bool): whether or not to use a tpm2 device. + * **_threshold_** (int): sets the minimum number of pieces required to decrypt the device. Default is 1. + * **_custom_** (object): overrides the clevis configuration. The `pin` & `config` will be passed directly to `clevis luks bind`. If specified, all other clevis options must be omitted. + * **pin** (string): the clevis pin. + * **config** (string): the clevis configuration JSON. + * **_needs_network_** (bool): whether or not the device requires networking. + * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Symlinks must not be present. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. File attributes can be overridden by creating a corresponding entry in the `files` section; such entries must omit `contents`. + * **local** (string): the base of the local directory tree, relative to the directory specified by the `--files-dir` command-line argument. + * **_path_** (string): the path of the tree within the target system. Defaults to `/`. +* **_systemd_** (object): describes the desired state of the systemd units. + * **_units_** (list of objects): the list of systemd units. Every unit must have a unique `name`. + * **name** (string): the name of the unit. This must be suffixed with a valid unit type (e.g. "thing.service"). + * **_enabled_** (boolean): whether or not the service shall be enabled. When true, the service is enabled. When false, the service is disabled. When omitted, the service is unmodified. In order for this to have any effect, the unit must have an install section. + * **_mask_** (boolean): whether or not the service shall be masked. When true, the service is masked by symlinking it to `/dev/null`. + * **_contents_** (string): the contents of the unit. + * **_dropins_** (list of objects): the list of drop-ins for the unit. Every drop-in must have a unique `name`. + * **name** (string): the name of the drop-in. This must be suffixed with ".conf". + * **_contents_** (string): the contents of the drop-in. +* **_passwd_** (object): describes the desired additions to the passwd database. + * **_users_** (list of objects): the list of accounts that shall exist. All users must have a unique `name`. + * **name** (string): the username for the account. Must be `core`. + * **_password_hash_** (string): the hashed password for the account. + * **_ssh_authorized_keys_** (list of strings): a list of SSH keys to be added to `.ssh/authorized_keys` in the user's home directory. All SSH keys must be unique. +* **_boot_device_** (object): describes the desired boot device configuration. At least one of `luks` or `mirror` must be specified. + * **_layout_** (string): the disk layout of the target OS image. Supported values are `aarch64`, `ppc64le`, and `x86_64`. Defaults to `x86_64`. + * **_luks_** (object): describes the clevis configuration for encrypting the root filesystem. + * **_tang_** (list of objects): describes a tang server. Every server must have a unique `url`. + * **url** (string): url of the tang server. + * **thumbprint** (string): thumbprint of a trusted signing key. + * **advertisement** (string): the advertisement JSON. If not specified, the advertisement is fetched from the tang server during provisioning. + * **_tpm2_** (bool): whether or not to use a tpm2 device. + * **_threshold_** (int): sets the minimum number of pieces required to decrypt the device. Default is 1. + * **_discard_** (boolean): whether to issue discard commands to the underlying block device when blocks are freed. Enabling this improves performance and device longevity on SSDs and space utilization on thinly provisioned SAN devices, but leaks information about which disk blocks contain data. If omitted, it defaults to false. + * **_mirror_** (object): describes mirroring of the boot disk for fault tolerance. + * **_devices_** (list of strings): the list of whole-disk devices (not partitions) to include in the disk array, referenced by their absolute path. At least two devices must be specified. +* **_openshift_** (object): describes miscellaneous OpenShift configuration. Respected when rendering to a MachineConfig, ignored when rendering directly to an Ignition config. + * **_kernel_type_** (string): which kernel to use on the node. Must be `default` or `realtime`. + * **_kernel_arguments_** (list of strings): arguments to be added to the kernel command line. + * **_extensions_** (list of strings): RHCOS extensions to be installed on the node. + * **_fips_** (bool): whether or not to enable FIPS 140-2 compatibility. If omitted, defaults to false. + +[k8s-names]: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names +[k8s-labels]: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ +[part-types]: http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs +[rfc2397]: https://tools.ietf.org/html/rfc2397 +[systemd-escape]: https://www.freedesktop.org/software/systemd/man/systemd-escape.html From 23ce49a000e3f7430075159ce05f92598048a21d Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Mon, 13 Mar 2023 14:39:50 -0400 Subject: [PATCH 3/3] Stabilize openshift 4.13.0; create openshift 4.14.0-experimental The MCO doesn't support Ignition spec 3.3.0 yet, so we also need to roll back openshift 4.13.0 to fcos 1.3.0 and base 0.3 --- config/config.go | 6 +- config/openshift/v4_13/result/schema.go | 2 +- config/openshift/v4_13/schema.go | 4 +- config/openshift/v4_13/translate.go | 64 ++-------- config/openshift/v4_13/translate_test.go | 128 +++---------------- config/openshift/v4_13/validate.go | 2 +- config/openshift/v4_13/validate_test.go | 2 +- config/openshift/v4_14_exp/schema.go | 2 +- config/openshift/v4_14_exp/translate.go | 20 +-- config/openshift/v4_14_exp/translate_test.go | 8 +- config/openshift/v4_14_exp/validate.go | 2 +- config/openshift/v4_14_exp/validate_test.go | 2 +- docs/config-openshift-v4_13.md | 25 ++-- docs/config-openshift-v4_14-exp.md | 6 +- docs/release-notes.md | 16 ++- docs/specs.md | 6 +- docs/upgrading-openshift.md | 4 + 17 files changed, 82 insertions(+), 217 deletions(-) diff --git a/config/config.go b/config/config.go index 54ea1f2c..5e4308c5 100644 --- a/config/config.go +++ b/config/config.go @@ -29,7 +29,8 @@ import ( openshift4_10 "github.com/coreos/butane/config/openshift/v4_10" openshift4_11 "github.com/coreos/butane/config/openshift/v4_11" openshift4_12 "github.com/coreos/butane/config/openshift/v4_12" - openshift4_13_exp "github.com/coreos/butane/config/openshift/v4_13_exp" + openshift4_13 "github.com/coreos/butane/config/openshift/v4_13" + openshift4_14_exp "github.com/coreos/butane/config/openshift/v4_14_exp" openshift4_8 "github.com/coreos/butane/config/openshift/v4_8" openshift4_9 "github.com/coreos/butane/config/openshift/v4_9" r4e1_0 "github.com/coreos/butane/config/r4e/v1_0" @@ -64,7 +65,8 @@ func init() { RegisterTranslator("openshift", "4.10.0", openshift4_10.ToConfigBytes) RegisterTranslator("openshift", "4.11.0", openshift4_11.ToConfigBytes) RegisterTranslator("openshift", "4.12.0", openshift4_12.ToConfigBytes) - RegisterTranslator("openshift", "4.13.0-experimental", openshift4_13_exp.ToConfigBytes) + RegisterTranslator("openshift", "4.13.0", openshift4_13.ToConfigBytes) + RegisterTranslator("openshift", "4.14.0-experimental", openshift4_14_exp.ToConfigBytes) RegisterTranslator("r4e", "1.0.0", r4e1_0.ToIgn3_3Bytes) RegisterTranslator("r4e", "1.1.0-experimental", r4e1_1_exp.ToIgn3_4Bytes) RegisterTranslator("rhcos", "0.1.0", unsupportedRhcosVariant) diff --git a/config/openshift/v4_13/result/schema.go b/config/openshift/v4_13/result/schema.go index ad5abd8e..37e49f30 100644 --- a/config/openshift/v4_13/result/schema.go +++ b/config/openshift/v4_13/result/schema.go @@ -15,7 +15,7 @@ package result import ( - "github.com/coreos/ignition/v2/config/v3_4/types" + "github.com/coreos/ignition/v2/config/v3_2/types" ) const ( diff --git a/config/openshift/v4_13/schema.go b/config/openshift/v4_13/schema.go index ab204d39..54e6bfda 100644 --- a/config/openshift/v4_13/schema.go +++ b/config/openshift/v4_13/schema.go @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_13 import ( - fcos "github.com/coreos/butane/config/fcos/v1_5_exp" + fcos "github.com/coreos/butane/config/fcos/v1_3" ) const ROLE_LABEL_KEY = "machineconfiguration.openshift.io/role" diff --git a/config/openshift/v4_13/translate.go b/config/openshift/v4_13/translate.go index 4f951b49..9f1404f6 100644 --- a/config/openshift/v4_13/translate.go +++ b/config/openshift/v4_13/translate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_13 import ( "net/url" @@ -20,12 +20,12 @@ import ( "strings" "github.com/coreos/butane/config/common" - "github.com/coreos/butane/config/openshift/v4_13_exp/result" + "github.com/coreos/butane/config/openshift/v4_13/result" cutil "github.com/coreos/butane/config/util" "github.com/coreos/butane/translate" "github.com/coreos/ignition/v2/config/util" - "github.com/coreos/ignition/v2/config/v3_4/types" + "github.com/coreos/ignition/v2/config/v3_2/types" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" ) @@ -42,11 +42,10 @@ const ( // can be tracked back to their source in the source config. No config // validation is performed on input or output. func (c Config) ToMachineConfig4_13Unvalidated(options common.TranslateOptions) (result.MachineConfig, translate.TranslationSet, report.Report) { - cfg, ts, r := c.Config.ToIgn3_4Unvalidated(options) + cfg, ts, r := c.Config.ToIgn3_2Unvalidated(options) if r.IsFatal() { return result.MachineConfig{}, ts, r } - ts = translateUserGrubCfg(&cfg, &ts) // wrap ts = ts.PrefixPaths(path.New("yaml"), path.New("json", "spec", "config")) @@ -103,11 +102,11 @@ func (c Config) ToMachineConfig4_13(options common.TranslateOptions) (result.Mac return cfg.(result.MachineConfig), r, err } -// ToIgn3_4Unvalidated translates the config to an Ignition config. It also +// ToIgn3_2Unvalidated translates the config to an Ignition config. It also // returns the set of translations it did so paths in the resultant config // can be tracked back to their source in the source config. No config // validation is performed on input or output. -func (c Config) ToIgn3_4Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { +func (c Config) ToIgn3_2Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { mc, ts, r := c.ToMachineConfig4_13Unvalidated(options) cfg := mc.Spec.Config @@ -122,21 +121,21 @@ func (c Config) ToIgn3_4Unvalidated(options common.TranslateOptions) (types.Conf return cfg, ts, r } -// ToIgn3_4 translates the config to an Ignition config. It returns a +// ToIgn3_2 translates the config to an Ignition config. It returns a // report of any errors or warnings in the source and resultant config. If // the report has fatal errors or it encounters other problems translating, // an error is returned. -func (c Config) ToIgn3_4(options common.TranslateOptions) (types.Config, report.Report, error) { - cfg, r, err := cutil.Translate(c, "ToIgn3_4Unvalidated", options) +func (c Config) ToIgn3_2(options common.TranslateOptions) (types.Config, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToIgn3_2Unvalidated", options) return cfg.(types.Config), r, err } -// ToConfigBytes translates from a v4.13 Butane config to a v4.13 MachineConfig or a v3.4.0 Ignition config. It returns a report of any errors or +// ToConfigBytes translates from a v4.13 Butane config to a v4.13 MachineConfig or a v3.2.0 Ignition config. It returns a report of any errors or // warnings in the source and resultant config. If the report has fatal errors or it encounters other problems // translating, an error is returned. func ToConfigBytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { if options.Raw { - return cutil.TranslateBytes(input, &Config{}, "ToIgn3_4", options) + return cutil.TranslateBytes(input, &Config{}, "ToIgn3_2", options) } else { return cutil.TranslateBytesYAML(input, &Config{}, "ToMachineConfig4_13", options) } @@ -207,10 +206,6 @@ func validateMCOSupport(mc result.MachineConfig, ts translate.TranslationSet) re // FORBIDDEN - Not supported by the MCD. If present in MC, MCD will // mark the node degraded. We reject these. // - // REDUNDANT - Feature is also provided by a MachineConfig-specific - // field with different semantics. To reduce confusion, disable - // this implementation. - // // IMMUTABLE - Permitted in MC, passed through to Ignition, but not // supported by the MCD. MCD will mark the node degraded if the // field changes after the node is provisioned. We reject these @@ -223,12 +218,6 @@ func validateMCOSupport(mc result.MachineConfig, ts translate.TranslationSet) re // supported fields. We reject these. var r report.Report - for i, fs := range mc.Spec.Config.Storage.Filesystems { - if fs.Format != nil && *fs.Format == "none" { - // UNPARSABLE - r.AddOnError(path.New("json", "spec", "config", "storage", "filesystems", i, "format"), common.ErrFilesystemNoneSupport) - } - } for i := range mc.Spec.Config.Storage.Directories { // IMMUTABLE r.AddOnError(path.New("json", "spec", "config", "storage", "directories", i), common.ErrDirectorySupport) @@ -286,36 +275,5 @@ func validateMCOSupport(mc result.MachineConfig, ts translate.TranslationSet) re r.AddOnError(path.New("json", "spec", "config", "passwd", "users", i), common.ErrUserNameSupport) } } - for i := range mc.Spec.Config.KernelArguments.ShouldExist { - // UNPARSABLE, REDUNDANT - r.AddOnError(path.New("json", "spec", "config", "kernelArguments", "shouldExist", i), common.ErrKernelArgumentSupport) - } - for i := range mc.Spec.Config.KernelArguments.ShouldNotExist { - // UNPARSABLE, REDUNDANT - r.AddOnError(path.New("json", "spec", "config", "kernelArguments", "shouldNotExist", i), common.ErrKernelArgumentSupport) - } return cutil.TranslateReportPaths(r, ts) } - -// fcos config generates a user.cfg file using append; however, OpenShift config -// does not support append (since MCO does not support it). Let change the file to use contents -func translateUserGrubCfg(config *types.Config, ts *translate.TranslationSet) translate.TranslationSet { - newMappings := translate.NewTranslationSet("json", "json") - for i, file := range config.Storage.Files { - if file.Path == "/boot/grub2/user.cfg" { - if len(file.Append) != 1 { - // The number of append objects was different from expected, this file - // was created by the user and not via butane GRUB sugar - return *ts - } - fromPath := path.New("json", "storage", "files", i, "append", 0) - translatedPath := path.New("json", "storage", "files", i, "contents") - config.Storage.Files[i].FileEmbedded1.Contents = file.Append[0] - config.Storage.Files[i].FileEmbedded1.Append = nil - newMappings.AddFromCommonObject(fromPath, translatedPath, config.Storage.Files[i].FileEmbedded1.Contents) - - return ts.Map(newMappings) - } - } - return *ts -} diff --git a/config/openshift/v4_13/translate_test.go b/config/openshift/v4_13/translate_test.go index 77ea7da4..4719df6b 100644 --- a/config/openshift/v4_13/translate_test.go +++ b/config/openshift/v4_13/translate_test.go @@ -12,21 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_13 import ( "fmt" "testing" baseutil "github.com/coreos/butane/base/util" - base "github.com/coreos/butane/base/v0_5_exp" + base "github.com/coreos/butane/base/v0_3" "github.com/coreos/butane/config/common" - fcos "github.com/coreos/butane/config/fcos/v1_5_exp" - "github.com/coreos/butane/config/openshift/v4_13_exp/result" + fcos "github.com/coreos/butane/config/fcos/v1_3" + "github.com/coreos/butane/config/openshift/v4_13/result" "github.com/coreos/butane/translate" "github.com/coreos/ignition/v2/config/util" - "github.com/coreos/ignition/v2/config/v3_4/types" + "github.com/coreos/ignition/v2/config/v3_2/types" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" "github.com/stretchr/testify/assert" @@ -51,7 +51,7 @@ func TestElidedFieldWarning(t *testing.T) { expected.AddOnWarn(path.New("yaml", "openshift", "fips"), common.ErrFieldElided) expected.AddOnWarn(path.New("yaml", "openshift", "kernel_type"), common.ErrFieldElided) - _, _, r := in.ToIgn3_4Unvalidated(common.TranslateOptions{}) + _, _, r := in.ToIgn3_2Unvalidated(common.TranslateOptions{}) assert.Equal(t, expected, r, "report mismatch") } @@ -83,7 +83,7 @@ func TestTranslateConfig(t *testing.T) { Spec: result.Spec{ Config: types.Config{ Ignition: types.Ignition{ - Version: "3.4.0", + Version: "3.2.0", }, }, }, @@ -118,23 +118,23 @@ func TestTranslateConfig(t *testing.T) { }, { Name: "b", - Options: []string{"b", "b"}, + Options: []base.LuksOption{"b", "b"}, }, { Name: "c", - Options: []string{"c", "--cipher", "c"}, + Options: []base.LuksOption{"c", "--cipher", "c"}, }, { Name: "d", - Options: []string{"--cipher=z"}, + Options: []base.LuksOption{"--cipher=z"}, }, { Name: "e", - Options: []string{"-c", "z"}, + Options: []base.LuksOption{"-c", "z"}, }, { Name: "f", - Options: []string{"--ciphertext"}, + Options: []base.LuksOption{"--ciphertext"}, }, }, }, @@ -158,7 +158,7 @@ func TestTranslateConfig(t *testing.T) { Spec: result.Spec{ Config: types.Config{ Ignition: types.Ignition{ - Version: "3.4.0", + Version: "3.2.0", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -176,7 +176,7 @@ func TestTranslateConfig(t *testing.T) { Label: util.StrToPtr("luks-root"), WipeVolume: util.BoolToPtr(true), Options: []types.LuksOption{fipsCipherOption, fipsCipherArgument}, - Clevis: types.Clevis{ + Clevis: &types.Clevis{ Tpm2: util.BoolToPtr(true), }, }, @@ -271,89 +271,6 @@ func TestTranslateConfig(t *testing.T) { {From: path.New("yaml", "openshift", "fips"), To: path.New("json", "spec", "fips")}, }, }, - // Test Grub config - { - Config{ - Metadata: Metadata{ - Name: "z", - Labels: map[string]string{ - ROLE_LABEL_KEY: "z", - }, - }, - Config: fcos.Config{ - Grub: fcos.Grub{ - Users: []fcos.GrubUser{ - { - Name: "root", - PasswordHash: util.StrToPtr("grub.pbkdf2.sha512.10000.874A958E526409..."), - }, - }, - }, - }, - }, - result.MachineConfig{ - ApiVersion: result.MC_API_VERSION, - Kind: result.MC_KIND, - Metadata: result.Metadata{ - Name: "z", - Labels: map[string]string{ - ROLE_LABEL_KEY: "z", - }, - }, - Spec: result.Spec{ - Config: types.Config{ - Ignition: types.Ignition{ - Version: "3.4.0", - }, - Storage: types.Storage{ - Filesystems: []types.Filesystem{ - { - Device: "/dev/disk/by-label/boot", - Format: util.StrToPtr("ext4"), - Path: util.StrToPtr("/boot"), - }, - }, - Files: []types.File{ - { - Node: types.Node{ - Path: "/boot/grub2/user.cfg", - }, - FileEmbedded1: types.FileEmbedded1{ - Contents: types.Resource{ - Source: util.StrToPtr("data:,%23%20Generated%20by%20Butane%0A%0Aset%20superusers%3D%22root%22%0Apassword_pbkdf2%20root%20grub.pbkdf2.sha512.10000.874A958E526409...%0A"), - Compression: util.StrToPtr(""), - }, - }, - }, - }, - }, - }, - }, - }, - []translate.Translation{ - {From: path.New("yaml", "version"), To: path.New("json", "apiVersion")}, - {From: path.New("yaml", "version"), To: path.New("json", "kind")}, - {From: path.New("yaml", "version"), To: path.New("json", "spec")}, - {From: path.New("yaml"), To: path.New("json", "spec", "config")}, - {From: path.New("yaml", "ignition"), To: path.New("json", "spec", "config", "ignition")}, - {From: path.New("yaml", "version"), To: path.New("json", "spec", "config", "ignition", "version")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems", 0)}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "path")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "device")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "filesystems", 0, "format")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0)}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "path")}, - // "append" field is a remnant of translations performed in fcos config - // TODO: add a delete function to translation.TranslationSet and delete "append" translation - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "append")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "contents")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "contents", "source")}, - {From: path.New("yaml", "grub", "users"), To: path.New("json", "spec", "config", "storage", "files", 0, "contents", "compression")}, - }, - }, } for i, test := range tests { @@ -478,10 +395,6 @@ func TestValidateSupport(t *testing.T) { Device: "/dev/vda4", Format: util.StrToPtr("btrfs"), }, - { - Device: "/dev/vda5", - Format: util.StrToPtr("none"), - }, }, Directories: []base.Directory{ { @@ -491,7 +404,7 @@ func TestValidateSupport(t *testing.T) { Links: []base.Link{ { Path: "/l", - Target: util.StrToPtr("/t"), + Target: "/t", }, }, }, @@ -525,20 +438,11 @@ func TestValidateSupport(t *testing.T) { }, }, }, - KernelArguments: base.KernelArguments{ - ShouldExist: []base.KernelArgument{ - "foo", - }, - ShouldNotExist: []base.KernelArgument{ - "bar", - }, - }, }, }, }, []entry{ {report.Error, common.ErrBtrfsSupport, path.New("yaml", "storage", "filesystems", 0, "format")}, - {report.Error, common.ErrFilesystemNoneSupport, path.New("yaml", "storage", "filesystems", 1, "format")}, {report.Error, common.ErrDirectorySupport, path.New("yaml", "storage", "directories", 0)}, {report.Error, common.ErrFileAppendSupport, path.New("yaml", "storage", "files", 1, "append")}, {report.Error, common.ErrFileSchemeSupport, path.New("yaml", "storage", "files", 2, "contents", "source")}, @@ -557,8 +461,6 @@ func TestValidateSupport(t *testing.T) { {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "system")}, {report.Error, common.ErrUserFieldSupport, path.New("yaml", "passwd", "users", 0, "uid")}, {report.Error, common.ErrUserNameSupport, path.New("yaml", "passwd", "users", 1)}, - {report.Error, common.ErrKernelArgumentSupport, path.New("yaml", "kernel_arguments", "should_exist", 0)}, - {report.Error, common.ErrKernelArgumentSupport, path.New("yaml", "kernel_arguments", "should_not_exist", 0)}, }, }, } diff --git a/config/openshift/v4_13/validate.go b/config/openshift/v4_13/validate.go index 74f0955d..d1e932d6 100644 --- a/config/openshift/v4_13/validate.go +++ b/config/openshift/v4_13/validate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_13 import ( "github.com/coreos/butane/config/common" diff --git a/config/openshift/v4_13/validate_test.go b/config/openshift/v4_13/validate_test.go index 7e293283..c4f15253 100644 --- a/config/openshift/v4_13/validate_test.go +++ b/config/openshift/v4_13/validate_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_13 import ( "fmt" diff --git a/config/openshift/v4_14_exp/schema.go b/config/openshift/v4_14_exp/schema.go index ab204d39..07d5d1c0 100644 --- a/config/openshift/v4_14_exp/schema.go +++ b/config/openshift/v4_14_exp/schema.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_14_exp import ( fcos "github.com/coreos/butane/config/fcos/v1_5_exp" diff --git a/config/openshift/v4_14_exp/translate.go b/config/openshift/v4_14_exp/translate.go index 4f951b49..1d05a67b 100644 --- a/config/openshift/v4_14_exp/translate.go +++ b/config/openshift/v4_14_exp/translate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_14_exp import ( "net/url" @@ -20,7 +20,7 @@ import ( "strings" "github.com/coreos/butane/config/common" - "github.com/coreos/butane/config/openshift/v4_13_exp/result" + "github.com/coreos/butane/config/openshift/v4_14_exp/result" cutil "github.com/coreos/butane/config/util" "github.com/coreos/butane/translate" @@ -37,11 +37,11 @@ const ( fipsCipherArgument = types.LuksOption("aes-cbc-essiv:sha256") ) -// ToMachineConfig4_13Unvalidated translates the config to a MachineConfig. It also +// ToMachineConfig4_14Unvalidated translates the config to a MachineConfig. It also // returns the set of translations it did so paths in the resultant config // can be tracked back to their source in the source config. No config // validation is performed on input or output. -func (c Config) ToMachineConfig4_13Unvalidated(options common.TranslateOptions) (result.MachineConfig, translate.TranslationSet, report.Report) { +func (c Config) ToMachineConfig4_14Unvalidated(options common.TranslateOptions) (result.MachineConfig, translate.TranslationSet, report.Report) { cfg, ts, r := c.Config.ToIgn3_4Unvalidated(options) if r.IsFatal() { return result.MachineConfig{}, ts, r @@ -94,12 +94,12 @@ func (c Config) ToMachineConfig4_13Unvalidated(options common.TranslateOptions) return mc, ts, r } -// ToMachineConfig4_13 translates the config to a MachineConfig. It returns a +// ToMachineConfig4_14 translates the config to a MachineConfig. It returns a // report of any errors or warnings in the source and resultant config. If // the report has fatal errors or it encounters other problems translating, // an error is returned. -func (c Config) ToMachineConfig4_13(options common.TranslateOptions) (result.MachineConfig, report.Report, error) { - cfg, r, err := cutil.Translate(c, "ToMachineConfig4_13Unvalidated", options) +func (c Config) ToMachineConfig4_14(options common.TranslateOptions) (result.MachineConfig, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToMachineConfig4_14Unvalidated", options) return cfg.(result.MachineConfig), r, err } @@ -108,7 +108,7 @@ func (c Config) ToMachineConfig4_13(options common.TranslateOptions) (result.Mac // can be tracked back to their source in the source config. No config // validation is performed on input or output. func (c Config) ToIgn3_4Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { - mc, ts, r := c.ToMachineConfig4_13Unvalidated(options) + mc, ts, r := c.ToMachineConfig4_14Unvalidated(options) cfg := mc.Spec.Config // report warnings if there are any non-empty fields in Spec (other @@ -131,14 +131,14 @@ func (c Config) ToIgn3_4(options common.TranslateOptions) (types.Config, report. return cfg.(types.Config), r, err } -// ToConfigBytes translates from a v4.13 Butane config to a v4.13 MachineConfig or a v3.4.0 Ignition config. It returns a report of any errors or +// ToConfigBytes translates from a v4.14 Butane config to a v4.14 MachineConfig or a v3.4.0 Ignition config. It returns a report of any errors or // warnings in the source and resultant config. If the report has fatal errors or it encounters other problems // translating, an error is returned. func ToConfigBytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { if options.Raw { return cutil.TranslateBytes(input, &Config{}, "ToIgn3_4", options) } else { - return cutil.TranslateBytesYAML(input, &Config{}, "ToMachineConfig4_13", options) + return cutil.TranslateBytesYAML(input, &Config{}, "ToMachineConfig4_14", options) } } diff --git a/config/openshift/v4_14_exp/translate_test.go b/config/openshift/v4_14_exp/translate_test.go index 77ea7da4..cbc6932a 100644 --- a/config/openshift/v4_14_exp/translate_test.go +++ b/config/openshift/v4_14_exp/translate_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_14_exp import ( "fmt" @@ -22,7 +22,7 @@ import ( base "github.com/coreos/butane/base/v0_5_exp" "github.com/coreos/butane/config/common" fcos "github.com/coreos/butane/config/fcos/v1_5_exp" - "github.com/coreos/butane/config/openshift/v4_13_exp/result" + "github.com/coreos/butane/config/openshift/v4_14_exp/result" "github.com/coreos/butane/translate" "github.com/coreos/ignition/v2/config/util" @@ -358,7 +358,7 @@ func TestTranslateConfig(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { - actual, translations, r := test.in.ToMachineConfig4_13Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.in.ToMachineConfig4_14Unvalidated(common.TranslateOptions{}) assert.Equal(t, test.out, actual, "translation mismatch") assert.Equal(t, report.Report{}, r, "non-empty report") baseutil.VerifyTranslations(t, translations, test.exceptions) @@ -569,7 +569,7 @@ func TestValidateSupport(t *testing.T) { for _, entry := range test.entries { expectedReport.AddOn(entry.path, entry.err, entry.kind) } - actual, translations, r := test.in.ToMachineConfig4_13Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.in.ToMachineConfig4_14Unvalidated(common.TranslateOptions{}) assert.Equal(t, expectedReport, r, "report mismatch") assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") }) diff --git a/config/openshift/v4_14_exp/validate.go b/config/openshift/v4_14_exp/validate.go index 74f0955d..09cc1673 100644 --- a/config/openshift/v4_14_exp/validate.go +++ b/config/openshift/v4_14_exp/validate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_14_exp import ( "github.com/coreos/butane/config/common" diff --git a/config/openshift/v4_14_exp/validate_test.go b/config/openshift/v4_14_exp/validate_test.go index 7e293283..89101d71 100644 --- a/config/openshift/v4_14_exp/validate_test.go +++ b/config/openshift/v4_14_exp/validate_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v4_13_exp +package v4_14_exp import ( "fmt" diff --git a/docs/config-openshift-v4_13.md b/docs/config-openshift-v4_13.md index 712a6c83..f416e4ef 100644 --- a/docs/config-openshift-v4_13.md +++ b/docs/config-openshift-v4_13.md @@ -1,24 +1,22 @@ --- -title: OpenShift v4.13.0-experimental +title: OpenShift v4.13.0 parent: Configuration specifications -nav_order: 150 +nav_order: 144 --- -# OpenShift Specification v4.13.0-experimental - -**Note: This configuration is experimental and has not been stabilized. It is subject to change without warning or announcement.** +# OpenShift Specification v4.13.0 The OpenShift configuration is a YAML document conforming to the following specification, with **_italicized_** entries being optional: * **variant** (string): used to differentiate configs for different operating systems. Must be `openshift` for this specification. -* **version** (string): the semantic version of the spec for this document. This document is for version `4.13.0-experimental` and generates Ignition configs with version `3.4.0`. +* **version** (string): the semantic version of the spec for this document. This document is for version `4.13.0` and generates Ignition configs with version `3.2.0`. * **metadata** (object): metadata about the generated MachineConfig resource. Respected when rendering to a MachineConfig, ignored when rendering directly to an Ignition config. * **name** (string): a unique [name][k8s-names] for this MachineConfig resource. * **labels** (object): string key/value pairs to apply as [Kubernetes labels][k8s-labels] to this MachineConfig resource. `machineconfiguration.openshift.io/role` is required. * **_ignition_** (object): metadata about the configuration itself. * **_config_** (objects): options related to the configuration. * **_merge_** (list of objects): a list of the configs to be merged to the current config. - * **_source_** (string): the URL of the config. Supported schemes are `http`, `https`, `s3`, `arn`, `gs`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. + * **_source_** (string): the URL of the config. Supported schemes are `http`, `https`, `s3`, `gs`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. * **_inline_** (string): the contents of the config. Mutually exclusive with `source` and `local`. * **_local_** (string): a local path to the contents of the config, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `source` and `inline`. * **_compression_** (string): the type of compression used on the config (null or gzip). Compression cannot be used with S3. @@ -28,7 +26,7 @@ The OpenShift configuration is a YAML document conforming to the following speci * **_verification_** (object): options related to the verification of the config. * **_hash_** (string): the hash of the config, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed config. * **_replace_** (object): the config that will replace the current. - * **_source_** (string): the URL of the config. Supported schemes are `http`, `https`, `s3`, `arn`, `gs`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. + * **_source_** (string): the URL of the config. Supported schemes are `http`, `https`, `s3`, `gs`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. * **_inline_** (string): the contents of the config. Mutually exclusive with `source` and `local`. * **_local_** (string): a local path to the contents of the config, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `source` and `inline`. * **_compression_** (string): the type of compression used on the config (null or gzip). Compression cannot be used with S3. @@ -43,7 +41,7 @@ The OpenShift configuration is a YAML document conforming to the following speci * **_security_** (object): options relating to network security. * **_tls_** (object): options relating to TLS when fetching resources over `https`. * **_certificate_authorities_** (list of objects): the list of additional certificate authorities (in addition to the system authorities) to be used for TLS verification when fetching over `https`. All certificate authorities must have a unique `source`, `inline`, or `local`. - * **_source_** (string): the URL of the certificate bundle (in PEM format). With Ignition ≥ 2.4.0, the bundle can contain multiple concatenated certificates. Supported schemes are `http`, `https`, `s3`, `arn`, `gs`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. + * **_source_** (string): the URL of the certificate bundle (in PEM format). With Ignition ≥ 2.4.0, the bundle can contain multiple concatenated certificates. Supported schemes are `http`, `https`, `s3`, `gs`, `tftp`, and [`data`][rfc2397]. Note: When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. * **_inline_** (string): the contents of the certificate bundle (in PEM format). With Ignition ≥ 2.4.0, the bundle can contain multiple concatenated certificates. Mutually exclusive with `source` and `local`. * **_local_** (string): a local path to the contents of the certificate bundle (in PEM format), relative to the directory specified by the `--files-dir` command-line argument. With Ignition ≥ 2.4.0, the bundle can contain multiple concatenated certificates. Mutually exclusive with `source` and `inline`. * **_compression_** (string): the type of compression used on the certificate (null or gzip). Compression cannot be used with S3. @@ -108,7 +106,7 @@ The OpenShift configuration is a YAML document conforming to the following speci * **device** (string): the absolute path to the device. Devices are typically referenced by the `/dev/disk/by-*` symlinks. * **_key_file_** (string): options related to the contents of the key file. * **_compression_** (string): the type of compression used on the contents (null or gzip). Compression cannot be used with S3. - * **_source_** (string): the URL of the key file contents. Supported schemes are `http`, `https`, `tftp`, `s3`, `arn`, `gs`, and [`data`][rfc2397]. When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. + * **_source_** (string): the URL of the key file contents. Supported schemes are `http`, `https`, `tftp`, `s3`, `gs`, and [`data`][rfc2397]. When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. Mutually exclusive with `inline` and `local`. * **_inline_** (string): the contents of the key file. Mutually exclusive with `source` and `local`. * **_local_** (string): a local path to the contents of the key file, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `source` and `inline`. * **_http_headers_** (list of objects): a list of HTTP headers to be added to the request. Available for `http` and `https` source schemes only. @@ -119,14 +117,11 @@ The OpenShift configuration is a YAML document conforming to the following speci * **_label_** (string): the label of the luks device. * **_uuid_** (string): the uuid of the luks device. * **_options_** (list of strings): any additional options to be passed to `cryptsetup luksFormat`. - * **_discard_** (boolean): whether to issue discard commands to the underlying block device when blocks are freed. Enabling this improves performance and device longevity on SSDs and space utilization on thinly provisioned SAN devices, but leaks information about which disk blocks contain data. If omitted, it defaults to false. - * **_open_options_** (list of strings): any additional options to be passed to `cryptsetup luksOpen`. Supported options will be persistently written to the luks volume. * **_wipe_volume_** (boolean): whether or not to wipe the device before volume creation, see [the Ignition documentation on filesystems](https://coreos.github.io/ignition/operator-notes/#filesystem-reuse-semantics) for more information. * **_clevis_** (object): describes the clevis configuration for the luks device. * **_tang_** (list of objects): describes a tang server. Every server must have a unique `url`. * **url** (string): url of the tang server. * **thumbprint** (string): thumbprint of a trusted signing key. - * **advertisement** (string): the advertisement JSON. If not specified, the advertisement is fetched from the tang server during provisioning. * **_tpm2_** (bool): whether or not to use a tpm2 device. * **_threshold_** (int): sets the minimum number of pieces required to decrypt the device. Default is 1. * **_custom_** (object): overrides the clevis configuration. The `pin` & `config` will be passed directly to `clevis luks bind`. If specified, all other clevis options must be omitted. @@ -149,17 +144,15 @@ The OpenShift configuration is a YAML document conforming to the following speci * **_users_** (list of objects): the list of accounts that shall exist. All users must have a unique `name`. * **name** (string): the username for the account. Must be `core`. * **_password_hash_** (string): the hashed password for the account. - * **_ssh_authorized_keys_** (list of strings): a list of SSH keys to be added to `.ssh/authorized_keys` in the user's home directory. All SSH keys must be unique. + * **_ssh_authorized_keys_** (list of strings): a list of SSH keys to be added to `.ssh/authorized_keys.d/ignition` in the user's home directory. All SSH keys must be unique. * **_boot_device_** (object): describes the desired boot device configuration. At least one of `luks` or `mirror` must be specified. * **_layout_** (string): the disk layout of the target OS image. Supported values are `aarch64`, `ppc64le`, and `x86_64`. Defaults to `x86_64`. * **_luks_** (object): describes the clevis configuration for encrypting the root filesystem. * **_tang_** (list of objects): describes a tang server. Every server must have a unique `url`. * **url** (string): url of the tang server. * **thumbprint** (string): thumbprint of a trusted signing key. - * **advertisement** (string): the advertisement JSON. If not specified, the advertisement is fetched from the tang server during provisioning. * **_tpm2_** (bool): whether or not to use a tpm2 device. * **_threshold_** (int): sets the minimum number of pieces required to decrypt the device. Default is 1. - * **_discard_** (boolean): whether to issue discard commands to the underlying block device when blocks are freed. Enabling this improves performance and device longevity on SSDs and space utilization on thinly provisioned SAN devices, but leaks information about which disk blocks contain data. If omitted, it defaults to false. * **_mirror_** (object): describes mirroring of the boot disk for fault tolerance. * **_devices_** (list of strings): the list of whole-disk devices (not partitions) to include in the disk array, referenced by their absolute path. At least two devices must be specified. * **_openshift_** (object): describes miscellaneous OpenShift configuration. Respected when rendering to a MachineConfig, ignored when rendering directly to an Ignition config. diff --git a/docs/config-openshift-v4_14-exp.md b/docs/config-openshift-v4_14-exp.md index 4ed6a100..128d8dbb 100644 --- a/docs/config-openshift-v4_14-exp.md +++ b/docs/config-openshift-v4_14-exp.md @@ -1,17 +1,17 @@ --- -title: OpenShift v4.13.0-experimental +title: OpenShift v4.14.0-experimental parent: Configuration specifications nav_order: 150 --- -# OpenShift Specification v4.13.0-experimental +# OpenShift Specification v4.14.0-experimental **Note: This configuration is experimental and has not been stabilized. It is subject to change without warning or announcement.** The OpenShift configuration is a YAML document conforming to the following specification, with **_italicized_** entries being optional: * **variant** (string): used to differentiate configs for different operating systems. Must be `openshift` for this specification. -* **version** (string): the semantic version of the spec for this document. This document is for version `4.13.0-experimental` and generates Ignition configs with version `3.4.0`. +* **version** (string): the semantic version of the spec for this document. This document is for version `4.14.0-experimental` and generates Ignition configs with version `3.4.0`. * **metadata** (object): metadata about the generated MachineConfig resource. Respected when rendering to a MachineConfig, ignored when rendering directly to an Ignition config. * **name** (string): a unique [name][k8s-names] for this MachineConfig resource. * **labels** (object): string key/value pairs to apply as [Kubernetes labels][k8s-labels] to this MachineConfig resource. `machineconfiguration.openshift.io/role` is required. diff --git a/docs/release-notes.md b/docs/release-notes.md index 558350eb..98021906 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -12,12 +12,15 @@ nav_order: 9 ### Features +- Stabilize OpenShift spec 4.13.0, targeting Ignition spec 3.2.0 +- Add OpenShift spec 4.14.0-experimental, targeting Ignition spec + 3.4.0 - Allow enabling discard passthrough on LUKS devices _(fcos 1.5.0-exp, - flatcar 1.1.0-exp, openshift 4.13.0-exp)_ + flatcar 1.1.0-exp, openshift 4.14.0-exp)_ - Allow specifying arbitrary LUKS open options _(fcos 1.5.0-exp, - flatcar 1.1.0-exp, openshift 4.13.0-exp)_ -- Allow specifying user password hash _(openshift 4.13.0-exp)_ -- Support offline Tang provisioning via pre-shared advertisement _(fcos 1.5.0-exp, openshift 4.13.0-exp)_ + flatcar 1.1.0-exp, openshift 4.14.0-exp)_ +- Allow specifying user password hash _(openshift 4.13.0+)_ +- Support offline Tang provisioning via pre-shared advertisement _(fcos 1.5.0-exp, openshift 4.14.0-exp)_ ### Bug fixes @@ -26,9 +29,10 @@ nav_order: 9 ### Misc. changes -- Drop `extensions` section _(fcos 1.5.0-exp, openshift 4.13.0-exp)_ +- Roll back to Ignition spec 3.2.0 _(openshift 4.13.0)_ +- Drop `extensions` section _(fcos 1.5.0-exp, openshift 4.13.0+)_ - Drop `LuksOption` and `RaidOption` types _(Go API for fcos 1.5.0-experimental, - flatcar 1.1.0-experimental, openshift 4.13.0-experimental)_ + flatcar 1.1.0-experimental, openshift 4.14.0-experimental)_ - Require Go 1.18+ ### Docs changes diff --git a/docs/specs.md b/docs/specs.md index eb626dfe..ba12cd3c 100644 --- a/docs/specs.md +++ b/docs/specs.md @@ -23,6 +23,7 @@ We recommend that you always use the latest **stable** specification for your op - Flatcar (`flatcar`) - [v1.0.0](config-flatcar-v1_0.md) - OpenShift (`openshift`) + - [v4.13.0](config-openshift-v4_13.md) - [v4.12.0](config-openshift-v4_12.md) - [v4.11.0](config-openshift-v4_11.md) - [v4.10.0](config-openshift-v4_10.md) @@ -40,7 +41,7 @@ Do not use **experimental** specifications for anything beyond **development and - Flatcar (`flatcar`) - [v1.1.0-experimental](config-flatcar-v1_1-exp.md) - OpenShift (`openshift`) - - [v4.13.0-experimental](config-openshift-v4_13-exp.md) + - [v4.14.0-experimental](config-openshift-v4_14-exp.md) - RHEL for Edge (`r4e`) - [v1.1.0-experimental](config-r4e-v1_1-exp.md) @@ -63,6 +64,7 @@ Each version of the Butane specification corresponds to a version of the Ignitio | `openshift` | 4.10.0 | 3.2.0 | | `openshift` | 4.11.0 | 3.2.0 | | `openshift` | 4.12.0 | 3.2.0 | -| `openshift` | 4.13.0-experimental | 3.4.0 | +| `openshift` | 4.13.0 | 3.2.0 | +| `openshift` | 4.14.0-experimental | 3.4.0 | | `r4e` | 1.0.0 | 3.3.0 | | `r4e` | 1.1.0-experimental | 3.4.0 | diff --git a/docs/upgrading-openshift.md b/docs/upgrading-openshift.md index 395dd924..55bd796c 100644 --- a/docs/upgrading-openshift.md +++ b/docs/upgrading-openshift.md @@ -13,6 +13,10 @@ Occasionally, changes are made to OpenShift Butane configs (those that specify ` 1. TOC {:toc} +## From Version 4.12.0 to 4.13.0 + +There are no breaking changes between versions 4.12.0 and 4.13.0 of the `openshift` configuration specification. Any valid 4.12.0 configuration can be updated to a 4.13.0 configuration by changing the version string in the config. + ## From Version 4.11.0 to 4.12.0 There are no breaking changes between versions 4.11.0 and 4.12.0 of the `openshift` configuration specification. Any valid 4.11.0 configuration can be updated to a 4.12.0 configuration by changing the version string in the config.