Skip to content

Commit 2f054ab

Browse files
Fix related image environment variable discovery (#5656)
The related image discovery feature currently only reads environment variables from the manager container. This was causing an error when the deployment labels or container name that was expected were not present. This fixes that by collecting related images from all containers across all deployments. This change also enables users to use related images in other containers since related images from everywhere will be considered. Signed-off-by: Ryan King <[email protected]> Co-authored-by: Ryan King <[email protected]>
1 parent 34b03b8 commit 2f054ab

File tree

5 files changed

+359
-64
lines changed

5 files changed

+359
-64
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
entries:
2+
- description: >
3+
The related image discovery feature currently only reads environment
4+
variables from the manager container. This was causing an error when the
5+
deployment labels or container name that was expected were not present.
6+
This fixes that by collecting related images from all containers across
7+
all deployments.
8+
9+
This change also enables users to use related images in other containers
10+
since related images from everywhere will be considered.
11+
kind: "bugfix"

internal/cmd/operator-sdk/generate/bundle/bundle.go

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,12 @@ import (
2020
"io/ioutil"
2121
"os"
2222
"path/filepath"
23-
"strings"
2423

2524
"github.com/operator-framework/api/pkg/apis/scorecard/v1alpha3"
2625
"github.com/operator-framework/operator-manifest-tools/pkg/image"
2726
"github.com/operator-framework/operator-manifest-tools/pkg/imageresolver"
2827
"github.com/operator-framework/operator-manifest-tools/pkg/pullspec"
2928
"github.com/operator-framework/operator-registry/pkg/lib/bundle"
30-
corev1 "k8s.io/api/core/v1"
3129
"sigs.k8s.io/yaml"
3230

3331
metricsannotations "github.com/operator-framework/operator-sdk/internal/annotations/metrics"
@@ -193,7 +191,7 @@ func (c bundleCmd) runManifests() (err error) {
193191
c.println("Building a ClusterServiceVersion without an existing base")
194192
}
195193

196-
relatedImages, err := c.findRelatedImages(col)
194+
relatedImages, err := genutil.FindRelatedImages(col)
197195
if err != nil {
198196
return err
199197
}
@@ -305,54 +303,6 @@ func (c bundleCmd) runMetadata() error {
305303
return bundleMetadata.GenerateMetadata()
306304
}
307305

308-
// findRelatedImages looks in the controller manager's environment for images used by the operator.
309-
func (c bundleCmd) findRelatedImages(col *collector.Manifests) (map[string]string, error) {
310-
const relatedImagePrefix = "RELATED_IMAGE_"
311-
env, err := c.findManagerEnvironment(col)
312-
if err != nil {
313-
return nil, err
314-
}
315-
imageNames := make(map[string]string, len(env))
316-
for _, envVar := range env {
317-
if strings.HasPrefix(envVar.Name, relatedImagePrefix) {
318-
if envVar.ValueFrom != nil {
319-
return nil, fmt.Errorf("related images with valueFrom field unsupported, found in %s`", envVar.Name)
320-
}
321-
322-
// transforms RELATED_IMAGE_This_IS_a_cool_image to this-is-a-cool-image
323-
name := strings.ToLower(strings.Replace(strings.TrimPrefix(envVar.Name, relatedImagePrefix), "_", "-", -1))
324-
imageNames[name] = envVar.Value
325-
}
326-
}
327-
328-
return imageNames, nil
329-
}
330-
331-
// findManagerEnvironment returns the environment passed to the controller manager container.
332-
func (c bundleCmd) findManagerEnvironment(col *collector.Manifests) ([]corev1.EnvVar, error) {
333-
const (
334-
managerLabel = "control-plane"
335-
managerLabelValue = "controller-manager"
336-
managerContainerName = "manager"
337-
)
338-
339-
for _, deployment := range col.Deployments {
340-
if val, ok := deployment.GetLabels()[managerLabel]; ok && val == managerLabelValue {
341-
for _, container := range deployment.Spec.Template.Spec.Containers {
342-
if container.Name == managerContainerName {
343-
return container.Env, nil
344-
}
345-
}
346-
347-
return nil, fmt.Errorf("manager deployment does not have container named %q", managerContainerName)
348-
}
349-
}
350-
351-
return nil, fmt.Errorf(
352-
"could not find manager deployment, should have label %s=%s", managerLabel, managerLabelValue,
353-
)
354-
}
355-
356306
// pinImages is used to replace all image tags in the given manifests with digests
357307
func (c bundleCmd) pinImages(manifestPath string) error {
358308
manifests, err := pullspec.FromDirectory(manifestPath, nil)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
// Copyright 2020 The Operator-SDK Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package genutil
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
21+
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
22+
"github.com/operator-framework/operator-sdk/internal/generate/collector"
23+
log "github.com/sirupsen/logrus"
24+
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/util/sets"
26+
)
27+
28+
// FindRelatedImages looks in the controller manager's environment for images used by the operator.
29+
func FindRelatedImages(manifestCol *collector.Manifests) ([]operatorsv1alpha1.RelatedImage, error) {
30+
col := relatedImageCollector{
31+
relatedImages: []*relatedImage{},
32+
relatedImagesByName: make(map[string][]*relatedImage),
33+
relatedImagesByImageRef: make(map[string][]*relatedImage),
34+
seenRelatedImages: sets.NewString(),
35+
}
36+
37+
for _, deployment := range manifestCol.Deployments {
38+
containers := append(deployment.Spec.Template.Spec.Containers, deployment.Spec.Template.Spec.InitContainers...)
39+
for _, container := range containers {
40+
// containerRef can just be the deployment if there's only one container
41+
// otherwise we need {{ deployment.Name }}-{{ container.Name }}
42+
containerRef := deployment.Name
43+
if len(containers) > 1 {
44+
containerRef += "-" + container.Name
45+
}
46+
47+
if err := col.collectFromEnvironment(containerRef, container.Env); err != nil {
48+
return nil, err
49+
}
50+
}
51+
}
52+
53+
return col.collectedRelatedImages(), nil
54+
}
55+
56+
const relatedImagePrefix = "RELATED_IMAGE_"
57+
58+
type relatedImage struct {
59+
name string
60+
imageRef string
61+
containerRef string // If 1 container then {{deployment}} else {{deployment}}-{{container}}
62+
}
63+
64+
type relatedImageCollector struct {
65+
relatedImages []*relatedImage
66+
relatedImagesByName map[string][]*relatedImage
67+
relatedImagesByImageRef map[string][]*relatedImage
68+
seenRelatedImages sets.String
69+
}
70+
71+
func (c *relatedImageCollector) collectFromEnvironment(containerRef string, env []corev1.EnvVar) error {
72+
for _, envVar := range env {
73+
if strings.HasPrefix(envVar.Name, relatedImagePrefix) {
74+
if envVar.ValueFrom != nil {
75+
return fmt.Errorf("related images with valueFrom field unsupported, found in %s`", envVar.Name)
76+
}
77+
78+
name := c.formatName(envVar.Name)
79+
c.collect(name, envVar.Value, containerRef)
80+
}
81+
}
82+
83+
return nil
84+
}
85+
86+
func (c *relatedImageCollector) collect(name, imageRef, containerRef string) {
87+
// Don't add exact duplicates (same name and image)
88+
key := name + "-" + imageRef
89+
if c.seenRelatedImages.Has(key) {
90+
return
91+
}
92+
c.seenRelatedImages.Insert(key)
93+
94+
relatedImg := relatedImage{
95+
name: name,
96+
imageRef: imageRef,
97+
containerRef: containerRef,
98+
}
99+
100+
c.relatedImages = append(c.relatedImages, &relatedImg)
101+
if relatedImages, ok := c.relatedImagesByName[name]; ok {
102+
c.relatedImagesByName[name] = append(relatedImages, &relatedImg)
103+
} else {
104+
c.relatedImagesByName[name] = []*relatedImage{&relatedImg}
105+
}
106+
107+
if relatedImages, ok := c.relatedImagesByImageRef[imageRef]; ok {
108+
c.relatedImagesByImageRef[imageRef] = append(relatedImages, &relatedImg)
109+
} else {
110+
c.relatedImagesByImageRef[imageRef] = []*relatedImage{&relatedImg}
111+
}
112+
}
113+
114+
func (c *relatedImageCollector) collectedRelatedImages() []operatorsv1alpha1.RelatedImage {
115+
final := make([]operatorsv1alpha1.RelatedImage, 0, len(c.relatedImages))
116+
117+
for _, relatedImage := range c.relatedImages {
118+
name := relatedImage.name
119+
120+
// Prefix the name with the containerRef on name collisions.
121+
if len(c.relatedImagesByName[relatedImage.name]) > 1 {
122+
name = relatedImage.containerRef + "-" + name
123+
}
124+
125+
// Only add the related image to the final list if it's the first occurrence of an image.
126+
// Blank out the name since the image is used multiple times and warn the user.
127+
// Multiple containers using she same related image should use the same exact name.
128+
if relatedImages := c.relatedImagesByImageRef[relatedImage.imageRef]; len(relatedImages) > 1 {
129+
if relatedImages[0].name != relatedImage.name {
130+
continue
131+
}
132+
133+
name = ""
134+
log.Warnf(
135+
"warning: multiple related images with the same image ref, %q, and different names found."+
136+
"The image will only be listed once with an empty name."+
137+
"It is recmmended to either remove the duplicate or use the exact same name.",
138+
relatedImage.name,
139+
)
140+
}
141+
142+
final = append(final, operatorsv1alpha1.RelatedImage{Name: name, Image: relatedImage.imageRef})
143+
}
144+
145+
return final
146+
}
147+
148+
// formatName transforms RELATED_IMAGE_This_IS_a_cool_image to this-is-a-cool-image
149+
func (c *relatedImageCollector) formatName(name string) string {
150+
return strings.ToLower(strings.Replace(strings.TrimPrefix(name, relatedImagePrefix), "_", "-", -1))
151+
}

0 commit comments

Comments
 (0)