Skip to content

Commit cf85bf7

Browse files
authored
OCPBUGS-4955: Set ImagePullPolicy of bundle unpacker to "IfNotPresent" for image digests (#2908)
* Capture ImagePullPolicy selection from image as func, export for wider use, implement in bundle unpacker, add tests Signed-off-by: Daniel Franz <[email protected]> * Fixed using wrong image as reference for pull policy inference Signed-off-by: Daniel Franz <[email protected]> * Fixed unit test using wrong vars for path/image/etc. Signed-off-by: Daniel Franz <[email protected]> * Remove comment block Signed-off-by: Daniel Franz <[email protected]> * Generate hash just once for digestPath Signed-off-by: Daniel Franz <[email protected]> Signed-off-by: Daniel Franz <[email protected]>
1 parent d82537c commit cf85bf7

File tree

5 files changed

+87
-41
lines changed

5 files changed

+87
-41
lines changed

pkg/controller/bundle/bundle_unpacker.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
listersoperatorsv1alpha1 "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1alpha1"
3030
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install"
3131
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/projection"
32+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/image"
3233
)
3334

3435
const (
@@ -170,7 +171,7 @@ func (c *ConfigMapUnpacker) job(cmRef *corev1.ObjectReference, bundlePath string
170171
{
171172
Name: "pull",
172173
Image: bundlePath,
173-
ImagePullPolicy: "Always",
174+
ImagePullPolicy: image.InferImagePullPolicy(bundlePath),
174175
Command: []string{"/util/cpb", "/bundle"}, // Copy bundle content to its mount
175176
VolumeMounts: []corev1.VolumeMount{
176177
{

pkg/controller/bundle/bundle_unpacker_test.go

+29-26
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@ const (
3333
opmImage = "opm-image"
3434
utilImage = "util-image"
3535
bundlePath = "bundle-path"
36+
digestPath = "bundle-path@sha256:54d626e08c1c802b305dad30b7e54a82f102390cc92c7d4db112048935236e9c"
3637
runAsUser = 1001
3738
)
3839

3940
func TestConfigMapUnpacker(t *testing.T) {
4041
pathHash := hash(bundlePath)
42+
digestHash := hash(digestPath)
4143
start := metav1.Now()
4244
now := func() metav1.Time {
4345
return start
@@ -398,18 +400,18 @@ func TestConfigMapUnpacker(t *testing.T) {
398400
},
399401
},
400402
{
401-
description: "CatalogSourcePresent/ConfigMapPresent/JobPresent/Unpacked",
403+
description: "CatalogSourcePresent/ConfigMapPresent/JobPresent/DigestImage/Unpacked",
402404
fields: fields{
403405
objs: []runtime.Object{
404406
&batchv1.Job{
405407
ObjectMeta: metav1.ObjectMeta{
406-
Name: pathHash,
408+
Name: digestHash,
407409
Namespace: "ns-a",
408410
OwnerReferences: []metav1.OwnerReference{
409411
{
410412
APIVersion: "v1",
411413
Kind: "ConfigMap",
412-
Name: pathHash,
414+
Name: digestHash,
413415
Controller: &blockOwnerDeletion,
414416
BlockOwnerDeletion: &blockOwnerDeletion,
415417
},
@@ -420,7 +422,7 @@ func TestConfigMapUnpacker(t *testing.T) {
420422
BackoffLimit: &backoffLimit,
421423
Template: corev1.PodTemplateSpec{
422424
ObjectMeta: metav1.ObjectMeta{
423-
Name: pathHash,
425+
Name: digestHash,
424426
},
425427
Spec: corev1.PodSpec{
426428
RestartPolicy: corev1.RestartPolicyNever,
@@ -435,11 +437,11 @@ func TestConfigMapUnpacker(t *testing.T) {
435437
{
436438
Name: "extract",
437439
Image: opmImage,
438-
Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", pathHash, "-z"},
440+
Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", digestHash, "-z"},
439441
Env: []corev1.EnvVar{
440442
{
441443
Name: configmap.EnvContainerImage,
442-
Value: bundlePath,
444+
Value: digestPath,
443445
},
444446
},
445447
VolumeMounts: []corev1.VolumeMount{
@@ -488,8 +490,8 @@ func TestConfigMapUnpacker(t *testing.T) {
488490
},
489491
{
490492
Name: "pull",
491-
Image: bundlePath,
492-
ImagePullPolicy: "Always",
493+
Image: digestPath,
494+
ImagePullPolicy: "IfNotPresent",
493495
Command: []string{"/util/cpb", "/bundle"}, // Copy bundle content to its mount
494496
VolumeMounts: []corev1.VolumeMount{
495497
{
@@ -548,7 +550,7 @@ func TestConfigMapUnpacker(t *testing.T) {
548550
},
549551
&corev1.ConfigMap{
550552
ObjectMeta: metav1.ObjectMeta{
551-
Name: pathHash,
553+
Name: digestHash,
552554
Namespace: "ns-a",
553555
OwnerReferences: []metav1.OwnerReference{
554556
{
@@ -580,7 +582,7 @@ func TestConfigMapUnpacker(t *testing.T) {
580582
args: args{
581583
annotationTimeout: -1 * time.Minute,
582584
lookup: &operatorsv1alpha1.BundleLookup{
583-
Path: bundlePath,
585+
Path: digestPath,
584586
Replaces: "",
585587
CatalogSourceRef: &corev1.ObjectReference{
586588
Namespace: "ns-a",
@@ -600,14 +602,14 @@ func TestConfigMapUnpacker(t *testing.T) {
600602
expected: expected{
601603
res: &BundleUnpackResult{
602604
BundleLookup: &operatorsv1alpha1.BundleLookup{
603-
Path: bundlePath,
605+
Path: digestPath,
604606
Replaces: "",
605607
CatalogSourceRef: &corev1.ObjectReference{
606608
Namespace: "ns-a",
607609
Name: "src-a",
608610
},
609611
},
610-
name: pathHash,
612+
name: digestHash,
611613
bundle: &api.Bundle{
612614
CsvName: "etcdoperator.v0.9.2",
613615
CsvJson: csvJSON + "\n",
@@ -622,7 +624,7 @@ func TestConfigMapUnpacker(t *testing.T) {
622624
configMaps: []*corev1.ConfigMap{
623625
{
624626
ObjectMeta: metav1.ObjectMeta{
625-
Name: pathHash,
627+
Name: digestHash,
626628
Namespace: "ns-a",
627629
Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue},
628630
OwnerReferences: []metav1.OwnerReference{
@@ -646,13 +648,13 @@ func TestConfigMapUnpacker(t *testing.T) {
646648
jobs: []*batchv1.Job{
647649
{
648650
ObjectMeta: metav1.ObjectMeta{
649-
Name: pathHash,
651+
Name: digestHash,
650652
Namespace: "ns-a",
651653
OwnerReferences: []metav1.OwnerReference{
652654
{
653655
APIVersion: "v1",
654656
Kind: "ConfigMap",
655-
Name: pathHash,
657+
Name: digestHash,
656658
Controller: &blockOwnerDeletion,
657659
BlockOwnerDeletion: &blockOwnerDeletion,
658660
},
@@ -663,7 +665,7 @@ func TestConfigMapUnpacker(t *testing.T) {
663665
BackoffLimit: &backoffLimit,
664666
Template: corev1.PodTemplateSpec{
665667
ObjectMeta: metav1.ObjectMeta{
666-
Name: pathHash,
668+
Name: digestHash,
667669
},
668670
Spec: corev1.PodSpec{
669671
RestartPolicy: corev1.RestartPolicyNever,
@@ -678,11 +680,11 @@ func TestConfigMapUnpacker(t *testing.T) {
678680
{
679681
Name: "extract",
680682
Image: opmImage,
681-
Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", pathHash, "-z"},
683+
Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", digestHash, "-z"},
682684
Env: []corev1.EnvVar{
683685
{
684686
Name: configmap.EnvContainerImage,
685-
Value: bundlePath,
687+
Value: digestPath,
686688
},
687689
},
688690
VolumeMounts: []corev1.VolumeMount{
@@ -731,8 +733,8 @@ func TestConfigMapUnpacker(t *testing.T) {
731733
},
732734
{
733735
Name: "pull",
734-
Image: bundlePath,
735-
ImagePullPolicy: "Always",
736+
Image: digestPath,
737+
ImagePullPolicy: "IfNotPresent",
736738
Command: []string{"/util/cpb", "/bundle"}, // Copy bundle content to its mount
737739
VolumeMounts: []corev1.VolumeMount{
738740
{
@@ -793,13 +795,13 @@ func TestConfigMapUnpacker(t *testing.T) {
793795
roles: []*rbacv1.Role{
794796
{
795797
ObjectMeta: metav1.ObjectMeta{
796-
Name: pathHash,
798+
Name: digestHash,
797799
Namespace: "ns-a",
798800
OwnerReferences: []metav1.OwnerReference{
799801
{
800802
APIVersion: "v1",
801803
Kind: "ConfigMap",
802-
Name: pathHash,
804+
Name: digestHash,
803805
Controller: &blockOwnerDeletion,
804806
BlockOwnerDeletion: &blockOwnerDeletion,
805807
},
@@ -817,7 +819,7 @@ func TestConfigMapUnpacker(t *testing.T) {
817819
"configmaps",
818820
},
819821
ResourceNames: []string{
820-
pathHash,
822+
digestHash,
821823
},
822824
},
823825
},
@@ -826,13 +828,13 @@ func TestConfigMapUnpacker(t *testing.T) {
826828
roleBindings: []*rbacv1.RoleBinding{
827829
{
828830
ObjectMeta: metav1.ObjectMeta{
829-
Name: pathHash,
831+
Name: digestHash,
830832
Namespace: "ns-a",
831833
OwnerReferences: []metav1.OwnerReference{
832834
{
833835
APIVersion: "v1",
834836
Kind: "ConfigMap",
835-
Name: pathHash,
837+
Name: digestHash,
836838
Controller: &blockOwnerDeletion,
837839
BlockOwnerDeletion: &blockOwnerDeletion,
838840
},
@@ -849,7 +851,7 @@ func TestConfigMapUnpacker(t *testing.T) {
849851
RoleRef: rbacv1.RoleRef{
850852
APIGroup: "rbac.authorization.k8s.io",
851853
Kind: "Role",
852-
Name: pathHash,
854+
Name: digestHash,
853855
},
854856
},
855857
},
@@ -1506,6 +1508,7 @@ func TestConfigMapUnpacker(t *testing.T) {
15061508
if tt.expected.res.bundle == nil {
15071509
require.Nil(t, res.bundle)
15081510
} else {
1511+
require.NotNil(t, res.bundle)
15091512
require.Equal(t, tt.expected.res.bundle.CsvJson, res.bundle.CsvJson)
15101513
require.Equal(t, tt.expected.res.bundle.CsvName, res.bundle.CsvName)
15111514
require.Equal(t, tt.expected.res.bundle.Version, res.bundle.Version)

pkg/controller/registry/reconciler/reconciler.go

+4-14
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ package reconciler
44
import (
55
"fmt"
66
"hash/fnv"
7-
"strings"
87

98
corev1 "k8s.io/api/core/v1"
109
"k8s.io/apimachinery/pkg/api/resource"
@@ -14,6 +13,7 @@ import (
1413

1514
operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1"
1615
controllerclient "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/controller-runtime/client"
16+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/image"
1717
hashutil "github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubernetes/pkg/util/hash"
1818
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorclient"
1919
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/operatorlister"
@@ -107,17 +107,7 @@ func NewRegistryReconcilerFactory(lister operatorlister.OperatorLister, opClient
107107
}
108108
}
109109

110-
func Pod(source *operatorsv1alpha1.CatalogSource, name string, image string, saName string, labels map[string]string, annotations map[string]string, readinessDelay int32, livenessDelay int32, runAsUser int64) *corev1.Pod {
111-
// Ensure the catalog image is always pulled if the image is not based on a digest, measured by whether an "@" is included.
112-
// See https://github.com/docker/distribution/blob/master/reference/reference.go for more info.
113-
// This means recreating non-digest based catalog pods will result in the latest version of the catalog content being delivered on-cluster.
114-
var pullPolicy corev1.PullPolicy
115-
if strings.Contains(image, "@") {
116-
pullPolicy = corev1.PullIfNotPresent
117-
} else {
118-
pullPolicy = corev1.PullAlways
119-
}
120-
110+
func Pod(source *operatorsv1alpha1.CatalogSource, name string, img string, saName string, labels map[string]string, annotations map[string]string, readinessDelay int32, livenessDelay int32, runAsUser int64) *corev1.Pod {
121111
// make a copy of the labels and annotations to avoid mutating the input parameters
122112
podLabels := make(map[string]string)
123113
podAnnotations := make(map[string]string)
@@ -141,7 +131,7 @@ func Pod(source *operatorsv1alpha1.CatalogSource, name string, image string, saN
141131
Containers: []corev1.Container{
142132
{
143133
Name: name,
144-
Image: image,
134+
Image: img,
145135
Ports: []corev1.ContainerPort{
146136
{
147137
Name: "grpc",
@@ -184,7 +174,7 @@ func Pod(source *operatorsv1alpha1.CatalogSource, name string, image string, saN
184174
SecurityContext: &corev1.SecurityContext{
185175
ReadOnlyRootFilesystem: pointer.Bool(false),
186176
},
187-
ImagePullPolicy: pullPolicy,
177+
ImagePullPolicy: image.InferImagePullPolicy(img),
188178
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
189179
},
190180
},

pkg/lib/image/image.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package image
2+
3+
import (
4+
"strings"
5+
6+
corev1 "k8s.io/api/core/v1"
7+
)
8+
9+
func InferImagePullPolicy(image string) corev1.PullPolicy {
10+
// Ensure the image is always pulled if the image is not based on a digest, measured by whether an "@" is included.
11+
// See https://github.com/docker/distribution/blob/master/reference/reference.go for more info.
12+
// This means recreating non-digest based pods will result in the latest version of the content being delivered on-cluster.
13+
if strings.Contains(image, "@") {
14+
return corev1.PullIfNotPresent
15+
}
16+
return corev1.PullAlways
17+
}

pkg/lib/image/image_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package image_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/image"
7+
"github.com/stretchr/testify/require"
8+
corev1 "k8s.io/api/core/v1"
9+
)
10+
11+
func TestInferImagePullPolicy(t *testing.T) {
12+
tests := []struct {
13+
description string
14+
img string
15+
expectedPolicy corev1.PullPolicy
16+
}{
17+
{
18+
description: "WithImageTag",
19+
img: "my-image:my-tag",
20+
expectedPolicy: corev1.PullAlways,
21+
},
22+
{
23+
description: "WithImageDigest",
24+
img: "my-image@sha256:54d626e08c1c802b305dad30b7e54a82f102390cc92c7d4db112048935236e9c",
25+
expectedPolicy: corev1.PullIfNotPresent,
26+
},
27+
}
28+
29+
for _, tt := range tests {
30+
t.Run(tt.description, func(t *testing.T) {
31+
policy := image.InferImagePullPolicy(tt.img)
32+
require.Equal(t, tt.expectedPolicy, policy)
33+
})
34+
}
35+
}

0 commit comments

Comments
 (0)