github.com/openshift/installer@v1.4.17/pkg/asset/agent/image/ignition.go (about)

     1  package image
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"net"
     8  	"net/url"
     9  	"path"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/coreos/ignition/v2/config/util"
    14  	igntypes "github.com/coreos/ignition/v2/config/v3_2/types"
    15  	"github.com/coreos/stream-metadata-go/arch"
    16  	"github.com/coreos/stream-metadata-go/stream"
    17  	"github.com/pkg/errors"
    18  	"github.com/sirupsen/logrus"
    19  	"gopkg.in/yaml.v2"
    20  
    21  	hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1"
    22  	"github.com/openshift/assisted-service/api/v1beta1"
    23  	"github.com/openshift/assisted-service/models"
    24  	"github.com/openshift/installer/pkg/asset"
    25  	agentcommon "github.com/openshift/installer/pkg/asset/agent"
    26  	"github.com/openshift/installer/pkg/asset/agent/agentconfig"
    27  	"github.com/openshift/installer/pkg/asset/agent/common"
    28  	"github.com/openshift/installer/pkg/asset/agent/gencrypto"
    29  	"github.com/openshift/installer/pkg/asset/agent/joiner"
    30  	"github.com/openshift/installer/pkg/asset/agent/manifests"
    31  	"github.com/openshift/installer/pkg/asset/agent/mirror"
    32  	"github.com/openshift/installer/pkg/asset/agent/workflow"
    33  	"github.com/openshift/installer/pkg/asset/ignition"
    34  	"github.com/openshift/installer/pkg/asset/ignition/bootstrap"
    35  	"github.com/openshift/installer/pkg/asset/password"
    36  	"github.com/openshift/installer/pkg/asset/tls"
    37  	"github.com/openshift/installer/pkg/types"
    38  	"github.com/openshift/installer/pkg/types/agent"
    39  	"github.com/openshift/installer/pkg/version"
    40  )
    41  
    42  const addNodesEnvPath = "/etc/assisted/add-nodes.env"
    43  const rendezvousHostEnvPath = "/etc/assisted/rendezvous-host.env"
    44  const manifestPath = "/etc/assisted/manifests"
    45  const hostnamesPath = "/etc/assisted/hostnames"
    46  const nmConnectionsPath = "/etc/assisted/network"
    47  const extraManifestPath = "/etc/assisted/extra-manifests"
    48  const registriesConfPath = "/etc/containers/registries.conf"
    49  const registryCABundlePath = "/etc/pki/ca-trust/source/anchors/domain.crt"
    50  const clusterConfigPath = "/etc/assisted/clusterconfig"
    51  
    52  // Ignition is an asset that generates the agent installer ignition file.
    53  type Ignition struct {
    54  	Config       *igntypes.Config
    55  	CPUArch      string
    56  	RendezvousIP string
    57  }
    58  
    59  // agentTemplateData is the data used to replace values in agent template
    60  // files.
    61  type agentTemplateData struct {
    62  	ServiceProtocol           string
    63  	PullSecret                string
    64  	ControlPlaneAgents        int
    65  	WorkerAgents              int
    66  	ReleaseImages             string
    67  	ReleaseImage              string
    68  	ReleaseImageMirror        string
    69  	HaveMirrorConfig          bool
    70  	PublicContainerRegistries string
    71  	InfraEnvID                string
    72  	ClusterName               string
    73  	OSImage                   *models.OsImage
    74  	Proxy                     *v1beta1.Proxy
    75  	ConfigImageFiles          string
    76  	ImageTypeISO              string
    77  	PublicKeyPEM              string
    78  	Token                     string
    79  	TokenExpiry               string
    80  	AuthType                  string
    81  	CaBundleMount             string
    82  }
    83  
    84  // Name returns the human-friendly name of the asset.
    85  func (a *Ignition) Name() string {
    86  	return "Agent Installer Ignition"
    87  }
    88  
    89  // Dependencies returns the assets on which the Ignition asset depends.
    90  func (a *Ignition) Dependencies() []asset.Asset {
    91  	return []asset.Asset{
    92  		&workflow.AgentWorkflow{},
    93  		&joiner.ClusterInfo{},
    94  		&joiner.AddNodesConfig{},
    95  		&manifests.AgentManifests{},
    96  		&manifests.ExtraManifests{},
    97  		&tls.KubeAPIServerLBSignerCertKey{},
    98  		&tls.KubeAPIServerLocalhostSignerCertKey{},
    99  		&tls.KubeAPIServerServiceNetworkSignerCertKey{},
   100  		&tls.AdminKubeConfigSignerCertKey{},
   101  		&password.KubeadminPassword{},
   102  		&agentconfig.AgentConfig{},
   103  		&agentconfig.AgentHosts{},
   104  		&mirror.RegistriesConf{},
   105  		&mirror.CaBundle{},
   106  		&gencrypto.AuthConfig{},
   107  		&common.InfraEnvID{},
   108  	}
   109  }
   110  
   111  // Generate generates the agent installer ignition.
   112  func (a *Ignition) Generate(_ context.Context, dependencies asset.Parents) error {
   113  	agentWorkflow := &workflow.AgentWorkflow{}
   114  	clusterInfo := &joiner.ClusterInfo{}
   115  	addNodesConfig := &joiner.AddNodesConfig{}
   116  	agentManifests := &manifests.AgentManifests{}
   117  	agentConfigAsset := &agentconfig.AgentConfig{}
   118  	agentHostsAsset := &agentconfig.AgentHosts{}
   119  	extraManifests := &manifests.ExtraManifests{}
   120  	authConfig := &gencrypto.AuthConfig{}
   121  	infraEnvAsset := &common.InfraEnvID{}
   122  	dependencies.Get(agentManifests, agentConfigAsset, agentHostsAsset, extraManifests, authConfig, agentWorkflow, clusterInfo, addNodesConfig, infraEnvAsset)
   123  
   124  	pwd := &password.KubeadminPassword{}
   125  	dependencies.Get(pwd)
   126  	pwdHash := string(pwd.PasswordHash)
   127  
   128  	infraEnv := agentManifests.InfraEnv
   129  
   130  	config := igntypes.Config{
   131  		Ignition: igntypes.Ignition{
   132  			Version: igntypes.MaxVersion.String(),
   133  		},
   134  		Passwd: igntypes.Passwd{
   135  			Users: []igntypes.PasswdUser{
   136  				{
   137  					Name: "core",
   138  					SSHAuthorizedKeys: []igntypes.SSHAuthorizedKey{
   139  						igntypes.SSHAuthorizedKey(infraEnv.Spec.SSHAuthorizedKey),
   140  					},
   141  					PasswordHash: &pwdHash,
   142  				},
   143  			},
   144  		},
   145  	}
   146  
   147  	clusterName := ""
   148  	imageTypeISO := "full-iso"
   149  	numMasters := 0
   150  	numWorkers := 0
   151  	enabledServices := getDefaultEnabledServices()
   152  	openshiftVersion := ""
   153  	var err error
   154  	var streamGetter CoreOSBuildFetcher
   155  
   156  	switch agentWorkflow.Workflow {
   157  	case workflow.AgentWorkflowTypeInstall:
   158  		// Set rendezvous IP.
   159  		nodeZeroIP, err := RetrieveRendezvousIP(agentConfigAsset.Config, agentHostsAsset.Hosts, agentManifests.NMStateConfigs)
   160  		if err != nil {
   161  			return err
   162  		}
   163  		a.RendezvousIP = nodeZeroIP
   164  		logrus.Infof("The rendezvous host IP (node0 IP) is %s", a.RendezvousIP)
   165  		// Define cluster name and image type.
   166  		clusterName = fmt.Sprintf("%s.%s", agentManifests.ClusterDeployment.Spec.ClusterName, agentManifests.ClusterDeployment.Spec.BaseDomain)
   167  		if agentManifests.AgentClusterInstall.Spec.PlatformType == hiveext.ExternalPlatformType {
   168  			imageTypeISO = "minimal-iso"
   169  		}
   170  		// Fetch the required number of master and worker nodes.
   171  		numMasters = agentManifests.AgentClusterInstall.Spec.ProvisionRequirements.ControlPlaneAgents
   172  		numWorkers = agentManifests.AgentClusterInstall.Spec.ProvisionRequirements.WorkerAgents
   173  		// Enable specific install services
   174  		enabledServices = append(enabledServices, "start-cluster-installation.service")
   175  		// Version is retrieved from the embedded data
   176  		openshiftVersion, err = version.Version()
   177  		if err != nil {
   178  			return err
   179  		}
   180  		streamGetter = DefaultCoreOSStreamGetter
   181  
   182  	case workflow.AgentWorkflowTypeAddNodes:
   183  		// In the add-nodes workflow, every node will act independently from the others.
   184  		a.RendezvousIP = "127.0.0.1"
   185  		// Reuse the existing cluster name.
   186  		clusterName = clusterInfo.ClusterName
   187  		// Fetch the required number of master and worker nodes. Currently only adding workers
   188  		// is supported, so forcing the expected number of masters to zero, and assuming implcitly
   189  		// that all the hosts defined are workers.
   190  		numMasters = 0
   191  		numWorkers = len(addNodesConfig.Config.Hosts)
   192  
   193  		// Enable add-nodes specific services
   194  		enabledServices = append(enabledServices, "agent-add-node.service")
   195  		// Generate add-nodes.env file
   196  		addNodesEnvFile := ignition.FileFromString(addNodesEnvPath, "root", 0644, getAddNodesEnv(*clusterInfo, authConfig.AgentAuthTokenExpiry))
   197  		config.Storage.Files = append(config.Storage.Files, addNodesEnvFile)
   198  
   199  		// Enable auth token service
   200  		enabledServices = append(enabledServices, "agent-auth-token-status.service")
   201  
   202  		// Version matches the source cluster one
   203  		openshiftVersion = clusterInfo.Version
   204  		streamGetter = func(ctx context.Context) (*stream.Stream, error) {
   205  			return clusterInfo.OSImage, nil
   206  		}
   207  		// If defined, add the ignition endpoints
   208  		if err := addDay2IgnitionEndpoints(&config, *clusterInfo); err != nil {
   209  			return err
   210  		}
   211  
   212  	default:
   213  		return fmt.Errorf("AgentWorkflowType value not supported: %s", agentWorkflow.Workflow)
   214  	}
   215  
   216  	// Default to x86_64
   217  	archName := arch.RpmArch(types.ArchitectureAMD64)
   218  	if infraEnv.Spec.CpuArchitecture != "" {
   219  		archName = infraEnv.Spec.CpuArchitecture
   220  	}
   221  	// Examine the release payload to see if its multi
   222  	releaseArch, err := agentcommon.DetermineReleaseImageArch(agentManifests.GetPullSecretData(), agentManifests.ClusterImageSet.Spec.ReleaseImage)
   223  	if err != nil {
   224  		logrus.Warnf("Unable to validate the release image architecture, using infraEnv.Spec.CpuArchitecture for the release image arch")
   225  		releaseArch = archName
   226  	} else {
   227  		releaseArch = arch.RpmArch(releaseArch)
   228  		logrus.Debugf("Found Release Image Architecture: %s", releaseArch)
   229  	}
   230  	releaseArchs := []string{releaseArch}
   231  	if releaseArch == "multi" {
   232  		releaseArchs = []string{arch.RpmArch(types.ArchitectureARM64), arch.RpmArch(types.ArchitectureAMD64), arch.RpmArch(types.ArchitecturePPC64LE), arch.RpmArch(types.ArchitectureS390X)}
   233  	}
   234  	releaseImageList, err := releaseImageListWithVersion(agentManifests.ClusterImageSet.Spec.ReleaseImage, releaseArch, releaseArchs, openshiftVersion)
   235  	if err != nil {
   236  		return err
   237  	}
   238  
   239  	registriesConfig := &mirror.RegistriesConf{}
   240  	registryCABundle := &mirror.CaBundle{}
   241  	dependencies.Get(registriesConfig, registryCABundle)
   242  
   243  	publicContainerRegistries := getPublicContainerRegistries(registriesConfig)
   244  
   245  	releaseImageMirror := mirror.GetMirrorFromRelease(agentManifests.ClusterImageSet.Spec.ReleaseImage, registriesConfig)
   246  
   247  	infraEnvID := infraEnvAsset.ID
   248  	logrus.Debug("Generated random infra-env id ", infraEnvID)
   249  
   250  	osImage, err := getOSImagesInfo(archName, openshiftVersion, streamGetter)
   251  	if err != nil {
   252  		return err
   253  	}
   254  	a.CPUArch = *osImage.CPUArchitecture
   255  
   256  	caBundleMount := defineCABundleMount(registriesConfig, registryCABundle)
   257  	agentTemplateData := getTemplateData(
   258  		clusterName,
   259  		agentManifests.GetPullSecretData(),
   260  		releaseImageList,
   261  		agentManifests.ClusterImageSet.Spec.ReleaseImage,
   262  		releaseImageMirror,
   263  		publicContainerRegistries,
   264  		imageTypeISO,
   265  		infraEnvID,
   266  		authConfig.PublicKey,
   267  		authConfig.AuthType,
   268  		authConfig.AgentAuthToken,
   269  		authConfig.AgentAuthTokenExpiry,
   270  		caBundleMount,
   271  		len(registriesConfig.MirrorConfig) > 0,
   272  		numMasters, numWorkers,
   273  		osImage,
   274  		infraEnv.Spec.Proxy,
   275  	)
   276  
   277  	err = bootstrap.AddStorageFiles(&config, "/", "agent/files", agentTemplateData)
   278  	if err != nil {
   279  		return err
   280  	}
   281  
   282  	rendezvousHostFile := ignition.FileFromString(rendezvousHostEnvPath,
   283  		"root", 0644,
   284  		getRendezvousHostEnv(agentTemplateData.ServiceProtocol, a.RendezvousIP, authConfig.AgentAuthToken, agentWorkflow.Workflow))
   285  	config.Storage.Files = append(config.Storage.Files, rendezvousHostFile)
   286  
   287  	err = addBootstrapScripts(&config, agentManifests.ClusterImageSet.Spec.ReleaseImage)
   288  	if err != nil {
   289  		return err
   290  	}
   291  
   292  	// add ZTP manifests to manifestPath
   293  	for _, file := range agentManifests.FileList {
   294  		manifestFile := ignition.FileFromBytes(filepath.Join(manifestPath, filepath.Base(file.Filename)),
   295  			"root", 0600, file.Data)
   296  		config.Storage.Files = append(config.Storage.Files, manifestFile)
   297  	}
   298  
   299  	// add AgentConfig if provided
   300  	if agentConfigAsset.Config != nil {
   301  		agentConfigFile := ignition.FileFromBytes(filepath.Join(manifestPath, filepath.Base(agentConfigAsset.File.Filename)),
   302  			"root", 0600, agentConfigAsset.File.Data)
   303  		config.Storage.Files = append(config.Storage.Files, agentConfigFile)
   304  	}
   305  
   306  	addMacAddressToHostnameMappings(&config, agentHostsAsset)
   307  
   308  	err = addStaticNetworkConfig(&config, agentManifests.StaticNetworkConfigs)
   309  	if err != nil {
   310  		return err
   311  	}
   312  
   313  	// Enable pre-network-manager-config.service only when there are network configs defined
   314  	if len(agentManifests.StaticNetworkConfigs) != 0 {
   315  		enabledServices = append(enabledServices, "pre-network-manager-config.service")
   316  	}
   317  
   318  	err = bootstrap.AddSystemdUnits(&config, "agent/systemd/units", agentTemplateData, enabledServices)
   319  	if err != nil {
   320  		return err
   321  	}
   322  
   323  	addTLSData(&config, dependencies)
   324  
   325  	addMirrorData(&config, registriesConfig, registryCABundle)
   326  
   327  	err = addHostConfig(&config, agentHostsAsset)
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	err = addExtraManifests(&config, extraManifests)
   333  	if err != nil {
   334  		return err
   335  	}
   336  
   337  	a.Config = &config
   338  	return nil
   339  }
   340  
   341  func getDefaultEnabledServices() []string {
   342  	return []string{
   343  		"agent-interactive-console.service",
   344  		"agent-interactive-console-serial@.service",
   345  		"agent-register-cluster.service",
   346  		"agent-import-cluster.service",
   347  		"agent-register-infraenv.service",
   348  		"agent.service",
   349  		"assisted-service-db.service",
   350  		"assisted-service-pod.service",
   351  		"assisted-service.service",
   352  		"node-zero.service",
   353  		"multipathd.service",
   354  		"selinux.service",
   355  		"install-status.service",
   356  		"set-hostname.service",
   357  	}
   358  }
   359  
   360  func addBootstrapScripts(config *igntypes.Config, releaseImage string) (err error) {
   361  	// Set up bootstrap service recording
   362  	if err := bootstrap.AddStorageFiles(config,
   363  		"/usr/local/bin/bootstrap-service-record.sh",
   364  		"bootstrap/files/usr/local/bin/bootstrap-service-record.sh",
   365  		nil); err != nil {
   366  		return err
   367  	}
   368  
   369  	// Use bootstrap script to get container images
   370  	relImgData := struct{ ReleaseImage string }{
   371  		ReleaseImage: releaseImage,
   372  	}
   373  	for _, script := range []string{"release-image.sh", "release-image-download.sh"} {
   374  		if err := bootstrap.AddStorageFiles(config,
   375  			"/usr/local/bin/"+script,
   376  			"bootstrap/files/usr/local/bin/"+script+".template",
   377  			relImgData); err != nil {
   378  			return err
   379  		}
   380  	}
   381  	return nil
   382  }
   383  
   384  func getTemplateData(name, pullSecret, releaseImageList, releaseImage, releaseImageMirror, publicContainerRegistries,
   385  	imageTypeISO, infraEnvID, publicKey, authType, token, tokenExpiry, caBundleMount string,
   386  	haveMirrorConfig bool,
   387  	numMasters, numWorkers int,
   388  	osImage *models.OsImage,
   389  	proxy *v1beta1.Proxy) *agentTemplateData {
   390  	return &agentTemplateData{
   391  		ServiceProtocol:           "http",
   392  		PullSecret:                pullSecret,
   393  		ControlPlaneAgents:        numMasters,
   394  		WorkerAgents:              numWorkers,
   395  		ReleaseImages:             releaseImageList,
   396  		ReleaseImage:              releaseImage,
   397  		ReleaseImageMirror:        releaseImageMirror,
   398  		HaveMirrorConfig:          haveMirrorConfig,
   399  		PublicContainerRegistries: publicContainerRegistries,
   400  		InfraEnvID:                infraEnvID,
   401  		ClusterName:               name,
   402  		OSImage:                   osImage,
   403  		Proxy:                     proxy,
   404  		ImageTypeISO:              imageTypeISO,
   405  		PublicKeyPEM:              publicKey,
   406  		AuthType:                  authType,
   407  		Token:                     token,
   408  		TokenExpiry:               tokenExpiry,
   409  		CaBundleMount:             caBundleMount,
   410  	}
   411  }
   412  
   413  func getRendezvousHostEnv(serviceProtocol, nodeZeroIP, token string, workflowType workflow.AgentWorkflowType) string {
   414  	serviceBaseURL := url.URL{
   415  		Scheme: serviceProtocol,
   416  		Host:   net.JoinHostPort(nodeZeroIP, "8090"),
   417  		Path:   "/",
   418  	}
   419  	imageServiceBaseURL := url.URL{
   420  		Scheme: serviceProtocol,
   421  		Host:   net.JoinHostPort(nodeZeroIP, "8888"),
   422  		Path:   "/",
   423  	}
   424  	// AGENT_AUTH_TOKEN is required to authenticate API requests against agent-installer-local auth type.
   425  	// PULL_SECRET_TOKEN contains the same value as AGENT_AUTH_TOKEN. The name PULL_SECRET_TOKEN is used in
   426  	// assisted-installer-agent, which is responsible for authenticating API requests related to agents.
   427  	// Historically, PULL_SECRET_TOKEN was used solely to store the pull secrets.
   428  	// However, as the authentication mechanisms have evolved, PULL_SECRET_TOKEN now
   429  	// stores a JWT (JSON Web Token) in the context of local authentication.
   430  	// Consequently, PULL_SECRET_TOKEN must be set with the value of AGENT_AUTH_TOKEN to maintain compatibility
   431  	// and ensure successful authentication.
   432  	// In the absence of PULL_SECRET_TOKEN, the cluster installation will wait forever.
   433  
   434  	return fmt.Sprintf(`NODE_ZERO_IP=%s
   435  SERVICE_BASE_URL=%s
   436  IMAGE_SERVICE_BASE_URL=%s
   437  AGENT_AUTH_TOKEN=%s
   438  PULL_SECRET_TOKEN=%s
   439  WORKFLOW_TYPE=%s
   440  `, nodeZeroIP, serviceBaseURL.String(), imageServiceBaseURL.String(), token, token, workflowType)
   441  }
   442  
   443  func getAddNodesEnv(clusterInfo joiner.ClusterInfo, authTokenExpiry string) string {
   444  	return fmt.Sprintf(`CLUSTER_ID=%s
   445  CLUSTER_NAME=%s
   446  CLUSTER_API_VIP_DNS_NAME=%s
   447  AGENT_AUTH_TOKEN_EXPIRY=%s
   448  `, clusterInfo.ClusterID, clusterInfo.ClusterName, clusterInfo.APIDNSName, authTokenExpiry)
   449  }
   450  
   451  func addStaticNetworkConfig(config *igntypes.Config, staticNetworkConfig []*models.HostStaticNetworkConfig) (err error) {
   452  	if len(staticNetworkConfig) == 0 {
   453  		return nil
   454  	}
   455  
   456  	// Get the static network configuration from nmstate and generate NetworkManager ignition files
   457  	filesList, err := manifests.GetNMIgnitionFiles(staticNetworkConfig)
   458  	if err != nil {
   459  		return err
   460  	}
   461  
   462  	for i := range filesList {
   463  		nmFilePath := path.Join(nmConnectionsPath, filesList[i].FilePath)
   464  		nmStateIgnFile := ignition.FileFromBytes(nmFilePath, "root", 0600, []byte(filesList[i].FileContents))
   465  		config.Storage.Files = append(config.Storage.Files, nmStateIgnFile)
   466  	}
   467  
   468  	nmStateScriptFilePath := "/usr/local/bin/pre-network-manager-config.sh"
   469  	// A local version of the assisted-service internal script is currently used
   470  	nmStateScript := ignition.FileFromBytes(nmStateScriptFilePath, "root", 0755, []byte(manifests.PreNetworkConfigScript))
   471  	config.Storage.Files = append(config.Storage.Files, nmStateScript)
   472  
   473  	return nil
   474  }
   475  
   476  func addTLSData(config *igntypes.Config, dependencies asset.Parents) {
   477  	certKeys := []asset.Asset{
   478  		&tls.KubeAPIServerLBSignerCertKey{},
   479  		&tls.KubeAPIServerLocalhostSignerCertKey{},
   480  		&tls.KubeAPIServerServiceNetworkSignerCertKey{},
   481  		&tls.AdminKubeConfigSignerCertKey{},
   482  	}
   483  	dependencies.Get(certKeys...)
   484  
   485  	for _, ck := range certKeys {
   486  		for _, d := range ck.(asset.WritableAsset).Files() {
   487  			f := ignition.FileFromBytes(path.Join("/opt/agent", d.Filename), "root", 0600, d.Data)
   488  			config.Storage.Files = append(config.Storage.Files, f)
   489  		}
   490  	}
   491  
   492  	pwd := &password.KubeadminPassword{}
   493  	dependencies.Get(pwd)
   494  	config.Storage.Files = append(config.Storage.Files,
   495  		ignition.FileFromBytes("/opt/agent/tls/kubeadmin-password.hash", "root", 0600, pwd.PasswordHash))
   496  }
   497  
   498  func addMirrorData(config *igntypes.Config, registriesConfig *mirror.RegistriesConf, registryCABundle *mirror.CaBundle) {
   499  
   500  	// This is required for assisted-service to build the ICSP for openshift-install
   501  	if registriesConfig.File != nil {
   502  		registriesFile := ignition.FileFromBytes(registriesConfPath,
   503  			"root", 0644, registriesConfig.File.Data)
   504  		config.Storage.Files = append(config.Storage.Files, registriesFile)
   505  	}
   506  
   507  	// This is required for the agent to run the podman commands to the mirror
   508  	if registryCABundle.File != nil && len(registryCABundle.File.Data) > 0 {
   509  		caFile := ignition.FileFromBytes(registryCABundlePath,
   510  			"root", 0600, registryCABundle.File.Data)
   511  		config.Storage.Files = append(config.Storage.Files, caFile)
   512  	}
   513  }
   514  
   515  func defineCABundleMount(registriesConfig *mirror.RegistriesConf, registryCABundle *mirror.CaBundle) string {
   516  	// By default, the current host CA bundle is used (it will also contain eventually a user CA bundle, if
   517  	// defined in the AdditionalTrustBundle field of install-config.yaml).
   518  	hostSourceCABundle := "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem"
   519  
   520  	// If mirror registry is configured and the user provided a bundle, then let's mount just the user one.
   521  	if len(registriesConfig.MirrorConfig) > 0 && registryCABundle.File != nil && len(registryCABundle.File.Data) > 0 {
   522  		hostSourceCABundle = registryCABundlePath
   523  	}
   524  
   525  	return fmt.Sprintf("-v %s:/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem:z", hostSourceCABundle)
   526  }
   527  
   528  // Creates a file named with a host's MAC address. The desired hostname
   529  // is the file's content. The files are read by a systemd service that
   530  // sets the hostname using "hostnamectl set-hostname" when the ISO boots.
   531  func addMacAddressToHostnameMappings(
   532  	config *igntypes.Config,
   533  	agentHostsAsset *agentconfig.AgentHosts) {
   534  	if len(agentHostsAsset.Hosts) == 0 {
   535  		return
   536  	}
   537  	for _, host := range agentHostsAsset.Hosts {
   538  		if host.Hostname != "" {
   539  			file := ignition.FileFromBytes(filepath.Join(hostnamesPath,
   540  				strings.ToLower(filepath.Base(host.Interfaces[0].MacAddress))),
   541  				"root", 0600, []byte(host.Hostname))
   542  			config.Storage.Files = append(config.Storage.Files, file)
   543  		}
   544  	}
   545  }
   546  
   547  func addHostConfig(config *igntypes.Config, agentHosts *agentconfig.AgentHosts) error {
   548  	confs, err := agentHosts.HostConfigFiles()
   549  	if err != nil {
   550  		return err
   551  	}
   552  
   553  	for path, content := range confs {
   554  		hostConfigFile := ignition.FileFromBytes(filepath.Join("/etc/assisted/hostconfig", path), "root", 0644, content)
   555  		config.Storage.Files = append(config.Storage.Files, hostConfigFile)
   556  	}
   557  	return nil
   558  }
   559  
   560  func addDay2IgnitionEndpoints(config *igntypes.Config, clusterInfo joiner.ClusterInfo) error {
   561  	if clusterInfo.IgnitionEndpointWorker == nil {
   562  		return nil
   563  	}
   564  
   565  	user := "root"
   566  	mode := 0644
   567  	config.Storage.Directories = append(config.Storage.Directories, igntypes.Directory{
   568  		Node: igntypes.Node{
   569  			Path: clusterConfigPath,
   570  			User: igntypes.NodeUser{
   571  				Name: &user,
   572  			},
   573  			Overwrite: util.BoolToPtr(true),
   574  		},
   575  		DirectoryEmbedded1: igntypes.DirectoryEmbedded1{
   576  			Mode: &mode,
   577  		},
   578  	})
   579  
   580  	workerIgnitionBytes, err := json.Marshal(clusterInfo.IgnitionEndpointWorker)
   581  	if err != nil {
   582  		return err
   583  	}
   584  	workerIgnitionFile := ignition.FileFromBytes(path.Join(clusterConfigPath, "worker-ignition-endpoint.json"), user, mode, workerIgnitionBytes)
   585  	config.Storage.Files = append(config.Storage.Files, workerIgnitionFile)
   586  	return nil
   587  }
   588  
   589  func addExtraManifests(config *igntypes.Config, extraManifests *manifests.ExtraManifests) error {
   590  
   591  	user := "root"
   592  	mode := 0644
   593  
   594  	config.Storage.Directories = append(config.Storage.Directories, igntypes.Directory{
   595  		Node: igntypes.Node{
   596  			Path: extraManifestPath,
   597  			User: igntypes.NodeUser{
   598  				Name: &user,
   599  			},
   600  			Overwrite: util.BoolToPtr(true),
   601  		},
   602  		DirectoryEmbedded1: igntypes.DirectoryEmbedded1{
   603  			Mode: &mode,
   604  		},
   605  	})
   606  
   607  	for _, file := range extraManifests.FileList {
   608  
   609  		type unstructured map[string]interface{}
   610  
   611  		yamlList, err := manifests.GetMultipleYamls[unstructured](file.Data)
   612  		if err != nil {
   613  			return errors.Wrapf(err, "could not decode YAML for %s", file.Filename)
   614  		}
   615  
   616  		for n, manifest := range yamlList {
   617  			m, err := yaml.Marshal(manifest)
   618  			if err != nil {
   619  				return err
   620  			}
   621  
   622  			base := filepath.Base(file.Filename)
   623  			ext := filepath.Ext(file.Filename)
   624  			baseWithoutExt := strings.TrimSuffix(base, ext)
   625  			baseFileName := filepath.Join(extraManifestPath, baseWithoutExt)
   626  			fileName := fmt.Sprintf("%s-%d%s", baseFileName, n, ext)
   627  
   628  			extraFile := ignition.FileFromBytes(fileName, user, mode, m)
   629  			config.Storage.Files = append(config.Storage.Files, extraFile)
   630  		}
   631  	}
   632  
   633  	return nil
   634  }
   635  
   636  func getOSImagesInfo(cpuArch string, openshiftVersion string, streamGetter CoreOSBuildFetcher) (*models.OsImage, error) {
   637  	st, err := streamGetter(context.Background())
   638  	if err != nil {
   639  		return nil, err
   640  	}
   641  
   642  	osImage := &models.OsImage{
   643  		CPUArchitecture: &cpuArch,
   644  	}
   645  	osImage.OpenshiftVersion = &openshiftVersion
   646  
   647  	streamArch, err := st.GetArchitecture(cpuArch)
   648  	if err != nil {
   649  		return nil, err
   650  	}
   651  
   652  	artifacts, ok := streamArch.Artifacts["metal"]
   653  	if !ok {
   654  		return nil, fmt.Errorf("failed to retrieve coreos metal info for architecture %s", cpuArch)
   655  	}
   656  	osImage.Version = &artifacts.Release
   657  
   658  	isoFormat, ok := artifacts.Formats["iso"]
   659  	if !ok {
   660  		return nil, fmt.Errorf("failed to retrieve coreos ISO info for architecture %s", cpuArch)
   661  	}
   662  	osImage.URL = &isoFormat.Disk.Location
   663  
   664  	return osImage, nil
   665  }
   666  
   667  // RetrieveRendezvousIP Returns the Rendezvous IP from either AgentConfig or NMStateConfig
   668  func RetrieveRendezvousIP(agentConfig *agent.Config, hosts []agent.Host, nmStateConfigs []*v1beta1.NMStateConfig) (string, error) {
   669  	var err error
   670  	var rendezvousIP string
   671  
   672  	if agentConfig != nil && agentConfig.RendezvousIP != "" {
   673  		rendezvousIP = agentConfig.RendezvousIP
   674  		logrus.Debug("RendezvousIP from the AgentConfig ", rendezvousIP)
   675  
   676  	} else {
   677  		rendezvousIP, err = manifests.GetNodeZeroIP(hosts, nmStateConfigs)
   678  		if err != nil {
   679  			return "", errors.Wrap(err, "missing rendezvousIP in agent-config, at least one host networkConfig, or at least one NMStateConfig manifest")
   680  		}
   681  		logrus.Debug("RendezvousIP from the NMStateConfig ", rendezvousIP)
   682  	}
   683  
   684  	// Convert IPv6 address to canonical to match host format for comparisons
   685  	addr := net.ParseIP(rendezvousIP)
   686  	if addr == nil {
   687  		err = errors.New(fmt.Sprintf("invalid rendezvous IP: %s", rendezvousIP))
   688  		return "", err
   689  	}
   690  	return addr.String(), err
   691  }
   692  
   693  func getPublicContainerRegistries(registriesConfig *mirror.RegistriesConf) string {
   694  
   695  	if len(registriesConfig.MirrorConfig) > 0 {
   696  		registries := []string{}
   697  		for _, config := range registriesConfig.MirrorConfig {
   698  			location := strings.SplitN(config.Location, "/", 2)[0]
   699  
   700  			allRegs := fmt.Sprint(registries)
   701  			if !strings.Contains(allRegs, location) {
   702  				registries = append(registries, location)
   703  			}
   704  		}
   705  		return strings.Join(registries, ",")
   706  	}
   707  
   708  	return "quay.io"
   709  }