github.com/openshift/installer@v1.4.17/pkg/asset/cluster/tfvars/tfvars.go (about)

     1  package tfvars
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"math/rand"
     8  	"os"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/IBM/vpc-go-sdk/vpcv1"
    14  	igntypes "github.com/coreos/ignition/v2/config/v3_2/types"
    15  	"github.com/pkg/errors"
    16  	"github.com/sirupsen/logrus"
    17  	"k8s.io/utils/ptr"
    18  	"sigs.k8s.io/yaml"
    19  
    20  	configv1 "github.com/openshift/api/config/v1"
    21  	machinev1 "github.com/openshift/api/machine/v1"
    22  	machinev1beta1 "github.com/openshift/api/machine/v1beta1"
    23  	ovirtprovider "github.com/openshift/cluster-api-provider-ovirt/pkg/apis/ovirtprovider/v1beta1"
    24  	"github.com/openshift/installer/pkg/asset"
    25  	"github.com/openshift/installer/pkg/asset/ignition"
    26  	"github.com/openshift/installer/pkg/asset/ignition/bootstrap"
    27  	baremetalbootstrap "github.com/openshift/installer/pkg/asset/ignition/bootstrap/baremetal"
    28  	gcpbootstrap "github.com/openshift/installer/pkg/asset/ignition/bootstrap/gcp"
    29  	"github.com/openshift/installer/pkg/asset/ignition/machine"
    30  	"github.com/openshift/installer/pkg/asset/installconfig"
    31  	awsconfig "github.com/openshift/installer/pkg/asset/installconfig/aws"
    32  	aztypes "github.com/openshift/installer/pkg/asset/installconfig/azure"
    33  	gcpconfig "github.com/openshift/installer/pkg/asset/installconfig/gcp"
    34  	ibmcloudconfig "github.com/openshift/installer/pkg/asset/installconfig/ibmcloud"
    35  	ovirtconfig "github.com/openshift/installer/pkg/asset/installconfig/ovirt"
    36  	powervsconfig "github.com/openshift/installer/pkg/asset/installconfig/powervs"
    37  	"github.com/openshift/installer/pkg/asset/machines"
    38  	"github.com/openshift/installer/pkg/asset/manifests"
    39  	"github.com/openshift/installer/pkg/asset/openshiftinstall"
    40  	"github.com/openshift/installer/pkg/asset/rhcos"
    41  	"github.com/openshift/installer/pkg/tfvars"
    42  	awstfvars "github.com/openshift/installer/pkg/tfvars/aws"
    43  	azuretfvars "github.com/openshift/installer/pkg/tfvars/azure"
    44  	baremetaltfvars "github.com/openshift/installer/pkg/tfvars/baremetal"
    45  	gcptfvars "github.com/openshift/installer/pkg/tfvars/gcp"
    46  	ibmcloudtfvars "github.com/openshift/installer/pkg/tfvars/ibmcloud"
    47  	nutanixtfvars "github.com/openshift/installer/pkg/tfvars/nutanix"
    48  	openstacktfvars "github.com/openshift/installer/pkg/tfvars/openstack"
    49  	ovirttfvars "github.com/openshift/installer/pkg/tfvars/ovirt"
    50  	powervstfvars "github.com/openshift/installer/pkg/tfvars/powervs"
    51  	"github.com/openshift/installer/pkg/types"
    52  	"github.com/openshift/installer/pkg/types/aws"
    53  	"github.com/openshift/installer/pkg/types/azure"
    54  	"github.com/openshift/installer/pkg/types/baremetal"
    55  	"github.com/openshift/installer/pkg/types/external"
    56  	"github.com/openshift/installer/pkg/types/gcp"
    57  	"github.com/openshift/installer/pkg/types/ibmcloud"
    58  	"github.com/openshift/installer/pkg/types/none"
    59  	"github.com/openshift/installer/pkg/types/nutanix"
    60  	"github.com/openshift/installer/pkg/types/openstack"
    61  	"github.com/openshift/installer/pkg/types/ovirt"
    62  	"github.com/openshift/installer/pkg/types/powervs"
    63  	"github.com/openshift/installer/pkg/types/vsphere"
    64  	ibmcloudprovider "github.com/openshift/machine-api-provider-ibmcloud/pkg/apis/ibmcloudprovider/v1"
    65  )
    66  
    67  const (
    68  	// GCPFirewallPermission is the role/permission to create or skip the creation of
    69  	// firewall rules for GCP during an xpn installation.
    70  	GCPFirewallPermission = "compute.firewalls.create"
    71  
    72  	// TfVarsFileName is the filename for Terraform variables.
    73  	TfVarsFileName = "terraform.tfvars.json"
    74  
    75  	// TfPlatformVarsFileName is the name for platform-specific
    76  	// Terraform variable files.
    77  	//
    78  	// https://www.terraform.io/docs/configuration/variables.html#variable-files
    79  	TfPlatformVarsFileName = "terraform.platform.auto.tfvars.json"
    80  
    81  	tfvarsAssetName = "Cluster Infrastructure Variables"
    82  )
    83  
    84  // TerraformVariables depends on InstallConfig, Manifests,
    85  // and Ignition to generate the terrafor.tfvars.
    86  type TerraformVariables struct {
    87  	FileList []*asset.File
    88  }
    89  
    90  var _ asset.WritableAsset = (*TerraformVariables)(nil)
    91  
    92  // Name returns the human-friendly name of the asset.
    93  func (t *TerraformVariables) Name() string {
    94  	return tfvarsAssetName
    95  }
    96  
    97  // Dependencies returns the dependency of the TerraformVariable.
    98  func (t *TerraformVariables) Dependencies() []asset.Asset {
    99  	return []asset.Asset{
   100  		&installconfig.ClusterID{},
   101  		&installconfig.InstallConfig{},
   102  		new(rhcos.Image),
   103  		new(rhcos.Release),
   104  		new(rhcos.BootstrapImage),
   105  		&bootstrap.Bootstrap{},
   106  		&machine.Master{},
   107  		&machines.Master{},
   108  		&machines.Worker{},
   109  		&baremetalbootstrap.IronicCreds{},
   110  		&installconfig.PlatformProvisionCheck{},
   111  		&manifests.Manifests{},
   112  	}
   113  }
   114  
   115  // Generate generates the terraform.tfvars file.
   116  //
   117  //nolint:gocyclo // legacy, pre-linter cyclomatic complexity
   118  func (t *TerraformVariables) Generate(ctx context.Context, parents asset.Parents) error {
   119  	clusterID := &installconfig.ClusterID{}
   120  	installConfig := &installconfig.InstallConfig{}
   121  	bootstrapIgnAsset := &bootstrap.Bootstrap{}
   122  	masterIgnAsset := &machine.Master{}
   123  	mastersAsset := &machines.Master{}
   124  	workersAsset := &machines.Worker{}
   125  	manifestsAsset := &manifests.Manifests{}
   126  	rhcosImage := new(rhcos.Image)
   127  	rhcosRelease := new(rhcos.Release)
   128  	rhcosBootstrapImage := new(rhcos.BootstrapImage)
   129  	ironicCreds := &baremetalbootstrap.IronicCreds{}
   130  	parents.Get(clusterID, installConfig, bootstrapIgnAsset, masterIgnAsset, mastersAsset, workersAsset, manifestsAsset, rhcosImage, rhcosRelease, rhcosBootstrapImage, ironicCreds)
   131  
   132  	platform := installConfig.Config.Platform.Name()
   133  	switch platform {
   134  	case external.Name, none.Name:
   135  		return errors.Errorf("cannot create the cluster because %q is a UPI platform", platform)
   136  	}
   137  
   138  	masterIgn := string(masterIgnAsset.Files()[0].Data)
   139  	bootstrapIgn, err := injectInstallInfo(bootstrapIgnAsset.Files()[0].Data)
   140  	if err != nil {
   141  		return errors.Wrap(err, "unable to inject installation info")
   142  	}
   143  
   144  	var useIPv4, useIPv6 bool
   145  	for _, network := range installConfig.Config.Networking.ServiceNetwork {
   146  		if network.IP.To4() != nil {
   147  			useIPv4 = true
   148  		} else {
   149  			useIPv6 = true
   150  		}
   151  	}
   152  
   153  	machineV4CIDRs, machineV6CIDRs := []string{}, []string{}
   154  	for _, network := range installConfig.Config.Networking.MachineNetwork {
   155  		if network.CIDR.IPNet.IP.To4() != nil {
   156  			machineV4CIDRs = append(machineV4CIDRs, network.CIDR.IPNet.String())
   157  		} else {
   158  			machineV6CIDRs = append(machineV6CIDRs, network.CIDR.IPNet.String())
   159  		}
   160  	}
   161  
   162  	masterCount := len(mastersAsset.MachineFiles)
   163  	mastersSchedulable := false
   164  	for _, f := range manifestsAsset.Files() {
   165  		if f.Filename == manifests.SchedulerCfgFilename {
   166  			schedulerConfig := configv1.Scheduler{}
   167  			err = yaml.Unmarshal(f.Data, &schedulerConfig)
   168  			if err != nil {
   169  				return errors.Wrapf(err, "failed to unmarshall %s", manifests.SchedulerCfgFilename)
   170  			}
   171  			mastersSchedulable = schedulerConfig.Spec.MastersSchedulable
   172  			break
   173  		}
   174  	}
   175  
   176  	lengthBootstrapFile := int64(len(bootstrapIgn))
   177  	if installConfig.Config.Platform.Azure != nil && installConfig.Config.Platform.Azure.CustomerManagedKey != nil &&
   178  		installConfig.Config.Platform.Azure.CustomerManagedKey.UserAssignedIdentityKey != "" {
   179  		if lengthBootstrapFile%512 != 0 {
   180  			lengthBootstrapFile = (((lengthBootstrapFile / 512) + 1) * 512)
   181  		}
   182  	}
   183  
   184  	data, err := tfvars.TFVars(
   185  		clusterID.InfraID,
   186  		installConfig.Config.ClusterDomain(),
   187  		installConfig.Config.BaseDomain,
   188  		machineV4CIDRs,
   189  		machineV6CIDRs,
   190  		useIPv4,
   191  		useIPv6,
   192  		bootstrapIgn,
   193  		lengthBootstrapFile,
   194  		masterIgn,
   195  		masterCount,
   196  		mastersSchedulable,
   197  	)
   198  	if err != nil {
   199  		return errors.Wrap(err, "failed to get Terraform variables")
   200  	}
   201  	t.FileList = []*asset.File{
   202  		{
   203  			Filename: TfVarsFileName,
   204  			Data:     data,
   205  		},
   206  	}
   207  
   208  	if masterCount == 0 {
   209  		return errors.Errorf("master slice cannot be empty")
   210  	}
   211  
   212  	numWorkers := int64(0)
   213  	for _, worker := range installConfig.Config.Compute {
   214  		numWorkers += ptr.Deref(worker.Replicas, 0)
   215  	}
   216  
   217  	switch platform {
   218  	case aws.Name:
   219  		var vpc string
   220  		var privateSubnets []string
   221  		var publicSubnets []string
   222  
   223  		if len(installConfig.Config.Platform.AWS.Subnets) > 0 {
   224  			subnets, err := installConfig.AWS.PrivateSubnets(ctx)
   225  			if err != nil {
   226  				return err
   227  			}
   228  
   229  			for id := range subnets {
   230  				privateSubnets = append(privateSubnets, id)
   231  			}
   232  
   233  			subnets, err = installConfig.AWS.PublicSubnets(ctx)
   234  			if err != nil {
   235  				return err
   236  			}
   237  
   238  			for id := range subnets {
   239  				publicSubnets = append(publicSubnets, id)
   240  			}
   241  
   242  			vpc, err = installConfig.AWS.VPC(ctx)
   243  			if err != nil {
   244  				return err
   245  			}
   246  		}
   247  
   248  		sess, err := installConfig.AWS.Session(ctx)
   249  		if err != nil {
   250  			return err
   251  		}
   252  		object := "bootstrap.ign"
   253  		bucket := fmt.Sprintf("%s-bootstrap", clusterID.InfraID)
   254  		url, err := awsconfig.PresignedS3URL(sess, installConfig.Config.Platform.AWS.Region, bucket, object)
   255  		if err != nil {
   256  			return err
   257  		}
   258  		masters, err := mastersAsset.Machines()
   259  		if err != nil {
   260  			return err
   261  		}
   262  		masterConfigs := make([]*machinev1beta1.AWSMachineProviderConfig, len(masters))
   263  		for i, m := range masters {
   264  			masterConfigs[i] = m.Spec.ProviderSpec.Value.Object.(*machinev1beta1.AWSMachineProviderConfig) //nolint:errcheck // legacy, pre-linter
   265  		}
   266  		workers, err := workersAsset.MachineSets()
   267  		if err != nil {
   268  			return err
   269  		}
   270  
   271  		workerConfigs := make([]*machinev1beta1.AWSMachineProviderConfig, len(workers))
   272  		for i, m := range workers {
   273  			workerConfigs[i] = m.Spec.Template.Spec.ProviderSpec.Value.Object.(*machinev1beta1.AWSMachineProviderConfig) //nolint:errcheck // legacy, pre-linter
   274  		}
   275  		osImage := strings.SplitN(rhcosImage.ControlPlane, ",", 2)
   276  		osImageID := osImage[0]
   277  		osImageRegion := installConfig.Config.AWS.Region
   278  		if len(osImage) == 2 {
   279  			osImageRegion = osImage[1]
   280  		}
   281  
   282  		workerIAMRoleName := ""
   283  		if mp := installConfig.Config.WorkerMachinePool(); mp != nil {
   284  			awsMP := &aws.MachinePool{}
   285  			awsMP.Set(installConfig.Config.AWS.DefaultMachinePlatform)
   286  			awsMP.Set(mp.Platform.AWS)
   287  			workerIAMRoleName = awsMP.IAMRole
   288  		}
   289  
   290  		var securityGroups []string
   291  		if mp := installConfig.Config.AWS.DefaultMachinePlatform; mp != nil {
   292  			securityGroups = mp.AdditionalSecurityGroupIDs
   293  		}
   294  		masterIAMRoleName := ""
   295  		if mp := installConfig.Config.ControlPlane; mp != nil {
   296  			awsMP := &aws.MachinePool{}
   297  			awsMP.Set(installConfig.Config.AWS.DefaultMachinePlatform)
   298  			awsMP.Set(mp.Platform.AWS)
   299  			masterIAMRoleName = awsMP.IAMRole
   300  			if len(awsMP.AdditionalSecurityGroupIDs) > 0 {
   301  				securityGroups = awsMP.AdditionalSecurityGroupIDs
   302  			}
   303  		}
   304  
   305  		// AWS Zones is used to determine which route table the edge zone will be associated.
   306  		allZones, err := installConfig.AWS.AllZones(ctx)
   307  		if err != nil {
   308  			return err
   309  		}
   310  
   311  		data, err := awstfvars.TFVars(awstfvars.TFVarsSources{
   312  			VPC:                       vpc,
   313  			PrivateSubnets:            privateSubnets,
   314  			PublicSubnets:             publicSubnets,
   315  			AvailabilityZones:         allZones,
   316  			InternalZone:              installConfig.Config.AWS.HostedZone,
   317  			InternalZoneRole:          installConfig.Config.AWS.HostedZoneRole,
   318  			Services:                  installConfig.Config.AWS.ServiceEndpoints,
   319  			Publish:                   installConfig.Config.Publish,
   320  			MasterConfigs:             masterConfigs,
   321  			WorkerConfigs:             workerConfigs,
   322  			AMIID:                     osImageID,
   323  			AMIRegion:                 osImageRegion,
   324  			IgnitionBucket:            bucket,
   325  			IgnitionPresignedURL:      url,
   326  			AdditionalTrustBundle:     installConfig.Config.AdditionalTrustBundle,
   327  			MasterIAMRoleName:         masterIAMRoleName,
   328  			WorkerIAMRoleName:         workerIAMRoleName,
   329  			Architecture:              installConfig.Config.ControlPlane.Architecture,
   330  			Proxy:                     installConfig.Config.Proxy,
   331  			PreserveBootstrapIgnition: installConfig.Config.AWS.BestEffortDeleteIgnition,
   332  			MasterSecurityGroups:      securityGroups,
   333  			PublicIpv4Pool:            installConfig.Config.AWS.PublicIpv4Pool,
   334  		})
   335  		if err != nil {
   336  			return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
   337  		}
   338  		t.FileList = append(t.FileList, &asset.File{
   339  			Filename: TfPlatformVarsFileName,
   340  			Data:     data,
   341  		})
   342  	case azure.Name:
   343  		session, err := installConfig.Azure.Session()
   344  		if err != nil {
   345  			return err
   346  		}
   347  
   348  		auth := azuretfvars.Auth{
   349  			SubscriptionID:            session.Credentials.SubscriptionID,
   350  			ClientID:                  session.Credentials.ClientID,
   351  			ClientSecret:              session.Credentials.ClientSecret,
   352  			TenantID:                  session.Credentials.TenantID,
   353  			ClientCertificatePath:     session.Credentials.ClientCertificatePath,
   354  			ClientCertificatePassword: session.Credentials.ClientCertificatePassword,
   355  			UseMSI:                    session.AuthType == aztypes.ManagedIdentityAuth,
   356  		}
   357  		masters, err := mastersAsset.Machines()
   358  		if err != nil {
   359  			return err
   360  		}
   361  		masterConfigs := make([]*machinev1beta1.AzureMachineProviderSpec, len(masters))
   362  		for i, m := range masters {
   363  			masterConfigs[i] = m.Spec.ProviderSpec.Value.Object.(*machinev1beta1.AzureMachineProviderSpec) //nolint:errcheck // legacy, pre-linter
   364  		}
   365  		workers, err := workersAsset.MachineSets()
   366  		if err != nil {
   367  			return err
   368  		}
   369  		workerConfigs := make([]*machinev1beta1.AzureMachineProviderSpec, len(workers))
   370  		for i, w := range workers {
   371  			workerConfigs[i] = w.Spec.Template.Spec.ProviderSpec.Value.Object.(*machinev1beta1.AzureMachineProviderSpec) //nolint:errcheck // legacy, pre-linter
   372  		}
   373  		client := aztypes.NewClient(session)
   374  		hyperVGeneration, err := client.GetHyperVGenerationVersion(ctx, masterConfigs[0].VMSize, masterConfigs[0].Location, "")
   375  		if err != nil {
   376  			return err
   377  		}
   378  
   379  		preexistingnetwork := installConfig.Config.Azure.VirtualNetwork != ""
   380  
   381  		var bootstrapIgnStub, bootstrapIgnURLPlaceholder string
   382  		if installConfig.Azure.CloudName == azure.StackCloud {
   383  			// Due to the SAS created in Terraform to limit access to bootstrap ignition, we cannot know the URL in advance.
   384  			// Instead, we will pass a placeholder string in the ignition to be replaced in TF once the value is known.
   385  			bootstrapIgnURLPlaceholder = "BOOTSTRAP_IGNITION_URL_PLACEHOLDER"
   386  			shim, err := bootstrap.GenerateIgnitionShimWithCertBundleAndProxy(bootstrapIgnURLPlaceholder, installConfig.Config.AdditionalTrustBundle, installConfig.Config.Proxy)
   387  			if err != nil {
   388  				return errors.Wrap(err, "failed to create stub Ignition config for bootstrap")
   389  			}
   390  			bootstrapIgnStub = string(shim)
   391  		}
   392  
   393  		managedKeys := azure.CustomerManagedKey{}
   394  		if installConfig.Config.Azure.CustomerManagedKey != nil {
   395  			managedKeys.KeyVault = azure.KeyVault{
   396  				ResourceGroup: installConfig.Config.Azure.CustomerManagedKey.KeyVault.ResourceGroup,
   397  				Name:          installConfig.Config.Azure.CustomerManagedKey.KeyVault.Name,
   398  				KeyName:       installConfig.Config.Azure.CustomerManagedKey.KeyVault.KeyName,
   399  			}
   400  			managedKeys.UserAssignedIdentityKey = installConfig.Config.Azure.CustomerManagedKey.UserAssignedIdentityKey
   401  		}
   402  
   403  		lbPrivate := false
   404  		if installConfig.Config.OperatorPublishingStrategy != nil {
   405  			lbPrivate = installConfig.Config.OperatorPublishingStrategy.APIServer == "Internal"
   406  		}
   407  
   408  		data, err := azuretfvars.TFVars(
   409  			azuretfvars.TFVarsSources{
   410  				Auth:                            auth,
   411  				CloudName:                       installConfig.Config.Azure.CloudName,
   412  				ARMEndpoint:                     installConfig.Config.Azure.ARMEndpoint,
   413  				ResourceGroupName:               installConfig.Config.Azure.ResourceGroupName,
   414  				BaseDomainResourceGroupName:     installConfig.Config.Azure.BaseDomainResourceGroupName,
   415  				MasterConfigs:                   masterConfigs,
   416  				WorkerConfigs:                   workerConfigs,
   417  				ImageURL:                        rhcosImage.ControlPlane,
   418  				ImageRelease:                    rhcosRelease.GetAzureReleaseVersion(),
   419  				PreexistingNetwork:              preexistingnetwork,
   420  				Publish:                         installConfig.Config.Publish,
   421  				OutboundType:                    installConfig.Config.Azure.OutboundType,
   422  				BootstrapIgnStub:                bootstrapIgnStub,
   423  				BootstrapIgnitionURLPlaceholder: bootstrapIgnURLPlaceholder,
   424  				HyperVGeneration:                hyperVGeneration,
   425  				VMArchitecture:                  installConfig.Config.ControlPlane.Architecture,
   426  				InfrastructureName:              clusterID.InfraID,
   427  				KeyVault:                        managedKeys.KeyVault,
   428  				UserAssignedIdentityKey:         managedKeys.UserAssignedIdentityKey,
   429  				LBPrivate:                       lbPrivate,
   430  			},
   431  		)
   432  		if err != nil {
   433  			return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
   434  		}
   435  		t.FileList = append(t.FileList, &asset.File{
   436  			Filename: TfPlatformVarsFileName,
   437  			Data:     data,
   438  		})
   439  	case gcp.Name:
   440  		sess, err := gcpconfig.GetSession(ctx)
   441  		if err != nil {
   442  			return err
   443  		}
   444  
   445  		auth := gcptfvars.Auth{
   446  			ProjectID:        installConfig.Config.GCP.ProjectID,
   447  			NetworkProjectID: installConfig.Config.GCP.NetworkProjectID,
   448  			ServiceAccount:   string(sess.Credentials.JSON),
   449  		}
   450  
   451  		client, err := gcpconfig.NewClient(context.Background())
   452  		if err != nil {
   453  			return err
   454  		}
   455  
   456  		// In the case of a shared vpn, the firewall rules should only be created if the user has permissions to do so
   457  		createFirewallRules := true
   458  		if installConfig.Config.GCP.NetworkProjectID != "" {
   459  			permissions, err := client.GetProjectPermissions(context.Background(), installConfig.Config.GCP.NetworkProjectID, []string{
   460  				GCPFirewallPermission,
   461  			})
   462  			if err != nil {
   463  				return err
   464  			}
   465  			createFirewallRules = permissions.Has(GCPFirewallPermission)
   466  
   467  			if !createFirewallRules {
   468  				logrus.Warnf("failed to find permission %s, skipping firewall rule creation", GCPFirewallPermission)
   469  			}
   470  		}
   471  
   472  		masters, err := mastersAsset.Machines()
   473  		if err != nil {
   474  			return err
   475  		}
   476  		masterConfigs := make([]*machinev1beta1.GCPMachineProviderSpec, len(masters))
   477  		for i, m := range masters {
   478  			masterConfigs[i] = m.Spec.ProviderSpec.Value.Object.(*machinev1beta1.GCPMachineProviderSpec) //nolint:errcheck // legacy, pre-linter
   479  		}
   480  		workers, err := workersAsset.MachineSets()
   481  		if err != nil {
   482  			return err
   483  		}
   484  		// Based on the number of workers, we could have the following outcomes:
   485  		// 1. compute replicas > 0, worker machinesets > 0, masters not schedulable, valid cluster
   486  		// 2. compute replicas > 0, worker machinesets = 0, invalid cluster
   487  		// 3. compute replicas = 0, masters schedulable, valid cluster
   488  		if numWorkers != 0 && len(workers) == 0 {
   489  			return fmt.Errorf("invalid configuration. No worker assets available for requested number of compute replicas (%d)", numWorkers)
   490  		}
   491  		if numWorkers == 0 && !mastersSchedulable {
   492  			return fmt.Errorf("invalid configuration. No workers requested but masters are not schedulable")
   493  		}
   494  
   495  		workerConfigs := make([]*machinev1beta1.GCPMachineProviderSpec, len(workers))
   496  		for i, w := range workers {
   497  			workerConfigs[i] = w.Spec.Template.Spec.ProviderSpec.Value.Object.(*machinev1beta1.GCPMachineProviderSpec) //nolint:errcheck // legacy, pre-linter
   498  		}
   499  		preexistingnetwork := installConfig.Config.GCP.Network != ""
   500  
   501  		// Search the project for a dns zone with the specified base domain.
   502  		// When the user has selected a custom DNS solution, the zones should be skipped.
   503  		publicZoneName := ""
   504  		privateZoneName := ""
   505  
   506  		if installConfig.Config.GCP.UserProvisionedDNS != gcp.UserProvisionedDNSEnabled {
   507  			if installConfig.Config.Publish == types.ExternalPublishingStrategy {
   508  				publicZone, err := client.GetDNSZone(ctx, installConfig.Config.GCP.ProjectID, installConfig.Config.BaseDomain, true)
   509  				if err != nil {
   510  					return errors.Wrapf(err, "failed to get GCP public zone")
   511  				}
   512  				publicZoneName = publicZone.Name
   513  			}
   514  
   515  			if installConfig.Config.GCP.NetworkProjectID != "" {
   516  				privateZone, err := client.GetDNSZone(ctx, installConfig.Config.GCP.ProjectID, installConfig.Config.ClusterDomain(), false)
   517  				if err != nil {
   518  					return errors.Wrapf(err, "failed to get GCP private zone")
   519  				}
   520  				if privateZone != nil {
   521  					privateZoneName = privateZone.Name
   522  				}
   523  			}
   524  		}
   525  
   526  		ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
   527  		defer cancel()
   528  
   529  		url, err := gcpbootstrap.CreateSignedURL(clusterID.InfraID)
   530  		if err != nil {
   531  			return fmt.Errorf("failed to provision gcp bootstrap storage resources: %w", err)
   532  		}
   533  
   534  		shim, err := bootstrap.GenerateIgnitionShimWithCertBundleAndProxy(url, installConfig.Config.AdditionalTrustBundle, installConfig.Config.Proxy)
   535  		if err != nil {
   536  			return fmt.Errorf("failed to create gcp ignition shim: %w", err)
   537  		}
   538  
   539  		tags, err := gcpconfig.NewTagManager(client).GetUserTags(ctx,
   540  			installConfig.Config.Platform.GCP.ProjectID,
   541  			installConfig.Config.Platform.GCP.UserTags)
   542  		if err != nil {
   543  			return fmt.Errorf("failed to fetch user-defined tags: %w", err)
   544  		}
   545  
   546  		data, err := gcptfvars.TFVars(
   547  			gcptfvars.TFVarsSources{
   548  				Auth:                auth,
   549  				MasterConfigs:       masterConfigs,
   550  				WorkerConfigs:       workerConfigs,
   551  				CreateFirewallRules: createFirewallRules,
   552  				PreexistingNetwork:  preexistingnetwork,
   553  				PublicZoneName:      publicZoneName,
   554  				PrivateZoneName:     privateZoneName,
   555  				PublishStrategy:     installConfig.Config.Publish,
   556  				InfrastructureName:  clusterID.InfraID,
   557  				UserProvisionedDNS:  installConfig.Config.GCP.UserProvisionedDNS == gcp.UserProvisionedDNSEnabled,
   558  				UserTags:            tags,
   559  				IgnitionShim:        string(shim),
   560  				PresignedURL:        url,
   561  			},
   562  		)
   563  		if err != nil {
   564  			return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
   565  		}
   566  		t.FileList = append(t.FileList, &asset.File{
   567  			Filename: TfPlatformVarsFileName,
   568  			Data:     data,
   569  		})
   570  	case ibmcloud.Name:
   571  		meta := ibmcloudconfig.NewMetadata(installConfig.Config)
   572  		client, err := meta.Client()
   573  		if err != nil {
   574  			return err
   575  		}
   576  		auth := ibmcloudtfvars.Auth{
   577  			APIKey: client.GetAPIKey(),
   578  		}
   579  
   580  		// Get master and worker machine info
   581  		masters, err := mastersAsset.Machines()
   582  		if err != nil {
   583  			return err
   584  		}
   585  		masterConfigs := make([]*ibmcloudprovider.IBMCloudMachineProviderSpec, len(masters))
   586  		for i, m := range masters {
   587  			masterConfigs[i] = m.Spec.ProviderSpec.Value.Object.(*ibmcloudprovider.IBMCloudMachineProviderSpec) //nolint:errcheck // legacy, pre-linter
   588  		}
   589  		workers, err := workersAsset.MachineSets()
   590  		if err != nil {
   591  			return err
   592  		}
   593  		workerConfigs := make([]*ibmcloudprovider.IBMCloudMachineProviderSpec, len(workers))
   594  		for i, w := range workers {
   595  			workerConfigs[i] = w.Spec.Template.Spec.ProviderSpec.Value.Object.(*ibmcloudprovider.IBMCloudMachineProviderSpec) //nolint:errcheck // legacy, pre-linter
   596  		}
   597  
   598  		// Set existing network (boolean of whether one is being used)
   599  		preexistingVPC := installConfig.Config.Platform.IBMCloud.GetVPCName() != ""
   600  
   601  		// Set machine pool info
   602  		var masterMachinePool ibmcloud.MachinePool
   603  		var workerMachinePool ibmcloud.MachinePool
   604  		if installConfig.Config.Platform.IBMCloud.DefaultMachinePlatform != nil {
   605  			masterMachinePool.Set(installConfig.Config.Platform.IBMCloud.DefaultMachinePlatform)
   606  			workerMachinePool.Set(installConfig.Config.Platform.IBMCloud.DefaultMachinePlatform)
   607  		}
   608  		if installConfig.Config.ControlPlane.Platform.IBMCloud != nil {
   609  			masterMachinePool.Set(installConfig.Config.ControlPlane.Platform.IBMCloud)
   610  		}
   611  		if worker := installConfig.Config.WorkerMachinePool(); worker != nil {
   612  			workerMachinePool.Set(worker.Platform.IBMCloud)
   613  		}
   614  
   615  		// Get master dedicated host info
   616  		var masterDedicatedHosts []ibmcloudtfvars.DedicatedHost
   617  		for _, dhost := range masterMachinePool.DedicatedHosts {
   618  			if dhost.Name != "" {
   619  				dh, err := client.GetDedicatedHostByName(ctx, dhost.Name, installConfig.Config.Platform.IBMCloud.Region)
   620  				if err != nil {
   621  					return err
   622  				}
   623  				masterDedicatedHosts = append(masterDedicatedHosts, ibmcloudtfvars.DedicatedHost{
   624  					ID: *dh.ID,
   625  				})
   626  			} else {
   627  				masterDedicatedHosts = append(masterDedicatedHosts, ibmcloudtfvars.DedicatedHost{
   628  					Profile: dhost.Profile,
   629  				})
   630  			}
   631  		}
   632  
   633  		// Get worker dedicated host info
   634  		var workerDedicatedHosts []ibmcloudtfvars.DedicatedHost
   635  		for _, dhost := range workerMachinePool.DedicatedHosts {
   636  			if dhost.Name != "" {
   637  				dh, err := client.GetDedicatedHostByName(ctx, dhost.Name, installConfig.Config.Platform.IBMCloud.Region)
   638  				if err != nil {
   639  					return err
   640  				}
   641  				workerDedicatedHosts = append(workerDedicatedHosts, ibmcloudtfvars.DedicatedHost{
   642  					ID: *dh.ID,
   643  				})
   644  			} else {
   645  				workerDedicatedHosts = append(workerDedicatedHosts, ibmcloudtfvars.DedicatedHost{
   646  					Profile: dhost.Profile,
   647  				})
   648  			}
   649  		}
   650  
   651  		var cisCRN, dnsID string
   652  		vpcPermitted := false
   653  
   654  		if installConfig.Config.Publish == types.InternalPublishingStrategy {
   655  			// Get DNSInstanceCRN from metadata
   656  			dnsInstance, err := meta.DNSInstance(ctx)
   657  			if err != nil {
   658  				return err
   659  			}
   660  			if dnsInstance != nil {
   661  				dnsID = dnsInstance.ID
   662  			}
   663  			// If the VPC already exists and the cluster is Private, check if the VPC is already a Permitted Network on DNS Instance
   664  			if preexistingVPC {
   665  				vpcPermitted, err = meta.IsVPCPermittedNetwork(ctx, installConfig.Config.Platform.IBMCloud.VPCName)
   666  				if err != nil {
   667  					return err
   668  				}
   669  			}
   670  		} else {
   671  			// Get CISInstanceCRN from metadata
   672  			cisCRN, err = meta.CISInstanceCRN(ctx)
   673  			if err != nil {
   674  				return err
   675  			}
   676  		}
   677  
   678  		// NOTE(cjschaef): If one or more ServiceEndpoint's are supplied, attempt to build the Terraform endpoint_file_path
   679  		// https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/guides/custom-service-endpoints#file-structure-for-endpoints-file
   680  		var endpointsJSONFile string
   681  		// Set Terraform visibility mode if necessary
   682  		terraformPrivateVisibility := false
   683  		if len(installConfig.Config.Platform.IBMCloud.ServiceEndpoints) > 0 {
   684  			// Determine if any endpoints require 'private' Terraform visibility mode (any contain 'private' or 'direct' for COS)
   685  			// This is a requirement for the IBM Cloud Terraform provider, forcing 'public' or 'private' visibility mode.
   686  			for _, endpoint := range installConfig.Config.Platform.IBMCloud.ServiceEndpoints {
   687  				if strings.Contains(endpoint.URL, "private") || strings.Contains(endpoint.URL, "direct") {
   688  					// If at least one endpoint is private (or direct) we expect to use Private visibility mode
   689  					terraformPrivateVisibility = true
   690  					break
   691  				}
   692  			}
   693  
   694  			endpointData, err := ibmcloudtfvars.CreateEndpointJSON(installConfig.Config.Platform.IBMCloud.ServiceEndpoints, installConfig.Config.Platform.IBMCloud.Region)
   695  			if err != nil {
   696  				return err
   697  			}
   698  			// While service endpoints may not be empty, they may not be required for Terraform.
   699  			// So, if we have not endpoint data, we don't need to generate the JSON override file.
   700  			if endpointData != nil {
   701  				// Add endpoint JSON data to list of generated files for Terraform
   702  				t.FileList = append(t.FileList, &asset.File{
   703  					Filename: ibmcloudtfvars.IBMCloudEndpointJSONFileName,
   704  					Data:     endpointData,
   705  				})
   706  				endpointsJSONFile = ibmcloudtfvars.IBMCloudEndpointJSONFileName
   707  			}
   708  		}
   709  
   710  		data, err = ibmcloudtfvars.TFVars(
   711  			ibmcloudtfvars.TFVarsSources{
   712  				Auth:                       auth,
   713  				CISInstanceCRN:             cisCRN,
   714  				DNSInstanceID:              dnsID,
   715  				EndpointsJSONFile:          endpointsJSONFile,
   716  				ImageURL:                   rhcosImage.ControlPlane,
   717  				MasterConfigs:              masterConfigs,
   718  				MasterDedicatedHosts:       masterDedicatedHosts,
   719  				NetworkResourceGroupName:   installConfig.Config.Platform.IBMCloud.NetworkResourceGroupName,
   720  				PreexistingVPC:             preexistingVPC,
   721  				PublishStrategy:            installConfig.Config.Publish,
   722  				ResourceGroupName:          installConfig.Config.Platform.IBMCloud.ResourceGroupName,
   723  				TerraformPrivateVisibility: terraformPrivateVisibility,
   724  				VPCPermitted:               vpcPermitted,
   725  				WorkerConfigs:              workerConfigs,
   726  				WorkerDedicatedHosts:       workerDedicatedHosts,
   727  			},
   728  		)
   729  		if err != nil {
   730  			return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
   731  		}
   732  		t.FileList = append(t.FileList, &asset.File{
   733  			Filename: TfPlatformVarsFileName,
   734  			Data:     data,
   735  		})
   736  	case openstack.Name:
   737  		data, err = openstacktfvars.TFVars(
   738  			ctx,
   739  			installConfig,
   740  			mastersAsset,
   741  			workersAsset,
   742  			rhcosImage.ControlPlane,
   743  			clusterID,
   744  			bootstrapIgn,
   745  		)
   746  		if err != nil {
   747  			return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
   748  		}
   749  		t.FileList = append(t.FileList, &asset.File{
   750  			Filename: TfPlatformVarsFileName,
   751  			Data:     data,
   752  		})
   753  	case baremetal.Name:
   754  		data, err = baremetaltfvars.TFVars(
   755  			installConfig.Config.Platform.BareMetal.LibvirtURI,
   756  			string(*rhcosBootstrapImage),
   757  			installConfig.Config.Platform.BareMetal.ExternalBridge,
   758  			installConfig.Config.Platform.BareMetal.ExternalMACAddress,
   759  			installConfig.Config.Platform.BareMetal.ProvisioningBridge,
   760  			installConfig.Config.Platform.BareMetal.ProvisioningMACAddress,
   761  		)
   762  		if err != nil {
   763  			return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
   764  		}
   765  		t.FileList = append(t.FileList, &asset.File{
   766  			Filename: TfPlatformVarsFileName,
   767  			Data:     data,
   768  		})
   769  	case ovirt.Name:
   770  		config, err := ovirtconfig.NewConfig()
   771  		if err != nil {
   772  			return err
   773  		}
   774  		con, err := ovirtconfig.NewConnection()
   775  		if err != nil {
   776  			return err
   777  		}
   778  		defer con.Close()
   779  
   780  		if installConfig.Config.Platform.Ovirt.VNICProfileID == "" {
   781  			profiles, err := ovirtconfig.FetchVNICProfileByClusterNetwork(
   782  				con,
   783  				installConfig.Config.Platform.Ovirt.ClusterID,
   784  				installConfig.Config.Platform.Ovirt.NetworkName)
   785  			if err != nil {
   786  				return errors.Wrapf(err, "failed to compute values for Engine platform")
   787  			}
   788  			if len(profiles) != 1 {
   789  				return fmt.Errorf("failed to compute values for Engine platform, "+
   790  					"there are multiple vNIC profiles. found %v vNIC profiles for network %s",
   791  					len(profiles), installConfig.Config.Platform.Ovirt.NetworkName)
   792  			}
   793  			installConfig.Config.Platform.Ovirt.VNICProfileID = profiles[0].MustId()
   794  		}
   795  
   796  		masters, err := mastersAsset.Machines()
   797  		if err != nil {
   798  			return err
   799  		}
   800  
   801  		data, err := ovirttfvars.TFVars(
   802  			ovirttfvars.Auth{
   803  				URL:      config.URL,
   804  				Username: config.Username,
   805  				Password: config.Password,
   806  				Cafile:   config.CAFile,
   807  				Cabundle: config.CABundle,
   808  				Insecure: config.Insecure,
   809  			},
   810  			installConfig.Config.Platform.Ovirt.ClusterID,
   811  			installConfig.Config.Platform.Ovirt.StorageDomainID,
   812  			installConfig.Config.Platform.Ovirt.NetworkName,
   813  			installConfig.Config.Platform.Ovirt.VNICProfileID,
   814  			rhcosImage.ControlPlane,
   815  			clusterID.InfraID,
   816  			masters[0].Spec.ProviderSpec.Value.Object.(*ovirtprovider.OvirtMachineProviderSpec),
   817  			installConfig.Config.Platform.Ovirt.AffinityGroups,
   818  		)
   819  		if err != nil {
   820  			return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
   821  		}
   822  		t.FileList = append(t.FileList, &asset.File{
   823  			Filename: TfPlatformVarsFileName,
   824  			Data:     data,
   825  		})
   826  	case powervs.Name:
   827  		APIKey, err := installConfig.PowerVS.APIKey(ctx)
   828  		if err != nil {
   829  			return err
   830  		}
   831  
   832  		masters, err := mastersAsset.Machines()
   833  		if err != nil {
   834  			return err
   835  		}
   836  
   837  		var (
   838  			cisCRN, dnsCRN, vpcGatewayName, vpcSubnet string
   839  			vpcPermitted, vpcGatewayAttached          bool
   840  		)
   841  		if len(installConfig.Config.PowerVS.VPCSubnets) > 0 {
   842  			vpcSubnet = installConfig.Config.PowerVS.VPCSubnets[0]
   843  		}
   844  		switch installConfig.Config.Publish {
   845  		case types.InternalPublishingStrategy:
   846  			// Get DNSInstanceCRN from InstallConfig metadata
   847  			dnsCRN, err = installConfig.PowerVS.DNSInstanceCRN(ctx)
   848  			if err != nil {
   849  				return err
   850  			}
   851  
   852  			// If the VPC already exists and the cluster is Private, check if the VPC is already a Permitted Network on DNS Instance
   853  			if installConfig.Config.PowerVS.VPCName != "" {
   854  				vpcPermitted, err = installConfig.PowerVS.IsVPCPermittedNetwork(ctx, installConfig.Config.Platform.PowerVS.VPCName, installConfig.Config.BaseDomain)
   855  				if err != nil {
   856  					return err
   857  				}
   858  				vpcGatewayName, vpcGatewayAttached, err = installConfig.PowerVS.GetExistingVPCGateway(ctx, installConfig.Config.Platform.PowerVS.VPCName, vpcSubnet)
   859  				if err != nil {
   860  					return err
   861  				}
   862  			}
   863  		case types.ExternalPublishingStrategy:
   864  			// Get CISInstanceCRN from InstallConfig metadata
   865  			cisCRN, err = installConfig.PowerVS.CISInstanceCRN(ctx)
   866  			if err != nil {
   867  				return err
   868  			}
   869  		default:
   870  			return errors.New("unknown publishing strategy")
   871  		}
   872  
   873  		masterConfigs := make([]*machinev1.PowerVSMachineProviderConfig, len(masters))
   874  		for i, m := range masters {
   875  			masterConfigs[i] = m.Spec.ProviderSpec.Value.Object.(*machinev1.PowerVSMachineProviderConfig) //nolint:errcheck // legacy, pre-linter
   876  		}
   877  
   878  		client, err := powervsconfig.NewClient()
   879  		if err != nil {
   880  			return err
   881  		}
   882  		var (
   883  			vpcRegion, vpcZone string
   884  			vpc                *vpcv1.VPC
   885  		)
   886  		vpcName := installConfig.Config.PowerVS.VPCName
   887  		if vpcName != "" {
   888  			vpc, err = client.GetVPCByName(ctx, vpcName)
   889  			if err != nil {
   890  				return err
   891  			}
   892  			var crnElems = strings.SplitN(*vpc.CRN, ":", 8)
   893  			vpcRegion = crnElems[5]
   894  		} else {
   895  			specified := installConfig.Config.PowerVS.VPCRegion
   896  			if specified != "" {
   897  				if powervs.ValidateVPCRegion(specified) {
   898  					vpcRegion = specified
   899  				} else {
   900  					return errors.New("unknown VPC region")
   901  				}
   902  			} else if vpcRegion, err = powervs.VPCRegionForPowerVSRegion(installConfig.Config.PowerVS.Region); err != nil {
   903  				return err
   904  			}
   905  		}
   906  		if vpcSubnet != "" {
   907  			var sn *vpcv1.Subnet
   908  			sn, err = client.GetSubnetByName(ctx, vpcSubnet, vpcRegion)
   909  			if err != nil {
   910  				return err
   911  			}
   912  			vpcZone = *sn.Zone.Name
   913  		} else {
   914  			rand.New(rand.NewSource(time.Now().UnixNano()))           //nolint:gosec // we don't need a crypto secure number
   915  			vpcZone = fmt.Sprintf("%s-%d", vpcRegion, rand.Intn(2)+1) //nolint:gosec // we don't need a crypto secure number
   916  		}
   917  
   918  		cpStanza := installConfig.Config.ControlPlane
   919  		if cpStanza == nil || cpStanza.Platform.PowerVS == nil || cpStanza.Platform.PowerVS.SysType == "" {
   920  			sysTypes, err := powervs.AvailableSysTypes(installConfig.Config.PowerVS.Region)
   921  			if err != nil {
   922  				return err
   923  			}
   924  			for i := range masters {
   925  				masterConfigs[i].SystemType = sysTypes[0]
   926  			}
   927  		}
   928  
   929  		attachedTG := ""
   930  		tgConnectionVPCID := ""
   931  		if installConfig.Config.PowerVS.ServiceInstanceGUID != "" {
   932  			attachedTG, err = client.GetAttachedTransitGateway(ctx, installConfig.Config.PowerVS.ServiceInstanceGUID)
   933  			if err != nil {
   934  				return err
   935  			}
   936  			if attachedTG != "" && vpc != nil {
   937  				tgConnectionVPCID, err = client.GetTGConnectionVPC(ctx, attachedTG, *vpc.ID)
   938  				if err != nil {
   939  					return err
   940  				}
   941  			}
   942  		}
   943  
   944  		// If a service instance GUID was passed in the install-config.yaml file, then
   945  		// find the corresponding name for it.  Otherwise, we expect our Terraform to
   946  		// dynamically create one.
   947  		serviceInstanceName, err := client.ServiceInstanceGUIDToName(ctx, installConfig.Config.PowerVS.ServiceInstanceGUID)
   948  		if err != nil {
   949  			return err
   950  		}
   951  
   952  		osImage := strings.SplitN(rhcosImage.ControlPlane, "/", 2)
   953  		data, err = powervstfvars.TFVars(
   954  			powervstfvars.TFVarsSources{
   955  				MasterConfigs:          masterConfigs,
   956  				Region:                 installConfig.Config.Platform.PowerVS.Region,
   957  				Zone:                   installConfig.Config.Platform.PowerVS.Zone,
   958  				APIKey:                 APIKey,
   959  				SSHKey:                 installConfig.Config.SSHKey,
   960  				PowerVSResourceGroup:   installConfig.Config.PowerVS.PowerVSResourceGroup,
   961  				ImageBucketName:        osImage[0],
   962  				ImageBucketFileName:    osImage[1],
   963  				VPCRegion:              vpcRegion,
   964  				VPCZone:                vpcZone,
   965  				VPCName:                vpcName,
   966  				VPCSubnetName:          vpcSubnet,
   967  				VPCPermitted:           vpcPermitted,
   968  				VPCGatewayName:         vpcGatewayName,
   969  				VPCGatewayAttached:     vpcGatewayAttached,
   970  				CISInstanceCRN:         cisCRN,
   971  				DNSInstanceCRN:         dnsCRN,
   972  				PublishStrategy:        installConfig.Config.Publish,
   973  				EnableSNAT:             len(installConfig.Config.DeprecatedImageContentSources) == 0 && len(installConfig.Config.ImageDigestSources) == 0,
   974  				AttachedTransitGateway: attachedTG,
   975  				TGConnectionVPCID:      tgConnectionVPCID,
   976  				ServiceInstanceName:    serviceInstanceName,
   977  			},
   978  		)
   979  		if err != nil {
   980  			return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
   981  		}
   982  		t.FileList = append(t.FileList, &asset.File{
   983  			Filename: TfPlatformVarsFileName,
   984  			Data:     data,
   985  		})
   986  
   987  	case vsphere.Name:
   988  		t.FileList = make([]*asset.File, 0)
   989  		logrus.Warn("installing on vSphere via terraform is no longer supported")
   990  		return nil
   991  	case nutanix.Name:
   992  		controlPlanes, err := mastersAsset.Machines()
   993  		if err != nil {
   994  			return errors.Wrapf(err, "error getting control plane machines")
   995  		}
   996  		controlPlaneConfigs := make([]*machinev1.NutanixMachineProviderConfig, len(controlPlanes))
   997  		for i, c := range controlPlanes {
   998  			controlPlaneConfigs[i] = c.Spec.ProviderSpec.Value.Object.(*machinev1.NutanixMachineProviderConfig) //nolint:errcheck // legacy, pre-linter
   999  		}
  1000  
  1001  		imgURI := rhcosImage.ControlPlane
  1002  		if installConfig.Config.Nutanix.ClusterOSImage != "" {
  1003  			imgURI = installConfig.Config.Nutanix.ClusterOSImage
  1004  		}
  1005  		data, err = nutanixtfvars.TFVars(
  1006  			nutanixtfvars.TFVarsSources{
  1007  				PrismCentralAddress:   installConfig.Config.Nutanix.PrismCentral.Endpoint.Address,
  1008  				Port:                  strconv.Itoa(int(installConfig.Config.Nutanix.PrismCentral.Endpoint.Port)),
  1009  				Username:              installConfig.Config.Nutanix.PrismCentral.Username,
  1010  				Password:              installConfig.Config.Nutanix.PrismCentral.Password,
  1011  				ImageURI:              imgURI,
  1012  				BootstrapIgnitionData: bootstrapIgn,
  1013  				ClusterID:             clusterID.InfraID,
  1014  				ControlPlaneConfigs:   controlPlaneConfigs,
  1015  			},
  1016  		)
  1017  		if err != nil {
  1018  			return errors.Wrapf(err, "failed to get %s Terraform variables", platform)
  1019  		}
  1020  		t.FileList = append(t.FileList, &asset.File{
  1021  			Filename: TfPlatformVarsFileName,
  1022  			Data:     data,
  1023  		})
  1024  	default:
  1025  		logrus.Warnf("unrecognized platform %s", platform)
  1026  	}
  1027  
  1028  	return nil
  1029  }
  1030  
  1031  // Files returns the files generated by the asset.
  1032  func (t *TerraformVariables) Files() []*asset.File {
  1033  	return t.FileList
  1034  }
  1035  
  1036  // Load reads the terraform.tfvars from disk.
  1037  func (t *TerraformVariables) Load(f asset.FileFetcher) (found bool, err error) {
  1038  	file, err := f.FetchByName(TfVarsFileName)
  1039  	if err != nil {
  1040  		if os.IsNotExist(err) {
  1041  			return false, nil
  1042  		}
  1043  		return false, err
  1044  	}
  1045  	t.FileList = []*asset.File{file}
  1046  
  1047  	switch file, err := f.FetchByName(TfPlatformVarsFileName); {
  1048  	case err == nil:
  1049  		t.FileList = append(t.FileList, file)
  1050  	case !os.IsNotExist(err):
  1051  		return false, err
  1052  	}
  1053  
  1054  	return true, nil
  1055  }
  1056  
  1057  // injectInstallInfo adds information about the installer and its invoker as a
  1058  // ConfigMap to the provided bootstrap Ignition config.
  1059  func injectInstallInfo(bootstrap []byte) (string, error) {
  1060  	config := &igntypes.Config{}
  1061  	if err := json.Unmarshal(bootstrap, &config); err != nil {
  1062  		return "", errors.Wrap(err, "failed to unmarshal bootstrap Ignition config")
  1063  	}
  1064  
  1065  	cm, err := openshiftinstall.CreateInstallConfigMap("openshift-install")
  1066  	if err != nil {
  1067  		return "", errors.Wrap(err, "failed to generate openshift-install config")
  1068  	}
  1069  
  1070  	config.Storage.Files = append(config.Storage.Files, ignition.FileFromString("/opt/openshift/manifests/openshift-install.yaml", "root", 0644, cm))
  1071  
  1072  	ign, err := ignition.Marshal(config)
  1073  	if err != nil {
  1074  		return "", errors.Wrap(err, "failed to marshal bootstrap Ignition config")
  1075  	}
  1076  
  1077  	return string(ign), nil
  1078  }