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  }