From e2aedd33aaaf3b1202a44deba8d28258d18e573b Mon Sep 17 00:00:00 2001 From: Isabella Janssen Date: Fri, 3 Apr 2026 13:32:37 -0400 Subject: [PATCH 1/4] agent: support osImageStream for day-0 ABI install workflow for rhel-10 stream --- pkg/asset/agent/image/agentimage.go | 7 +++-- pkg/asset/agent/image/baseiso.go | 23 +++++++++++--- pkg/asset/agent/image/ignition.go | 10 +++--- .../agent/image/unconfigured_ignition.go | 2 +- pkg/asset/agent/manifests/agent.go | 6 ++++ pkg/asset/agent/manifests/agent_test.go | 3 ++ .../agent/manifests/agentclusterinstall.go | 7 +++++ pkg/asset/manifests/osimagestream.go | 31 +++++++++++++++++-- pkg/asset/rhcos/iso.go | 30 +++++++++++++----- pkg/asset/rhcos/iso_test.go | 3 +- pkg/infrastructure/baremetal/bootstrap.go | 3 +- pkg/types/validation/installconfig_test.go | 10 ++++++ 12 files changed, 112 insertions(+), 23 deletions(-) diff --git a/pkg/asset/agent/image/agentimage.go b/pkg/asset/agent/image/agentimage.go index 71bcbf97ba9..6937f235bd8 100644 --- a/pkg/asset/agent/image/agentimage.go +++ b/pkg/asset/agent/image/agentimage.go @@ -14,6 +14,7 @@ import ( "github.com/openshift/assisted-image-service/pkg/isoeditor" hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" "github.com/openshift/installer/pkg/asset/agent/gencrypto" "github.com/openshift/installer/pkg/asset/agent/joiner" "github.com/openshift/installer/pkg/asset/agent/manifests" @@ -53,6 +54,7 @@ func (a *AgentImage) Dependencies() []asset.Asset { &manifests.AgentManifests{}, &BaseIso{}, &gencrypto.AuthConfig{}, + &agent.OptionalInstallConfig{}, } } @@ -63,7 +65,8 @@ func (a *AgentImage) Generate(ctx context.Context, dependencies asset.Parents) e agentArtifacts := &AgentArtifacts{} agentManifests := &manifests.AgentManifests{} baseIso := &BaseIso{} - dependencies.Get(agentArtifacts, agentManifests, baseIso, agentWorkflow, clusterInfo) + installConfig := &agent.OptionalInstallConfig{} + dependencies.Get(agentArtifacts, agentManifests, baseIso, agentWorkflow, clusterInfo, installConfig) if err := workflowreport.GetReport(ctx).Stage(workflow.StageGenerateISO); err != nil { return err @@ -106,7 +109,7 @@ func (a *AgentImage) Generate(ctx context.Context, dependencies asset.Parents) e logrus.Debugf("Using custom rootfs URL: %s", a.rootFSURL) } else { // Default to the URL from the RHCOS streams file - defaultRootFSURL, err := baseIso.getRootFSURL(ctx, a.cpuArch, agentWorkflow, clusterInfo) + defaultRootFSURL, err := baseIso.getRootFSURL(ctx, a.cpuArch, agentWorkflow, clusterInfo, installConfig) if err != nil { return err } diff --git a/pkg/asset/agent/image/baseiso.go b/pkg/asset/agent/image/baseiso.go index 16fd508a3d2..15ebca94e4e 100644 --- a/pkg/asset/agent/image/baseiso.go +++ b/pkg/asset/agent/image/baseiso.go @@ -37,9 +37,12 @@ func (i *BaseIso) Name() string { } // Fetch RootFS URL using the rhcos.json. -func (i *BaseIso) getRootFSURL(ctx context.Context, archName string, agentWorkflow *workflow.AgentWorkflow, clusterInfo *joiner.ClusterInfo) (string, error) { +func (i *BaseIso) getRootFSURL(ctx context.Context, archName string, agentWorkflow *workflow.AgentWorkflow, clusterInfo *joiner.ClusterInfo, installConfig *agent.OptionalInstallConfig) (string, error) { metal, err := rhcos.GetMetalArtifact( - ctx, archName, customStreamGetter(agentWorkflow, clusterInfo)) + ctx, + archName, + customStreamGetter(agentWorkflow, clusterInfo), + getOSImageStream(agentWorkflow, installConfig)) if err != nil { return "", err } @@ -69,11 +72,13 @@ func (i *BaseIso) Generate(ctx context.Context, dependencies asset.Parents) erro registriesConf := &mirror.RegistriesConf{} agentWorkflow := &workflow.AgentWorkflow{} clusterInfo := &joiner.ClusterInfo{} - dependencies.Get(agentManifests, registriesConf, agentWorkflow, clusterInfo) + installConfig := &agent.OptionalInstallConfig{} + dependencies.Get(agentManifests, registriesConf, agentWorkflow, clusterInfo, installConfig) baseIsoFileName, err := rhcos.NewBaseISOFetcher( i.getRelease(agentManifests, registriesConf.MirrorConfig), - customStreamGetter(agentWorkflow, clusterInfo)).GetBaseISOFilename(ctx, agentManifests.InfraEnv.Spec.CpuArchitecture) + customStreamGetter(agentWorkflow, clusterInfo), + getOSImageStream(agentWorkflow, installConfig)).GetBaseISOFilename(ctx, agentManifests.InfraEnv.Spec.CpuArchitecture) if err == nil { logrus.Debugf("Using base ISO image %s", baseIsoFileName) @@ -86,6 +91,7 @@ func (i *BaseIso) Generate(ctx context.Context, dependencies asset.Parents) erro } func customStreamGetter(agentWorkflow *workflow.AgentWorkflow, clusterInfo *joiner.ClusterInfo) rhcos.CoreOSBuildFetcher { + // Only for add-nodes workflow - use cluster's existing OS image if agentWorkflow.Workflow == workflow.AgentWorkflowTypeAddNodes { return func(ctx context.Context) (*stream.Stream, error) { return clusterInfo.OSImage, nil @@ -94,6 +100,15 @@ func customStreamGetter(agentWorkflow *workflow.AgentWorkflow, clusterInfo *join return nil } +func getOSImageStream(agentWorkflow *workflow.AgentWorkflow, installConfig *agent.OptionalInstallConfig) types.OSImageStream { + // For install workflow, use osImageStream from install-config if supplied + if agentWorkflow.Workflow == workflow.AgentWorkflowTypeInstall && + installConfig.Supplied && installConfig.Config != nil { + return installConfig.Config.OSImageStream + } + return "" +} + func (i *BaseIso) getRelease(agentManifests *manifests.AgentManifests, mirrorConfig types.MirrorConfig) rhcos.ReleasePayload { if i.ocRelease != nil { return i.ocRelease diff --git a/pkg/asset/agent/image/ignition.go b/pkg/asset/agent/image/ignition.go index 6b64060e0cd..0e98826cbc8 100644 --- a/pkg/asset/agent/image/ignition.go +++ b/pkg/asset/agent/image/ignition.go @@ -115,6 +115,7 @@ func (a *Ignition) Dependencies() []asset.Asset { &mirror.CaBundle{}, &gencrypto.AuthConfig{}, &common.InfraEnvID{}, + &agentcommon.OptionalInstallConfig{}, } } @@ -128,7 +129,8 @@ func (a *Ignition) Generate(ctx context.Context, dependencies asset.Parents) err fencingCredentials := &agentconfig.FencingCredentials{} authConfig := &gencrypto.AuthConfig{} infraEnvAsset := &common.InfraEnvID{} - dependencies.Get(agentManifests, agentConfigAsset, agentHostsAsset, extraManifests, fencingCredentials, authConfig, agentWorkflow, infraEnvAsset) + installConfig := &agentcommon.OptionalInstallConfig{} + dependencies.Get(agentManifests, agentConfigAsset, agentHostsAsset, extraManifests, fencingCredentials, authConfig, agentWorkflow, infraEnvAsset, installConfig) clusterInfo := &joiner.ClusterInfo{} if err := workflowreport.GetReport(ctx).Stage(workflow.StageIgnition); err != nil { @@ -269,7 +271,7 @@ func (a *Ignition) Generate(ctx context.Context, dependencies asset.Parents) err infraEnvID := infraEnvAsset.ID logrus.Debug("Generated random infra-env id ", infraEnvID) - osImage, err := getOSImagesInfo(ctx, archName, openshiftVersion, customStreamGetter(agentWorkflow, clusterInfo)) + osImage, err := getOSImagesInfo(ctx, archName, openshiftVersion, customStreamGetter(agentWorkflow, clusterInfo), getOSImageStream(agentWorkflow, installConfig)) if err != nil { return err } @@ -745,13 +747,13 @@ func addExtraManifests(config *igntypes.Config, extraManifests *manifests.ExtraM return nil } -func getOSImagesInfo(ctx context.Context, cpuArch string, openshiftVersion string, streamGetter rhcos.CoreOSBuildFetcher) (*models.OsImage, error) { +func getOSImagesInfo(ctx context.Context, cpuArch string, openshiftVersion string, streamGetter rhcos.CoreOSBuildFetcher, osImageStream types.OSImageStream) (*models.OsImage, error) { osImage := &models.OsImage{ CPUArchitecture: &cpuArch, } osImage.OpenshiftVersion = &openshiftVersion - artifacts, err := rhcos.GetMetalArtifact(ctx, cpuArch, streamGetter) + artifacts, err := rhcos.GetMetalArtifact(ctx, cpuArch, streamGetter, osImageStream) if err != nil { return nil, err } diff --git a/pkg/asset/agent/image/unconfigured_ignition.go b/pkg/asset/agent/image/unconfigured_ignition.go index 00e3cf66da4..f046ac6e0b7 100644 --- a/pkg/asset/agent/image/unconfigured_ignition.go +++ b/pkg/asset/agent/image/unconfigured_ignition.go @@ -155,7 +155,7 @@ func (a *UnconfiguredIgnition) Generate(ctx context.Context, dependencies asset. if err != nil { return err } - osImage, err := getOSImagesInfo(ctx, archName, openshiftVersion, nil) + osImage, err := getOSImagesInfo(ctx, archName, openshiftVersion, nil, "") if err != nil { return err } diff --git a/pkg/asset/agent/manifests/agent.go b/pkg/asset/agent/manifests/agent.go index 55566272112..265ea5649a6 100644 --- a/pkg/asset/agent/manifests/agent.go +++ b/pkg/asset/agent/manifests/agent.go @@ -16,6 +16,7 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/agent/workflow" workflowreport "github.com/openshift/installer/pkg/asset/agent/workflow/report" + "github.com/openshift/installer/pkg/asset/manifests" ) const ( @@ -55,6 +56,7 @@ func (m *AgentManifests) Dependencies() []asset.Asset { &AgentClusterInstall{}, &ClusterDeployment{}, &ClusterImageSet{}, + &manifests.OSImageStream{}, } } @@ -71,6 +73,7 @@ func (m *AgentManifests) Generate(ctx context.Context, dependencies asset.Parent &AgentClusterInstall{}, &ClusterDeployment{}, &ClusterImageSet{}, + &manifests.OSImageStream{}, } { dependencies.Get(a) @@ -93,6 +96,9 @@ func (m *AgentManifests) Generate(ctx context.Context, dependencies asset.Parent m.ClusterDeployment = v.Config case *ClusterImageSet: m.ClusterImageSet = v.Config + case *manifests.OSImageStream: + // OSImageStream is optional, only generated when install-config is provided + // No need to store in AgentManifests struct } m.FileList = append(m.FileList, a.Files()...) diff --git a/pkg/asset/agent/manifests/agent_test.go b/pkg/asset/agent/manifests/agent_test.go index bff8008ae16..bd0a530f71a 100644 --- a/pkg/asset/agent/manifests/agent_test.go +++ b/pkg/asset/agent/manifests/agent_test.go @@ -13,6 +13,7 @@ import ( "github.com/openshift/assisted-service/models" hivev1 "github.com/openshift/hive/apis/hive/v1" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/manifests" ) func TestAgentManifests_Generate(t *testing.T) { @@ -59,6 +60,7 @@ func TestAgentManifests_Generate(t *testing.T) { &AgentClusterInstall{Config: fakeAgentClusterInstall}, &ClusterDeployment{Config: fakeClusterDeployment}, &ClusterImageSet{Config: fakeClusterImageSet}, + &manifests.OSImageStream{}, }, ExpectedPullSecret: fakeSecret, ExpectedInfraEnv: fakeInfraEnv, @@ -93,6 +95,7 @@ func TestAgentManifests_Generate(t *testing.T) { &AgentClusterInstall{}, &ClusterDeployment{}, &ClusterImageSet{}, + &manifests.OSImageStream{}, }, ExpectedError: "invalid agent configuration: spec.nmStateConfigLabelSelector.matchLabels: Required value: infraEnv and fake-nmState NMState Config labels do not match. Expected: map[missing-label:missing-label] Found: map[]", }, diff --git a/pkg/asset/agent/manifests/agentclusterinstall.go b/pkg/asset/agent/manifests/agentclusterinstall.go index eef1c71b10c..e8998851d21 100644 --- a/pkg/asset/agent/manifests/agentclusterinstall.go +++ b/pkg/asset/agent/manifests/agentclusterinstall.go @@ -131,6 +131,8 @@ type agentClusterInstallInstallConfigOverrides struct { FeatureSet configv1.FeatureSet `json:"featureSet,omitempty"` // Allow override of FeatureGates FeatureGates []string `json:"featureGates,omitempty"` + // OSImageStream is the OS Image Stream to be used for all machines in the cluster + OSImageStream types.OSImageStream `json:"osImageStream,omitempty"` } var _ asset.WritableAsset = (*AgentClusterInstall)(nil) @@ -262,6 +264,11 @@ func (a *AgentClusterInstall) Generate(_ context.Context, dependencies asset.Par icOverrides.FeatureGates = installConfig.Config.FeatureGates } + if installConfig.Config.OSImageStream != "" { + icOverridden = true + icOverrides.OSImageStream = installConfig.Config.OSImageStream + } + if installConfig.Config.Proxy != nil { rendezvousIP := "" if agentConfig.Config != nil { diff --git a/pkg/asset/manifests/osimagestream.go b/pkg/asset/manifests/osimagestream.go index 4c6c9d46bb8..fca5c99da75 100644 --- a/pkg/asset/manifests/osimagestream.go +++ b/pkg/asset/manifests/osimagestream.go @@ -11,8 +11,11 @@ import ( "github.com/openshift/api/features" mcfgv1alpha "github.com/openshift/api/machineconfiguration/v1alpha1" "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/agent/workflow" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/rhcos" + "github.com/openshift/installer/pkg/types" ) var osImageStreamFileName = path.Join(openshiftManifestDir, "99_osimagestream.yaml") @@ -39,19 +42,43 @@ func (*OSImageStream) Dependencies() []asset.Asset { // Generate generates the OSImageStream CRD. func (f *OSImageStream) Generate(_ context.Context, dependencies asset.Parents) error { + // Try IPI install config first installConfig := &installconfig.InstallConfig{} dependencies.Get(installConfig) + // Then try agent optional install config + agentInstallConfig := &agent.OptionalInstallConfig{} + agentWorkflow := &workflow.AgentWorkflow{} + dependencies.Get(agentInstallConfig, agentWorkflow) + + var config *types.InstallConfig + + // Determine which install config to use + switch { + case installConfig.Config != nil: + // IPI workflow + config = installConfig.Config + case agentInstallConfig.Supplied && agentInstallConfig.Config != nil: + // Agent workflow - only generate for install workflow, not add-nodes + if agentWorkflow.Workflow != workflow.AgentWorkflowTypeInstall { + return nil + } + config = agentInstallConfig.Config + default: + // No install config available + return nil + } + // If one of the following are true the OSImageStream CR is not generated // 1. The feature is not enabled // 2. The target is CentOS Stream CoreOS - if ic := installConfig.Config; !ic.Enabled(features.FeatureGateOSStreams) || ic.IsSCOS() { + if !config.Enabled(features.FeatureGateOSStreams) || config.IsSCOS() { // FG disabled or not OCP return nil } // If no stream was set just report the default one for the current version - stream := installConfig.Config.OSImageStream + stream := config.OSImageStream if stream == "" { stream = rhcos.DefaultOSImageStream } diff --git a/pkg/asset/rhcos/iso.go b/pkg/asset/rhcos/iso.go index ae7bb26fdfc..188e7c8bb87 100644 --- a/pkg/asset/rhcos/iso.go +++ b/pkg/asset/rhcos/iso.go @@ -35,10 +35,16 @@ var defaultCoreOSStreamGetter = func(ctx context.Context) (*stream.Stream, error } // NewBaseISOFetcher returns a struct that can be used to fetch a base ISO using -// the default method. -func NewBaseISOFetcher(ocRelease ReleasePayload, streamGetter CoreOSBuildFetcher) *BaseIso { +// the default method. If streamGetter is nil, osImageStream will be used if provided. +func NewBaseISOFetcher(ocRelease ReleasePayload, streamGetter CoreOSBuildFetcher, osImageStream types.OSImageStream) *BaseIso { if streamGetter == nil { - streamGetter = defaultCoreOSStreamGetter + if osImageStream != "" { + streamGetter = func(ctx context.Context) (*stream.Stream, error) { + return rhcos.FetchCoreOSBuild(ctx, osImageStream) + } + } else { + streamGetter = defaultCoreOSStreamGetter + } } return &BaseIso{ streamGetter: streamGetter, @@ -65,10 +71,18 @@ func (i *BaseIso) GetBaseISOFilename(ctx context.Context, arch string) (baseIsoF } // GetMetalArtifact returns the CoreOS artifacts for metal for a given arch -// from a given stream. -func GetMetalArtifact(ctx context.Context, archName string, streamGetter CoreOSBuildFetcher) (stream.PlatformArtifacts, error) { +// from a given stream. If streamGetter is nil, uses osImageStream if provided, +// otherwise falls back to the default stream. +func GetMetalArtifact(ctx context.Context, archName string, streamGetter CoreOSBuildFetcher, osImageStream types.OSImageStream) (stream.PlatformArtifacts, error) { if streamGetter == nil { - streamGetter = defaultCoreOSStreamGetter + // OSImageStream is part of the default implementation + if osImageStream != "" { + streamGetter = func(ctx context.Context) (*stream.Stream, error) { + return rhcos.FetchCoreOSBuild(ctx, osImageStream) + } + } else { + streamGetter = defaultCoreOSStreamGetter + } } ctx, cancel := context.WithTimeout(ctx, 30*time.Second) @@ -95,7 +109,7 @@ func GetMetalArtifact(ctx context.Context, archName string, streamGetter CoreOSB // Download the ISO using the URL in rhcos.json. func (i *BaseIso) downloadIso(ctx context.Context, archName string) (string, error) { - metal, err := GetMetalArtifact(ctx, archName, i.streamGetter) + metal, err := GetMetalArtifact(ctx, archName, i.streamGetter, "") if err != nil { return "", err } @@ -126,7 +140,7 @@ func (i *BaseIso) checkReleasePayloadBaseISOVersion(ctx context.Context, r Relea } // Get pinned version from installer - metal, err := GetMetalArtifact(ctx, archName, i.streamGetter) + metal, err := GetMetalArtifact(ctx, archName, i.streamGetter, "") if err != nil { logrus.Warnf("unable to determine base ISO version: %s", err.Error()) return diff --git a/pkg/asset/rhcos/iso_test.go b/pkg/asset/rhcos/iso_test.go index 60bbbb20fdd..c3049c5f73f 100644 --- a/pkg/asset/rhcos/iso_test.go +++ b/pkg/asset/rhcos/iso_test.go @@ -96,7 +96,8 @@ func TestBaseIso(t *testing.T) { }, }, }, nil - }) + }, + "") filename, err := fetcher.GetBaseISOFilename(context.Background(), "") if tc.expectedError == "" { diff --git a/pkg/infrastructure/baremetal/bootstrap.go b/pkg/infrastructure/baremetal/bootstrap.go index e4f4304b487..231b55a1cc5 100644 --- a/pkg/infrastructure/baremetal/bootstrap.go +++ b/pkg/infrastructure/baremetal/bootstrap.go @@ -178,7 +178,8 @@ func getLiveISO(config baremetalConfig, arch string) (string, error) { config.PullSecret, config.MirrorConfig, ), - nil) + nil, + "") return fetcher.GetBaseISOFilename(context.Background(), arch) } diff --git a/pkg/types/validation/installconfig_test.go b/pkg/types/validation/installconfig_test.go index 7b76fbbba6a..8b1a629f22a 100644 --- a/pkg/types/validation/installconfig_test.go +++ b/pkg/types/validation/installconfig_test.go @@ -3015,6 +3015,16 @@ func TestValidateInstallConfig(t *testing.T) { }(), expectedError: "the Ingress capability is required", }, + { + name: "invalid OSImageStream value", + installConfig: func() *types.InstallConfig { + c := validInstallConfig() + c.FeatureSet = configv1.TechPreviewNoUpgrade + c.OSImageStream = "invalid" + return c + }(), + expectedError: "Unsupported OS Image Stream. Supported values are: rhel-9, rhel-10", + }, { name: "invalid OSImageStream set", installConfig: func() *types.InstallConfig { From c1a889ac94bc0ad3c8792e9a692ee7809a7d02c6 Mon Sep 17 00:00:00 2001 From: Dalia Khater Date: Thu, 23 Apr 2026 12:44:56 -0500 Subject: [PATCH 2/4] agent: Use AgentOSImageStream for stream value Creates separate AgentOSImageStream asset for Agent workflows to avoid breaking ZTP-only deployments. IPI OSImageStream depends on required InstallConfig, while AgentOSImageStream depends on OptionalInstallConfig and can work without install-config.yaml. --- pkg/asset/agent/image/agentimage.go | 9 ++-- pkg/asset/agent/image/baseiso.go | 27 +++------- pkg/asset/agent/image/ignition.go | 8 +-- pkg/asset/agent/manifests/osimagestream.go | 63 ++++++++++++++++++++++ pkg/asset/manifests/osimagestream.go | 51 ++++++++---------- 5 files changed, 99 insertions(+), 59 deletions(-) create mode 100644 pkg/asset/agent/manifests/osimagestream.go diff --git a/pkg/asset/agent/image/agentimage.go b/pkg/asset/agent/image/agentimage.go index 6937f235bd8..629480870a4 100644 --- a/pkg/asset/agent/image/agentimage.go +++ b/pkg/asset/agent/image/agentimage.go @@ -14,7 +14,6 @@ import ( "github.com/openshift/assisted-image-service/pkg/isoeditor" hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/agent" "github.com/openshift/installer/pkg/asset/agent/gencrypto" "github.com/openshift/installer/pkg/asset/agent/joiner" "github.com/openshift/installer/pkg/asset/agent/manifests" @@ -54,7 +53,7 @@ func (a *AgentImage) Dependencies() []asset.Asset { &manifests.AgentManifests{}, &BaseIso{}, &gencrypto.AuthConfig{}, - &agent.OptionalInstallConfig{}, + &manifests.AgentOSImageStream{}, } } @@ -65,8 +64,8 @@ func (a *AgentImage) Generate(ctx context.Context, dependencies asset.Parents) e agentArtifacts := &AgentArtifacts{} agentManifests := &manifests.AgentManifests{} baseIso := &BaseIso{} - installConfig := &agent.OptionalInstallConfig{} - dependencies.Get(agentArtifacts, agentManifests, baseIso, agentWorkflow, clusterInfo, installConfig) + osImageStream := &manifests.AgentOSImageStream{} + dependencies.Get(agentArtifacts, agentManifests, baseIso, agentWorkflow, clusterInfo, osImageStream) if err := workflowreport.GetReport(ctx).Stage(workflow.StageGenerateISO); err != nil { return err @@ -109,7 +108,7 @@ func (a *AgentImage) Generate(ctx context.Context, dependencies asset.Parents) e logrus.Debugf("Using custom rootfs URL: %s", a.rootFSURL) } else { // Default to the URL from the RHCOS streams file - defaultRootFSURL, err := baseIso.getRootFSURL(ctx, a.cpuArch, agentWorkflow, clusterInfo, installConfig) + defaultRootFSURL, err := baseIso.getRootFSURL(ctx, a.cpuArch, agentWorkflow, clusterInfo, osImageStream) if err != nil { return err } diff --git a/pkg/asset/agent/image/baseiso.go b/pkg/asset/agent/image/baseiso.go index 15ebca94e4e..ba4befe1088 100644 --- a/pkg/asset/agent/image/baseiso.go +++ b/pkg/asset/agent/image/baseiso.go @@ -10,7 +10,6 @@ import ( "github.com/sirupsen/logrus" "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/agent" "github.com/openshift/installer/pkg/asset/agent/joiner" "github.com/openshift/installer/pkg/asset/agent/manifests" "github.com/openshift/installer/pkg/asset/agent/mirror" @@ -37,12 +36,8 @@ func (i *BaseIso) Name() string { } // Fetch RootFS URL using the rhcos.json. -func (i *BaseIso) getRootFSURL(ctx context.Context, archName string, agentWorkflow *workflow.AgentWorkflow, clusterInfo *joiner.ClusterInfo, installConfig *agent.OptionalInstallConfig) (string, error) { - metal, err := rhcos.GetMetalArtifact( - ctx, - archName, - customStreamGetter(agentWorkflow, clusterInfo), - getOSImageStream(agentWorkflow, installConfig)) +func (i *BaseIso) getRootFSURL(ctx context.Context, archName string, agentWorkflow *workflow.AgentWorkflow, clusterInfo *joiner.ClusterInfo, osImageStream *manifests.AgentOSImageStream) (string, error) { + metal, err := rhcos.GetMetalArtifact(ctx, archName, customStreamGetter(agentWorkflow, clusterInfo), osImageStream.Stream) if err != nil { return "", err } @@ -61,7 +56,7 @@ func (i *BaseIso) Dependencies() []asset.Asset { &workflow.AgentWorkflow{}, &joiner.ClusterInfo{}, &manifests.AgentManifests{}, - &agent.OptionalInstallConfig{}, + &manifests.AgentOSImageStream{}, &mirror.RegistriesConf{}, } } @@ -72,13 +67,13 @@ func (i *BaseIso) Generate(ctx context.Context, dependencies asset.Parents) erro registriesConf := &mirror.RegistriesConf{} agentWorkflow := &workflow.AgentWorkflow{} clusterInfo := &joiner.ClusterInfo{} - installConfig := &agent.OptionalInstallConfig{} - dependencies.Get(agentManifests, registriesConf, agentWorkflow, clusterInfo, installConfig) + osImageStream := &manifests.AgentOSImageStream{} + dependencies.Get(agentManifests, registriesConf, agentWorkflow, clusterInfo, osImageStream) baseIsoFileName, err := rhcos.NewBaseISOFetcher( i.getRelease(agentManifests, registriesConf.MirrorConfig), customStreamGetter(agentWorkflow, clusterInfo), - getOSImageStream(agentWorkflow, installConfig)).GetBaseISOFilename(ctx, agentManifests.InfraEnv.Spec.CpuArchitecture) + osImageStream.Stream).GetBaseISOFilename(ctx, agentManifests.InfraEnv.Spec.CpuArchitecture) if err == nil { logrus.Debugf("Using base ISO image %s", baseIsoFileName) @@ -91,7 +86,6 @@ func (i *BaseIso) Generate(ctx context.Context, dependencies asset.Parents) erro } func customStreamGetter(agentWorkflow *workflow.AgentWorkflow, clusterInfo *joiner.ClusterInfo) rhcos.CoreOSBuildFetcher { - // Only for add-nodes workflow - use cluster's existing OS image if agentWorkflow.Workflow == workflow.AgentWorkflowTypeAddNodes { return func(ctx context.Context) (*stream.Stream, error) { return clusterInfo.OSImage, nil @@ -100,15 +94,6 @@ func customStreamGetter(agentWorkflow *workflow.AgentWorkflow, clusterInfo *join return nil } -func getOSImageStream(agentWorkflow *workflow.AgentWorkflow, installConfig *agent.OptionalInstallConfig) types.OSImageStream { - // For install workflow, use osImageStream from install-config if supplied - if agentWorkflow.Workflow == workflow.AgentWorkflowTypeInstall && - installConfig.Supplied && installConfig.Config != nil { - return installConfig.Config.OSImageStream - } - return "" -} - func (i *BaseIso) getRelease(agentManifests *manifests.AgentManifests, mirrorConfig types.MirrorConfig) rhcos.ReleasePayload { if i.ocRelease != nil { return i.ocRelease diff --git a/pkg/asset/agent/image/ignition.go b/pkg/asset/agent/image/ignition.go index 0e98826cbc8..6204d59d63c 100644 --- a/pkg/asset/agent/image/ignition.go +++ b/pkg/asset/agent/image/ignition.go @@ -115,7 +115,7 @@ func (a *Ignition) Dependencies() []asset.Asset { &mirror.CaBundle{}, &gencrypto.AuthConfig{}, &common.InfraEnvID{}, - &agentcommon.OptionalInstallConfig{}, + &manifests.AgentOSImageStream{}, } } @@ -129,8 +129,8 @@ func (a *Ignition) Generate(ctx context.Context, dependencies asset.Parents) err fencingCredentials := &agentconfig.FencingCredentials{} authConfig := &gencrypto.AuthConfig{} infraEnvAsset := &common.InfraEnvID{} - installConfig := &agentcommon.OptionalInstallConfig{} - dependencies.Get(agentManifests, agentConfigAsset, agentHostsAsset, extraManifests, fencingCredentials, authConfig, agentWorkflow, infraEnvAsset, installConfig) + osImageStream := &manifests.AgentOSImageStream{} + dependencies.Get(agentManifests, agentConfigAsset, agentHostsAsset, extraManifests, fencingCredentials, authConfig, agentWorkflow, infraEnvAsset, osImageStream) clusterInfo := &joiner.ClusterInfo{} if err := workflowreport.GetReport(ctx).Stage(workflow.StageIgnition); err != nil { @@ -271,7 +271,7 @@ func (a *Ignition) Generate(ctx context.Context, dependencies asset.Parents) err infraEnvID := infraEnvAsset.ID logrus.Debug("Generated random infra-env id ", infraEnvID) - osImage, err := getOSImagesInfo(ctx, archName, openshiftVersion, customStreamGetter(agentWorkflow, clusterInfo), getOSImageStream(agentWorkflow, installConfig)) + osImage, err := getOSImagesInfo(ctx, archName, openshiftVersion, customStreamGetter(agentWorkflow, clusterInfo), osImageStream.Stream) if err != nil { return err } diff --git a/pkg/asset/agent/manifests/osimagestream.go b/pkg/asset/agent/manifests/osimagestream.go new file mode 100644 index 00000000000..fe72bff59df --- /dev/null +++ b/pkg/asset/agent/manifests/osimagestream.go @@ -0,0 +1,63 @@ +package manifests + +import ( + "context" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/agent/workflow" + "github.com/openshift/installer/pkg/rhcos" + "github.com/openshift/installer/pkg/types" +) + +// AgentOSImageStream exposes the OS image stream value for Agent workflows. +// This is a pure data asset that does not generate any files. +type AgentOSImageStream struct { + Stream types.OSImageStream +} + +var _ asset.Asset = (*AgentOSImageStream)(nil) + +// Name returns the human-friendly name of the asset. +func (*AgentOSImageStream) Name() string { + return "Agent OS Image Stream" +} + +// Dependencies returns all of the dependencies directly needed to generate +// the asset. +func (*AgentOSImageStream) Dependencies() []asset.Asset { + return []asset.Asset{ + &agent.OptionalInstallConfig{}, + &workflow.AgentWorkflow{}, + } +} + +// Generate extracts the OS image stream value from available sources. +func (a *AgentOSImageStream) Generate(_ context.Context, dependencies asset.Parents) error { + installConfig := &agent.OptionalInstallConfig{} + agentWorkflow := &workflow.AgentWorkflow{} + dependencies.Get(installConfig, agentWorkflow) + + var stream types.OSImageStream + + // Only generate for install workflow, not add-nodes + if agentWorkflow.Workflow != workflow.AgentWorkflowTypeInstall { + return nil + } + + // Try install-config (if provided) + if installConfig.Supplied && installConfig.Config != nil { + stream = installConfig.Config.OSImageStream + } + + // TODO: Add fallback to AgentClusterInstall.Spec.OSImageStream once + // the field is available in the assisted-service API + + // Default + if stream == "" { + stream = rhcos.DefaultOSImageStream + } + + a.Stream = stream + return nil +} diff --git a/pkg/asset/manifests/osimagestream.go b/pkg/asset/manifests/osimagestream.go index fca5c99da75..14f4068e62b 100644 --- a/pkg/asset/manifests/osimagestream.go +++ b/pkg/asset/manifests/osimagestream.go @@ -11,8 +11,6 @@ import ( "github.com/openshift/api/features" mcfgv1alpha "github.com/openshift/api/machineconfiguration/v1alpha1" "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/agent" - "github.com/openshift/installer/pkg/asset/agent/workflow" "github.com/openshift/installer/pkg/asset/installconfig" "github.com/openshift/installer/pkg/rhcos" "github.com/openshift/installer/pkg/types" @@ -20,9 +18,10 @@ import ( var osImageStreamFileName = path.Join(openshiftManifestDir, "99_osimagestream.yaml") -// OSImageStream generates the OSImageStream manifest. +// OSImageStream generates the OSImageStream manifest and exposes the stream value. type OSImageStream struct { FileList []*asset.File + Stream types.OSImageStream } var _ asset.WritableAsset = (*OSImageStream)(nil) @@ -42,33 +41,15 @@ func (*OSImageStream) Dependencies() []asset.Asset { // Generate generates the OSImageStream CRD. func (f *OSImageStream) Generate(_ context.Context, dependencies asset.Parents) error { - // Try IPI install config first installConfig := &installconfig.InstallConfig{} dependencies.Get(installConfig) - // Then try agent optional install config - agentInstallConfig := &agent.OptionalInstallConfig{} - agentWorkflow := &workflow.AgentWorkflow{} - dependencies.Get(agentInstallConfig, agentWorkflow) - - var config *types.InstallConfig - - // Determine which install config to use - switch { - case installConfig.Config != nil: - // IPI workflow - config = installConfig.Config - case agentInstallConfig.Supplied && agentInstallConfig.Config != nil: - // Agent workflow - only generate for install workflow, not add-nodes - if agentWorkflow.Workflow != workflow.AgentWorkflowTypeInstall { - return nil - } - config = agentInstallConfig.Config - default: - // No install config available + if installConfig.Config == nil { return nil } + config := installConfig.Config + // If one of the following are true the OSImageStream CR is not generated // 1. The feature is not enabled // 2. The target is CentOS Stream CoreOS @@ -77,12 +58,25 @@ func (f *OSImageStream) Generate(_ context.Context, dependencies asset.Parents) return nil } - // If no stream was set just report the default one for the current version + // Extract and store the stream value stream := config.OSImageStream if stream == "" { stream = rhcos.DefaultOSImageStream } + f.Stream = stream + + // Generate manifest file + manifest, err := f.createManifest(stream) + if err != nil { + return err + } + f.FileList = append(f.FileList, manifest) + return nil +} + +// createManifest generates the OSImageStream manifest file. +func (f *OSImageStream) createManifest(stream types.OSImageStream) (*asset.File, error) { osImageStream := &mcfgv1alpha.OSImageStream{ TypeMeta: metav1.TypeMeta{ APIVersion: mcfgv1alpha.SchemeGroupVersion.String(), @@ -98,14 +92,13 @@ func (f *OSImageStream) Generate(_ context.Context, dependencies asset.Parents) osImageStreamData, err := yaml.Marshal(osImageStream) if err != nil { - return fmt.Errorf("failed to create %s manifests from InstallConfig. Error: %w", f.Name(), err) + return nil, fmt.Errorf("failed to create %s manifests from InstallConfig. Error: %w", f.Name(), err) } - f.FileList = append(f.FileList, &asset.File{ + return &asset.File{ Filename: osImageStreamFileName, Data: osImageStreamData, - }) - return nil + }, nil } // Files returns the files generated by the asset. From 537d719ed8edd3c0368e182511e6d2ce4ea20fa8 Mon Sep 17 00:00:00 2001 From: Dalia Khater Date: Thu, 30 Apr 2026 10:25:58 -0500 Subject: [PATCH 3/4] agent: Address review feedback on AgentOSImageStream - Pass stream value instead of whole asset to getRootFSURL - Remove IPI OSImageStream dependency from AgentManifests - Read from AgentClusterInstall.installConfigOverrides (Layer 2) - Query cluster OSImageStream CR for add-nodes workflow --- pkg/asset/agent/image/agentimage.go | 2 +- pkg/asset/agent/image/baseiso.go | 4 +- pkg/asset/agent/manifests/agent.go | 6 -- pkg/asset/agent/manifests/osimagestream.go | 85 ++++++++++++++++++---- 4 files changed, 73 insertions(+), 24 deletions(-) diff --git a/pkg/asset/agent/image/agentimage.go b/pkg/asset/agent/image/agentimage.go index 629480870a4..0f3788c3c3c 100644 --- a/pkg/asset/agent/image/agentimage.go +++ b/pkg/asset/agent/image/agentimage.go @@ -108,7 +108,7 @@ func (a *AgentImage) Generate(ctx context.Context, dependencies asset.Parents) e logrus.Debugf("Using custom rootfs URL: %s", a.rootFSURL) } else { // Default to the URL from the RHCOS streams file - defaultRootFSURL, err := baseIso.getRootFSURL(ctx, a.cpuArch, agentWorkflow, clusterInfo, osImageStream) + defaultRootFSURL, err := baseIso.getRootFSURL(ctx, a.cpuArch, agentWorkflow, clusterInfo, osImageStream.Stream) if err != nil { return err } diff --git a/pkg/asset/agent/image/baseiso.go b/pkg/asset/agent/image/baseiso.go index ba4befe1088..a9709ba5f07 100644 --- a/pkg/asset/agent/image/baseiso.go +++ b/pkg/asset/agent/image/baseiso.go @@ -36,8 +36,8 @@ func (i *BaseIso) Name() string { } // Fetch RootFS URL using the rhcos.json. -func (i *BaseIso) getRootFSURL(ctx context.Context, archName string, agentWorkflow *workflow.AgentWorkflow, clusterInfo *joiner.ClusterInfo, osImageStream *manifests.AgentOSImageStream) (string, error) { - metal, err := rhcos.GetMetalArtifact(ctx, archName, customStreamGetter(agentWorkflow, clusterInfo), osImageStream.Stream) +func (i *BaseIso) getRootFSURL(ctx context.Context, archName string, agentWorkflow *workflow.AgentWorkflow, clusterInfo *joiner.ClusterInfo, osImageStream types.OSImageStream) (string, error) { + metal, err := rhcos.GetMetalArtifact(ctx, archName, customStreamGetter(agentWorkflow, clusterInfo), osImageStream) if err != nil { return "", err } diff --git a/pkg/asset/agent/manifests/agent.go b/pkg/asset/agent/manifests/agent.go index 265ea5649a6..55566272112 100644 --- a/pkg/asset/agent/manifests/agent.go +++ b/pkg/asset/agent/manifests/agent.go @@ -16,7 +16,6 @@ import ( "github.com/openshift/installer/pkg/asset" "github.com/openshift/installer/pkg/asset/agent/workflow" workflowreport "github.com/openshift/installer/pkg/asset/agent/workflow/report" - "github.com/openshift/installer/pkg/asset/manifests" ) const ( @@ -56,7 +55,6 @@ func (m *AgentManifests) Dependencies() []asset.Asset { &AgentClusterInstall{}, &ClusterDeployment{}, &ClusterImageSet{}, - &manifests.OSImageStream{}, } } @@ -73,7 +71,6 @@ func (m *AgentManifests) Generate(ctx context.Context, dependencies asset.Parent &AgentClusterInstall{}, &ClusterDeployment{}, &ClusterImageSet{}, - &manifests.OSImageStream{}, } { dependencies.Get(a) @@ -96,9 +93,6 @@ func (m *AgentManifests) Generate(ctx context.Context, dependencies asset.Parent m.ClusterDeployment = v.Config case *ClusterImageSet: m.ClusterImageSet = v.Config - case *manifests.OSImageStream: - // OSImageStream is optional, only generated when install-config is provided - // No need to store in AgentManifests struct } m.FileList = append(m.FileList, a.Files()...) diff --git a/pkg/asset/agent/manifests/osimagestream.go b/pkg/asset/agent/manifests/osimagestream.go index fe72bff59df..817f4baccb1 100644 --- a/pkg/asset/agent/manifests/osimagestream.go +++ b/pkg/asset/agent/manifests/osimagestream.go @@ -2,14 +2,25 @@ package manifests import ( "context" + "encoding/json" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + hiveext "github.com/openshift/assisted-service/api/hiveextension/v1beta1" + mcfgclient "github.com/openshift/client-go/machineconfiguration/clientset/versioned" "github.com/openshift/installer/pkg/asset" - "github.com/openshift/installer/pkg/asset/agent" + "github.com/openshift/installer/pkg/asset/agent/joiner" "github.com/openshift/installer/pkg/asset/agent/workflow" "github.com/openshift/installer/pkg/rhcos" "github.com/openshift/installer/pkg/types" ) +// installConfigOverridesData is used to parse the installConfigOverrides annotation. +type installConfigOverridesData struct { + OSImageStream types.OSImageStream `json:"osImageStream,omitempty"` +} + // AgentOSImageStream exposes the OS image stream value for Agent workflows. // This is a pure data asset that does not generate any files. type AgentOSImageStream struct { @@ -27,32 +38,40 @@ func (*AgentOSImageStream) Name() string { // the asset. func (*AgentOSImageStream) Dependencies() []asset.Asset { return []asset.Asset{ - &agent.OptionalInstallConfig{}, &workflow.AgentWorkflow{}, + &joiner.ClusterInfo{}, + &AgentManifests{}, } } // Generate extracts the OS image stream value from available sources. -func (a *AgentOSImageStream) Generate(_ context.Context, dependencies asset.Parents) error { - installConfig := &agent.OptionalInstallConfig{} +func (a *AgentOSImageStream) Generate(ctx context.Context, dependencies asset.Parents) error { agentWorkflow := &workflow.AgentWorkflow{} - dependencies.Get(installConfig, agentWorkflow) + clusterInfo := &joiner.ClusterInfo{} + agentManifests := &AgentManifests{} + dependencies.Get(agentWorkflow, clusterInfo, agentManifests) var stream types.OSImageStream - // Only generate for install workflow, not add-nodes - if agentWorkflow.Workflow != workflow.AgentWorkflowTypeInstall { - return nil - } + switch agentWorkflow.Workflow { + case workflow.AgentWorkflowTypeInstall: + // For install workflow, read from AgentClusterInstall manifest (Layer 2) + if agentManifests.AgentClusterInstall != nil { + stream = a.getStreamFromAgentClusterInstall(agentManifests.AgentClusterInstall) + } - // Try install-config (if provided) - if installConfig.Supplied && installConfig.Config != nil { - stream = installConfig.Config.OSImageStream + case workflow.AgentWorkflowTypeAddNodes: + // For add-nodes workflow, query the cluster's OSImageStream CR + if clusterInfo.OpenshiftMachineConfigClient != nil { + var err error + stream, err = a.getStreamFromCluster(ctx, clusterInfo.OpenshiftMachineConfigClient) + if err != nil { + // Log but don't fail - fall back to default + logrus.Warnf("Failed to query cluster OSImageStream: %v", err) + } + } } - // TODO: Add fallback to AgentClusterInstall.Spec.OSImageStream once - // the field is available in the assisted-service API - // Default if stream == "" { stream = rhcos.DefaultOSImageStream @@ -61,3 +80,39 @@ func (a *AgentOSImageStream) Generate(_ context.Context, dependencies asset.Pare a.Stream = stream return nil } + +// getStreamFromAgentClusterInstall extracts the osImageStream from the +// AgentClusterInstall installConfigOverrides annotation. +func (a *AgentOSImageStream) getStreamFromAgentClusterInstall(aci *hiveext.AgentClusterInstall) types.OSImageStream { + if aci.Annotations == nil { + return "" + } + + overridesJSON, ok := aci.Annotations[installConfigOverrides] + if !ok { + return "" + } + + var overrides installConfigOverridesData + if err := json.Unmarshal([]byte(overridesJSON), &overrides); err != nil { + logrus.Debugf("Failed to parse installConfigOverrides: %v", err) + return "" + } + + return overrides.OSImageStream +} + +// getStreamFromCluster queries the cluster's OSImageStream CR to determine +// the default stream. +func (a *AgentOSImageStream) getStreamFromCluster(ctx context.Context, client mcfgclient.Interface) (types.OSImageStream, error) { + osImageStream, err := client.MachineconfigurationV1alpha1().OSImageStreams().Get(ctx, "cluster", metav1.GetOptions{}) + if err != nil { + return "", err + } + + if osImageStream.Spec == nil { + return "", nil + } + + return types.OSImageStream(osImageStream.Spec.DefaultStream), nil +} From ac9df48c350d2664606abd7a4768d6a734e3e6b2 Mon Sep 17 00:00:00 2001 From: Dalia Khater Date: Tue, 5 May 2026 07:44:04 -0500 Subject: [PATCH 4/4] rhcos: Fix stream-specific file paths in machine-os-images RHEL 10 uses different file naming in machine-os-images container: - rhel-9: /coreos/coreos-{arch}.iso - rhel-10: /coreos/coreos10-{arch}.iso Updated releaseextract to use stream-aware file path helpers: - getCoreOsFileName() - getCoreOsSha256FileName() - getCoreOsStreamFileName() Without this fix, rhel-10 deployments fail when extracting the boot ISO from the release payload. --- pkg/asset/rhcos/iso.go | 19 +++++++----- pkg/asset/rhcos/iso_test.go | 6 ++-- pkg/asset/rhcos/releaseextract.go | 51 ++++++++++++++++++++++--------- 3 files changed, 53 insertions(+), 23 deletions(-) diff --git a/pkg/asset/rhcos/iso.go b/pkg/asset/rhcos/iso.go index 188e7c8bb87..6c289609e91 100644 --- a/pkg/asset/rhcos/iso.go +++ b/pkg/asset/rhcos/iso.go @@ -22,8 +22,9 @@ import ( // BaseIso generates the base ISO file for the image. type BaseIso struct { - streamGetter CoreOSBuildFetcher - ocRelease ReleasePayload + streamGetter CoreOSBuildFetcher + ocRelease ReleasePayload + osImageStream types.OSImageStream } // CoreOSBuildFetcher will be to used to switch the source of the coreos metadata. @@ -44,11 +45,15 @@ func NewBaseISOFetcher(ocRelease ReleasePayload, streamGetter CoreOSBuildFetcher } } else { streamGetter = defaultCoreOSStreamGetter + osImageStream = rhcos.DefaultOSImageStream } + } else if osImageStream == "" { + osImageStream = rhcos.DefaultOSImageStream } return &BaseIso{ - streamGetter: streamGetter, - ocRelease: ocRelease, + streamGetter: streamGetter, + ocRelease: ocRelease, + osImageStream: osImageStream, } } @@ -133,14 +138,14 @@ func (i *BaseIso) checkReleasePayloadBaseISOVersion(ctx context.Context, r Relea logrus.Debugf("Checking release payload base ISO version") // Get current release payload CoreOS version - payloadRelease, err := r.GetBaseIsoVersion(archName) + payloadRelease, err := r.GetBaseIsoVersion(archName, i.osImageStream) if err != nil { logrus.Warnf("unable to determine base ISO version: %s", err.Error()) return } // Get pinned version from installer - metal, err := GetMetalArtifact(ctx, archName, i.streamGetter, "") + metal, err := GetMetalArtifact(ctx, archName, i.streamGetter, i.osImageStream) if err != nil { logrus.Warnf("unable to determine base ISO version: %s", err.Error()) return @@ -165,7 +170,7 @@ func (i *BaseIso) retrieveBaseIso(ctx context.Context, archName string) (string, if err := workflowreport.GetReport(ctx).SubStage(workflow.StageFetchBaseISOExtract); err != nil { return "", err } - baseIsoFileName, err := i.ocRelease.GetBaseIso(archName, i.streamGetter) + baseIsoFileName, err := i.ocRelease.GetBaseIso(archName, i.streamGetter, i.osImageStream) if err == nil { if err := workflowreport.GetReport(ctx).SubStage(workflow.StageFetchBaseISOVerify); err != nil { return "", err diff --git a/pkg/asset/rhcos/iso_test.go b/pkg/asset/rhcos/iso_test.go index c3049c5f73f..36622d0480e 100644 --- a/pkg/asset/rhcos/iso_test.go +++ b/pkg/asset/rhcos/iso_test.go @@ -12,6 +12,8 @@ import ( "github.com/coreos/stream-metadata-go/stream" "github.com/stretchr/testify/assert" + + "github.com/openshift/installer/pkg/types" ) func TestBaseIso(t *testing.T) { @@ -116,14 +118,14 @@ type mockRelease struct { baseIsoError error } -func (m *mockRelease) GetBaseIso(architecture string, streamGetter CoreOSBuildFetcher) (string, error) { +func (m *mockRelease) GetBaseIso(architecture string, streamGetter CoreOSBuildFetcher, osImageStream types.OSImageStream) (string, error) { if m.baseIsoError != nil { return "", m.baseIsoError } return m.baseIsoFileName, nil } -func (m *mockRelease) GetBaseIsoVersion(architecture string) (string, error) { +func (m *mockRelease) GetBaseIsoVersion(architecture string, osImageStream types.OSImageStream) (string, error) { return m.isoBaseVersion, nil } diff --git a/pkg/asset/rhcos/releaseextract.go b/pkg/asset/rhcos/releaseextract.go index 5cf80760f58..d7bd92d8df0 100644 --- a/pkg/asset/rhcos/releaseextract.go +++ b/pkg/asset/rhcos/releaseextract.go @@ -27,16 +27,38 @@ import ( ) const ( - machineOsImageName = "machine-os-images" - coreOsFileName = "/coreos/coreos-%s.iso" - coreOsSha256FileName = "/coreos/coreos-%s.iso.sha256" - coreOsStreamFileName = "/coreos/coreos-stream.json" + machineOsImageName = "machine-os-images" // ocDefaultTries is the number of times to execute the oc command on failures. ocDefaultTries = 5 // ocDefaultRetryDelay is the time between retries. ocDefaultRetryDelay = time.Second * 5 ) +// getCoreOsFileName returns the ISO file path in machine-os-images based on stream. +func getCoreOsFileName(stream types.OSImageStream, architecture string) string { + // rhel-10 uses coreos10- prefix, rhel-9 uses coreos- prefix + if stream == types.OSImageStreamRHCOS10 { + return fmt.Sprintf("/coreos/coreos10-%s.iso", architecture) + } + return fmt.Sprintf("/coreos/coreos-%s.iso", architecture) +} + +// getCoreOsSha256FileName returns the ISO checksum file path based on stream. +func getCoreOsSha256FileName(stream types.OSImageStream, architecture string) string { + if stream == types.OSImageStreamRHCOS10 { + return fmt.Sprintf("/coreos/coreos10-%s.iso.sha256", architecture) + } + return fmt.Sprintf("/coreos/coreos-%s.iso.sha256", architecture) +} + +// getCoreOsStreamFileName returns the stream metadata file path based on stream. +func getCoreOsStreamFileName(stream types.OSImageStream) string { + if stream == types.OSImageStreamRHCOS10 { + return "/coreos/coreos10-stream.json" + } + return "/coreos/coreos-stream.json" +} + // ExtractConfig is used to set up the retries for extracting the base ISO. type ExtractConfig struct { MaxTries uint @@ -45,8 +67,8 @@ type ExtractConfig struct { // ReleasePayload is the interface to use the oc command to the get image info. type ReleasePayload interface { - GetBaseIso(architecture string, streamGetter CoreOSBuildFetcher) (string, error) - GetBaseIsoVersion(architecture string) (string, error) + GetBaseIso(architecture string, streamGetter CoreOSBuildFetcher, osImageStream types.OSImageStream) (string, error) + GetBaseIsoVersion(architecture string, osImageStream types.OSImageStream) (string, error) ExtractFile(image string, filename string, architecture string) ([]string, error) } @@ -93,7 +115,7 @@ func (r *releasePayload) ExtractFile(image string, filename string, architecture } // Get the CoreOS ISO from the releaseImage. -func (r *releasePayload) GetBaseIso(architecture string, streamGetter CoreOSBuildFetcher) (string, error) { +func (r *releasePayload) GetBaseIso(architecture string, streamGetter CoreOSBuildFetcher, osImageStream types.OSImageStream) (string, error) { // Get the machine-os-images pullspec from the release and use that to get the CoreOS ISO image, err := r.getImageFromRelease(machineOsImageName, architecture) if err != nil { @@ -105,7 +127,7 @@ func (r *releasePayload) GetBaseIso(architecture string, streamGetter CoreOSBuil return "", err } - filename := fmt.Sprintf(coreOsFileName, architecture) + filename := getCoreOsFileName(osImageStream, architecture) // Check if file is already cached cachedFile, err := cache.GetFileFromCache(path.Base(filename), cacheDir) if err != nil { @@ -113,7 +135,7 @@ func (r *releasePayload) GetBaseIso(architecture string, streamGetter CoreOSBuil } if cachedFile != "" { logrus.Info("Verifying cached file") - valid, err := r.verifyCacheFile(image, cachedFile, architecture, streamGetter) + valid, err := r.verifyCacheFile(image, cachedFile, architecture, streamGetter, osImageStream) if err != nil { return "", err } @@ -132,14 +154,15 @@ func (r *releasePayload) GetBaseIso(architecture string, streamGetter CoreOSBuil return path[0], err } -func (r *releasePayload) GetBaseIsoVersion(architecture string) (string, error) { - files, err := r.ExtractFile(machineOsImageName, coreOsStreamFileName, architecture) +func (r *releasePayload) GetBaseIsoVersion(architecture string, osImageStream types.OSImageStream) (string, error) { + streamFileName := getCoreOsStreamFileName(osImageStream) + files, err := r.ExtractFile(machineOsImageName, streamFileName, architecture) if err != nil { return "", err } if len(files) > 1 { - return "", fmt.Errorf("too many files found for %s", coreOsStreamFileName) + return "", fmt.Errorf("too many files found for %s", streamFileName) } rawData, err := os.ReadFile(files[0]) @@ -298,7 +321,7 @@ func matchingHash(imageSha []byte, sha string) bool { } // Check if there is a different base ISO in the release payload. -func (r *releasePayload) verifyCacheFile(image, file, architecture string, streamGetter CoreOSBuildFetcher) (bool, error) { +func (r *releasePayload) verifyCacheFile(image, file, architecture string, streamGetter CoreOSBuildFetcher, osImageStream types.OSImageStream) (bool, error) { // Get hash of cached file f, err := os.Open(file) if err != nil { @@ -327,7 +350,7 @@ func (r *releasePayload) verifyCacheFile(image, file, architecture string, strea defer os.RemoveAll(tempDir) - shaFilename := fmt.Sprintf(coreOsSha256FileName, architecture) + shaFilename := getCoreOsSha256FileName(osImageStream, architecture) shaFile, err := r.extractFileFromImage(image, shaFilename, tempDir, architecture) if err != nil { logrus.Debug("Could not get SHA from payload for cache comparison")