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

     1  package image
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/fs"
     7  	"os"
     8  	"os/exec"
     9  	"time"
    10  
    11  	"github.com/coreos/stream-metadata-go/arch"
    12  	"github.com/coreos/stream-metadata-go/stream"
    13  	"github.com/pkg/errors"
    14  	"github.com/sirupsen/logrus"
    15  
    16  	"github.com/openshift/installer/pkg/asset"
    17  	"github.com/openshift/installer/pkg/asset/agent"
    18  	"github.com/openshift/installer/pkg/asset/agent/joiner"
    19  	"github.com/openshift/installer/pkg/asset/agent/manifests"
    20  	"github.com/openshift/installer/pkg/asset/agent/mirror"
    21  	"github.com/openshift/installer/pkg/asset/agent/workflow"
    22  	"github.com/openshift/installer/pkg/rhcos"
    23  	"github.com/openshift/installer/pkg/rhcos/cache"
    24  	"github.com/openshift/installer/pkg/types"
    25  )
    26  
    27  // BaseIso generates the base ISO file for the image
    28  type BaseIso struct {
    29  	File         *asset.File
    30  	streamGetter CoreOSBuildFetcher
    31  	ocRelease    Release
    32  }
    33  
    34  // CoreOSBuildFetcher will be to used to switch the source of the coreos metadata.
    35  type CoreOSBuildFetcher func(ctx context.Context) (*stream.Stream, error)
    36  
    37  var (
    38  	baseIsoFilename = ""
    39  	// DefaultCoreOSStreamGetter uses the pinned metadata.
    40  	DefaultCoreOSStreamGetter = rhcos.FetchCoreOSBuild
    41  )
    42  
    43  var _ asset.WritableAsset = (*BaseIso)(nil)
    44  
    45  // Name returns the human-friendly name of the asset.
    46  func (i *BaseIso) Name() string {
    47  	return "BaseIso Image"
    48  }
    49  
    50  func (i *BaseIso) getMetalArtifact(ctx context.Context, archName string) (stream.PlatformArtifacts, error) {
    51  	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
    52  	defer cancel()
    53  
    54  	// Get the ISO to use from rhcos.json
    55  	st, err := i.streamGetter(ctx)
    56  	if err != nil {
    57  		return stream.PlatformArtifacts{}, err
    58  	}
    59  
    60  	streamArch, err := st.GetArchitecture(archName)
    61  	if err != nil {
    62  		return stream.PlatformArtifacts{}, err
    63  	}
    64  
    65  	metal, ok := streamArch.Artifacts["metal"]
    66  	if !ok {
    67  		return stream.PlatformArtifacts{}, fmt.Errorf("coreOs stream data not found for 'metal' artifact")
    68  	}
    69  
    70  	return metal, nil
    71  }
    72  
    73  // Download the ISO using the URL in rhcos.json.
    74  func (i *BaseIso) downloadIso(ctx context.Context, archName string) (string, error) {
    75  	metal, err := i.getMetalArtifact(ctx, archName)
    76  	if err != nil {
    77  		return "", err
    78  	}
    79  
    80  	format, ok := metal.Formats["iso"]
    81  	if !ok {
    82  		return "", fmt.Errorf("no ISO found to download for %s", archName)
    83  	}
    84  
    85  	url := format.Disk.Location
    86  	sha := format.Disk.Sha256
    87  	cachedImage, err := cache.DownloadImageFileWithSha(url, cache.AgentApplicationName, sha)
    88  	if err != nil {
    89  		return "", errors.Wrapf(err, "failed to download base ISO image %s", url)
    90  	}
    91  
    92  	return cachedImage, nil
    93  }
    94  
    95  // Fetch RootFS URL using the rhcos.json.
    96  func (i *BaseIso) getRootFSURL(ctx context.Context, archName string) (string, error) {
    97  	metal, err := i.getMetalArtifact(ctx, archName)
    98  	if err != nil {
    99  		return "", err
   100  	}
   101  
   102  	if format, ok := metal.Formats["pxe"]; ok {
   103  		rootFSUrl := format.Rootfs.Location
   104  		return rootFSUrl, nil
   105  	}
   106  
   107  	return "", fmt.Errorf("no RootFSURL found for %s", archName)
   108  }
   109  
   110  // Dependencies returns dependencies used by the asset.
   111  func (i *BaseIso) Dependencies() []asset.Asset {
   112  	return []asset.Asset{
   113  		&workflow.AgentWorkflow{},
   114  		&joiner.ClusterInfo{},
   115  		&manifests.AgentManifests{},
   116  		&agent.OptionalInstallConfig{},
   117  		&mirror.RegistriesConf{},
   118  	}
   119  }
   120  
   121  func (i *BaseIso) checkReleasePayloadBaseISOVersion(ctx context.Context, r Release, archName string) {
   122  	logrus.Debugf("Checking release payload base ISO version")
   123  
   124  	// Get current release payload CoreOS version
   125  	payloadRelease, err := r.GetBaseIsoVersion(archName)
   126  	if err != nil {
   127  		logrus.Warnf("unable to determine base ISO version: %s", err.Error())
   128  		return
   129  	}
   130  
   131  	// Get pinned version from installer
   132  	metal, err := i.getMetalArtifact(ctx, archName)
   133  	if err != nil {
   134  		logrus.Warnf("unable to determine base ISO version: %s", err.Error())
   135  		return
   136  	}
   137  
   138  	// Check for a mismatch
   139  	if metal.Release != payloadRelease {
   140  		logrus.Warnf("base ISO version mismatch in release payload. Expected version %s but found %s", metal.Release, payloadRelease)
   141  	}
   142  }
   143  
   144  // Generate the baseIso
   145  func (i *BaseIso) Generate(ctx context.Context, dependencies asset.Parents) error {
   146  	var err error
   147  	var baseIsoFileName string
   148  
   149  	if urlOverride, ok := os.LookupEnv("OPENSHIFT_INSTALL_OS_IMAGE_OVERRIDE"); ok && urlOverride != "" {
   150  		logrus.Warn("Found override for OS Image. Please be warned, this is not advised")
   151  		baseIsoFileName, err = cache.DownloadImageFile(urlOverride, cache.AgentApplicationName)
   152  	} else {
   153  		i.setStreamGetter(dependencies)
   154  		baseIsoFileName, err = i.retrieveBaseIso(ctx, dependencies)
   155  	}
   156  
   157  	if err == nil {
   158  		logrus.Debugf("Using base ISO image %s", baseIsoFileName)
   159  		i.File = &asset.File{Filename: baseIsoFileName}
   160  		return nil
   161  	}
   162  	logrus.Debugf("Failed to download base ISO: %s", err)
   163  
   164  	return errors.Wrap(err, "failed to get base ISO image")
   165  }
   166  
   167  func (i *BaseIso) setStreamGetter(dependencies asset.Parents) {
   168  	if i.streamGetter != nil {
   169  		return
   170  	}
   171  
   172  	agentWorkflow := &workflow.AgentWorkflow{}
   173  	clusterInfo := &joiner.ClusterInfo{}
   174  	dependencies.Get(agentWorkflow, clusterInfo)
   175  
   176  	i.streamGetter = DefaultCoreOSStreamGetter
   177  	if agentWorkflow.Workflow == workflow.AgentWorkflowTypeAddNodes {
   178  		i.streamGetter = func(ctx context.Context) (*stream.Stream, error) {
   179  			return clusterInfo.OSImage, nil
   180  		}
   181  	}
   182  }
   183  
   184  func (i *BaseIso) getRelease(agentManifests *manifests.AgentManifests, registriesConf *mirror.RegistriesConf) Release {
   185  	if i.ocRelease != nil {
   186  		return i.ocRelease
   187  	}
   188  
   189  	releaseImage := agentManifests.ClusterImageSet.Spec.ReleaseImage
   190  	pullSecret := agentManifests.GetPullSecretData()
   191  
   192  	i.ocRelease = NewRelease(
   193  		Config{MaxTries: OcDefaultTries, RetryDelay: OcDefaultRetryDelay},
   194  		releaseImage, pullSecret, registriesConf.MirrorConfig, i.streamGetter)
   195  
   196  	return i.ocRelease
   197  }
   198  
   199  func (i *BaseIso) retrieveBaseIso(ctx context.Context, dependencies asset.Parents) (string, error) {
   200  	// use the GetIso function to get the BaseIso from the release payload
   201  	agentManifests := &manifests.AgentManifests{}
   202  	registriesConf := &mirror.RegistriesConf{}
   203  	dependencies.Get(agentManifests, registriesConf)
   204  
   205  	// Default iso archName to x86_64.
   206  	archName := arch.RpmArch(types.ArchitectureAMD64)
   207  
   208  	if agentManifests.ClusterImageSet != nil {
   209  		// If specified, use InfraEnv.Spec.CpuArchitecture for iso archName
   210  		if agentManifests.InfraEnv.Spec.CpuArchitecture != "" {
   211  			archName = agentManifests.InfraEnv.Spec.CpuArchitecture
   212  		}
   213  
   214  		// If we have the image registry location and 'oc' command is available then get from release payload
   215  		ocRelease := i.getRelease(agentManifests, registriesConf)
   216  		logrus.Info("Extracting base ISO from release payload")
   217  		baseIsoFileName, err := ocRelease.GetBaseIso(archName)
   218  		if err == nil {
   219  			i.checkReleasePayloadBaseISOVersion(ctx, ocRelease, archName)
   220  
   221  			logrus.Debugf("Extracted base ISO image %s from release payload", baseIsoFileName)
   222  			i.File = &asset.File{Filename: baseIsoFileName}
   223  			return baseIsoFileName, nil
   224  		}
   225  
   226  		if errors.Is(err, fs.ErrNotExist) {
   227  			// if image extract failed to extract the iso that architecture may be missing from release image
   228  			return "", fmt.Errorf("base ISO for %s not found in release image, check release image architecture", archName)
   229  		}
   230  		if !errors.Is(err, &exec.Error{}) { // Already warned about missing oc binary
   231  			logrus.Warning("Failed to extract base ISO from release payload - check registry configuration")
   232  		}
   233  	}
   234  
   235  	logrus.Info("Downloading base ISO")
   236  	return i.downloadIso(ctx, archName)
   237  }
   238  
   239  // Files returns the files generated by the asset.
   240  func (i *BaseIso) Files() []*asset.File {
   241  
   242  	if i.File != nil {
   243  		return []*asset.File{i.File}
   244  	}
   245  	return []*asset.File{}
   246  }
   247  
   248  // Load returns the cached baseIso
   249  func (i *BaseIso) Load(f asset.FileFetcher) (bool, error) {
   250  
   251  	if baseIsoFilename == "" {
   252  		return false, nil
   253  	}
   254  
   255  	baseIso, err := f.FetchByName(baseIsoFilename)
   256  	if err != nil {
   257  		if os.IsNotExist(err) {
   258  			return false, nil
   259  		}
   260  		return false, errors.Wrap(err, fmt.Sprintf("failed to load %s file", baseIsoFilename))
   261  	}
   262  
   263  	i.File = baseIso
   264  	return true, nil
   265  }