From 3ed9315bc6e954c95be00d7637cfa69bbdddfd68 Mon Sep 17 00:00:00 2001 From: Patrick Dillon Date: Thu, 14 May 2026 16:02:40 -0400 Subject: [PATCH 1/2] CORS-4495: Populate RHCOS10 Marketplace Stream Updates the script to populate azure marketplace images to include the rhel10 stream. If RHCOS10 images are not present, we can fall back to RHCOS9, as the first-boot image will be updated to RHCOS10. --- .../coreos/marketplace/coreos-rhel-10.json | 116 ++++++++++++++++++ .../coreos/marketplace/coreos-rhel-9.json | 36 +++--- .../rhcos/populate-marketplace-imagestream.go | 81 ++++++++---- pkg/rhcos/marketplace/azure/azure.go | 28 +++++ 4 files changed, 222 insertions(+), 39 deletions(-) create mode 100644 data/data/coreos/marketplace/coreos-rhel-10.json diff --git a/data/data/coreos/marketplace/coreos-rhel-10.json b/data/data/coreos/marketplace/coreos-rhel-10.json new file mode 100644 index 00000000000..aa6ca21ce1f --- /dev/null +++ b/data/data/coreos/marketplace/coreos-rhel-10.json @@ -0,0 +1,116 @@ +{ + "aarch64": { + "azure": { + "no-purchase-plan": { + "hyperVGen2": { + "publisher": "azureopenshift", + "offer": "aro4", + "sku": "aro_422-arm", + "version": "9.8.20260428" + } + } + } + }, + "x86_64": { + "azure": { + "no-purchase-plan": { + "hyperVGen1": { + "publisher": "azureopenshift", + "offer": "aro4", + "sku": "aro_422", + "version": "9.8.20260428" + }, + "hyperVGen2": { + "publisher": "azureopenshift", + "offer": "aro4", + "sku": "aro_422-v2", + "version": "9.8.20260428" + } + }, + "ocp": { + "hyperVGen1": { + "publisher": "redhat", + "offer": "rh-ocp-worker", + "sku": "rh-ocp-worker-gen1", + "version": "9.6.2026030314" + }, + "hyperVGen2": { + "publisher": "redhat", + "offer": "rh-ocp-worker", + "sku": "rh-ocp-worker", + "version": "9.6.2026030314" + } + }, + "opp": { + "hyperVGen1": { + "publisher": "redhat", + "offer": "rh-opp-worker", + "sku": "rh-opp-worker-gen1", + "version": "9.6.2026030314" + }, + "hyperVGen2": { + "publisher": "redhat", + "offer": "rh-opp-worker", + "sku": "rh-opp-worker", + "version": "9.6.2026030314" + } + }, + "oke": { + "hyperVGen1": { + "publisher": "redhat", + "offer": "rh-oke-worker", + "sku": "rh-oke-worker-gen1", + "version": "9.6.2026030314" + }, + "hyperVGen2": { + "publisher": "redhat", + "offer": "rh-oke-worker", + "sku": "rh-oke-worker", + "version": "9.6.2026030314" + } + }, + "ocp-emea": { + "hyperVGen1": { + "publisher": "redhat-limited", + "offer": "rh-ocp-worker", + "sku": "rh-ocp-worker-gen1", + "version": "4.18.2026012111" + }, + "hyperVGen2": { + "publisher": "redhat-limited", + "offer": "rh-ocp-worker", + "sku": "rh-ocp-worker", + "version": "4.18.2026012111" + } + }, + "opp-emea": { + "hyperVGen1": { + "publisher": "redhat-limited", + "offer": "rh-opp-worker", + "sku": "rh-opp-worker-gen1", + "version": "4.18.2026012111" + }, + "hyperVGen2": { + "publisher": "redhat-limited", + "offer": "rh-opp-worker", + "sku": "rh-opp-worker", + "version": "4.18.2026012111" + } + }, + "oke-emea": { + "hyperVGen1": { + "publisher": "redhat-limited", + "offer": "rh-oke-worker", + "sku": "rh-oke-worker-gen1", + "version": "4.18.2026012111" + }, + "hyperVGen2": { + "publisher": "redhat-limited", + "offer": "rh-oke-worker", + "sku": "rh-oke-worker", + "version": "4.18.2026012111" + } + } + } + } +} diff --git a/data/data/coreos/marketplace/coreos-rhel-9.json b/data/data/coreos/marketplace/coreos-rhel-9.json index dde107a847b..aa6ca21ce1f 100644 --- a/data/data/coreos/marketplace/coreos-rhel-9.json +++ b/data/data/coreos/marketplace/coreos-rhel-9.json @@ -5,8 +5,8 @@ "hyperVGen2": { "publisher": "azureopenshift", "offer": "aro4", - "sku": "420-arm", - "version": "9.6.20251015" + "sku": "aro_422-arm", + "version": "9.8.20260428" } } } @@ -17,14 +17,14 @@ "hyperVGen1": { "publisher": "azureopenshift", "offer": "aro4", - "sku": "aro_420", - "version": "9.6.20251015" + "sku": "aro_422", + "version": "9.8.20260428" }, "hyperVGen2": { "publisher": "azureopenshift", "offer": "aro4", - "sku": "420-v2", - "version": "9.6.20251015" + "sku": "aro_422-v2", + "version": "9.8.20260428" } }, "ocp": { @@ -32,13 +32,13 @@ "publisher": "redhat", "offer": "rh-ocp-worker", "sku": "rh-ocp-worker-gen1", - "version": "4.18.2025031114" + "version": "9.6.2026030314" }, "hyperVGen2": { "publisher": "redhat", "offer": "rh-ocp-worker", "sku": "rh-ocp-worker", - "version": "4.18.2025031114" + "version": "9.6.2026030314" } }, "opp": { @@ -46,13 +46,13 @@ "publisher": "redhat", "offer": "rh-opp-worker", "sku": "rh-opp-worker-gen1", - "version": "4.18.2025031114" + "version": "9.6.2026030314" }, "hyperVGen2": { "publisher": "redhat", "offer": "rh-opp-worker", "sku": "rh-opp-worker", - "version": "4.18.2025031114" + "version": "9.6.2026030314" } }, "oke": { @@ -60,13 +60,13 @@ "publisher": "redhat", "offer": "rh-oke-worker", "sku": "rh-oke-worker-gen1", - "version": "4.18.2025031114" + "version": "9.6.2026030314" }, "hyperVGen2": { "publisher": "redhat", "offer": "rh-oke-worker", "sku": "rh-oke-worker", - "version": "4.18.2025031114" + "version": "9.6.2026030314" } }, "ocp-emea": { @@ -74,13 +74,13 @@ "publisher": "redhat-limited", "offer": "rh-ocp-worker", "sku": "rh-ocp-worker-gen1", - "version": "4.18.2025031114" + "version": "4.18.2026012111" }, "hyperVGen2": { "publisher": "redhat-limited", "offer": "rh-ocp-worker", "sku": "rh-ocp-worker", - "version": "4.18.2025031114" + "version": "4.18.2026012111" } }, "opp-emea": { @@ -88,13 +88,13 @@ "publisher": "redhat-limited", "offer": "rh-opp-worker", "sku": "rh-opp-worker-gen1", - "version": "4.18.2025031114" + "version": "4.18.2026012111" }, "hyperVGen2": { "publisher": "redhat-limited", "offer": "rh-opp-worker", "sku": "rh-opp-worker", - "version": "4.18.2025031114" + "version": "4.18.2026012111" } }, "oke-emea": { @@ -102,13 +102,13 @@ "publisher": "redhat-limited", "offer": "rh-oke-worker", "sku": "rh-oke-worker-gen1", - "version": "4.18.2025031114" + "version": "4.18.2026012111" }, "hyperVGen2": { "publisher": "redhat-limited", "offer": "rh-oke-worker", "sku": "rh-oke-worker", - "version": "4.18.2025031114" + "version": "4.18.2026012111" } } } diff --git a/hack/rhcos/populate-marketplace-imagestream.go b/hack/rhcos/populate-marketplace-imagestream.go index 86ade34b707..80aa854caf4 100644 --- a/hack/rhcos/populate-marketplace-imagestream.go +++ b/hack/rhcos/populate-marketplace-imagestream.go @@ -15,41 +15,65 @@ import ( ) const ( - streamRHCOSJSON = "data/data/coreos/coreos-rhel-9.json" - streamMarketplaceRHCOSJSON = "data/data/coreos/marketplace-coreos-rhel-9.json" - x86 = "x86_64" arm64 = "aarch64" ) +type streamConfig struct { + name string + inputFile string + outputFile string +} + +var ( + streamRHEL9 = streamConfig{ + name: "rhel-9", + inputFile: "data/data/coreos/coreos-rhel-9.json", + outputFile: "data/data/coreos/marketplace/coreos-rhel-9.json", + } + streamRHEL10 = streamConfig{ + name: "rhel-10", + inputFile: "data/data/coreos/coreos-rhel-10.json", + outputFile: "data/data/coreos/marketplace/coreos-rhel-10.json", + } +) + // arch -> marketplace type marketplaceStream map[string]*rhcos.Marketplace func main() { ctx := context.Background() - stream := marketplaceStream{} - if err := stream.populate(ctx); err != nil { - log.Fatalln("Failed to populate marketplace stream:", err) + rhel9 := marketplaceStream{} + if err := rhel9.populate(ctx, streamRHEL9); err != nil { + log.Fatalln("Failed to populate RHEL 9 marketplace stream:", err) + } + if err := rhel9.write(streamRHEL9); err != nil { + log.Fatalln("Failed to write RHEL 9 marketplace stream:", err) } + log.Printf("Successfully wrote marketplace stream to %s", streamRHEL9.outputFile) - if err := stream.write(); err != nil { - log.Fatalln("Failed to write marketplace stream:", err) + rhel10 := marketplaceStream{} + if err := rhel10.populateWithFallback(ctx, streamRHEL10, rhel9); err != nil { + log.Fatalln("Failed to populate RHEL 10 marketplace stream:", err) } - log.Printf("Successfully wrote marketplace stream to %s", streamMarketplaceRHCOSJSON) + if err := rhel10.write(streamRHEL10); err != nil { + log.Fatalln("Failed to write RHEL 10 marketplace stream:", err) + } + log.Printf("Successfully wrote marketplace stream to %s", streamRHEL10.outputFile) } // populate gathers the marketplace images for each cloud // and adds them to the marketplace stream data structure. -func (s marketplaceStream) populate(ctx context.Context) error { - clouds := []func(ctx context.Context, arch string) error{ +func (s marketplaceStream) populate(ctx context.Context, cfg streamConfig) error { + clouds := []func(ctx context.Context, arch string, cfg streamConfig) error{ s.azure, } for _, supportedArch := range []string{arm64, x86} { s[supportedArch] = &rhcos.Marketplace{} for _, populateCloud := range clouds { - if err := populateCloud(ctx, supportedArch); err != nil { + if err := populateCloud(ctx, supportedArch, cfg); err != nil { return err } } @@ -57,31 +81,46 @@ func (s marketplaceStream) populate(ctx context.Context) error { return nil } +// populateWithFallback attempts to populate marketplace data for each architecture. +// Any individual image types not found are filled in from the fallback stream. +func (s marketplaceStream) populateWithFallback(ctx context.Context, cfg streamConfig, fallback marketplaceStream) error { + for _, supportedArch := range []string{arm64, x86} { + s[supportedArch] = &rhcos.Marketplace{} + + if err := s.azure(ctx, supportedArch, cfg); err != nil { + return err + } + + if fb, ok := fallback[supportedArch]; ok && fb != nil { + azure.FillMissing(s[supportedArch].Azure, fb.Azure) + } + } + return nil +} + // write serializes the marketplace stream to disk. -func (s marketplaceStream) write() error { +func (s marketplaceStream) write(cfg streamConfig) error { contents, err := json.MarshalIndent(s, "", " ") if err != nil { return fmt.Errorf("error marshaling stream: %w", err) } - // TODO(padillon): dumb question time, git is still complaining \ No newline at end of file - // what am I doing wrong? contents = append(contents, []byte("\n")...) - if err := os.WriteFile(streamMarketplaceRHCOSJSON, contents, 0644); err != nil { + if err := os.WriteFile(cfg.outputFile, contents, 0644); err != nil { return fmt.Errorf("error writing stream: %w", err) } return nil } -func (s marketplaceStream) azure(ctx context.Context, arch string) error { +func (s marketplaceStream) azure(ctx context.Context, arch string, cfg streamConfig) error { var err error s[arch].Azure = &rhcos.AzureMarketplace{} - rel, err := getReleaseFromStream() + rel, err := getReleaseFromStream(cfg) if err != nil { - return fmt.Errorf("failed to get release from rhcos stream: %w", err) + return fmt.Errorf("failed to get release from %s rhcos stream: %w", cfg.name, err) } azClient, err := azure.NewStreamClient() @@ -97,12 +136,12 @@ func (s marketplaceStream) azure(ctx context.Context, arch string) error { } // getXYFromStream obtains the X.Y version from rhcos.json. -func getReleaseFromStream() (string, error) { +func getReleaseFromStream(cfg streamConfig) (string, error) { if rel, ok := os.LookupEnv("STREAM_RELEASE_OVERRIDE"); ok { log.Printf("Found STREAM_RELEASE_OVERRIDE %s", rel) return rel, nil } - fileContents, err := os.ReadFile(streamRHCOSJSON) + fileContents, err := os.ReadFile(cfg.inputFile) if err != nil { return "", err } diff --git a/pkg/rhcos/marketplace/azure/azure.go b/pkg/rhcos/marketplace/azure/azure.go index a7f56582477..a4f4438015d 100644 --- a/pkg/rhcos/marketplace/azure/azure.go +++ b/pkg/rhcos/marketplace/azure/azure.go @@ -273,6 +273,34 @@ func convertToSemver(ver string) string { return "" } +// FillMissing copies any nil image types from fallback into target. +func FillMissing(target, fallback *rhcos.AzureMarketplace) { + if target == nil || fallback == nil { + return + } + if target.NoPurchasePlan == nil { + target.NoPurchasePlan = fallback.NoPurchasePlan + } + if target.OCP == nil { + target.OCP = fallback.OCP + } + if target.OPP == nil { + target.OPP = fallback.OPP + } + if target.OKE == nil { + target.OKE = fallback.OKE + } + if target.OCPEMEA == nil { + target.OCPEMEA = fallback.OCPEMEA + } + if target.OPPEMEA == nil { + target.OPPEMEA = fallback.OPPEMEA + } + if target.OKEEMEA == nil { + target.OKEEMEA = fallback.OKEEMEA + } +} + func checkIfNewer(candidate, release string) bool { img, err := strconv.Atoi(strings.Split(semver.MajorMinor(candidate), ".")[1]) if err != nil { From 5c761c39d651f0175114e9062c7f9895a243e3de Mon Sep 17 00:00:00 2001 From: Patrick Dillon Date: Wed, 20 May 2026 12:38:22 -0400 Subject: [PATCH 2/2] rhcos marketplace: adjust to new ARO naming conventions In 4.22, ARO changed the SKU format so that it includes -v2 or -arm prefixes. This commit updates the populate script to take these changes into account and find these images. Also, a slight tweak to adjust for the version discrepency. Newer ARO versions use RHEL versions, while older versions use OCP versions. --- .../rhcos/populate-marketplace-imagestream.go | 8 +++- pkg/rhcos/marketplace/azure/azure.go | 44 +++++++++++++++---- 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/hack/rhcos/populate-marketplace-imagestream.go b/hack/rhcos/populate-marketplace-imagestream.go index 80aa854caf4..0b9800441dc 100644 --- a/hack/rhcos/populate-marketplace-imagestream.go +++ b/hack/rhcos/populate-marketplace-imagestream.go @@ -135,7 +135,7 @@ func (s marketplaceStream) azure(ctx context.Context, arch string, cfg streamCon return nil } -// getXYFromStream obtains the X.Y version from rhcos.json. +// getReleaseFromStream obtains the X.Y version from rhcos.json. func getReleaseFromStream(cfg streamConfig) (string, error) { if rel, ok := os.LookupEnv("STREAM_RELEASE_OVERRIDE"); ok { log.Printf("Found STREAM_RELEASE_OVERRIDE %s", rel) @@ -151,5 +151,9 @@ func getReleaseFromStream(cfg streamConfig) (string, error) { return "", fmt.Errorf("failed to unmarshal RHCOS stream: %w", err) } - return strings.Split(st.Stream, "-")[1], nil + parts := strings.Split(st.Stream, "-") + if len(parts) < 2 { + return "", fmt.Errorf("unexpected stream format in %s: %q", cfg.inputFile, st.Stream) + } + return parts[1], nil } diff --git a/pkg/rhcos/marketplace/azure/azure.go b/pkg/rhcos/marketplace/azure/azure.go index a4f4438015d..cf34f98abb9 100644 --- a/pkg/rhcos/marketplace/azure/azure.go +++ b/pkg/rhcos/marketplace/azure/azure.go @@ -191,6 +191,11 @@ func (az *MarketplaceStream) getImage(ctx context.Context, pub, offer, sku, xyVe } } + if foundVersion == "" { + logrus.Infof("No matching version found for publisher: %s, offer: %s, sku: %s, architecture: %s in release %s", pub, offer, sku, arch, xyVersion) + return nil, nil + } + // Now that we've found the version, check the architecture and the plan. img, err := az.client.Get(ctx, region, pub, offer, sku, foundVersion, nil) if err != nil { @@ -221,20 +226,21 @@ func (az *MarketplaceStream) getImage(ctx context.Context, pub, offer, sku, xyVe // parseARO takes the release from coreos stream and // uses conventions to generate the SKU (gen1 & gen2) and version. -// For instance, with a coreos release of "4.19" -// gen1SKU: "aro_418" -// gen2SKU: "418-v2" -// version: "418.94.20241009" (removes timestamp & build number) +// For instance, with a coreos release of "4.22": +// +// gen1SKU: "aro_422" +// gen2SKU: "aro_422-v2" +// armSKU: "aro_422-arm" func parseAROSKUs(release, arch string) (string, string) { xyVersion := strings.ReplaceAll(release, ".", "") var gen1SKU, gen2SKU string switch arch { case x86: gen1SKU = fmt.Sprintf("aro_%s", xyVersion) - gen2SKU = fmt.Sprintf("%s-v2", xyVersion) + gen2SKU = fmt.Sprintf("aro_%s-v2", xyVersion) case arm64: gen1SKU = "" - gen2SKU = fmt.Sprintf("%s-arm", xyVersion) + gen2SKU = fmt.Sprintf("aro_%s-arm", xyVersion) } return gen1SKU, gen2SKU } @@ -302,15 +308,35 @@ func FillMissing(target, fallback *rhcos.AzureMarketplace) { } func checkIfNewer(candidate, release string) bool { - img, err := strconv.Atoi(strings.Split(semver.MajorMinor(candidate), ".")[1]) + mm := semver.MajorMinor(candidate) + imgMajor, err := strconv.Atoi(strings.TrimPrefix(strings.Split(mm, ".")[0], "v")) + if err != nil { + logrus.Infof("Error converting major version to int with version %s", candidate) + return true + } + // Images using RHEL version numbering (9.x, 10.x) are not comparable + // to OCP release versions and should never be filtered out. + if imgMajor == 9 || imgMajor == 10 { + return false + } + imgMinor, err := strconv.Atoi(strings.Split(mm, ".")[1]) if err != nil { logrus.Infof("Error converting minor version to int with version %s", candidate) return true } - rel, err := strconv.Atoi(strings.Split(release, ".")[1]) + relParts := strings.Split(release, ".") + relMajor, err := strconv.Atoi(relParts[0]) + if err != nil { + logrus.Infof("Error converting major version to int with version %s", release) + return true + } + relMinor, err := strconv.Atoi(relParts[1]) if err != nil { logrus.Infof("Error converting minor version to int with version %s", release) return true } - return img > rel + if imgMajor != relMajor { + return imgMajor > relMajor + } + return imgMinor > relMinor }