github.com/openshift/installer@v1.4.17/pkg/infrastructure/clusterapi/clusterapi.go (about) 1 package clusterapi 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "os" 8 "path/filepath" 9 "time" 10 11 "github.com/sirupsen/logrus" 12 "gopkg.in/yaml.v2" 13 corev1 "k8s.io/api/core/v1" 14 apierrors "k8s.io/apimachinery/pkg/api/errors" 15 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 utilerrors "k8s.io/apimachinery/pkg/util/errors" 17 "k8s.io/apimachinery/pkg/util/wait" 18 "k8s.io/utils/ptr" 19 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1" 20 utilkubeconfig "sigs.k8s.io/cluster-api/util/kubeconfig" 21 "sigs.k8s.io/controller-runtime/pkg/client" 22 23 "github.com/openshift/installer/pkg/asset" 24 "github.com/openshift/installer/pkg/asset/cluster/metadata" 25 "github.com/openshift/installer/pkg/asset/cluster/tfvars" 26 "github.com/openshift/installer/pkg/asset/ignition/bootstrap" 27 "github.com/openshift/installer/pkg/asset/ignition/machine" 28 "github.com/openshift/installer/pkg/asset/installconfig" 29 "github.com/openshift/installer/pkg/asset/kubeconfig" 30 "github.com/openshift/installer/pkg/asset/machines" 31 "github.com/openshift/installer/pkg/asset/manifests" 32 "github.com/openshift/installer/pkg/asset/manifests/capiutils" 33 capimanifests "github.com/openshift/installer/pkg/asset/manifests/clusterapi" 34 "github.com/openshift/installer/pkg/asset/rhcos" 35 "github.com/openshift/installer/pkg/clusterapi" 36 "github.com/openshift/installer/pkg/infrastructure" 37 "github.com/openshift/installer/pkg/metrics/timer" 38 "github.com/openshift/installer/pkg/types" 39 ) 40 41 // Ensure that clusterapi.InfraProvider implements 42 // the infrastructure.Provider interface, which is the 43 // interface the installer uses to call this provider. 44 var _ infrastructure.Provider = (*InfraProvider)(nil) 45 46 const ( 47 preProvisionStage = "Infrastructure Pre-provisioning" 48 infrastructureStage = "Network-infrastructure Provisioning" 49 infrastructureReadyStage = "Post-network, pre-machine Provisioning" 50 ignitionStage = "Bootstrap Ignition Provisioning" 51 machineStage = "Machine Provisioning" 52 postProvisionStage = "Infrastructure Post-provisioning" 53 ) 54 55 // InfraProvider implements common Cluster API logic and 56 // contains the platform CAPI provider, which is called 57 // in the lifecycle defined by the Provider interface. 58 type InfraProvider struct { 59 impl Provider 60 61 appliedManifests []client.Object 62 } 63 64 // InitializeProvider returns a ClusterAPI provider implementation 65 // for a specific cloud platform. 66 func InitializeProvider(platform Provider) infrastructure.Provider { 67 return &InfraProvider{impl: platform} 68 } 69 70 // Provision creates cluster resources by applying CAPI manifests to a locally running control plane. 71 // 72 //nolint:gocyclo 73 func (i *InfraProvider) Provision(ctx context.Context, dir string, parents asset.Parents) (fileList []*asset.File, err error) { 74 manifestsAsset := &manifests.Manifests{} 75 workersAsset := &machines.Worker{} 76 capiManifestsAsset := &capimanifests.Cluster{} 77 capiMachinesAsset := &machines.ClusterAPI{} 78 clusterKubeconfigAsset := &kubeconfig.AdminClient{} 79 clusterID := &installconfig.ClusterID{} 80 installConfig := &installconfig.InstallConfig{} 81 rhcosImage := new(rhcos.Image) 82 bootstrapIgnAsset := &bootstrap.Bootstrap{} 83 masterIgnAsset := &machine.Master{} 84 tfvarsAsset := &tfvars.TerraformVariables{} 85 parents.Get( 86 manifestsAsset, 87 workersAsset, 88 capiManifestsAsset, 89 clusterKubeconfigAsset, 90 clusterID, 91 installConfig, 92 rhcosImage, 93 bootstrapIgnAsset, 94 masterIgnAsset, 95 capiMachinesAsset, 96 tfvarsAsset, 97 ) 98 99 var clusterIDs []string 100 101 // Collect cluster and non-machine-related infra manifests 102 // to be applied during the initial stage. 103 infraManifests := []client.Object{} 104 for _, m := range capiManifestsAsset.RuntimeFiles() { 105 // Check for cluster definition so that we can collect the names. 106 if cluster, ok := m.Object.(*clusterv1.Cluster); ok { 107 clusterIDs = append(clusterIDs, cluster.GetName()) 108 } 109 110 infraManifests = append(infraManifests, m.Object) 111 } 112 113 // Machine manifests will be applied after the infra 114 // manifests and subsequent hooks. 115 machineManifests := []client.Object{} 116 for _, m := range capiMachinesAsset.RuntimeFiles() { 117 machineManifests = append(machineManifests, m.Object) 118 } 119 120 if p, ok := i.impl.(PreProvider); ok { 121 preProvisionInput := PreProvisionInput{ 122 InfraID: clusterID.InfraID, 123 InstallConfig: installConfig, 124 RhcosImage: rhcosImage, 125 ManifestsAsset: manifestsAsset, 126 MachineManifests: machineManifests, 127 WorkersAsset: workersAsset, 128 } 129 timer.StartTimer(preProvisionStage) 130 if err := p.PreProvision(ctx, preProvisionInput); err != nil { 131 return fileList, fmt.Errorf("failed during pre-provisioning: %w", err) 132 } 133 timer.StopTimer(preProvisionStage) 134 } else { 135 logrus.Debugf("No pre-provisioning requirements for the %s provider", i.impl.Name()) 136 } 137 138 // If we're skipping bootstrap destroy, shutdown the local control plane. 139 // Otherwise, we will shut it down after bootstrap destroy. 140 // This has to execute as the last defer in the stack since previous defers might still need the local controlplane. 141 if oi, ok := os.LookupEnv("OPENSHIFT_INSTALL_PRESERVE_BOOTSTRAP"); ok && oi != "" { 142 defer func() { 143 logrus.Warn("OPENSHIFT_INSTALL_PRESERVE_BOOTSTRAP is set, shutting down local control plane.") 144 clusterapi.System().Teardown() 145 }() 146 } 147 148 // Make sure to always return generated manifests, even if errors happened 149 defer func(ctx context.Context) { 150 var errs []error 151 // Overriding the named return with the generated list 152 fileList, errs = i.collectManifests(ctx, clusterapi.System().Client()) 153 // If Provision returned an error, add it to the list 154 if err != nil { 155 clusterapi.System().CleanEtcd() 156 errs = append(errs, err) 157 } 158 err = utilerrors.NewAggregate(errs) 159 }(ctx) 160 161 // Run the CAPI system. 162 timer.StartTimer(infrastructureStage) 163 capiSystem := clusterapi.System() 164 if err := capiSystem.Run(ctx); err != nil { 165 return fileList, fmt.Errorf("failed to run cluster api system: %w", err) 166 } 167 168 // Grab the client. 169 cl := capiSystem.Client() 170 171 i.appliedManifests = []client.Object{} 172 173 // Create the infra manifests. 174 logrus.Info("Creating infra manifests...") 175 for _, m := range infraManifests { 176 m.SetNamespace(capiutils.Namespace) 177 if err := cl.Create(ctx, m); err != nil { 178 return fileList, fmt.Errorf("failed to create infrastructure manifest: %w", err) 179 } 180 i.appliedManifests = append(i.appliedManifests, m) 181 logrus.Infof("Created manifest %+T, namespace=%s name=%s", m, m.GetNamespace(), m.GetName()) 182 } 183 logrus.Info("Done creating infra manifests") 184 185 // Pass cluster kubeconfig and store it in; this is usually the role of a bootstrap provider. 186 for _, capiClusterID := range clusterIDs { 187 logrus.Infof("Creating kubeconfig entry for capi cluster %v", capiClusterID) 188 key := client.ObjectKey{ 189 Name: capiClusterID, 190 Namespace: capiutils.Namespace, 191 } 192 cluster := &clusterv1.Cluster{} 193 if err := cl.Get(ctx, key, cluster); err != nil { 194 return fileList, err 195 } 196 // Create the secret. 197 clusterKubeconfig := clusterKubeconfigAsset.Files()[0].Data 198 secret := utilkubeconfig.GenerateSecret(cluster, clusterKubeconfig) 199 if err := cl.Create(ctx, secret); err != nil { 200 return fileList, err 201 } 202 } 203 204 var networkTimeout = 15 * time.Minute 205 206 if p, ok := i.impl.(Timeouts); ok { 207 networkTimeout = p.NetworkTimeout() 208 } 209 210 // Wait for successful provisioning by checking the InfrastructureReady 211 // status on the cluster object. 212 untilTime := time.Now().Add(networkTimeout) 213 timezone, _ := untilTime.Zone() 214 logrus.Infof("Waiting up to %v (until %v %s) for network infrastructure to become ready...", networkTimeout, untilTime.Format(time.Kitchen), timezone) 215 var cluster *clusterv1.Cluster 216 { 217 if err := wait.PollUntilContextTimeout(ctx, 15*time.Second, networkTimeout, true, 218 func(ctx context.Context) (bool, error) { 219 c := &clusterv1.Cluster{} 220 var clusters []*clusterv1.Cluster 221 for _, curClusterID := range clusterIDs { 222 if err := cl.Get(ctx, client.ObjectKey{ 223 Name: curClusterID, 224 Namespace: capiutils.Namespace, 225 }, c); err != nil { 226 if apierrors.IsNotFound(err) { 227 return false, nil 228 } 229 return false, err 230 } 231 clusters = append(clusters, c) 232 } 233 234 for _, curCluster := range clusters { 235 if !curCluster.Status.InfrastructureReady { 236 return false, nil 237 } 238 } 239 240 cluster = clusters[0] 241 return true, nil 242 }); err != nil { 243 if wait.Interrupted(err) { 244 return fileList, fmt.Errorf("infrastructure was not ready within %v: %w", networkTimeout, err) 245 } 246 return fileList, fmt.Errorf("infrastructure is not ready: %w", err) 247 } 248 if cluster == nil { 249 return fileList, fmt.Errorf("error occurred during load balancer ready check") 250 } 251 if cluster.Spec.ControlPlaneEndpoint.Host == "" { 252 return fileList, fmt.Errorf("control plane endpoint is not set") 253 } 254 } 255 timer.StopTimer(infrastructureStage) 256 logrus.Info("Network infrastructure is ready") 257 258 if p, ok := i.impl.(InfraReadyProvider); ok { 259 infraReadyInput := InfraReadyInput{ 260 Client: cl, 261 InstallConfig: installConfig, 262 InfraID: clusterID.InfraID, 263 } 264 265 timer.StartTimer(infrastructureReadyStage) 266 if err := p.InfraReady(ctx, infraReadyInput); err != nil { 267 return fileList, fmt.Errorf("failed provisioning resources after infrastructure ready: %w", err) 268 } 269 timer.StopTimer(infrastructureReadyStage) 270 } else { 271 logrus.Debugf("No infrastructure ready requirements for the %s provider", i.impl.Name()) 272 } 273 274 bootstrapIgnData, err := injectInstallInfo(bootstrapIgnAsset.Files()[0].Data) 275 if err != nil { 276 return fileList, fmt.Errorf("unable to inject installation info: %w", err) 277 } 278 279 // The cloud-platform may need to override the default 280 // bootstrap ignition behavior. 281 if p, ok := i.impl.(IgnitionProvider); ok { 282 ignInput := IgnitionInput{ 283 Client: cl, 284 BootstrapIgnData: bootstrapIgnData, 285 InfraID: clusterID.InfraID, 286 InstallConfig: installConfig, 287 TFVarsAsset: tfvarsAsset, 288 } 289 290 timer.StartTimer(ignitionStage) 291 if bootstrapIgnData, err = p.Ignition(ctx, ignInput); err != nil { 292 return fileList, fmt.Errorf("failed preparing ignition data: %w", err) 293 } 294 timer.StopTimer(ignitionStage) 295 } else { 296 logrus.Debugf("No Ignition requirements for the %s provider", i.impl.Name()) 297 } 298 bootstrapIgnSecret := IgnitionSecret(bootstrapIgnData, clusterID.InfraID, "bootstrap") 299 masterIgnSecret := IgnitionSecret(masterIgnAsset.Files()[0].Data, clusterID.InfraID, "master") 300 machineManifests = append(machineManifests, bootstrapIgnSecret, masterIgnSecret) 301 302 // Create the machine manifests. 303 timer.StartTimer(machineStage) 304 machineNames := []string{} 305 306 for _, m := range machineManifests { 307 m.SetNamespace(capiutils.Namespace) 308 if err := cl.Create(ctx, m); err != nil { 309 return fileList, fmt.Errorf("failed to create control-plane manifest: %w", err) 310 } 311 i.appliedManifests = append(i.appliedManifests, m) 312 313 if machine, ok := m.(*clusterv1.Machine); ok { 314 machineNames = append(machineNames, machine.Name) 315 } 316 logrus.Infof("Created manifest %+T, namespace=%s name=%s", m, m.GetNamespace(), m.GetName()) 317 } 318 319 var provisionTimeout = 15 * time.Minute 320 321 if p, ok := i.impl.(Timeouts); ok { 322 provisionTimeout = p.ProvisionTimeout() 323 } 324 325 { 326 untilTime := time.Now().Add(provisionTimeout) 327 timezone, _ := untilTime.Zone() 328 reqBootstrapPubIP := installConfig.Config.Publish == types.ExternalPublishingStrategy && i.impl.PublicGatherEndpoint() == ExternalIP 329 logrus.Infof("Waiting up to %v (until %v %s) for machines %v to provision...", provisionTimeout, untilTime.Format(time.Kitchen), timezone, machineNames) 330 if err := wait.PollUntilContextTimeout(ctx, 15*time.Second, provisionTimeout, true, 331 func(ctx context.Context) (bool, error) { 332 allReady := true 333 for _, machineName := range machineNames { 334 machine := &clusterv1.Machine{} 335 if err := cl.Get(ctx, client.ObjectKey{ 336 Name: machineName, 337 Namespace: capiutils.Namespace, 338 }, machine); err != nil { 339 if apierrors.IsNotFound(err) { 340 logrus.Debugf("Not found") 341 return false, nil 342 } 343 return false, err 344 } 345 reqPubIP := reqBootstrapPubIP && machineName == capiutils.GenerateBoostrapMachineName(clusterID.InfraID) 346 ready, err := checkMachineReady(machine, reqPubIP) 347 if err != nil { 348 return false, fmt.Errorf("failed waiting for machines: %w", err) 349 } 350 if !ready { 351 allReady = false 352 } else { 353 logrus.Debugf("Machine %s is ready. Phase: %s", machine.Name, machine.Status.Phase) 354 } 355 } 356 return allReady, nil 357 }); err != nil { 358 if wait.Interrupted(err) { 359 return fileList, fmt.Errorf("control-plane machines were not provisioned within %v: %w", provisionTimeout, err) 360 } 361 return fileList, fmt.Errorf("control-plane machines are not ready: %w", err) 362 } 363 } 364 timer.StopTimer(machineStage) 365 logrus.Info("Control-plane machines are ready") 366 367 if p, ok := i.impl.(PostProvider); ok { 368 postMachineInput := PostProvisionInput{ 369 Client: cl, 370 InstallConfig: installConfig, 371 InfraID: clusterID.InfraID, 372 } 373 374 timer.StartTimer(postProvisionStage) 375 if err = p.PostProvision(ctx, postMachineInput); err != nil { 376 return fileList, fmt.Errorf("failed during post-machine creation hook: %w", err) 377 } 378 timer.StopTimer(postProvisionStage) 379 } 380 381 logrus.Infof("Cluster API resources have been created. Waiting for cluster to become ready...") 382 383 return fileList, nil 384 } 385 386 // DestroyBootstrap destroys the temporary bootstrap resources. 387 func (i *InfraProvider) DestroyBootstrap(ctx context.Context, dir string) error { 388 defer clusterapi.System().CleanEtcd() 389 390 metadata, err := metadata.Load(dir) 391 if err != nil { 392 return err 393 } 394 395 sys := clusterapi.System() 396 if sys.State() != clusterapi.SystemStateRunning { 397 if err := sys.Run(ctx); err != nil { 398 return fmt.Errorf("failed to run capi system: %w", err) 399 } 400 } 401 402 if p, ok := i.impl.(BootstrapDestroyer); ok { 403 bootstrapDestoryInput := BootstrapDestroyInput{ 404 Client: sys.Client(), 405 Metadata: *metadata, 406 } 407 408 if err = p.DestroyBootstrap(ctx, bootstrapDestoryInput); err != nil { 409 return fmt.Errorf("failed during the destroy bootstrap hook: %w", err) 410 } 411 } 412 413 machineName := capiutils.GenerateBoostrapMachineName(metadata.InfraID) 414 machineNamespace := capiutils.Namespace 415 if err := sys.Client().Delete(ctx, &clusterv1.Machine{ 416 ObjectMeta: metav1.ObjectMeta{ 417 Name: machineName, 418 Namespace: machineNamespace, 419 }, 420 }); err != nil { 421 return fmt.Errorf("failed to delete bootstrap machine: %w", err) 422 } 423 424 machineDeletionTimeout := 5 * time.Minute 425 logrus.Infof("Waiting up to %v for bootstrap machine deletion %s/%s...", machineDeletionTimeout, machineNamespace, machineName) 426 cctx, cancel := context.WithTimeout(ctx, machineDeletionTimeout) 427 wait.UntilWithContext(cctx, func(context.Context) { 428 err := sys.Client().Get(cctx, client.ObjectKey{ 429 Name: machineName, 430 Namespace: machineNamespace, 431 }, &clusterv1.Machine{}) 432 if err != nil { 433 if apierrors.IsNotFound(err) { 434 logrus.Debugf("Machine deleted: %s", machineName) 435 cancel() 436 } else { 437 logrus.Debugf("Error when deleting bootstrap machine: %s", err) 438 } 439 } 440 }, 2*time.Second) 441 442 err = cctx.Err() 443 if err != nil && !errors.Is(err, context.Canceled) { 444 logrus.Warnf("Timeout deleting bootstrap machine: %s", err) 445 } 446 clusterapi.System().Teardown() 447 448 if p, ok := i.impl.(PostDestroyer); ok { 449 postDestroyInput := PostDestroyerInput{ 450 Metadata: *metadata, 451 } 452 if err := p.PostDestroy(ctx, postDestroyInput); err != nil { 453 return fmt.Errorf("failed during post-destroy hook: %w", err) 454 } 455 logrus.Debugf("Finished running post-destroy hook") 456 } else { 457 logrus.Infof("no post-destroy requirements for the %s provider", i.impl.Name()) 458 } 459 460 logrus.Infof("Finished destroying bootstrap resources") 461 return nil 462 } 463 464 type machineManifest struct { 465 Status struct { 466 Addresses []clusterv1.MachineAddress `yaml:"addresses"` 467 } `yaml:"status"` 468 } 469 470 // extractIPAddress extracts the IP address from a machine manifest file in a 471 // provider-agnostic way by reading only the "status" stanza, which should be 472 // present in all providers. 473 func extractIPAddress(manifestPath string) ([]string, error) { 474 data, err := os.ReadFile(manifestPath) 475 if err != nil { 476 return []string{}, fmt.Errorf("failed to read machine manifest %s: %w", manifestPath, err) 477 } 478 var manifest machineManifest 479 if err := yaml.Unmarshal(data, &manifest); err != nil { 480 return []string{}, fmt.Errorf("failed to unmarshal manifest %s: %w", manifestPath, err) 481 } 482 483 var externalIPAddrs []string 484 var internalIPAddrs []string 485 for _, addr := range manifest.Status.Addresses { 486 switch addr.Type { 487 case clusterv1.MachineExternalIP: 488 externalIPAddrs = append(externalIPAddrs, addr.Address) 489 case clusterv1.MachineInternalIP: 490 internalIPAddrs = append(internalIPAddrs, addr.Address) 491 default: 492 continue 493 } 494 } 495 496 // prioritize the external address in the front of the list 497 externalIPAddrs = append(externalIPAddrs, internalIPAddrs...) 498 499 return externalIPAddrs, nil 500 } 501 502 // ExtractHostAddresses extracts the IPs of the bootstrap and control plane machines. 503 func (i *InfraProvider) ExtractHostAddresses(dir string, config *types.InstallConfig, ha *infrastructure.HostAddresses) error { 504 manifestsDir := filepath.Join(dir, clusterapi.ArtifactsDir) 505 logrus.Debugf("Looking for machine manifests in %s", manifestsDir) 506 507 addr, err := i.getBootstrapAddress(config, manifestsDir) 508 if err != nil { 509 return fmt.Errorf("failed to get bootstrap address: %w", err) 510 } 511 ha.Bootstrap = addr 512 513 masterFiles, err := filepath.Glob(filepath.Join(manifestsDir, "Machine\\-openshift\\-cluster\\-api\\-guests\\-*\\-master\\-?.yaml")) 514 if err != nil { 515 return fmt.Errorf("failed to list master machine manifests: %w", err) 516 } 517 logrus.Debugf("master machine manifests found: %v", masterFiles) 518 519 if replicas := int(*config.ControlPlane.Replicas); replicas != len(masterFiles) { 520 logrus.Warnf("not all master manifests found: %d. Expected %d.", len(masterFiles), replicas) 521 } 522 for _, manifest := range masterFiles { 523 addrs, err := extractIPAddress(manifest) 524 if err != nil { 525 // Log the error but keep parsing the remaining files 526 logrus.Warnf("failed to extract IP address for %s: %v", manifest, err) 527 continue 528 } 529 logrus.Debugf("found master address: %s", addrs) 530 531 ha.Masters = append(ha.Masters, prioritizeIPv4(config, addrs)) 532 } 533 534 return nil 535 } 536 537 func (i *InfraProvider) getBootstrapAddress(config *types.InstallConfig, manifestsDir string) (string, error) { 538 // If the bootstrap node cannot have a public IP address, we 539 // SSH through the load balancer, as is this case on Azure. 540 if i.impl.PublicGatherEndpoint() == APILoadBalancer && config.Publish != types.InternalPublishingStrategy { 541 return fmt.Sprintf("api.%s", config.ClusterDomain()), nil 542 } 543 544 bootstrapFiles, err := filepath.Glob(filepath.Join(manifestsDir, "Machine\\-openshift\\-cluster\\-api\\-guests\\-*\\-bootstrap.yaml")) 545 if err != nil { 546 return "", fmt.Errorf("failed to list bootstrap manifests: %w", err) 547 } 548 logrus.Debugf("bootstrap manifests found: %v", bootstrapFiles) 549 550 if len(bootstrapFiles) != 1 { 551 return "", fmt.Errorf("wrong number of bootstrap manifests found: %v. Expected exactly one", bootstrapFiles) 552 } 553 addrs, err := extractIPAddress(bootstrapFiles[0]) 554 if err != nil { 555 return "", fmt.Errorf("failed to extract IP address for bootstrap: %w", err) 556 } 557 logrus.Debugf("found bootstrap address: %s", addrs) 558 return prioritizeIPv4(config, addrs), nil 559 } 560 561 // IgnitionSecret provides the basic formatting for creating the 562 // ignition secret. 563 func IgnitionSecret(ign []byte, infraID, role string) *corev1.Secret { 564 secret := &corev1.Secret{ 565 ObjectMeta: metav1.ObjectMeta{ 566 Name: fmt.Sprintf("%s-%s", infraID, role), 567 Namespace: capiutils.Namespace, 568 Labels: map[string]string{ 569 "cluster.x-k8s.io/cluster-name": infraID, 570 }, 571 }, 572 Data: map[string][]byte{ 573 "format": []byte("ignition"), 574 "value": ign, 575 }, 576 } 577 secret.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Secret")) 578 return secret 579 } 580 581 func (i *InfraProvider) collectManifests(ctx context.Context, cl client.Client) ([]*asset.File, []error) { 582 logrus.Debug("Collecting applied cluster api manifests...") 583 errorList := []error{} 584 fileList := []*asset.File{} 585 for _, m := range i.appliedManifests { 586 key := client.ObjectKey{ 587 Name: m.GetName(), 588 Namespace: m.GetNamespace(), 589 } 590 if err := cl.Get(ctx, key, m); err != nil { 591 errorList = append(errorList, fmt.Errorf("failed to get manifest %s: %w", m.GetName(), err)) 592 continue 593 } 594 595 gvk, err := cl.GroupVersionKindFor(m) 596 if err != nil { 597 errorList = append(errorList, fmt.Errorf("failed to get GVK for manifest %s: %w", m.GetName(), err)) 598 continue 599 } 600 fileName := filepath.Join(clusterapi.ArtifactsDir, fmt.Sprintf("%s-%s-%s.yaml", gvk.Kind, m.GetNamespace(), m.GetName())) 601 objData, err := yaml.Marshal(m) 602 if err != nil { 603 errorList = append(errorList, fmt.Errorf("failed to marshal manifest %s: %w", fileName, err)) 604 continue 605 } 606 fileList = append(fileList, &asset.File{ 607 Filename: fileName, 608 Data: objData, 609 }) 610 } 611 return fileList, errorList 612 } 613 614 func checkMachineReady(machine *clusterv1.Machine, requirePublicIP bool) (bool, error) { 615 logrus.Debugf("Checking that machine %s has provisioned...", machine.Name) 616 if machine.Status.Phase != string(clusterv1.MachinePhaseProvisioned) && 617 machine.Status.Phase != string(clusterv1.MachinePhaseRunning) { 618 logrus.Debugf("Machine %s has not yet provisioned: %s", machine.Name, machine.Status.Phase) 619 return false, nil 620 } else if machine.Status.Phase == string(clusterv1.MachinePhaseFailed) { 621 msg := ptr.Deref(machine.Status.FailureMessage, "machine.Status.FailureMessage was not set") 622 return false, fmt.Errorf("machine %s failed to provision: %s", machine.Name, msg) 623 } 624 logrus.Debugf("Machine %s has status: %s", machine.Name, machine.Status.Phase) 625 return hasRequiredIP(machine, requirePublicIP), nil 626 } 627 628 func hasRequiredIP(machine *clusterv1.Machine, requirePublicIP bool) bool { 629 logrus.Debugf("Checking that IP addresses are populated in the status of machine %s...", machine.Name) 630 631 for _, addr := range machine.Status.Addresses { 632 switch { 633 case len(addr.Address) == 0: 634 continue 635 case addr.Type == clusterv1.MachineExternalIP: 636 logrus.Debugf("Found external IP address: %s", addr.Address) 637 return true 638 case addr.Type == clusterv1.MachineInternalIP && !requirePublicIP: 639 logrus.Debugf("Found internal IP address: %s", addr.Address) 640 return true 641 } 642 logrus.Debugf("Checked IP %s: %s", addr.Type, addr.Address) 643 } 644 logrus.Debugf("Still waiting for machine %s to get required IPs", machine.Name) 645 return false 646 }