diff --git a/pkg/asset/ignition/bootstrap/common.go b/pkg/asset/ignition/bootstrap/common.go index 70ad2a42d1..80fe61c03e 100644 --- a/pkg/asset/ignition/bootstrap/common.go +++ b/pkg/asset/ignition/bootstrap/common.go @@ -22,6 +22,7 @@ import ( "github.com/sirupsen/logrus" "github.com/vincent-petithory/dataurl" utilsnet "k8s.io/utils/net" + "k8s.io/utils/ptr" configv1 "github.com/openshift/api/config/v1" "github.com/openshift/installer/data" @@ -39,6 +40,7 @@ import ( "github.com/openshift/installer/pkg/asset/tls" "github.com/openshift/installer/pkg/types" baremetaltypes "github.com/openshift/installer/pkg/types/baremetal" + nutanixtypes "github.com/openshift/installer/pkg/types/nutanix" vspheretypes "github.com/openshift/installer/pkg/types/vsphere" ) @@ -111,6 +113,7 @@ func (a *Common) Dependencies() []asset.Asset { &baremetal.IronicCreds{}, &CVOIgnore{}, &installconfig.InstallConfig{}, + &installconfig.ClusterID{}, &kubeconfig.AdminInternalClient{}, &kubeconfig.Kubelet{}, &kubeconfig.LoopbackClient{}, @@ -167,7 +170,8 @@ func (a *Common) Dependencies() []asset.Asset { func (a *Common) generateConfig(dependencies asset.Parents, templateData *bootstrapTemplateData) error { installConfig := &installconfig.InstallConfig{} bootstrapSSHKeyPair := &tls.BootstrapSSHKeyPair{} - dependencies.Get(installConfig, bootstrapSSHKeyPair) + clusterID := &installconfig.ClusterID{} + dependencies.Get(installConfig, bootstrapSSHKeyPair, clusterID) a.Config = &igntypes.Config{ Ignition: igntypes.Ignition{ @@ -218,6 +222,24 @@ func (a *Common) generateConfig(dependencies asset.Parents, templateData *bootst }}, ) + if platform == nutanixtypes.Name { + // Inserts the file "/etc/hostname" with the bootstrap machine name to the bootstrap ignition data + hostname := fmt.Sprintf("%s-bootstrap", clusterID.InfraID) + hostnameFile := igntypes.File{ + Node: igntypes.Node{ + Path: "/etc/hostname", + Overwrite: ptr.To(true), + }, + FileEmbedded1: igntypes.FileEmbedded1{ + Mode: ptr.To(420), + Contents: igntypes.Resource{ + Source: ptr.To(dataurl.EncodeBytes([]byte(hostname))), + }, + }, + } + a.Config.Storage.Files = append(a.Config.Storage.Files, hostnameFile) + } + return nil } diff --git a/pkg/asset/machines/nutanix/capimachines.go b/pkg/asset/machines/nutanix/capimachines.go index 48d0ba578c..6876f033c8 100644 --- a/pkg/asset/machines/nutanix/capimachines.go +++ b/pkg/asset/machines/nutanix/capimachines.go @@ -61,7 +61,7 @@ func GenerateMachines(clusterID string, config *types.InstallConfig, pool *types Spec: capv1.MachineSpec{ ClusterName: clusterID, Bootstrap: capv1.Bootstrap{ - DataSecretName: ptr.To(fmt.Sprintf("%s-%s", clusterID, role)), + DataSecretName: ptr.To(ntxMachine.Name), }, InfrastructureRef: v1.ObjectReference{ APIVersion: "infrastructure.cluster.x-k8s.io/v1beta1", diff --git a/pkg/infrastructure/azure/azure.go b/pkg/infrastructure/azure/azure.go index 676946d0ce..4a072d7a9d 100644 --- a/pkg/infrastructure/azure/azure.go +++ b/pkg/infrastructure/azure/azure.go @@ -17,6 +17,7 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage" "github.com/coreos/stream-metadata-go/arch" "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" "k8s.io/utils/ptr" capz "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -512,7 +513,7 @@ func randomString(length int) string { // Ignition provisions the Azure container that holds the bootstrap ignition // file. -func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([]byte, error) { +func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([]*corev1.Secret, error) { session, err := in.InstallConfig.Azure.Session() if err != nil { return nil, fmt.Errorf("failed to get session: %w", err) @@ -557,5 +558,10 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] return nil, fmt.Errorf("failed to create ignition shim: %w", err) } - return ignShim, nil + ignSecrets := []*corev1.Secret{ + clusterapi.IgnitionSecret(ignShim, in.InfraID, "bootstrap"), + clusterapi.IgnitionSecret(in.MasterIgnData, in.InfraID, "master"), + } + + return ignSecrets, nil } diff --git a/pkg/infrastructure/clusterapi/clusterapi.go b/pkg/infrastructure/clusterapi/clusterapi.go index c20a4eea82..a889a6ba79 100644 --- a/pkg/infrastructure/clusterapi/clusterapi.go +++ b/pkg/infrastructure/clusterapi/clusterapi.go @@ -249,10 +249,12 @@ func (i *InfraProvider) Provision(ctx context.Context, dir string, parents asset logrus.Debugf("No infrastructure ready requirements for the %s provider", i.impl.Name()) } + masterIgnData := masterIgnAsset.Files()[0].Data bootstrapIgnData, err := injectInstallInfo(bootstrapIgnAsset.Files()[0].Data) if err != nil { return fileList, fmt.Errorf("unable to inject installation info: %w", err) } + ignitionSecrets := []*corev1.Secret{} // The cloud-platform may need to override the default // bootstrap ignition behavior. @@ -260,22 +262,27 @@ func (i *InfraProvider) Provision(ctx context.Context, dir string, parents asset ignInput := IgnitionInput{ Client: cl, BootstrapIgnData: bootstrapIgnData, + MasterIgnData: masterIgnData, InfraID: clusterID.InfraID, InstallConfig: installConfig, TFVarsAsset: tfvarsAsset, } timer.StartTimer(ignitionStage) - if bootstrapIgnData, err = p.Ignition(ctx, ignInput); err != nil { + if ignitionSecrets, err = p.Ignition(ctx, ignInput); err != nil { return fileList, fmt.Errorf("failed preparing ignition data: %w", err) } timer.StopTimer(ignitionStage) } else { - logrus.Debugf("No Ignition requirements for the %s provider", i.impl.Name()) + logrus.Debugf("Using default ignition for the %s provider", i.impl.Name()) + bootstrapIgnSecret := IgnitionSecret(bootstrapIgnData, clusterID.InfraID, "bootstrap") + masterIgnSecret := IgnitionSecret(masterIgnData, clusterID.InfraID, "master") + ignitionSecrets = append(ignitionSecrets, bootstrapIgnSecret, masterIgnSecret) + } + + for _, secret := range ignitionSecrets { + machineManifests = append(machineManifests, secret) } - bootstrapIgnSecret := IgnitionSecret(bootstrapIgnData, clusterID.InfraID, "bootstrap") - masterIgnSecret := IgnitionSecret(masterIgnAsset.Files()[0].Data, clusterID.InfraID, "master") - machineManifests = append(machineManifests, bootstrapIgnSecret, masterIgnSecret) // Create the machine manifests. timer.StartTimer(machineStage) diff --git a/pkg/infrastructure/clusterapi/types.go b/pkg/infrastructure/clusterapi/types.go index f1ec19bb8c..742bc966a5 100644 --- a/pkg/infrastructure/clusterapi/types.go +++ b/pkg/infrastructure/clusterapi/types.go @@ -4,6 +4,7 @@ import ( "context" "time" + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/openshift/installer/pkg/asset/cluster/tfvars" @@ -46,18 +47,20 @@ type PreProvisionInput struct { WorkersAsset *machines.Worker } -// IgnitionProvider handles preconditions for bootstrap ignition and -// generates ignition data for the CAPI bootstrap ignition secret. +// IgnitionProvider handles preconditions for bootstrap ignition, +// such as pushing to cloud storage. Returns bootstrap and master +// ignition secrets. // // WARNING! Low-level primitive. Use only if absolutely necessary. type IgnitionProvider interface { - Ignition(ctx context.Context, in IgnitionInput) ([]byte, error) + Ignition(ctx context.Context, in IgnitionInput) ([]*corev1.Secret, error) } // IgnitionInput collects the args passed to the IgnitionProvider call. type IgnitionInput struct { Client client.Client BootstrapIgnData []byte + MasterIgnData []byte InfraID string InstallConfig *installconfig.InstallConfig TFVarsAsset *tfvars.TerraformVariables diff --git a/pkg/infrastructure/gcp/clusterapi/clusterapi.go b/pkg/infrastructure/gcp/clusterapi/clusterapi.go index 432bcd3937..379fdafb3e 100644 --- a/pkg/infrastructure/gcp/clusterapi/clusterapi.go +++ b/pkg/infrastructure/gcp/clusterapi/clusterapi.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" capg "sigs.k8s.io/cluster-api-provider-gcp/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -76,7 +77,7 @@ func (p Provider) PreProvision(ctx context.Context, in clusterapi.PreProvisionIn // populate the metadata field of the bootstrap instance as the data can be too large. Instead, the data is // added to a bucket. A signed url is generated to point to the bucket and the ignition data will be // updated to point to the url. This is also allows for bootstrap data to be edited after its initial creation. -func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([]byte, error) { +func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([]*corev1.Secret, error) { // Create the bucket and presigned url. The url is generated using a known/expected name so that the // url can be retrieved from the api by this name. bucketName := gcp.GetBootstrapStorageName(in.InfraID) @@ -104,6 +105,7 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] return nil, fmt.Errorf("ignition failed to fill bucket: %w", err) } + var ignShim string for _, file := range in.TFVarsAsset.Files() { if file.Filename == tfvars.TfPlatformVarsFileName { var found bool @@ -113,16 +115,19 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] return nil, fmt.Errorf("failed to unmarshal %s to json: %w", tfvars.TfPlatformVarsFileName, err) } - ignShim, found := tfvarsData["gcp_ignition_shim"].(string) + ignShim, found = tfvarsData["gcp_ignition_shim"].(string) if !found { return nil, fmt.Errorf("failed to find ignition shim") } - - return []byte(ignShim), nil } } - return nil, fmt.Errorf("failed to complete ignition process") + ignSecrets := []*corev1.Secret{ + clusterapi.IgnitionSecret([]byte(ignShim), in.InfraID, "bootstrap"), + clusterapi.IgnitionSecret(in.MasterIgnData, in.InfraID, "master"), + } + + return ignSecrets, nil } // InfraReady is called once cluster.Status.InfrastructureReady diff --git a/pkg/infrastructure/nutanix/clusterapi/clusterapi.go b/pkg/infrastructure/nutanix/clusterapi/clusterapi.go index a10c2debe5..a8537d5c8b 100644 --- a/pkg/infrastructure/nutanix/clusterapi/clusterapi.go +++ b/pkg/infrastructure/nutanix/clusterapi/clusterapi.go @@ -8,6 +8,7 @@ import ( nutanixclientv3 "github.com/nutanix-cloud-native/prism-go-client/v3" "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/utils/ptr" @@ -120,10 +121,10 @@ func (p Provider) PreProvision(ctx context.Context, in infracapi.PreProvisionInp return nil } -// Ignition handles preconditions for bootstrap ignition and -// generates ignition data for the CAPI bootstrap ignition secret. +// Ignition handles preconditions for bootstrap and master ignition and +// generates the CAPI ignition data secrets. // Load the ignition iso image to prism_central. -func (p Provider) Ignition(ctx context.Context, in infracapi.IgnitionInput) ([]byte, error) { +func (p Provider) Ignition(ctx context.Context, in infracapi.IgnitionInput) ([]*corev1.Secret, error) { ic := in.InstallConfig.Config nutanixCl, err := nutanixtypes.CreateNutanixClientFromPlatform(ic.Platform.Nutanix) if err != nil { @@ -207,11 +208,25 @@ func (p Provider) Ignition(ctx context.Context, in infracapi.IgnitionInput) ([]b err = fmt.Errorf("failed to create/upload the bootstrap image object %s in PC: %w", imgName, err) } - return in.BootstrapIgnData, err + return nil, err } logrus.Infof("Successfully created the bootstrap image object %s and uploaded its image data", imgName) - return in.BootstrapIgnData, nil + ignSecrets := []*corev1.Secret{ + infracapi.IgnitionSecret(in.BootstrapIgnData, in.InfraID, "bootstrap"), + } + + for i := 0; i < int(*in.InstallConfig.Config.ControlPlane.Replicas); i++ { + // Inserts the file "/etc/hostname" with the master machine name to the ignition data + hostname := fmt.Sprintf("%s-master-%v", in.InfraID, i) + masterIgnData, err := nutanixtypes.InsertHostnameIgnition(in.MasterIgnData, hostname) + if err != nil { + return nil, fmt.Errorf("failed to insert the file '/etc/hostname' to the ignition data for machine %q: %w", hostname, err) + } + ignSecrets = append(ignSecrets, infracapi.IgnitionSecret(masterIgnData, in.InfraID, fmt.Sprintf("master-%v", i))) + } + + return ignSecrets, nil } // createImage creates the image object in PC, with the provided request input. diff --git a/pkg/infrastructure/openstack/clusterapi/clusterapi.go b/pkg/infrastructure/openstack/clusterapi/clusterapi.go index 01927c1bd2..457d16c267 100644 --- a/pkg/infrastructure/openstack/clusterapi/clusterapi.go +++ b/pkg/infrastructure/openstack/clusterapi/clusterapi.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" capo "sigs.k8s.io/cluster-api-provider-openstack/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/yaml" @@ -131,7 +132,7 @@ func (p Provider) InfraReady(ctx context.Context, in clusterapi.InfraReadyInput) var _ clusterapi.IgnitionProvider = Provider{} // Ignition uploads the bootstrap machine's Ignition file to OpenStack. -func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([]byte, error) { +func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([]*corev1.Secret, error) { logrus.Debugf("Uploading the bootstrap machine's Ignition file to OpenStack") var ( bootstrapIgnData = in.BootstrapIgnData @@ -139,7 +140,16 @@ func (p Provider) Ignition(ctx context.Context, in clusterapi.IgnitionInput) ([] installConfig = in.InstallConfig ) - return preprovision.UploadIgnitionAndBuildShim(ctx, installConfig.Config.Platform.OpenStack.Cloud, infraID, installConfig.Config.Proxy, bootstrapIgnData) + ignShim, err := preprovision.UploadIgnitionAndBuildShim(ctx, installConfig.Config.Platform.OpenStack.Cloud, infraID, installConfig.Config.Proxy, bootstrapIgnData) + if err != nil { + return nil, fmt.Errorf("failed to upload and build ignition shim: %w", err) + } + + ignSecrets := []*corev1.Secret{ + clusterapi.IgnitionSecret(ignShim, in.InfraID, "bootstrap"), + clusterapi.IgnitionSecret(in.MasterIgnData, in.InfraID, "master"), + } + return ignSecrets, nil } var _ clusterapi.PostProvider = Provider{} diff --git a/pkg/types/nutanix/helpers.go b/pkg/types/nutanix/helpers.go index d771946414..79f6f6ac24 100644 --- a/pkg/types/nutanix/helpers.go +++ b/pkg/types/nutanix/helpers.go @@ -9,11 +9,14 @@ import ( "strings" "time" + igntypes "github.com/coreos/ignition/v2/config/v3_2/types" "github.com/google/uuid" "github.com/kdomanski/iso9660" "github.com/nutanix-cloud-native/prism-go-client/utils" nutanixclientv3 "github.com/nutanix-cloud-native/prism-go-client/v3" "github.com/pkg/errors" + "github.com/vincent-petithory/dataurl" + "k8s.io/utils/ptr" ) const ( @@ -194,3 +197,36 @@ func CategoryKey(infraID string) string { categoryKey := fmt.Sprintf("%s%s", categoryKeyPrefix, infraID) return categoryKey } + +// InsertHostnameIgnition inserts the file "/etc/hostname" with the given hostname to the provided Ignition config data. +func InsertHostnameIgnition(ignData []byte, hostname string) ([]byte, error) { + ignConfig := &igntypes.Config{} + if err := json.Unmarshal(ignData, &ignConfig); err != nil { + return nil, fmt.Errorf("failed to unmarshal Ignition config: %w", err) + } + + hostnameFile := igntypes.File{ + Node: igntypes.Node{ + Path: "/etc/hostname", + Overwrite: ptr.To(true), + }, + FileEmbedded1: igntypes.FileEmbedded1{ + Mode: ptr.To(420), + Contents: igntypes.Resource{ + Source: ptr.To(dataurl.EncodeBytes([]byte(hostname))), + }, + }, + } + + if ignConfig.Storage.Files == nil { + ignConfig.Storage.Files = make([]igntypes.File, 0) + } + ignConfig.Storage.Files = append(ignConfig.Storage.Files, hostnameFile) + + ign, err := json.Marshal(ignConfig) + if err != nil { + return nil, fmt.Errorf("failed to marshal ignition data: %w", err) + } + + return ign, nil +}