diff --git a/pkg/asset/ignition/bootstrap/bootstrap.go b/pkg/asset/ignition/bootstrap/bootstrap.go index 17603c27151..106dbe0502b 100644 --- a/pkg/asset/ignition/bootstrap/bootstrap.go +++ b/pkg/asset/ignition/bootstrap/bootstrap.go @@ -28,6 +28,7 @@ import ( "github.com/openshift/installer/pkg/asset/ignition" "github.com/openshift/installer/pkg/asset/ignition/bootstrap/baremetal" "github.com/openshift/installer/pkg/asset/ignition/bootstrap/vsphere" + mcign "github.com/openshift/installer/pkg/asset/ignition/machine" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/asset/kubeconfig" "github.com/openshift/installer/pkg/asset/machines" @@ -82,6 +83,8 @@ func (a *Bootstrap) Dependencies() []asset.Asset { &kubeconfig.AdminInternalClient{}, &kubeconfig.Kubelet{}, &kubeconfig.LoopbackClient{}, + &mcign.MasterIgnitionCustomizations{}, + &mcign.WorkerIgnitionCustomizations{}, &machines.Master{}, &machines.Worker{}, &manifests.Manifests{}, @@ -461,6 +464,8 @@ func (a *Bootstrap) addParentFiles(dependencies asset.Parents) { &manifests.Openshift{}, &machines.Master{}, &machines.Worker{}, + &mcign.MasterIgnitionCustomizations{}, + &mcign.WorkerIgnitionCustomizations{}, } { dependencies.Get(asset) diff --git a/pkg/asset/ignition/machine/master_ignition_customizations.go b/pkg/asset/ignition/machine/master_ignition_customizations.go new file mode 100644 index 00000000000..6f42fc612f5 --- /dev/null +++ b/pkg/asset/ignition/machine/master_ignition_customizations.go @@ -0,0 +1,84 @@ +package machine + +import ( + "path/filepath" + + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" + mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" +) + +var ( + masterMachineConfigFileName = filepath.Join(directory, "99_openshift-installer-ignition_master.yaml") +) + +// MasterIgnitionCustomizations is an asset that checks for any customizations a user might +// have made to the pointer ignition configs before creating the cluster. If customizations +// are made, then the updates are reconciled as a MachineConfig file +type MasterIgnitionCustomizations struct { + File *asset.File +} + +var _ asset.WritableAsset = (*MasterIgnitionCustomizations)(nil) + +// Dependencies returns the dependencies for MasterIgnitionCustomizations +func (a *MasterIgnitionCustomizations) Dependencies() []asset.Asset { + return []asset.Asset{ + &installconfig.InstallConfig{}, + &tls.RootCA{}, + &Master{}, + } +} + +// Generate queries for input from the user. +func (a *MasterIgnitionCustomizations) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + rootCA := &tls.RootCA{} + master := &Master{} + dependencies.Get(installConfig, rootCA, master) + + defaultPointerIgnition := pointerIgnitionConfig(installConfig.Config, rootCA.Cert(), "master") + savedPointerIgnition := master.Config + + if savedPointerIgnition != defaultPointerIgnition { + logrus.Infof("Master pointer ignition was modified. Saving contents to a machineconfig") + mc := &mcfgv1.MachineConfig{} + mc, err := generatePointerMachineConfig(*savedPointerIgnition, "master") + if err != nil { + return errors.Wrap(err, "failed to generate master installer machineconfig") + } + configData, err := yaml.Marshal(mc) + if err != nil { + return errors.Wrap(err, "failed to marshal master installer machineconfig") + } + a.File = &asset.File{ + Filename: masterMachineConfigFileName, + Data: configData, + } + } + + return nil +} + +// Name returns the human-friendly name of the asset. +func (a *MasterIgnitionCustomizations) Name() string { + return "Master Ignition Customization Check" +} + +// Files returns the files generated by the asset. +func (a *MasterIgnitionCustomizations) Files() []*asset.File { + if a.File != nil { + return []*asset.File{a.File} + } + return []*asset.File{} +} + +// Load does nothing, since we consume the ignition-configs +func (a *MasterIgnitionCustomizations) Load(f asset.FileFetcher) (found bool, err error) { + return false, nil +} diff --git a/pkg/asset/ignition/machine/master_ignition_customizations_test.go b/pkg/asset/ignition/machine/master_ignition_customizations_test.go new file mode 100644 index 00000000000..9bfca8f7469 --- /dev/null +++ b/pkg/asset/ignition/machine/master_ignition_customizations_test.go @@ -0,0 +1,50 @@ +package machine + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" + "github.com/openshift/installer/pkg/ipnet" + "github.com/openshift/installer/pkg/types" + "github.com/openshift/installer/pkg/types/aws" +) + +// TestMasterIgnitionCustomizationsGenerate tests generating the master ignition check asset. +func TestMasterIgnitionCustomizationsGenerate(t *testing.T) { + installConfig := &installconfig.InstallConfig{ + Config: &types.InstallConfig{ + Networking: &types.Networking{ + ServiceNetwork: []ipnet.IPNet{*ipnet.MustParseCIDR("10.0.1.0/24")}, + }, + Platform: types.Platform{ + AWS: &aws.Platform{ + Region: "us-east", + }, + }, + }, + } + + rootCA := &tls.RootCA{} + err := rootCA.Generate(nil) + assert.NoError(t, err, "unexpected error generating root CA") + + parents := asset.Parents{} + parents.Add(installConfig, rootCA) + + master := &Master{} + err = master.Generate(parents) + assert.NoError(t, err, "unexpected error generating master asset") + + parents.Add(master) + masterIgnCheck := &MasterIgnitionCustomizations{} + err = masterIgnCheck.Generate(parents) + assert.NoError(t, err, "unexpected error generating master ignition check asset") + + actualFiles := masterIgnCheck.Files() + assert.Equal(t, 1, len(actualFiles), "unexpected number of files in master state") + assert.Equal(t, masterMachineConfigFileName, actualFiles[0].Filename, "unexpected name for master ignition config") +} diff --git a/pkg/asset/ignition/machine/node.go b/pkg/asset/ignition/machine/node.go index 1ebda003e28..75b80e6accf 100644 --- a/pkg/asset/ignition/machine/node.go +++ b/pkg/asset/ignition/machine/node.go @@ -8,14 +8,19 @@ import ( ignutil "github.com/coreos/ignition/v2/config/util" igntypes "github.com/coreos/ignition/v2/config/v3_1/types" "github.com/vincent-petithory/dataurl" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/openshift/installer/pkg/asset/ignition" "github.com/openshift/installer/pkg/types" baremetaltypes "github.com/openshift/installer/pkg/types/baremetal" openstacktypes "github.com/openshift/installer/pkg/types/openstack" ovirttypes "github.com/openshift/installer/pkg/types/ovirt" vspheretypes "github.com/openshift/installer/pkg/types/vsphere" + mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" ) +const directory = "openshift" + // pointerIgnitionConfig generates a config which references the remote config // served by the machine config server. func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, role string) *igntypes.Config { @@ -61,3 +66,31 @@ func pointerIgnitionConfig(installConfig *types.InstallConfig, rootCA []byte, ro }, } } + +// generatePointerMachineConfig generates a machineconfig when a user customizes +// the pointer ignition file manually in an IPI deployment +func generatePointerMachineConfig(config igntypes.Config, role string) (*mcfgv1.MachineConfig, error) { + // Remove the merge section from the pointer config + config.Ignition.Config.Merge = nil + + rawExt, err := ignition.ConvertToRawExtension(config) + if err != nil { + return nil, err + } + + return &mcfgv1.MachineConfig{ + TypeMeta: metav1.TypeMeta{ + APIVersion: mcfgv1.SchemeGroupVersion.String(), + Kind: "MachineConfig", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("99-installer-ignition-%s", role), + Labels: map[string]string{ + "machineconfiguration.openshift.io/role": role, + }, + }, + Spec: mcfgv1.MachineConfigSpec{ + Config: rawExt, + }, + }, nil +} diff --git a/pkg/asset/ignition/machine/worker_ignition_customizations.go b/pkg/asset/ignition/machine/worker_ignition_customizations.go new file mode 100644 index 00000000000..f74e4f3fa1b --- /dev/null +++ b/pkg/asset/ignition/machine/worker_ignition_customizations.go @@ -0,0 +1,85 @@ +package machine + +import ( + "path/filepath" + + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" + mcfgv1 "github.com/openshift/machine-config-operator/pkg/apis/machineconfiguration.openshift.io/v1" +) + +var ( + workerMachineConfigFileName = filepath.Join(directory, "99_openshift-installer-ignition_worker.yaml") +) + +// WorkerIgnitionCustomizations is an asset that checks for any customizations a user might +// have made to the pointer ignition configs before creating the cluster. If customizations +// are made, then the updates are reconciled as a MachineConfig file +type WorkerIgnitionCustomizations struct { + File *asset.File +} + +var _ asset.WritableAsset = (*WorkerIgnitionCustomizations)(nil) + +// Dependencies returns the dependencies for WorkerIgnitionCustomizations +func (a *WorkerIgnitionCustomizations) Dependencies() []asset.Asset { + return []asset.Asset{ + &installconfig.InstallConfig{}, + &tls.RootCA{}, + &Worker{}, + } +} + +// Generate queries for input from the user. +func (a *WorkerIgnitionCustomizations) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + rootCA := &tls.RootCA{} + worker := &Worker{} + dependencies.Get(installConfig, rootCA, worker) + + defaultPointerIgnition := pointerIgnitionConfig(installConfig.Config, rootCA.Cert(), "worker") + savedPointerIgnition := worker.Config + + // Create a machineconfig if the ignition has been modified + if savedPointerIgnition != defaultPointerIgnition { + logrus.Infof("Worker pointer ignition was modified. Saving contents to a machineconfig") + mc := &mcfgv1.MachineConfig{} + mc, err := generatePointerMachineConfig(*savedPointerIgnition, "worker") + if err != nil { + return errors.Wrap(err, "failed to generate worker installer machineconfig") + } + configData, err := yaml.Marshal(mc) + if err != nil { + return errors.Wrap(err, "failed to marshal worker installer machineconfig") + } + a.File = &asset.File{ + Filename: workerMachineConfigFileName, + Data: configData, + } + } + + return nil +} + +// Name returns the human-friendly name of the asset. +func (a *WorkerIgnitionCustomizations) Name() string { + return "Worker Ignition Customization Check" +} + +// Files returns the files generated by the asset. +func (a *WorkerIgnitionCustomizations) Files() []*asset.File { + if a.File != nil { + return []*asset.File{a.File} + } + return []*asset.File{} +} + +// Load does nothing, since we consume the ignition-configs +func (a *WorkerIgnitionCustomizations) Load(f asset.FileFetcher) (found bool, err error) { + return false, nil +} diff --git a/pkg/asset/ignition/machine/worker_ignition_customizations_test.go b/pkg/asset/ignition/machine/worker_ignition_customizations_test.go new file mode 100644 index 00000000000..5ea7d08e596 --- /dev/null +++ b/pkg/asset/ignition/machine/worker_ignition_customizations_test.go @@ -0,0 +1,50 @@ +package machine + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/tls" + "github.com/openshift/installer/pkg/ipnet" + "github.com/openshift/installer/pkg/types" + "github.com/openshift/installer/pkg/types/aws" +) + +// TestWorkerIgnitionCustomizationsGenerate tests generating the worker ignition check asset. +func TestWorkerIgnitionCustomizationsGenerate(t *testing.T) { + installConfig := &installconfig.InstallConfig{ + Config: &types.InstallConfig{ + Networking: &types.Networking{ + ServiceNetwork: []ipnet.IPNet{*ipnet.MustParseCIDR("10.0.1.0/24")}, + }, + Platform: types.Platform{ + AWS: &aws.Platform{ + Region: "us-east", + }, + }, + }, + } + + rootCA := &tls.RootCA{} + err := rootCA.Generate(nil) + assert.NoError(t, err, "unexpected error generating root CA") + + parents := asset.Parents{} + parents.Add(installConfig, rootCA) + + worker := &Worker{} + err = worker.Generate(parents) + assert.NoError(t, err, "unexpected error generating worker asset") + + parents.Add(worker) + workerIgnCheck := &WorkerIgnitionCustomizations{} + err = workerIgnCheck.Generate(parents) + assert.NoError(t, err, "unexpected error generating worker ignition check asset") + + actualFiles := workerIgnCheck.Files() + assert.Equal(t, 1, len(actualFiles), "unexpected number of files in worker state") + assert.Equal(t, workerMachineConfigFileName, actualFiles[0].Filename, "unexpected name for worker ignition config") +} diff --git a/pkg/asset/targets/targets.go b/pkg/asset/targets/targets.go index 1de83039d0b..9eb5af6d21d 100644 --- a/pkg/asset/targets/targets.go +++ b/pkg/asset/targets/targets.go @@ -62,6 +62,8 @@ var ( // Cluster are the cluster targeted assets. Cluster = []asset.WritableAsset{ &cluster.Metadata{}, + &machine.MasterIgnitionCustomizations{}, + &machine.WorkerIgnitionCustomizations{}, &cluster.TerraformVariables{}, &kubeconfig.AdminClient{}, &password.KubeadminPassword{},