diff --git a/cmd/openshift-install/create.go b/cmd/openshift-install/create.go index ee19d48bea2..9fd2c93bef3 100644 --- a/cmd/openshift-install/create.go +++ b/cmd/openshift-install/create.go @@ -139,7 +139,7 @@ func newCreateCmd() *cobra.Command { } func runTargetCmd(targets ...asset.WritableAsset) func(cmd *cobra.Command, args []string) { - runner := func(directory string) error { + runner := func(fcos bool, directory string) error { assetStore, err := assetstore.NewStore(directory) if err != nil { return errors.Wrap(err, "failed to create asset store") @@ -151,7 +151,7 @@ func runTargetCmd(targets ...asset.WritableAsset) func(cmd *cobra.Command, args err = errors.Wrapf(err, "failed to fetch %s", a.Name()) } - if err2 := asset.PersistToFile(a, directory); err2 != nil { + if err2 := asset.PersistToFile(a, directory, fcos); err2 != nil { err2 = errors.Wrapf(err2, "failed to write asset (%s) to disk", a.Name()) if err != nil { logrus.Error(err2) @@ -171,7 +171,7 @@ func runTargetCmd(targets ...asset.WritableAsset) func(cmd *cobra.Command, args cleanup := setupFileHook(rootOpts.dir) defer cleanup() - err := runner(rootOpts.dir) + err := runner(rootOpts.fcos, rootOpts.dir) if err != nil { logrus.Fatal(err) } diff --git a/cmd/openshift-install/main.go b/cmd/openshift-install/main.go index 778cb14985e..b0cf8eddc46 100644 --- a/cmd/openshift-install/main.go +++ b/cmd/openshift-install/main.go @@ -19,6 +19,7 @@ var ( rootOpts struct { dir string logLevel string + fcos bool } ) @@ -71,6 +72,7 @@ func newRootCmd() *cobra.Command { } cmd.PersistentFlags().StringVar(&rootOpts.dir, "dir", ".", "assets directory") cmd.PersistentFlags().StringVar(&rootOpts.logLevel, "log-level", "info", "log level (e.g. \"debug | info | warn | error\")") + cmd.PersistentFlags().BoolVar(&rootOpts.fcos, "fcos", false, "set to true to generate ignition v3 configs") return cmd } diff --git a/pkg/asset/asset.go b/pkg/asset/asset.go index 4c248e79860..99869f6b6e6 100644 --- a/pkg/asset/asset.go +++ b/pkg/asset/asset.go @@ -1,11 +1,13 @@ package asset import ( + "encoding/json" "io" "io/ioutil" "os" "path/filepath" "sort" + "strings" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -46,12 +48,20 @@ type File struct { // PersistToFile writes all of the files of the specified asset into the specified // directory. -func PersistToFile(asset WritableAsset, directory string) error { +func PersistToFile(asset WritableAsset, directory string, fcos bool) error { for _, f := range asset.Files() { path := filepath.Join(directory, f.Filename) if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { return errors.Wrap(err, "failed to create dir") } + if fcos && strings.HasSuffix(f.Filename, ".ign") { + // Run transpiler here + spec3data, err := convertSpec2ToSpec3(f.Data) + if err != nil { + return errors.Wrap(err, "failed to convert spec2 to spec3") + } + f.Data = spec3data + } if err := ioutil.WriteFile(path, f.Data, 0644); err != nil { return errors.Wrap(err, "failed to write file") } @@ -59,6 +69,61 @@ func PersistToFile(asset WritableAsset, directory string) error { return nil } +func convertSpec2ToSpec3(spec2data []byte) ([]byte, error) { + // Unmarshal + jsonMap := make(map[string]interface{}) + err := json.Unmarshal(spec2data, &jsonMap) + if err != nil { + return nil, errors.Wrap(err, "failed to Marshal Ignition config") + } + + // Replace ignition.version + ign := jsonMap["ignition"].(map[string]interface{}) + ign["version"] = "3.0.0" + + // ignition.config.append -> ignition.config.merge + config := ign["config"].(map[string]interface{}) + if val, ok := config["append"]; ok { + config["merge"] = val + delete(config, "append") + } + ign["config"] = config + jsonMap["ignition"] = ign + + // Delete networkd section + if _, ok := jsonMap["networkd"]; ok { + delete(jsonMap, "networkd") + } + + // Remove filesystem in storage.files + if sval, ok := jsonMap["storage"]; ok { + storage := sval.(map[string]interface{}) + + if fval, ok := storage["files"]; ok { + files := fval.([]interface{}) + + updatedFiles := make([]interface{}, 0) + + for i := range files { + file := files[i].(map[string]interface{}) + if _, ok := file["filesystem"]; ok { + delete(file, "filesystem") + } + updatedFiles = append(updatedFiles, file) + } + storage["files"] = updatedFiles + } + jsonMap["storage"] = storage + } + + // Convert to bytes + spec3data, err := json.Marshal(jsonMap) + if err != nil { + return nil, errors.Wrap(err, "failed to Marshal Ignition config") + } + return spec3data, nil +} + // DeleteAssetFromDisk removes all the files for asset from disk. // this is function is not safe for calling concurrently on the same directory. func DeleteAssetFromDisk(asset WritableAsset, directory string) error { diff --git a/pkg/asset/asset_test.go b/pkg/asset/asset_test.go index 18cbf499c4e..b446479aa39 100644 --- a/pkg/asset/asset_test.go +++ b/pkg/asset/asset_test.go @@ -41,22 +41,47 @@ func TestPersistToFile(t *testing.T) { cases := []struct { name string filenames []string + fcos bool }{ { name: "no files", filenames: []string{}, + fcos: false, }, { name: "single file", filenames: []string{"file1"}, + fcos: false, }, { name: "multiple files", filenames: []string{"file1", "file2"}, + fcos: false, }, { name: "new directory", filenames: []string{"dir1/file1"}, + fcos: false, + }, + { + name: "no files spec3", + filenames: []string{}, + fcos: true, + }, + { + name: "single file spec3", + filenames: []string{"file1"}, + fcos: true, + }, + { + name: "multiple files spec3", + filenames: []string{"file1", "file2"}, + fcos: true, + }, + { + name: "new directory", + filenames: []string{"dir1/file1"}, + fcos: true, }, } for _, tc := range cases { @@ -79,7 +104,7 @@ func TestPersistToFile(t *testing.T) { } expectedFiles[filepath.Join(dir, filename)] = data } - err = PersistToFile(asset, dir) + err = PersistToFile(asset, dir, tc.fcos) assert.NoError(t, err, "unexpected error persisting state to file") verifyFilesCreated(t, dir, expectedFiles) }) diff --git a/pkg/asset/store/assetcreate_test.go b/pkg/asset/store/assetcreate_test.go index 2f6e2a31d86..bd553713569 100644 --- a/pkg/asset/store/assetcreate_test.go +++ b/pkg/asset/store/assetcreate_test.go @@ -36,22 +36,27 @@ func TestCreatedAssetsAreNotDirty(t *testing.T) { cases := []struct { name string targets []asset.WritableAsset + fcos bool }{ { name: "install config", targets: targets.InstallConfig, + fcos: false, }, { name: "manifest templates", targets: targets.ManifestTemplates, + fcos: false, }, { name: "manifests", targets: targets.Manifests, + fcos: false, }, { name: "ignition configs", targets: targets.IgnitionConfigs, + fcos: false, }, } for _, tc := range cases { @@ -76,7 +81,7 @@ func TestCreatedAssetsAreNotDirty(t *testing.T) { t.Fatalf("failed to fetch %q: %v", a.Name(), err) } - if err := asset.PersistToFile(a, tempDir); err != nil { + if err := asset.PersistToFile(a, tempDir, tc.fcos); err != nil { t.Fatalf("failed to write asset %q to disk: %v", a.Name(), err) } } diff --git a/pkg/asset/store/store_test.go b/pkg/asset/store/store_test.go index 2f895d50247..7bed990ca9c 100644 --- a/pkg/asset/store/store_test.go +++ b/pkg/asset/store/store_test.go @@ -409,7 +409,7 @@ func TestStoreFetchIdempotency(t *testing.T) { if !assert.NoError(t, err, "(loop %d) unexpected error fetching asset %q", a.Name()) { t.Fatal() } - err = asset.PersistToFile(a, tempDir) + err = asset.PersistToFile(a, tempDir, false) if !assert.NoError(t, err, "(loop %d) unexpected error persisting asset %q", a.Name()) { t.Fatal() }