From 817d9781edd75cb335334c63919a3e6b1bad51d8 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Tue, 19 Dec 2023 13:30:06 +0100 Subject: [PATCH] Seed image format version OCI image label IBU reconciler now has a `checkSeedImageCompatibility` method that checks if the seed image is compatible with the current version of the lifecycle-agent. It does so by inspecting the OCI image's labels and checking if the specified format version equals the hard-coded one that this version of the lifecycle agent expects. That format version is set by the imager during the image build process, and is only manually bumped by developers when the image format changes in a way that is incompatible with previous versions of the lifecycle-agent. --- controllers/prep_handlers.go | 51 +++++++++++++++++++++++++++ ibu-imager/seedcreator/seedcreator.go | 9 ++++- internal/common/consts.go | 4 +++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/controllers/prep_handlers.go b/controllers/prep_handlers.go index e08c27364..d56e302c8 100644 --- a/controllers/prep_handlers.go +++ b/controllers/prep_handlers.go @@ -18,6 +18,7 @@ package controllers import ( "context" + "encoding/json" "errors" "fmt" "os" @@ -68,8 +69,58 @@ func (r *ImageBasedUpgradeReconciler) getSeedImage( return fmt.Errorf("failed to pull image: %w", err) } + r.Log.Info("Checking seed image compatibility") + if err := r.checkSeedImageCompatibility(ctx, pullSecretFilename, ibu.Spec.SeedImageRef.Image); err != nil { + return fmt.Errorf("checking seed image compatibility: %w", err) + } + return nil +} + +// checkSeedImageCompatibility checks if the seed image is compatible with the +// current version of the lifecycle-agent by inspecting the OCI image's labels +// and checking if the specified format version equals the hard-coded one that +// this version of the lifecycle agent expects. That format version is set by +// the imager during the image build process, and is only manually bumped by +// developers when the image format changes in a way that is incompatible with +// previous versions of the lifecycle-agent. +func (r *ImageBasedUpgradeReconciler) checkSeedImageCompatibility(_ context.Context, pullSecretFilename, seedImageRef string) error { + inspectArgs := []string{ + "inspect", + "--format", "json", + seedImageRef, + } + + var inspect struct { + Labels map[string]string `json:"Labels"` + } + + // TODO: use the context when execute supports it + if inspectRaw, err := r.Executor.Execute("podman", inspectArgs...); err != nil || inspectRaw == "" { + return fmt.Errorf("failed to inspect image: %w", err) + } else { + if err := json.Unmarshal([]byte(inspectRaw), &inspect); err != nil { + return fmt.Errorf("failed to unmarshal image inspect output: %w", err) + } + } + seedFormatLabelValue, ok := inspect.Labels[common.SeedFormatOCILabel] + if !ok { + return fmt.Errorf( + "seed image %s is missing the %s label, please build a new image using the latest version of the imager", + seedImageRef, common.SeedFormatOCILabel) + } + + // Hard equal since we don't have backwards compatibility guarantees yet. + // In the future we might want to have backwards compatibility code to + // handle older seed formats and in that case we'll look at the version + // number and do the right thing. + if seedFormatLabelValue != fmt.Sprintf("%d", common.SeedFormatVersion) { + return fmt.Errorf("seed image format version mismatch: expected %d, got %s", + common.SeedFormatVersion, inspect.Labels[common.SeedFormatOCILabel]) + } + + return nil } func readPrecachingList(imageListFile, clusterRegistry, seedRegistry string, overrideSeedRegistry bool) (imageList []string, err error) { diff --git a/ibu-imager/seedcreator/seedcreator.go b/ibu-imager/seedcreator/seedcreator.go index 10057729f..e0283812a 100644 --- a/ibu-imager/seedcreator/seedcreator.go +++ b/ibu-imager/seedcreator/seedcreator.go @@ -414,8 +414,15 @@ func (s *SeedCreator) createAndPushSeedImage() error { _ = tmpfile.Close() // Close the temporary file // Build the single OCI image (note: We could include --squash-all option, as well) + podmanBuildArgs := []string{ + "build", + "--file", tmpfile.Name(), + "--tag", s.containerRegistry, + "--label", fmt.Sprintf("%s=%d", common.SeedFormatOCILabel, common.SeedFormatVersion), + s.backupDir, + } _, err = s.ops.RunInHostNamespace( - "podman", []string{"build", "-f", tmpfile.Name(), "-t", s.containerRegistry, s.backupDir}...) + "podman", podmanBuildArgs...) if err != nil { return fmt.Errorf("failed to build seed image: %w", err) } diff --git a/internal/common/consts.go b/internal/common/consts.go index a4ae3f1cb..825ef22be 100644 --- a/internal/common/consts.go +++ b/internal/common/consts.go @@ -57,6 +57,10 @@ const ( InstallConfigCM = "cluster-config-v1" // InstallConfigCMNamespace cm namespace InstallConfigCMNamespace = "kube-system" + + // Bump this every time the seed format changes in a backwards incompatible way + SeedFormatVersion = 1 + SeedFormatOCILabel = "com.openshift.lifecycle-agent.seed_format_version" ) // CertPrefixes is the list of certificate prefixes to be backed up