From f8ebd96f1f8d160ec959ffc0f2431008841476e1 Mon Sep 17 00:00:00 2001 From: Ish Shah Date: Thu, 27 Jan 2022 10:48:55 -0800 Subject: [PATCH 1/4] Adding SHA Digest based bundle options to Makefiles & CLI (#5512) Signed-off-by: Ish Shah --- .../cmd/operator-sdk/generate/bundle/cmd.go | 5 +++++ internal/plugins/manifests/v2/init.go | 22 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/internal/cmd/operator-sdk/generate/bundle/cmd.go b/internal/cmd/operator-sdk/generate/bundle/cmd.go index 091adf95dd9..e4803a962b5 100644 --- a/internal/cmd/operator-sdk/generate/bundle/cmd.go +++ b/internal/cmd/operator-sdk/generate/bundle/cmd.go @@ -49,6 +49,9 @@ type bundleCmd struct { // These are set if a PROJECT config is not present. layout string packageName string + + // Use Image Digests flag to toggle using traditional Image tags vs SHA Digests + useImageDigests bool } // NewCmd returns the 'bundle' command configured for the new project layout. @@ -139,6 +142,8 @@ func (c *bundleCmd) addFlagsTo(fs *pflag.FlagSet) { fs.BoolVar(&c.stdout, "stdout", false, "Write bundle manifest to stdout") fs.StringVar(&c.packageName, "package", "", "Bundle's package name") + + fs.BoolVar(&c.useImageDigests, "use-image-digests", false, "Use SHA Digest for images") } func (c bundleCmd) println(a ...interface{}) { diff --git a/internal/plugins/manifests/v2/init.go b/internal/plugins/manifests/v2/init.go index 7b6164ae8a1..2cb3dac79c8 100644 --- a/internal/plugins/manifests/v2/init.go +++ b/internal/plugins/manifests/v2/init.go @@ -155,6 +155,17 @@ IMAGE_TAG_BASE ?= %[1]s/%[2]s # BUNDLE_IMG defines the image:tag used for the bundle. # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) + +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif ` makefileBundleFragmentGo = ` @@ -162,7 +173,7 @@ BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. operator-sdk generate kustomize manifests -q cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) operator-sdk bundle validate ./bundle ` @@ -175,6 +186,15 @@ bundle: kustomize ## Generate bundle manifests and metadata, then validate gener operator-sdk bundle validate ./bundle ` + makefileBundleFragmentSHA = ` +.PHONY: bundle +bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. + operator-sdk generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle --use-image-digests -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + operator-sdk bundle validate ./bundle +` + makefileBundleBuildPushFragment = ` .PHONY: bundle-build bundle-build: ## Build the bundle image. From 3311991f6966c9159a4e3d27ff87ecb5842ae6e8 Mon Sep 17 00:00:00 2001 From: Ryan King Date: Thu, 27 Jan 2022 15:16:17 -0500 Subject: [PATCH 2/4] Add related images to bundle generation (#5519) * add related images to bundle generation Signed-off-by: Ryan King * regenerate and fix typos Signed-off-by: Ryan King * no related images will be null, not an empty slice Signed-off-by: Ryan King * regenerate to fix sanity Signed-off-by: Ryan King --- .../operator-sdk/generate/bundle/bundle.go | 56 +++++++++++++++++++ .../clusterserviceversion.go | 14 +++++ internal/plugins/manifests/v2/init.go | 9 --- testdata/ansible/memcached-operator/Makefile | 11 ++++ testdata/go/v2/memcached-operator/Makefile | 13 ++++- testdata/go/v3/memcached-operator/Makefile | 13 ++++- testdata/helm/memcached-operator/Makefile | 11 ++++ .../docs/cli/operator-sdk_generate_bundle.md | 1 + 8 files changed, 117 insertions(+), 11 deletions(-) diff --git a/internal/cmd/operator-sdk/generate/bundle/bundle.go b/internal/cmd/operator-sdk/generate/bundle/bundle.go index 1008af4a4ff..0da140bfc35 100644 --- a/internal/cmd/operator-sdk/generate/bundle/bundle.go +++ b/internal/cmd/operator-sdk/generate/bundle/bundle.go @@ -20,9 +20,11 @@ import ( "io/ioutil" "os" "path/filepath" + "strings" "github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3" "github.com/operator-framework/operator-registry/pkg/lib/bundle" + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/yaml" metricsannotations "github.com/operator-framework/operator-sdk/internal/annotations/metrics" @@ -188,6 +190,11 @@ func (c bundleCmd) runManifests() (err error) { c.println("Building a ClusterServiceVersion without an existing base") } + relatedImages, err := c.findRelatedImages(col) + if err != nil { + return err + } + var opts []gencsv.Option stdout := genutil.NewMultiManifestWriter(os.Stdout) if c.stdout { @@ -202,6 +209,7 @@ func (c bundleCmd) runManifests() (err error) { Collector: col, Annotations: metricsannotations.MakeBundleObjectAnnotations(c.layout), ExtraServiceAccounts: c.extraServiceAccounts, + RelatedImages: relatedImages, } if err := csvGen.Generate(opts...); err != nil { return fmt.Errorf("error generating ClusterServiceVersion: %v", err) @@ -287,3 +295,51 @@ func (c bundleCmd) runMetadata() error { return bundleMetadata.GenerateMetadata() } + +// findRelatedImages looks in the controller manager's environment for images used by the operator. +func (c bundleCmd) findRelatedImages(col *collector.Manifests) (map[string]string, error) { + const relatedImagePrefix = "RELATED_IMAGE_" + env, err := c.findManagerEnvironment(col) + if err != nil { + return nil, err + } + imageNames := make(map[string]string, len(env)) + for _, envVar := range env { + if strings.HasPrefix(envVar.Name, relatedImagePrefix) { + if envVar.ValueFrom != nil { + return nil, fmt.Errorf("related images with valueFrom field unsupported, found in %s`", envVar.Name) + } + + // transforms RELATED_IMAGE_This_IS_a_cool_image to this-is-a-cool-image + name := strings.ToLower(strings.Replace(strings.TrimPrefix(envVar.Name, relatedImagePrefix), "_", "-", -1)) + imageNames[name] = envVar.Value + } + } + + return imageNames, nil +} + +// findManagerEnvironment returns the environment passed to the controller manager container. +func (c bundleCmd) findManagerEnvironment(col *collector.Manifests) ([]corev1.EnvVar, error) { + const ( + managerLabel = "control-plane" + managerLabelValue = "controller-manager" + managerContainerName = "manager" + ) + + for _, deployment := range col.Deployments { + if val, ok := deployment.GetLabels()[managerLabel]; ok && val == managerLabelValue { + for _, container := range deployment.Spec.Template.Spec.Containers { + if container.Name == managerContainerName { + return container.Env, nil + } + } + + return nil, fmt.Errorf("manager deployment does not have container named %q", managerContainerName) + } + } + + return nil, fmt.Errorf( + "could not find manager deployment, should have label %s=%s", managerLabel, managerLabelValue, + ) +} diff --git a/internal/generate/clusterserviceversion/clusterserviceversion.go b/internal/generate/clusterserviceversion/clusterserviceversion.go index 585fd11a522..bfd7db9a1a8 100644 --- a/internal/generate/clusterserviceversion/clusterserviceversion.go +++ b/internal/generate/clusterserviceversion/clusterserviceversion.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "path/filepath" + "sort" "strings" "github.com/blang/semver/v4" @@ -56,6 +57,9 @@ type Generator struct { // ExtraServiceAccounts are ServiceAccount names to consider when matching // {Cluster}Roles to include in a CSV via their Bindings. ExtraServiceAccounts []string + // RelatedImages are additional images used by the operator. + // It is a mapping of an image name to an image URL + RelatedImages map[string]string // Func that returns the writer the generated CSV's bytes are written to. getWriter func() (io.Writer, error) @@ -165,6 +169,16 @@ func (g *Generator) generate() (base *operatorsv1alpha1.ClusterServiceVersion, e if g.FromVersion != "" { base.Spec.Replaces = genutil.MakeCSVName(g.OperatorName, g.FromVersion) } + if len(g.RelatedImages) > 0 { + base.Spec.RelatedImages = make([]operatorsv1alpha1.RelatedImage, 0, len(g.RelatedImages)) + for name, image := range g.RelatedImages { + base.Spec.RelatedImages = append(base.Spec.RelatedImages, operatorsv1alpha1.RelatedImage{Name: name, Image: image}) + } + // ensure deterministic order + sort.SliceStable(base.Spec.RelatedImages, func(i, j int) bool { + return strings.Compare(base.Spec.RelatedImages[i].Name, base.Spec.RelatedImages[j].Name) > 0 + }) + } if err := ApplyTo(g.Collector, base, g.ExtraServiceAccounts); err != nil { return nil, err diff --git a/internal/plugins/manifests/v2/init.go b/internal/plugins/manifests/v2/init.go index 2cb3dac79c8..7ceaaeba63a 100644 --- a/internal/plugins/manifests/v2/init.go +++ b/internal/plugins/manifests/v2/init.go @@ -186,15 +186,6 @@ bundle: kustomize ## Generate bundle manifests and metadata, then validate gener operator-sdk bundle validate ./bundle ` - makefileBundleFragmentSHA = ` -.PHONY: bundle -bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. - operator-sdk generate kustomize manifests -q - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle --use-image-digests -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) - operator-sdk bundle validate ./bundle -` - makefileBundleBuildPushFragment = ` .PHONY: bundle-build bundle-build: ## Build the bundle image. diff --git a/testdata/ansible/memcached-operator/Makefile b/testdata/ansible/memcached-operator/Makefile index d55079ca73d..824794a8481 100644 --- a/testdata/ansible/memcached-operator/Makefile +++ b/testdata/ansible/memcached-operator/Makefile @@ -35,6 +35,17 @@ IMAGE_TAG_BASE ?= example.com/memcached-operator # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + # Image URL to use all building/pushing image targets IMG ?= controller:latest diff --git a/testdata/go/v2/memcached-operator/Makefile b/testdata/go/v2/memcached-operator/Makefile index cf3edbcd8f4..c85bcc67c6e 100644 --- a/testdata/go/v2/memcached-operator/Makefile +++ b/testdata/go/v2/memcached-operator/Makefile @@ -35,6 +35,17 @@ IMAGE_TAG_BASE ?= example.com/memcached-operator # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + # Image URL to use all building/pushing image targets IMG ?= controller:latest # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) @@ -170,7 +181,7 @@ endif bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. operator-sdk generate kustomize manifests --interactive=false -q cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) operator-sdk bundle validate ./bundle .PHONY: bundle-build diff --git a/testdata/go/v3/memcached-operator/Makefile b/testdata/go/v3/memcached-operator/Makefile index e1a3d21b8af..af9d723e874 100644 --- a/testdata/go/v3/memcached-operator/Makefile +++ b/testdata/go/v3/memcached-operator/Makefile @@ -35,6 +35,17 @@ IMAGE_TAG_BASE ?= example.com/memcached-operator # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + # Image URL to use all building/pushing image targets IMG ?= controller:latest # ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. @@ -169,7 +180,7 @@ endef bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. operator-sdk generate kustomize manifests --interactive=false -q cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) operator-sdk bundle validate ./bundle .PHONY: bundle-build diff --git a/testdata/helm/memcached-operator/Makefile b/testdata/helm/memcached-operator/Makefile index 6ab9c215989..01e086ccc26 100644 --- a/testdata/helm/memcached-operator/Makefile +++ b/testdata/helm/memcached-operator/Makefile @@ -35,6 +35,17 @@ IMAGE_TAG_BASE ?= example.com/memcached-operator # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) +# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command +BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + +# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests +# You can enable this value if you would like to use SHA Based Digests +# To enable set flag to true +USE_IMAGE_DIGESTS ?= false +ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests +endif + # Image URL to use all building/pushing image targets IMG ?= controller:latest diff --git a/website/content/en/docs/cli/operator-sdk_generate_bundle.md b/website/content/en/docs/cli/operator-sdk_generate_bundle.md index 2a8c2b8d0fc..84aa231d861 100644 --- a/website/content/en/docs/cli/operator-sdk_generate_bundle.md +++ b/website/content/en/docs/cli/operator-sdk_generate_bundle.md @@ -103,6 +103,7 @@ operator-sdk generate bundle [flags] --package string Bundle's package name -q, --quiet Run in quiet mode --stdout Write bundle manifest to stdout + --use-image-digests Use SHA Digest for images -v, --version string Semantic version of the operator in the generated bundle. Only set if creating a new bundle or upgrading your operator ``` From 33c2e099ee1cc8fafeea5737e693844316ac9fb7 Mon Sep 17 00:00:00 2001 From: Ryan King Date: Wed, 23 Feb 2022 16:58:28 -0500 Subject: [PATCH 3/4] Add changelog Signed-off-by: Ryan King --- changelog/fragments/image-digests.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog/fragments/image-digests.yaml diff --git a/changelog/fragments/image-digests.yaml b/changelog/fragments/image-digests.yaml new file mode 100644 index 00000000000..afdeca725c4 --- /dev/null +++ b/changelog/fragments/image-digests.yaml @@ -0,0 +1,5 @@ +entries: + - description: > + Adds support to bundle operators using image digests instead of tags. + kind: "addition" + breaking: false From 80d7022de4de649c713eb18f7a549ee3009d8375 Mon Sep 17 00:00:00 2001 From: "jesus m. rodriguez" Date: Fri, 25 Feb 2022 16:04:45 -0500 Subject: [PATCH 4/4] Add migration steps to changelog Signed-off-by: jesus m. rodriguez --- changelog/fragments/image-digests.yaml | 33 ++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/changelog/fragments/image-digests.yaml b/changelog/fragments/image-digests.yaml index afdeca725c4..b4f3d00ee4a 100644 --- a/changelog/fragments/image-digests.yaml +++ b/changelog/fragments/image-digests.yaml @@ -3,3 +3,36 @@ entries: Adds support to bundle operators using image digests instead of tags. kind: "addition" breaking: false + migration: + header: Support image digests instead of tags + body: | + Add following variables to your project's `Makefile` below the `BUNDLE_IMG ?=`. + + ``` + # BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command + BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + + # USE_IMAGE_DIGESTS defines if images are resolved via tags or digests + # You can enable this value if you would like to use SHA Based Digests + # To enable set flag to true + USE_IMAGE_DIGESTS ?= false + ifeq ($(USE_IMAGE_DIGESTS), true) + BUNDLE_GEN_FLAGS += --use-image-digests + endif + ``` + + Using the YAML string '|' operator means that newlines in this string will + Then in the `bundle` target we want to replace the flags passed to + `generate bundle` with a reference to the `BUNDLE_GEN_FLAGS` above. + + The `generate bundle` line should look like this + + ``` + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS) + ``` + + For reference the *PREVIOUS* version looked as follows + + ``` + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + ```