Skip to content

Commit 9fd8933

Browse files
authored
ROX-7150: Allow overriding related images in operator (stackrox/rox#8496)
1 parent e35defb commit 9fd8933

File tree

20 files changed

+326
-19
lines changed

20 files changed

+326
-19
lines changed

.idea/codeStyles/Project.xml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

image/templates/helm/stackrox-secured-cluster/internal/config-shape.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ image:
3535
fullRef: null # string
3636
pullPolicy: null # string
3737
collector:
38+
slim:
39+
fullRef: null # string
40+
full:
41+
fullRef: null # string
3842
registry: null # string
3943
name: null # string
4044
repository: null # string

image/templates/helm/stackrox-secured-cluster/internal/defaults/50-images.yaml.htpl

+11-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ image:
2121
collector:
2222
repository: {{ list ._rox.image.collector.registry ._rox.image.collector.name | compact | join "/" }}
2323
---
24+
collector:
25+
slimMode: {{ eq ._rox.image.collector.registry "[< .CollectorRegistry >]" }}
26+
---
27+
image:
28+
collector:
29+
{{- if and ._rox.collector.slimMode ._rox.image.collector.slim.fullRef }}
30+
fullRef: {{ ._rox.image.collector.slim.fullRef }}
31+
{{- else if and (not ._rox.collector.slimMode) ._rox.image.collector.full.fullRef }}
32+
fullRef: {{ ._rox.image.collector.full.fullRef }}
33+
{{- end }}
34+
---
2435
image:
2536
main:
2637
{{- if or ._rox.image.main.tag ._rox.image.main.fullRef }}
@@ -39,9 +50,6 @@ image:
3950
_abbrevImageRef: {{ ._rox.image.collector.repository }}
4051
{{- end }}
4152
---
42-
collector:
43-
slimMode: {{ eq ._rox.image.collector.registry "[< .CollectorRegistry >]" }}
44-
---
4553
image:
4654
collector:
4755
{{- if ._rox.collector.slimMode }}

operator/api/common/v1alpha1/types.go

-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ type DeploymentSpec struct {
3333
Resources *Resources `json:"resources,omitempty"`
3434
// Customizations to apply on this deployment.
3535
Customize *CustomizeSpec `json:"customize,omitempty"`
36-
// TODO(ROX-7150): We do not support setting image in the CRs because they are determined by
37-
// the operator version whose lifecycle is orthogonal to that of the CR.
3836
}
3937

4038
// ServiceTLSSpec is just a wrapper for ServiceTLS field to make documentation available in all spots where it is used.

operator/api/securedcluster/v1alpha1/securedcluster_types.go

-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ type SecuredClusterSpec struct {
5353
// Customizations to apply on all secured cluster components.
5454
//+operator-sdk:csv:customresourcedefinitions:type=spec
5555
Customize *common.CustomizeSpec `json:"customize,omitempty"`
56-
// TODO(ROX-7150): We do not support setting image in the CRs because they are determined by
57-
// the operator version whose lifecycle is orthogonal to that of the CR.
5856
}
5957

6058
// SensorComponentSpec defines settings for sensor.

operator/bundle/manifests/rhacs-operator.clusterserviceversion.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,12 @@ spec:
307307
- --health-probe-bind-address=:8081
308308
- --metrics-bind-address=127.0.0.1:8080
309309
- --leader-elect
310+
env:
311+
- name: RELATED_IMAGE_MAIN
312+
- name: RELATED_IMAGE_SCANNER
313+
- name: RELATED_IMAGE_SCANNER_DB
314+
- name: RELATED_IMAGE_COLLECTOR_SLIM
315+
- name: RELATED_IMAGE_COLLECTOR_FULL
310316
image: docker.io/stackrox/stackrox-operator:0.0.1
311317
livenessProbe:
312318
httpGet:

operator/config/manager/manager.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ spec:
2727
containers:
2828
- args:
2929
- --leader-elect
30+
env:
31+
- name: RELATED_IMAGE_MAIN
32+
- name: RELATED_IMAGE_SCANNER
33+
- name: RELATED_IMAGE_SCANNER_DB
34+
- name: RELATED_IMAGE_COLLECTOR_SLIM
35+
- name: RELATED_IMAGE_COLLECTOR_FULL
3036
image: controller:latest
3137
name: manager
3238
securityContext:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package translation
2+
3+
import "github.com/stackrox/rox/operator/pkg/images"
4+
5+
var (
6+
imageOverrides = images.Overrides{
7+
images.Main: "central.image.fullRef",
8+
images.Scanner: "scanner.image.fullRef",
9+
images.ScannerDB: "scanner.dbImage.fullRef",
10+
}
11+
)

operator/pkg/central/values/translation/translation.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/pkg/errors"
1111
central "github.com/stackrox/rox/operator/api/central/v1alpha1"
1212
"github.com/stackrox/rox/operator/pkg/values/translation"
13+
"github.com/stackrox/rox/pkg/helmutil"
1314
"github.com/stackrox/rox/pkg/utils"
1415
"helm.sh/helm/v3/pkg/chartutil"
1516
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -45,7 +46,11 @@ func (t Translator) Translate(ctx context.Context, u *unstructured.Unstructured)
4546
return nil, err
4647
}
4748

48-
return chartutil.CoalesceTables(baseValues, valsFromCR), nil
49+
imageOverrideVals, err := imageOverrides.ToValues()
50+
if err != nil {
51+
return nil, errors.Wrap(err, "computing image override values")
52+
}
53+
return helmutil.CoalesceTables(baseValues, imageOverrideVals, valsFromCR), nil
4954
}
5055

5156
// translate translates a Central CR into helm values.

operator/pkg/images/env.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package images
2+
3+
import "github.com/stackrox/rox/pkg/env"
4+
5+
// Environment variable settings for related image overrides.
6+
var (
7+
Main = env.RegisterSetting("RELATED_IMAGE_MAIN")
8+
Scanner = env.RegisterSetting("RELATED_IMAGE_SCANNER")
9+
ScannerDB = env.RegisterSetting("RELATED_IMAGE_SCANNER_DB")
10+
CollectorSlim = env.RegisterSetting("RELATED_IMAGE_COLLECTOR_SLIM")
11+
CollectorFull = env.RegisterSetting("RELATED_IMAGE_COLLECTOR_FULL")
12+
)

operator/pkg/images/overrides.go

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package images
2+
3+
import (
4+
"github.com/pkg/errors"
5+
"github.com/stackrox/rox/pkg/env"
6+
"github.com/stackrox/rox/pkg/helmutil"
7+
"helm.sh/helm/v3/pkg/chartutil"
8+
)
9+
10+
// Overrides defines a mapping from image override environment variable settings to
11+
// Helm chart configuration paths.
12+
type Overrides map[env.Setting]string
13+
14+
// ToValues returns a Helm chart values object that applies the override settings that are set.
15+
func (o Overrides) ToValues() (chartutil.Values, error) {
16+
vals := chartutil.Values{}
17+
for setting, configPath := range o {
18+
val := setting.Setting()
19+
if val == "" {
20+
continue
21+
}
22+
newVals, err := helmutil.ValuesForKVPair(configPath, val)
23+
if err != nil {
24+
return nil, errors.Wrapf(err, "applying image override from %s", setting.EnvVar())
25+
}
26+
vals = chartutil.CoalesceTables(vals, newVals)
27+
}
28+
return vals, nil
29+
}

