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 }