diff --git a/config/fcos/v1_5_exp/schema.go b/config/fcos/v1_5_exp/schema.go index 452450b5..5d0847fe 100644 --- a/config/fcos/v1_5_exp/schema.go +++ b/config/fcos/v1_5_exp/schema.go @@ -21,6 +21,7 @@ import ( type Config struct { base.Config `yaml:",inline"` BootDevice BootDevice `yaml:"boot_device"` + Extensions Extensions `yaml:"extensions"` } type BootDevice struct { @@ -38,3 +39,5 @@ type BootDeviceLuks struct { type BootDeviceMirror struct { Devices []string `yaml:"devices"` } + +type Extensions []string diff --git a/config/fcos/v1_5_exp/translate.go b/config/fcos/v1_5_exp/translate.go index 21f1b786..c2c19f86 100644 --- a/config/fcos/v1_5_exp/translate.go +++ b/config/fcos/v1_5_exp/translate.go @@ -15,6 +15,8 @@ package v1_5_exp import ( + "crypto/sha256" + "encoding/hex" "fmt" baseutil "github.com/coreos/butane/base/util" @@ -26,6 +28,7 @@ import ( "github.com/coreos/ignition/v2/config/v3_4_experimental/types" "github.com/coreos/vcontext/path" "github.com/coreos/vcontext/report" + "gopkg.in/yaml.v3" ) const ( @@ -78,6 +81,11 @@ func (c Config) ToIgn3_4Unvalidated(options common.TranslateOptions) (types.Conf } } } + + retp, tsp, rp := c.processPackages(options) + retConfig, ts := baseutil.MergeTranslatedConfigs(retp, tsp, ret, ts) + ret = retConfig.(types.Config) + r.Merge(rp) return ret, ts, r } @@ -292,3 +300,50 @@ func translateBootDeviceLuks(from BootDeviceLuks, options common.TranslateOption tm.AddTranslation(path.New("yaml"), path.New("json")) return } + +func (c Config) processPackages(options common.TranslateOptions) (types.Config, translate.TranslationSet, report.Report) { + yamlPath := path.New("yaml", "extensions") + ret := types.Config{} + ts := translate.NewTranslationSet("yaml", "json") + var r report.Report + if len(c.Extensions) == 0 { + return ret, ts, r + } + + treeFileContents, err := yaml.Marshal(&struct { + Packages []string `yaml:"packages"` + }{ + Packages: c.Extensions, + }) + if err != nil { + r.AddOnError(yamlPath, err) + return ret, ts, r + } + fullYamlContents := append([]byte("# Generated by Butane\n\n"), treeFileContents...) + src, gzipped, err := baseutil.MakeDataURL(fullYamlContents, nil, !options.NoResourceAutoCompression) + if err != nil { + r.AddOnError(yamlPath, err) + return ret, ts, r + } + hash := sha256.New() + hash.Write([]byte(src)) + sha := hex.EncodeToString(hash.Sum(nil))[0:7] + file := types.File{ + Node: types.Node{ + Path: "/etc/rpm-ostree/origin.d/extensions-" + sha + ".yaml", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr(src), + }, + Mode: util.IntToPtr(0644), + }, + } + if gzipped { + file.Contents.Compression = util.StrToPtr("gzip") + } + + ret.Storage.Files = append(ret.Storage.Files, file) + ts.AddFromCommonSource(yamlPath, path.New("json", "storage"), ret.Storage) + return ret, ts, r +} diff --git a/config/fcos/v1_5_exp/translate_test.go b/config/fcos/v1_5_exp/translate_test.go index 9f8e5610..6b4584b3 100644 --- a/config/fcos/v1_5_exp/translate_test.go +++ b/config/fcos/v1_5_exp/translate_test.go @@ -1417,3 +1417,59 @@ func TestTranslateBootDevice(t *testing.T) { assert.NoError(t, translations.DebugVerifyCoverage(actual), "#%d: incomplete TranslationSet coverage", i) } } + +// TestTranslateExtensions tests translating the Butane config extensions section. +func TestTranslateExtensions(t *testing.T) { + tests := []struct { + in Config + out types.Config + exceptions []translate.Translation + report report.Report + }{ + // empty config + { + Config{ + Extensions: []string{"strace", "zsh"}, + }, + types.Config{ + Ignition: types.Ignition{ + Version: "3.4.0-experimental", + }, + Storage: types.Storage{ + Files: []types.File{ + { + Node: types.Node{ + Path: "/etc/rpm-ostree/origin.d/extensions-e2ecf66.yaml", + }, + FileEmbedded1: types.FileEmbedded1{ + Contents: types.Resource{ + Source: util.StrToPtr("data:;base64,IyBHZW5lcmF0ZWQgYnkgQnV0YW5lCgpwYWNrYWdlczoKICAgIC0gc3RyYWNlCiAgICAtIHpzaAo="), + }, + Mode: util.IntToPtr(420), + }, + }, + }, + }, + }, + []translate.Translation{ + {path.New("yaml", "version"), path.New("json", "ignition", "version")}, + {path.New("yaml", "extensions"), path.New("json", "storage")}, + {path.New("yaml", "extensions"), path.New("json", "storage", "files")}, + {path.New("yaml", "extensions"), path.New("json", "storage", "files", 0)}, + {path.New("yaml", "extensions"), path.New("json", "storage", "files", 0, "path")}, + {path.New("yaml", "extensions"), path.New("json", "storage", "files", 0, "mode")}, + {path.New("yaml", "extensions"), path.New("json", "storage", "files", 0, "contents")}, + {path.New("yaml", "extensions"), path.New("json", "storage", "files", 0, "contents", "source")}, + }, + report.Report{}, + }, + } + + for i, test := range tests { + actual, translations, r := test.in.ToIgn3_4Unvalidated(common.TranslateOptions{}) + assert.Equal(t, test.out, actual, "#%d: translation mismatch", i) + assert.Equal(t, test.report, r, "#%d: report mimatch", i) + baseutil.VerifyTranslations(t, translations, test.exceptions, "#%d", i) + assert.NoError(t, translations.DebugVerifyCoverage(actual), "#%d: incomplete TranslationSet coverage", i) + } +} diff --git a/docs/config-fcos-v1_5-exp.md b/docs/config-fcos-v1_5-exp.md index 8338bb4e..240f13e3 100644 --- a/docs/config-fcos-v1_5-exp.md +++ b/docs/config-fcos-v1_5-exp.md @@ -203,6 +203,7 @@ The Fedora CoreOS configuration is a YAML document conforming to the following s * **_threshold_** (int): sets the minimum number of pieces required to decrypt the device. Default is 1. * **_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. +* **extensions** (list of strings): A list of packages to layer on top of the OS. [part-types]: http://en.wikipedia.org/wiki/GUID_Partition_Table#Partition_type_GUIDs [rfc2397]: https://tools.ietf.org/html/rfc2397