Skip to content

Commit dd879d8

Browse files
Add new validator for deprecate images
This validator will help us to warning users about the usage of deprecated images found in the CSV
1 parent a4a9bfd commit dd879d8

File tree

3 files changed

+212
-0
lines changed

3 files changed

+212
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package internal
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/operator-framework/api/pkg/manifests"
8+
"github.com/operator-framework/api/pkg/validation/errors"
9+
interfaces "github.com/operator-framework/api/pkg/validation/interfaces"
10+
)
11+
12+
// DeprecatedImages contains a list of deprecated images and their respective messages
13+
var DeprecatedImages = map[string]string{
14+
"gcr.io/kubebuilder/kube-rbac-proxy": "Your bundle uses the image `gcr.io/kubebuilder/kube-rbac-proxy`. This upstream image is deprecated and may become unavailable at any point. \n\nPlease use an equivalent image from a trusted source or update your approach to protect the metrics endpoint. \n\nFor further information: https://github.com/kubernetes-sigs/kubebuilder/discussions/3907",
15+
}
16+
17+
// ImageDeprecateValidator implements Validator to validate bundle objects for deprecated image usage.
18+
var ImageDeprecateValidator interfaces.Validator = interfaces.ValidatorFunc(validateImageDeprecateValidator)
19+
20+
// validateImageDeprecateValidator checks for the presence of deprecated images in the bundle's CSV and deployment specs.
21+
func validateImageDeprecateValidator(objs ...interface{}) (results []errors.ManifestResult) {
22+
for _, obj := range objs {
23+
switch v := obj.(type) {
24+
case *manifests.Bundle:
25+
results = append(results, validateDeprecatedImage(v))
26+
}
27+
}
28+
return results
29+
}
30+
31+
// validateDeprecatedImage checks for deprecated images in both the CSV `RelatedImages` and deployment specs.
32+
func validateDeprecatedImage(bundle *manifests.Bundle) errors.ManifestResult {
33+
result := errors.ManifestResult{}
34+
if bundle == nil {
35+
result.Add(errors.ErrInvalidBundle("Bundle is nil", nil))
36+
return result
37+
}
38+
39+
result.Name = bundle.Name
40+
41+
if bundle.CSV != nil {
42+
for _, relatedImage := range bundle.CSV.Spec.RelatedImages {
43+
for deprecatedImage, message := range DeprecatedImages {
44+
if strings.Contains(relatedImage.Image, deprecatedImage) {
45+
result.Add(errors.WarnFailedValidation(
46+
fmt.Sprintf(message, relatedImage.Image),
47+
relatedImage.Name,
48+
))
49+
}
50+
}
51+
}
52+
}
53+
54+
if bundle.CSV != nil {
55+
for _, deploymentSpec := range bundle.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs {
56+
for _, container := range deploymentSpec.Spec.Template.Spec.Containers {
57+
for deprecatedImage, message := range DeprecatedImages {
58+
if strings.Contains(container.Image, deprecatedImage) {
59+
result.Add(errors.WarnFailedValidation(
60+
fmt.Sprintf(message, container.Image),
61+
deploymentSpec.Name,
62+
))
63+
}
64+
}
65+
}
66+
}
67+
}
68+
69+
return result
70+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package internal
2+
3+
import (
4+
appsv1 "k8s.io/api/apps/v1"
5+
corev1 "k8s.io/api/core/v1"
6+
"reflect"
7+
"strings"
8+
"testing"
9+
10+
"github.com/operator-framework/api/pkg/manifests"
11+
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestValidateDeprecatedImage(t *testing.T) {
16+
type args struct {
17+
bundleDir string
18+
}
19+
tests := []struct {
20+
name string
21+
args args
22+
modifyBundle func(bundle *manifests.Bundle)
23+
wantErr []string
24+
want map[string]string
25+
}{
26+
{
27+
name: "should return no error or warning for a valid bundle",
28+
args: args{
29+
bundleDir: "./testdata/valid_bundle",
30+
},
31+
modifyBundle: func(bundle *manifests.Bundle) {
32+
// Do not modify the bundle
33+
},
34+
wantErr: []string{},
35+
want: map[string]string{},
36+
},
37+
{
38+
name: "should detect deprecated image in RelatedImages",
39+
args: args{
40+
bundleDir: "./testdata/valid_bundle",
41+
},
42+
modifyBundle: func(bundle *manifests.Bundle) {
43+
bundle.CSV.Spec.RelatedImages = append(bundle.CSV.Spec.RelatedImages, operatorsv1alpha1.RelatedImage{
44+
Name: "kube-rbac-proxy",
45+
Image: "gcr.io/kubebuilder/kube-rbac-proxy",
46+
})
47+
},
48+
wantErr: []string{
49+
"Your bundle uses the image `gcr.io/kubebuilder/kube-rbac-proxy`",
50+
},
51+
want: map[string]string{
52+
"RelatedImage": "gcr.io/kubebuilder/kube-rbac-proxy",
53+
},
54+
},
55+
{
56+
name: "should detect deprecated image in DeploymentSpecs",
57+
args: args{
58+
bundleDir: "./testdata/valid_bundle",
59+
},
60+
modifyBundle: func(bundle *manifests.Bundle) {
61+
bundle.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs = append(
62+
bundle.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs,
63+
operatorsv1alpha1.StrategyDeploymentSpec{
64+
Name: "test-deployment",
65+
Spec: appsv1.DeploymentSpec{
66+
Template: corev1.PodTemplateSpec{
67+
Spec: corev1.PodSpec{
68+
Containers: []corev1.Container{
69+
{
70+
Name: "kube-rbac-proxy",
71+
Image: "gcr.io/kubebuilder/kube-rbac-proxy",
72+
},
73+
},
74+
},
75+
},
76+
},
77+
},
78+
)
79+
},
80+
wantErr: []string{
81+
"Your bundle uses the image `gcr.io/kubebuilder/kube-rbac-proxy`",
82+
},
83+
want: map[string]string{
84+
"DeploymentSpec": "gcr.io/kubebuilder/kube-rbac-proxy",
85+
},
86+
},
87+
}
88+
89+
for _, tt := range tests {
90+
t.Run(tt.name, func(t *testing.T) {
91+
bundle, err := manifests.GetBundleFromDir(tt.args.bundleDir)
92+
require.NoError(t, err)
93+
94+
tt.modifyBundle(bundle)
95+
result := validateDeprecatedImage(bundle)
96+
97+
var gotWarnings []string
98+
for _, warn := range result.Warnings {
99+
gotWarnings = append(gotWarnings, warn.Error())
100+
}
101+
102+
for _, expectedErr := range tt.wantErr {
103+
found := false
104+
for _, gotWarning := range gotWarnings {
105+
if strings.Contains(gotWarning, expectedErr) {
106+
found = true
107+
break
108+
}
109+
}
110+
if !found {
111+
t.Errorf("Expected warning containing '%s' but did not find it", expectedErr)
112+
}
113+
}
114+
115+
gotImages := make(map[string]string)
116+
if bundle.CSV != nil {
117+
for _, relatedImage := range bundle.CSV.Spec.RelatedImages {
118+
for deprecatedImage := range DeprecatedImages {
119+
if relatedImage.Image == deprecatedImage {
120+
gotImages["RelatedImage"] = deprecatedImage
121+
}
122+
}
123+
}
124+
for _, deploymentSpec := range bundle.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs {
125+
for _, container := range deploymentSpec.Spec.Template.Spec.Containers {
126+
for deprecatedImage := range DeprecatedImages {
127+
if container.Image == deprecatedImage {
128+
gotImages["DeploymentSpec"] = deprecatedImage
129+
}
130+
}
131+
}
132+
}
133+
}
134+
135+
require.True(t, reflect.DeepEqual(tt.want, gotImages))
136+
})
137+
}
138+
}

pkg/validation/validation.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ var GoodPracticesValidator = internal.GoodPracticesValidator
7878
// information check: https://olm.operatorframework.io/docs/advanced-tasks/ship-operator-supporting-multiarch/
7979
var MultipleArchitecturesValidator = internal.MultipleArchitecturesValidator
8080

81+
// ImageDeprecateValidator implements Validator to validate Images Deprecated found in the CSV configuration.
82+
var ImageDeprecateValidator = internal.ImageDeprecateValidator
83+
8184
// AllValidators implements Validator to validate all Operator manifest types.
8285
var AllValidators = interfaces.Validators{
8386
PackageManifestValidator,
@@ -93,6 +96,7 @@ var AllValidators = interfaces.Validators{
9396
AlphaDeprecatedAPIsValidator,
9497
GoodPracticesValidator,
9598
MultipleArchitecturesValidator,
99+
ImageDeprecateValidator,
96100
}
97101

98102
var DefaultBundleValidators = interfaces.Validators{

0 commit comments

Comments
 (0)