operator/pkg/images/overrides_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package images
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stackrox/rox/pkg/testutils/envisolator"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
"helm.sh/helm/v3/pkg/chartutil"
10+
)
11+
12+
func TestToValues(t *testing.T) {
13+
testOverrides := Overrides{
14+
Main: "central.image.fullRef",
15+
Scanner: "scanner.image.fullRef",
16+
ScannerDB: "scanner.dbImage.fullRef",
17+
}
18+
19+
ei := envisolator.NewEnvIsolator(t)
20+
defer ei.RestoreAll()
21+
22+
ei.Setenv(Main.EnvVar(), "override-main")
23+
ei.Unsetenv(Scanner.EnvVar())
24+
ei.Setenv(ScannerDB.EnvVar(), "")
25+
26+
vals, err := testOverrides.ToValues()
27+
require.NoError(t, err)
28+
29+
expectedVals := chartutil.Values{
30+
"central": map[string]interface{}{
31+
"image": map[string]interface{}{
32+
"fullRef": "override-main",
33+
},
34+
},
35+
}
36+
37+
assert.Equal(t, expectedVals, vals)
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package translation
2+
3+
import "github.com/stackrox/rox/operator/pkg/images"
4+
5+
var (
6+
imageOverrides = images.Overrides{
7+
images.Main: "image.main.fullRef",
8+
images.CollectorSlim: "image.collector.slim.fullRef",
9+
images.CollectorFull: "image.collector.full.fullRef",
10+
}
11+
)

operator/pkg/securedcluster/values/translation/translation.go

+12-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
securedcluster "github.com/stackrox/rox/operator/api/securedcluster/v1alpha1"
1313
"github.com/stackrox/rox/operator/pkg/values/translation"
1414
"github.com/stackrox/rox/pkg/buildinfo"
15+
"github.com/stackrox/rox/pkg/helmutil"
1516
"helm.sh/helm/v3/pkg/chartutil"
1617
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
1718
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -53,7 +54,17 @@ func (t Translator) Translate(ctx context.Context, u *unstructured.Unstructured)
5354
return nil, err
5455
}
5556

56-
return t.translate(ctx, sc)
57+
valsFromCR, err := t.translate(ctx, sc)
58+
if err != nil {
59+
return nil, err
60+
}
61+
62+
imageOverrideVals, err := imageOverrides.ToValues()
63+
if err != nil {
64+
return nil, errors.Wrap(err, "computing image override values")
65+
}
66+
67+
return helmutil.CoalesceTables(imageOverrideVals, valsFromCR), nil
5768
}
5869

