From 71bfc4fd20753bc916ea3e42f6e6410f7c370e7f Mon Sep 17 00:00:00 2001 From: Pablo Rodriguez Nava Date: Tue, 5 May 2026 08:41:51 +0200 Subject: [PATCH] MCO-2181: Default version switchover This change makes the installer pick the proper bootimages, ISOs and OS Image Stream depending on FeatureGate presence or build version if there's no FeatureGate at hand. Signed-off-by: Pablo Rodriguez Nava --- pkg/asset/installconfig/aws/platform.go | 4 +- pkg/asset/rhcos/iso.go | 2 +- pkg/asset/rhcos/releaseextract.go | 2 +- pkg/coreoscli/cmd.go | 11 ++++- pkg/destroy/bootstrap/bootstrap.go | 6 +-- pkg/rhcos/stream.go | 20 ++------ pkg/rhcos/stream_scos.go | 6 --- pkg/types/defaults/installconfig.go | 6 +++ pkg/types/defaults/machinepools.go | 3 +- pkg/types/installconfig.go | 6 +-- pkg/types/utils.go | 26 ++++++---- pkg/types/validation/installconfig.go | 8 ++-- pkg/version/version.go | 63 ++++++++++++++++++++++--- 13 files changed, 109 insertions(+), 54 deletions(-) diff --git a/pkg/asset/installconfig/aws/platform.go b/pkg/asset/installconfig/aws/platform.go index bda29d76927..e9ee9a9469f 100644 --- a/pkg/asset/installconfig/aws/platform.go +++ b/pkg/asset/installconfig/aws/platform.go @@ -11,13 +11,13 @@ import ( "github.com/sirupsen/logrus" "github.com/openshift/installer/pkg/rhcos" + "github.com/openshift/installer/pkg/types" "github.com/openshift/installer/pkg/types/aws" - "github.com/openshift/installer/pkg/version" ) // Platform collects AWS-specific configuration. func Platform(ctx context.Context) (*aws.Platform, error) { - architecture := version.DefaultArch() + architecture := types.DefaultArch() regions, err := knownPublicRegions(architecture, rhcos.DefaultOSImageStream) if err != nil { return nil, fmt.Errorf("failed to get AWS public regions: %w", err) diff --git a/pkg/asset/rhcos/iso.go b/pkg/asset/rhcos/iso.go index a38f86f38a9..87c9c090496 100644 --- a/pkg/asset/rhcos/iso.go +++ b/pkg/asset/rhcos/iso.go @@ -57,7 +57,7 @@ func GetMetalArtifact(ctx context.Context, archName string) (stream.PlatformArti ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() - st, err := rhcos.FetchCoreOSBuild(ctx, rhcos.DefaultOSImageStream) + st, err := rhcos.FetchCoreOSBuild(ctx, types.OSImageStreamRHCOS9) if err != nil { return stream.PlatformArtifacts{}, err } diff --git a/pkg/asset/rhcos/releaseextract.go b/pkg/asset/rhcos/releaseextract.go index 4f8d0f6a640..7a4e3ced765 100644 --- a/pkg/asset/rhcos/releaseextract.go +++ b/pkg/asset/rhcos/releaseextract.go @@ -270,7 +270,7 @@ func (r *releasePayload) getHashFromInstaller(architecture string) (bool, string ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second) defer cancel() - st, err := rhcos.FetchCoreOSBuild(ctx, rhcos.DefaultOSImageStream) + st, err := rhcos.FetchCoreOSBuild(ctx, types.OSImageStreamRHCOS9) if err != nil { return false, "" } diff --git a/pkg/coreoscli/cmd.go b/pkg/coreoscli/cmd.go index d36aa1aa866..8a614bf3e4b 100644 --- a/pkg/coreoscli/cmd.go +++ b/pkg/coreoscli/cmd.go @@ -5,6 +5,8 @@ import ( "fmt" "os" + "github.com/openshift/installer/pkg/types/defaults" + "github.com/spf13/cobra" "github.com/openshift/installer/pkg/rhcos" @@ -13,11 +15,12 @@ import ( // printStreamJSON is the implementation of print-stream-json func printStreamJSON(cmd *cobra.Command, _ []string) error { - osImageStream := rhcos.DefaultOSImageStream streamFlag, err := cmd.Flags().GetString("stream") if err != nil { return err } + + osImageStream := rhcos.DefaultOSImageStream if streamFlag != "" { s := types.OSImageStream(streamFlag) valid := false @@ -31,7 +34,13 @@ func printStreamJSON(cmd *cobra.Command, _ []string) error { return fmt.Errorf("invalid value %q for --stream; must be one of %v", streamFlag, types.OSImageStreamValues) } osImageStream = s + } else { + // If no stream is given get it from the default FeatureSet for the build version + installConfig := types.InstallConfig{} + defaults.SetInstallConfigDefaults(&installConfig) + osImageStream = installConfig.OSImageStream } + streamData, err := rhcos.FetchRawCoreOSStream(context.Background(), osImageStream) if err != nil { return err diff --git a/pkg/destroy/bootstrap/bootstrap.go b/pkg/destroy/bootstrap/bootstrap.go index 953680ad395..676aab9bdad 100644 --- a/pkg/destroy/bootstrap/bootstrap.go +++ b/pkg/destroy/bootstrap/bootstrap.go @@ -69,9 +69,9 @@ func Destroy(ctx context.Context, dir string) (err error) { } } - featureSets, ok := types.FeatureSetsForProfile() - if !ok { - return fmt.Errorf("no feature sets for cluster profile %q", types.GetClusterProfileName()) + featureSets, err := types.FeatureSetsForProfile() + if err != nil { + return fmt.Errorf("no feature sets for cluster profile %q. %w", types.GetClusterProfileName(), err) } fg := featuregates.FeatureGateFromFeatureSets(featureSets, metadata.FeatureSet, metadata.CustomFeatureSet) diff --git a/pkg/rhcos/stream.go b/pkg/rhcos/stream.go index 16c8683cbf0..678112c36c2 100644 --- a/pkg/rhcos/stream.go +++ b/pkg/rhcos/stream.go @@ -8,14 +8,9 @@ import ( "github.com/openshift/installer/pkg/types" ) -const ( - // DefaultOSImageStream is the OS image stream used when the install-config - // does not specify one. - DefaultOSImageStream = types.OSImageStreamRHCOS9 - - payloadImageStreamTagRHCOS9 = "rhel-coreos" - payloadImageStreamTagRHCOS10 = "rhel-coreos-10" -) +// DefaultOSImageStream is the OS image stream used when the install-config +// does not specify one. +const DefaultOSImageStream = types.OSImageStreamRHCOS10 func getStreamFileName(stream types.OSImageStream) string { return fmt.Sprintf("coreos/coreos-%v.json", stream) @@ -24,12 +19,3 @@ func getStreamFileName(stream types.OSImageStream) string { func getMarketplaceStreamFileName(stream types.OSImageStream) string { return fmt.Sprintf("coreos/marketplace/coreos-%v.json", stream) } - -// GetPayloadImageStreamTag returns the payload image stream tag corresponding -// to the given OS image stream. -func GetPayloadImageStreamTag(stream types.OSImageStream) string { - if stream == types.OSImageStreamRHCOS9 { - return payloadImageStreamTagRHCOS9 - } - return payloadImageStreamTagRHCOS10 -} diff --git a/pkg/rhcos/stream_scos.go b/pkg/rhcos/stream_scos.go index 518aa0b1f5f..687eac51e3c 100644 --- a/pkg/rhcos/stream_scos.go +++ b/pkg/rhcos/stream_scos.go @@ -18,9 +18,3 @@ func getMarketplaceStreamFileName(_ types.OSImageStream) string { // functions will gracefully handle the missing file. return "coreos/marketplace/marketplace-scos.json" } - -// GetPayloadImageStreamTag returns the payload image stream tag corresponding -// to the given OS image stream. For SCOS, this always returns "stream-coreos". -func GetPayloadImageStreamTag(_ types.OSImageStream) string { - return "stream-coreos" -} diff --git a/pkg/types/defaults/installconfig.go b/pkg/types/defaults/installconfig.go index 6e88ba5aba7..c7c1be3f8a8 100644 --- a/pkg/types/defaults/installconfig.go +++ b/pkg/types/defaults/installconfig.go @@ -1,6 +1,7 @@ package defaults import ( + "github.com/openshift/api/features" operv1 "github.com/openshift/api/operator/v1" "github.com/openshift/installer/pkg/ipnet" "github.com/openshift/installer/pkg/rhcos" @@ -137,6 +138,11 @@ func SetInstallConfigDefaults(c *types.InstallConfig) { if c.OSImageStream == "" { c.OSImageStream = rhcos.DefaultOSImageStream + if !c.IsSCOS() && !c.Enabled(features.FeatureGateOSStreams) { + // Use RHEL 9 by default only if the FG is disabled + // OKD uses only 1 stream so this condition doesn't apply + c.OSImageStream = types.OSImageStreamRHCOS9 + } } if c.AdditionalTrustBundlePolicy == "" { diff --git a/pkg/types/defaults/machinepools.go b/pkg/types/defaults/machinepools.go index cf778e1e670..ad5475d98a0 100644 --- a/pkg/types/defaults/machinepools.go +++ b/pkg/types/defaults/machinepools.go @@ -10,7 +10,6 @@ import ( azuredefaults "github.com/openshift/installer/pkg/types/azure/defaults" "github.com/openshift/installer/pkg/types/gcp" gcpdefaults "github.com/openshift/installer/pkg/types/gcp/defaults" - "github.com/openshift/installer/pkg/version" ) // SetMachinePoolDefaults sets the defaults for the machine pool. @@ -26,7 +25,7 @@ func SetMachinePoolDefaults(p *types.MachinePool, platform *types.Platform) { p.Hyperthreading = types.HyperthreadingEnabled } if p.Architecture == "" { - p.Architecture = version.DefaultArch() + p.Architecture = types.DefaultArch() } if p.Fencing != nil { diff --git a/pkg/types/installconfig.go b/pkg/types/installconfig.go index fcfda23cc9e..faace481566 100644 --- a/pkg/types/installconfig.go +++ b/pkg/types/installconfig.go @@ -623,9 +623,9 @@ func (c *InstallConfig) EnabledFeatureGates() featuregates.FeatureGate { customFS = featuregates.GenerateCustomFeatures(c.FeatureGates) } - featureSets, ok := FeatureSetsForProfile() - if !ok { - logrus.Warnf("no feature sets for cluster profile %q", GetClusterProfileName()) + featureSets, err := FeatureSetsForProfile() + if err != nil { + logrus.Warnf("no feature sets for cluster profile %q. %v", GetClusterProfileName(), err) } fg := featuregates.FeatureGateFromFeatureSets(featureSets, c.FeatureSet, customFS) diff --git a/pkg/types/utils.go b/pkg/types/utils.go index 99c8014fb15..fa03d42a284 100644 --- a/pkg/types/utils.go +++ b/pkg/types/utils.go @@ -4,6 +4,8 @@ import ( "fmt" "os" + "github.com/openshift/installer/pkg/version" + "github.com/sirupsen/logrus" capz "sigs.k8s.io/cluster-api-provider-azure/api/v1beta1" @@ -43,21 +45,24 @@ func MachineNetworksToCIDRs(nets []MachineNetworkEntry) []configv1.CIDR { return res } -// openshiftMajorVersion is the major version of OpenShift that this installer targets. -// This is used when looking up feature sets from the API. -const openshiftMajorVersion uint64 = 4 - // FeatureSetsForProfile returns the feature sets for the current cluster profile // and OpenShift major version. -func FeatureSetsForProfile() (map[configv1.FeatureSet]*features.FeatureGateEnabledDisabled, bool) { +func FeatureSetsForProfile() (map[configv1.FeatureSet]*features.FeatureGateEnabledDisabled, error) { clusterProfile := GetClusterProfileName() allSets := features.AllFeatureSets() - versionSets, ok := allSets[openshiftMajorVersion] + versionInfo, err := version.GetVersionInfo() + if err != nil { + return nil, fmt.Errorf("unable to determine version information to calculate FeatureSets: %w", err) + } + versionSets, ok := allSets[uint64(versionInfo.Major)] if !ok { - return nil, false + return nil, fmt.Errorf("no FeatureSet available for version %d", versionInfo.Major) } profileSets, ok := versionSets[clusterProfile] - return profileSets, ok + if !ok { + return nil, fmt.Errorf("no FeatureSet available for %s cluster profile", clusterProfile) + } + return profileSets, nil } // GetClusterProfileName utility method to retrieve the cluster profile setting. This is used @@ -103,3 +108,8 @@ func (c *InstallConfig) CreateAzureIdentity() bool { return defaultNeedsID && (computeNeedsID || cpNeedsID) } + +// DefaultArch returns the default release architecture +func DefaultArch() Architecture { + return Architecture(version.DefaultArch()) +} diff --git a/pkg/types/validation/installconfig.go b/pkg/types/validation/installconfig.go index d6c088ee596..d5f0066ffee 100644 --- a/pkg/types/validation/installconfig.go +++ b/pkg/types/validation/installconfig.go @@ -1574,9 +1574,9 @@ func validateAdditionalCABundlePolicy(c *types.InstallConfig) error { func ValidateFeatureSet(c *types.InstallConfig) field.ErrorList { allErrs := field.ErrorList{} - featureSets, ok := types.FeatureSetsForProfile() - if !ok { - logrus.Warnf("no feature sets for cluster profile %q", types.GetClusterProfileName()) + featureSets, err := types.FeatureSetsForProfile() + if err != nil { + logrus.Warnf("no feature sets for cluster profile %q. %s", types.GetClusterProfileName(), err) } if _, ok := featureSets[c.FeatureSet]; c.FeatureSet != configv1.CustomNoUpgrade && !ok { sortedFeatureSets := func() []string { @@ -1679,7 +1679,7 @@ func validateGatedFeatures(c *types.InstallConfig) field.ErrorList { func validateReleaseArchitecture(controlPlanePool *types.MachinePool, computePool []types.MachinePool, releaseArch types.Architecture) field.ErrorList { allErrs := field.ErrorList{} - clusterArch := version.DefaultArch() + clusterArch := types.DefaultArch() if controlPlanePool != nil && controlPlanePool.Architecture != "" { clusterArch = controlPlanePool.Architecture } diff --git a/pkg/version/version.go b/pkg/version/version.go index 730e553d99d..b4f296ab4a2 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -4,13 +4,51 @@ package version import ( "fmt" "os" + "strconv" "strings" "github.com/sirupsen/logrus" - - "github.com/openshift/installer/pkg/types" ) +// VersionInfo represents a parsed semantic version. +type VersionInfo struct { + Major int + Minor int + Patch int +} + +// GetVersionInfo returns the build version parsed as a VersionInfo. +func GetVersionInfo() (VersionInfo, error) { + s, err := Version() + if err != nil { + return VersionInfo{}, err + } + return parseVersionInfo(s) +} + +func parseVersionInfo(s string) (VersionInfo, error) { + if idx := strings.Index(s, "-"); idx != -1 { + s = s[:idx] + } + parts := strings.Split(s, ".") + if len(parts) == 0 { + return VersionInfo{}, fmt.Errorf("invalid version %q", s) + } + var v VersionInfo + major, err := strconv.Atoi(parts[0]) + if err != nil { + return VersionInfo{}, fmt.Errorf("invalid version %q", s) + } + v.Major = major + if len(parts) > 1 { + v.Minor, _ = strconv.Atoi(parts[1]) + } + if len(parts) > 2 { + v.Patch, _ = strconv.Atoi(parts[2]) + } + return v, nil +} + // This file handles correctly identifying the default release version, which is expected to be // replaced in the binary post-compile by the release name extracted from a payload. The expected modification is: // @@ -65,7 +103,7 @@ func String() (string, error) { // Version returns the installer/release version. func Version() (string, error) { if strings.HasPrefix(defaultVersionPadded, defaultVersionPrefix) { - return Raw, nil + return removeGoVersionPrefix(Raw), nil } nullTerminator := strings.IndexByte(defaultVersionPadded, '\x00') if nullTerminator == -1 { @@ -125,7 +163,20 @@ func cleanArch(releaseArchitecture string) string { return strings.ReplaceAll(releaseArchitecture, "linux/", "") } -// DefaultArch returns the default release architecture -func DefaultArch() types.Architecture { - return types.Architecture(defaultArch) +// removeGoVersionPrefix converts a Go module version tag (e.g. "v1.4.22") +// into the corresponding OCP version (e.g. "4.22"). +func removeGoVersionPrefix(version string) string { + if strings.HasPrefix(version, "v") { + version = strings.TrimPrefix(version, "v") + parts := strings.SplitN(version, ".", 2) + if len(parts) > 1 { + version = parts[1] + } + } + return version +} + +// DefaultArch returns the default release architecture embedded in the binary +func DefaultArch() string { + return defaultArch }