diff --git a/pkg/payload/payload.go b/pkg/payload/payload.go index 7c7af838b8..8722964801 100644 --- a/pkg/payload/payload.go +++ b/pkg/payload/payload.go @@ -153,7 +153,7 @@ func LoadUpdate(dir, releaseImage, excludeIdentifier string, requiredFeatureSet return nil, err } - tasks := loadPayloadTasks(releaseDir, cvoDir, releaseImage, profile) + tasks := loadPayloadTasks(releaseDir, cvoDir, releaseImage, profile, payload.ImageRef) var onlyKnownCaps *configv1.ClusterVersionCapabilitiesStatus @@ -195,7 +195,13 @@ func LoadUpdate(dir, releaseImage, excludeIdentifier string, requiredFeatureSet if task.preprocess != nil { raw, err = task.preprocess(raw) if err != nil { - errs = append(errs, fmt.Errorf("preprocess %s: %w", file.Name(), err)) + // Template rendering may fail when an older CVO binary + // loads a newer payload that uses template fields the + // older binary does not know about (e.g. .Images). Skip + // the manifest with a warning — the new CVO binary will + // re-load the full payload after it replaces the old one + // at run-level 0. + klog.Warningf("Skipping manifest %s: template rendering failed (may require newer CVO): %v", file.Name(), err) continue } } @@ -317,13 +323,14 @@ type payloadTasks struct { skipFiles sets.Set[string] } -func loadPayloadTasks(releaseDir, cvoDir, releaseImage, clusterProfile string) []payloadTasks { +func loadPayloadTasks(releaseDir, cvoDir, releaseImage, clusterProfile string, imageRef *imagev1.ImageStream) []payloadTasks { cjf := filepath.Join(releaseDir, cincinnatiJSONFile) irf := filepath.Join(releaseDir, imageReferencesFile) mrc := manifestRenderConfig{ ReleaseImage: releaseImage, ClusterProfile: clusterProfile, + Images: imagesFromImageRef(imageRef), } return []payloadTasks{{ diff --git a/pkg/payload/payload_test.go b/pkg/payload/payload_test.go index cf2bf0856e..cc32d01122 100644 --- a/pkg/payload/payload_test.go +++ b/pkg/payload/payload_test.go @@ -150,6 +150,28 @@ func TestLoadUpdate(t *testing.T) { } } +func TestLoadUpdateSkipsUnknownTemplateFields(t *testing.T) { + update, err := LoadUpdate("testdata/payload-unknown-template", "image:test", "", "", DefaultClusterProfile, nil, sets.Set[string]{}) + if err != nil { + t.Fatalf("LoadUpdate should not fail when a manifest uses unknown template fields, got: %v", err) + } + + // The valid manifest (using known .ReleaseImage) should be loaded + var foundValid bool + for _, m := range update.Manifests { + if m.OriginalFilename == "0000_00_valid.yaml" { + foundValid = true + } + // The manifest with unknown .FutureField should be skipped (not loaded) + if m.OriginalFilename == "0000_50_unknown-field.yaml" { + t.Error("manifest with unknown template field should have been skipped, but was loaded") + } + } + if !foundValid { + t.Error("expected valid manifest (0000_00_valid.yaml) to be loaded") + } +} + func TestLoadUpdateArchitecture(t *testing.T) { type args struct { dir string diff --git a/pkg/payload/render.go b/pkg/payload/render.go index fc075a6e5c..edd20aed25 100644 --- a/pkg/payload/render.go +++ b/pkg/payload/render.go @@ -20,6 +20,7 @@ import ( "github.com/openshift/api/config" configv1 "github.com/openshift/api/config/v1" + imagev1 "github.com/openshift/api/image/v1" "github.com/openshift/library-go/pkg/manifest" ) @@ -38,6 +39,12 @@ func Render(outputDir, releaseImage, clusterVersionManifestPath, featureGateMani } ) + imageRef, err := loadImageReferences(releaseManifestsDir) + if err != nil { + return fmt.Errorf("error loading image references for manifest rendering: %w", err) + } + renderConfig.Images = imagesFromImageRef(imageRef) + overrides, err := parseClusterVersionManifest(clusterVersionManifestPath) if err != nil { return fmt.Errorf("error parsing cluster version manifest: %w", err) @@ -181,6 +188,21 @@ func renderDir(renderConfig manifestRenderConfig, idir, odir string, overrides [ type manifestRenderConfig struct { ReleaseImage string ClusterProfile string + Images map[string]string +} + +// imagesFromImageRef builds a map from image short names to their resolved URIs. +func imagesFromImageRef(imageRef *imagev1.ImageStream) map[string]string { + images := make(map[string]string) + if imageRef == nil { + return images + } + for _, tag := range imageRef.Spec.Tags { + if tag.From != nil && tag.From.Kind == "DockerImage" { + images[tag.Name] = tag.From.Name + } + } + return images } // renderManifest Executes go text template from `manifestBytes` with `config`. diff --git a/pkg/payload/render_test.go b/pkg/payload/render_test.go index deb475b161..4f134551fe 100644 --- a/pkg/payload/render_test.go +++ b/pkg/payload/render_test.go @@ -11,14 +11,79 @@ import ( "github.com/google/go-cmp/cmp" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" configv1 "github.com/openshift/api/config/v1" + imagev1 "github.com/openshift/api/image/v1" "github.com/openshift/library-go/pkg/manifest" ) +func TestImagesFromImageRef(t *testing.T) { + t.Run("nil ImageStream returns empty map", func(t *testing.T) { + images := imagesFromImageRef(nil) + if len(images) != 0 { + t.Errorf("expected empty map, got %v", images) + } + }) + + t.Run("maps DockerImage tags to their names", func(t *testing.T) { + imageRef := &imagev1.ImageStream{ + Spec: imagev1.ImageStreamSpec{ + Tags: []imagev1.TagReference{ + { + Name: "console", + From: &corev1.ObjectReference{Kind: "DockerImage", Name: "quay.io/openshift/console:latest"}, + }, + { + Name: "cluster-update-console-plugin", + From: &corev1.ObjectReference{Kind: "DockerImage", Name: "quay.io/openshift/plugin:v1"}, + }, + }, + }, + } + images := imagesFromImageRef(imageRef) + if len(images) != 2 { + t.Fatalf("expected 2 images, got %d", len(images)) + } + if images["console"] != "quay.io/openshift/console:latest" { + t.Errorf("expected console image, got %q", images["console"]) + } + if images["cluster-update-console-plugin"] != "quay.io/openshift/plugin:v1" { + t.Errorf("expected plugin image, got %q", images["cluster-update-console-plugin"]) + } + }) + + t.Run("skips non-DockerImage tags", func(t *testing.T) { + imageRef := &imagev1.ImageStream{ + Spec: imagev1.ImageStreamSpec{ + Tags: []imagev1.TagReference{ + { + Name: "docker-tag", + From: &corev1.ObjectReference{Kind: "DockerImage", Name: "example.com/img:v1"}, + }, + { + Name: "image-stream-tag", + From: &corev1.ObjectReference{Kind: "ImageStreamTag", Name: "other:latest"}, + }, + { + Name: "no-from", + }, + }, + }, + } + images := imagesFromImageRef(imageRef) + if len(images) != 1 { + t.Fatalf("expected 1 image, got %d: %v", len(images), images) + } + if _, ok := images["docker-tag"]; !ok { + t.Error("expected docker-tag to be present") + } + }) +} + func TestRenderManifest(t *testing.T) { tests := []struct { diff --git a/pkg/payload/testdata/payload-unknown-template/manifests/0000_00_valid.yaml b/pkg/payload/testdata/payload-unknown-template/manifests/0000_00_valid.yaml new file mode 100644 index 0000000000..e8295d59d1 --- /dev/null +++ b/pkg/payload/testdata/payload-unknown-template/manifests/0000_00_valid.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: valid-manifest + namespace: test + annotations: + include.release.openshift.io/self-managed-high-availability: "true" +data: + release-image: '{{.ReleaseImage}}' diff --git a/pkg/payload/testdata/payload-unknown-template/manifests/0000_50_unknown-field.yaml b/pkg/payload/testdata/payload-unknown-template/manifests/0000_50_unknown-field.yaml new file mode 100644 index 0000000000..746c4c8ad5 --- /dev/null +++ b/pkg/payload/testdata/payload-unknown-template/manifests/0000_50_unknown-field.yaml @@ -0,0 +1,9 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: future-manifest + namespace: test + annotations: + include.release.openshift.io/self-managed-high-availability: "true" +data: + image: '{{index .FutureField "some-key"}}' diff --git a/pkg/payload/testdata/payload-unknown-template/release-manifests/image-references b/pkg/payload/testdata/payload-unknown-template/release-manifests/image-references new file mode 100644 index 0000000000..c955da2e3c --- /dev/null +++ b/pkg/payload/testdata/payload-unknown-template/release-manifests/image-references @@ -0,0 +1,7 @@ +{ + "kind": "ImageStream", + "apiVersion": "image.openshift.io/v1", + "metadata": { + "name": "1.0.0-test" + } +} diff --git a/pkg/payload/testdata/payload-unknown-template/release-manifests/release-metadata b/pkg/payload/testdata/payload-unknown-template/release-manifests/release-metadata new file mode 100644 index 0000000000..c7afa3bd23 --- /dev/null +++ b/pkg/payload/testdata/payload-unknown-template/release-manifests/release-metadata @@ -0,0 +1 @@ +{"kind":"cincinnati-metadata-v0","version":"1.0.0-test"}