5970
// Translate translates a SecuredCluster CR into helm values.
@@ -73,7 +84,6 @@ func (t Translator) translate(ctx context.Context, sc securedcluster.SecuredClus
7384
v.AddAllFrom(translation.GetImagePullSecrets(sc.Spec.ImagePullSecrets))
7485

7586
// TODO(ROX-7178): support explicit env.openshift and env.istio setting
76-
// TODO(ROX-7150): support setting/overriding images
7787

7888
customize := translation.NewValuesBuilder()
7989

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: "test explicit image reference specified for slim or full mode (Operator use only)"
2+
defs: |
3+
def collector:
4+
container(.daemonsets.collector; "collector");
5+
values:
6+
image:
7+
collector:
8+
registry: docker.io/stackrox
9+
name: collector
10+
tests:
11+
- name: "slim mode"
12+
set:
13+
collector.slimMode: true
14+
tests:
15+
- name: "default image is used if full ref is specified for full image only"
16+
set:
17+
image.collector.full.fullRef: "registry.redhat.io/rh-acs/collector:1.2.3-latest"
18+
expect: |
19+
collector | .image | assertThat(startswith("docker.io/stackrox/collector:") and endswith("-slim"))
20+
- name: "override image is used if full ref is specified"
21+
set:
22+
image.collector.slim.fullRef: "registry.redhat.io/rh-acs/collector:1.2.3-slim"
23+
expect: |
24+
collector | .image | assertThat(. == "registry.redhat.io/rh-acs/collector:1.2.3-slim")
25+
tests:
26+
- name: "for slim image only"
27+
- name: "for both images"
28+
set:
29+
image.collector.full.fullRef: "registry.redhat.io/rh-acs/collector:1.2.3-latest"
30+
- name: "full mode"
31+
set:
32+
collector.slimMode: false
33+
tests:
34+
- name: "default image is used if full ref is specified for slim image only"
35+
set:
36+
image.collector.slim.fullRef: "registry.redhat.io/rh-acs/collector:1.2.3-slim"
37+
expect: |
38+
collector | .image | assertThat(startswith("docker.io/stackrox/collector:") and endswith("-latest"))
39+
- name: "override image is used if full ref is specified"
40+
set:
41+
image.collector.full.fullRef: "registry.redhat.io/rh-acs/collector:1.2.3-latest"
42+
expect: |
43+
collector | .image | assertThat(. == "registry.redhat.io/rh-acs/collector:1.2.3-latest")
44+
tests:
45+
- name: "for full image only"
46+
- name: "for both images"
47+
set:
48+
image.collector.slim.fullRef: "registry.redhat.io/rh-acs/collector:1.2.3-slim"

pkg/helmtest/test.go

+5-9
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,19 @@ import (
66
"testing"
77

88
"github.com/pkg/errors"
9+
"github.com/stackrox/rox/pkg/helmutil"
910
"github.com/stackrox/rox/pkg/pointers"
1011
"helm.sh/helm/v3/pkg/chartutil"
1112
)
1213

1314
// applySetOptions takes the values specified in the `set` stanza and merges them into the otherwise defined values.
1415
func (t *Test) applySetOptions() error {
1516
for keyPathStr, val := range t.Set {
16-
keyPath := strings.Split(keyPathStr, ".")
17-
if len(keyPath) == 0 {
18-
return errors.New("empty key in 'set'")
17+
vals, err := helmutil.ValuesForKVPair(keyPathStr, val)
18+
if err != nil {
19+
return errors.Wrap(err, "in 'set'")
1920
}
20-
21-
mapForSet := map[string]interface{}{keyPath[len(keyPath)-1]: val}
22-
for i := len(keyPath) - 2; i >= 0; i-- {
23-
mapForSet = map[string]interface{}{keyPath[i]: mapForSet}
24-
}
25-
t.Values = chartutil.CoalesceTables(mapForSet, t.Values)
21+
t.Values = chartutil.CoalesceTables(vals, t.Values)
2622
}
2723
t.Set = nil // no longer used, but make sure this is idempotent.
2824

pkg/helmutil/tables.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package helmutil
2+
3+
import "helm.sh/helm/v3/pkg/chartutil"
4+
5+
// CoalesceTables is a variadic version of chartutil.CoalesceTables from the official Helm libraries.
6+
// It combines an arbitrary number of tables, modifying the first argument (`dst`) and giving preference
7+
// to arguments in left-to-right order.
8+
// Hence, `CoalesceTables(dst, src1, src2, ..., srcN)` is equivalent to calling
9+
// CoalesceTables(...CoalesceTables(CoalesceTables(dst, src1), src2)..., srcN)
10+
func CoalesceTables(dst map[string]interface{}, srcs ...map[string]interface{}) map[string]interface{} {
11+
res := dst
12+
for _, src := range srcs {
13+
res = chartutil.CoalesceTables(res, src)
14+
}
15+
return res
16+
}

pkg/helmutil/tables_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package helmutil
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestCoalesceTables_LeftToRight(t *testing.T) {
10+
dst := map[string]interface{}{
11+
"foo": map[string]interface{}{
12+
"bar": "baz",
13+
},
14+
}
15+
src1 := map[string]interface{}{
16+
"foo": map[string]interface{}{
17+
"bar": "nope",
18+
"qux": "quux",
19+
},
20+
}
21+
src2 := map[string]interface{}{
22+
"foo": map[string]interface{}{
23+
"bar": "nope nope",
24+
"qux": "NOPE",
25+
},
26+
"quuz": "corge",
27+
}
28+
29+
result := CoalesceTables(dst, src1, src2)
30+
31+
expected := map[string]interface{}{
32+
"foo": map[string]interface{}{
33+
"bar": "baz",
34+
"qux": "quux",
35+
},
36+
"quuz": "corge",
37+
}
38+
39+
assert.Equal(t, expected, result)
40+
}

0 commit comments

Comments
 (0)