Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions changelog/fragments/image-digests.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
entries:
- description: >
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)
```
56 changes: 56 additions & 0 deletions internal/cmd/operator-sdk/generate/bundle/bundle.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 {
Expand All @@ -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)
Expand Down Expand Up @@ -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,
)
}
5 changes: 5 additions & 0 deletions internal/cmd/operator-sdk/generate/bundle/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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{}) {
Expand Down
14 changes: 14 additions & 0 deletions internal/generate/clusterserviceversion/clusterserviceversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"fmt"
"io"
"path/filepath"
"sort"
"strings"

"github.com/blang/semver/v4"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion internal/plugins/manifests/v2/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,25 @@ 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=<some-registry>/<project-name-bundle>:<tag>)
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 = `
.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 -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
$(KUSTOMIZE) build config/manifests | operator-sdk generate bundle $(BUNDLE_GEN_FLAGS)
operator-sdk bundle validate ./bundle
`

Expand Down
11 changes: 11 additions & 0 deletions testdata/ansible/memcached-operator/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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=<some-registry>/<project-name-bundle>:<tag>)
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

Expand Down
13 changes: 12 additions & 1 deletion testdata/go/v2/memcached-operator/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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=<some-registry>/<project-name-bundle>:<tag>)
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)
Expand Down Expand Up @@ -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
Expand Down
13 changes: 12 additions & 1 deletion testdata/go/v3/memcached-operator/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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=<some-registry>/<project-name-bundle>:<tag>)
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.
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions testdata/helm/memcached-operator/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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=<some-registry>/<project-name-bundle>:<tag>)
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down