From 66c2a31e29d30da5ced85bfeda1efacc01209d4d Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:15:18 -0500 Subject: [PATCH 01/13] base: copy v0_6_exp to v0_7_exp --- base/v0_7_exp/schema.go | 267 ++++ base/v0_7_exp/translate.go | 501 +++++++ base/v0_7_exp/translate_test.go | 2364 +++++++++++++++++++++++++++++++ base/v0_7_exp/util.go | 125 ++ base/v0_7_exp/validate.go | 92 ++ base/v0_7_exp/validate_test.go | 389 +++++ 6 files changed, 3738 insertions(+) create mode 100644 base/v0_7_exp/schema.go create mode 100644 base/v0_7_exp/translate.go create mode 100644 base/v0_7_exp/translate_test.go create mode 100644 base/v0_7_exp/util.go create mode 100644 base/v0_7_exp/validate.go create mode 100644 base/v0_7_exp/validate_test.go diff --git a/base/v0_7_exp/schema.go b/base/v0_7_exp/schema.go new file mode 100644 index 00000000..1104198f --- /dev/null +++ b/base/v0_7_exp/schema.go @@ -0,0 +1,267 @@ +// 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 v0_6_exp + +type Cex struct { + Enabled *bool `yaml:"enabled"` +} + +type Clevis struct { + Custom ClevisCustom `yaml:"custom"` + Tang []Tang `yaml:"tang"` + Threshold *int `yaml:"threshold"` + Tpm2 *bool `yaml:"tpm2"` +} + +type ClevisCustom struct { + Config *string `yaml:"config"` + NeedsNetwork *bool `yaml:"needs_network"` + Pin *string `yaml:"pin"` +} + +type Config struct { + Version string `yaml:"version"` + Variant string `yaml:"variant"` + Ignition Ignition `yaml:"ignition"` + KernelArguments KernelArguments `yaml:"kernel_arguments"` + Passwd Passwd `yaml:"passwd"` + Storage Storage `yaml:"storage"` + Systemd Systemd `yaml:"systemd"` +} + +type Device string + +type Directory struct { + Group NodeGroup `yaml:"group"` + Overwrite *bool `yaml:"overwrite"` + Path string `yaml:"path"` + User NodeUser `yaml:"user"` + Mode *int `yaml:"mode"` +} + +type Disk struct { + Device string `yaml:"device"` + Partitions []Partition `yaml:"partitions"` + WipeTable *bool `yaml:"wipe_table"` +} + +type Dropin struct { + Contents *string `yaml:"contents"` + ContentsLocal *string `yaml:"contents_local"` + Name string `yaml:"name"` +} + +type File struct { + Group NodeGroup `yaml:"group"` + Overwrite *bool `yaml:"overwrite"` + Path string `yaml:"path"` + User NodeUser `yaml:"user"` + Append []Resource `yaml:"append"` + Contents Resource `yaml:"contents"` + Mode *int `yaml:"mode"` +} + +type Filesystem struct { + Device string `yaml:"device"` + Format *string `yaml:"format"` + Label *string `yaml:"label"` + MountOptions []string `yaml:"mount_options"` + Options []string `yaml:"options"` + Path *string `yaml:"path"` + UUID *string `yaml:"uuid"` + WipeFilesystem *bool `yaml:"wipe_filesystem"` + WithMountUnit *bool `yaml:"with_mount_unit" butane:"auto_skip"` // Added, not in Ignition spec +} + +type Group string + +type HTTPHeader struct { + Name string `yaml:"name"` + Value *string `yaml:"value"` +} + +type HTTPHeaders []HTTPHeader + +type Ignition struct { + Config IgnitionConfig `yaml:"config"` + Proxy Proxy `yaml:"proxy"` + Security Security `yaml:"security"` + Timeouts Timeouts `yaml:"timeouts"` +} + +type IgnitionConfig struct { + Merge []Resource `yaml:"merge"` + Replace Resource `yaml:"replace"` +} + +type KernelArgument string + +type KernelArguments struct { + ShouldExist []KernelArgument `yaml:"should_exist"` + ShouldNotExist []KernelArgument `yaml:"should_not_exist"` +} + +type Link struct { + Group NodeGroup `yaml:"group"` + Overwrite *bool `yaml:"overwrite"` + Path string `yaml:"path"` + User NodeUser `yaml:"user"` + Hard *bool `yaml:"hard"` + Target *string `yaml:"target"` +} + +type Luks struct { + Cex Cex `yaml:"cex"` + Clevis Clevis `yaml:"clevis"` + Device *string `yaml:"device"` + Discard *bool `yaml:"discard"` + KeyFile Resource `yaml:"key_file"` + Label *string `yaml:"label"` + Name string `yaml:"name"` + OpenOptions []string `yaml:"open_options"` + Options []string `yaml:"options"` + UUID *string `yaml:"uuid"` + WipeVolume *bool `yaml:"wipe_volume"` +} + +type NodeGroup struct { + ID *int `yaml:"id"` + Name *string `yaml:"name"` +} + +type NodeUser struct { + ID *int `yaml:"id"` + Name *string `yaml:"name"` +} + +type Partition struct { + GUID *string `yaml:"guid"` + Label *string `yaml:"label"` + Number int `yaml:"number"` + Resize *bool `yaml:"resize"` + ShouldExist *bool `yaml:"should_exist"` + SizeMiB *int `yaml:"size_mib"` + StartMiB *int `yaml:"start_mib"` + TypeGUID *string `yaml:"type_guid"` + WipePartitionEntry *bool `yaml:"wipe_partition_entry"` +} + +type Passwd struct { + Groups []PasswdGroup `yaml:"groups"` + Users []PasswdUser `yaml:"users"` +} + +type PasswdGroup struct { + Gid *int `yaml:"gid"` + Name string `yaml:"name"` + PasswordHash *string `yaml:"password_hash"` + ShouldExist *bool `yaml:"should_exist"` + System *bool `yaml:"system"` +} + +type PasswdUser struct { + Gecos *string `yaml:"gecos"` + Groups []Group `yaml:"groups"` + HomeDir *string `yaml:"home_dir"` + Name string `yaml:"name"` + NoCreateHome *bool `yaml:"no_create_home"` + NoLogInit *bool `yaml:"no_log_init"` + NoUserGroup *bool `yaml:"no_user_group"` + PasswordHash *string `yaml:"password_hash"` + PrimaryGroup *string `yaml:"primary_group"` + ShouldExist *bool `yaml:"should_exist"` + SSHAuthorizedKeys []SSHAuthorizedKey `yaml:"ssh_authorized_keys"` + SSHAuthorizedKeysLocal []string `yaml:"ssh_authorized_keys_local"` + Shell *string `yaml:"shell"` + System *bool `yaml:"system"` + UID *int `yaml:"uid"` +} + +type Proxy struct { + HTTPProxy *string `yaml:"http_proxy"` + HTTPSProxy *string `yaml:"https_proxy"` + NoProxy []string `yaml:"no_proxy"` +} + +type Raid struct { + Devices []Device `yaml:"devices"` + Level *string `yaml:"level"` + Name string `yaml:"name"` + Options []string `yaml:"options"` + Spares *int `yaml:"spares"` +} + +type Resource struct { + Compression *string `yaml:"compression"` + HTTPHeaders HTTPHeaders `yaml:"http_headers"` + Source *string `yaml:"source"` + Inline *string `yaml:"inline"` // Added, not in ignition spec + Local *string `yaml:"local"` // Added, not in ignition spec + Verification Verification `yaml:"verification"` +} + +type SSHAuthorizedKey string + +type Security struct { + TLS TLS `yaml:"tls"` +} + +type Storage struct { + Directories []Directory `yaml:"directories"` + Disks []Disk `yaml:"disks"` + Files []File `yaml:"files"` + Filesystems []Filesystem `yaml:"filesystems"` + Links []Link `yaml:"links"` + Luks []Luks `yaml:"luks"` + Raid []Raid `yaml:"raid"` + Trees []Tree `yaml:"trees" butane:"auto_skip"` // Added, not in ignition spec +} + +type Systemd struct { + Units []Unit `yaml:"units"` +} + +type Tang struct { + Thumbprint *string `yaml:"thumbprint"` + URL string `yaml:"url"` + Advertisement *string `yaml:"advertisement"` +} + +type TLS struct { + CertificateAuthorities []Resource `yaml:"certificate_authorities"` +} + +type Timeouts struct { + HTTPResponseHeaders *int `yaml:"http_response_headers"` + HTTPTotal *int `yaml:"http_total"` +} + +type Tree struct { + Local string `yaml:"local"` + Path *string `yaml:"path"` +} + +type Unit struct { + Contents *string `yaml:"contents"` + ContentsLocal *string `yaml:"contents_local"` + Dropins []Dropin `yaml:"dropins"` + Enabled *bool `yaml:"enabled"` + Mask *bool `yaml:"mask"` + Name string `yaml:"name"` +} + +type Verification struct { + Hash *string `yaml:"hash"` +} diff --git a/base/v0_7_exp/translate.go b/base/v0_7_exp/translate.go new file mode 100644 index 00000000..b17f5a52 --- /dev/null +++ b/base/v0_7_exp/translate.go @@ -0,0 +1,501 @@ +// 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 v0_6_exp + +import ( + "fmt" + "os" + slashpath "path" + "path/filepath" + "regexp" + "strings" + "text/template" + + baseutil "github.com/coreos/butane/base/util" + "github.com/coreos/butane/config/common" + "github.com/coreos/butane/translate" + + "github.com/coreos/go-systemd/v22/unit" + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +var ( + mountUnitTemplate = template.Must(template.New("unit").Parse(` +{{- define "options" }} + {{- if or .MountOptions .Remote }} +Options= + {{- range $i, $opt := .MountOptions }} + {{- if $i }},{{ end }} + {{- $opt }} + {{- end }} + {{- if .Remote }}{{ if .MountOptions }},{{ end }}_netdev{{ end }} + {{- end }} +{{- end -}} + +# Generated by Butane +{{- if .Swap }} +[Swap] +What={{.Device}} +{{- template "options" . }} + +[Install] +RequiredBy=swap.target +{{- else }} +[Unit] +Requires=systemd-fsck@{{.EscapedDevice}}.service +After=systemd-fsck@{{.EscapedDevice}}.service + +[Mount] +Where={{.Path}} +What={{.Device}} +Type={{.Format}} +{{- template "options" . }} + +[Install] +{{- if .Remote }} +RequiredBy=remote-fs.target +{{- else }} +RequiredBy=local-fs.target +{{- end }} +{{- end }}`)) +) + +// ToIgn3_5Unvalidated 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_5Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { + ret := types.Config{} + + tr := translate.NewTranslator("yaml", "json", options) + tr.AddCustomTranslator(translateIgnition) + tr.AddCustomTranslator(translateFile) + tr.AddCustomTranslator(translateDirectory) + tr.AddCustomTranslator(translateLink) + tr.AddCustomTranslator(translateResource) + tr.AddCustomTranslator(translatePasswdUser) + tr.AddCustomTranslator(translateUnit) + + tm, r := translate.Prefixed(tr, "ignition", &c.Ignition, &ret.Ignition) + tm.AddTranslation(path.New("yaml", "version"), path.New("json", "ignition", "version")) + tm.AddTranslation(path.New("yaml", "ignition"), path.New("json", "ignition")) + translate.MergeP2(tr, tm, &r, "kernel_arguments", &c.KernelArguments, "kernelArguments", &ret.KernelArguments) + translate.MergeP(tr, tm, &r, "passwd", &c.Passwd, &ret.Passwd) + translate.MergeP(tr, tm, &r, "storage", &c.Storage, &ret.Storage) + translate.MergeP(tr, tm, &r, "systemd", &c.Systemd, &ret.Systemd) + + c.addMountUnits(&ret, &tm) + + tm2, r2 := c.processTrees(&ret, options) + tm.Merge(tm2) + r.Merge(r2) + + if r.IsFatal() { + return types.Config{}, translate.TranslationSet{}, r + } + return ret, tm, r +} + +func translateIgnition(from Ignition, options common.TranslateOptions) (to types.Ignition, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + tr.AddCustomTranslator(translateResource) + to.Version = types.MaxVersion.String() + tm, r = translate.Prefixed(tr, "config", &from.Config, &to.Config) + translate.MergeP(tr, tm, &r, "proxy", &from.Proxy, &to.Proxy) + translate.MergeP(tr, tm, &r, "security", &from.Security, &to.Security) + translate.MergeP(tr, tm, &r, "timeouts", &from.Timeouts, &to.Timeouts) + return +} + +func translateFile(from File, options common.TranslateOptions) (to types.File, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + tr.AddCustomTranslator(translateResource) + tm, r = translate.Prefixed(tr, "group", &from.Group, &to.Group) + translate.MergeP(tr, tm, &r, "user", &from.User, &to.User) + translate.MergeP(tr, tm, &r, "append", &from.Append, &to.Append) + translate.MergeP(tr, tm, &r, "contents", &from.Contents, &to.Contents) + translate.MergeP(tr, tm, &r, "overwrite", &from.Overwrite, &to.Overwrite) + translate.MergeP(tr, tm, &r, "path", &from.Path, &to.Path) + translate.MergeP(tr, tm, &r, "mode", &from.Mode, &to.Mode) + return +} + +func translateResource(from Resource, options common.TranslateOptions) (to types.Resource, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + tm, r = translate.Prefixed(tr, "verification", &from.Verification, &to.Verification) + translate.MergeP2(tr, tm, &r, "http_headers", &from.HTTPHeaders, "httpHeaders", &to.HTTPHeaders) + translate.MergeP(tr, tm, &r, "source", &from.Source, &to.Source) + translate.MergeP(tr, tm, &r, "compression", &from.Compression, &to.Compression) + + if from.Local != nil { + c := path.New("yaml", "local") + contents, err := baseutil.ReadLocalFile(*from.Local, options.FilesDir) + if err != nil { + r.AddOnError(c, err) + return + } + src, compression, err := baseutil.MakeDataURL(contents, to.Compression, !options.NoResourceAutoCompression) + if err != nil { + r.AddOnError(c, err) + return + } + to.Source = &src + tm.AddTranslation(c, path.New("json", "source")) + if compression != nil { + to.Compression = compression + tm.AddTranslation(c, path.New("json", "compression")) + } + } + + if from.Inline != nil { + c := path.New("yaml", "inline") + + src, compression, err := baseutil.MakeDataURL([]byte(*from.Inline), to.Compression, !options.NoResourceAutoCompression) + if err != nil { + r.AddOnError(c, err) + return + } + to.Source = &src + tm.AddTranslation(c, path.New("json", "source")) + if compression != nil { + to.Compression = compression + tm.AddTranslation(c, path.New("json", "compression")) + } + } + return +} + +func translateDirectory(from Directory, options common.TranslateOptions) (to types.Directory, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + tm, r = translate.Prefixed(tr, "group", &from.Group, &to.Group) + translate.MergeP(tr, tm, &r, "user", &from.User, &to.User) + translate.MergeP(tr, tm, &r, "overwrite", &from.Overwrite, &to.Overwrite) + translate.MergeP(tr, tm, &r, "path", &from.Path, &to.Path) + translate.MergeP(tr, tm, &r, "mode", &from.Mode, &to.Mode) + return +} + +func translateLink(from Link, options common.TranslateOptions) (to types.Link, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + tm, r = translate.Prefixed(tr, "group", &from.Group, &to.Group) + translate.MergeP(tr, tm, &r, "user", &from.User, &to.User) + translate.MergeP(tr, tm, &r, "target", &from.Target, &to.Target) + translate.MergeP(tr, tm, &r, "hard", &from.Hard, &to.Hard) + translate.MergeP(tr, tm, &r, "overwrite", &from.Overwrite, &to.Overwrite) + translate.MergeP(tr, tm, &r, "path", &from.Path, &to.Path) + return +} + +func translatePasswdUser(from PasswdUser, options common.TranslateOptions) (to types.PasswdUser, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + tm, r = translate.Prefixed(tr, "gecos", &from.Gecos, &to.Gecos) + translate.MergeP(tr, tm, &r, "groups", &from.Groups, &to.Groups) + translate.MergeP2(tr, tm, &r, "home_dir", &from.HomeDir, "homeDir", &to.HomeDir) + translate.MergeP(tr, tm, &r, "name", &from.Name, &to.Name) + translate.MergeP2(tr, tm, &r, "no_create_home", &from.NoCreateHome, "noCreateHome", &to.NoCreateHome) + translate.MergeP2(tr, tm, &r, "no_log_init", &from.NoLogInit, "noLogInit", &to.NoLogInit) + translate.MergeP2(tr, tm, &r, "no_user_group", &from.NoUserGroup, "noUserGroup", &to.NoUserGroup) + translate.MergeP2(tr, tm, &r, "password_hash", &from.PasswordHash, "passwordHash", &to.PasswordHash) + translate.MergeP2(tr, tm, &r, "primary_group", &from.PrimaryGroup, "primaryGroup", &to.PrimaryGroup) + translate.MergeP(tr, tm, &r, "shell", &from.Shell, &to.Shell) + translate.MergeP2(tr, tm, &r, "should_exist", &from.ShouldExist, "shouldExist", &to.ShouldExist) + translate.MergeP2(tr, tm, &r, "ssh_authorized_keys", &from.SSHAuthorizedKeys, "sshAuthorizedKeys", &to.SSHAuthorizedKeys) + translate.MergeP(tr, tm, &r, "system", &from.System, &to.System) + translate.MergeP(tr, tm, &r, "uid", &from.UID, &to.UID) + + if len(from.SSHAuthorizedKeysLocal) > 0 { + c := path.New("yaml", "ssh_authorized_keys_local") + tm.AddTranslation(c, path.New("json", "sshAuthorizedKeys")) + + if options.FilesDir == "" { + r.AddOnError(c, common.ErrNoFilesDir) + return + } + + for keyFileIndex, sshKeyFile := range from.SSHAuthorizedKeysLocal { + sshKeys, err := baseutil.ReadLocalFile(sshKeyFile, options.FilesDir) + if err != nil { + r.AddOnError(c.Append(keyFileIndex), err) + continue + } + for _, line := range regexp.MustCompile("\r?\n").Split(string(sshKeys), -1) { + if line == "" { + continue + } + tm.AddTranslation(c.Append(keyFileIndex), path.New("json", "sshAuthorizedKeys", len(to.SSHAuthorizedKeys))) + to.SSHAuthorizedKeys = append(to.SSHAuthorizedKeys, types.SSHAuthorizedKey(line)) + } + } + } + + return +} + +func translateUnit(from Unit, options common.TranslateOptions) (to types.Unit, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + tr.AddCustomTranslator(translateDropin) + tm, r = translate.Prefixed(tr, "contents", &from.Contents, &to.Contents) + translate.MergeP(tr, tm, &r, "dropins", &from.Dropins, &to.Dropins) + translate.MergeP(tr, tm, &r, "enabled", &from.Enabled, &to.Enabled) + translate.MergeP(tr, tm, &r, "mask", &from.Mask, &to.Mask) + translate.MergeP(tr, tm, &r, "name", &from.Name, &to.Name) + + if util.NotEmpty(from.ContentsLocal) { + c := path.New("yaml", "contents_local") + contents, err := baseutil.ReadLocalFile(*from.ContentsLocal, options.FilesDir) + if err != nil { + r.AddOnError(c, err) + return + } + tm.AddTranslation(c, path.New("json", "contents")) + to.Contents = util.StrToPtr(string(contents)) + } + + return +} + +func translateDropin(from Dropin, options common.TranslateOptions) (to types.Dropin, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + tm, r = translate.Prefixed(tr, "contents", &from.Contents, &to.Contents) + translate.MergeP(tr, tm, &r, "name", &from.Name, &to.Name) + + if util.NotEmpty(from.ContentsLocal) { + c := path.New("yaml", "contents_local") + contents, err := baseutil.ReadLocalFile(*from.ContentsLocal, options.FilesDir) + if err != nil { + r.AddOnError(c, err) + return + } + tm.AddTranslation(c, path.New("json", "contents")) + to.Contents = util.StrToPtr(string(contents)) + } + + return +} + +func (c Config) processTrees(ret *types.Config, options common.TranslateOptions) (translate.TranslationSet, report.Report) { + ts := translate.NewTranslationSet("yaml", "json") + var r report.Report + if len(c.Storage.Trees) == 0 { + return ts, r + } + t := newNodeTracker(ret) + + for i, tree := range c.Storage.Trees { + yamlPath := path.New("yaml", "storage", "trees", i) + if options.FilesDir == "" { + r.AddOnError(yamlPath, common.ErrNoFilesDir) + return ts, r + } + + // calculate base path within FilesDir and check for + // path traversal + srcBaseDir := filepath.Join(options.FilesDir, filepath.FromSlash(tree.Local)) + if err := baseutil.EnsurePathWithinFilesDir(srcBaseDir, options.FilesDir); err != nil { + r.AddOnError(yamlPath, err) + continue + } + info, err := os.Stat(srcBaseDir) + if err != nil { + r.AddOnError(yamlPath, err) + continue + } + if !info.IsDir() { + r.AddOnError(yamlPath, common.ErrTreeNotDirectory) + continue + } + destBaseDir := "/" + if util.NotEmpty(tree.Path) { + destBaseDir = *tree.Path + } + + walkTree(yamlPath, &ts, &r, t, srcBaseDir, destBaseDir, options) + } + return ts, r +} + +func walkTree(yamlPath path.ContextPath, ts *translate.TranslationSet, r *report.Report, t *nodeTracker, srcBaseDir, destBaseDir string, options common.TranslateOptions) { + // The strategy for errors within WalkFunc is to add an error to + // the report and return nil, so walking continues but translation + // will fail afterward. + err := filepath.Walk(srcBaseDir, func(srcPath string, info os.FileInfo, err error) error { + if err != nil { + r.AddOnError(yamlPath, err) + return nil + } + relPath, err := filepath.Rel(srcBaseDir, srcPath) + if err != nil { + r.AddOnError(yamlPath, err) + return nil + } + destPath := slashpath.Join(destBaseDir, filepath.ToSlash(relPath)) + + if info.Mode().IsDir() { + return nil + } else if info.Mode().IsRegular() { + i, file := t.GetFile(destPath) + if file != nil { + if util.NotEmpty(file.Contents.Source) { + r.AddOnError(yamlPath, common.ErrNodeExists) + return nil + } + } else { + if t.Exists(destPath) { + r.AddOnError(yamlPath, common.ErrNodeExists) + return nil + } + i, file = t.AddFile(types.File{ + Node: types.Node{ + Path: destPath, + }, + }) + ts.AddFromCommonSource(yamlPath, path.New("json", "storage", "files", i), file) + if i == 0 { + ts.AddTranslation(yamlPath, path.New("json", "storage", "files")) + } + } + contents, err := os.ReadFile(srcPath) + if err != nil { + r.AddOnError(yamlPath, err) + return nil + } + url, compression, err := baseutil.MakeDataURL(contents, file.Contents.Compression, !options.NoResourceAutoCompression) + if err != nil { + r.AddOnError(yamlPath, err) + return nil + } + file.Contents.Source = &url + ts.AddTranslation(yamlPath, path.New("json", "storage", "files", i, "contents", "source")) + if compression != nil { + file.Contents.Compression = compression + ts.AddTranslation(yamlPath, path.New("json", "storage", "files", i, "contents", "compression")) + } + ts.AddTranslation(yamlPath, path.New("json", "storage", "files", i, "contents")) + if file.Mode == nil { + mode := 0644 + if info.Mode()&0111 != 0 { + mode = 0755 + } + file.Mode = &mode + ts.AddTranslation(yamlPath, path.New("json", "storage", "files", i, "mode")) + } + } else if info.Mode()&os.ModeType == os.ModeSymlink { + i, link := t.GetLink(destPath) + if link != nil { + if util.NotEmpty(link.Target) { + r.AddOnError(yamlPath, common.ErrNodeExists) + return nil + } + } else { + if t.Exists(destPath) { + r.AddOnError(yamlPath, common.ErrNodeExists) + return nil + } + i, link = t.AddLink(types.Link{ + Node: types.Node{ + Path: destPath, + }, + }) + ts.AddFromCommonSource(yamlPath, path.New("json", "storage", "links", i), link) + if i == 0 { + ts.AddTranslation(yamlPath, path.New("json", "storage", "links")) + } + } + target, err := os.Readlink(srcPath) + if err != nil { + r.AddOnError(yamlPath, err) + return nil + } + link.Target = util.StrToPtr(filepath.ToSlash(target)) + ts.AddTranslation(yamlPath, path.New("json", "storage", "links", i, "target")) + } else { + r.AddOnError(yamlPath, common.ErrFileType) + return nil + } + return nil + }) + r.AddOnError(yamlPath, err) +} + +func (c Config) addMountUnits(config *types.Config, ts *translate.TranslationSet) { + if len(c.Storage.Filesystems) == 0 { + return + } + var rendered types.Config + renderedTranslations := translate.NewTranslationSet("yaml", "json") + renderedTranslations.AddTranslation(path.New("yaml", "storage", "filesystems"), path.New("json", "systemd")) + renderedTranslations.AddTranslation(path.New("yaml", "storage", "filesystems"), path.New("json", "systemd", "units")) + for i, fs := range c.Storage.Filesystems { + if !util.IsTrue(fs.WithMountUnit) { + continue + } + fromPath := path.New("yaml", "storage", "filesystems", i, "with_mount_unit") + remote := false + // check filesystems targeting /dev/mapper devices against LUKS to determine if a + // remote mount is needed + if strings.HasPrefix(fs.Device, "/dev/mapper/") || strings.HasPrefix(fs.Device, "/dev/disk/by-id/dm-name-") { + for _, luks := range c.Storage.Luks { + // LUKS devices are opened with their name specified + if fs.Device == fmt.Sprintf("/dev/mapper/%s", luks.Name) || fs.Device == fmt.Sprintf("/dev/disk/by-id/dm-name-%s", luks.Name) { + if len(luks.Clevis.Tang) > 0 { + remote = true + break + } + } + } + } + newUnit := mountUnitFromFS(fs, remote) + unitPath := path.New("json", "systemd", "units", len(rendered.Systemd.Units)) + rendered.Systemd.Units = append(rendered.Systemd.Units, newUnit) + renderedTranslations.AddFromCommonSource(fromPath, unitPath, newUnit) + } + retConfig, retTranslations := baseutil.MergeTranslatedConfigs(rendered, renderedTranslations, *config, *ts) + *config = retConfig.(types.Config) + *ts = retTranslations +} + +func mountUnitFromFS(fs Filesystem, remote bool) types.Unit { + context := struct { + *Filesystem + EscapedDevice string + Remote bool + Swap bool + }{ + Filesystem: &fs, + EscapedDevice: unit.UnitNamePathEscape(fs.Device), + Remote: remote, + // unchecked deref of format ok, fs would fail validation otherwise + Swap: *fs.Format == "swap", + } + contents := strings.Builder{} + err := mountUnitTemplate.Execute(&contents, context) + if err != nil { + panic(err) + } + var unitName string + if context.Swap { + unitName = unit.UnitNamePathEscape(fs.Device) + ".swap" + } else { + // unchecked deref of path ok, fs would fail validation otherwise + unitName = unit.UnitNamePathEscape(*fs.Path) + ".mount" + } + return types.Unit{ + Name: unitName, + Enabled: util.BoolToPtr(true), + Contents: util.StrToPtr(contents.String()), + } +} diff --git a/base/v0_7_exp/translate_test.go b/base/v0_7_exp/translate_test.go new file mode 100644 index 00000000..8b549055 --- /dev/null +++ b/base/v0_7_exp/translate_test.go @@ -0,0 +1,2364 @@ +// 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 v0_6_exp + +import ( + "fmt" + "net" + "os" + "path/filepath" + "reflect" + "runtime" + "strings" + "testing" + + baseutil "github.com/coreos/butane/base/util" + "github.com/coreos/butane/config/common" + confutil "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_5/types" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" + "github.com/stretchr/testify/assert" +) + +// Most of this is covered by the Ignition translator generic tests, so just test the custom bits + +var ( + osStatName string + osNotFound string +) + +func init() { + if runtime.GOOS == "windows" { + osStatName = "CreateFile" + osNotFound = "The system cannot find the file specified." + } else { + osStatName = "stat" + osNotFound = "no such file or directory" + } +} + +// TestTranslateFile tests translating the ct storage.files.[i] entries to ignition storage.files.[i] entries. +func TestTranslateFile(t *testing.T) { + zzz := "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz" + zzz_gz := "data:;base64,H4sIAAAAAAAC/6oajAAQAAD//5tA8d+VAAAA" + random := "\xc0\x9cl\x01\x89i\xa5\xbfW\xe4\x1b\xf4J_\xb79P\xa3#\xa7" + random_b64 := "data:;base64,wJxsAYlppb9X5Bv0Sl+3OVCjI6c=" + + filesDir := t.TempDir() + fileContents := map[string]string{ + "file-1": "file contents\n", + "file-2": zzz, + "file-3": random, + "subdir/file-4": "subdir file contents\n", + } + for name, contents := range fileContents { + if err := os.MkdirAll(filepath.Join(filesDir, filepath.Dir(name)), 0755); err != nil { + t.Error(err) + return + } + err := os.WriteFile(filepath.Join(filesDir, name), []byte(contents), 0644) + if err != nil { + t.Error(err) + return + } + } + + tests := []struct { + in File + out types.File + exceptions []translate.Translation + report string + options common.TranslateOptions + }{ + { + File{}, + types.File{}, + nil, + "", + common.TranslateOptions{}, + }, + { + // contains invalid (by the validator's definition) combinations of fields, + // but the translator doesn't care and we can check they all get translated at once + File{ + Path: "/foo", + Group: NodeGroup{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("foobar"), + }, + User: NodeUser{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("bazquux"), + }, + Mode: util.IntToPtr(420), + Append: []Resource{ + { + Source: util.StrToPtr("http://example/com"), + Compression: util.StrToPtr("gzip"), + HTTPHeaders: HTTPHeaders{ + HTTPHeader{ + Name: "Header", + Value: util.StrToPtr("this isn't validated"), + }, + }, + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + { + Inline: util.StrToPtr("hello"), + Compression: util.StrToPtr("gzip"), + HTTPHeaders: HTTPHeaders{ + HTTPHeader{ + Name: "Header", + Value: util.StrToPtr("this isn't validated"), + }, + }, + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + { + Local: util.StrToPtr("file-1"), + }, + }, + Overwrite: util.BoolToPtr(true), + Contents: Resource{ + Source: util.StrToPtr("http://example/com"), + Compression: util.StrToPtr("gzip"), + HTTPHeaders: HTTPHeaders{ + HTTPHeader{ + Name: "Header", + Value: util.StrToPtr("this isn't validated"), + }, + }, + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + }, + types.File{ + Node: types.Node{ + Path: "/foo", + Group: types.NodeGroup{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("foobar"), + }, + User: types.NodeUser{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("bazquux"), + }, + Overwrite: util.BoolToPtr(true), + }, + FileEmbedded1: types.FileEmbedded1{ + Mode: util.IntToPtr(420), + Append: []types.Resource{ + { + Source: util.StrToPtr("http://example/com"), + Compression: util.StrToPtr("gzip"), + HTTPHeaders: types.HTTPHeaders{ + types.HTTPHeader{ + Name: "Header", + Value: util.StrToPtr("this isn't validated"), + }, + }, + Verification: types.Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + { + Source: util.StrToPtr("data:,hello"), + Compression: util.StrToPtr("gzip"), + HTTPHeaders: types.HTTPHeaders{ + types.HTTPHeader{ + Name: "Header", + Value: util.StrToPtr("this isn't validated"), + }, + }, + Verification: types.Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + { + Source: util.StrToPtr("data:,file%20contents%0A"), + Compression: util.StrToPtr(""), + }, + }, + Contents: types.Resource{ + Source: util.StrToPtr("http://example/com"), + Compression: util.StrToPtr("gzip"), + HTTPHeaders: types.HTTPHeaders{ + types.HTTPHeader{ + Name: "Header", + Value: util.StrToPtr("this isn't validated"), + }, + }, + Verification: types.Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + }, + }, + []translate.Translation{ + { + From: path.New("yaml", "append", 0, "http_headers"), + To: path.New("json", "append", 0, "httpHeaders"), + }, + { + From: path.New("yaml", "append", 0, "http_headers", 0), + To: path.New("json", "append", 0, "httpHeaders", 0), + }, + { + From: path.New("yaml", "append", 0, "http_headers", 0, "name"), + To: path.New("json", "append", 0, "httpHeaders", 0, "name"), + }, + { + From: path.New("yaml", "append", 0, "http_headers", 0, "value"), + To: path.New("json", "append", 0, "httpHeaders", 0, "value"), + }, + { + From: path.New("yaml", "append", 1, "inline"), + To: path.New("json", "append", 1, "source"), + }, + { + From: path.New("yaml", "append", 1, "http_headers"), + To: path.New("json", "append", 1, "httpHeaders"), + }, + { + From: path.New("yaml", "append", 1, "http_headers", 0), + To: path.New("json", "append", 1, "httpHeaders", 0), + }, + { + From: path.New("yaml", "append", 1, "http_headers", 0, "name"), + To: path.New("json", "append", 1, "httpHeaders", 0, "name"), + }, + { + From: path.New("yaml", "append", 1, "http_headers", 0, "value"), + To: path.New("json", "append", 1, "httpHeaders", 0, "value"), + }, + { + From: path.New("yaml", "append", 2, "local"), + To: path.New("json", "append", 2, "source"), + }, + { + From: path.New("yaml", "append", 2, "local"), + To: path.New("json", "append", 2, "compression"), + }, + { + From: path.New("yaml", "contents", "http_headers"), + To: path.New("json", "contents", "httpHeaders"), + }, + { + From: path.New("yaml", "contents", "http_headers", 0), + To: path.New("json", "contents", "httpHeaders", 0), + }, + { + From: path.New("yaml", "contents", "http_headers", 0, "name"), + To: path.New("json", "contents", "httpHeaders", 0, "name"), + }, + { + From: path.New("yaml", "contents", "http_headers", 0, "value"), + To: path.New("json", "contents", "httpHeaders", 0, "value"), + }, + }, + "", + common.TranslateOptions{ + FilesDir: filesDir, + }, + }, + // inline file contents + { + File{ + Path: "/foo", + Contents: Resource{ + // String is too short for auto gzip compression + Inline: util.StrToPtr("xyzzy"), + }, + }, + types.File{ + Node: types.Node{ + Path: "/foo", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,xyzzy"), + Compression: util.StrToPtr(""), + }, + }, + }, + []translate.Translation{ + { + From: path.New("yaml", "contents", "inline"), + To: path.New("json", "contents", "source"), + }, + { + From: path.New("yaml", "contents", "inline"), + To: path.New("json", "contents", "compression"), + }, + }, + "", + common.TranslateOptions{}, + }, + // local file contents + { + File{ + Path: "/foo", + Contents: Resource{ + Local: util.StrToPtr("file-1"), + }, + }, + types.File{ + Node: types.Node{ + Path: "/foo", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,file%20contents%0A"), + Compression: util.StrToPtr(""), + }, + }, + }, + []translate.Translation{ + { + From: path.New("yaml", "contents", "local"), + To: path.New("json", "contents", "source"), + }, + { + From: path.New("yaml", "contents", "local"), + To: path.New("json", "contents", "compression"), + }, + }, + "", + common.TranslateOptions{ + FilesDir: filesDir, + }, + }, + // local file in subdirectory + { + File{ + Path: "/foo", + Contents: Resource{ + Local: util.StrToPtr("subdir/file-4"), + }, + }, + types.File{ + Node: types.Node{ + Path: "/foo", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,subdir%20file%20contents%0A"), + Compression: util.StrToPtr(""), + }, + }, + }, + []translate.Translation{ + { + From: path.New("yaml", "contents", "local"), + To: path.New("json", "contents", "source"), + }, + { + From: path.New("yaml", "contents", "local"), + To: path.New("json", "contents", "compression"), + }, + }, + "", + common.TranslateOptions{ + FilesDir: filesDir, + }, + }, + // filesDir not specified + { + File{ + Path: "/foo", + Contents: Resource{ + Local: util.StrToPtr("file-1"), + }, + }, + types.File{ + Node: types.Node{ + Path: "/foo", + }, + }, + []translate.Translation{}, + "error at $.contents.local: " + common.ErrNoFilesDir.Error() + "\n", + common.TranslateOptions{}, + }, + // attempted directory traversal + { + File{ + Path: "/foo", + Contents: Resource{ + Local: util.StrToPtr("../file-1"), + }, + }, + types.File{ + Node: types.Node{ + Path: "/foo", + }, + }, + []translate.Translation{}, + "error at $.contents.local: " + common.ErrFilesDirEscape.Error() + "\n", + common.TranslateOptions{ + FilesDir: filesDir, + }, + }, + // attempted inclusion of nonexistent file + { + File{ + Path: "/foo", + Contents: Resource{ + Local: util.StrToPtr("file-missing"), + }, + }, + types.File{ + Node: types.Node{ + Path: "/foo", + }, + }, + []translate.Translation{}, + "error at $.contents.local: open " + filepath.Join(filesDir, "file-missing") + ": " + osNotFound + "\n", + common.TranslateOptions{ + FilesDir: filesDir, + }, + }, + // inline and local automatic file encoding + { + File{ + Path: "/foo", + Contents: Resource{ + // gzip + Inline: util.StrToPtr(zzz), + }, + Append: []Resource{ + { + // gzip + Local: util.StrToPtr("file-2"), + }, + { + // base64 + Inline: util.StrToPtr(random), + }, + { + // base64 + Local: util.StrToPtr("file-3"), + }, + { + // URL-escaped + Inline: util.StrToPtr(zzz), + Compression: util.StrToPtr("invalid"), + }, + { + // URL-escaped + Local: util.StrToPtr("file-2"), + Compression: util.StrToPtr("invalid"), + }, + }, + }, + types.File{ + Node: types.Node{ + Path: "/foo", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr(zzz_gz), + Compression: util.StrToPtr("gzip"), + }, + Append: []types.Resource{ + { + Source: util.StrToPtr(zzz_gz), + Compression: util.StrToPtr("gzip"), + }, + { + Source: util.StrToPtr(random_b64), + Compression: util.StrToPtr(""), + }, + { + Source: util.StrToPtr(random_b64), + Compression: util.StrToPtr(""), + }, + { + Source: util.StrToPtr("data:," + zzz), + Compression: util.StrToPtr("invalid"), + }, + { + Source: util.StrToPtr("data:," + zzz), + Compression: util.StrToPtr("invalid"), + }, + }, + }, + }, + []translate.Translation{ + { + From: path.New("yaml", "contents", "inline"), + To: path.New("json", "contents", "source"), + }, + { + From: path.New("yaml", "contents", "inline"), + To: path.New("json", "contents", "compression"), + }, + { + From: path.New("yaml", "append", 0, "local"), + To: path.New("json", "append", 0, "source"), + }, + { + From: path.New("yaml", "append", 0, "local"), + To: path.New("json", "append", 0, "compression"), + }, + { + From: path.New("yaml", "append", 1, "inline"), + To: path.New("json", "append", 1, "source"), + }, + { + From: path.New("yaml", "append", 1, "inline"), + To: path.New("json", "append", 1, "compression"), + }, + { + From: path.New("yaml", "append", 2, "local"), + To: path.New("json", "append", 2, "source"), + }, + { + From: path.New("yaml", "append", 2, "local"), + To: path.New("json", "append", 2, "compression"), + }, + { + From: path.New("yaml", "append", 3, "inline"), + To: path.New("json", "append", 3, "source"), + }, + { + From: path.New("yaml", "append", 4, "local"), + To: path.New("json", "append", 4, "source"), + }, + }, + "", + common.TranslateOptions{ + FilesDir: filesDir, + }, + }, + // Test disable automatic gzip compression + { + File{ + Path: "/foo", + Contents: Resource{ + Inline: util.StrToPtr(zzz), + }, + }, + types.File{ + Node: types.Node{ + Path: "/foo", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:," + zzz), + Compression: util.StrToPtr(""), + }, + }, + }, + []translate.Translation{ + { + From: path.New("yaml", "contents", "inline"), + To: path.New("json", "contents", "source"), + }, + { + From: path.New("yaml", "contents", "inline"), + To: path.New("json", "contents", "compression"), + }, + }, + "", + common.TranslateOptions{ + NoResourceAutoCompression: true, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := translateFile(test.in, test.options) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, test.report, r.String(), "bad report") + baseutil.VerifyTranslations(t, translations, test.exceptions) + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// TestTranslateDirectory tests translating the ct storage.directories.[i] entries to ignition storage.directories.[i] entires. +func TestTranslateDirectory(t *testing.T) { + tests := []struct { + in Directory + out types.Directory + }{ + { + Directory{}, + types.Directory{}, + }, + { + // contains invalid (by the validator's definition) combinations of fields, + // but the translator doesn't care and we can check they all get translated at once + Directory{ + Path: "/foo", + Group: NodeGroup{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("foobar"), + }, + User: NodeUser{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("bazquux"), + }, + Mode: util.IntToPtr(420), + Overwrite: util.BoolToPtr(true), + }, + types.Directory{ + Node: types.Node{ + Path: "/foo", + Group: types.NodeGroup{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("foobar"), + }, + User: types.NodeUser{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("bazquux"), + }, + Overwrite: util.BoolToPtr(true), + }, + DirectoryEmbedded1: types.DirectoryEmbedded1{ + Mode: util.IntToPtr(420), + }, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := translateDirectory(test.in, common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, report.Report{}, r, "non-empty report") + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// TestTranslateLink tests translating the ct storage.links.[i] entries to ignition storage.links.[i] entires. +func TestTranslateLink(t *testing.T) { + tests := []struct { + in Link + out types.Link + }{ + { + Link{}, + types.Link{}, + }, + { + // contains invalid (by the validator's definition) combinations of fields, + // but the translator doesn't care and we can check they all get translated at once + Link{ + Path: "/foo", + Group: NodeGroup{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("foobar"), + }, + User: NodeUser{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("bazquux"), + }, + Overwrite: util.BoolToPtr(true), + Target: util.StrToPtr("/bar"), + Hard: util.BoolToPtr(false), + }, + types.Link{ + Node: types.Node{ + Path: "/foo", + Group: types.NodeGroup{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("foobar"), + }, + User: types.NodeUser{ + ID: util.IntToPtr(1), + Name: util.StrToPtr("bazquux"), + }, + Overwrite: util.BoolToPtr(true), + }, + LinkEmbedded1: types.LinkEmbedded1{ + Target: util.StrToPtr("/bar"), + Hard: util.BoolToPtr(false), + }, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := translateLink(test.in, common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, report.Report{}, r, "non-empty report") + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// TestTranslateFilesystem tests translating the butane storage.filesystems.[i] entries to ignition storage.filesystems.[i] entries. +func TestTranslateFilesystem(t *testing.T) { + tests := []struct { + in Filesystem + out types.Filesystem + }{ + { + Filesystem{}, + types.Filesystem{}, + }, + { + // contains invalid (by the validator's definition) combinations of fields, + // but the translator doesn't care and we can check they all get translated at once + Filesystem{ + Device: "/foo", + Format: util.StrToPtr("/bar"), + Label: util.StrToPtr("/baz"), + MountOptions: []string{"yes", "no", "maybe"}, + Options: []string{"foo", "foo", "bar"}, + Path: util.StrToPtr("/quux"), + UUID: util.StrToPtr("1234"), + WipeFilesystem: util.BoolToPtr(true), + WithMountUnit: util.BoolToPtr(true), + }, + types.Filesystem{ + Device: "/foo", + Format: util.StrToPtr("/bar"), + Label: util.StrToPtr("/baz"), + MountOptions: []types.MountOption{"yes", "no", "maybe"}, + Options: []types.FilesystemOption{"foo", "foo", "bar"}, + Path: util.StrToPtr("/quux"), + UUID: util.StrToPtr("1234"), + WipeFilesystem: util.BoolToPtr(true), + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + // Filesystem doesn't have a custom translator, so embed in a + // complete config + in := Config{ + Storage: Storage{ + Filesystems: []Filesystem{test.in}, + }, + } + expected := []types.Filesystem{test.out} + actual, translations, r := in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, expected, actual.Storage.Filesystems, "translation mismatch") + assert.Equal(t, report.Report{}, r, "non-empty report") + // FIXME: Zero values are pruned from merge transcripts and + // TranslationSets to make them more compact in debug output + // and tests. As a result, if the user specifies an empty + // struct in a list, the translation coverage will be + // incomplete and the report entry marker will end up + // pointing to the base of the list, or to a parent if the + // struct is the only entry in the list. Skip the coverage + // test for this case. + if !reflect.ValueOf(test.out).IsZero() { + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + } + }) + } +} + +// TestTranslateMountUnit tests the Butane storage.filesystems.[i].with_mount_unit flag. +func TestTranslateMountUnit(t *testing.T) { + tests := []struct { + in Config + out types.Config + }{ + // local mount with options, overridden enabled flag + { + Config{ + Storage: Storage{ + Filesystems: []Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("ext4"), + MountOptions: []string{"ro", "noatime"}, + Path: util.StrToPtr("/var/lib/containers"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "var-lib-containers.mount", + Enabled: util.BoolToPtr(false), + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("ext4"), + MountOptions: []types.MountOption{"ro", "noatime"}, + Path: util.StrToPtr("/var/lib/containers"), + }, + }, + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Enabled: util.BoolToPtr(false), + Contents: util.StrToPtr(`# Generated by Butane +[Unit] +Requires=systemd-fsck@dev-disk-by\x2dlabel-foo.service +After=systemd-fsck@dev-disk-by\x2dlabel-foo.service + +[Mount] +Where=/var/lib/containers +What=/dev/disk/by-label/foo +Type=ext4 +Options=ro,noatime + +[Install] +RequiredBy=local-fs.target`), + Name: "var-lib-containers.mount", + }, + }, + }, + }, + }, + // remote mount with options + { + Config{ + Storage: Storage{ + Filesystems: []Filesystem{ + { + Device: "/dev/mapper/foo-bar", + Format: util.StrToPtr("ext4"), + MountOptions: []string{"ro", "noatime"}, + Path: util.StrToPtr("/var/lib/containers"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + Luks: []Luks{ + { + Name: "foo-bar", + Device: util.StrToPtr("/dev/bar"), + Clevis: Clevis{ + Tang: []Tang{ + { + URL: "http://example.com", + }, + }, + }, + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/mapper/foo-bar", + Format: util.StrToPtr("ext4"), + MountOptions: []types.MountOption{"ro", "noatime"}, + Path: util.StrToPtr("/var/lib/containers"), + }, + }, + Luks: []types.Luks{ + { + Name: "foo-bar", + Device: util.StrToPtr("/dev/bar"), + Clevis: types.Clevis{ + Tang: []types.Tang{ + { + URL: "http://example.com", + }, + }, + }, + }, + }, + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Enabled: util.BoolToPtr(true), + Contents: util.StrToPtr(`# Generated by Butane +[Unit] +Requires=systemd-fsck@dev-mapper-foo\x2dbar.service +After=systemd-fsck@dev-mapper-foo\x2dbar.service + +[Mount] +Where=/var/lib/containers +What=/dev/mapper/foo-bar +Type=ext4 +Options=ro,noatime,_netdev + +[Install] +RequiredBy=remote-fs.target`), + Name: "var-lib-containers.mount", + }, + }, + }, + }, + }, + // local mount, no options + { + Config{ + Storage: Storage{ + Filesystems: []Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("ext4"), + Path: util.StrToPtr("/var/lib/containers"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("ext4"), + Path: util.StrToPtr("/var/lib/containers"), + }, + }, + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Enabled: util.BoolToPtr(true), + Contents: util.StrToPtr(`# Generated by Butane +[Unit] +Requires=systemd-fsck@dev-disk-by\x2dlabel-foo.service +After=systemd-fsck@dev-disk-by\x2dlabel-foo.service + +[Mount] +Where=/var/lib/containers +What=/dev/disk/by-label/foo +Type=ext4 + +[Install] +RequiredBy=local-fs.target`), + Name: "var-lib-containers.mount", + }, + }, + }, + }, + }, + // remote mount, no options + { + Config{ + Storage: Storage{ + Filesystems: []Filesystem{ + { + Device: "/dev/mapper/foo-bar", + Format: util.StrToPtr("ext4"), + Path: util.StrToPtr("/var/lib/containers"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + Luks: []Luks{ + { + Name: "foo-bar", + Device: util.StrToPtr("/dev/bar"), + Clevis: Clevis{ + Tang: []Tang{ + { + URL: "http://example.com", + }, + }, + }, + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/mapper/foo-bar", + Format: util.StrToPtr("ext4"), + Path: util.StrToPtr("/var/lib/containers"), + }, + }, + Luks: []types.Luks{ + { + Name: "foo-bar", + Device: util.StrToPtr("/dev/bar"), + Clevis: types.Clevis{ + Tang: []types.Tang{ + { + URL: "http://example.com", + }, + }, + }, + }, + }, + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Enabled: util.BoolToPtr(true), + Contents: util.StrToPtr(`# Generated by Butane +[Unit] +Requires=systemd-fsck@dev-mapper-foo\x2dbar.service +After=systemd-fsck@dev-mapper-foo\x2dbar.service + +[Mount] +Where=/var/lib/containers +What=/dev/mapper/foo-bar +Type=ext4 +Options=_netdev + +[Install] +RequiredBy=remote-fs.target`), + Name: "var-lib-containers.mount", + }, + }, + }, + }, + }, + // overridden mount unit + { + Config{ + Storage: Storage{ + Filesystems: []Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("ext4"), + Path: util.StrToPtr("/var/lib/containers"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + Systemd: Systemd{ + Units: []Unit{ + { + Name: "var-lib-containers.mount", + Contents: util.StrToPtr("[Service]\nExecStart=/bin/false\n"), + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("ext4"), + Path: util.StrToPtr("/var/lib/containers"), + }, + }, + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Enabled: util.BoolToPtr(true), + Contents: util.StrToPtr("[Service]\nExecStart=/bin/false\n"), + Name: "var-lib-containers.mount", + }, + }, + }, + }, + }, + // swap, no options + { + Config{ + Storage: Storage{ + Filesystems: []Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("swap"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("swap"), + }, + }, + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Enabled: util.BoolToPtr(true), + Contents: util.StrToPtr(`# Generated by Butane +[Swap] +What=/dev/disk/by-label/foo + +[Install] +RequiredBy=swap.target`), + Name: "dev-disk-by\\x2dlabel-foo.swap", + }, + }, + }, + }, + }, + // swap with options + { + Config{ + Storage: Storage{ + Filesystems: []Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("swap"), + MountOptions: []string{"pri=1", "discard=pages"}, + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-label/foo", + Format: util.StrToPtr("swap"), + MountOptions: []types.MountOption{"pri=1", "discard=pages"}, + }, + }, + }, + Systemd: types.Systemd{ + Units: []types.Unit{ + { + Enabled: util.BoolToPtr(true), + Contents: util.StrToPtr(`# Generated by Butane +[Swap] +What=/dev/disk/by-label/foo +Options=pri=1,discard=pages + +[Install] +RequiredBy=swap.target`), + Name: "dev-disk-by\\x2dlabel-foo.swap", + }, + }, + }, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + out, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, out, "bad output") + assert.Equal(t, report.Report{}, r, "expected empty report") + assert.NoError(t, translations.DebugVerifyCoverage(out), "incomplete TranslationSet coverage") + }) + } +} + +// TestTranslateTree tests translating the butane storage.trees.[i] entries to ignition storage.files.[i] entries. +func TestTranslateTree(t *testing.T) { + tests := []struct { + options *common.TranslateOptions // defaulted if not specified + dirDirs map[string]os.FileMode // relative path -> mode + dirFiles map[string]os.FileMode // relative path -> mode + dirLinks map[string]string // relative path -> target + dirSockets []string // relative path + inTrees []Tree + inFiles []File + inDirs []Directory + inLinks []Link + outFiles []types.File + outLinks []types.Link + report string + skip func(t *testing.T) + }{ + // smoke test + {}, + // basic functionality + { + dirFiles: map[string]os.FileMode{ + "tree/executable": 0700, + "tree/file": 0600, + "tree/overridden": 0644, + "tree/overridden-executable": 0700, + "tree/subdir/file": 0644, + // compressed contents + "tree/subdir/subdir/subdir/subdir/subdir/subdir/subdir/subdir/subdir/file": 0644, + "tree2/file": 0600, + }, + dirLinks: map[string]string{ + "tree/subdir/bad-link": "../nonexistent", + "tree/subdir/link": "../file", + "tree/subdir/overridden-link": "../file", + }, + inTrees: []Tree{ + { + Local: "tree", + }, + { + Local: "tree2", + Path: util.StrToPtr("/etc"), + }, + }, + inFiles: []File{ + { + Path: "/overridden", + Mode: util.IntToPtr(0600), + User: NodeUser{ + Name: util.StrToPtr("bovik"), + }, + }, + { + Path: "/overridden-executable", + Mode: util.IntToPtr(0600), + User: NodeUser{ + Name: util.StrToPtr("bovik"), + }, + }, + }, + inLinks: []Link{ + { + Path: "/subdir/overridden-link", + User: NodeUser{ + Name: util.StrToPtr("bovik"), + }, + }, + }, + outFiles: []types.File{ + { + Node: types.Node{ + Path: "/overridden", + User: types.NodeUser{ + Name: util.StrToPtr("bovik"), + }, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree%2Foverridden"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0600), + }, + }, + { + Node: types.Node{ + Path: "/overridden-executable", + User: types.NodeUser{ + Name: util.StrToPtr("bovik"), + }, + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree%2Foverridden-executable"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0600), + }, + }, + { + Node: types.Node{ + Path: "/executable", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree%2Fexecutable"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(func() int { + if runtime.GOOS != "windows" { + return 0755 + } else { + // Windows doesn't have executable bits + return 0644 + } + }()), + }, + }, + { + Node: types.Node{ + Path: "/file", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree%2Ffile"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0644), + }, + }, + { + Node: types.Node{ + Path: "/subdir/file", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree%2Fsubdir%2Ffile"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0644), + }, + }, + { + Node: types.Node{ + Path: "/subdir/subdir/subdir/subdir/subdir/subdir/subdir/subdir/subdir/file", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:;base64,H4sIAAAAAAAC/yopSk3VLy5NSsksIptKy8xJBQQAAP//gkRzjkgAAAA="), + Compression: util.StrToPtr("gzip"), + }, + Mode: util.IntToPtr(0644), + }, + }, + { + Node: types.Node{ + Path: "/etc/file", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree2%2Ffile"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0644), + }, + }, + }, + outLinks: []types.Link{ + { + Node: types.Node{ + Path: "/subdir/overridden-link", + User: types.NodeUser{ + Name: util.StrToPtr("bovik"), + }, + }, + LinkEmbedded1: types.LinkEmbedded1{ + Target: util.StrToPtr("../file"), + }, + }, + { + Node: types.Node{ + Path: "/subdir/bad-link", + }, + LinkEmbedded1: types.LinkEmbedded1{ + Target: util.StrToPtr("../nonexistent"), + }, + }, + { + Node: types.Node{ + Path: "/subdir/link", + }, + LinkEmbedded1: types.LinkEmbedded1{ + Target: util.StrToPtr("../file"), + }, + }, + }, + }, + // TranslationSet completeness without overrides + { + dirFiles: map[string]os.FileMode{ + "tree/file": 0600, + "tree/subdir/file": 0644, + }, + dirDirs: map[string]os.FileMode{ + "tree/dir": 0700, + }, + dirLinks: map[string]string{ + "tree/subdir/link": "../file", + }, + inTrees: []Tree{ + { + Local: "tree", + }, + }, + outFiles: []types.File{ + { + Node: types.Node{ + Path: "/file", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree%2Ffile"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0644), + }, + }, + { + Node: types.Node{ + Path: "/subdir/file", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:,tree%2Fsubdir%2Ffile"), + Compression: util.StrToPtr(""), + }, + Mode: util.IntToPtr(0644), + }, + }, + }, + outLinks: []types.Link{ + { + Node: types.Node{ + Path: "/subdir/link", + }, + LinkEmbedded1: types.LinkEmbedded1{ + Target: util.StrToPtr("../file"), + }, + }, + }, + }, + // collisions + { + dirFiles: map[string]os.FileMode{ + "tree0/file": 0600, + "tree1/directory": 0600, + "tree2/link": 0600, + "tree3/file-partial": 0600, // should be okay + "tree4/link-partial": 0600, + "tree5/tree-file": 0600, // set up for tree/tree collision + "tree6/tree-file": 0600, + "tree15/tree-link": 0600, + }, + dirLinks: map[string]string{ + "tree7/file": "file", + "tree8/directory": "file", + "tree9/link": "file", + "tree10/file-partial": "file", + "tree11/link-partial": "file", // should be okay + "tree12/tree-file": "file", + "tree13/tree-link": "file", // set up for tree/tree collision + "tree14/tree-link": "file", + }, + inTrees: []Tree{ + { + Local: "tree0", + }, + { + Local: "tree1", + }, + { + Local: "tree2", + }, + { + Local: "tree3", + }, + { + Local: "tree4", + }, + { + Local: "tree5", + }, + { + Local: "tree6", + }, + { + Local: "tree7", + }, + { + Local: "tree8", + }, + { + Local: "tree9", + }, + { + Local: "tree10", + }, + { + Local: "tree11", + }, + { + Local: "tree12", + }, + { + Local: "tree13", + }, + { + Local: "tree14", + }, + { + Local: "tree15", + }, + }, + inFiles: []File{ + { + Path: "/file", + Contents: Resource{ + Source: util.StrToPtr("data:,foo"), + }, + }, + { + Path: "/file-partial", + }, + }, + inDirs: []Directory{ + { + Path: "/directory", + }, + }, + inLinks: []Link{ + { + Path: "/link", + Target: util.StrToPtr("file"), + }, + { + Path: "/link-partial", + }, + }, + report: "error at $.storage.trees.0: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.1: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.2: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.4: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.6: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.7: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.8: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.9: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.10: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.12: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.14: " + common.ErrNodeExists.Error() + "\n" + + "error at $.storage.trees.15: " + common.ErrNodeExists.Error() + "\n", + }, + // files-dir escape + { + inTrees: []Tree{ + { + Local: "../escape", + }, + }, + report: "error at $.storage.trees.0: " + common.ErrFilesDirEscape.Error() + "\n", + }, + // no files-dir + { + options: &common.TranslateOptions{}, + inTrees: []Tree{ + { + Local: "tree", + }, + }, + report: "error at $.storage.trees.0: " + common.ErrNoFilesDir.Error() + "\n", + }, + // non-file/dir/symlink in directory tree + { + dirSockets: []string{ + "tree/socket", + }, + inTrees: []Tree{ + { + Local: "tree", + }, + }, + report: "error at $.storage.trees.0: " + common.ErrFileType.Error() + "\n", + skip: func(t *testing.T) { + if runtime.GOOS == "windows" { + // Windows supports Unix domain sockets, but os.Stat() + // doesn't detect them correctly. + t.Skip("skipping test due to https://github.com/golang/go/issues/33357") + } + }, + }, + // unreadable file + { + dirDirs: map[string]os.FileMode{ + "tree/subdir": 0000, + "tree2": 0000, + }, + dirFiles: map[string]os.FileMode{ + "tree/file": 0000, + }, + inTrees: []Tree{ + { + Local: "tree", + }, + { + Local: "tree2", + }, + }, + report: "error at $.storage.trees.0: open %FilesDir%/tree/file: permission denied\n" + + "error at $.storage.trees.0: open %FilesDir%/tree/subdir: permission denied\n" + + "error at $.storage.trees.1: open %FilesDir%/tree2: permission denied\n", + skip: func(t *testing.T) { + if runtime.GOOS == "windows" { + // os.Chmod() only respects the writable bit and there + // isn't a trivial way to make inodes inaccessible + t.Skip("skipping test on Windows") + } + }, + }, + // local is not a directory + { + dirFiles: map[string]os.FileMode{ + "tree": 0600, + }, + inTrees: []Tree{ + { + Local: "tree", + }, + { + Local: "nonexistent", + }, + }, + report: "error at $.storage.trees.0: " + common.ErrTreeNotDirectory.Error() + "\n" + + "error at $.storage.trees.1: " + osStatName + " %FilesDir%" + string(filepath.Separator) + "nonexistent: " + osNotFound + "\n", + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + if test.skip != nil { + // give the test an opportunity to skip + test.skip(t) + } + filesDir := t.TempDir() + for testPath, mode := range test.dirDirs { + absPath := filepath.Join(filesDir, filepath.FromSlash(testPath)) + if err := os.MkdirAll(absPath, 0755); err != nil { + t.Error(err) + return + } + if err := os.Chmod(absPath, mode); err != nil { + t.Error(err) + return + } + } + for testPath, mode := range test.dirFiles { + absPath := filepath.Join(filesDir, filepath.FromSlash(testPath)) + if err := os.MkdirAll(filepath.Dir(absPath), 0755); err != nil { + t.Error(err) + return + } + if err := os.WriteFile(absPath, []byte(testPath), mode); err != nil { + t.Error(err) + return + } + } + for testPath, target := range test.dirLinks { + absPath := filepath.Join(filesDir, filepath.FromSlash(testPath)) + if err := os.MkdirAll(filepath.Dir(absPath), 0755); err != nil { + t.Error(err) + return + } + if err := os.Symlink(target, absPath); err != nil { + t.Error(err) + return + } + } + for _, testPath := range test.dirSockets { + absPath := filepath.Join(filesDir, filepath.FromSlash(testPath)) + if err := os.MkdirAll(filepath.Dir(absPath), 0755); err != nil { + t.Error(err) + return + } + listener, err := net.ListenUnix("unix", &net.UnixAddr{ + Name: absPath, + Net: "unix", + }) + if err != nil { + t.Error(err) + return + } + defer listener.Close() + } + + config := Config{ + Storage: Storage{ + Files: test.inFiles, + Directories: test.inDirs, + Links: test.inLinks, + Trees: test.inTrees, + }, + } + options := common.TranslateOptions{ + FilesDir: filesDir, + } + if test.options != nil { + options = *test.options + } + actual, translations, r := config.ToIgn3_5Unvalidated(options) + + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, config, r) + expectedReport := strings.ReplaceAll(test.report, "%FilesDir%", filesDir) + assert.Equal(t, expectedReport, r.String(), "bad report") + if expectedReport != "" { + return + } + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + + assert.Equal(t, test.outFiles, actual.Storage.Files, "files mismatch") + assert.Equal(t, []types.Directory(nil), actual.Storage.Directories, "directories mismatch") + assert.Equal(t, test.outLinks, actual.Storage.Links, "links mismatch") + }) + } +} + +// TestTranslateIgnition tests translating the ct config.ignition to the ignition config.ignition section. +// It ensures that the version is set as well. +func TestTranslateIgnition(t *testing.T) { + tests := []struct { + in Ignition + out types.Ignition + }{ + { + Ignition{}, + types.Ignition{ + Version: "3.5.0", + }, + }, + { + Ignition{ + Config: IgnitionConfig{ + Merge: []Resource{ + { + Inline: util.StrToPtr("xyzzy"), + }, + }, + Replace: Resource{ + Inline: util.StrToPtr("xyzzy"), + }, + }, + }, + types.Ignition{ + Version: "3.5.0", + Config: types.IgnitionConfig{ + Merge: []types.Resource{ + { + Source: util.StrToPtr("data:,xyzzy"), + Compression: util.StrToPtr(""), + }, + }, + Replace: types.Resource{ + Source: util.StrToPtr("data:,xyzzy"), + Compression: util.StrToPtr(""), + }, + }, + }, + }, + { + Ignition{ + Proxy: Proxy{ + HTTPProxy: util.StrToPtr("https://example.com:8080"), + NoProxy: []string{"example.com"}, + }, + }, + types.Ignition{ + Version: "3.5.0", + Proxy: types.Proxy{ + HTTPProxy: util.StrToPtr("https://example.com:8080"), + NoProxy: []types.NoProxyItem{types.NoProxyItem("example.com")}, + }, + }, + }, + { + Ignition{ + Security: Security{ + TLS: TLS{ + CertificateAuthorities: []Resource{ + { + Inline: util.StrToPtr("xyzzy"), + }, + }, + }, + }, + }, + types.Ignition{ + Version: "3.5.0", + Security: types.Security{ + TLS: types.TLS{ + CertificateAuthorities: []types.Resource{ + { + Source: util.StrToPtr("data:,xyzzy"), + Compression: util.StrToPtr(""), + }, + }, + }, + }, + }, + }, + } + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := translateIgnition(test.in, common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, report.Report{}, r, "non-empty report") + // DebugVerifyCoverage wants to see a translation for $.version but + // translateIgnition doesn't create one; ToIgn3_*Unvalidated handles + // that since it has access to the Butane config version + translations.AddTranslation(path.New("yaml", "bogus"), path.New("json", "version")) + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// TestTranslateKernelArguments tests translating the butane kernel_arguments.{should_exist,should_not_exist}.[i] entries to +// ignition kernelArguments.{shouldExist,shouldNotExist}.[i] entries. +// +// KernelArguments do not use a custom translation function (it utilizes the MergeP2 functionality) so pass an entire config +func TestTranslateKernelArguments(t *testing.T) { + tests := []struct { + in Config + out types.Config + }{ + { + Config{ + KernelArguments: KernelArguments{ + ShouldExist: []KernelArgument{ + "foo", + }, + ShouldNotExist: []KernelArgument{ + "bar", + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + KernelArguments: types.KernelArguments{ + ShouldExist: []types.KernelArgument{ + "foo", + }, + ShouldNotExist: []types.KernelArgument{ + "bar", + }, + }, + }, + }, + } + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, report.Report{}, r, "non-empty report") + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// TestTranslateLuks test translating the butane storage.luks.clevis.tang.[i] arguments to ignition storage.luks.clevis.tang.[i] entries. +func TestTranslateTang(t *testing.T) { + tests := []struct { + in Config + out types.Config + }{ + // Luks with tang and all options set, returns a valid ignition config with the same options + { + Config{ + Storage: Storage{ + Filesystems: []Filesystem{ + { + Device: "/dev/mapper/foo-bar", + Path: util.StrToPtr("/var/lib/containers"), + }, + }, + Luks: []Luks{ + { + Name: "foo-bar", + Device: util.StrToPtr("/dev/bar"), + Clevis: Clevis{ + Tang: []Tang{ + { + URL: "http://example.com", + Thumbprint: util.StrToPtr("xyzzy"), + Advertisement: util.StrToPtr("{\"payload\": \"xyzzy\"}"), + }, + }, + }, + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Filesystems: []types.Filesystem{ + { + Device: "/dev/mapper/foo-bar", + Path: util.StrToPtr("/var/lib/containers"), + }, + }, + Luks: []types.Luks{ + { + Name: "foo-bar", + Device: util.StrToPtr("/dev/bar"), + Clevis: types.Clevis{ + Tang: []types.Tang{ + { + URL: "http://example.com", + Thumbprint: util.StrToPtr("xyzzy"), + Advertisement: util.StrToPtr("{\"payload\": \"xyzzy\"}"), + }, + }, + }, + }, + }, + }, + }, + }, + } + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, report.Report{}, r, "non-empty report") + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// TestTranslateSSHAuthorizedKey tests translating the butane passwd.users[i].ssh_authorized_keys_local[j] entries to ignition passwd.users[i].ssh_authorized_keys[j] entries. +func TestTranslateSSHAuthorizedKey(t *testing.T) { + sshKeyDir := t.TempDir() + randomDir := t.TempDir() + var sshKeyInline = "ssh-rsa AAAAAAAAA" + var sshKey1 = "ssh-rsa BBBBBBBBB" + var sshKey2 = "ssh-rsa CCCCCCCCC" + var sshKey3 = "ssh-rsa DDDDDDDDD" + var sshKeyFileName = "id_rsa.pub" + var sshKeyMultipleKeysFileName = "multiple.pub" + var sshKeyEmptyFileName = "empty.pub" + var sshKeyBlankFileName = "blank.pub" + var sshKeyWindowsLineEndingsFileName = "windows.pub" + var sshKeyNonExistingFileName = "id_ed25519.pub" + + sshKeyData := map[string][]byte{ + sshKeyFileName: []byte(sshKey1), + sshKeyMultipleKeysFileName: []byte(fmt.Sprintf("%s\n#comment\n\n\n%s\n", sshKey2, sshKey3)), + sshKeyEmptyFileName: []byte(""), + sshKeyBlankFileName: []byte("\n\t"), + sshKeyWindowsLineEndingsFileName: []byte(fmt.Sprintf("%s\r\n#comment\r\n", sshKey1)), + } + + for fileName, contents := range sshKeyData { + if err := os.WriteFile(filepath.Join(sshKeyDir, fileName), contents, 0644); err != nil { + t.Error(err) + } + } + + tests := []struct { + name string + in PasswdUser + out types.PasswdUser + translations []translate.Translation + report string + fileDir string + }{ + { + "empty user", + PasswdUser{}, + types.PasswdUser{}, + []translate.Translation{}, + "", + sshKeyDir, + }, + { + "valid inline keys", + PasswdUser{SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(sshKeyInline)}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey(sshKeyInline)}}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + }, + "", + sshKeyDir, + }, + { + "valid local keys", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey(sshKey1)}}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + }, + "", + sshKeyDir, + }, + { + "valid local keys with multiple keys per file", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyMultipleKeysFileName}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{ + types.SSHAuthorizedKey(sshKey2), + types.SSHAuthorizedKey("#comment"), + types.SSHAuthorizedKey(sshKey3), + }}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 1)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 2)}, + }, + "", + sshKeyDir, + }, + { + "valid multiple local key files", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName, sshKeyMultipleKeysFileName}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{ + types.SSHAuthorizedKey(sshKey1), + types.SSHAuthorizedKey(sshKey2), + types.SSHAuthorizedKey("#comment"), + types.SSHAuthorizedKey(sshKey3), + }}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 1), To: path.New("json", "sshAuthorizedKeys", 1)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 1), To: path.New("json", "sshAuthorizedKeys", 2)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 1), To: path.New("json", "sshAuthorizedKeys", 3)}, + }, + "", + sshKeyDir, + }, + { + "valid local and inline keys", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName}, SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(sshKeyInline)}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey(sshKeyInline), types.SSHAuthorizedKey(sshKey1)}}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 1)}, + }, + "", + sshKeyDir, + }, + { + "valid local keys with multiple keys per file and inline keys", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyMultipleKeysFileName}, SSHAuthorizedKeys: []SSHAuthorizedKey{SSHAuthorizedKey(sshKeyInline)}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{ + types.SSHAuthorizedKey(sshKeyInline), + types.SSHAuthorizedKey(sshKey2), + types.SSHAuthorizedKey("#comment"), + types.SSHAuthorizedKey(sshKey3), + }}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 1)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 2)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 3)}, + }, + "", + sshKeyDir, + }, + { + "valid empty local file", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyEmptyFileName}}, + types.PasswdUser{}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + }, + "", + sshKeyDir, + }, + { + "valid blank local file", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyBlankFileName}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{types.SSHAuthorizedKey("\t")}}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + }, + "", + sshKeyDir, + }, + { + "valid Windows style line endings in local file", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyWindowsLineEndingsFileName}}, + types.PasswdUser{SSHAuthorizedKeys: []types.SSHAuthorizedKey{ + types.SSHAuthorizedKey(sshKey1), + types.SSHAuthorizedKey("#comment"), + }}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 0)}, + {From: path.New("yaml", "ssh_authorized_keys_local", 0), To: path.New("json", "sshAuthorizedKeys", 1)}, + }, + "", + sshKeyDir, + }, + { + "missing local file", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyNonExistingFileName}}, + types.PasswdUser{}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + }, + "error at $.ssh_authorized_keys_local.0: open " + filepath.Join(sshKeyDir, sshKeyNonExistingFileName) + ": " + osNotFound + "\n", + sshKeyDir, + }, + { + "missing embed directory", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName}}, + types.PasswdUser{}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + }, + "error at $.ssh_authorized_keys_local: " + common.ErrNoFilesDir.Error() + "\n", + "", + }, + { + "wrong embed directory", + PasswdUser{SSHAuthorizedKeysLocal: []string{sshKeyFileName}}, + types.PasswdUser{}, + []translate.Translation{ + {From: path.New("yaml", "ssh_authorized_keys_local"), To: path.New("json", "sshAuthorizedKeys")}, + }, + "error at $.ssh_authorized_keys_local.0: open " + filepath.Join(randomDir, sshKeyFileName) + ": " + osNotFound + "\n", + randomDir, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual, translations, r := translatePasswdUser(test.in, common.TranslateOptions{FilesDir: test.fileDir}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, test.report, r.String(), "bad report") + baseutil.VerifyTranslations(t, translations, test.translations) + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// TestTranslateUnitLocal tests translating the butane systemd.units[i].contents_local entries to ignition systemd.units[i].contents entries. +func TestTranslateUnitLocal(t *testing.T) { + unitDir := t.TempDir() + randomDir := t.TempDir() + var unitName = "example.service" + var dropinName = "example.conf" + var unitDefinitionInline = "[Service]\nExecStart=/bin/false\n" + var unitDefinitionFile = "[Service]\nExecStart=/bin/true\n" + var unitEmptyFileName = "empty.service" + var unitEmptyDefinition = "" + var unitNonExistingFileName = "random.service" + + err := os.WriteFile(filepath.Join(unitDir, unitName), []byte(unitDefinitionFile), 0644) + if err != nil { + t.Error(err) + } + err = os.WriteFile(filepath.Join(unitDir, unitEmptyFileName), []byte(unitEmptyDefinition), 0644) + if err != nil { + t.Error(err) + } + + tests := []struct { + name string + in Unit + out types.Unit + translations []translate.Translation + report string + fileDir string + }{ + { + "empty unit", + Unit{}, + types.Unit{}, + []translate.Translation{}, + "", + "", + }, + { + "valid contents", + Unit{Contents: &unitDefinitionInline, Name: unitName}, + types.Unit{Contents: &unitDefinitionInline, Name: unitName}, + []translate.Translation{}, + "", + "", + }, + { + "valid contents_local", + Unit{ContentsLocal: &unitName, Name: unitName}, + types.Unit{Contents: &unitDefinitionFile, Name: unitName}, + []translate.Translation{ + {From: path.New("yaml", "contents_local"), To: path.New("json", "contents")}, + }, + "", + unitDir, + }, + { + "non existing contents_local file name", + Unit{ContentsLocal: &unitNonExistingFileName, Name: unitName}, + types.Unit{Name: unitName}, + []translate.Translation{}, + "error at $.contents_local: open " + filepath.Join(unitDir, unitNonExistingFileName) + ": " + osNotFound + "\n", + unitDir, + }, + { + "valid empty contents_local file", + Unit{ContentsLocal: &unitEmptyFileName, Name: unitName}, + types.Unit{Contents: &unitEmptyDefinition, Name: unitName}, + []translate.Translation{ + {From: path.New("yaml", "contents_local"), To: path.New("json", "contents")}, + }, + "", + unitDir, + }, + { + "missing embed directory", + Unit{ContentsLocal: &unitName, Name: unitName}, + types.Unit{Name: unitName}, + []translate.Translation{}, + "error at $.contents_local: " + common.ErrNoFilesDir.Error() + "\n", + "", + }, + { + "wrong embed directory", + Unit{ContentsLocal: &unitName, Name: unitName}, + types.Unit{Name: unitName}, + []translate.Translation{}, + "error at $.contents_local: open " + filepath.Join(randomDir, unitName) + ": " + osNotFound + "\n", + randomDir, + }, + { + "empty dropin unit", + Unit{Name: dropinName, Dropins: nil}, + types.Unit{Name: dropinName, Dropins: nil}, + []translate.Translation{}, + "", + "", + }, + { + "valid dropin contents", + Unit{Dropins: []Dropin{{Name: dropinName, Contents: &unitDefinitionInline}}, Name: unitName}, + types.Unit{Dropins: []types.Dropin{{Name: dropinName, Contents: &unitDefinitionInline}}, Name: unitName}, + []translate.Translation{}, + "", + "", + }, + { + "valid dropin contents_local", + Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitName}}, Name: unitName}, + types.Unit{Dropins: []types.Dropin{{Name: dropinName, Contents: &unitDefinitionFile}}, Name: unitName}, + []translate.Translation{ + {From: path.New("yaml", "dropins", 0, "contents_local"), To: path.New("json", "dropins", 0, "contents")}, + }, + "", + unitDir, + }, + { + "non existing dropin contents_local file name", + Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitNonExistingFileName}}, Name: unitName}, + types.Unit{Dropins: []types.Dropin{{Name: dropinName}}, Name: unitName}, + []translate.Translation{}, + "error at $.dropins.0.contents_local: open " + filepath.Join(unitDir, unitNonExistingFileName) + ": " + osNotFound + "\n", + unitDir, + }, + { + "valid empty dropin contents_local file", + Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitEmptyFileName}}, Name: unitName}, + types.Unit{Dropins: []types.Dropin{{Name: dropinName, Contents: &unitEmptyDefinition}}, Name: unitName}, + []translate.Translation{ + {From: path.New("yaml", "dropins", 0, "contents_local"), To: path.New("json", "dropins", 0, "contents")}, + }, + "", + unitDir, + }, + { + "missing embed directory for dropin", + Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitName}}, Name: unitName}, + types.Unit{Dropins: []types.Dropin{{Name: dropinName}}, Name: unitName}, + []translate.Translation{}, + "error at $.dropins.0.contents_local: " + common.ErrNoFilesDir.Error() + "\n", + "", + }, + { + "wrong embed directory for dropin", + Unit{Dropins: []Dropin{{Name: dropinName, ContentsLocal: &unitName}}, Name: unitName}, + types.Unit{Dropins: []types.Dropin{{Name: dropinName}}, Name: unitName}, + []translate.Translation{}, + "error at $.dropins.0.contents_local: open " + filepath.Join(randomDir, unitName) + ": " + osNotFound + "\n", + randomDir, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + actual, translations, r := translateUnit(test.in, common.TranslateOptions{FilesDir: test.fileDir}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, test.report, r.String(), "bad report") + baseutil.VerifyTranslations(t, translations, test.translations) + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// TestToIgn3_5 tests the config.ToIgn3_5 function ensuring it will generate a valid config even when empty. Not much else is +// tested since it uses the Ignition translation code which has its own set of tests. +func TestToIgn3_5(t *testing.T) { + tests := []struct { + in Config + out types.Config + }{ + { + Config{}, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + }, + }, + } + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, report.Report{}, r, "non-empty report") + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} diff --git a/base/v0_7_exp/util.go b/base/v0_7_exp/util.go new file mode 100644 index 00000000..f92b73c6 --- /dev/null +++ b/base/v0_7_exp/util.go @@ -0,0 +1,125 @@ +// 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 v0_6_exp + +import ( + "github.com/coreos/ignition/v2/config/v3_5/types" +) + +type nodeTracker struct { + files *[]types.File + fileMap map[string]int + + dirs *[]types.Directory + dirMap map[string]int + + links *[]types.Link + linkMap map[string]int +} + +func newNodeTracker(c *types.Config) *nodeTracker { + t := nodeTracker{ + files: &c.Storage.Files, + fileMap: make(map[string]int, len(c.Storage.Files)), + + dirs: &c.Storage.Directories, + dirMap: make(map[string]int, len(c.Storage.Directories)), + + links: &c.Storage.Links, + linkMap: make(map[string]int, len(c.Storage.Links)), + } + for i, n := range *t.files { + t.fileMap[n.Path] = i + } + for i, n := range *t.dirs { + t.dirMap[n.Path] = i + } + for i, n := range *t.links { + t.linkMap[n.Path] = i + } + return &t +} + +func (t *nodeTracker) Exists(path string) bool { + for _, m := range []map[string]int{t.fileMap, t.dirMap, t.linkMap} { + if _, ok := m[path]; ok { + return true + } + } + return false +} + +func (t *nodeTracker) GetFile(path string) (int, *types.File) { + if i, ok := t.fileMap[path]; ok { + return i, &(*t.files)[i] + } else { + return 0, nil + } +} + +func (t *nodeTracker) AddFile(f types.File) (int, *types.File) { + if f.Path == "" { + panic("File path missing") + } + if _, ok := t.fileMap[f.Path]; ok { + panic("Adding already existing file") + } + i := len(*t.files) + *t.files = append(*t.files, f) + t.fileMap[f.Path] = i + return i, &(*t.files)[i] +} + +func (t *nodeTracker) GetDir(path string) (int, *types.Directory) { + if i, ok := t.dirMap[path]; ok { + return i, &(*t.dirs)[i] + } else { + return 0, nil + } +} + +func (t *nodeTracker) AddDir(d types.Directory) (int, *types.Directory) { + if d.Path == "" { + panic("Directory path missing") + } + if _, ok := t.dirMap[d.Path]; ok { + panic("Adding already existing directory") + } + i := len(*t.dirs) + *t.dirs = append(*t.dirs, d) + t.dirMap[d.Path] = i + return i, &(*t.dirs)[i] +} + +func (t *nodeTracker) GetLink(path string) (int, *types.Link) { + if i, ok := t.linkMap[path]; ok { + return i, &(*t.links)[i] + } else { + return 0, nil + } +} + +func (t *nodeTracker) AddLink(l types.Link) (int, *types.Link) { + if l.Path == "" { + panic("Link path missing") + } + if _, ok := t.linkMap[l.Path]; ok { + panic("Adding already existing link") + } + i := len(*t.links) + *t.links = append(*t.links, l) + t.linkMap[l.Path] = i + return i, &(*t.links)[i] +} diff --git a/base/v0_7_exp/validate.go b/base/v0_7_exp/validate.go new file mode 100644 index 00000000..ad3917be --- /dev/null +++ b/base/v0_7_exp/validate.go @@ -0,0 +1,92 @@ +// 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 v0_6_exp + +import ( + baseutil "github.com/coreos/butane/base/util" + "github.com/coreos/butane/config/common" + + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (rs Resource) Validate(c path.ContextPath) (r report.Report) { + var field string + sources := 0 + if rs.Local != nil { + sources++ + field = "local" + } + if rs.Inline != nil { + sources++ + field = "inline" + } + if rs.Source != nil { + sources++ + field = "source" + } + if sources > 1 { + r.AddOnError(c.Append(field), common.ErrTooManyResourceSources) + } + return +} + +func (fs Filesystem) Validate(c path.ContextPath) (r report.Report) { + if !util.IsTrue(fs.WithMountUnit) { + return + } + if util.NilOrEmpty(fs.Format) { + r.AddOnError(c.Append("format"), common.ErrMountUnitNoFormat) + } else if *fs.Format != "swap" && util.NilOrEmpty(fs.Path) { + r.AddOnError(c.Append("path"), common.ErrMountUnitNoPath) + } + return +} + +func (d Directory) Validate(c path.ContextPath) (r report.Report) { + if d.Mode != nil { + r.AddOnWarn(c.Append("mode"), baseutil.CheckForDecimalMode(*d.Mode, true)) + } + return +} + +func (f File) Validate(c path.ContextPath) (r report.Report) { + if f.Mode != nil { + r.AddOnWarn(c.Append("mode"), baseutil.CheckForDecimalMode(*f.Mode, false)) + } + return +} + +func (t Tree) Validate(c path.ContextPath) (r report.Report) { + if t.Local == "" { + r.AddOnError(c, common.ErrTreeNoLocal) + } + return +} + +func (rs Unit) Validate(c path.ContextPath) (r report.Report) { + if rs.ContentsLocal != nil && rs.Contents != nil { + r.AddOnError(c.Append("contents_local"), common.ErrTooManySystemdSources) + } + return +} + +func (rs Dropin) Validate(c path.ContextPath) (r report.Report) { + if rs.ContentsLocal != nil && rs.Contents != nil { + r.AddOnError(c.Append("contents_local"), common.ErrTooManySystemdSources) + } + return +} diff --git a/base/v0_7_exp/validate_test.go b/base/v0_7_exp/validate_test.go new file mode 100644 index 00000000..78e72910 --- /dev/null +++ b/base/v0_7_exp/validate_test.go @@ -0,0 +1,389 @@ +// 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 v0_6_exp + +import ( + "fmt" + "testing" + + baseutil "github.com/coreos/butane/base/util" + "github.com/coreos/butane/config/common" + + "github.com/coreos/ignition/v2/config/util" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" + "github.com/stretchr/testify/assert" +) + +// TestValidateResource tests that multiple sources (i.e. urls and inline) are not allowed but zero or one sources are +func TestValidateResource(t *testing.T) { + tests := []struct { + in Resource + out error + errPath path.ContextPath + }{ + {}, + // source specified + { + // contains invalid (by the validator's definition) combinations of fields, + // but the translator doesn't care and we can check they all get translated at once + Resource{ + Source: util.StrToPtr("http://example/com"), + Compression: util.StrToPtr("gzip"), + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + nil, + path.New("yaml"), + }, + // inline specified + { + Resource{ + Inline: util.StrToPtr("hello"), + Compression: util.StrToPtr("gzip"), + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + nil, + path.New("yaml"), + }, + // local specified + { + Resource{ + Local: util.StrToPtr("hello"), + Compression: util.StrToPtr("gzip"), + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + nil, + path.New("yaml"), + }, + // source + inline, invalid + { + Resource{ + Source: util.StrToPtr("data:,hello"), + Inline: util.StrToPtr("hello"), + Compression: util.StrToPtr("gzip"), + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + common.ErrTooManyResourceSources, + path.New("yaml", "source"), + }, + // source + local, invalid + { + Resource{ + Source: util.StrToPtr("data:,hello"), + Local: util.StrToPtr("hello"), + Compression: util.StrToPtr("gzip"), + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + common.ErrTooManyResourceSources, + path.New("yaml", "source"), + }, + // inline + local, invalid + { + Resource{ + Inline: util.StrToPtr("hello"), + Local: util.StrToPtr("hello"), + Compression: util.StrToPtr("gzip"), + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + common.ErrTooManyResourceSources, + path.New("yaml", "inline"), + }, + // source + inline + local, invalid + { + Resource{ + Source: util.StrToPtr("data:,hello"), + Inline: util.StrToPtr("hello"), + Local: util.StrToPtr("hello"), + Compression: util.StrToPtr("gzip"), + Verification: Verification{ + Hash: util.StrToPtr("this isn't validated"), + }, + }, + common.ErrTooManyResourceSources, + path.New("yaml", "source"), + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnError(test.errPath, test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} + +func TestValidateTree(t *testing.T) { + tests := []struct { + in Tree + out error + }{ + { + in: Tree{}, + out: common.ErrTreeNoLocal, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnError(path.New("yaml"), test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} + +func TestValidateFileMode(t *testing.T) { + fileTests := []struct { + in File + out error + }{ + { + in: File{}, + out: nil, + }, + { + in: File{ + Mode: util.IntToPtr(0600), + }, + out: nil, + }, + { + in: File{ + Mode: util.IntToPtr(600), + }, + out: common.ErrDecimalMode, + }, + } + + for i, test := range fileTests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnWarn(path.New("yaml", "mode"), test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} + +func TestValidateDirMode(t *testing.T) { + dirTests := []struct { + in Directory + out error + }{ + { + in: Directory{}, + out: nil, + }, + { + in: Directory{ + Mode: util.IntToPtr(01770), + }, + out: nil, + }, + { + in: Directory{ + Mode: util.IntToPtr(1770), + }, + out: common.ErrDecimalMode, + }, + } + + for i, test := range dirTests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnWarn(path.New("yaml", "mode"), test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} + +func TestValidateFilesystem(t *testing.T) { + tests := []struct { + in Filesystem + out error + errPath path.ContextPath + }{ + { + Filesystem{}, + nil, + path.New("yaml"), + }, + { + Filesystem{ + Device: "/dev/foo", + }, + nil, + path.New("yaml"), + }, + { + Filesystem{ + Device: "/dev/foo", + Format: util.StrToPtr("zzz"), + Path: util.StrToPtr("/z"), + WithMountUnit: util.BoolToPtr(true), + }, + nil, + path.New("yaml"), + }, + { + Filesystem{ + Device: "/dev/foo", + Format: util.StrToPtr("swap"), + WithMountUnit: util.BoolToPtr(true), + }, + nil, + path.New("yaml"), + }, + { + Filesystem{ + Device: "/dev/foo", + WithMountUnit: util.BoolToPtr(true), + }, + common.ErrMountUnitNoFormat, + path.New("yaml", "format"), + }, + { + Filesystem{ + Device: "/dev/foo", + Format: util.StrToPtr("zzz"), + WithMountUnit: util.BoolToPtr(true), + }, + common.ErrMountUnitNoPath, + path.New("yaml", "path"), + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnError(test.errPath, test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} + +// TestValidateUnit tests that multiple sources (i.e. contents and contents_local) are not allowed but zero or one sources are +func TestValidateUnit(t *testing.T) { + tests := []struct { + in Unit + out error + errPath path.ContextPath + }{ + {}, + // contents specified + { + Unit{ + Contents: util.StrToPtr("hello"), + }, + nil, + path.New("yaml"), + }, + // contents_local specified + { + Unit{ + ContentsLocal: util.StrToPtr("hello"), + }, + nil, + path.New("yaml"), + }, + // contents + contents_local, invalid + { + Unit{ + Contents: util.StrToPtr("hello"), + ContentsLocal: util.StrToPtr("hello, too"), + }, + common.ErrTooManySystemdSources, + path.New("yaml", "contents_local"), + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnError(test.errPath, test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} + +// TestValidateDropin tests that multiple sources (i.e. contents and contents_local) are not allowed but zero or one sources are +func TestValidateDropin(t *testing.T) { + tests := []struct { + in Dropin + out error + errPath path.ContextPath + }{ + {}, + // contents specified + { + Dropin{ + Contents: util.StrToPtr("hello"), + }, + nil, + path.New("yaml"), + }, + // contents_local specified + { + Dropin{ + ContentsLocal: util.StrToPtr("hello"), + }, + nil, + path.New("yaml"), + }, + // contents + contents_local, invalid + { + Dropin{ + Contents: util.StrToPtr("hello"), + ContentsLocal: util.StrToPtr("hello, too"), + }, + common.ErrTooManySystemdSources, + path.New("yaml", "contents_local"), + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnError(test.errPath, test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} From 67d33f0df71ec17510b00ef563b46d2240bcd424 Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:18:34 -0500 Subject: [PATCH 02/13] base/v0_7_exp: update imports and package names --- base/v0_7_exp/schema.go | 2 +- base/v0_7_exp/translate.go | 8 +++--- base/v0_7_exp/translate_test.go | 48 ++++++++++++++++----------------- base/v0_7_exp/util.go | 4 +-- base/v0_7_exp/validate.go | 2 +- base/v0_7_exp/validate_test.go | 2 +- 6 files changed, 33 insertions(+), 33 deletions(-) diff --git a/base/v0_7_exp/schema.go b/base/v0_7_exp/schema.go index 1104198f..ecdeff1e 100644 --- a/base/v0_7_exp/schema.go +++ b/base/v0_7_exp/schema.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_7_exp type Cex struct { Enabled *bool `yaml:"enabled"` diff --git a/base/v0_7_exp/translate.go b/base/v0_7_exp/translate.go index b17f5a52..6d4f65f2 100644 --- a/base/v0_7_exp/translate.go +++ b/base/v0_7_exp/translate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_7_exp import ( "fmt" @@ -29,7 +29,7 @@ import ( "github.com/coreos/go-systemd/v22/unit" "github.com/coreos/ignition/v2/config/util" - "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" ) @@ -75,10 +75,10 @@ RequiredBy=local-fs.target {{- end }}`)) ) -// ToIgn3_5Unvalidated translates the config to an Ignition config. It also returns the set of translations +// ToIgn3_6Unvalidated 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_5Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { +func (c Config) ToIgn3_6Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { ret := types.Config{} tr := translate.NewTranslator("yaml", "json", options) diff --git a/base/v0_7_exp/translate_test.go b/base/v0_7_exp/translate_test.go index 8b549055..0433fad3 100644 --- a/base/v0_7_exp/translate_test.go +++ b/base/v0_7_exp/translate_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_7_exp import ( "fmt" @@ -30,7 +30,7 @@ import ( "github.com/coreos/butane/translate" "github.com/coreos/ignition/v2/config/util" - "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" "github.com/stretchr/testify/assert" @@ -765,7 +765,7 @@ func TestTranslateFilesystem(t *testing.T) { }, } expected := []types.Filesystem{test.out} - actual, translations, r := in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + actual, translations, r := in.ToIgn3_6Unvalidated(common.TranslateOptions{}) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, test.in, r) assert.Equal(t, expected, actual.Storage.Filesystems, "translation mismatch") @@ -816,7 +816,7 @@ func TestTranslateMountUnit(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -881,7 +881,7 @@ RequiredBy=local-fs.target`), }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -945,7 +945,7 @@ RequiredBy=remote-fs.target`), }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -1007,7 +1007,7 @@ RequiredBy=local-fs.target`), }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -1078,7 +1078,7 @@ RequiredBy=remote-fs.target`), }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -1115,7 +1115,7 @@ RequiredBy=remote-fs.target`), }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -1157,7 +1157,7 @@ RequiredBy=swap.target`), }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -1189,7 +1189,7 @@ RequiredBy=swap.target`), for i, test := range tests { t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { - out, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + out, translations, r := test.in.ToIgn3_6Unvalidated(common.TranslateOptions{}) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, test.in, r) assert.Equal(t, test.out, out, "bad output") @@ -1716,7 +1716,7 @@ func TestTranslateTree(t *testing.T) { if test.options != nil { options = *test.options } - actual, translations, r := config.ToIgn3_5Unvalidated(options) + actual, translations, r := config.ToIgn3_6Unvalidated(options) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, config, r) @@ -1744,7 +1744,7 @@ func TestTranslateIgnition(t *testing.T) { { Ignition{}, types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, }, { @@ -1761,7 +1761,7 @@ func TestTranslateIgnition(t *testing.T) { }, }, types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", Config: types.IgnitionConfig{ Merge: []types.Resource{ { @@ -1784,7 +1784,7 @@ func TestTranslateIgnition(t *testing.T) { }, }, types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", Proxy: types.Proxy{ HTTPProxy: util.StrToPtr("https://example.com:8080"), NoProxy: []types.NoProxyItem{types.NoProxyItem("example.com")}, @@ -1804,7 +1804,7 @@ func TestTranslateIgnition(t *testing.T) { }, }, types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", Security: types.Security{ TLS: types.TLS{ CertificateAuthorities: []types.Resource{ @@ -1856,7 +1856,7 @@ func TestTranslateKernelArguments(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, KernelArguments: types.KernelArguments{ ShouldExist: []types.KernelArgument{ @@ -1871,7 +1871,7 @@ func TestTranslateKernelArguments(t *testing.T) { } for i, test := range tests { t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { - actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.in.ToIgn3_6Unvalidated(common.TranslateOptions{}) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, test.in, r) assert.Equal(t, test.out, actual, "translation mismatch") @@ -1916,7 +1916,7 @@ func TestTranslateTang(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -1946,7 +1946,7 @@ func TestTranslateTang(t *testing.T) { } for i, test := range tests { t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { - actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.in.ToIgn3_6Unvalidated(common.TranslateOptions{}) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, test.in, r) assert.Equal(t, test.out, actual, "translation mismatch") @@ -2335,9 +2335,9 @@ func TestTranslateUnitLocal(t *testing.T) { } } -// TestToIgn3_5 tests the config.ToIgn3_5 function ensuring it will generate a valid config even when empty. Not much else is +// TestToIgn3_6 tests the config.ToIgn3_6 function ensuring it will generate a valid config even when empty. Not much else is // tested since it uses the Ignition translation code which has its own set of tests. -func TestToIgn3_5(t *testing.T) { +func TestToIgn3_6(t *testing.T) { tests := []struct { in Config out types.Config @@ -2346,14 +2346,14 @@ func TestToIgn3_5(t *testing.T) { Config{}, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, }, }, } for i, test := range tests { t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { - actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.in.ToIgn3_6Unvalidated(common.TranslateOptions{}) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, test.in, r) assert.Equal(t, test.out, actual, "translation mismatch") diff --git a/base/v0_7_exp/util.go b/base/v0_7_exp/util.go index f92b73c6..dbbd7d29 100644 --- a/base/v0_7_exp/util.go +++ b/base/v0_7_exp/util.go @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_7_exp import ( - "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" ) type nodeTracker struct { diff --git a/base/v0_7_exp/validate.go b/base/v0_7_exp/validate.go index ad3917be..2d7c69e6 100644 --- a/base/v0_7_exp/validate.go +++ b/base/v0_7_exp/validate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_7_exp import ( baseutil "github.com/coreos/butane/base/util" diff --git a/base/v0_7_exp/validate_test.go b/base/v0_7_exp/validate_test.go index 78e72910..499ebb14 100644 --- a/base/v0_7_exp/validate_test.go +++ b/base/v0_7_exp/validate_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_7_exp import ( "fmt" From fba29d0b7f63267f419f9895f86289300d11ca25 Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:21:57 -0500 Subject: [PATCH 03/13] mod: re-vendor to bring in new v3_6_exp type --- .../v2/config/v3_6_experimental/types/cex.go | 33 +++ .../config/v3_6_experimental/types/clevis.go | 49 ++++ .../config/v3_6_experimental/types/config.go | 65 +++++ .../config/v3_6_experimental/types/device.go | 25 ++ .../v3_6_experimental/types/directory.go | 26 ++ .../v2/config/v3_6_experimental/types/disk.go | 135 +++++++++ .../v2/config/v3_6_experimental/types/file.go | 43 +++ .../v3_6_experimental/types/filesystem.go | 106 +++++++ .../config/v3_6_experimental/types/headers.go | 65 +++++ .../v3_6_experimental/types/ignition.go | 49 ++++ .../config/v3_6_experimental/types/kargs.go | 22 ++ .../v2/config/v3_6_experimental/types/luks.go | 82 ++++++ .../v2/config/v3_6_experimental/types/mode.go | 26 ++ .../v2/config/v3_6_experimental/types/node.go | 59 ++++ .../v3_6_experimental/types/partition.go | 91 ++++++ .../config/v3_6_experimental/types/passwd.go | 23 ++ .../v2/config/v3_6_experimental/types/path.go | 42 +++ .../config/v3_6_experimental/types/proxy.go | 49 ++++ .../v2/config/v3_6_experimental/types/raid.go | 62 ++++ .../v3_6_experimental/types/resource.go | 91 ++++++ .../config/v3_6_experimental/types/schema.go | 264 ++++++++++++++++++ .../config/v3_6_experimental/types/storage.go | 115 ++++++++ .../config/v3_6_experimental/types/systemd.go | 61 ++++ .../v2/config/v3_6_experimental/types/tang.go | 65 +++++ .../v2/config/v3_6_experimental/types/tls.go | 27 ++ .../v2/config/v3_6_experimental/types/unit.go | 68 +++++ .../v2/config/v3_6_experimental/types/url.go | 83 ++++++ .../v3_6_experimental/types/verification.go | 71 +++++ vendor/modules.txt | 1 + 29 files changed, 1898 insertions(+) create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/cex.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/clevis.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/config.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/device.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/directory.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/disk.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/file.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/filesystem.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/headers.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/ignition.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/kargs.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/luks.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/mode.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/node.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/partition.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/passwd.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/path.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/proxy.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/raid.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/resource.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/schema.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/storage.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/systemd.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/tang.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/tls.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/unit.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/url.go create mode 100644 vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/verification.go diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/cex.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/cex.go new file mode 100644 index 00000000..b34f5f52 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/cex.go @@ -0,0 +1,33 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (cm Cex) IsPresent() bool { + return util.IsTrue(cm.Enabled) +} + +func (cx Cex) Validate(c path.ContextPath) (r report.Report) { + if !util.IsTrue(cx.Enabled) { + return + } + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/clevis.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/clevis.go new file mode 100644 index 00000000..68887d43 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/clevis.go @@ -0,0 +1,49 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (c Clevis) IsPresent() bool { + return util.NotEmpty(c.Custom.Pin) || + len(c.Tang) > 0 || + util.IsTrue(c.Tpm2) || + c.Threshold != nil && *c.Threshold != 0 +} + +func (cu ClevisCustom) Validate(c path.ContextPath) (r report.Report) { + if util.NilOrEmpty(cu.Pin) && util.NilOrEmpty(cu.Config) && !util.IsTrue(cu.NeedsNetwork) { + return + } + if util.NotEmpty(cu.Pin) { + switch *cu.Pin { + case "tpm2", "tang", "sss": + default: + r.AddOnError(c.Append("pin"), errors.ErrUnknownClevisPin) + } + } else { + r.AddOnError(c.Append("pin"), errors.ErrClevisPinRequired) + } + if util.NilOrEmpty(cu.Config) { + r.AddOnError(c.Append("config"), errors.ErrClevisConfigRequired) + } + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/config.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/config.go new file mode 100644 index 00000000..9428b0bb --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/config.go @@ -0,0 +1,65 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/go-semver/semver" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +var ( + MaxVersion = semver.Version{ + Major: 3, + Minor: 6, + PreRelease: "experimental", + } +) + +func (cfg Config) Validate(c path.ContextPath) (r report.Report) { + systemdPath := "/etc/systemd/system/" + unitPaths := map[string]struct{}{} + for _, unit := range cfg.Systemd.Units { + if !util.NilOrEmpty(unit.Contents) { + pathString := systemdPath + unit.Name + unitPaths[pathString] = struct{}{} + } + for _, dropin := range unit.Dropins { + if !util.NilOrEmpty(dropin.Contents) { + pathString := systemdPath + unit.Name + ".d/" + dropin.Name + unitPaths[pathString] = struct{}{} + } + } + } + for i, f := range cfg.Storage.Files { + if _, exists := unitPaths[f.Path]; exists { + r.AddOnError(c.Append("storage", "files", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, d := range cfg.Storage.Directories { + if _, exists := unitPaths[d.Path]; exists { + r.AddOnError(c.Append("storage", "directories", i, "path"), errors.ErrPathConflictsSystemd) + } + } + for i, l := range cfg.Storage.Links { + if _, exists := unitPaths[l.Path]; exists { + r.AddOnError(c.Append("storage", "links", i, "path"), errors.ErrPathConflictsSystemd) + } + } + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/device.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/device.go new file mode 100644 index 00000000..a10ce97b --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/device.go @@ -0,0 +1,25 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (d Device) Validate(c path.ContextPath) (r report.Report) { + r.AddOnError(c, validatePath(string(d))) + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/directory.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/directory.go new file mode 100644 index 00000000..f6f06845 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/directory.go @@ -0,0 +1,26 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (d Directory) Validate(c path.ContextPath) (r report.Report) { + r.Merge(d.Node.Validate(c)) + r.AddOnError(c.Append("mode"), validateMode(d.Mode)) + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/disk.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/disk.go new file mode 100644 index 00000000..8caf8499 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/disk.go @@ -0,0 +1,135 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (d Disk) Key() string { + return d.Device +} + +func (n Disk) Validate(c path.ContextPath) (r report.Report) { + if len(n.Device) == 0 { + r.AddOnError(c.Append("device"), errors.ErrDiskDeviceRequired) + return + } + r.AddOnError(c.Append("device"), validatePath(n.Device)) + + if collides, p := n.partitionNumbersCollide(); collides { + r.AddOnError(c.Append("partitions", p), errors.ErrPartitionNumbersCollide) + } + if overlaps, p := n.partitionsOverlap(); overlaps { + r.AddOnError(c.Append("partitions", p), errors.ErrPartitionsOverlap) + } + if n.partitionsMixZeroesAndNonexistence() { + r.AddOnError(c.Append("partitions"), errors.ErrZeroesWithShouldNotExist) + } + if collides, p := n.partitionLabelsCollide(); collides { + r.AddOnError(c.Append("partitions", p), errors.ErrDuplicateLabels) + } + return +} + +// partitionNumbersCollide returns true if partition numbers in n.Partitions are not unique. It also returns the +// index of the colliding partition +func (n Disk) partitionNumbersCollide() (bool, int) { + m := map[int][]int{} // from partition number to index into array + for i, p := range n.Partitions { + if p.Number != 0 { + // a number of 0 means next available number, multiple devices can specify this + m[p.Number] = append(m[p.Number], i) + } + } + for _, n := range m { + if len(n) > 1 { + // TODO(vc): return information describing the collision for logging + return true, n[1] + } + } + return false, 0 +} + +func (d Disk) partitionLabelsCollide() (bool, int) { + m := map[string]struct{}{} + for i, p := range d.Partitions { + if p.Label != nil { + // a number of 0 means next available number, multiple devices can specify this + if _, exists := m[*p.Label]; exists { + return true, i + } + m[*p.Label] = struct{}{} + } + } + return false, 0 +} + +// end returns the last sector of a partition. Only used by partitionsOverlap. Requires non-nil Start and Size. +func (p Partition) end() int { + if *p.SizeMiB == 0 { + // a size of 0 means "fill available", just return the start as the end for those. + return *p.StartMiB + } + return *p.StartMiB + *p.SizeMiB - 1 +} + +// partitionsOverlap returns true if any explicitly dimensioned partitions overlap. It also returns the index of +// the overlapping partition +func (n Disk) partitionsOverlap() (bool, int) { + for _, p := range n.Partitions { + // Starts of 0 are placed by sgdisk into the "largest available block" at that time. + // We aren't going to check those for overlap since we don't have the disk geometry. + if p.StartMiB == nil || p.SizeMiB == nil || *p.StartMiB == 0 { + continue + } + + for i, o := range n.Partitions { + if o.StartMiB == nil || o.SizeMiB == nil || p == o || *o.StartMiB == 0 { + continue + } + + // is p.StartMiB within o? + if *p.StartMiB >= *o.StartMiB && *p.StartMiB <= o.end() { + return true, i + } + + // is p.end() within o? + if p.end() >= *o.StartMiB && p.end() <= o.end() { + return true, i + } + + // do p.StartMiB and p.end() straddle o? + if *p.StartMiB < *o.StartMiB && p.end() > o.end() { + return true, i + } + } + } + return false, 0 +} + +func (n Disk) partitionsMixZeroesAndNonexistence() bool { + hasZero := false + hasShouldNotExist := false + for _, p := range n.Partitions { + hasShouldNotExist = hasShouldNotExist || util.IsFalse(p.ShouldExist) + hasZero = hasZero || (p.Number == 0) + } + return hasZero && hasShouldNotExist +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/file.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/file.go new file mode 100644 index 00000000..9b71bb26 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/file.go @@ -0,0 +1,43 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (f File) Validate(c path.ContextPath) (r report.Report) { + r.Merge(f.Node.Validate(c)) + r.AddOnError(c.Append("mode"), validateMode(f.Mode)) + r.AddOnError(c.Append("overwrite"), f.validateOverwrite()) + return +} + +func (f File) validateOverwrite() error { + if util.IsTrue(f.Overwrite) && f.Contents.Source == nil { + return errors.ErrOverwriteAndNilSource + } + return nil +} + +func (f FileEmbedded1) IgnoreDuplicates() map[string]struct{} { + return map[string]struct{}{ + "Append": {}, + } +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/filesystem.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/filesystem.go new file mode 100644 index 00000000..c722b363 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/filesystem.go @@ -0,0 +1,106 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (f Filesystem) Key() string { + return f.Device +} + +func (f Filesystem) IgnoreDuplicates() map[string]struct{} { + return map[string]struct{}{ + "Options": {}, + "MountOptions": {}, + } +} + +func (f Filesystem) Validate(c path.ContextPath) (r report.Report) { + r.AddOnError(c.Append("path"), f.validatePath()) + r.AddOnError(c.Append("device"), validatePath(f.Device)) + r.AddOnError(c.Append("format"), f.validateFormat()) + r.AddOnError(c.Append("label"), f.validateLabel()) + return +} + +func (f Filesystem) validatePath() error { + return validatePathNilOK(f.Path) +} + +func (f Filesystem) validateFormat() error { + if util.NilOrEmpty(f.Format) { + if util.NotEmpty(f.Path) || + util.NotEmpty(f.Label) || + util.NotEmpty(f.UUID) || + util.IsTrue(f.WipeFilesystem) || + len(f.MountOptions) != 0 || + len(f.Options) != 0 { + return errors.ErrFormatNilWithOthers + } + } else { + switch *f.Format { + case "ext4", "btrfs", "xfs", "swap", "vfat", "none": + default: + return errors.ErrFilesystemInvalidFormat + } + } + return nil +} + +func (f Filesystem) validateLabel() error { + if util.NilOrEmpty(f.Label) { + return nil + } + if util.NilOrEmpty(f.Format) { + return errors.ErrLabelNeedsFormat + } + + switch *f.Format { + case "ext4": + if len(*f.Label) > 16 { + // source: man mkfs.ext4 + return errors.ErrExt4LabelTooLong + } + case "btrfs": + if len(*f.Label) > 256 { + // source: man mkfs.btrfs + return errors.ErrBtrfsLabelTooLong + } + case "xfs": + if len(*f.Label) > 12 { + // source: man mkfs.xfs + return errors.ErrXfsLabelTooLong + } + case "swap": + // mkswap's man page does not state a limit on label size, but through + // experimentation it appears that mkswap will truncate long labels to + // 15 characters, so let's enforce that. + if len(*f.Label) > 15 { + return errors.ErrSwapLabelTooLong + } + case "vfat": + if len(*f.Label) > 11 { + // source: man mkfs.fat + return errors.ErrVfatLabelTooLong + } + } + return nil +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/headers.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/headers.go new file mode 100644 index 00000000..be1aadad --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/headers.go @@ -0,0 +1,65 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "net/http" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +// Parse generates standard net/http headers from the data in HTTPHeaders +func (hs HTTPHeaders) Parse() (http.Header, error) { + headers := http.Header{} + for _, header := range hs { + if header.Name == "" { + return nil, errors.ErrEmptyHTTPHeaderName + } + if header.Value == nil || string(*header.Value) == "" { + return nil, errors.ErrInvalidHTTPHeader + } + headers.Add(header.Name, string(*header.Value)) + } + return headers, nil +} + +func (h HTTPHeader) Validate(c path.ContextPath) (r report.Report) { + r.AddOnError(c.Append("name"), h.validateName()) + r.AddOnError(c.Append("value"), h.validateValue()) + return +} + +func (h HTTPHeader) validateName() error { + if h.Name == "" { + return errors.ErrEmptyHTTPHeaderName + } + return nil +} + +func (h HTTPHeader) validateValue() error { + if h.Value == nil { + return nil + } + if string(*h.Value) == "" { + return errors.ErrInvalidHTTPHeader + } + return nil +} + +func (h HTTPHeader) Key() string { + return h.Name +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/ignition.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/ignition.go new file mode 100644 index 00000000..190445bd --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/ignition.go @@ -0,0 +1,49 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/go-semver/semver" + + "github.com/coreos/ignition/v2/config/shared/errors" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (v Ignition) Semver() (*semver.Version, error) { + return semver.NewVersion(v.Version) +} + +func (ic IgnitionConfig) Validate(c path.ContextPath) (r report.Report) { + for i, res := range ic.Merge { + r.AddOnError(c.Append("merge", i), res.validateRequiredSource()) + } + return +} + +func (v Ignition) Validate(c path.ContextPath) (r report.Report) { + c = c.Append("version") + tv, err := v.Semver() + if err != nil { + r.AddOnError(c, errors.ErrInvalidVersion) + return + } + + if MaxVersion != *tv { + r.AddOnError(c, errors.ErrUnknownVersion) + } + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/kargs.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/kargs.go new file mode 100644 index 00000000..42c29408 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/kargs.go @@ -0,0 +1,22 @@ +// 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 types + +func (k KernelArguments) MergedKeys() map[string]string { + return map[string]string{ + "ShouldExist": "KernelArgument", + "ShouldNotExist": "KernelArgument", + } +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/luks.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/luks.go new file mode 100644 index 00000000..e4c1d681 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/luks.go @@ -0,0 +1,82 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "strings" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (l Luks) Key() string { + return l.Name +} + +func (l Luks) IgnoreDuplicates() map[string]struct{} { + return map[string]struct{}{ + "Options": {}, + } +} + +func (l Luks) Validate(c path.ContextPath) (r report.Report) { + if strings.Contains(l.Name, "/") { + r.AddOnError(c.Append("name"), errors.ErrLuksNameContainsSlash) + } + r.AddOnError(c.Append("label"), l.validateLabel()) + if util.NilOrEmpty(l.Device) { + r.AddOnError(c.Append("device"), errors.ErrDiskDeviceRequired) + } else { + r.AddOnError(c.Append("device"), validatePath(*l.Device)) + } + + if util.NotEmpty(l.Clevis.Custom.Pin) && (len(l.Clevis.Tang) > 0 || util.IsTrue(l.Clevis.Tpm2) || (l.Clevis.Threshold != nil && *l.Clevis.Threshold != 0)) { + r.AddOnError(c.Append("clevis"), errors.ErrClevisCustomWithOthers) + } + + // fail if a key file is provided and is not valid + if err := validateURLNilOK(l.KeyFile.Source); err != nil { + r.AddOnError(c.Append("keys"), errors.ErrInvalidLuksKeyFile) + } + + // fail if Cex use with Clevis + if l.Clevis.IsPresent() && l.Cex.IsPresent() { + r.AddOnError(c.Append("cex"), errors.ErrCexWithClevis) + } + + // fail if key file is provided along with Cex + if l.Cex.IsPresent() && util.NotEmpty(l.KeyFile.Source) { + r.AddOnError(c.Append("cex"), errors.ErrCexWithKeyFile) + } + + return +} + +func (l Luks) validateLabel() error { + if util.NilOrEmpty(l.Label) { + return nil + } + + if len(*l.Label) > 47 { + // LUKS2_LABEL_L has a maximum length of 48 (including the null terminator) + // https://gitlab.com/cryptsetup/cryptsetup/-/blob/1633f030e89ad2f11ae649ba9600997a41abd3fc/lib/luks2/luks2.h#L86 + return errors.ErrLuksLabelTooLong + } + + return nil +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/mode.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/mode.go new file mode 100644 index 00000000..9eb7573d --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/mode.go @@ -0,0 +1,26 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/ignition/v2/config/shared/errors" +) + +func validateMode(m *int) error { + if m != nil && (*m < 0 || *m > 07777) { + return errors.ErrFileIllegalMode + } + return nil +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/node.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/node.go new file mode 100644 index 00000000..248276e7 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/node.go @@ -0,0 +1,59 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "path" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + vpath "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (n Node) Key() string { + return n.Path +} + +func (n Node) Validate(c vpath.ContextPath) (r report.Report) { + r.AddOnError(c.Append("path"), validatePath(n.Path)) + return +} + +func (n Node) Depth() int { + count := 0 + for p := path.Clean(string(n.Path)); p != "/"; count++ { + p = path.Dir(p) + } + return count +} + +func validateIDorName(id *int, name *string) error { + if id != nil && util.NotEmpty(name) { + return errors.ErrBothIDAndNameSet + } + return nil +} + +func (nu NodeUser) Validate(c vpath.ContextPath) (r report.Report) { + r.AddOnError(c, validateIDorName(nu.ID, nu.Name)) + return +} + +func (ng NodeGroup) Validate(c vpath.ContextPath) (r report.Report) { + r.AddOnError(c, validateIDorName(ng.ID, ng.Name)) + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/partition.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/partition.go new file mode 100644 index 00000000..1b2d97ed --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/partition.go @@ -0,0 +1,91 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "fmt" + "regexp" + "strings" + + "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" +) + +const ( + guidRegexStr = "^(|[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12})$" +) + +var ( + guidRegex = regexp.MustCompile(guidRegexStr) +) + +func (p Partition) Key() string { + if p.Number != 0 { + return fmt.Sprintf("number:%d", p.Number) + } else if p.Label != nil { + return fmt.Sprintf("label:%s", *p.Label) + } else { + return "" + } +} + +func (p Partition) Validate(c path.ContextPath) (r report.Report) { + if util.IsFalse(p.ShouldExist) && + (p.Label != nil || util.NotEmpty(p.TypeGUID) || util.NotEmpty(p.GUID) || p.StartMiB != nil || p.SizeMiB != nil) { + r.AddOnError(c, errors.ErrShouldNotExistWithOthers) + } + if p.Number == 0 && p.Label == nil { + r.AddOnError(c, errors.ErrNeedLabelOrNumber) + } + + r.AddOnError(c.Append("label"), p.validateLabel()) + r.AddOnError(c.Append("guid"), validateGUID(p.GUID)) + r.AddOnError(c.Append("typeGuid"), validateGUID(p.TypeGUID)) + return +} + +func (p Partition) validateLabel() error { + if p.Label == nil { + return nil + } + // http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_entries: + // 56 (0x38) 72 bytes Partition name (36 UTF-16LE code units) + + // XXX(vc): note GPT calls it a name, we're using label for consistency + // with udev naming /dev/disk/by-partlabel/*. + if len(*p.Label) > 36 { + return errors.ErrLabelTooLong + } + + // sgdisk uses colons for delimitting compound arguments and does not allow escaping them. + if strings.Contains(*p.Label, ":") { + return errors.ErrLabelContainsColon + } + return nil +} + +func validateGUID(guidPointer *string) error { + if guidPointer == nil { + return nil + } + guid := *guidPointer + if ok := guidRegex.MatchString(guid); !ok { + return errors.ErrDoesntMatchGUIDRegex + } + return nil +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/passwd.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/passwd.go new file mode 100644 index 00000000..4060a2a6 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/passwd.go @@ -0,0 +1,23 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +func (p PasswdUser) Key() string { + return p.Name +} + +func (g PasswdGroup) Key() string { + return g.Name +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/path.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/path.go new file mode 100644 index 00000000..131e300c --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/path.go @@ -0,0 +1,42 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "path" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" +) + +func validatePath(p string) error { + if p == "" { + return errors.ErrNoPath + } + if !path.IsAbs(p) { + return errors.ErrPathRelative + } + if path.Clean(p) != p { + return errors.ErrDirtyPath + } + return nil +} + +func validatePathNilOK(p *string) error { + if util.NilOrEmpty(p) { + return nil + } + return validatePath(*p) +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/proxy.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/proxy.go new file mode 100644 index 00000000..d48d210a --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/proxy.go @@ -0,0 +1,49 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "net/url" + + "github.com/coreos/ignition/v2/config/shared/errors" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (p Proxy) Validate(c path.ContextPath) (r report.Report) { + validateProxyURL(p.HTTPProxy, c.Append("httpProxy"), &r, true) + validateProxyURL(p.HTTPSProxy, c.Append("httpsProxy"), &r, false) + return +} + +func validateProxyURL(s *string, p path.ContextPath, r *report.Report, httpOk bool) { + if s == nil { + return + } + u, err := url.Parse(*s) + if err != nil { + r.AddOnError(p, errors.ErrInvalidUrl) + return + } + + if u.Scheme != "https" && u.Scheme != "http" { + r.AddOnError(p, errors.ErrInvalidProxy) + return + } + if u.Scheme == "http" && !httpOk { + r.AddOnWarn(p, errors.ErrInsecureProxy) + } +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/raid.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/raid.go new file mode 100644 index 00000000..9d69aa36 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/raid.go @@ -0,0 +1,62 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (r Raid) Key() string { + return r.Name +} + +func (r Raid) IgnoreDuplicates() map[string]struct{} { + return map[string]struct{}{ + "Options": {}, + } +} + +func (ra Raid) Validate(c path.ContextPath) (r report.Report) { + r.AddOnError(c.Append("level"), ra.validateLevel()) + if len(ra.Devices) == 0 { + r.AddOnError(c.Append("devices"), errors.ErrRaidDevicesRequired) + } + return +} + +func (r Raid) validateLevel() error { + if util.NilOrEmpty(r.Level) { + return errors.ErrRaidLevelRequired + } + switch *r.Level { + case "linear", "raid0", "0", "stripe": + if r.Spares != nil && *r.Spares != 0 { + return errors.ErrSparesUnsupportedForLevel + } + case "raid1", "1", "mirror": + case "raid4", "4": + case "raid5", "5": + case "raid6", "6": + case "raid10", "10": + default: + return errors.ErrUnrecognizedRaidLevel + } + + return nil +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/resource.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/resource.go new file mode 100644 index 00000000..68da6c7b --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/resource.go @@ -0,0 +1,91 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "net/url" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (res Resource) Key() string { + if res.Source == nil { + return "" + } + return *res.Source +} + +func (res Resource) Validate(c path.ContextPath) (r report.Report) { + r.AddOnError(c.Append("compression"), res.validateCompression()) + r.AddOnError(c.Append("verification", "hash"), res.validateVerification()) + r.AddOnError(c.Append("source"), validateURLNilOK(res.Source)) + r.AddOnError(c.Append("httpHeaders"), res.validateSchemeForHTTPHeaders()) + return +} + +func (res Resource) validateCompression() error { + if res.Compression != nil { + switch *res.Compression { + case "", "gzip": + default: + return errors.ErrCompressionInvalid + } + } + return nil +} + +func (res Resource) validateVerification() error { + if res.Verification.Hash != nil && res.Source == nil { + return errors.ErrVerificationAndNilSource + } + return nil +} + +func (res Resource) validateSchemeForHTTPHeaders() error { + if len(res.HTTPHeaders) < 1 { + return nil + } + + if util.NilOrEmpty(res.Source) { + return errors.ErrInvalidUrl + } + + u, err := url.Parse(*res.Source) + if err != nil { + return errors.ErrInvalidUrl + } + + switch u.Scheme { + case "http", "https": + return nil + default: + return errors.ErrUnsupportedSchemeForHTTPHeaders + } +} + +// Ensure that the Source is specified and valid. This is not called by +// Resource.Validate() because some structs that embed Resource don't +// require Source to be specified. Containing structs that require Source +// should call this function from their Validate(). +func (res Resource) validateRequiredSource() error { + if util.NilOrEmpty(res.Source) { + return errors.ErrSourceRequired + } + return validateURL(*res.Source) +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/schema.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/schema.go new file mode 100644 index 00000000..c652d666 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/schema.go @@ -0,0 +1,264 @@ +package types + +// generated by "schematyper --package=types config/v3_6_experimental/schema/ignition.json -o config/v3_6_experimental/types/schema.go --root-type=Config" -- DO NOT EDIT + +type Cex struct { + Enabled *bool `json:"enabled,omitempty"` +} + +type Clevis struct { + Custom ClevisCustom `json:"custom,omitempty"` + Tang []Tang `json:"tang,omitempty"` + Threshold *int `json:"threshold,omitempty"` + Tpm2 *bool `json:"tpm2,omitempty"` +} + +type ClevisCustom struct { + Config *string `json:"config,omitempty"` + NeedsNetwork *bool `json:"needsNetwork,omitempty"` + Pin *string `json:"pin,omitempty"` +} + +type Config struct { + Ignition Ignition `json:"ignition"` + KernelArguments KernelArguments `json:"kernelArguments,omitempty"` + Passwd Passwd `json:"passwd,omitempty"` + Storage Storage `json:"storage,omitempty"` + Systemd Systemd `json:"systemd,omitempty"` +} + +type Device string + +type Directory struct { + Node + DirectoryEmbedded1 +} + +type DirectoryEmbedded1 struct { + Mode *int `json:"mode,omitempty"` +} + +type Disk struct { + Device string `json:"device"` + Partitions []Partition `json:"partitions,omitempty"` + WipeTable *bool `json:"wipeTable,omitempty"` +} + +type Dropin struct { + Contents *string `json:"contents,omitempty"` + Name string `json:"name"` +} + +type File struct { + Node + FileEmbedded1 +} + +type FileEmbedded1 struct { + Append []Resource `json:"append,omitempty"` + Contents Resource `json:"contents,omitempty"` + Mode *int `json:"mode,omitempty"` +} + +type Filesystem struct { + Device string `json:"device"` + Format *string `json:"format,omitempty"` + Label *string `json:"label,omitempty"` + MountOptions []MountOption `json:"mountOptions,omitempty"` + Options []FilesystemOption `json:"options,omitempty"` + Path *string `json:"path,omitempty"` + UUID *string `json:"uuid,omitempty"` + WipeFilesystem *bool `json:"wipeFilesystem,omitempty"` +} + +type FilesystemOption string + +type Group string + +type HTTPHeader struct { + Name string `json:"name"` + Value *string `json:"value,omitempty"` +} + +type HTTPHeaders []HTTPHeader + +type Ignition struct { + Config IgnitionConfig `json:"config,omitempty"` + Proxy Proxy `json:"proxy,omitempty"` + Security Security `json:"security,omitempty"` + Timeouts Timeouts `json:"timeouts,omitempty"` + Version string `json:"version"` +} + +type IgnitionConfig struct { + Merge []Resource `json:"merge,omitempty"` + Replace Resource `json:"replace,omitempty"` +} + +type KernelArgument string + +type KernelArguments struct { + ShouldExist []KernelArgument `json:"shouldExist,omitempty"` + ShouldNotExist []KernelArgument `json:"shouldNotExist,omitempty"` +} + +type Link struct { + Node + LinkEmbedded1 +} + +type LinkEmbedded1 struct { + Hard *bool `json:"hard,omitempty"` + Target *string `json:"target,omitempty"` +} + +type Luks struct { + Cex Cex `json:"cex,omitempty"` + Clevis Clevis `json:"clevis,omitempty"` + Device *string `json:"device,omitempty"` + Discard *bool `json:"discard,omitempty"` + KeyFile Resource `json:"keyFile,omitempty"` + Label *string `json:"label,omitempty"` + Name string `json:"name"` + OpenOptions []OpenOption `json:"openOptions,omitempty"` + Options []LuksOption `json:"options,omitempty"` + UUID *string `json:"uuid,omitempty"` + WipeVolume *bool `json:"wipeVolume,omitempty"` +} + +type LuksOption string + +type MountOption string + +type NoProxyItem string + +type Node struct { + Group NodeGroup `json:"group,omitempty"` + Overwrite *bool `json:"overwrite,omitempty"` + Path string `json:"path"` + User NodeUser `json:"user,omitempty"` +} + +type NodeGroup struct { + ID *int `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +type NodeUser struct { + ID *int `json:"id,omitempty"` + Name *string `json:"name,omitempty"` +} + +type OpenOption string + +type Partition struct { + GUID *string `json:"guid,omitempty"` + Label *string `json:"label,omitempty"` + Number int `json:"number,omitempty"` + Resize *bool `json:"resize,omitempty"` + ShouldExist *bool `json:"shouldExist,omitempty"` + SizeMiB *int `json:"sizeMiB,omitempty"` + StartMiB *int `json:"startMiB,omitempty"` + TypeGUID *string `json:"typeGuid,omitempty"` + WipePartitionEntry *bool `json:"wipePartitionEntry,omitempty"` +} + +type Passwd struct { + Groups []PasswdGroup `json:"groups,omitempty"` + Users []PasswdUser `json:"users,omitempty"` +} + +type PasswdGroup struct { + Gid *int `json:"gid,omitempty"` + Name string `json:"name"` + PasswordHash *string `json:"passwordHash,omitempty"` + ShouldExist *bool `json:"shouldExist,omitempty"` + System *bool `json:"system,omitempty"` +} + +type PasswdUser struct { + Gecos *string `json:"gecos,omitempty"` + Groups []Group `json:"groups,omitempty"` + HomeDir *string `json:"homeDir,omitempty"` + Name string `json:"name"` + NoCreateHome *bool `json:"noCreateHome,omitempty"` + NoLogInit *bool `json:"noLogInit,omitempty"` + NoUserGroup *bool `json:"noUserGroup,omitempty"` + PasswordHash *string `json:"passwordHash,omitempty"` + PrimaryGroup *string `json:"primaryGroup,omitempty"` + SSHAuthorizedKeys []SSHAuthorizedKey `json:"sshAuthorizedKeys,omitempty"` + Shell *string `json:"shell,omitempty"` + ShouldExist *bool `json:"shouldExist,omitempty"` + System *bool `json:"system,omitempty"` + UID *int `json:"uid,omitempty"` +} + +type Proxy struct { + HTTPProxy *string `json:"httpProxy,omitempty"` + HTTPSProxy *string `json:"httpsProxy,omitempty"` + NoProxy []NoProxyItem `json:"noProxy,omitempty"` +} + +type Raid struct { + Devices []Device `json:"devices,omitempty"` + Level *string `json:"level,omitempty"` + Name string `json:"name"` + Options []RaidOption `json:"options,omitempty"` + Spares *int `json:"spares,omitempty"` +} + +type RaidOption string + +type Resource struct { + Compression *string `json:"compression,omitempty"` + HTTPHeaders HTTPHeaders `json:"httpHeaders,omitempty"` + Source *string `json:"source,omitempty"` + Verification Verification `json:"verification,omitempty"` +} + +type SSHAuthorizedKey string + +type Security struct { + TLS TLS `json:"tls,omitempty"` +} + +type Storage struct { + Directories []Directory `json:"directories,omitempty"` + Disks []Disk `json:"disks,omitempty"` + Files []File `json:"files,omitempty"` + Filesystems []Filesystem `json:"filesystems,omitempty"` + Links []Link `json:"links,omitempty"` + Luks []Luks `json:"luks,omitempty"` + Raid []Raid `json:"raid,omitempty"` +} + +type Systemd struct { + Units []Unit `json:"units,omitempty"` +} + +type TLS struct { + CertificateAuthorities []Resource `json:"certificateAuthorities,omitempty"` +} + +type Tang struct { + Advertisement *string `json:"advertisement,omitempty"` + Thumbprint *string `json:"thumbprint,omitempty"` + URL string `json:"url,omitempty"` +} + +type Timeouts struct { + HTTPResponseHeaders *int `json:"httpResponseHeaders,omitempty"` + HTTPTotal *int `json:"httpTotal,omitempty"` +} + +type Unit struct { + Contents *string `json:"contents,omitempty"` + Dropins []Dropin `json:"dropins,omitempty"` + Enabled *bool `json:"enabled,omitempty"` + Mask *bool `json:"mask,omitempty"` + Name string `json:"name"` +} + +type Verification struct { + Hash *string `json:"hash,omitempty"` +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/storage.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/storage.go new file mode 100644 index 00000000..20cb7304 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/storage.go @@ -0,0 +1,115 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "path" + "strings" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + vpath "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (s Storage) MergedKeys() map[string]string { + return map[string]string{ + "Directories": "Node", + "Files": "Node", + "Links": "Node", + } +} + +func (s Storage) Validate(c vpath.ContextPath) (r report.Report) { + s.validateDirectories(c, &r) + s.validateFiles(c, &r) + s.validateLinks(c, &r) + s.validateFilesystems(c, &r) + return +} + +func (s Storage) validateDirectories(c vpath.ContextPath, r *report.Report) { + for i, d := range s.Directories { + for _, l := range s.Links { + if strings.HasPrefix(d.Path, l.Path+"/") { + r.AddOnError(c.Append("directories", i), errors.ErrDirectoryUsedSymlink) + } + } + } +} + +func (s Storage) validateFiles(c vpath.ContextPath, r *report.Report) { + for i, f := range s.Files { + for _, l := range s.Links { + if strings.HasPrefix(f.Path, l.Path+"/") { + r.AddOnError(c.Append("files", i), errors.ErrFileUsedSymlink) + } + } + } +} + +func (s Storage) validateLinks(c vpath.ContextPath, r *report.Report) { + for i, l1 := range s.Links { + for _, l2 := range s.Links { + if strings.HasPrefix(l1.Path, l2.Path+"/") { + r.AddOnError(c.Append("links", i), errors.ErrLinkUsedSymlink) + } + } + if util.NilOrEmpty(l1.Target) { + r.AddOnError(c.Append("links", i, "target"), errors.ErrLinkTargetRequired) + continue + } + if !util.IsTrue(l1.Hard) { + continue + } + target := path.Clean(*l1.Target) + if !path.IsAbs(target) { + target = path.Join(l1.Path, *l1.Target) + } + for _, d := range s.Directories { + if target == d.Path { + r.AddOnError(c.Append("links", i), errors.ErrHardLinkToDirectory) + } + } + ownerCheck := func(ok bool, path vpath.ContextPath) { + if !ok { + r.AddOnWarn(path, errors.ErrHardLinkSpecifiesOwner) + } + } + ownerCheck(l1.User.ID == nil, c.Append("links", i, "user", "id")) + ownerCheck(l1.User.Name == nil, c.Append("links", i, "user", "name")) + ownerCheck(l1.Group.ID == nil, c.Append("links", i, "group", "id")) + ownerCheck(l1.Group.Name == nil, c.Append("links", i, "group", "name")) + } +} + +func (s Storage) validateFilesystems(c vpath.ContextPath, r *report.Report) { + disks := make(map[string]Disk) + for _, d := range s.Disks { + disks[d.Device] = d + } + + for i, f := range s.Filesystems { + disk, exist := disks[f.Device] + if exist { + if len(disk.Partitions) > 0 { + r.AddOnWarn(c.Append("filesystems", i, "device"), errors.ErrPartitionsOverwritten) + } else if !util.IsTrue(f.WipeFilesystem) && util.IsTrue(disk.WipeTable) { + r.AddOnWarn(c.Append("filesystems", i, "device"), errors.ErrFilesystemImplicitWipe) + } + } + } +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/systemd.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/systemd.go new file mode 100644 index 00000000..ac521ba7 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/systemd.go @@ -0,0 +1,61 @@ +// Copyright 2022 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "regexp" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/shared/parse" + "github.com/coreos/ignition/v2/config/util" + + vpath "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (s Systemd) Validate(c vpath.ContextPath) (r report.Report) { + units := make(map[string]Unit) + checkInstanceUnit := regexp.MustCompile(`^(.+?)@(.+?)\.service$`) + for _, d := range s.Units { + units[d.Name] = d + } + for index, unit := range s.Units { + if checkInstanceUnit.MatchString(unit.Name) && util.IsTrue(unit.Enabled) { + instUnitSlice := checkInstanceUnit.FindSubmatch([]byte(unit.Name)) + instantiableUnit := string(instUnitSlice[1]) + "@.service" + if _, ok := units[instantiableUnit]; ok && util.NotEmpty(units[instantiableUnit].Contents) { + foundInstallSection := false + // we're doing a separate validation pass on each unit to identify + // if an instantiable unit has the install section. So logging an + // `AddOnError` will produce duplicate errors on bad unit contents + // because we're already doing that while validating a unit separately. + opts, err := parse.ParseUnitContents(units[instantiableUnit].Contents) + if err != nil { + continue + } + for _, section := range opts { + if section.Section == "Install" { + foundInstallSection = true + break + } + } + if !foundInstallSection { + r.AddOnWarn(c.Append("units", index, "contents"), errors.NewNoInstallSectionForInstantiableUnitError(instantiableUnit, unit.Name)) + } + } + } + } + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/tang.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/tang.go new file mode 100644 index 00000000..1839d6cc --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/tang.go @@ -0,0 +1,65 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "encoding/json" + "net/url" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (t Tang) Key() string { + return t.URL +} + +func (t Tang) Validate(c path.ContextPath) (r report.Report) { + r.AddOnError(c.Append("url"), validateTangURL(t.URL)) + if util.NilOrEmpty(t.Thumbprint) { + r.AddOnError(c.Append("thumbprint"), errors.ErrTangThumbprintRequired) + } + r.AddOnError(c.Append("advertisement"), validateTangAdvertisement(t.Advertisement)) + return +} + +func validateTangURL(s string) error { + u, err := url.Parse(s) + if err != nil { + return errors.ErrInvalidUrl + } + + switch u.Scheme { + case "http", "https": + return nil + default: + return errors.ErrInvalidScheme + } +} + +func validateTangAdvertisement(s *string) error { + if util.NotEmpty(s) { + var adv any + err := json.Unmarshal([]byte(*s), &adv) + if err != nil { + return errors.ErrInvalidTangAdvertisement + } + } + + return nil +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/tls.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/tls.go new file mode 100644 index 00000000..8890e397 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/tls.go @@ -0,0 +1,27 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (tls TLS) Validate(c path.ContextPath) (r report.Report) { + for i, ca := range tls.CertificateAuthorities { + r.AddOnError(c.Append("certificateAuthorities", i), ca.validateRequiredSource()) + } + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/unit.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/unit.go new file mode 100644 index 00000000..c5ee1e8e --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/unit.go @@ -0,0 +1,68 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "path" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/shared/parse" + "github.com/coreos/ignition/v2/config/shared/validations" + "github.com/coreos/ignition/v2/config/util" + + cpath "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +func (u Unit) Key() string { + return u.Name +} + +func (d Dropin) Key() string { + return d.Name +} + +func (u Unit) Validate(c cpath.ContextPath) (r report.Report) { + r.AddOnError(c.Append("name"), validateName(u.Name)) + c = c.Append("contents") + opts, err := parse.ParseUnitContents(u.Contents) + r.AddOnError(c, err) + + r.AddOnWarn(c, validations.ValidateInstallSection(u.Name, util.IsTrue(u.Enabled), util.NilOrEmpty(u.Contents), opts)) + + return +} + +func validateName(name string) error { + switch path.Ext(name) { + case ".service", ".socket", ".device", ".mount", ".automount", ".swap", ".target", ".path", ".timer", ".snapshot", ".slice", ".scope": + default: + return errors.ErrInvalidSystemdExt + } + return nil +} + +func (d Dropin) Validate(c cpath.ContextPath) (r report.Report) { + _, err := parse.ParseUnitContents(d.Contents) + r.AddOnError(c.Append("contents"), err) + + switch path.Ext(d.Name) { + case ".conf": + default: + r.AddOnError(c.Append("name"), errors.ErrInvalidSystemdDropinExt) + } + + return +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/url.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/url.go new file mode 100644 index 00000000..3ca189da --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/url.go @@ -0,0 +1,83 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "net/url" + "strings" + + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/vincent-petithory/dataurl" + + "github.com/coreos/ignition/v2/config/shared/errors" + "github.com/coreos/ignition/v2/config/util" +) + +func validateURL(s string) error { + u, err := url.Parse(s) + if err != nil { + return errors.ErrInvalidUrl + } + + switch u.Scheme { + case "http", "https", "tftp", "gs": + return nil + case "s3": + if v, ok := u.Query()["versionId"]; ok { + if len(v) == 0 || v[0] == "" { + return errors.ErrInvalidS3ObjectVersionId + } + } + return nil + case "arn": + fullURL := u.Scheme + ":" + u.Opaque + if !arn.IsARN(fullURL) { + return errors.ErrInvalidS3ARN + } + s3arn, err := arn.Parse(fullURL) + if err != nil { + return err + } + if s3arn.Service != "s3" { + return errors.ErrInvalidS3ARN + } + urlSplit := strings.Split(fullURL, "/") + if strings.HasPrefix(s3arn.Resource, "accesspoint/") && len(urlSplit) < 3 { + return errors.ErrInvalidS3ARN + } else if len(urlSplit) < 2 { + return errors.ErrInvalidS3ARN + } + if v, ok := u.Query()["versionId"]; ok { + if len(v) == 0 || v[0] == "" { + return errors.ErrInvalidS3ObjectVersionId + } + } + return nil + case "data": + if _, err := dataurl.DecodeString(s); err != nil { + return err + } + return nil + default: + return errors.ErrInvalidScheme + } +} + +func validateURLNilOK(s *string) error { + if util.NilOrEmpty(s) { + return nil + } + return validateURL(*s) +} diff --git a/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/verification.go b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/verification.go new file mode 100644 index 00000000..5def6f04 --- /dev/null +++ b/vendor/github.com/coreos/ignition/v2/config/v3_6_experimental/types/verification.go @@ -0,0 +1,71 @@ +// Copyright 2020 Red Hat, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package types + +import ( + "crypto" + "encoding/hex" + "strings" + + "github.com/coreos/ignition/v2/config/shared/errors" + + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +// HashParts will return the sum and function (in that order) of the hash stored +// in this Verification, or an error if there is an issue during parsing. +func (v Verification) HashParts() (string, string, error) { + if v.Hash == nil { + // The hash can be nil + return "", "", nil + } + parts := strings.SplitN(*v.Hash, "-", 2) + if len(parts) != 2 { + return "", "", errors.ErrHashMalformed + } + + return parts[0], parts[1], nil +} + +func (v Verification) Validate(c path.ContextPath) (r report.Report) { + c = c.Append("hash") + if v.Hash == nil { + // The hash can be nil + return + } + + function, sum, err := v.HashParts() + if err != nil { + r.AddOnError(c, err) + return + } + var hash crypto.Hash + switch function { + case "sha512": + hash = crypto.SHA512 + case "sha256": + hash = crypto.SHA256 + default: + r.AddOnError(c, errors.ErrHashUnrecognized) + return + } + + if len(sum) != hex.EncodedLen(hash.Size()) { + r.AddOnError(c, errors.ErrHashWrongSize) + } + + return +} diff --git a/vendor/modules.txt b/vendor/modules.txt index b0f0d9db..3abbdf56 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -27,6 +27,7 @@ github.com/coreos/ignition/v2/config/v3_2/types github.com/coreos/ignition/v2/config/v3_3/types github.com/coreos/ignition/v2/config/v3_4/types github.com/coreos/ignition/v2/config/v3_5/types +github.com/coreos/ignition/v2/config/v3_6_experimental/types github.com/coreos/ignition/v2/config/validate # github.com/coreos/vcontext v0.0.0-20230201181013-d72178a18687 ## explicit; go 1.18 From 0d098cad263ade94f3db228e6d39b68ad66fb68e Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:24:36 -0500 Subject: [PATCH 04/13] base/v0_6: stabilize v0_6_exp to v0_6 --- base/{v0_6_exp => v0_6}/schema.go | 2 +- base/{v0_6_exp => v0_6}/translate.go | 2 +- base/{v0_6_exp => v0_6}/translate_test.go | 2 +- base/{v0_6_exp => v0_6}/util.go | 2 +- base/{v0_6_exp => v0_6}/validate.go | 2 +- base/{v0_6_exp => v0_6}/validate_test.go | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename base/{v0_6_exp => v0_6}/schema.go (99%) rename base/{v0_6_exp => v0_6}/translate.go (99%) rename base/{v0_6_exp => v0_6}/translate_test.go (99%) rename base/{v0_6_exp => v0_6}/util.go (99%) rename base/{v0_6_exp => v0_6}/validate.go (99%) rename base/{v0_6_exp => v0_6}/validate_test.go (99%) diff --git a/base/v0_6_exp/schema.go b/base/v0_6/schema.go similarity index 99% rename from base/v0_6_exp/schema.go rename to base/v0_6/schema.go index 1104198f..bb56c372 100644 --- a/base/v0_6_exp/schema.go +++ b/base/v0_6/schema.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_6 type Cex struct { Enabled *bool `yaml:"enabled"` diff --git a/base/v0_6_exp/translate.go b/base/v0_6/translate.go similarity index 99% rename from base/v0_6_exp/translate.go rename to base/v0_6/translate.go index b17f5a52..76921684 100644 --- a/base/v0_6_exp/translate.go +++ b/base/v0_6/translate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_6 import ( "fmt" diff --git a/base/v0_6_exp/translate_test.go b/base/v0_6/translate_test.go similarity index 99% rename from base/v0_6_exp/translate_test.go rename to base/v0_6/translate_test.go index 8b549055..1dc5bcb2 100644 --- a/base/v0_6_exp/translate_test.go +++ b/base/v0_6/translate_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_6 import ( "fmt" diff --git a/base/v0_6_exp/util.go b/base/v0_6/util.go similarity index 99% rename from base/v0_6_exp/util.go rename to base/v0_6/util.go index f92b73c6..fc0ce33e 100644 --- a/base/v0_6_exp/util.go +++ b/base/v0_6/util.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_6 import ( "github.com/coreos/ignition/v2/config/v3_5/types" diff --git a/base/v0_6_exp/validate.go b/base/v0_6/validate.go similarity index 99% rename from base/v0_6_exp/validate.go rename to base/v0_6/validate.go index ad3917be..9c0d796a 100644 --- a/base/v0_6_exp/validate.go +++ b/base/v0_6/validate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_6 import ( baseutil "github.com/coreos/butane/base/util" diff --git a/base/v0_6_exp/validate_test.go b/base/v0_6/validate_test.go similarity index 99% rename from base/v0_6_exp/validate_test.go rename to base/v0_6/validate_test.go index 78e72910..0c2c7494 100644 --- a/base/v0_6_exp/validate_test.go +++ b/base/v0_6/validate_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v0_6_exp +package v0_6 import ( "fmt" From 97194bc7f199e747a1ee00c23f6e6df9c50655d6 Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:33:40 -0500 Subject: [PATCH 05/13] fcos: copy v1_6_exp to v1_7_exp --- config/fcos/v1_7_exp/schema.go | 53 + config/fcos/v1_7_exp/translate.go | 413 ++++++ config/fcos/v1_7_exp/translate_test.go | 1639 ++++++++++++++++++++++++ config/fcos/v1_7_exp/validate.go | 115 ++ config/fcos/v1_7_exp/validate_test.go | 605 +++++++++ 5 files changed, 2825 insertions(+) create mode 100644 config/fcos/v1_7_exp/schema.go create mode 100644 config/fcos/v1_7_exp/translate.go create mode 100644 config/fcos/v1_7_exp/translate_test.go create mode 100644 config/fcos/v1_7_exp/validate.go create mode 100644 config/fcos/v1_7_exp/validate_test.go diff --git a/config/fcos/v1_7_exp/schema.go b/config/fcos/v1_7_exp/schema.go new file mode 100644 index 00000000..c72795ca --- /dev/null +++ b/config/fcos/v1_7_exp/schema.go @@ -0,0 +1,53 @@ +// 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 v1_6_exp + +import ( + base "github.com/coreos/butane/base/v0_6_exp" +) + +type Config struct { + base.Config `yaml:",inline"` + BootDevice BootDevice `yaml:"boot_device"` + Grub Grub `yaml:"grub"` +} + +type BootDevice struct { + Layout *string `yaml:"layout"` + Luks BootDeviceLuks `yaml:"luks"` + Mirror BootDeviceMirror `yaml:"mirror"` +} + +type BootDeviceLuks struct { + Cex base.Cex `yaml:"cex"` + Discard *bool `yaml:"discard"` + Device *string `yaml:"device"` + Tang []base.Tang `yaml:"tang"` + Threshold *int `yaml:"threshold"` + Tpm2 *bool `yaml:"tpm2"` +} + +type BootDeviceMirror struct { + Devices []string `yaml:"devices"` +} + +type Grub struct { + Users []GrubUser `yaml:"users"` +} + +type GrubUser struct { + Name string `yaml:"name"` + PasswordHash *string `yaml:"password_hash"` +} diff --git a/config/fcos/v1_7_exp/translate.go b/config/fcos/v1_7_exp/translate.go new file mode 100644 index 00000000..1428866d --- /dev/null +++ b/config/fcos/v1_7_exp/translate.go @@ -0,0 +1,413 @@ +// 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 v1_6_exp + +import ( + "fmt" + "strings" + + baseutil "github.com/coreos/butane/base/util" + "github.com/coreos/butane/config/common" + 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_5/types" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" +) + +const ( + reservedTypeGuid = "8DA63339-0007-60C0-C436-083AC8230908" + biosTypeGuid = "21686148-6449-6E6F-744E-656564454649" + prepTypeGuid = "9E1A2D38-C612-4316-AA26-8B49521E5A8B" + espTypeGuid = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B" + + // The partition layout implemented in this file replicates + // the layout of the OS image defined in: + // https://github.com/coreos/coreos-assembler/blob/main/src/create_disk.sh + // + // It's not critical that we match that layout exactly; the hard + // constraints are: + // - The desugared partition cannot be smaller than the one it + // replicates + // - The new BIOS-BOOT partition (and maybe the PReP one?) must be + // at the same offset as the original + // + // Do not change these constants! New partition layouts must be + // encoded into new layout templates. + reservedV1SizeMiB = 1 + biosV1SizeMiB = 1 + prepV1SizeMiB = 4 + espV1SizeMiB = 127 + bootV1SizeMiB = 384 +) + +// Return FieldFilters for this spec. +func (c Config) FieldFilters() *cutil.FieldFilters { + return nil +} + +// ToIgn3_5Unvalidated 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_5Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { + ret, ts, r := c.Config.ToIgn3_5Unvalidated(options) + if r.IsFatal() { + return types.Config{}, translate.TranslationSet{}, r + } + r.Merge(c.processBootDevice(&ret, &ts, options)) + for i, disk := range ret.Storage.Disks { + // In the boot_device.mirror case, nothing specifies partition numbers + // so match existing partitions only when `wipeTable` is false + if !util.IsTrue(disk.WipeTable) { + for j, partition := range disk.Partitions { + // check for reserved partlabels + if partition.Label != nil { + if (*partition.Label == "BIOS-BOOT" && partition.Number != 1) || (*partition.Label == "PowerPC-PReP-boot" && partition.Number != 1) || (*partition.Label == "EFI-SYSTEM" && partition.Number != 2) || (*partition.Label == "boot" && partition.Number != 3) || (*partition.Label == "root" && partition.Number != 4) { + r.AddOnWarn(path.New("json", "storage", "disks", i, "partitions", j, "label"), common.ErrWrongPartitionNumber) + } + } + } + } + } + + retp, tsp, rp := c.handleUserGrubCfg(options) + retConfig, ts := baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts) + ret = retConfig.(types.Config) + r.Merge(rp) + return ret, ts, r +} + +// ToIgn3_5 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_5(options common.TranslateOptions) (types.Config, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToIgn3_5Unvalidated", options) + return cfg.(types.Config), r, err +} + +// ToIgn3_5Bytes translates from a v1.6 Butane config to a v3.5.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 ToIgn3_5Bytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { + return cutil.TranslateBytes(input, &Config{}, "ToIgn3_5", options) +} + +func (c Config) processBootDevice(config *types.Config, ts *translate.TranslationSet, options common.TranslateOptions) report.Report { + var rendered types.Config + renderedTranslations := translate.NewTranslationSet("yaml", "json") + var r report.Report + + // check for high-level features + wantLuks := util.IsTrue(c.BootDevice.Luks.Tpm2) || len(c.BootDevice.Luks.Tang) > 0 || util.IsTrue(c.BootDevice.Luks.Cex.Enabled) + wantMirror := len(c.BootDevice.Mirror.Devices) > 0 + if !wantLuks && !wantMirror { + return r + } + + // compute layout rendering options + var wantBIOSPart bool + var wantEFIPart bool + var wantPRePPart bool + layout := c.BootDevice.Layout + switch { + case layout == nil || *layout == "x86_64": + wantBIOSPart = true + wantEFIPart = true + case *layout == "aarch64": + wantEFIPart = true + case *layout == "ppc64le": + wantPRePPart = true + case *layout == "s390x-eckd" || *layout == "s390x-virt" || *layout == "s390x-zfcp": + default: + // should have failed validation + panic("unknown layout") + } + + // mirrored root disk + if wantMirror { + // partition disks + for i, device := range c.BootDevice.Mirror.Devices { + labelIndex := len(rendered.Storage.Disks) + 1 + disk := types.Disk{ + Device: device, + WipeTable: util.BoolToPtr(true), + } + if wantBIOSPart { + disk.Partitions = append(disk.Partitions, types.Partition{ + Label: util.StrToPtr(fmt.Sprintf("bios-%d", labelIndex)), + SizeMiB: util.IntToPtr(biosV1SizeMiB), + TypeGUID: util.StrToPtr(biosTypeGuid), + }) + } else if wantPRePPart { + disk.Partitions = append(disk.Partitions, types.Partition{ + Label: util.StrToPtr(fmt.Sprintf("prep-%d", labelIndex)), + SizeMiB: util.IntToPtr(prepV1SizeMiB), + TypeGUID: util.StrToPtr(prepTypeGuid), + }) + } else { + disk.Partitions = append(disk.Partitions, types.Partition{ + Label: util.StrToPtr(fmt.Sprintf("reserved-%d", labelIndex)), + SizeMiB: util.IntToPtr(reservedV1SizeMiB), + TypeGUID: util.StrToPtr(reservedTypeGuid), + }) + } + if wantEFIPart { + disk.Partitions = append(disk.Partitions, types.Partition{ + Label: util.StrToPtr(fmt.Sprintf("esp-%d", labelIndex)), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }) + } else { + disk.Partitions = append(disk.Partitions, types.Partition{ + Label: util.StrToPtr(fmt.Sprintf("reserved-%d", labelIndex)), + SizeMiB: util.IntToPtr(reservedV1SizeMiB), + TypeGUID: util.StrToPtr(reservedTypeGuid), + }) + } + disk.Partitions = append(disk.Partitions, types.Partition{ + Label: util.StrToPtr(fmt.Sprintf("boot-%d", labelIndex)), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, types.Partition{ + Label: util.StrToPtr(fmt.Sprintf("root-%d", labelIndex)), + }) + renderedTranslations.AddFromCommonSource(path.New("yaml", "boot_device", "mirror", "devices", i), path.New("json", "storage", "disks", len(rendered.Storage.Disks)), disk) + rendered.Storage.Disks = append(rendered.Storage.Disks, disk) + + if wantEFIPart { + // add ESP filesystem + espFilesystem := types.Filesystem{ + Device: fmt.Sprintf("/dev/disk/by-partlabel/esp-%d", labelIndex), + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr(fmt.Sprintf("esp-%d", labelIndex)), + WipeFilesystem: util.BoolToPtr(true), + } + renderedTranslations.AddFromCommonSource(path.New("yaml", "boot_device", "mirror", "devices", i), path.New("json", "storage", "filesystems", len(rendered.Storage.Filesystems)), espFilesystem) + rendered.Storage.Filesystems = append(rendered.Storage.Filesystems, espFilesystem) + } + } + renderedTranslations.AddTranslation(path.New("yaml", "boot_device", "mirror", "devices"), path.New("json", "storage", "disks")) + + // create RAIDs + raidDevices := func(labelPrefix string) []types.Device { + count := len(rendered.Storage.Disks) + ret := make([]types.Device, count) + for i := 0; i < count; i++ { + ret[i] = types.Device(fmt.Sprintf("/dev/disk/by-partlabel/%s-%d", labelPrefix, i+1)) + } + return ret + } + rendered.Storage.Raid = []types.Raid{{ + Devices: raidDevices("boot"), + Level: util.StrToPtr("raid1"), + Name: "md-boot", + // put the RAID superblock at the end of the + // partition so BIOS GRUB doesn't need to + // understand RAID + Options: []types.RaidOption{"--metadata=1.0"}, + }, { + Devices: raidDevices("root"), + Level: util.StrToPtr("raid1"), + Name: "md-root", + }} + renderedTranslations.AddFromCommonSource(path.New("yaml", "boot_device", "mirror"), path.New("json", "storage", "raid"), rendered.Storage.Raid) + + // create boot filesystem + bootFilesystem := types.Filesystem{ + Device: "/dev/md/md-boot", + Format: util.StrToPtr("ext4"), + Label: util.StrToPtr("boot"), + WipeFilesystem: util.BoolToPtr(true), + } + renderedTranslations.AddFromCommonSource(path.New("yaml", "boot_device", "mirror"), path.New("json", "storage", "filesystems", len(rendered.Storage.Filesystems)), bootFilesystem) + rendered.Storage.Filesystems = append(rendered.Storage.Filesystems, bootFilesystem) + } + + // encrypted root partition + if wantLuks { + var luksDevice string + switch { + //Luks Device for dasd and zFCP-scsi + case layout != nil && *layout == "s390x-eckd": + luksDevice = *c.BootDevice.Luks.Device + "2" + case layout != nil && *layout == "s390x-zfcp": + luksDevice = *c.BootDevice.Luks.Device + "4" + case wantMirror: + luksDevice = "/dev/md/md-root" + default: + luksDevice = "/dev/disk/by-partlabel/root" + } + if util.IsTrue(c.BootDevice.Luks.Cex.Enabled) { + cex, ts2, r2 := translateBootDeviceLuksCex(c.BootDevice.Luks, options) + rendered.Storage.Luks = []types.Luks{{ + Cex: cex, + Device: &luksDevice, + Discard: c.BootDevice.Luks.Discard, + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }} + lpath := path.New("yaml", "boot_device", "luks") + rpath := path.New("json", "storage", "luks", 0) + renderedTranslations.Merge(ts2.PrefixPaths(lpath, rpath.Append("cex"))) + renderedTranslations.AddTranslation(lpath.Append("discard"), rpath.Append("discard")) + for _, f := range []string{"device", "label", "name", "wipeVolume"} { + renderedTranslations.AddTranslation(lpath, rpath.Append(f)) + } + renderedTranslations.AddTranslation(lpath, rpath) + renderedTranslations.AddTranslation(lpath, path.New("json", "storage", "luks")) + r.Merge(r2) + } else { + clevis, ts2, r2 := translateBootDeviceLuks(c.BootDevice.Luks, options) + rendered.Storage.Luks = []types.Luks{{ + Clevis: clevis, + Device: &luksDevice, + Discard: c.BootDevice.Luks.Discard, + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }} + lpath := path.New("yaml", "boot_device", "luks") + rpath := path.New("json", "storage", "luks", 0) + renderedTranslations.Merge(ts2.PrefixPaths(lpath, rpath.Append("clevis"))) + renderedTranslations.AddTranslation(lpath.Append("discard"), rpath.Append("discard")) + for _, f := range []string{"device", "label", "name", "wipeVolume"} { + renderedTranslations.AddTranslation(lpath, rpath.Append(f)) + } + renderedTranslations.AddTranslation(lpath, rpath) + renderedTranslations.AddTranslation(lpath, path.New("json", "storage", "luks")) + r.Merge(r2) + } + } + + // create root filesystem + var rootDevice string + switch { + case wantLuks: + // LUKS, or LUKS on RAID + rootDevice = "/dev/mapper/root" + case wantMirror: + // RAID without LUKS + rootDevice = "/dev/md/md-root" + default: + panic("can't happen") + } + rootFilesystem := types.Filesystem{ + Device: rootDevice, + Format: util.StrToPtr("xfs"), + Label: util.StrToPtr("root"), + WipeFilesystem: util.BoolToPtr(true), + } + renderedTranslations.AddFromCommonSource(path.New("yaml", "boot_device"), path.New("json", "storage", "filesystems", len(rendered.Storage.Filesystems)), rootFilesystem) + renderedTranslations.AddTranslation(path.New("yaml", "boot_device"), path.New("json", "storage", "filesystems")) + rendered.Storage.Filesystems = append(rendered.Storage.Filesystems, rootFilesystem) + + // merge with translated config + renderedTranslations.AddTranslation(path.New("yaml", "boot_device"), path.New("json", "storage")) + retConfig, retTranslations := baseutil.MergeTranslatedConfigs(rendered, renderedTranslations, *config, *ts) + *config = retConfig.(types.Config) + *ts = retTranslations + return r +} + +func translateBootDeviceLuks(from BootDeviceLuks, options common.TranslateOptions) (to types.Clevis, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + // Discard field is handled by the caller because it doesn't go + // into types.Clevis + tm, r = translate.Prefixed(tr, "tang", &from.Tang, &to.Tang) + translate.MergeP(tr, tm, &r, "threshold", &from.Threshold, &to.Threshold) + translate.MergeP(tr, tm, &r, "tpm2", &from.Tpm2, &to.Tpm2) + // we're being called manually, not via the translate package's + // custom translator mechanism, so we have to add the base + // translation ourselves + tm.AddTranslation(path.New("yaml"), path.New("json")) + return +} + +func translateBootDeviceLuksCex(from BootDeviceLuks, options common.TranslateOptions) (to types.Cex, tm translate.TranslationSet, r report.Report) { + tr := translate.NewTranslator("yaml", "json", options) + // Discard field is handled by the caller because it doesn't go + // into types.Cex + tm, r = translate.Prefixed(tr, "enabled", &from.Cex.Enabled, &to.Enabled) + translate.MergeP(tr, tm, &r, "enabled", &from.Cex.Enabled, &to.Enabled) + // we're being called manually, not via the translate package's + // custom translator mechanism, so we have to add the base + // translation ourselves + tm.AddTranslation(path.New("yaml"), path.New("json")) + return +} + +func (c Config) handleUserGrubCfg(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { + rendered := types.Config{} + ts := translate.NewTranslationSet("yaml", "json") + var r report.Report + yamlPath := path.New("yaml", "grub", "users") + if len(c.Grub.Users) == 0 { + // No users + return rendered, ts, r + } + + // create boot filesystem + rendered.Storage.Filesystems = append(rendered.Storage.Filesystems, + types.Filesystem{ + Device: "/dev/disk/by-label/boot", + Format: util.StrToPtr("ext4"), + Path: util.StrToPtr("/boot"), + }) + + userCfgContent := []byte(buildGrubConfig(c.Grub)) + src, compression, err := baseutil.MakeDataURL(userCfgContent, nil, !options.NoResourceAutoCompression) + if err != nil { + r.AddOnError(yamlPath, err) + return rendered, ts, r + } + + // Create user.cfg file and add it to rendered config + rendered.Storage.Files = append(rendered.Storage.Files, + types.File{ + Node: types.Node{ + Path: "/boot/grub2/user.cfg", + }, + FileEmbedded1: types.FileEmbedded1{ + Append: []types.Resource{ + { + Source: util.StrToPtr(src), + Compression: compression, + }, + }, + }, + }) + + ts.AddFromCommonSource(yamlPath, path.New("json", "storage"), rendered.Storage) + return rendered, ts, r +} + +func buildGrubConfig(gb Grub) string { + // Process super users and corresponding passwords + allUsers := []string{} + cmds := []string{} + + for _, user := range gb.Users { + // We have already validated that user.Name and user.PasswordHash are non-empty + allUsers = append(allUsers, user.Name) + // Command for setting users password + cmds = append(cmds, fmt.Sprintf("password_pbkdf2 %s %s", user.Name, *user.PasswordHash)) + } + superUserCmd := fmt.Sprintf("set superusers=\"%s\"\n", strings.Join(allUsers, " ")) + return "# Generated by Butane\n\n" + superUserCmd + strings.Join(cmds, "\n") + "\n" +} diff --git a/config/fcos/v1_7_exp/translate_test.go b/config/fcos/v1_7_exp/translate_test.go new file mode 100644 index 00000000..7e90d01d --- /dev/null +++ b/config/fcos/v1_7_exp/translate_test.go @@ -0,0 +1,1639 @@ +// 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 v1_6_exp + +import ( + "fmt" + "testing" + + baseutil "github.com/coreos/butane/base/util" + base "github.com/coreos/butane/base/v0_6_exp" + "github.com/coreos/butane/config/common" + confutil "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_5/types" + "github.com/coreos/vcontext/path" + "github.com/coreos/vcontext/report" + "github.com/stretchr/testify/assert" +) + +// Most of this is covered by the Ignition translator generic tests, so just test the custom bits + +// TestTranslateBootDevice tests translating the Butane config boot_device section. +func TestTranslateBootDevice(t *testing.T) { + tests := []struct { + in Config + out types.Config + exceptions []translate.Translation + report report.Report + }{ + // empty config + { + Config{}, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + }, + report.Report{}, + }, + // partition number for the `root` label is incorrect + { + Config{ + Config: base.Config{ + Storage: base.Storage{ + Disks: []base.Disk{ + { + Device: "/dev/vda", + Partitions: []base.Partition{ + { + Label: util.StrToPtr("root"), + SizeMiB: util.IntToPtr(12000), + Resize: util.BoolToPtr(true), + }, + { + Label: util.StrToPtr("var-home"), + SizeMiB: util.IntToPtr(10240), + }, + }, + }, + }, + Filesystems: []base.Filesystem{ + { + Device: "/dev/disk/by-partlabel/var-home", + Format: util.StrToPtr("xfs"), + Path: util.StrToPtr("/var/home"), + Label: util.StrToPtr("var-home"), + WipeFilesystem: util.BoolToPtr(false), + }, + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Disks: []types.Disk{ + { + Device: "/dev/vda", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("root"), + SizeMiB: util.IntToPtr(12000), + Resize: util.BoolToPtr(true), + }, + { + Label: util.StrToPtr("var-home"), + SizeMiB: util.IntToPtr(10240), + }, + }, + }, + }, + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-partlabel/var-home", + Format: util.StrToPtr("xfs"), + Path: util.StrToPtr("/var/home"), + Label: util.StrToPtr("var-home"), + WipeFilesystem: util.BoolToPtr(false), + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + {From: path.New("yaml", "storage", "disks", 0, "partitions", 0, "label"), To: path.New("json", "storage", "disks", 0, "partitions", 0, "label")}, + {From: path.New("yaml", "storage", "disks", 0, "partitions", 0, "size_mib"), To: path.New("json", "storage", "disks", 0, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "storage", "disks", 0, "partitions", 0, "resize"), To: path.New("json", "storage", "disks", 0, "partitions", 0, "resize")}, + {From: path.New("yaml", "storage", "disks", 0, "partitions", 1, "label"), To: path.New("json", "storage", "disks", 0, "partitions", 1, "label")}, + {From: path.New("yaml", "storage", "disks", 0, "partitions", 1, "size_mib"), To: path.New("json", "storage", "disks", 0, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "storage", "disks", 0, "partitions", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0)}, + {From: path.New("yaml", "storage", "disks", 0), To: path.New("json", "storage", "disks", 0)}, + {From: path.New("yaml", "storage", "filesystems", 0, "device"), To: path.New("json", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "storage", "filesystems", 0, "format"), To: path.New("json", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "storage", "filesystems", 0, "path"), To: path.New("json", "storage", "filesystems", 0, "path")}, + {From: path.New("yaml", "storage", "filesystems", 0, "label"), To: path.New("json", "storage", "filesystems", 0, "label")}, + {From: path.New("yaml", "storage", "filesystems", 0, "wipe_filesystem"), To: path.New("json", "storage", "filesystems", 0, "wipeFilesystem")}, + {From: path.New("yaml", "storage", "filesystems", 0), To: path.New("json", "storage", "filesystems", 0)}, + {From: path.New("yaml", "storage", "filesystems"), To: path.New("json", "storage", "filesystems")}, + {From: path.New("yaml", "storage"), To: path.New("json", "storage")}, + }, + report.Report{ + Entries: []report.Entry{ + { + Kind: report.Warn, + Message: common.ErrWrongPartitionNumber.Error(), + Context: path.New("yaml", "storage", "disks", 0, "partitions", 0, "label"), + }, + }, + }, + }, + // LUKS, x86_64 + { + Config{ + BootDevice: BootDevice{ + Luks: BootDeviceLuks{ + Discard: util.BoolToPtr(true), + Tang: []base.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Luks: []types.Luks{ + { + Clevis: types.Clevis{ + Tang: []types.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Device: util.StrToPtr("/dev/disk/by-partlabel/root"), + Discard: util.BoolToPtr(true), + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }, + }, + Filesystems: []types.Filesystem{ + { + Device: "/dev/mapper/root", + Format: util.StrToPtr("xfs"), + Label: util.StrToPtr("root"), + WipeFilesystem: util.BoolToPtr(true), + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "url"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "url")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "thumbprint"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "thumbprint")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0)}, + {From: path.New("yaml", "boot_device", "luks", "tang"), To: path.New("json", "storage", "luks", 0, "clevis", "tang")}, + {From: path.New("yaml", "boot_device", "luks", "threshold"), To: path.New("json", "storage", "luks", 0, "clevis", "threshold")}, + {From: path.New("yaml", "boot_device", "luks", "tpm2"), To: path.New("json", "storage", "luks", 0, "clevis", "tpm2")}, + {From: path.New("yaml", "boot_device", "luks", "discard"), To: path.New("json", "storage", "luks", 0, "discard")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "clevis")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "device")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "label")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "name")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "wipeVolume")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0)}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0, "label")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage")}, + }, + report.Report{}, + }, + // LUKS, x86_64, with Tang set for offline provisioning + { + Config{ + BootDevice: BootDevice{ + Luks: BootDeviceLuks{ + Tang: []base.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + Advertisement: util.StrToPtr("{\"payload\": \"xyzzy\"}"), + }}, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Luks: []types.Luks{ + { + Clevis: types.Clevis{ + Tang: []types.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + Advertisement: util.StrToPtr("{\"payload\": \"xyzzy\"}"), + }}, + }, + Device: util.StrToPtr("/dev/disk/by-partlabel/root"), + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }, + }, + Filesystems: []types.Filesystem{ + { + Device: "/dev/mapper/root", + Format: util.StrToPtr("xfs"), + Label: util.StrToPtr("root"), + WipeFilesystem: util.BoolToPtr(true), + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "url"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "url")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "thumbprint"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "thumbprint")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "advertisement"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "advertisement")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0)}, + {From: path.New("yaml", "boot_device", "luks", "tang"), To: path.New("json", "storage", "luks", 0, "clevis", "tang")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "clevis")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "device")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "label")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "name")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "wipeVolume")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0)}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0, "label")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 0)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage")}, + }, + report.Report{}, + }, + // 3-disk mirror, x86_64 + { + Config{ + BootDevice: BootDevice{ + Mirror: BootDeviceMirror{ + Devices: []string{"/dev/vda", "/dev/vdb", "/dev/vdc"}, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Disks: []types.Disk{ + { + Device: "/dev/vda", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("bios-1"), + SizeMiB: util.IntToPtr(biosV1SizeMiB), + TypeGUID: util.StrToPtr(biosTypeGuid), + }, + { + Label: util.StrToPtr("esp-1"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-1"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-1"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + { + Device: "/dev/vdb", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("bios-2"), + SizeMiB: util.IntToPtr(biosV1SizeMiB), + TypeGUID: util.StrToPtr(biosTypeGuid), + }, + { + Label: util.StrToPtr("esp-2"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-2"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-2"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + { + Device: "/dev/vdc", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("bios-3"), + SizeMiB: util.IntToPtr(biosV1SizeMiB), + TypeGUID: util.StrToPtr(biosTypeGuid), + }, + { + Label: util.StrToPtr("esp-3"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-3"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-3"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + }, + Raid: []types.Raid{ + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/boot-1", + "/dev/disk/by-partlabel/boot-2", + "/dev/disk/by-partlabel/boot-3", + }, + Level: util.StrToPtr("raid1"), + Name: "md-boot", + Options: []types.RaidOption{"--metadata=1.0"}, + }, + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/root-1", + "/dev/disk/by-partlabel/root-2", + "/dev/disk/by-partlabel/root-3", + }, + Level: util.StrToPtr("raid1"), + Name: "md-root", + }, + }, + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-partlabel/esp-1", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-1"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/disk/by-partlabel/esp-2", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-2"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/disk/by-partlabel/esp-3", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-3"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/md/md-boot", + Format: util.StrToPtr("ext4"), + Label: util.StrToPtr("boot"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/md/md-root", + Format: util.StrToPtr("xfs"), + Label: util.StrToPtr("root"), + WipeFilesystem: util.BoolToPtr(true), + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices"), To: path.New("json", "storage", "disks")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 2)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 2)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3, "device")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3, "format")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4, "device")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4, "format")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4, "label")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage")}, + }, + report.Report{}, + }, + // 3-disk mirror + LUKS, x86_64 + { + Config{ + BootDevice: BootDevice{ + Luks: BootDeviceLuks{ + Discard: util.BoolToPtr(true), + Tang: []base.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Mirror: BootDeviceMirror{ + Devices: []string{"/dev/vda", "/dev/vdb", "/dev/vdc"}, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Disks: []types.Disk{ + { + Device: "/dev/vda", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("bios-1"), + SizeMiB: util.IntToPtr(biosV1SizeMiB), + TypeGUID: util.StrToPtr(biosTypeGuid), + }, + { + Label: util.StrToPtr("esp-1"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-1"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-1"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + { + Device: "/dev/vdb", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("bios-2"), + SizeMiB: util.IntToPtr(biosV1SizeMiB), + TypeGUID: util.StrToPtr(biosTypeGuid), + }, + { + Label: util.StrToPtr("esp-2"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-2"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-2"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + { + Device: "/dev/vdc", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("bios-3"), + SizeMiB: util.IntToPtr(biosV1SizeMiB), + TypeGUID: util.StrToPtr(biosTypeGuid), + }, + { + Label: util.StrToPtr("esp-3"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-3"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-3"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + }, + Raid: []types.Raid{ + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/boot-1", + "/dev/disk/by-partlabel/boot-2", + "/dev/disk/by-partlabel/boot-3", + }, + Level: util.StrToPtr("raid1"), + Name: "md-boot", + Options: []types.RaidOption{"--metadata=1.0"}, + }, + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/root-1", + "/dev/disk/by-partlabel/root-2", + "/dev/disk/by-partlabel/root-3", + }, + Level: util.StrToPtr("raid1"), + Name: "md-root", + }, + }, + Luks: []types.Luks{ + { + Clevis: types.Clevis{ + Tang: []types.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Device: util.StrToPtr("/dev/md/md-root"), + Discard: util.BoolToPtr(true), + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }, + }, + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-partlabel/esp-1", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-1"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/disk/by-partlabel/esp-2", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-2"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/disk/by-partlabel/esp-3", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-3"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/md/md-boot", + Format: util.StrToPtr("ext4"), + Label: util.StrToPtr("boot"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/mapper/root", + Format: util.StrToPtr("xfs"), + Label: util.StrToPtr("root"), + WipeFilesystem: util.BoolToPtr(true), + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "disks", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 2), To: path.New("json", "storage", "filesystems", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices"), To: path.New("json", "storage", "disks")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 2)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 2)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "url"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "url")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "thumbprint"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "thumbprint")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0)}, + {From: path.New("yaml", "boot_device", "luks", "tang"), To: path.New("json", "storage", "luks", 0, "clevis", "tang")}, + {From: path.New("yaml", "boot_device", "luks", "threshold"), To: path.New("json", "storage", "luks", 0, "clevis", "threshold")}, + {From: path.New("yaml", "boot_device", "luks", "tpm2"), To: path.New("json", "storage", "luks", 0, "clevis", "tpm2")}, + {From: path.New("yaml", "boot_device", "luks", "discard"), To: path.New("json", "storage", "luks", 0, "discard")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "clevis")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "device")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "label")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "name")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "wipeVolume")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0)}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3, "device")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3, "format")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 3)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4, "device")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4, "format")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4, "label")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 4)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage")}, + }, + report.Report{}, + }, + // 2-disk mirror + LUKS, aarch64 + { + Config{ + BootDevice: BootDevice{ + Layout: util.StrToPtr("aarch64"), + Luks: BootDeviceLuks{ + Discard: util.BoolToPtr(true), + Tang: []base.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Mirror: BootDeviceMirror{ + Devices: []string{"/dev/vda", "/dev/vdb"}, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Disks: []types.Disk{ + { + Device: "/dev/vda", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("reserved-1"), + SizeMiB: util.IntToPtr(reservedV1SizeMiB), + TypeGUID: util.StrToPtr(reservedTypeGuid), + }, + { + Label: util.StrToPtr("esp-1"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-1"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-1"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + { + Device: "/dev/vdb", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("reserved-2"), + SizeMiB: util.IntToPtr(reservedV1SizeMiB), + TypeGUID: util.StrToPtr(reservedTypeGuid), + }, + { + Label: util.StrToPtr("esp-2"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-2"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-2"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + }, + Raid: []types.Raid{ + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/boot-1", + "/dev/disk/by-partlabel/boot-2", + }, + Level: util.StrToPtr("raid1"), + Name: "md-boot", + Options: []types.RaidOption{"--metadata=1.0"}, + }, + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/root-1", + "/dev/disk/by-partlabel/root-2", + }, + Level: util.StrToPtr("raid1"), + Name: "md-root", + }, + }, + Luks: []types.Luks{ + { + Clevis: types.Clevis{ + Tang: []types.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Device: util.StrToPtr("/dev/md/md-root"), + Discard: util.BoolToPtr(true), + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }, + }, + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-partlabel/esp-1", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-1"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/disk/by-partlabel/esp-2", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-2"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/md/md-boot", + Format: util.StrToPtr("ext4"), + Label: util.StrToPtr("boot"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/mapper/root", + Format: util.StrToPtr("xfs"), + Label: util.StrToPtr("root"), + WipeFilesystem: util.BoolToPtr(true), + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices"), To: path.New("json", "storage", "disks")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "url"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "url")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "thumbprint"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "thumbprint")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0)}, + {From: path.New("yaml", "boot_device", "luks", "tang"), To: path.New("json", "storage", "luks", 0, "clevis", "tang")}, + {From: path.New("yaml", "boot_device", "luks", "threshold"), To: path.New("json", "storage", "luks", 0, "clevis", "threshold")}, + {From: path.New("yaml", "boot_device", "luks", "tpm2"), To: path.New("json", "storage", "luks", 0, "clevis", "tpm2")}, + {From: path.New("yaml", "boot_device", "luks", "discard"), To: path.New("json", "storage", "luks", 0, "discard")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "clevis")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "device")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "label")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "name")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "wipeVolume")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0)}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2, "device")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2, "format")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 3, "device")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 3, "format")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 3, "label")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 3, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 3)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage")}, + }, + report.Report{}, + }, + // 2-disk mirror + LUKS, ppc64le + { + Config{ + BootDevice: BootDevice{ + Layout: util.StrToPtr("ppc64le"), + Luks: BootDeviceLuks{ + Discard: util.BoolToPtr(true), + Tang: []base.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Mirror: BootDeviceMirror{ + Devices: []string{"/dev/vda", "/dev/vdb"}, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Disks: []types.Disk{ + { + Device: "/dev/vda", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("prep-1"), + SizeMiB: util.IntToPtr(prepV1SizeMiB), + TypeGUID: util.StrToPtr(prepTypeGuid), + }, + { + Label: util.StrToPtr("reserved-1"), + SizeMiB: util.IntToPtr(reservedV1SizeMiB), + TypeGUID: util.StrToPtr(reservedTypeGuid), + }, + { + Label: util.StrToPtr("boot-1"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-1"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + { + Device: "/dev/vdb", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("prep-2"), + SizeMiB: util.IntToPtr(prepV1SizeMiB), + TypeGUID: util.StrToPtr(prepTypeGuid), + }, + { + Label: util.StrToPtr("reserved-2"), + SizeMiB: util.IntToPtr(reservedV1SizeMiB), + TypeGUID: util.StrToPtr(reservedTypeGuid), + }, + { + Label: util.StrToPtr("boot-2"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-2"), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + }, + Raid: []types.Raid{ + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/boot-1", + "/dev/disk/by-partlabel/boot-2", + }, + Level: util.StrToPtr("raid1"), + Name: "md-boot", + Options: []types.RaidOption{"--metadata=1.0"}, + }, + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/root-1", + "/dev/disk/by-partlabel/root-2", + }, + Level: util.StrToPtr("raid1"), + Name: "md-root", + }, + }, + Luks: []types.Luks{ + { + Clevis: types.Clevis{ + Tang: []types.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Device: util.StrToPtr("/dev/md/md-root"), + Discard: util.BoolToPtr(true), + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }, + }, + Filesystems: []types.Filesystem{ + { + Device: "/dev/md/md-boot", + Format: util.StrToPtr("ext4"), + Label: util.StrToPtr("boot"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/mapper/root", + Format: util.StrToPtr("xfs"), + Label: util.StrToPtr("root"), + WipeFilesystem: util.BoolToPtr(true), + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 3, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "wipeTable")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices"), To: path.New("json", "storage", "disks")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "url"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "url")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "thumbprint"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "thumbprint")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0)}, + {From: path.New("yaml", "boot_device", "luks", "tang"), To: path.New("json", "storage", "luks", 0, "clevis", "tang")}, + {From: path.New("yaml", "boot_device", "luks", "threshold"), To: path.New("json", "storage", "luks", 0, "clevis", "threshold")}, + {From: path.New("yaml", "boot_device", "luks", "tpm2"), To: path.New("json", "storage", "luks", 0, "clevis", "tpm2")}, + {From: path.New("yaml", "boot_device", "luks", "discard"), To: path.New("json", "storage", "luks", 0, "discard")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "clevis")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "device")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "label")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "name")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "wipeVolume")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0)}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 0, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 0)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 1, "device")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 1, "format")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 1, "label")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 1, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 1)}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage")}, + }, + report.Report{}, + }, + // 2-disk mirror + LUKS with overridden root partition size + // and filesystem type, x86_64 + { + Config{ + Config: base.Config{ + Storage: base.Storage{ + Disks: []base.Disk{ + { + Device: "/dev/vda", + Partitions: []base.Partition{ + { + Label: util.StrToPtr("root-1"), + SizeMiB: util.IntToPtr(8192), + }, + }, + }, + { + Device: "/dev/vdb", + Partitions: []base.Partition{ + { + Label: util.StrToPtr("root-2"), + SizeMiB: util.IntToPtr(8192), + }, + }, + }, + }, + Filesystems: []base.Filesystem{ + { + Device: "/dev/mapper/root", + Format: util.StrToPtr("ext4"), + }, + }, + }, + }, + BootDevice: BootDevice{ + Luks: BootDeviceLuks{ + Discard: util.BoolToPtr(true), + Tang: []base.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Mirror: BootDeviceMirror{ + Devices: []string{"/dev/vda", "/dev/vdb"}, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.0", + }, + Storage: types.Storage{ + Disks: []types.Disk{ + { + Device: "/dev/vda", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("bios-1"), + SizeMiB: util.IntToPtr(biosV1SizeMiB), + TypeGUID: util.StrToPtr(biosTypeGuid), + }, + { + Label: util.StrToPtr("esp-1"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-1"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-1"), + SizeMiB: util.IntToPtr(8192), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + { + Device: "/dev/vdb", + Partitions: []types.Partition{ + { + Label: util.StrToPtr("bios-2"), + SizeMiB: util.IntToPtr(biosV1SizeMiB), + TypeGUID: util.StrToPtr(biosTypeGuid), + }, + { + Label: util.StrToPtr("esp-2"), + SizeMiB: util.IntToPtr(espV1SizeMiB), + TypeGUID: util.StrToPtr(espTypeGuid), + }, + { + Label: util.StrToPtr("boot-2"), + SizeMiB: util.IntToPtr(bootV1SizeMiB), + }, + { + Label: util.StrToPtr("root-2"), + SizeMiB: util.IntToPtr(8192), + }, + }, + WipeTable: util.BoolToPtr(true), + }, + }, + Raid: []types.Raid{ + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/boot-1", + "/dev/disk/by-partlabel/boot-2", + }, + Level: util.StrToPtr("raid1"), + Name: "md-boot", + Options: []types.RaidOption{"--metadata=1.0"}, + }, + { + Devices: []types.Device{ + "/dev/disk/by-partlabel/root-1", + "/dev/disk/by-partlabel/root-2", + }, + Level: util.StrToPtr("raid1"), + Name: "md-root", + }, + }, + Luks: []types.Luks{ + { + Clevis: types.Clevis{ + Tang: []types.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("z"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Device: util.StrToPtr("/dev/md/md-root"), + Discard: util.BoolToPtr(true), + Label: util.StrToPtr("luks-root"), + Name: "root", + WipeVolume: util.BoolToPtr(true), + }, + }, + Filesystems: []types.Filesystem{ + { + Device: "/dev/disk/by-partlabel/esp-1", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-1"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/disk/by-partlabel/esp-2", + Format: util.StrToPtr("vfat"), + Label: util.StrToPtr("esp-2"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/md/md-boot", + Format: util.StrToPtr("ext4"), + Label: util.StrToPtr("boot"), + WipeFilesystem: util.BoolToPtr(true), + }, { + Device: "/dev/mapper/root", + Format: util.StrToPtr("ext4"), + Label: util.StrToPtr("root"), + WipeFilesystem: util.BoolToPtr(true), + }, + }, + }, + }, + []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "partitions", 2)}, + {From: path.New("yaml", "storage", "disks", 0, "partitions", 0, "label"), To: path.New("json", "storage", "disks", 0, "partitions", 3, "label")}, + {From: path.New("yaml", "storage", "disks", 0, "partitions", 0, "size_mib"), To: path.New("json", "storage", "disks", 0, "partitions", 3, "sizeMiB")}, + {From: path.New("yaml", "storage", "disks", 0, "partitions", 0), To: path.New("json", "storage", "disks", 0, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "disks", 0, "wipeTable")}, + {From: path.New("yaml", "storage", "disks", 0), To: path.New("json", "storage", "disks", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 0), To: path.New("json", "storage", "filesystems", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 0)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1, "typeGuid")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2, "sizeMiB")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "partitions", 2)}, + {From: path.New("yaml", "storage", "disks", 1, "partitions", 0, "label"), To: path.New("json", "storage", "disks", 1, "partitions", 3, "label")}, + {From: path.New("yaml", "storage", "disks", 1, "partitions", 0, "size_mib"), To: path.New("json", "storage", "disks", 1, "partitions", 3, "sizeMiB")}, + {From: path.New("yaml", "storage", "disks", 1, "partitions", 0), To: path.New("json", "storage", "disks", 1, "partitions", 3)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "disks", 1, "wipeTable")}, + {From: path.New("yaml", "storage", "disks", 1), To: path.New("json", "storage", "disks", 1)}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "device")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "format")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "label")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror", "devices", 1), To: path.New("json", "storage", "filesystems", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0, "options")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 0)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "devices")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "level")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1, "name")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid", 1)}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "raid")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "url"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "url")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0, "thumbprint"), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0, "thumbprint")}, + {From: path.New("yaml", "boot_device", "luks", "tang", 0), To: path.New("json", "storage", "luks", 0, "clevis", "tang", 0)}, + {From: path.New("yaml", "boot_device", "luks", "tang"), To: path.New("json", "storage", "luks", 0, "clevis", "tang")}, + {From: path.New("yaml", "boot_device", "luks", "threshold"), To: path.New("json", "storage", "luks", 0, "clevis", "threshold")}, + {From: path.New("yaml", "boot_device", "luks", "tpm2"), To: path.New("json", "storage", "luks", 0, "clevis", "tpm2")}, + {From: path.New("yaml", "boot_device", "luks", "discard"), To: path.New("json", "storage", "luks", 0, "discard")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "clevis")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "device")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "label")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "name")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0, "wipeVolume")}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks", 0)}, + {From: path.New("yaml", "boot_device", "luks"), To: path.New("json", "storage", "luks")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2, "device")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2, "format")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2, "label")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2, "wipeFilesystem")}, + {From: path.New("yaml", "boot_device", "mirror"), To: path.New("json", "storage", "filesystems", 2)}, + {From: path.New("yaml", "storage", "filesystems", 0, "device"), To: path.New("json", "storage", "filesystems", 3, "device")}, + {From: path.New("yaml", "storage", "filesystems", 0, "format"), To: path.New("json", "storage", "filesystems", 3, "format")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 3, "label")}, + {From: path.New("yaml", "boot_device"), To: path.New("json", "storage", "filesystems", 3, "wipeFilesystem")}, + {From: path.New("yaml", "storage", "filesystems", 0), To: path.New("json", "storage", "filesystems", 3)}, + {From: path.New("yaml", "storage", "filesystems"), To: path.New("json", "storage", "filesystems")}, + {From: path.New("yaml", "storage"), To: path.New("json", "storage")}, + }, + report.Report{}, + }, + } + + // The partition sizes of existing layouts must never change, but + // we use the constants in tests for clarity. Ensure no one has + // changed them. + assert.Equal(t, reservedV1SizeMiB, 1) + assert.Equal(t, biosV1SizeMiB, 1) + assert.Equal(t, prepV1SizeMiB, 4) + assert.Equal(t, espV1SizeMiB, 127) + assert.Equal(t, bootV1SizeMiB, 384) + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, test.report, r, "report mismatch") + baseutil.VerifyTranslations(t, translations, test.exceptions) + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} + +// TestTranslateGrub tests translating the Butane config Grub section. +func TestTranslateGrub(t *testing.T) { + // Some tests below have the same translations + translations := []translate.Translation{ + {From: path.New("yaml", "version"), To: path.New("json", "ignition", "version")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "filesystems")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "filesystems", 0)}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "filesystems", 0, "path")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "filesystems", 0, "device")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "filesystems", 0, "format")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "files")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "files", 0)}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "files", 0, "path")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "files", 0, "append")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "files", 0, "append", 0)}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "files", 0, "append", 0, "source")}, + {From: path.New("yaml", "grub", "users"), To: path.New("json", "storage", "files", 0, "append", 0, "compression")}, + } + tests := []struct { + in Config + out types.Config + exceptions []translate.Translation + report report.Report + }{ + // config with 1 user + { + Config{ + Grub: Grub{ + Users: []GrubUser{ + { + Name: "root", + PasswordHash: util.StrToPtr("grub.pbkdf2.sha512.10000.874A958E526409..."), + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.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{ + Append: []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(""), + }, + }, + }, + }, + }, + }, + }, + translations, + report.Report{}, + }, + // config with 2 users (and 2 different hashes) + { + Config{ + Grub: Grub{ + Users: []GrubUser{ + { + Name: "root1", + PasswordHash: util.StrToPtr("grub.pbkdf2.sha512.10000.874A958E526409..."), + }, + { + Name: "root2", + PasswordHash: util.StrToPtr("grub.pbkdf2.sha512.10000.874B829D126209..."), + }, + }, + }, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.5.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{ + Append: []types.Resource{ + { + Source: util.StrToPtr("data:;base64,H4sIAAAAAAAC/3zMsQrCMBDG8b1PcdT9SI62JoODRfExJCGngtCEuwTx7UWyiss3fH/47eDCG0uonCC+YW01bDwMyhW0FZamLHoYJedq4bs0DiWovrKka4nPdCPo8S4tYn9QH2G2hNYYY9Dtp6Of3XmmZTIeEX8C9BdYHfmTpYU68AkAAP//Mp8bt7YAAAA="), + Compression: util.StrToPtr("gzip"), + }, + }, + }, + }, + }, + }, + }, + translations, + report.Report{}, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { + actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + r = confutil.TranslateReportPaths(r, translations) + baseutil.VerifyReport(t, test.in, r) + assert.Equal(t, test.out, actual, "translation mismatch") + assert.Equal(t, test.report, r, "report mismatch") + baseutil.VerifyTranslations(t, translations, test.exceptions) + assert.NoError(t, translations.DebugVerifyCoverage(actual), "incomplete TranslationSet coverage") + }) + } +} diff --git a/config/fcos/v1_7_exp/validate.go b/config/fcos/v1_7_exp/validate.go new file mode 100644 index 00000000..443f6523 --- /dev/null +++ b/config/fcos/v1_7_exp/validate.go @@ -0,0 +1,115 @@ +// 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 v1_6_exp + +import ( + "regexp" + "strings" + + "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" +) + +const rootDevice = "/dev/disk/by-id/coreos-boot-disk" + +var allowedMountpoints = regexp.MustCompile(`^/(etc|var)(/|$)`) +var dasdRe = regexp.MustCompile("(/dev/dasd[a-z]$)") +var sdRe = regexp.MustCompile("(/dev/sd[a-z]$)") + +// We can't define a Validate function directly on Disk because that's defined in base, +// so we use a Validate function on the top-level Config instead. +func (conf Config) Validate(c path.ContextPath) (r report.Report) { + for i, disk := range conf.Storage.Disks { + if disk.Device != rootDevice && !util.IsTrue(disk.WipeTable) { + for p, partition := range disk.Partitions { + if partition.Number == 0 && partition.Label != nil { + r.AddOnWarn(c.Append("storage", "disks", i, "partitions", p, "number"), common.ErrReuseByLabel) + } + } + } + } + for i, fs := range conf.Storage.Filesystems { + if fs.Path != nil && !allowedMountpoints.MatchString(*fs.Path) && util.IsTrue(fs.WithMountUnit) { + r.AddOnError(c.Append("storage", "filesystems", i, "path"), common.ErrMountPointForbidden) + } + } + return +} + +func (d BootDevice) Validate(c path.ContextPath) (r report.Report) { + if d.Layout != nil { + switch *d.Layout { + case "aarch64", "ppc64le", "x86_64": + // Nothing to do + case "s390x-eckd": + if util.NilOrEmpty(d.Luks.Device) { + r.AddOnError(c.Append("layout"), common.ErrNoLuksBootDevice) + } else if !dasdRe.MatchString(*d.Luks.Device) { + r.AddOnError(c.Append("layout"), common.ErrLuksBootDeviceBadName) + } + case "s390x-zfcp": + if util.NilOrEmpty(d.Luks.Device) { + r.AddOnError(c.Append("layout"), common.ErrNoLuksBootDevice) + } else if !sdRe.MatchString(*d.Luks.Device) { + r.AddOnError(c.Append("layout"), common.ErrLuksBootDeviceBadName) + } + case "s390x-virt": + default: + r.AddOnError(c.Append("layout"), common.ErrUnknownBootDeviceLayout) + } + + // Mirroring the boot disk is not supported on s390x + if strings.HasPrefix(*d.Layout, "s390x") && len(d.Mirror.Devices) > 0 { + r.AddOnError(c.Append("layout"), common.ErrMirrorNotSupport) + } + } + + // CEX is only valid on s390x and incompatible with Clevis + if util.IsTrue(d.Luks.Cex.Enabled) { + if d.Layout == nil { + r.AddOnError(c.Append("luks", "cex"), common.ErrCexArchitectureMismatch) + } else if !strings.HasPrefix(*d.Layout, "s390x") { + r.AddOnError(c.Append("layout"), common.ErrCexArchitectureMismatch) + } + if len(d.Luks.Tang) > 0 || util.IsTrue(d.Luks.Tpm2) { + r.AddOnError(c.Append("luks"), errors.ErrCexWithClevis) + } + } + + r.Merge(d.Mirror.Validate(c.Append("mirror"))) + return +} + +func (m BootDeviceMirror) Validate(c path.ContextPath) (r report.Report) { + if len(m.Devices) == 1 { + r.AddOnError(c.Append("devices"), common.ErrTooFewMirrorDevices) + } + return +} + +func (user GrubUser) Validate(c path.ContextPath) (r report.Report) { + if user.Name == "" { + r.AddOnError(c.Append("name"), common.ErrGrubUserNameNotSpecified) + } + + if !util.NotEmpty(user.PasswordHash) { + r.AddOnError(c.Append("password_hash"), common.ErrGrubPasswordNotSpecified) + } + return +} diff --git a/config/fcos/v1_7_exp/validate_test.go b/config/fcos/v1_7_exp/validate_test.go new file mode 100644 index 00000000..b67a4447 --- /dev/null +++ b/config/fcos/v1_7_exp/validate_test.go @@ -0,0 +1,605 @@ +// 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 v1_6_exp + +import ( + "fmt" + "testing" + + baseutil "github.com/coreos/butane/base/util" + base "github.com/coreos/butane/base/v0_6_exp" + "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" +) + +// 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 + { + `storage: + files: + - path: /z + q: z`, + "Unused key q", + 4, + }, + // Butane YAML validation error + { + `storage: + files: + - path: /z + contents: + source: https://example.com + inline: z`, + common.ErrTooManyResourceSources.Error(), + 5, + }, + // Butane YAML validation warning + { + `storage: + files: + - path: /z + mode: 444`, + common.ErrDecimalMode.Error(), + 4, + }, + // Butane translation error + { + `storage: + files: + - path: /z + contents: + local: z`, + common.ErrNoFilesDir.Error(), + 5, + }, + // Ignition validation error, leaf node + { + `storage: + files: + - path: z`, + errors.ErrPathRelative.Error(), + 3, + }, + // Ignition validation error, partition + { + `storage: + disks: + - device: /dev/z + wipe_table: true + partitions: + - start_mib: 5`, + errors.ErrNeedLabelOrNumber.Error(), + 6, + }, + // Ignition validation error, partition list + { + `storage: + disks: + - device: /dev/z + wipe_table: true + partitions: + - number: 1 + should_exist: false + - label: z`, + errors.ErrZeroesWithShouldNotExist.Error(), + 6, + }, + // Ignition duplicate key check, paths + { + `storage: + files: + - path: /z + - path: /z`, + errors.ErrDuplicate.Error(), + 4, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + _, r, _ := ToIgn3_5Bytes([]byte(test.in), common.TranslateBytesOptions{}) + assert.Len(t, r.Entries, 1, "unexpected report length") + assert.Equal(t, test.message, r.Entries[0].Message, "bad error") + assert.NotNil(t, r.Entries[0].Marker.StartP, "marker start is nil") + assert.Equal(t, test.line, r.Entries[0].Marker.StartP.Line, "incorrect error line") + }) + } +} + +// TestValidateBootDevice tests boot device validation +func TestValidateBootDevice(t *testing.T) { + tests := []struct { + in BootDevice + out error + errPath path.ContextPath + }{ + // empty config + { + BootDevice{}, + nil, + path.New("yaml"), + }, + // complete config + { + BootDevice{ + Layout: util.StrToPtr("x86_64"), + Luks: BootDeviceLuks{ + Tang: []base.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("x"), + }}, + Threshold: util.IntToPtr(2), + Tpm2: util.BoolToPtr(true), + }, + Mirror: BootDeviceMirror{ + Devices: []string{"/dev/vda", "/dev/vdb"}, + }, + }, + nil, + path.New("yaml"), + }, + // complete config with cex + { + BootDevice{ + Layout: util.StrToPtr("s390x-eckd"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/dasda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + }, + }, + nil, + path.New("yaml"), + }, + // can not use both cex & tang + { + BootDevice{ + Layout: util.StrToPtr("s390x-eckd"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/dasda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + Tang: []base.Tang{{ + URL: "https://example.com/", + Thumbprint: util.StrToPtr("x"), + }}, + }, + }, + errors.ErrCexWithClevis, + path.New("yaml", "luks"), + }, + // can not use both cex & tpm2 + { + BootDevice{ + Layout: util.StrToPtr("s390x-eckd"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/dasda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + Tpm2: util.BoolToPtr(true), + }, + }, + errors.ErrCexWithClevis, + path.New("yaml", "luks"), + }, + // can not use cex on non s390x + { + BootDevice{ + Layout: util.StrToPtr("x86_64"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/sda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + }, + }, + common.ErrCexArchitectureMismatch, + path.New("yaml", "layout"), + }, + // must set s390x layout with cex + { + BootDevice{ + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/sda"), + Cex: base.Cex{ + Enabled: util.BoolToPtr(true), + }, + }, + }, + common.ErrCexArchitectureMismatch, + path.New("yaml", "luks", "cex"), + }, + // invalid layout + { + BootDevice{ + Layout: util.StrToPtr("sparc"), + }, + common.ErrUnknownBootDeviceLayout, + path.New("yaml", "layout"), + }, + // only one mirror device + { + BootDevice{ + Mirror: BootDeviceMirror{ + Devices: []string{"/dev/vda"}, + }, + }, + common.ErrTooFewMirrorDevices, + path.New("yaml", "mirror", "devices"), + }, + // s390x-eckd/s390x-zfcp layouts require a boot device with luks + { + BootDevice{ + Layout: util.StrToPtr("s390x-eckd"), + }, + common.ErrNoLuksBootDevice, + path.New("yaml", "layout"), + }, + // s390x-eckd/s390x-zfcp layouts do not support mirroring + { + BootDevice{ + Layout: util.StrToPtr("s390x-zfcp"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/sda"), + Tpm2: util.BoolToPtr(true), + }, + Mirror: BootDeviceMirror{ + Devices: []string{ + "/dev/sda", + "/dev/sdb", + }, + }, + }, + common.ErrMirrorNotSupport, + path.New("yaml", "layout"), + }, + // s390x-eckd devices must start with /dev/dasd + { + BootDevice{ + Layout: util.StrToPtr("s390x-eckd"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/sda"), + Tpm2: util.BoolToPtr(true), + }, + }, + common.ErrLuksBootDeviceBadName, + path.New("yaml", "layout"), + }, + // s390x-zfcp devices must start with /dev/sd + { + BootDevice{ + Layout: util.StrToPtr("s390x-eckd"), + Luks: BootDeviceLuks{ + Device: util.StrToPtr("/dev/dasd"), + Tpm2: util.BoolToPtr(true), + }, + }, + common.ErrLuksBootDeviceBadName, + path.New("yaml", "layout"), + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnError(test.errPath, test.out) + assert.Equal(t, expected, actual, "bad validation report") + }) + } +} + +func TestValidateGrubUser(t *testing.T) { + tests := []struct { + in GrubUser + out error + errPath path.ContextPath + }{ + // valid user + { + in: GrubUser{ + Name: "name", + PasswordHash: util.StrToPtr("pkcs5-pass"), + }, + out: nil, + errPath: path.New("yaml"), + }, + // username is not specified + { + in: GrubUser{ + Name: "", + PasswordHash: util.StrToPtr("pkcs5-pass"), + }, + out: common.ErrGrubUserNameNotSpecified, + errPath: path.New("yaml", "name"), + }, + // password is not specified + { + in: GrubUser{ + Name: "name", + }, + out: common.ErrGrubPasswordNotSpecified, + errPath: path.New("yaml", "password_hash"), + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnError(test.errPath, test.out) + assert.Equal(t, expected, actual, "bad report") + }) + } +} + +func TestValidateMountPoints(t *testing.T) { + tests := []struct { + in Config + out error + errPath path.ContextPath + }{ + // valid config (has prefix "/etc" or "/var") + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Filesystems: []base.Filesystem{ + { + Path: util.StrToPtr("/etc/foo"), + WithMountUnit: util.BoolToPtr(true), + }, + { + Path: util.StrToPtr("/var"), + WithMountUnit: util.BoolToPtr(true), + }, + { + Path: util.StrToPtr("/invalid/path"), + WithMountUnit: util.BoolToPtr(false), + }, + { + WithMountUnit: util.BoolToPtr(true), + }, + { + Path: nil, + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + }, + }, + }, + // invalid config (path name is '/') + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Filesystems: []base.Filesystem{ + { + Path: util.StrToPtr("/"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + }, + }, + out: common.ErrMountPointForbidden, + errPath: path.New("yaml", "storage", "filesystems", 0, "path"), + }, + // invalid config (path is /boot) + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Filesystems: []base.Filesystem{ + { + Path: util.StrToPtr("/boot"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + }, + }, + + out: common.ErrMountPointForbidden, + errPath: path.New("yaml", "storage", "filesystems", 0, "path"), + }, + // invalid config (path is invalid, does not contain /etc or /var) + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Filesystems: []base.Filesystem{ + { + Path: util.StrToPtr("/thisIsABugTest"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + }, + }, + + out: common.ErrMountPointForbidden, + errPath: path.New("yaml", "storage", "filesystems", 0, "path"), + }, + // invalid config (path is /varnish) + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Filesystems: []base.Filesystem{ + { + Path: util.StrToPtr("/varnish"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + }, + }, + + out: common.ErrMountPointForbidden, + errPath: path.New("yaml", "storage", "filesystems", 0, "path"), + }, + // invalid config (path is /foo/var) + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Filesystems: []base.Filesystem{ + { + Path: util.StrToPtr("/foo/var"), + WithMountUnit: util.BoolToPtr(true), + }, + }, + }, + }, + }, + + out: common.ErrMountPointForbidden, + errPath: path.New("yaml", "storage", "filesystems", 0, "path"), + }, + } + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnError(test.errPath, test.out) + assert.Equal(t, expected, actual, "invalid report") + }) + } +} + +func TestValidateConfig(t *testing.T) { + tests := []struct { + in Config + out error + errPath path.ContextPath + }{ + // valid config (wipe_table is true) + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Disks: []base.Disk{ + { + Device: "/dev/vda", + WipeTable: util.BoolToPtr(true), + Partitions: []base.Partition{ + { + Label: util.StrToPtr("foo"), + }, + }, + }, + }, + }, + }, + }, + }, + // valid config (disk is /dev/disk/by-id/coreos-boot-disk) + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Disks: []base.Disk{ + { + Device: rootDevice, + WipeTable: util.BoolToPtr(false), + Partitions: []base.Partition{ + { + Label: util.StrToPtr("bar"), + }, + }, + }, + }, + }, + }, + }, + }, + // invalid config (wipe_table is nil) + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Disks: []base.Disk{ + { + Device: "/dev/vda", + Partitions: []base.Partition{ + { + Label: util.StrToPtr("foo"), + }, + }, + }, + }, + }, + }, + }, + out: common.ErrReuseByLabel, + errPath: path.New("yaml", "storage", "disks", 0, "partitions", 0, "number"), + }, + // invalid config (wipe_table is false with a partition numbered 0) + { + in: Config{ + Config: base.Config{ + Storage: base.Storage{ + Disks: []base.Disk{ + { + Device: "/dev/vda", + WipeTable: util.BoolToPtr(false), + Partitions: []base.Partition{ + { + Label: util.StrToPtr("foo"), + }, + { + Label: util.StrToPtr("bar"), + Number: 2, + }, + }, + }, + }, + }, + }, + }, + out: common.ErrReuseByLabel, + errPath: path.New("yaml", "storage", "disks", 0, "partitions", 0, "number"), + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { + actual := test.in.Validate(path.New("yaml")) + baseutil.VerifyReport(t, test.in, actual) + expected := report.Report{} + expected.AddOnWarn(test.errPath, test.out) + assert.Equal(t, expected, actual, "invalid report") + }) + } +} From 969d8119a86d6564a529d8ca9c77d33b575e075d Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:36:54 -0500 Subject: [PATCH 06/13] fcos/v1_7_exp: update imports and package names --- config/config.go | 2 ++ config/fcos/v1_7_exp/schema.go | 4 ++-- config/fcos/v1_7_exp/translate.go | 22 +++++++++--------- config/fcos/v1_7_exp/translate_test.go | 32 +++++++++++++------------- config/fcos/v1_7_exp/validate.go | 2 +- config/fcos/v1_7_exp/validate_test.go | 6 ++--- 6 files changed, 35 insertions(+), 33 deletions(-) diff --git a/config/config.go b/config/config.go index 831e9c75..ab5d8158 100644 --- a/config/config.go +++ b/config/config.go @@ -25,6 +25,7 @@ import ( fcos1_4 "github.com/coreos/butane/config/fcos/v1_4" fcos1_5 "github.com/coreos/butane/config/fcos/v1_5" fcos1_6_exp "github.com/coreos/butane/config/fcos/v1_6_exp" + fcos1_7_exp "github.com/coreos/butane/config/fcos/v1_7_exp" fiot1_0 "github.com/coreos/butane/config/fiot/v1_0" fiot1_1_exp "github.com/coreos/butane/config/fiot/v1_1_exp" flatcar1_0 "github.com/coreos/butane/config/flatcar/v1_0" @@ -68,6 +69,7 @@ func init() { RegisterTranslator("fcos", "1.4.0", fcos1_4.ToIgn3_3Bytes) RegisterTranslator("fcos", "1.5.0", fcos1_5.ToIgn3_4Bytes) RegisterTranslator("fcos", "1.6.0-experimental", fcos1_6_exp.ToIgn3_5Bytes) + RegisterTranslator("fcos", "1.7.0-experimental", fcos1_7_exp.ToIgn3_6Bytes) RegisterTranslator("flatcar", "1.0.0", flatcar1_0.ToIgn3_3Bytes) RegisterTranslator("flatcar", "1.1.0", flatcar1_1.ToIgn3_4Bytes) RegisterTranslator("flatcar", "1.2.0-experimental", flatcar1_2_exp.ToIgn3_5Bytes) diff --git a/config/fcos/v1_7_exp/schema.go b/config/fcos/v1_7_exp/schema.go index c72795ca..b58c15b1 100644 --- a/config/fcos/v1_7_exp/schema.go +++ b/config/fcos/v1_7_exp/schema.go @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_7_exp import ( - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" ) type Config struct { diff --git a/config/fcos/v1_7_exp/translate.go b/config/fcos/v1_7_exp/translate.go index 1428866d..bd31358e 100644 --- a/config/fcos/v1_7_exp/translate.go +++ b/config/fcos/v1_7_exp/translate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_7_exp import ( "fmt" @@ -24,7 +24,7 @@ import ( "github.com/coreos/butane/translate" "github.com/coreos/ignition/v2/config/util" - "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" ) @@ -60,12 +60,12 @@ func (c Config) FieldFilters() *cutil.FieldFilters { return nil } -// ToIgn3_5Unvalidated translates the config to an Ignition config. It also +// ToIgn3_6Unvalidated 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_5Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { - ret, ts, r := c.Config.ToIgn3_5Unvalidated(options) +func (c Config) ToIgn3_6Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { + ret, ts, r := c.Config.ToIgn3_6Unvalidated(options) if r.IsFatal() { return types.Config{}, translate.TranslationSet{}, r } @@ -92,20 +92,20 @@ func (c Config) ToIgn3_5Unvalidated(options common.TranslateOptions) (types.Conf return ret, ts, r } -// ToIgn3_5 translates the config to an Ignition config. It returns a +// ToIgn3_6 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_5(options common.TranslateOptions) (types.Config, report.Report, error) { - cfg, r, err := cutil.Translate(c, "ToIgn3_5Unvalidated", options) +func (c Config) ToIgn3_6(options common.TranslateOptions) (types.Config, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToIgn3_6Unvalidated", options) return cfg.(types.Config), r, err } -// ToIgn3_5Bytes translates from a v1.6 Butane config to a v3.5.0 Ignition config. It returns a report of any errors or +// ToIgn3_6Bytes translates from a v1.6 Butane config to a v3.5.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 ToIgn3_5Bytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { - return cutil.TranslateBytes(input, &Config{}, "ToIgn3_5", options) +func ToIgn3_6Bytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { + return cutil.TranslateBytes(input, &Config{}, "ToIgn3_6", options) } func (c Config) processBootDevice(config *types.Config, ts *translate.TranslationSet, options common.TranslateOptions) report.Report { diff --git a/config/fcos/v1_7_exp/translate_test.go b/config/fcos/v1_7_exp/translate_test.go index 7e90d01d..380a80eb 100644 --- a/config/fcos/v1_7_exp/translate_test.go +++ b/config/fcos/v1_7_exp/translate_test.go @@ -12,20 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_7_exp import ( "fmt" "testing" baseutil "github.com/coreos/butane/base/util" - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" "github.com/coreos/butane/config/common" confutil "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_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" "github.com/stretchr/testify/assert" @@ -46,7 +46,7 @@ func TestTranslateBootDevice(t *testing.T) { Config{}, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, }, []translate.Translation{ @@ -89,7 +89,7 @@ func TestTranslateBootDevice(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Disks: []types.Disk{ @@ -164,7 +164,7 @@ func TestTranslateBootDevice(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Luks: []types.Luks{ @@ -235,7 +235,7 @@ func TestTranslateBootDevice(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Luks: []types.Luks{ @@ -298,7 +298,7 @@ func TestTranslateBootDevice(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Disks: []types.Disk{ @@ -544,7 +544,7 @@ func TestTranslateBootDevice(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Disks: []types.Disk{ @@ -822,7 +822,7 @@ func TestTranslateBootDevice(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Disks: []types.Disk{ @@ -1046,7 +1046,7 @@ func TestTranslateBootDevice(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Disks: []types.Disk{ @@ -1280,7 +1280,7 @@ func TestTranslateBootDevice(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Disks: []types.Disk{ @@ -1495,7 +1495,7 @@ func TestTranslateBootDevice(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { - actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.in.ToIgn3_6Unvalidated(common.TranslateOptions{}) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, test.in, r) assert.Equal(t, test.out, actual, "translation mismatch") @@ -1545,7 +1545,7 @@ func TestTranslateGrub(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -1593,7 +1593,7 @@ func TestTranslateGrub(t *testing.T) { }, types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -1627,7 +1627,7 @@ func TestTranslateGrub(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("translate %d", i), func(t *testing.T) { - actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.in.ToIgn3_6Unvalidated(common.TranslateOptions{}) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, test.in, r) assert.Equal(t, test.out, actual, "translation mismatch") diff --git a/config/fcos/v1_7_exp/validate.go b/config/fcos/v1_7_exp/validate.go index 443f6523..e84ed1e6 100644 --- a/config/fcos/v1_7_exp/validate.go +++ b/config/fcos/v1_7_exp/validate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_7_exp import ( "regexp" diff --git a/config/fcos/v1_7_exp/validate_test.go b/config/fcos/v1_7_exp/validate_test.go index b67a4447..9ba9066b 100644 --- a/config/fcos/v1_7_exp/validate_test.go +++ b/config/fcos/v1_7_exp/validate_test.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_7_exp import ( "fmt" "testing" baseutil "github.com/coreos/butane/base/util" - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" "github.com/coreos/butane/config/common" "github.com/coreos/ignition/v2/config/shared/errors" @@ -120,7 +120,7 @@ func TestReportCorrelation(t *testing.T) { for i, test := range tests { t.Run(fmt.Sprintf("validate %d", i), func(t *testing.T) { - _, r, _ := ToIgn3_5Bytes([]byte(test.in), common.TranslateBytesOptions{}) + _, r, _ := ToIgn3_6Bytes([]byte(test.in), common.TranslateBytesOptions{}) assert.Len(t, r.Entries, 1, "unexpected report length") assert.Equal(t, test.message, r.Entries[0].Message, "bad error") assert.NotNil(t, r.Entries[0].Marker.StartP, "marker start is nil") From 08d07640801fc08cdd865b816ee59c8860d06151 Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:38:40 -0500 Subject: [PATCH 07/13] fcos/v1_6: stabilize v1_6_exp to v1_6 --- config/config.go | 4 ++-- config/fcos/{v1_6_exp => v1_6}/schema.go | 4 ++-- config/fcos/{v1_6_exp => v1_6}/translate.go | 2 +- config/fcos/{v1_6_exp => v1_6}/translate_test.go | 4 ++-- config/fcos/{v1_6_exp => v1_6}/validate.go | 2 +- config/fcos/{v1_6_exp => v1_6}/validate_test.go | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) rename config/fcos/{v1_6_exp => v1_6}/schema.go (95%) rename config/fcos/{v1_6_exp => v1_6}/translate.go (99%) rename config/fcos/{v1_6_exp => v1_6}/translate_test.go (99%) rename config/fcos/{v1_6_exp => v1_6}/validate.go (99%) rename config/fcos/{v1_6_exp => v1_6}/validate_test.go (99%) diff --git a/config/config.go b/config/config.go index ab5d8158..7b5dc277 100644 --- a/config/config.go +++ b/config/config.go @@ -24,7 +24,7 @@ import ( fcos1_3 "github.com/coreos/butane/config/fcos/v1_3" fcos1_4 "github.com/coreos/butane/config/fcos/v1_4" fcos1_5 "github.com/coreos/butane/config/fcos/v1_5" - fcos1_6_exp "github.com/coreos/butane/config/fcos/v1_6_exp" + fcos1_6 "github.com/coreos/butane/config/fcos/v1_6" fcos1_7_exp "github.com/coreos/butane/config/fcos/v1_7_exp" fiot1_0 "github.com/coreos/butane/config/fiot/v1_0" fiot1_1_exp "github.com/coreos/butane/config/fiot/v1_1_exp" @@ -68,7 +68,7 @@ func init() { RegisterTranslator("fcos", "1.3.0", fcos1_3.ToIgn3_2Bytes) RegisterTranslator("fcos", "1.4.0", fcos1_4.ToIgn3_3Bytes) RegisterTranslator("fcos", "1.5.0", fcos1_5.ToIgn3_4Bytes) - RegisterTranslator("fcos", "1.6.0-experimental", fcos1_6_exp.ToIgn3_5Bytes) + RegisterTranslator("fcos", "1.6.0", fcos1_6.ToIgn3_5Bytes) RegisterTranslator("fcos", "1.7.0-experimental", fcos1_7_exp.ToIgn3_6Bytes) RegisterTranslator("flatcar", "1.0.0", flatcar1_0.ToIgn3_3Bytes) RegisterTranslator("flatcar", "1.1.0", flatcar1_1.ToIgn3_4Bytes) diff --git a/config/fcos/v1_6_exp/schema.go b/config/fcos/v1_6/schema.go similarity index 95% rename from config/fcos/v1_6_exp/schema.go rename to config/fcos/v1_6/schema.go index c72795ca..dd63a083 100644 --- a/config/fcos/v1_6_exp/schema.go +++ b/config/fcos/v1_6/schema.go @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_6 import ( - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_6" ) type Config struct { diff --git a/config/fcos/v1_6_exp/translate.go b/config/fcos/v1_6/translate.go similarity index 99% rename from config/fcos/v1_6_exp/translate.go rename to config/fcos/v1_6/translate.go index 1428866d..2d24cc91 100644 --- a/config/fcos/v1_6_exp/translate.go +++ b/config/fcos/v1_6/translate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_6 import ( "fmt" diff --git a/config/fcos/v1_6_exp/translate_test.go b/config/fcos/v1_6/translate_test.go similarity index 99% rename from config/fcos/v1_6_exp/translate_test.go rename to config/fcos/v1_6/translate_test.go index 7e90d01d..99c30253 100644 --- a/config/fcos/v1_6_exp/translate_test.go +++ b/config/fcos/v1_6/translate_test.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_6 import ( "fmt" "testing" baseutil "github.com/coreos/butane/base/util" - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_6" "github.com/coreos/butane/config/common" confutil "github.com/coreos/butane/config/util" "github.com/coreos/butane/translate" diff --git a/config/fcos/v1_6_exp/validate.go b/config/fcos/v1_6/validate.go similarity index 99% rename from config/fcos/v1_6_exp/validate.go rename to config/fcos/v1_6/validate.go index 443f6523..35e5a4c1 100644 --- a/config/fcos/v1_6_exp/validate.go +++ b/config/fcos/v1_6/validate.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_6 import ( "regexp" diff --git a/config/fcos/v1_6_exp/validate_test.go b/config/fcos/v1_6/validate_test.go similarity index 99% rename from config/fcos/v1_6_exp/validate_test.go rename to config/fcos/v1_6/validate_test.go index b67a4447..6c25e02f 100644 --- a/config/fcos/v1_6_exp/validate_test.go +++ b/config/fcos/v1_6/validate_test.go @@ -12,14 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License.) -package v1_6_exp +package v1_6 import ( "fmt" "testing" baseutil "github.com/coreos/butane/base/util" - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_6" "github.com/coreos/butane/config/common" "github.com/coreos/ignition/v2/config/shared/errors" From 65b9bffc0ced4f539db4db213059f2ca13015b08 Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:43:54 -0500 Subject: [PATCH 08/13] fiot/v1_1_exp: update import to new base/v0_7_exp --- config/config.go | 2 +- config/fiot/v1_1_exp/schema.go | 2 +- config/fiot/v1_1_exp/translate.go | 14 +++++++------- config/fiot/v1_1_exp/translate_test.go | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/config/config.go b/config/config.go index 7b5dc277..9f2851aa 100644 --- a/config/config.go +++ b/config/config.go @@ -88,7 +88,7 @@ func init() { RegisterTranslator("r4e", "1.1.0", r4e1_1.ToIgn3_4Bytes) RegisterTranslator("r4e", "1.2.0-experimental", r4e1_2_exp.ToIgn3_5Bytes) RegisterTranslator("fiot", "1.0.0", fiot1_0.ToIgn3_4Bytes) - RegisterTranslator("fiot", "1.1.0-experimental", fiot1_1_exp.ToIgn3_5Bytes) + RegisterTranslator("fiot", "1.1.0-experimental", fiot1_1_exp.ToIgn3_6Bytes) RegisterTranslator("rhcos", "0.1.0", unsupportedRhcosVariant) } diff --git a/config/fiot/v1_1_exp/schema.go b/config/fiot/v1_1_exp/schema.go index e8e5c415..20868d01 100644 --- a/config/fiot/v1_1_exp/schema.go +++ b/config/fiot/v1_1_exp/schema.go @@ -15,7 +15,7 @@ package v1_1_exp import ( - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" ) type Config struct { diff --git a/config/fiot/v1_1_exp/translate.go b/config/fiot/v1_1_exp/translate.go index e10faf8e..abba699c 100644 --- a/config/fiot/v1_1_exp/translate.go +++ b/config/fiot/v1_1_exp/translate.go @@ -18,7 +18,7 @@ import ( "github.com/coreos/butane/config/common" cutil "github.com/coreos/butane/config/util" - "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" "github.com/coreos/vcontext/report" ) @@ -37,18 +37,18 @@ func (c Config) FieldFilters() *cutil.FieldFilters { return &fieldFilters } -// ToIgn3_5 translates the config to an Ignition config. It returns a +// ToIgn3_6 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_5(options common.TranslateOptions) (types.Config, report.Report, error) { - cfg, r, err := cutil.Translate(c, "ToIgn3_5Unvalidated", options) +func (c Config) ToIgn3_6(options common.TranslateOptions) (types.Config, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToIgn3_6Unvalidated", options) return cfg.(types.Config), r, err } -// ToIgn3_5Bytes translates from a v1.2 Butane config to a v3.5.0 Ignition config. It returns a report of any errors or +// ToIgn3_6Bytes translates from a v1.2 Butane config to a v3.5.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 ToIgn3_5Bytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { - return cutil.TranslateBytes(input, &Config{}, "ToIgn3_5", options) +func ToIgn3_6Bytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { + return cutil.TranslateBytes(input, &Config{}, "ToIgn3_6", options) } diff --git a/config/fiot/v1_1_exp/translate_test.go b/config/fiot/v1_1_exp/translate_test.go index cd664f63..fc1d2a23 100644 --- a/config/fiot/v1_1_exp/translate_test.go +++ b/config/fiot/v1_1_exp/translate_test.go @@ -19,7 +19,7 @@ import ( "testing" baseutil "github.com/coreos/butane/base/util" - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" "github.com/coreos/butane/config/common" confutil "github.com/coreos/butane/config/util" "github.com/coreos/ignition/v2/config/util" @@ -169,7 +169,7 @@ func TestTranslateInvalid(t *testing.T) { for _, entry := range test.Entries { expectedReport.AddOnError(entry.Path, entry.Err) } - actual, translations, r := test.In.ToIgn3_5Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.In.ToIgn3_6Unvalidated(common.TranslateOptions{}) r.Merge(fieldFilters.Verify(actual)) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, test.In, r) From 04d18878810f71565ef9242d9f7ccd419700a7cc Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:49:36 -0500 Subject: [PATCH 09/13] flatcar/v1_2_exp: update import to new base/v0_7_exp --- config/config.go | 2 +- config/flatcar/v1_2_exp/schema.go | 2 +- config/flatcar/v1_2_exp/translate.go | 10 +++++----- config/flatcar/v1_2_exp/translate_test.go | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config/config.go b/config/config.go index 9f2851aa..d108862f 100644 --- a/config/config.go +++ b/config/config.go @@ -72,7 +72,7 @@ func init() { RegisterTranslator("fcos", "1.7.0-experimental", fcos1_7_exp.ToIgn3_6Bytes) RegisterTranslator("flatcar", "1.0.0", flatcar1_0.ToIgn3_3Bytes) RegisterTranslator("flatcar", "1.1.0", flatcar1_1.ToIgn3_4Bytes) - RegisterTranslator("flatcar", "1.2.0-experimental", flatcar1_2_exp.ToIgn3_5Bytes) + RegisterTranslator("flatcar", "1.2.0-experimental", flatcar1_2_exp.ToIgn3_6Bytes) RegisterTranslator("openshift", "4.8.0", openshift4_8.ToConfigBytes) RegisterTranslator("openshift", "4.9.0", openshift4_9.ToConfigBytes) RegisterTranslator("openshift", "4.10.0", openshift4_10.ToConfigBytes) diff --git a/config/flatcar/v1_2_exp/schema.go b/config/flatcar/v1_2_exp/schema.go index 3719293d..55da8b63 100644 --- a/config/flatcar/v1_2_exp/schema.go +++ b/config/flatcar/v1_2_exp/schema.go @@ -15,7 +15,7 @@ package v1_2_exp import ( - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" ) type Config struct { diff --git a/config/flatcar/v1_2_exp/translate.go b/config/flatcar/v1_2_exp/translate.go index a9642e34..59bee14e 100644 --- a/config/flatcar/v1_2_exp/translate.go +++ b/config/flatcar/v1_2_exp/translate.go @@ -18,7 +18,7 @@ import ( "github.com/coreos/butane/config/common" cutil "github.com/coreos/butane/config/util" - "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" "github.com/coreos/vcontext/report" ) @@ -37,14 +37,14 @@ func (c Config) FieldFilters() *cutil.FieldFilters { // 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_5(options common.TranslateOptions) (types.Config, report.Report, error) { - cfg, r, err := cutil.Translate(c, "ToIgn3_5Unvalidated", options) +func (c Config) ToIgn3_6(options common.TranslateOptions) (types.Config, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToIgn3_6Unvalidated", options) return cfg.(types.Config), r, err } // ToIgn3_5Bytes translates from a v1.2 Butane config to a v3.5.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 ToIgn3_5Bytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { - return cutil.TranslateBytes(input, &Config{}, "ToIgn3_5", options) +func ToIgn3_6Bytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { + return cutil.TranslateBytes(input, &Config{}, "ToIgn3_6", options) } diff --git a/config/flatcar/v1_2_exp/translate_test.go b/config/flatcar/v1_2_exp/translate_test.go index d276a1af..d0f4b5aa 100644 --- a/config/flatcar/v1_2_exp/translate_test.go +++ b/config/flatcar/v1_2_exp/translate_test.go @@ -19,7 +19,7 @@ import ( "testing" baseutil "github.com/coreos/butane/base/util" - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" "github.com/coreos/butane/config/common" confutil "github.com/coreos/butane/config/util" @@ -69,7 +69,7 @@ func TestTranslation(t *testing.T) { for _, entry := range test.entries { expectedReport.AddOn(entry.path, entry.err, entry.kind) } - actual, translations, r := test.in.ToIgn3_5Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.in.ToIgn3_6Unvalidated(common.TranslateOptions{}) if test.in.FieldFilters() != nil { r.Merge(test.in.FieldFilters().Verify(actual)) } From 95fb547109ea092f466124c4b01519dc96a8ce91 Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:50:34 -0500 Subject: [PATCH 10/13] r4e/v1_2_exp: update import to new base/v0_7_exp --- config/config.go | 2 +- config/r4e/v1_2_exp/schema.go | 2 +- config/r4e/v1_2_exp/translate.go | 14 +++++++------- config/r4e/v1_2_exp/translate_test.go | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/config/config.go b/config/config.go index d108862f..b4c22c49 100644 --- a/config/config.go +++ b/config/config.go @@ -86,7 +86,7 @@ func init() { RegisterTranslator("openshift", "4.18.0-experimental", openshift4_18_exp.ToConfigBytes) RegisterTranslator("r4e", "1.0.0", r4e1_0.ToIgn3_3Bytes) RegisterTranslator("r4e", "1.1.0", r4e1_1.ToIgn3_4Bytes) - RegisterTranslator("r4e", "1.2.0-experimental", r4e1_2_exp.ToIgn3_5Bytes) + RegisterTranslator("r4e", "1.2.0-experimental", r4e1_2_exp.ToIgn3_6Bytes) RegisterTranslator("fiot", "1.0.0", fiot1_0.ToIgn3_4Bytes) RegisterTranslator("fiot", "1.1.0-experimental", fiot1_1_exp.ToIgn3_6Bytes) RegisterTranslator("rhcos", "0.1.0", unsupportedRhcosVariant) diff --git a/config/r4e/v1_2_exp/schema.go b/config/r4e/v1_2_exp/schema.go index ffece594..56eea478 100644 --- a/config/r4e/v1_2_exp/schema.go +++ b/config/r4e/v1_2_exp/schema.go @@ -15,7 +15,7 @@ package v1_2_exp import ( - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" ) type Config struct { diff --git a/config/r4e/v1_2_exp/translate.go b/config/r4e/v1_2_exp/translate.go index cce6d222..cde4d4a0 100644 --- a/config/r4e/v1_2_exp/translate.go +++ b/config/r4e/v1_2_exp/translate.go @@ -18,7 +18,7 @@ import ( "github.com/coreos/butane/config/common" cutil "github.com/coreos/butane/config/util" - "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" "github.com/coreos/vcontext/report" ) @@ -37,18 +37,18 @@ func (c Config) FieldFilters() *cutil.FieldFilters { return &fieldFilters } -// ToIgn3_5 translates the config to an Ignition config. It returns a +// ToIgn3_6 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_5(options common.TranslateOptions) (types.Config, report.Report, error) { - cfg, r, err := cutil.Translate(c, "ToIgn3_5Unvalidated", options) +func (c Config) ToIgn3_6(options common.TranslateOptions) (types.Config, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToIgn3_6Unvalidated", options) return cfg.(types.Config), r, err } -// ToIgn3_5Bytes translates from a v1.2 Butane config to a v3.5.0 Ignition config. It returns a report of any errors or +// ToIgn3_6Bytes translates from a v1.2 Butane config to a v3.5.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 ToIgn3_5Bytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { - return cutil.TranslateBytes(input, &Config{}, "ToIgn3_5", options) +func ToIgn3_6Bytes(input []byte, options common.TranslateBytesOptions) ([]byte, report.Report, error) { + return cutil.TranslateBytes(input, &Config{}, "ToIgn3_6", options) } diff --git a/config/r4e/v1_2_exp/translate_test.go b/config/r4e/v1_2_exp/translate_test.go index df4f2061..efd656be 100644 --- a/config/r4e/v1_2_exp/translate_test.go +++ b/config/r4e/v1_2_exp/translate_test.go @@ -19,7 +19,7 @@ import ( "testing" baseutil "github.com/coreos/butane/base/util" - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" "github.com/coreos/butane/config/common" confutil "github.com/coreos/butane/config/util" "github.com/coreos/ignition/v2/config/util" @@ -169,7 +169,7 @@ func TestTranslateInvalid(t *testing.T) { for _, entry := range test.Entries { expectedReport.AddOnError(entry.Path, entry.Err) } - actual, translations, r := test.In.ToIgn3_5Unvalidated(common.TranslateOptions{}) + actual, translations, r := test.In.ToIgn3_6Unvalidated(common.TranslateOptions{}) r.Merge(fieldFilters.Verify(actual)) r = confutil.TranslateReportPaths(r, translations) baseutil.VerifyReport(t, test.In, r) From 4cb7c67c839145f64ba122bf849faf95d31122a0 Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 14:58:50 -0500 Subject: [PATCH 11/13] openshift/v4_18_exp: update import for fcos and base to latest --- config/openshift/v4_18_exp/result/schema.go | 2 +- config/openshift/v4_18_exp/schema.go | 2 +- config/openshift/v4_18_exp/translate.go | 18 +++++++++--------- config/openshift/v4_18_exp/translate_test.go | 14 +++++++------- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/config/openshift/v4_18_exp/result/schema.go b/config/openshift/v4_18_exp/result/schema.go index 62bc2054..09b5b597 100644 --- a/config/openshift/v4_18_exp/result/schema.go +++ b/config/openshift/v4_18_exp/result/schema.go @@ -15,7 +15,7 @@ package result import ( - "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" ) const ( diff --git a/config/openshift/v4_18_exp/schema.go b/config/openshift/v4_18_exp/schema.go index b9dfbe4d..36df2720 100644 --- a/config/openshift/v4_18_exp/schema.go +++ b/config/openshift/v4_18_exp/schema.go @@ -15,7 +15,7 @@ package v4_18_exp import ( - fcos "github.com/coreos/butane/config/fcos/v1_6_exp" + fcos "github.com/coreos/butane/config/fcos/v1_7_exp" ) const ROLE_LABEL_KEY = "machineconfiguration.openshift.io/role" diff --git a/config/openshift/v4_18_exp/translate.go b/config/openshift/v4_18_exp/translate.go index 09d22bb7..a74f793a 100644 --- a/config/openshift/v4_18_exp/translate.go +++ b/config/openshift/v4_18_exp/translate.go @@ -24,7 +24,7 @@ import ( "github.com/coreos/butane/translate" "github.com/coreos/ignition/v2/config/util" - "github.com/coreos/ignition/v2/config/v3_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" ) @@ -113,7 +113,7 @@ func (c Config) FieldFilters() *cutil.FieldFilters { // can be tracked back to their source in the source config. No config // validation is performed on input or output. func (c Config) ToMachineConfig4_18Unvalidated(options common.TranslateOptions) (result.MachineConfig, translate.TranslationSet, report.Report) { - cfg, ts, r := c.Config.ToIgn3_5Unvalidated(options) + cfg, ts, r := c.Config.ToIgn3_6Unvalidated(options) if r.IsFatal() { return result.MachineConfig{}, ts, r } @@ -174,11 +174,11 @@ func (c Config) ToMachineConfig4_18(options common.TranslateOptions) (result.Mac return cfg.(result.MachineConfig), r, err } -// ToIgn3_5Unvalidated translates the config to an Ignition config. It also +// ToIgn3_6Unvalidated 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_5Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { +func (c Config) ToIgn3_6Unvalidated(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { mc, ts, r := c.ToMachineConfig4_18Unvalidated(options) cfg := mc.Spec.Config @@ -194,21 +194,21 @@ func (c Config) ToIgn3_5Unvalidated(options common.TranslateOptions) (types.Conf return cfg, ts, r } -// ToIgn3_5 translates the config to an Ignition config. It returns a +// ToIgn3_6 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_5(options common.TranslateOptions) (types.Config, report.Report, error) { - cfg, r, err := cutil.Translate(c, "ToIgn3_5Unvalidated", options) +func (c Config) ToIgn3_6(options common.TranslateOptions) (types.Config, report.Report, error) { + cfg, r, err := cutil.Translate(c, "ToIgn3_6Unvalidated", options) return cfg.(types.Config), r, err } -// ToConfigBytes translates from a v4.17 Butane config to a v4.17 MachineConfig or a v3.5.0 Ignition config. It returns a report of any errors or +// ToConfigBytes translates from a v4.17 Butane config to a v4.17 MachineConfig or a v3.6.0-experimental 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_5", options) + return cutil.TranslateBytes(input, &Config{}, "ToIgn3_6", options) } else { return cutil.TranslateBytesYAML(input, &Config{}, "ToMachineConfig4_18", options) } diff --git a/config/openshift/v4_18_exp/translate_test.go b/config/openshift/v4_18_exp/translate_test.go index 796814c4..7b8a0f8b 100644 --- a/config/openshift/v4_18_exp/translate_test.go +++ b/config/openshift/v4_18_exp/translate_test.go @@ -19,15 +19,15 @@ import ( "testing" baseutil "github.com/coreos/butane/base/util" - base "github.com/coreos/butane/base/v0_6_exp" + base "github.com/coreos/butane/base/v0_7_exp" "github.com/coreos/butane/config/common" - fcos "github.com/coreos/butane/config/fcos/v1_6_exp" + fcos "github.com/coreos/butane/config/fcos/v1_7_exp" "github.com/coreos/butane/config/openshift/v4_18_exp/result" confutil "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_5/types" + "github.com/coreos/ignition/v2/config/v3_6_experimental/types" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" "github.com/stretchr/testify/assert" @@ -52,7 +52,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_5Unvalidated(common.TranslateOptions{}) + _, _, r := in.ToIgn3_6Unvalidated(common.TranslateOptions{}) assert.Equal(t, expected, r, "report mismatch") } @@ -84,7 +84,7 @@ func TestTranslateConfig(t *testing.T) { Spec: result.Spec{ Config: types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, }, }, @@ -159,7 +159,7 @@ func TestTranslateConfig(t *testing.T) { Spec: result.Spec{ Config: types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ @@ -304,7 +304,7 @@ func TestTranslateConfig(t *testing.T) { Spec: result.Spec{ Config: types.Config{ Ignition: types.Ignition{ - Version: "3.5.0", + Version: "3.6.0-experimental", }, Storage: types.Storage{ Filesystems: []types.Filesystem{ From f9142bca253e483a85622a66eb12e3e9635d15fb Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 15:15:52 -0500 Subject: [PATCH 12/13] docs/main: add new fcos/v1_7_exp spec to doc generation, and run it --- docs/config-fcos-v1_6.md | 229 ++++++++++++++++++ ...os-v1_6-exp.md => config-fcos-v1_7-exp.md} | 6 +- docs/config-fiot-v1_1-exp.md | 2 +- docs/config-flatcar-v1_2-exp.md | 2 +- docs/config-openshift-v4_18-exp.md | 2 +- docs/config-r4e-v1_2-exp.md | 2 +- internal/doc/main.go | 6 +- 7 files changed, 240 insertions(+), 9 deletions(-) create mode 100644 docs/config-fcos-v1_6.md rename docs/{config-fcos-v1_6-exp.md => config-fcos-v1_7-exp.md} (99%) diff --git a/docs/config-fcos-v1_6.md b/docs/config-fcos-v1_6.md new file mode 100644 index 00000000..1fc92fdd --- /dev/null +++ b/docs/config-fcos-v1_6.md @@ -0,0 +1,229 @@ +--- +# This file is automatically generated from internal/doc and Ignition's +# config/doc. Do not edit. +title: Fedora CoreOS v1.6.0 +parent: Configuration specifications +nav_order: 43 +--- + +# Fedora CoreOS Specification v1.6.0 + +The Fedora CoreOS 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 `fcos` for this specification. +* **version** (string): the semantic version of the spec for this document. This document is for version `1.6.0` and generates Ignition configs with version `3.5.0`. +* **_ignition_** (object): metadata about the configuration itself. + * **_config_** (object): 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`, `tftp`, `s3`, `arn`, `gs`, and [`data`](https://tools.ietf.org/html/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 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`, `tftp`, `s3`, `arn`, `gs`, and [`data`](https://tools.ietf.org/html/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 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). The bundle can contain multiple concatenated certificates. Supported schemes are `http`, `https`, `tftp`, `s3`, `arn`, `gs`, and [`data`](https://tools.ietf.org/html/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 certificate bundle (in PEM format). 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. The bundle can contain multiple concatenated certificates. Mutually exclusive with `source` and `inline`. + * **_compression_** (string): the type of compression used on the certificate bundle (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 bundle. + * **_hash_** (string): the hash of the certificate bundle, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed certificate bundle. + * **_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. The boot disk can be referenced as `/dev/disk/by-id/coreos-boot-disk`. + * **_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](https://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs). 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, btrfs, xfs, vfat, swap, or none). + * **_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 [Ignition's 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_** (boolean): 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](https://www.freedesktop.org/software/systemd/man/systemd-escape.html) 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. + * **_source_** (string): the URL of the file. Supported schemes are `http`, `https`, `tftp`, `s3`, `arn`, `gs`, and [`data`](https://tools.ietf.org/html/rfc2397). When using `http`, it is advisable to use the verification option to ensure the contents haven't been modified. 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`. + * **_compression_** (string): the type of compression used on the file (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 file. + * **_hash_** (string): the hash of the file, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed file. + * **_append_** (list of objects): list of fragments to be appended to the file. Follows the same structure as `contents`. + * **_source_** (string): the URL of the fragment. Supported schemes are `http`, `https`, `tftp`, `s3`, `arn`, `gs`, and [`data`](https://tools.ietf.org/html/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 fragment. Mutually exclusive with `source` and `local`. + * **_local_** (string): a local path to the contents of the fragment, 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 fragment (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 fragment. + * **_hash_** (string): the hash of the fragment, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed fragment. + * **_mode_** (integer): the file's permission mode. Setuid/setgid/sticky bits are 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. + * **_directories_** (list of objects): the list of directories to be created. Every file, directory, and link must have a unique `path`. + * **path** (string): the absolute path to the directory. + * **_overwrite_** (boolean): whether to delete preexisting nodes at the path. If false and a directory already exists at the path, Ignition will only set its permissions. If false and a non-directory exists at that path, Ignition will fail. Defaults to false. + * **_mode_** (integer): the directory's permission mode. Setuid/setgid/sticky bits are supported. If not specified, the permission mode for directories defaults to 0755 or the mode of an existing directory if `overwrite` is false and a directory already exists at the path. + * **_user_** (object): specifies the directory's owner. + * **_id_** (integer): the user ID of the owner. + * **_name_** (string): the user name of the owner. + * **_group_** (object): specifies the directory's group. + * **_id_** (integer): the group ID of the group. + * **_name_** (string): the group name of the group. + * **_links_** (list of objects): the list of links to be created. Every file, directory, and link must have a unique `path`. + * **path** (string): the absolute path to the link + * **_overwrite_** (boolean): whether to delete preexisting nodes at the path. If overwrite is false and a matching link exists at the path, Ignition will only set the owner and group. Defaults to false. + * **_user_** (object): specifies the owner for a symbolic link. Ignored for hard links. + * **_id_** (integer): the user ID of the owner. + * **_name_** (string): the user name of the owner. + * **_group_** (object): specifies the group for a symbolic link. Ignored for hard links. + * **_id_** (integer): the group ID of the group. + * **_name_** (string): the group name of the group. + * **target** (string): the target path of the link + * **_hard_** (boolean): a symbolic link is created if this is false, a hard one if this is true. + * **_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_** (object): options related to the contents of the key file. + * **_source_** (string): the URL of the key file. Supported schemes are `http`, `https`, `tftp`, `s3`, `arn`, `gs`, and [`data`](https://tools.ietf.org/html/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`. + * **_compression_** (string): the type of compression used on the key file (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 key file. + * **_hash_** (string): the hash of the key file, in the form `-` where type is either `sha512` or `sha256`. If `compression` is specified, the hash describes the decompressed key file. + * **_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 [Ignition's 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_** (boolean): whether or not to use a tpm2 device. + * **_threshold_** (integer): 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_** (boolean): whether or not the device requires networking. + * **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device. + * **_enabled_** (boolean): whether or not to enable cex compatibility for luks. If omitted, defaults to false. + * **_trees_** (list of objects): a list of local directory trees to be embedded in the config. Ownership is not preserved. File modes are set to 0755 if the local file is executable or 0644 otherwise. Attributes of files, directories, and symlinks can be overridden by creating a corresponding entry in the `files`, `directories`, or `links` section; such `files` entries must omit `contents` and such `links` entries must omit `target`. + * **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`. When false, the service is unmasked by deleting the symlink to `/dev/null` if it exists. + * **_contents_** (string): the contents of the unit. Mutually exclusive with `contents_local`. + * **_contents_local_** (string): a local path to the contents of the unit, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `contents`. + * **_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. Mutually exclusive with `contents_local`. + * **_contents_local_** (string): a local path to the contents of the drop-in, relative to the directory specified by the `--files-dir` command-line argument. Mutually exclusive with `contents`. +* **_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. + * **_password_hash_** (string): the hashed password for the account. + * **_ssh_authorized_keys_** (list of strings): a list of SSH keys to be added as an SSH key fragment at `.ssh/authorized_keys.d/ignition` in the user's home directory. All SSH keys must be unique. + * **_ssh_authorized_keys_local_** (list of strings): a list of local paths to SSH key files, relative to the directory specified by the `--files-dir` command-line argument, to be added as SSH key fragments at `.ssh/authorized_keys.d/ignition` in the user's home directory. All SSH keys must be unique. Each file may contain multiple SSH keys, one per line. + * **_uid_** (integer): the user ID of the account. + * **_gecos_** (string): the GECOS field of the account. + * **_home_dir_** (string): the home directory of the account. + * **_no_create_home_** (boolean): whether or not to create the user's home directory. This only has an effect if the account doesn't exist yet. + * **_primary_group_** (string): the name of the primary group of the account. + * **_groups_** (list of strings): the list of supplementary groups of the account. + * **_no_user_group_** (boolean): whether or not to create a group with the same name as the user. This only has an effect if the account doesn't exist yet. + * **_no_log_init_** (boolean): whether or not to add the user to the lastlog and faillog databases. This only has an effect if the account doesn't exist yet. + * **_shell_** (string): the login shell of the new account. + * **_should_exist_** (boolean): whether or not the user with the specified `name` should exist. If omitted, it defaults to true. If false, then Ignition will delete the specified user. + * **_system_** (boolean): whether or not this account should be a system account. This only has an effect if the account doesn't exist yet. + * **_groups_** (list of objects): the list of groups to be added. All groups must have a unique `name`. + * **name** (string): the name of the group. + * **_gid_** (integer): the group ID of the new group. + * **_password_hash_** (string): the hashed password of the new group. + * **_should_exist_** (boolean): whether or not the group with the specified `name` should exist. If omitted, it defaults to true. If false, then Ignition will delete the specified group. + * **_system_** (boolean): whether or not the group should be a system group. This only has an effect if the group doesn't exist yet. +* **_kernel_arguments_** (object): describes the desired kernel arguments. + * **_should_exist_** (list of strings): the list of kernel arguments that should exist. + * **_should_not_exist_** (list of strings): the list of kernel arguments that should not exist. +* **_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`, `s390x-eckd`, `s390x-virt`, `s390x-zfcp`, and `x86_64`. Defaults to `x86_64`. + * **_luks_** (object): describes the clevis configuration for encrypting the root filesystem. + * **_device_** (string): the whole-disk device (not partitions), referenced by their absolute path. Must start with `/dev/dasd` for `s390x-eckd` layout or `/dev/sd` for `s390x-zfcp` layouts. + * **_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_** (boolean): whether or not to use a tpm2 device. + * **_threshold_** (integer): 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. + * **_cex_** (object): describes the IBM Crypto Express (CEX) card configuration for the luks device. + * **_enabled_** (boolean): whether or not to enable cex compatibility for luks. If omitted, 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. +* **_grub_** (object): describes the desired GRUB bootloader configuration. + * **_users_** (list of objects): the list of GRUB superusers. + * **name** (string): the user name. + * **password_hash** (string): the PBKDF2 password hash, generated with `grub2-mkpasswd-pbkdf2`. diff --git a/docs/config-fcos-v1_6-exp.md b/docs/config-fcos-v1_7-exp.md similarity index 99% rename from docs/config-fcos-v1_6-exp.md rename to docs/config-fcos-v1_7-exp.md index 4e3ad5cf..de740ada 100644 --- a/docs/config-fcos-v1_6-exp.md +++ b/docs/config-fcos-v1_7-exp.md @@ -1,12 +1,12 @@ --- # This file is automatically generated from internal/doc and Ignition's # config/doc. Do not edit. -title: Fedora CoreOS v1.6.0-experimental +title: Fedora CoreOS v1.7.0-experimental parent: Configuration specifications nav_order: 50 --- -# Fedora CoreOS Specification v1.6.0-experimental +# Fedora CoreOS Specification v1.7.0-experimental **Note: This configuration is experimental and has not been stabilized. It is subject to change without warning or announcement.** @@ -15,7 +15,7 @@ The Fedora CoreOS configuration is a YAML document conforming to the following s
* **variant** (string): used to differentiate configs for different operating systems. Must be `fcos` for this specification. -* **version** (string): the semantic version of the spec for this document. This document is for version `1.6.0-experimental` and generates Ignition configs with version `3.5.0`. +* **version** (string): the semantic version of the spec for this document. This document is for version `1.7.0-experimental` and generates Ignition configs with version `3.6.0-experimental`. * **_ignition_** (object): metadata about the configuration itself. * **_config_** (object): options related to the configuration. * **_merge_** (list of objects): a list of the configs to be merged to the current config. diff --git a/docs/config-fiot-v1_1-exp.md b/docs/config-fiot-v1_1-exp.md index b220a976..3e8c33fb 100644 --- a/docs/config-fiot-v1_1-exp.md +++ b/docs/config-fiot-v1_1-exp.md @@ -15,7 +15,7 @@ The Fedora IoT configuration is a YAML document conforming to the following spec
* **variant** (string): used to differentiate configs for different operating systems. Must be `%VARIANT%` for this specification. -* **version** (string): the semantic version of the spec for this document. This document is for version `%VERSION%` and generates Ignition configs with version `3.5.0`. +* **version** (string): the semantic version of the spec for this document. This document is for version `%VERSION%` and generates Ignition configs with version `3.6.0-experimental`. * **_ignition_** (object): metadata about the configuration itself. * **_config_** (object): options related to the configuration. * **_merge_** (list of objects): a list of the configs to be merged to the current config. diff --git a/docs/config-flatcar-v1_2-exp.md b/docs/config-flatcar-v1_2-exp.md index 30089f8e..806556b4 100644 --- a/docs/config-flatcar-v1_2-exp.md +++ b/docs/config-flatcar-v1_2-exp.md @@ -15,7 +15,7 @@ The Flatcar configuration is a YAML document conforming to the following specifi
* **variant** (string): used to differentiate configs for different operating systems. Must be `flatcar` for this specification. -* **version** (string): the semantic version of the spec for this document. This document is for version `1.2.0-experimental` and generates Ignition configs with version `3.5.0`. +* **version** (string): the semantic version of the spec for this document. This document is for version `1.2.0-experimental` and generates Ignition configs with version `3.6.0-experimental`. * **_ignition_** (object): metadata about the configuration itself. * **_config_** (object): options related to the configuration. * **_merge_** (list of objects): a list of the configs to be merged to the current config. diff --git a/docs/config-openshift-v4_18-exp.md b/docs/config-openshift-v4_18-exp.md index 06955ad9..a1d39f15 100644 --- a/docs/config-openshift-v4_18-exp.md +++ b/docs/config-openshift-v4_18-exp.md @@ -15,7 +15,7 @@ The OpenShift configuration is a YAML document conforming to the following speci
* **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.18.0-experimental` and generates Ignition configs with version `3.5.0`. +* **version** (string): the semantic version of the spec for this document. This document is for version `4.18.0-experimental` and generates Ignition configs with version `3.6.0-experimental`. * **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](https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names) for this MachineConfig resource. * **labels** (object): string key/value pairs to apply as [Kubernetes labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) to this MachineConfig resource. `machineconfiguration.openshift.io/role` is required. diff --git a/docs/config-r4e-v1_2-exp.md b/docs/config-r4e-v1_2-exp.md index 2913caf5..9f309af0 100644 --- a/docs/config-r4e-v1_2-exp.md +++ b/docs/config-r4e-v1_2-exp.md @@ -15,7 +15,7 @@ The RHEL for Edge configuration is a YAML document conforming to the following s
* **variant** (string): used to differentiate configs for different operating systems. Must be `r4e` for this specification. -* **version** (string): the semantic version of the spec for this document. This document is for version `1.2.0-experimental` and generates Ignition configs with version `3.5.0`. +* **version** (string): the semantic version of the spec for this document. This document is for version `1.2.0-experimental` and generates Ignition configs with version `3.6.0-experimental`. * **_ignition_** (object): metadata about the configuration itself. * **_config_** (object): options related to the configuration. * **_merge_** (list of objects): a list of the configs to be merged to the current config. diff --git a/internal/doc/main.go b/internal/doc/main.go index 3da80765..25d87dd7 100644 --- a/internal/doc/main.go +++ b/internal/doc/main.go @@ -41,7 +41,8 @@ import ( fcos1_3 "github.com/coreos/butane/config/fcos/v1_3" fcos1_4 "github.com/coreos/butane/config/fcos/v1_4" fcos1_5 "github.com/coreos/butane/config/fcos/v1_5" - fcos1_6_exp "github.com/coreos/butane/config/fcos/v1_6_exp" + fcos1_6 "github.com/coreos/butane/config/fcos/v1_6" + fcos1_7_exp "github.com/coreos/butane/config/fcos/v1_7_exp" fiot1_0 "github.com/coreos/butane/config/fiot/v1_0" fiot1_1_exp "github.com/coreos/butane/config/fiot/v1_1_exp" flatcar1_0 "github.com/coreos/butane/config/flatcar/v1_0" @@ -102,13 +103,14 @@ func generate(dir string) error { "fcos", []version{ // inverse order of website navbar - {"1.6.0-experimental", fcos1_6_exp.Config{}}, + {"1.7.0-experimental", fcos1_7_exp.Config{}}, {"1.0.0", fcos1_0.Config{}}, {"1.1.0", fcos1_1.Config{}}, {"1.2.0", fcos1_2.Config{}}, {"1.3.0", fcos1_3.Config{}}, {"1.4.0", fcos1_4.Config{}}, {"1.5.0", fcos1_5.Config{}}, + {"1.6.0", fcos1_6.Config{}}, }, }, { From 142bfebe6297d8a0d221d8fb58a529c6e375108f Mon Sep 17 00:00:00 2001 From: Steven Presti Date: Tue, 26 Nov 2024 15:44:25 -0500 Subject: [PATCH 13/13] docs: update documentation for stablized and updated configs --- docs/examples.md | 8 ++--- docs/release-notes.md | 13 +++++++- docs/specs.md | 6 ++-- docs/upgrading-fcos.md | 70 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 7 deletions(-) diff --git a/docs/examples.md b/docs/examples.md index b6061633..57d3fa2f 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -301,7 +301,7 @@ This example uses the shortcut `boot_device` syntax to configure an encrypted ro ```yaml variant: fcos -version: 1.6.0-experimental +version: 1.6.0 boot_device: layout: s390x-eckd luks: @@ -316,7 +316,7 @@ This example uses the shortcut `boot_device` syntax to configure an encrypted ro ```yaml variant: fcos -version: 1.6.0-experimental +version: 1.6.0 boot_device: layout: s390x-zfcp luks: @@ -331,7 +331,7 @@ This example uses the shortcut `boot_device` syntax to configure an encrypted ro ```yaml variant: fcos -version: 1.6.0-experimental +version: 1.6.0 boot_device: layout: s390x-virt luks: @@ -345,7 +345,7 @@ This example uses the shortcut `boot_device` syntax to configure an encrypted ro ```yaml variant: fcos -version: 1.6.0-experimental +version: 1.6.0 boot_device: layout: s390x-eckd luks: diff --git a/docs/release-notes.md b/docs/release-notes.md index 1b60416d..8b2caed0 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -8,7 +8,18 @@ nav_order: 9 ### Features -- Support LUKS encryption using IBM CEX secure keys on s390x _(fcos 1.6-exp)_ _(openshift 4.18.0-exp)_ +- Stabilize Fcos spec 1.6.0, targeting Ignition spec 3.5.0 +- Add Fcos spec 1.7.0-experimental, targeting Ignition spec + 3.6.0-experimental +- Update Fiot spec 1.1.0-experimental to target Ignition spec + 3.6.0-experimental +- Update Flatcar spec 1.2.0-experimental to target Ignition spec + 3.6.0-experimental +- Update OpenShift spec 4.18.0-experimental, targeting Ignition spec + 3.6.0-experimental +- Update R4e spec 1.2.0-experimental to target Ignition spec + 3.6.0-experimental +- Support LUKS encryption using IBM CEX secure keys on s390x _(fcos 1.6)_ _(openshift 4.18.0-exp)_ ### Bug fixes diff --git a/docs/specs.md b/docs/specs.md index ead1f45f..33c978ef 100644 --- a/docs/specs.md +++ b/docs/specs.md @@ -15,6 +15,7 @@ See the [Upgrading Configs](upgrading.md) page for instructions to update a conf We recommend that you always use the latest **stable** specification for your operating system to benefit from new features and bug fixes. The following **stable** specification versions are currently supported in Butane: - Fedora CoreOS (`fcos`) + - [v1.6.0](config-fcos-v1_6.md) - [v1.5.0](config-fcos-v1_5.md) - [v1.4.0](config-fcos-v1_4.md) - [v1.3.0](config-fcos-v1_3.md) @@ -46,7 +47,7 @@ We recommend that you always use the latest **stable** specification for your op Do not use **experimental** specifications for anything beyond **development and testing** as they are subject to change **without warning or announcement**. The following **experimental** specification versions are currently available in Butane: - Fedora CoreOS (`fcos`) - - [v1.6.0-experimental](config-fcos-v1_6-exp.md) + - [v1.7.0-experimental](config-fcos-v1_7-exp.md) - Flatcar (`flatcar`) - [v1.2.0-experimental](config-flatcar-v1_2-exp.md) - OpenShift (`openshift`) @@ -68,7 +69,8 @@ Each version of the Butane specification corresponds to a version of the Ignitio | `fcos` | 1.3.0 | 3.2.0 | | `fcos` | 1.4.0 | 3.3.0 | | `fcos` | 1.5.0 | 3.4.0 | -| `fcos` | 1.6.0-experimental | 3.5.0-experimental | +| `fcos` | 1.6.0 | 3.5.0 | +| `fcos` | 1.7.0-experimental | 3.6.0-experimental | | `flatcar` | 1.0.0 | 3.3.0 | | `flatcar` | 1.1.0 | 3.4.0 | | `flatcar` | 1.2.0-experimental | 3.5.0-experimental | diff --git a/docs/upgrading-fcos.md b/docs/upgrading-fcos.md index 3a4c89c7..dd5fa3cc 100644 --- a/docs/upgrading-fcos.md +++ b/docs/upgrading-fcos.md @@ -13,6 +13,76 @@ Occasionally, changes are made to Fedora CoreOS Butane configs (those that speci 1. TOC {:toc} +## From Version 1.5.0 to Version 1.6.0 + +There are no breaking changes between versions 1.5.0 and 1.6.0 of the `fcos` configuration specification. Any valid 1.5.0 configuration can be updated to a 1.6.0 configuration by changing the version string in the config. + +The following is a list of notable new features. + +### LUKS CEX support + +The `luks` sections in `storage` and `boot_device` gained a `cex` field. If enabled, this will configure an encrypted root filesystem on a s390x system using IBM Crypto Express (CEX) card. + + +```yaml +variant: fcos +version: 1.6.0 +boot_device: + layout: s390x-eckd + luks: + device: /dev/dasda + cex: + enabled: true +``` + +### Boot_Device Layouts s390x support + +The `boot_device` section gained support for the following layouts `s390x-eckd`, `s390x-zfcp`, `s390x-virt`. This enables the use of the `boot_device` sugar for s390x systems. + +The `s390x-eckd` layout enables configuration of an encrypted root filesystem for a DASD device. + + +```yaml +variant: fcos +version: 1.6.0 +boot_device: + layout: s390x-eckd + luks: + device: /dev/dasda + tang: + - url: https://tang.example.com + thumbprint: REPLACE-THIS-WITH-YOUR-TANG-THUMBPRINT +``` + +The `s390x-zfcp` layout enables configuration of an encrypted root filesystem for a zFCP device. + + +```yaml +variant: fcos +version: 1.6.0 +boot_device: + layout: s390x-zfcp + luks: + device: /dev/sdb + tang: + - url: https://tang.example.com + thumbprint: REPLACE-THIS-WITH-YOUR-TANG-THUMBPRINT +``` + +The `s390x-virt` layout enables configuration of an encrypted root filesystem for KVM. + + +```yaml +variant: fcos +version: 1.6.0 +boot_device: + layout: s390x-virt + luks: + tang: + - url: https://tang.example.com + thumbprint: REPLACE-THIS-WITH-YOUR-TANG-THUMBPRINT +``` + ## From Version 1.4.0 to Version 1.5.0 There are no breaking changes between versions 1.4.0 and 1.5.0 of the `fcos` configuration specification. Any valid 1.4.0 configuration can be updated to a 1.5.0 configuration by changing the version string in the config.