github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/controller/bundle/bundle_unpacker_test.go (about) 1 package bundle 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 batchv1 "k8s.io/api/batch/v1" 13 corev1 "k8s.io/api/core/v1" 14 rbacv1 "k8s.io/api/rbac/v1" 15 "k8s.io/apimachinery/pkg/api/resource" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 "k8s.io/apimachinery/pkg/runtime" 18 "k8s.io/client-go/informers" 19 k8sfake "k8s.io/client-go/kubernetes/fake" 20 "k8s.io/client-go/tools/cache" 21 "k8s.io/utils/ptr" 22 23 operatorsv1 "github.com/operator-framework/api/pkg/operators/v1" 24 operatorsv1alpha1 "github.com/operator-framework/api/pkg/operators/v1alpha1" 25 crfake "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake" 26 crinformers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/informers/externalversions" 27 v1listers "github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/listers/operators/v1" 28 "github.com/operator-framework/operator-lifecycle-manager/pkg/controller/install" 29 "github.com/operator-framework/operator-registry/pkg/api" 30 "github.com/operator-framework/operator-registry/pkg/configmap" 31 ) 32 33 const ( 34 csvJSON = "{\"apiVersion\":\"operators.coreos.com/v1alpha1\",\"kind\":\"ClusterServiceVersion\",\"metadata\":{\"annotations\":{\"olm.skipRange\":\"\\u003c 0.6.0\",\"tectonic-visibility\":\"ocs\"},\"name\":\"etcdoperator.v0.9.2\",\"namespace\":\"placeholder\"},\"spec\":{\"customresourcedefinitions\":{\"owned\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]},{\"description\":\"Limits describes the minimum/maximum amount of compute resources required/allowed\",\"displayName\":\"Resource Requirements\",\"path\":\"pod.resources\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:resourceRequirements\"]}],\"statusDescriptors\":[{\"description\":\"The status of each of the member Pods for the etcd cluster.\",\"displayName\":\"Member Status\",\"path\":\"members\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podStatuses\"]},{\"description\":\"The service at which the running etcd cluster can be accessed.\",\"displayName\":\"Service\",\"path\":\"serviceName\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Service\"]},{\"description\":\"The current size of the etcd cluster.\",\"displayName\":\"Cluster Size\",\"path\":\"size\"},{\"description\":\"The current version of the etcd cluster.\",\"displayName\":\"Current Version\",\"path\":\"currentVersion\"},{\"description\":\"The target version of the etcd cluster, after upgrading.\",\"displayName\":\"Target Version\",\"path\":\"targetVersion\"},{\"description\":\"The current status of the etcd cluster.\",\"displayName\":\"Status\",\"path\":\"phase\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase\"]},{\"description\":\"Explanation for the current status of the cluster.\",\"displayName\":\"Status Details\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to backup an etcd cluster.\",\"displayName\":\"etcd Backup\",\"kind\":\"EtcdBackup\",\"name\":\"etcdbackups.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"Specifies the endpoints of an etcd cluster.\",\"displayName\":\"etcd Endpoint(s)\",\"path\":\"etcdEndpoints\",\"x-descriptors\":[\"urn:alm:descriptor:etcd:endpoint\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the backup was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any backup related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"},{\"description\":\"Represents the intent to restore an etcd cluster from a backup.\",\"displayName\":\"etcd Restore\",\"kind\":\"EtcdRestore\",\"name\":\"etcdrestores.etcd.database.coreos.com\",\"specDescriptors\":[{\"description\":\"References the EtcdCluster which should be restored,\",\"displayName\":\"etcd Cluster\",\"path\":\"etcdCluster.name\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:EtcdCluster\",\"urn:alm:descriptor:text\"]},{\"description\":\"The full AWS S3 path where the backup is saved.\",\"displayName\":\"S3 Path\",\"path\":\"s3.path\",\"x-descriptors\":[\"urn:alm:descriptor:aws:s3:path\"]},{\"description\":\"The name of the secret object that stores the AWS credential and config files.\",\"displayName\":\"AWS Secret\",\"path\":\"s3.awsSecret\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes:Secret\"]}],\"statusDescriptors\":[{\"description\":\"Indicates if the restore was successful.\",\"displayName\":\"Succeeded\",\"path\":\"succeeded\",\"x-descriptors\":[\"urn:alm:descriptor:text\"]},{\"description\":\"Indicates the reason for any restore related failures.\",\"displayName\":\"Reason\",\"path\":\"reason\",\"x-descriptors\":[\"urn:alm:descriptor:io.kubernetes.phase:reason\"]}],\"version\":\"v1beta2\"}],\"required\":[{\"description\":\"Represents a cluster of etcd nodes.\",\"displayName\":\"etcd Cluster\",\"kind\":\"EtcdCluster\",\"name\":\"etcdclusters.etcd.database.coreos.com\",\"resources\":[{\"kind\":\"Service\",\"version\":\"v1\"},{\"kind\":\"Pod\",\"version\":\"v1\"}],\"specDescriptors\":[{\"description\":\"The desired number of member Pods for the etcd cluster.\",\"displayName\":\"Size\",\"path\":\"size\",\"x-descriptors\":[\"urn:alm:descriptor:com.tectonic.ui:podCount\"]}],\"version\":\"v1beta2\"}]},\"description\":\"etcd is a distributed key value store that provides a reliable way to store data across a cluster of machines. It’s open-source and available on GitHub. etcd gracefully handles leader elections during network partitions and will tolerate machine failure, including the leader. Your applications can read and write data into etcd.\\nA simple use-case is to store database connection details or feature flags within etcd as key value pairs. These values can be watched, allowing your app to reconfigure itself when they change. Advanced uses take advantage of the consistency guarantees to implement database leader elections or do distributed locking across a cluster of workers.\\n\\n_The etcd Open Cloud Service is Public Alpha. The goal before Beta is to fully implement backup features._\\n\\n### Reading and writing to etcd\\n\\nCommunicate with etcd though its command line utility `etcdctl` or with the API using the automatically generated Kubernetes Service.\\n\\n[Read the complete guide to using the etcd Open Cloud Service](https://coreos.com/tectonic/docs/latest/alm/etcd-ocs.html)\\n\\n### Supported Features\\n\\n\\n**High availability**\\n\\n\\nMultiple instances of etcd are networked together and secured. Individual failures or networking issues are transparently handled to keep your cluster up and running.\\n\\n\\n**Automated updates**\\n\\n\\nRolling out a new etcd version works like all Kubernetes rolling updates. Simply declare the desired version, and the etcd service starts a safe rolling update to the new version automatically.\\n\\n\\n**Backups included**\\n\\n\\nComing soon, the ability to schedule backups to happen on or off cluster.\\n\",\"displayName\":\"etcd\",\"install\":{\"spec\":{\"deployments\":[{\"name\":\"etcd-operator\",\"spec\":{\"replicas\":1,\"selector\":{\"matchLabels\":{\"name\":\"etcd-operator-alm-owned\"}},\"template\":{\"metadata\":{\"labels\":{\"name\":\"etcd-operator-alm-owned\"},\"name\":\"etcd-operator-alm-owned\"},\"spec\":{\"containers\":[{\"command\":[\"etcd-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-operator\"},{\"command\":[\"etcd-backup-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-backup-operator\"},{\"command\":[\"etcd-restore-operator\",\"--create-crd=false\"],\"env\":[{\"name\":\"MY_POD_NAMESPACE\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.namespace\"}}},{\"name\":\"MY_POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"fieldPath\":\"metadata.name\"}}}],\"image\":\"quay.io/coreos/etcd-operator@sha256:c0301e4686c3ed4206e370b42de5a3bd2229b9fb4906cf85f3f30650424abec2\",\"name\":\"etcd-restore-operator\"}],\"serviceAccountName\":\"etcd-operator\"}}}}],\"permissions\":[{\"rules\":[{\"apiGroups\":[\"etcd.database.coreos.com\"],\"resources\":[\"etcdclusters\",\"etcdbackups\",\"etcdrestores\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"pods\",\"services\",\"endpoints\",\"persistentvolumeclaims\",\"events\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"apps\"],\"resources\":[\"deployments\"],\"verbs\":[\"*\"]},{\"apiGroups\":[\"\"],\"resources\":[\"secrets\"],\"verbs\":[\"get\"]}],\"serviceAccountName\":\"etcd-operator\"}]},\"strategy\":\"deployment\"},\"keywords\":[\"etcd\",\"key value\",\"database\",\"coreos\",\"open source\"],\"labels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"},\"links\":[{\"name\":\"Blog\",\"url\":\"https://coreos.com/etcd\"},{\"name\":\"Documentation\",\"url\":\"https://coreos.com/operators/etcd/docs/latest/\"},{\"name\":\"etcd Operator Source Code\",\"url\":\"https://github.com/coreos/etcd-operator\"}],\"maintainers\":[{\"email\":\"support@coreos.com\",\"name\":\"CoreOS, Inc\"}],\"maturity\":\"alpha\",\"provider\":{\"name\":\"CoreOS, Inc\"},\"relatedImages\":[{\"image\":\"quay.io/coreos/etcd@sha256:3816b6daf9b66d6ced6f0f966314e2d4f894982c6b1493061502f8c2bf86ac84\",\"name\":\"etcd-v3.4.0\"},{\"image\":\"quay.io/coreos/etcd@sha256:49d3d4a81e0d030d3f689e7167f23e120abf955f7d08dbedf3ea246485acee9f\",\"name\":\"etcd-3.4.1\"}],\"replaces\":\"etcdoperator.v0.9.0\",\"selector\":{\"matchLabels\":{\"alm-owner-etcd\":\"etcdoperator\",\"operated-by\":\"etcdoperator\"}},\"skips\":[\"etcdoperator.v0.9.1\"],\"version\":\"0.9.2\"}}" 35 etcdBackup = "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdbackups.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdBackup\",\"listKind\":\"EtcdBackupList\",\"plural\":\"etcdbackups\",\"singular\":\"etcdbackup\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}" 36 etcdCluster = "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdclusters.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdCluster\",\"listKind\":\"EtcdClusterList\",\"plural\":\"etcdclusters\",\"shortNames\":[\"etcdclus\",\"etcd\"],\"singular\":\"etcdcluster\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}" 37 etcdRestore = "{\"apiVersion\":\"apiextensions.k8s.io/v1beta1\",\"kind\":\"CustomResourceDefinition\",\"metadata\":{\"name\":\"etcdrestores.etcd.database.coreos.com\"},\"spec\":{\"group\":\"etcd.database.coreos.com\",\"names\":{\"kind\":\"EtcdRestore\",\"listKind\":\"EtcdRestoreList\",\"plural\":\"etcdrestores\",\"singular\":\"etcdrestore\"},\"scope\":\"Namespaced\",\"version\":\"v1beta2\"}}" 38 opmImage = "opm-image" 39 utilImage = "util-image" 40 bundlePath = "bundle-path" 41 digestPath = "bundle-path@sha256:54d626e08c1c802b305dad30b7e54a82f102390cc92c7d4db112048935236e9c" 42 runAsUser = 1001 43 ) 44 45 func TestConfigMapUnpacker(t *testing.T) { 46 pathHash := hash(bundlePath) 47 digestHash := hash(digestPath) 48 start := metav1.Now() 49 now := func() metav1.Time { 50 return start 51 } 52 backoffLimit := int32(3) 53 // Used to set the default value for job.spec.ActiveDeadlineSeconds 54 // that would normally be passed from the cmdline flag 55 defaultUnpackDuration := 10 * time.Minute 56 defaultUnpackTimeoutSeconds := int64(defaultUnpackDuration.Seconds()) 57 58 // Custom timeout to override the default cmdline flag ActiveDeadlineSeconds value 59 customAnnotationDuration := 2 * time.Minute 60 customAnnotationTimeoutSeconds := int64(customAnnotationDuration.Seconds()) 61 62 type fields struct { 63 objs []runtime.Object 64 crs []runtime.Object 65 } 66 type args struct { 67 lookup *operatorsv1alpha1.BundleLookup 68 // A negative timeout duration arg means it will be ignored and the default flag timeout will be used 69 annotationTimeout time.Duration 70 } 71 type expected struct { 72 res *BundleUnpackResult 73 err error 74 configMaps []*corev1.ConfigMap 75 jobs []*batchv1.Job 76 roles []*rbacv1.Role 77 roleBindings []*rbacv1.RoleBinding 78 } 79 80 tests := []struct { 81 description string 82 fields fields 83 args args 84 expected expected 85 }{ 86 { 87 description: "NoCatalogSource/NoConfigMap/NoJob/NotCreated/Pending", 88 fields: fields{}, 89 args: args{ 90 annotationTimeout: -1 * time.Minute, 91 lookup: &operatorsv1alpha1.BundleLookup{ 92 Path: bundlePath, 93 Replaces: "", 94 CatalogSourceRef: &corev1.ObjectReference{ 95 Namespace: "ns-a", 96 Name: "src-a", 97 }, 98 Conditions: []operatorsv1alpha1.BundleLookupCondition{ 99 { 100 Type: operatorsv1alpha1.BundleLookupPending, 101 Status: corev1.ConditionTrue, 102 Reason: JobNotStartedReason, 103 Message: JobNotStartedMessage, 104 }, 105 }, 106 }, 107 }, 108 expected: expected{ 109 res: &BundleUnpackResult{ 110 BundleLookup: &operatorsv1alpha1.BundleLookup{ 111 Path: bundlePath, 112 Replaces: "", 113 CatalogSourceRef: &corev1.ObjectReference{ 114 Namespace: "ns-a", 115 Name: "src-a", 116 }, 117 Conditions: []operatorsv1alpha1.BundleLookupCondition{ 118 { 119 Type: operatorsv1alpha1.BundleLookupPending, 120 Status: corev1.ConditionTrue, 121 Reason: CatalogSourceMissingReason, 122 Message: CatalogSourceMissingMessage, 123 LastTransitionTime: &start, 124 }, 125 }, 126 }, 127 name: pathHash, 128 }, 129 }, 130 }, 131 { 132 description: "CatalogSourcePresent/NoConfigMap/NoJob/JobCreated/Pending/WithCustomTimeout", 133 fields: fields{ 134 crs: []runtime.Object{ 135 &operatorsv1alpha1.CatalogSource{ 136 ObjectMeta: metav1.ObjectMeta{ 137 Namespace: "ns-a", 138 Name: "src-a", 139 }, 140 Spec: operatorsv1alpha1.CatalogSourceSpec{ 141 Secrets: []string{"my-secret"}, 142 }, 143 }, 144 }, 145 }, 146 args: args{ 147 // We override the default timeout and expect to see the job created with 148 // the custom annotation based timeout value 149 annotationTimeout: customAnnotationDuration, 150 lookup: &operatorsv1alpha1.BundleLookup{ 151 Path: bundlePath, 152 Replaces: "", 153 CatalogSourceRef: &corev1.ObjectReference{ 154 Namespace: "ns-a", 155 Name: "src-a", 156 }, 157 Conditions: []operatorsv1alpha1.BundleLookupCondition{ 158 { 159 Type: operatorsv1alpha1.BundleLookupPending, 160 Status: corev1.ConditionTrue, 161 Reason: JobNotStartedReason, 162 Message: JobNotStartedMessage, 163 }, 164 }, 165 }, 166 }, 167 expected: expected{ 168 res: &BundleUnpackResult{ 169 BundleLookup: &operatorsv1alpha1.BundleLookup{ 170 Path: bundlePath, 171 Replaces: "", 172 CatalogSourceRef: &corev1.ObjectReference{ 173 Namespace: "ns-a", 174 Name: "src-a", 175 }, 176 Conditions: []operatorsv1alpha1.BundleLookupCondition{ 177 { 178 Type: operatorsv1alpha1.BundleLookupPending, 179 Status: corev1.ConditionTrue, 180 Reason: JobIncompleteReason, 181 Message: JobIncompleteMessage, 182 LastTransitionTime: &start, 183 }, 184 }, 185 }, 186 name: pathHash, 187 }, 188 configMaps: []*corev1.ConfigMap{ 189 { 190 ObjectMeta: metav1.ObjectMeta{ 191 Name: pathHash, 192 Namespace: "ns-a", 193 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 194 OwnerReferences: []metav1.OwnerReference{ 195 { 196 APIVersion: "operators.coreos.com/v1alpha1", 197 Kind: "CatalogSource", 198 Name: "src-a", 199 Controller: &blockOwnerDeletion, 200 BlockOwnerDeletion: &blockOwnerDeletion, 201 }, 202 }, 203 }, 204 }, 205 }, 206 jobs: []*batchv1.Job{ 207 { 208 ObjectMeta: metav1.ObjectMeta{ 209 Name: pathHash, 210 Namespace: "ns-a", 211 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue, bundleUnpackRefLabel: pathHash}, 212 OwnerReferences: []metav1.OwnerReference{ 213 { 214 APIVersion: "v1", 215 Kind: "ConfigMap", 216 Name: pathHash, 217 Controller: &blockOwnerDeletion, 218 BlockOwnerDeletion: &blockOwnerDeletion, 219 }, 220 }, 221 }, 222 Spec: batchv1.JobSpec{ 223 // The expected job's timeout should be set to the custom annotation timeout 224 ActiveDeadlineSeconds: &customAnnotationTimeoutSeconds, 225 BackoffLimit: &backoffLimit, 226 Template: corev1.PodTemplateSpec{ 227 ObjectMeta: metav1.ObjectMeta{ 228 Name: pathHash, 229 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 230 }, 231 Spec: corev1.PodSpec{ 232 RestartPolicy: corev1.RestartPolicyNever, 233 ImagePullSecrets: []corev1.LocalObjectReference{{Name: "my-secret"}}, 234 SecurityContext: &corev1.PodSecurityContext{ 235 RunAsNonRoot: ptr.To(bool(true)), 236 RunAsUser: ptr.To(int64(runAsUser)), 237 SeccompProfile: &corev1.SeccompProfile{ 238 Type: corev1.SeccompProfileTypeRuntimeDefault, 239 }, 240 }, 241 Containers: []corev1.Container{ 242 { 243 Name: "extract", 244 Image: opmImage, 245 Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", pathHash, "-z"}, 246 Env: []corev1.EnvVar{ 247 { 248 Name: configmap.EnvContainerImage, 249 Value: bundlePath, 250 }, 251 }, 252 VolumeMounts: []corev1.VolumeMount{ 253 { 254 Name: "bundle", 255 MountPath: "/bundle", 256 }, 257 }, 258 Resources: corev1.ResourceRequirements{ 259 Requests: corev1.ResourceList{ 260 corev1.ResourceCPU: resource.MustParse("10m"), 261 corev1.ResourceMemory: resource.MustParse("50Mi"), 262 }, 263 }, 264 SecurityContext: &corev1.SecurityContext{ 265 AllowPrivilegeEscalation: ptr.To(bool(false)), 266 Capabilities: &corev1.Capabilities{ 267 Drop: []corev1.Capability{"ALL"}, 268 }, 269 }, 270 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 271 }, 272 }, 273 InitContainers: []corev1.Container{ 274 { 275 Name: "util", 276 Image: utilImage, 277 Command: []string{"/bin/cp", "-Rv", "/bin/cpb", "/util/cpb"}, // Copy tooling for the bundle container to use 278 VolumeMounts: []corev1.VolumeMount{ 279 { 280 Name: "util", 281 MountPath: "/util", 282 }, 283 }, 284 Resources: corev1.ResourceRequirements{ 285 Requests: corev1.ResourceList{ 286 corev1.ResourceCPU: resource.MustParse("10m"), 287 corev1.ResourceMemory: resource.MustParse("50Mi"), 288 }, 289 }, 290 SecurityContext: &corev1.SecurityContext{ 291 AllowPrivilegeEscalation: ptr.To(bool(false)), 292 Capabilities: &corev1.Capabilities{ 293 Drop: []corev1.Capability{"ALL"}, 294 }, 295 }, 296 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 297 }, 298 { 299 Name: "pull", 300 Image: bundlePath, 301 ImagePullPolicy: "Always", 302 Command: []string{"/util/cpb", "/bundle"}, // Copy bundle content to its mount 303 VolumeMounts: []corev1.VolumeMount{ 304 { 305 Name: "bundle", 306 MountPath: "/bundle", 307 }, 308 { 309 Name: "util", 310 MountPath: "/util", 311 }, 312 }, 313 Resources: corev1.ResourceRequirements{ 314 Requests: corev1.ResourceList{ 315 corev1.ResourceCPU: resource.MustParse("10m"), 316 corev1.ResourceMemory: resource.MustParse("50Mi"), 317 }, 318 }, 319 SecurityContext: &corev1.SecurityContext{ 320 AllowPrivilegeEscalation: ptr.To(bool(false)), 321 Capabilities: &corev1.Capabilities{ 322 Drop: []corev1.Capability{"ALL"}, 323 }, 324 }, 325 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 326 }, 327 }, 328 Volumes: []corev1.Volume{ 329 { 330 Name: "bundle", 331 VolumeSource: corev1.VolumeSource{ 332 EmptyDir: &corev1.EmptyDirVolumeSource{}, 333 }, 334 }, 335 { 336 Name: "util", 337 VolumeSource: corev1.VolumeSource{ 338 EmptyDir: &corev1.EmptyDirVolumeSource{}, 339 }, 340 }, 341 }, 342 NodeSelector: map[string]string{ 343 "kubernetes.io/os": "linux", 344 }, 345 Tolerations: []corev1.Toleration{ 346 { 347 Key: "kubernetes.io/arch", 348 Value: "amd64", 349 Operator: "Equal", 350 }, 351 { 352 Key: "kubernetes.io/arch", 353 Value: "arm64", 354 Operator: "Equal", 355 }, 356 { 357 Key: "kubernetes.io/arch", 358 Value: "ppc64le", 359 Operator: "Equal", 360 }, 361 { 362 Key: "kubernetes.io/arch", 363 Value: "s390x", 364 Operator: "Equal", 365 }, 366 }, 367 }, 368 }, 369 }, 370 }, 371 }, 372 roles: []*rbacv1.Role{ 373 { 374 ObjectMeta: metav1.ObjectMeta{ 375 Name: pathHash, 376 Namespace: "ns-a", 377 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 378 OwnerReferences: []metav1.OwnerReference{ 379 { 380 APIVersion: "v1", 381 Kind: "ConfigMap", 382 Name: pathHash, 383 Controller: &blockOwnerDeletion, 384 BlockOwnerDeletion: &blockOwnerDeletion, 385 }, 386 }, 387 }, 388 Rules: []rbacv1.PolicyRule{ 389 { 390 APIGroups: []string{ 391 "", 392 }, 393 Verbs: []string{ 394 "create", "get", "update", 395 }, 396 Resources: []string{ 397 "configmaps", 398 }, 399 ResourceNames: []string{ 400 pathHash, 401 }, 402 }, 403 }, 404 }, 405 }, 406 roleBindings: []*rbacv1.RoleBinding{ 407 { 408 ObjectMeta: metav1.ObjectMeta{ 409 Name: pathHash, 410 Namespace: "ns-a", 411 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 412 OwnerReferences: []metav1.OwnerReference{ 413 { 414 APIVersion: "v1", 415 Kind: "ConfigMap", 416 Name: pathHash, 417 Controller: &blockOwnerDeletion, 418 BlockOwnerDeletion: &blockOwnerDeletion, 419 }, 420 }, 421 }, 422 Subjects: []rbacv1.Subject{ 423 { 424 Kind: "ServiceAccount", 425 APIGroup: "", 426 Name: "default", 427 Namespace: "ns-a", 428 }, 429 }, 430 RoleRef: rbacv1.RoleRef{ 431 APIGroup: "rbac.authorization.k8s.io", 432 Kind: "Role", 433 Name: pathHash, 434 }, 435 }, 436 }, 437 }, 438 }, 439 { 440 description: "CatalogSourcePresent/ConfigMapPresent/JobPresent/DigestImage/Unpacked", 441 fields: fields{ 442 objs: []runtime.Object{ 443 &batchv1.Job{ 444 ObjectMeta: metav1.ObjectMeta{ 445 Name: digestHash, 446 Namespace: "ns-a", 447 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue, bundleUnpackRefLabel: digestHash}, 448 OwnerReferences: []metav1.OwnerReference{ 449 { 450 APIVersion: "v1", 451 Kind: "ConfigMap", 452 Name: digestHash, 453 Controller: &blockOwnerDeletion, 454 BlockOwnerDeletion: &blockOwnerDeletion, 455 }, 456 }, 457 }, 458 Spec: batchv1.JobSpec{ 459 ActiveDeadlineSeconds: &defaultUnpackTimeoutSeconds, 460 BackoffLimit: &backoffLimit, 461 Template: corev1.PodTemplateSpec{ 462 ObjectMeta: metav1.ObjectMeta{ 463 Name: digestHash, 464 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 465 }, 466 Spec: corev1.PodSpec{ 467 RestartPolicy: corev1.RestartPolicyNever, 468 SecurityContext: &corev1.PodSecurityContext{ 469 RunAsNonRoot: ptr.To(bool(true)), 470 RunAsUser: ptr.To(int64(runAsUser)), 471 SeccompProfile: &corev1.SeccompProfile{ 472 Type: corev1.SeccompProfileTypeRuntimeDefault, 473 }, 474 }, 475 Containers: []corev1.Container{ 476 { 477 Name: "extract", 478 Image: opmImage, 479 Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", digestHash, "-z"}, 480 Env: []corev1.EnvVar{ 481 { 482 Name: configmap.EnvContainerImage, 483 Value: digestPath, 484 }, 485 }, 486 VolumeMounts: []corev1.VolumeMount{ 487 { 488 Name: "bundle", 489 MountPath: "/bundle", 490 }, 491 }, 492 Resources: corev1.ResourceRequirements{ 493 Requests: corev1.ResourceList{ 494 corev1.ResourceCPU: resource.MustParse("10m"), 495 corev1.ResourceMemory: resource.MustParse("50Mi"), 496 }, 497 }, 498 SecurityContext: &corev1.SecurityContext{ 499 AllowPrivilegeEscalation: ptr.To(bool(false)), 500 Capabilities: &corev1.Capabilities{ 501 Drop: []corev1.Capability{"ALL"}, 502 }, 503 }, 504 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 505 }, 506 }, 507 InitContainers: []corev1.Container{ 508 { 509 Name: "util", 510 Image: utilImage, 511 Command: []string{"/bin/cp", "-Rv", "/bin/cpb", "/util/cpb"}, // Copy tooling for the bundle container to use 512 VolumeMounts: []corev1.VolumeMount{ 513 { 514 Name: "util", 515 MountPath: "/util", 516 }, 517 }, 518 Resources: corev1.ResourceRequirements{ 519 Requests: corev1.ResourceList{ 520 corev1.ResourceCPU: resource.MustParse("10m"), 521 corev1.ResourceMemory: resource.MustParse("50Mi"), 522 }, 523 }, 524 SecurityContext: &corev1.SecurityContext{ 525 AllowPrivilegeEscalation: ptr.To(bool(false)), 526 Capabilities: &corev1.Capabilities{ 527 Drop: []corev1.Capability{"ALL"}, 528 }, 529 }, 530 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 531 }, 532 { 533 Name: "pull", 534 Image: digestPath, 535 ImagePullPolicy: "IfNotPresent", 536 Command: []string{"/util/cpb", "/bundle"}, // Copy bundle content to its mount 537 VolumeMounts: []corev1.VolumeMount{ 538 { 539 Name: "bundle", 540 MountPath: "/bundle", 541 }, 542 { 543 Name: "util", 544 MountPath: "/util", 545 }, 546 }, 547 Resources: corev1.ResourceRequirements{ 548 Requests: corev1.ResourceList{ 549 corev1.ResourceCPU: resource.MustParse("10m"), 550 corev1.ResourceMemory: resource.MustParse("50Mi"), 551 }, 552 }, 553 SecurityContext: &corev1.SecurityContext{ 554 AllowPrivilegeEscalation: ptr.To(bool(false)), 555 Capabilities: &corev1.Capabilities{ 556 Drop: []corev1.Capability{"ALL"}, 557 }, 558 }, 559 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 560 }, 561 }, 562 Volumes: []corev1.Volume{ 563 { 564 Name: "bundle", 565 VolumeSource: corev1.VolumeSource{ 566 EmptyDir: &corev1.EmptyDirVolumeSource{}, 567 }, 568 }, 569 { 570 Name: "util", 571 VolumeSource: corev1.VolumeSource{ 572 EmptyDir: &corev1.EmptyDirVolumeSource{}, 573 }, 574 }, 575 }, 576 NodeSelector: map[string]string{ 577 "kubernetes.io/os": "linux", 578 }, 579 Tolerations: []corev1.Toleration{ 580 { 581 Key: "kubernetes.io/arch", 582 Value: "amd64", 583 Operator: "Equal", 584 }, 585 { 586 Key: "kubernetes.io/arch", 587 Value: "arm64", 588 Operator: "Equal", 589 }, 590 { 591 Key: "kubernetes.io/arch", 592 Value: "ppc64le", 593 Operator: "Equal", 594 }, 595 { 596 Key: "kubernetes.io/arch", 597 Value: "s390x", 598 Operator: "Equal", 599 }, 600 }, 601 }, 602 }, 603 }, 604 Status: batchv1.JobStatus{ 605 Succeeded: 1, 606 StartTime: &start, 607 CompletionTime: &start, 608 Conditions: []batchv1.JobCondition{ 609 { 610 LastProbeTime: start, 611 LastTransitionTime: start, 612 Status: corev1.ConditionTrue, 613 Type: batchv1.JobComplete, 614 }, 615 }, 616 }, 617 }, 618 &corev1.ConfigMap{ 619 ObjectMeta: metav1.ObjectMeta{ 620 Name: digestHash, 621 Namespace: "ns-a", 622 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 623 OwnerReferences: []metav1.OwnerReference{ 624 { 625 APIVersion: "operators.coreos.com/v1alpha1", 626 Kind: "CatalogSource", 627 Name: "src-a", 628 Controller: &blockOwnerDeletion, 629 BlockOwnerDeletion: &blockOwnerDeletion, 630 }, 631 }, 632 }, 633 Data: map[string]string{ 634 "etcdbackups.crd.json": etcdBackup, 635 "etcdclusters.crd.json": etcdCluster, 636 "csv.json": csvJSON, 637 "etcdrestores.crd.json": etcdRestore, 638 }, 639 }, 640 }, 641 crs: []runtime.Object{ 642 &operatorsv1alpha1.CatalogSource{ 643 ObjectMeta: metav1.ObjectMeta{ 644 Namespace: "ns-a", 645 Name: "src-a", 646 }, 647 }, 648 }, 649 }, 650 args: args{ 651 annotationTimeout: -1 * time.Minute, 652 lookup: &operatorsv1alpha1.BundleLookup{ 653 Path: digestPath, 654 Replaces: "", 655 CatalogSourceRef: &corev1.ObjectReference{ 656 Namespace: "ns-a", 657 Name: "src-a", 658 }, 659 Conditions: []operatorsv1alpha1.BundleLookupCondition{ 660 { 661 Type: operatorsv1alpha1.BundleLookupPending, 662 Status: corev1.ConditionTrue, 663 Reason: JobIncompleteReason, 664 Message: JobIncompleteMessage, 665 LastTransitionTime: &start, 666 }, 667 }, 668 }, 669 }, 670 expected: expected{ 671 res: &BundleUnpackResult{ 672 BundleLookup: &operatorsv1alpha1.BundleLookup{ 673 Path: digestPath, 674 Replaces: "", 675 CatalogSourceRef: &corev1.ObjectReference{ 676 Namespace: "ns-a", 677 Name: "src-a", 678 }, 679 }, 680 name: digestHash, 681 bundle: &api.Bundle{ 682 CsvName: "etcdoperator.v0.9.2", 683 CsvJson: csvJSON + "\n", 684 Object: []string{ 685 etcdBackup, 686 etcdCluster, 687 csvJSON, 688 etcdRestore, 689 }, 690 }, 691 }, 692 configMaps: []*corev1.ConfigMap{ 693 { 694 ObjectMeta: metav1.ObjectMeta{ 695 Name: digestHash, 696 Namespace: "ns-a", 697 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 698 OwnerReferences: []metav1.OwnerReference{ 699 { 700 APIVersion: "operators.coreos.com/v1alpha1", 701 Kind: "CatalogSource", 702 Name: "src-a", 703 Controller: &blockOwnerDeletion, 704 BlockOwnerDeletion: &blockOwnerDeletion, 705 }, 706 }, 707 }, 708 Data: map[string]string{ 709 "etcdbackups.crd.json": etcdBackup, 710 "etcdclusters.crd.json": etcdCluster, 711 "csv.json": csvJSON, 712 "etcdrestores.crd.json": etcdRestore, 713 }, 714 }, 715 }, 716 jobs: []*batchv1.Job{ 717 { 718 ObjectMeta: metav1.ObjectMeta{ 719 Name: digestHash, 720 Namespace: "ns-a", 721 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue, bundleUnpackRefLabel: digestHash}, 722 OwnerReferences: []metav1.OwnerReference{ 723 { 724 APIVersion: "v1", 725 Kind: "ConfigMap", 726 Name: digestHash, 727 Controller: &blockOwnerDeletion, 728 BlockOwnerDeletion: &blockOwnerDeletion, 729 }, 730 }, 731 }, 732 Spec: batchv1.JobSpec{ 733 ActiveDeadlineSeconds: &defaultUnpackTimeoutSeconds, 734 BackoffLimit: &backoffLimit, 735 Template: corev1.PodTemplateSpec{ 736 ObjectMeta: metav1.ObjectMeta{ 737 Name: digestHash, 738 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 739 }, 740 Spec: corev1.PodSpec{ 741 RestartPolicy: corev1.RestartPolicyNever, 742 SecurityContext: &corev1.PodSecurityContext{ 743 RunAsNonRoot: ptr.To(bool(true)), 744 RunAsUser: ptr.To(int64(runAsUser)), 745 SeccompProfile: &corev1.SeccompProfile{ 746 Type: corev1.SeccompProfileTypeRuntimeDefault, 747 }, 748 }, 749 Containers: []corev1.Container{ 750 { 751 Name: "extract", 752 Image: opmImage, 753 Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", digestHash, "-z"}, 754 Env: []corev1.EnvVar{ 755 { 756 Name: configmap.EnvContainerImage, 757 Value: digestPath, 758 }, 759 }, 760 VolumeMounts: []corev1.VolumeMount{ 761 { 762 Name: "bundle", 763 MountPath: "/bundle", 764 }, 765 }, 766 Resources: corev1.ResourceRequirements{ 767 Requests: corev1.ResourceList{ 768 corev1.ResourceCPU: resource.MustParse("10m"), 769 corev1.ResourceMemory: resource.MustParse("50Mi"), 770 }, 771 }, 772 SecurityContext: &corev1.SecurityContext{ 773 AllowPrivilegeEscalation: ptr.To(bool(false)), 774 Capabilities: &corev1.Capabilities{ 775 Drop: []corev1.Capability{"ALL"}, 776 }, 777 }, 778 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 779 }, 780 }, 781 InitContainers: []corev1.Container{ 782 { 783 Name: "util", 784 Image: utilImage, 785 Command: []string{"/bin/cp", "-Rv", "/bin/cpb", "/util/cpb"}, // Copy tooling for the bundle container to use 786 VolumeMounts: []corev1.VolumeMount{ 787 { 788 Name: "util", 789 MountPath: "/util", 790 }, 791 }, 792 Resources: corev1.ResourceRequirements{ 793 Requests: corev1.ResourceList{ 794 corev1.ResourceCPU: resource.MustParse("10m"), 795 corev1.ResourceMemory: resource.MustParse("50Mi"), 796 }, 797 }, 798 SecurityContext: &corev1.SecurityContext{ 799 AllowPrivilegeEscalation: ptr.To(bool(false)), 800 Capabilities: &corev1.Capabilities{ 801 Drop: []corev1.Capability{"ALL"}, 802 }, 803 }, 804 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 805 }, 806 { 807 Name: "pull", 808 Image: digestPath, 809 ImagePullPolicy: "IfNotPresent", 810 Command: []string{"/util/cpb", "/bundle"}, // Copy bundle content to its mount 811 VolumeMounts: []corev1.VolumeMount{ 812 { 813 Name: "bundle", 814 MountPath: "/bundle", 815 }, 816 { 817 Name: "util", 818 MountPath: "/util", 819 }, 820 }, 821 Resources: corev1.ResourceRequirements{ 822 Requests: corev1.ResourceList{ 823 corev1.ResourceCPU: resource.MustParse("10m"), 824 corev1.ResourceMemory: resource.MustParse("50Mi"), 825 }, 826 }, 827 SecurityContext: &corev1.SecurityContext{ 828 AllowPrivilegeEscalation: ptr.To(bool(false)), 829 Capabilities: &corev1.Capabilities{ 830 Drop: []corev1.Capability{"ALL"}, 831 }, 832 }, 833 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 834 }, 835 }, 836 Volumes: []corev1.Volume{ 837 { 838 Name: "bundle", 839 VolumeSource: corev1.VolumeSource{ 840 EmptyDir: &corev1.EmptyDirVolumeSource{}, 841 }, 842 }, 843 { 844 Name: "util", 845 VolumeSource: corev1.VolumeSource{ 846 EmptyDir: &corev1.EmptyDirVolumeSource{}, 847 }, 848 }, 849 }, 850 NodeSelector: map[string]string{ 851 "kubernetes.io/os": "linux", 852 }, 853 Tolerations: []corev1.Toleration{ 854 { 855 Key: "kubernetes.io/arch", 856 Value: "amd64", 857 Operator: "Equal", 858 }, 859 { 860 Key: "kubernetes.io/arch", 861 Value: "arm64", 862 Operator: "Equal", 863 }, 864 { 865 Key: "kubernetes.io/arch", 866 Value: "ppc64le", 867 Operator: "Equal", 868 }, 869 { 870 Key: "kubernetes.io/arch", 871 Value: "s390x", 872 Operator: "Equal", 873 }, 874 }, 875 }, 876 }, 877 }, 878 Status: batchv1.JobStatus{ 879 Succeeded: 1, 880 StartTime: &start, 881 CompletionTime: &start, 882 Conditions: []batchv1.JobCondition{ 883 { 884 LastProbeTime: start, 885 LastTransitionTime: start, 886 Status: corev1.ConditionTrue, 887 Type: batchv1.JobComplete, 888 }, 889 }, 890 }, 891 }, 892 }, 893 roles: []*rbacv1.Role{ 894 { 895 ObjectMeta: metav1.ObjectMeta{ 896 Name: digestHash, 897 Namespace: "ns-a", 898 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 899 OwnerReferences: []metav1.OwnerReference{ 900 { 901 APIVersion: "v1", 902 Kind: "ConfigMap", 903 Name: digestHash, 904 Controller: &blockOwnerDeletion, 905 BlockOwnerDeletion: &blockOwnerDeletion, 906 }, 907 }, 908 }, 909 Rules: []rbacv1.PolicyRule{ 910 { 911 APIGroups: []string{ 912 "", 913 }, 914 Verbs: []string{ 915 "create", "get", "update", 916 }, 917 Resources: []string{ 918 "configmaps", 919 }, 920 ResourceNames: []string{ 921 digestHash, 922 }, 923 }, 924 }, 925 }, 926 }, 927 roleBindings: []*rbacv1.RoleBinding{ 928 { 929 ObjectMeta: metav1.ObjectMeta{ 930 Name: digestHash, 931 Namespace: "ns-a", 932 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 933 OwnerReferences: []metav1.OwnerReference{ 934 { 935 APIVersion: "v1", 936 Kind: "ConfigMap", 937 Name: digestHash, 938 Controller: &blockOwnerDeletion, 939 BlockOwnerDeletion: &blockOwnerDeletion, 940 }, 941 }, 942 }, 943 Subjects: []rbacv1.Subject{ 944 { 945 Kind: "ServiceAccount", 946 APIGroup: "", 947 Name: "default", 948 Namespace: "ns-a", 949 }, 950 }, 951 RoleRef: rbacv1.RoleRef{ 952 APIGroup: "rbac.authorization.k8s.io", 953 Kind: "Role", 954 Name: digestHash, 955 }, 956 }, 957 }, 958 }, 959 }, 960 { 961 description: "CatalogSourcePresent/JobPending/PodPending/BundlePending/WithContainerStatus", 962 fields: fields{ 963 objs: []runtime.Object{ 964 &corev1.Pod{ 965 ObjectMeta: metav1.ObjectMeta{ 966 Name: pathHash + "-pod", 967 Namespace: "ns-a", 968 Labels: map[string]string{"job-name": pathHash}, 969 }, 970 Status: corev1.PodStatus{ 971 Phase: corev1.PodPending, 972 InitContainerStatuses: []corev1.ContainerStatus{ 973 { 974 Name: "pull", 975 Ready: false, 976 State: corev1.ContainerState{ 977 Waiting: &corev1.ContainerStateWaiting{ 978 Reason: "ErrImagePull", 979 Message: "pod pending for some reason", 980 }, 981 }, 982 }, 983 }, 984 }, 985 }, 986 &batchv1.Job{ 987 ObjectMeta: metav1.ObjectMeta{ 988 Name: pathHash, 989 Namespace: "ns-a", 990 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue, bundleUnpackRefLabel: pathHash}, 991 OwnerReferences: []metav1.OwnerReference{ 992 { 993 APIVersion: "v1", 994 Kind: "ConfigMap", 995 Name: pathHash, 996 Controller: &blockOwnerDeletion, 997 BlockOwnerDeletion: &blockOwnerDeletion, 998 }, 999 }, 1000 }, 1001 Spec: batchv1.JobSpec{ 1002 ActiveDeadlineSeconds: &defaultUnpackTimeoutSeconds, 1003 BackoffLimit: &backoffLimit, 1004 Template: corev1.PodTemplateSpec{ 1005 ObjectMeta: metav1.ObjectMeta{ 1006 Name: pathHash, 1007 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 1008 }, 1009 Spec: corev1.PodSpec{ 1010 RestartPolicy: corev1.RestartPolicyNever, 1011 SecurityContext: &corev1.PodSecurityContext{ 1012 RunAsNonRoot: ptr.To(bool(true)), 1013 RunAsUser: ptr.To(int64(runAsUser)), 1014 SeccompProfile: &corev1.SeccompProfile{ 1015 Type: corev1.SeccompProfileTypeRuntimeDefault, 1016 }, 1017 }, 1018 Containers: []corev1.Container{ 1019 { 1020 Name: "extract", 1021 Image: opmImage, 1022 Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", pathHash, "-z"}, 1023 Env: []corev1.EnvVar{ 1024 { 1025 Name: configmap.EnvContainerImage, 1026 Value: bundlePath, 1027 }, 1028 }, 1029 VolumeMounts: []corev1.VolumeMount{ 1030 { 1031 Name: "bundle", 1032 MountPath: "/bundle", 1033 }, 1034 }, 1035 Resources: corev1.ResourceRequirements{ 1036 Requests: corev1.ResourceList{ 1037 corev1.ResourceCPU: resource.MustParse("10m"), 1038 corev1.ResourceMemory: resource.MustParse("50Mi"), 1039 }, 1040 }, 1041 SecurityContext: &corev1.SecurityContext{ 1042 AllowPrivilegeEscalation: ptr.To(bool(false)), 1043 Capabilities: &corev1.Capabilities{ 1044 Drop: []corev1.Capability{"ALL"}, 1045 }, 1046 }, 1047 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 1048 }, 1049 }, 1050 InitContainers: []corev1.Container{ 1051 { 1052 Name: "util", 1053 Image: utilImage, 1054 Command: []string{"/bin/cp", "-Rv", "/bin/cpb", "/util/cpb"}, // Copy tooling for the bundle container to use 1055 VolumeMounts: []corev1.VolumeMount{ 1056 { 1057 Name: "util", 1058 MountPath: "/util", 1059 }, 1060 }, 1061 Resources: corev1.ResourceRequirements{ 1062 Requests: corev1.ResourceList{ 1063 corev1.ResourceCPU: resource.MustParse("10m"), 1064 corev1.ResourceMemory: resource.MustParse("50Mi"), 1065 }, 1066 }, 1067 SecurityContext: &corev1.SecurityContext{ 1068 AllowPrivilegeEscalation: ptr.To(bool(false)), 1069 Capabilities: &corev1.Capabilities{ 1070 Drop: []corev1.Capability{"ALL"}, 1071 }, 1072 }, 1073 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 1074 }, 1075 { 1076 Name: "pull", 1077 Image: bundlePath, 1078 ImagePullPolicy: "Always", 1079 Command: []string{"/util/cpb", "/bundle"}, // Copy bundle content to its mount 1080 VolumeMounts: []corev1.VolumeMount{ 1081 { 1082 Name: "bundle", 1083 MountPath: "/bundle", 1084 }, 1085 { 1086 Name: "util", 1087 MountPath: "/util", 1088 }, 1089 }, 1090 Resources: corev1.ResourceRequirements{ 1091 Requests: corev1.ResourceList{ 1092 corev1.ResourceCPU: resource.MustParse("10m"), 1093 corev1.ResourceMemory: resource.MustParse("50Mi"), 1094 }, 1095 }, 1096 SecurityContext: &corev1.SecurityContext{ 1097 AllowPrivilegeEscalation: ptr.To(bool(false)), 1098 Capabilities: &corev1.Capabilities{ 1099 Drop: []corev1.Capability{"ALL"}, 1100 }, 1101 }, 1102 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 1103 }, 1104 }, 1105 Volumes: []corev1.Volume{ 1106 { 1107 Name: "bundle", 1108 VolumeSource: corev1.VolumeSource{ 1109 EmptyDir: &corev1.EmptyDirVolumeSource{}, 1110 }, 1111 }, 1112 { 1113 Name: "util", 1114 VolumeSource: corev1.VolumeSource{ 1115 EmptyDir: &corev1.EmptyDirVolumeSource{}, 1116 }, 1117 }, 1118 }, 1119 NodeSelector: map[string]string{ 1120 "kubernetes.io/os": "linux", 1121 }, 1122 Tolerations: []corev1.Toleration{ 1123 { 1124 Key: "kubernetes.io/arch", 1125 Value: "amd64", 1126 Operator: "Equal", 1127 }, 1128 { 1129 Key: "kubernetes.io/arch", 1130 Value: "arm64", 1131 Operator: "Equal", 1132 }, 1133 { 1134 Key: "kubernetes.io/arch", 1135 Value: "ppc64le", 1136 Operator: "Equal", 1137 }, 1138 { 1139 Key: "kubernetes.io/arch", 1140 Value: "s390x", 1141 Operator: "Equal", 1142 }, 1143 }, 1144 }, 1145 }, 1146 }, 1147 }, 1148 &corev1.ConfigMap{ 1149 ObjectMeta: metav1.ObjectMeta{ 1150 Name: pathHash, 1151 Namespace: "ns-a", 1152 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 1153 OwnerReferences: []metav1.OwnerReference{ 1154 { 1155 APIVersion: "operators.coreos.com/v1alpha1", 1156 Kind: "CatalogSource", 1157 Name: "src-a", 1158 Controller: &blockOwnerDeletion, 1159 BlockOwnerDeletion: &blockOwnerDeletion, 1160 }, 1161 }, 1162 }, 1163 }, 1164 }, 1165 crs: []runtime.Object{ 1166 &operatorsv1alpha1.CatalogSource{ 1167 ObjectMeta: metav1.ObjectMeta{ 1168 Namespace: "ns-a", 1169 Name: "src-a", 1170 }, 1171 }, 1172 }, 1173 }, 1174 1175 args: args{ 1176 annotationTimeout: -1 * time.Minute, 1177 lookup: &operatorsv1alpha1.BundleLookup{ 1178 Path: bundlePath, 1179 Replaces: "", 1180 CatalogSourceRef: &corev1.ObjectReference{ 1181 Namespace: "ns-a", 1182 Name: "src-a", 1183 }, 1184 Conditions: []operatorsv1alpha1.BundleLookupCondition{ 1185 { 1186 Type: operatorsv1alpha1.BundleLookupPending, 1187 Status: corev1.ConditionTrue, 1188 Reason: JobIncompleteReason, 1189 Message: JobIncompleteMessage, 1190 LastTransitionTime: &start, 1191 }, 1192 }, 1193 }, 1194 }, 1195 1196 expected: expected{ 1197 res: &BundleUnpackResult{ 1198 name: pathHash, 1199 BundleLookup: &operatorsv1alpha1.BundleLookup{ 1200 Path: bundlePath, 1201 Replaces: "", 1202 CatalogSourceRef: &corev1.ObjectReference{ 1203 Namespace: "ns-a", 1204 Name: "src-a", 1205 }, 1206 Conditions: []operatorsv1alpha1.BundleLookupCondition{ 1207 { 1208 Type: operatorsv1alpha1.BundleLookupPending, 1209 Status: corev1.ConditionTrue, 1210 Reason: JobIncompleteReason, 1211 Message: fmt.Sprintf("%s: Unpack pod(ns-a/%s) container(pull) is pending. Reason: ErrImagePull, Message: pod pending for some reason", 1212 JobIncompleteMessage, pathHash+"-pod"), 1213 LastTransitionTime: &start, 1214 }, 1215 }, 1216 }, 1217 }, 1218 }, 1219 }, 1220 { 1221 description: "CatalogSourcePresent/JobFailed/BundleLookupFailed/WithJobFailReasonNoLabel", 1222 fields: fields{ 1223 objs: []runtime.Object{ 1224 &batchv1.Job{ 1225 ObjectMeta: metav1.ObjectMeta{ 1226 Name: pathHash, 1227 Namespace: "ns-a", 1228 //omit the "operatorframework.io/bundle-unpack-ref" label 1229 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 1230 OwnerReferences: []metav1.OwnerReference{ 1231 { 1232 APIVersion: "v1", 1233 Kind: "ConfigMap", 1234 Name: pathHash, 1235 Controller: &blockOwnerDeletion, 1236 BlockOwnerDeletion: &blockOwnerDeletion, 1237 }, 1238 }, 1239 }, 1240 Spec: batchv1.JobSpec{ 1241 ActiveDeadlineSeconds: &defaultUnpackTimeoutSeconds, 1242 BackoffLimit: &backoffLimit, 1243 Template: corev1.PodTemplateSpec{ 1244 ObjectMeta: metav1.ObjectMeta{ 1245 Name: pathHash, 1246 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 1247 }, 1248 Spec: corev1.PodSpec{ 1249 RestartPolicy: corev1.RestartPolicyNever, 1250 SecurityContext: &corev1.PodSecurityContext{ 1251 RunAsNonRoot: ptr.To(bool(true)), 1252 RunAsUser: ptr.To(int64(runAsUser)), 1253 SeccompProfile: &corev1.SeccompProfile{ 1254 Type: corev1.SeccompProfileTypeRuntimeDefault, 1255 }, 1256 }, 1257 Containers: []corev1.Container{ 1258 { 1259 Name: "extract", 1260 Image: opmImage, 1261 Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", pathHash, "-z"}, 1262 Env: []corev1.EnvVar{ 1263 { 1264 Name: configmap.EnvContainerImage, 1265 Value: bundlePath, 1266 }, 1267 }, 1268 VolumeMounts: []corev1.VolumeMount{ 1269 { 1270 Name: "bundle", 1271 MountPath: "/bundle", 1272 }, 1273 }, 1274 Resources: corev1.ResourceRequirements{ 1275 Requests: corev1.ResourceList{ 1276 corev1.ResourceCPU: resource.MustParse("10m"), 1277 corev1.ResourceMemory: resource.MustParse("50Mi"), 1278 }, 1279 }, 1280 SecurityContext: &corev1.SecurityContext{ 1281 AllowPrivilegeEscalation: ptr.To(bool(false)), 1282 Capabilities: &corev1.Capabilities{ 1283 Drop: []corev1.Capability{"ALL"}, 1284 }, 1285 }, 1286 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 1287 }, 1288 }, 1289 InitContainers: []corev1.Container{ 1290 { 1291 Name: "util", 1292 Image: utilImage, 1293 Command: []string{"/bin/cp", "-Rv", "/bin/cpb", "/util/cpb"}, // Copy tooling for the bundle container to use 1294 VolumeMounts: []corev1.VolumeMount{ 1295 { 1296 Name: "util", 1297 MountPath: "/util", 1298 }, 1299 }, 1300 Resources: corev1.ResourceRequirements{ 1301 Requests: corev1.ResourceList{ 1302 corev1.ResourceCPU: resource.MustParse("10m"), 1303 corev1.ResourceMemory: resource.MustParse("50Mi"), 1304 }, 1305 }, 1306 SecurityContext: &corev1.SecurityContext{ 1307 AllowPrivilegeEscalation: ptr.To(bool(false)), 1308 Capabilities: &corev1.Capabilities{ 1309 Drop: []corev1.Capability{"ALL"}, 1310 }, 1311 }, 1312 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 1313 }, 1314 { 1315 Name: "pull", 1316 Image: bundlePath, 1317 ImagePullPolicy: "Always", 1318 Command: []string{"/util/cpb", "/bundle"}, // Copy bundle content to its mount 1319 VolumeMounts: []corev1.VolumeMount{ 1320 { 1321 Name: "bundle", 1322 MountPath: "/bundle", 1323 }, 1324 { 1325 Name: "util", 1326 MountPath: "/util", 1327 }, 1328 }, 1329 Resources: corev1.ResourceRequirements{ 1330 Requests: corev1.ResourceList{ 1331 corev1.ResourceCPU: resource.MustParse("10m"), 1332 corev1.ResourceMemory: resource.MustParse("50Mi"), 1333 }, 1334 }, 1335 SecurityContext: &corev1.SecurityContext{ 1336 AllowPrivilegeEscalation: ptr.To(bool(false)), 1337 Capabilities: &corev1.Capabilities{ 1338 Drop: []corev1.Capability{"ALL"}, 1339 }, 1340 }, 1341 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 1342 }, 1343 }, 1344 Volumes: []corev1.Volume{ 1345 { 1346 Name: "bundle", 1347 VolumeSource: corev1.VolumeSource{ 1348 EmptyDir: &corev1.EmptyDirVolumeSource{}, 1349 }, 1350 }, 1351 { 1352 Name: "util", 1353 VolumeSource: corev1.VolumeSource{ 1354 EmptyDir: &corev1.EmptyDirVolumeSource{}, 1355 }, 1356 }, 1357 }, 1358 NodeSelector: map[string]string{ 1359 "kubernetes.io/os": "linux", 1360 }, 1361 Tolerations: []corev1.Toleration{ 1362 { 1363 Key: "kubernetes.io/arch", 1364 Value: "amd64", 1365 Operator: "Equal", 1366 }, 1367 { 1368 Key: "kubernetes.io/arch", 1369 Value: "arm64", 1370 Operator: "Equal", 1371 }, 1372 { 1373 Key: "kubernetes.io/arch", 1374 Value: "ppc64le", 1375 Operator: "Equal", 1376 }, 1377 { 1378 Key: "kubernetes.io/arch", 1379 Value: "s390x", 1380 Operator: "Equal", 1381 }, 1382 }, 1383 }, 1384 }, 1385 }, 1386 Status: batchv1.JobStatus{ 1387 Failed: 1, 1388 Conditions: []batchv1.JobCondition{ 1389 { 1390 1391 Type: batchv1.JobFailed, 1392 Status: corev1.ConditionTrue, 1393 Message: "Job was active longer than specified deadline", 1394 Reason: "DeadlineExceeded", 1395 }, 1396 }, 1397 }, 1398 }, 1399 &corev1.ConfigMap{ 1400 ObjectMeta: metav1.ObjectMeta{ 1401 Name: pathHash, 1402 Namespace: "ns-a", 1403 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 1404 OwnerReferences: []metav1.OwnerReference{ 1405 { 1406 APIVersion: "operators.coreos.com/v1alpha1", 1407 Kind: "CatalogSource", 1408 Name: "src-a", 1409 Controller: &blockOwnerDeletion, 1410 BlockOwnerDeletion: &blockOwnerDeletion, 1411 }, 1412 }, 1413 }, 1414 }, 1415 }, 1416 crs: []runtime.Object{ 1417 &operatorsv1alpha1.CatalogSource{ 1418 ObjectMeta: metav1.ObjectMeta{ 1419 Namespace: "ns-a", 1420 Name: "src-a", 1421 }, 1422 }, 1423 }, 1424 }, 1425 args: args{ 1426 annotationTimeout: -1 * time.Minute, 1427 lookup: &operatorsv1alpha1.BundleLookup{ 1428 Path: bundlePath, 1429 Replaces: "", 1430 CatalogSourceRef: &corev1.ObjectReference{ 1431 Namespace: "ns-a", 1432 Name: "src-a", 1433 }, 1434 Conditions: []operatorsv1alpha1.BundleLookupCondition{ 1435 { 1436 Type: operatorsv1alpha1.BundleLookupPending, 1437 Status: corev1.ConditionTrue, 1438 Reason: JobIncompleteReason, 1439 Message: JobIncompleteMessage, 1440 LastTransitionTime: &start, 1441 }, 1442 }, 1443 }, 1444 }, 1445 expected: expected{ 1446 // If job is not found due to missing "operatorframework.io/bundle-unpack-ref" label, 1447 // we will get an 'AlreadyExists' error in this test when the new job is created 1448 err: nil, 1449 res: &BundleUnpackResult{ 1450 name: pathHash, 1451 BundleLookup: &operatorsv1alpha1.BundleLookup{ 1452 Path: bundlePath, 1453 Replaces: "", 1454 CatalogSourceRef: &corev1.ObjectReference{ 1455 Namespace: "ns-a", 1456 Name: "src-a", 1457 }, 1458 Conditions: []operatorsv1alpha1.BundleLookupCondition{ 1459 { 1460 Type: operatorsv1alpha1.BundleLookupPending, 1461 Status: corev1.ConditionTrue, 1462 Reason: JobIncompleteReason, 1463 Message: JobIncompleteMessage, 1464 LastTransitionTime: &start, 1465 }, 1466 { 1467 Type: operatorsv1alpha1.BundleLookupFailed, 1468 Status: corev1.ConditionTrue, 1469 Reason: "DeadlineExceeded", 1470 Message: "Job was active longer than specified deadline", 1471 LastTransitionTime: &start, 1472 }, 1473 }, 1474 }, 1475 }, 1476 jobs: []*batchv1.Job{ 1477 { 1478 ObjectMeta: metav1.ObjectMeta{ 1479 Name: pathHash, 1480 Namespace: "ns-a", 1481 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 1482 OwnerReferences: []metav1.OwnerReference{ 1483 { 1484 APIVersion: "v1", 1485 Kind: "ConfigMap", 1486 Name: pathHash, 1487 Controller: &blockOwnerDeletion, 1488 BlockOwnerDeletion: &blockOwnerDeletion, 1489 }, 1490 }, 1491 }, 1492 Spec: batchv1.JobSpec{ 1493 ActiveDeadlineSeconds: &defaultUnpackTimeoutSeconds, 1494 BackoffLimit: &backoffLimit, 1495 Template: corev1.PodTemplateSpec{ 1496 ObjectMeta: metav1.ObjectMeta{ 1497 Name: pathHash, 1498 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue}, 1499 }, 1500 Spec: corev1.PodSpec{ 1501 RestartPolicy: corev1.RestartPolicyNever, 1502 SecurityContext: &corev1.PodSecurityContext{ 1503 RunAsNonRoot: ptr.To(bool(true)), 1504 RunAsUser: ptr.To(int64(runAsUser)), 1505 SeccompProfile: &corev1.SeccompProfile{ 1506 Type: corev1.SeccompProfileTypeRuntimeDefault, 1507 }, 1508 }, 1509 Containers: []corev1.Container{ 1510 { 1511 Name: "extract", 1512 Image: opmImage, 1513 Command: []string{"opm", "alpha", "bundle", "extract", "-m", "/bundle/", "-n", "ns-a", "-c", pathHash, "-z"}, 1514 Env: []corev1.EnvVar{ 1515 { 1516 Name: configmap.EnvContainerImage, 1517 Value: bundlePath, 1518 }, 1519 }, 1520 VolumeMounts: []corev1.VolumeMount{ 1521 { 1522 Name: "bundle", 1523 MountPath: "/bundle", 1524 }, 1525 }, 1526 Resources: corev1.ResourceRequirements{ 1527 Requests: corev1.ResourceList{ 1528 corev1.ResourceCPU: resource.MustParse("10m"), 1529 corev1.ResourceMemory: resource.MustParse("50Mi"), 1530 }, 1531 }, 1532 SecurityContext: &corev1.SecurityContext{ 1533 AllowPrivilegeEscalation: ptr.To(bool(false)), 1534 Capabilities: &corev1.Capabilities{ 1535 Drop: []corev1.Capability{"ALL"}, 1536 }, 1537 }, 1538 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 1539 }, 1540 }, 1541 InitContainers: []corev1.Container{ 1542 { 1543 Name: "util", 1544 Image: utilImage, 1545 Command: []string{"/bin/cp", "-Rv", "/bin/cpb", "/util/cpb"}, // Copy tooling for the bundle container to use 1546 VolumeMounts: []corev1.VolumeMount{ 1547 { 1548 Name: "util", 1549 MountPath: "/util", 1550 }, 1551 }, 1552 Resources: corev1.ResourceRequirements{ 1553 Requests: corev1.ResourceList{ 1554 corev1.ResourceCPU: resource.MustParse("10m"), 1555 corev1.ResourceMemory: resource.MustParse("50Mi"), 1556 }, 1557 }, 1558 SecurityContext: &corev1.SecurityContext{ 1559 AllowPrivilegeEscalation: ptr.To(bool(false)), 1560 Capabilities: &corev1.Capabilities{ 1561 Drop: []corev1.Capability{"ALL"}, 1562 }, 1563 }, 1564 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 1565 }, 1566 { 1567 Name: "pull", 1568 Image: bundlePath, 1569 ImagePullPolicy: "Always", 1570 Command: []string{"/util/cpb", "/bundle"}, // Copy bundle content to its mount 1571 VolumeMounts: []corev1.VolumeMount{ 1572 { 1573 Name: "bundle", 1574 MountPath: "/bundle", 1575 }, 1576 { 1577 Name: "util", 1578 MountPath: "/util", 1579 }, 1580 }, 1581 Resources: corev1.ResourceRequirements{ 1582 Requests: corev1.ResourceList{ 1583 corev1.ResourceCPU: resource.MustParse("10m"), 1584 corev1.ResourceMemory: resource.MustParse("50Mi"), 1585 }, 1586 }, 1587 SecurityContext: &corev1.SecurityContext{ 1588 AllowPrivilegeEscalation: ptr.To(bool(false)), 1589 Capabilities: &corev1.Capabilities{ 1590 Drop: []corev1.Capability{"ALL"}, 1591 }, 1592 }, 1593 TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, 1594 }, 1595 }, 1596 Volumes: []corev1.Volume{ 1597 { 1598 Name: "bundle", 1599 VolumeSource: corev1.VolumeSource{ 1600 EmptyDir: &corev1.EmptyDirVolumeSource{}, 1601 }, 1602 }, 1603 { 1604 Name: "util", 1605 VolumeSource: corev1.VolumeSource{ 1606 EmptyDir: &corev1.EmptyDirVolumeSource{}, 1607 }, 1608 }, 1609 }, 1610 NodeSelector: map[string]string{ 1611 "kubernetes.io/os": "linux", 1612 }, 1613 Tolerations: []corev1.Toleration{ 1614 { 1615 Key: "kubernetes.io/arch", 1616 Value: "amd64", 1617 Operator: "Equal", 1618 }, 1619 { 1620 Key: "kubernetes.io/arch", 1621 Value: "arm64", 1622 Operator: "Equal", 1623 }, 1624 { 1625 Key: "kubernetes.io/arch", 1626 Value: "ppc64le", 1627 Operator: "Equal", 1628 }, 1629 { 1630 Key: "kubernetes.io/arch", 1631 Value: "s390x", 1632 Operator: "Equal", 1633 }, 1634 }, 1635 }, 1636 }, 1637 }, 1638 Status: batchv1.JobStatus{ 1639 Failed: 1, 1640 Conditions: []batchv1.JobCondition{ 1641 { 1642 Type: batchv1.JobFailed, 1643 Status: corev1.ConditionTrue, 1644 Message: "Job was active longer than specified deadline", 1645 Reason: "DeadlineExceeded", 1646 }, 1647 }, 1648 }, 1649 }, 1650 }, 1651 }, 1652 }, 1653 } 1654 1655 for _, tt := range tests { 1656 t.Run(tt.description, func(t *testing.T) { 1657 client := k8sfake.NewSimpleClientset(tt.fields.objs...) 1658 1659 period := 5 * time.Minute 1660 factory := informers.NewSharedInformerFactory(client, period) 1661 configMapInformer := informers.NewSharedInformerFactoryWithOptions(client, period, informers.WithTweakListOptions(func(options *metav1.ListOptions) { 1662 options.LabelSelector = install.OLMManagedLabelKey 1663 })).Core().V1().ConfigMaps() 1664 cmLister := configMapInformer.Lister() 1665 jobLister := factory.Batch().V1().Jobs().Lister() 1666 podLister := factory.Core().V1().Pods().Lister() 1667 roleLister := factory.Rbac().V1().Roles().Lister() 1668 rbLister := factory.Rbac().V1().RoleBindings().Lister() 1669 1670 stop := make(chan struct{}) 1671 defer close(stop) 1672 1673 factory.Start(stop) 1674 factory.WaitForCacheSync(context.Background().Done()) 1675 1676 crClient := crfake.NewSimpleClientset(tt.fields.crs...) 1677 crFactory := crinformers.NewSharedInformerFactory(crClient, period) 1678 csLister := crFactory.Operators().V1alpha1().CatalogSources().Lister() 1679 crFactory.Start(stop) 1680 crFactory.WaitForCacheSync(context.Background().Done()) 1681 1682 unpacker, err := NewConfigmapUnpacker( 1683 WithClient(client), 1684 WithCatalogSourceLister(csLister), 1685 WithConfigMapLister(cmLister), 1686 WithJobLister(jobLister), 1687 WithPodLister(podLister), 1688 WithRoleLister(roleLister), 1689 WithRoleBindingLister(rbLister), 1690 WithOPMImage(opmImage), 1691 WithUtilImage(utilImage), 1692 WithNow(now), 1693 WithUnpackTimeout(defaultUnpackDuration), 1694 WithUserID(int64(runAsUser)), 1695 ) 1696 require.NoError(t, err) 1697 1698 res, err := unpacker.UnpackBundle(tt.args.lookup, tt.args.annotationTimeout, 0) 1699 require.Equal(t, tt.expected.err, err) 1700 1701 if tt.expected.res == nil { 1702 require.Nil(t, res) 1703 } else { 1704 if tt.expected.res.bundle == nil { 1705 require.Nil(t, res.bundle) 1706 } else { 1707 require.NotNil(t, res.bundle) 1708 require.Equal(t, tt.expected.res.bundle.CsvJson, res.bundle.CsvJson) 1709 require.Equal(t, tt.expected.res.bundle.CsvName, res.bundle.CsvName) 1710 require.Equal(t, tt.expected.res.bundle.Version, res.bundle.Version) 1711 require.Equal(t, tt.expected.res.bundle.SkipRange, res.bundle.SkipRange) 1712 require.Equal(t, tt.expected.res.bundle.ProvidedApis, res.bundle.ProvidedApis) 1713 require.Equal(t, tt.expected.res.bundle.RequiredApis, res.bundle.RequiredApis) 1714 require.Equal(t, tt.expected.res.bundle.PackageName, res.bundle.PackageName) 1715 require.Equal(t, tt.expected.res.bundle.ChannelName, res.bundle.ChannelName) 1716 1717 // Object order is not stable, so perform a set based assertion 1718 require.ElementsMatch(t, tt.expected.res.bundle.Object, res.bundle.Object) 1719 } 1720 require.Equal(t, tt.expected.res.Path, res.Path) 1721 require.Equal(t, tt.expected.res.Replaces, res.Replaces) 1722 require.Equal(t, tt.expected.res.CatalogSourceRef, res.CatalogSourceRef) 1723 require.ElementsMatch(t, tt.expected.res.Conditions, res.Conditions) 1724 } 1725 1726 opts := metav1.GetOptions{} 1727 for _, job := range tt.expected.jobs { 1728 stored, err := client.BatchV1().Jobs(job.GetNamespace()).Get(context.TODO(), job.GetName(), opts) 1729 require.NoError(t, err) 1730 require.Equal(t, job, stored) 1731 } 1732 1733 for _, cm := range tt.expected.configMaps { 1734 stored, err := client.CoreV1().ConfigMaps(cm.GetNamespace()).Get(context.TODO(), cm.GetName(), opts) 1735 require.NoError(t, err) 1736 require.Equal(t, cm, stored) 1737 } 1738 1739 for _, role := range tt.expected.roles { 1740 stored, err := client.RbacV1().Roles(role.GetNamespace()).Get(context.TODO(), role.GetName(), opts) 1741 require.NoError(t, err) 1742 require.Equal(t, role, stored) 1743 } 1744 1745 for _, rb := range tt.expected.roleBindings { 1746 stored, err := client.RbacV1().RoleBindings(rb.GetNamespace()).Get(context.TODO(), rb.GetName(), opts) 1747 require.NoError(t, err) 1748 require.Equal(t, rb, stored) 1749 } 1750 }) 1751 } 1752 } 1753 1754 func TestOperatorGroupBundleUnpackTimeout(t *testing.T) { 1755 nsName := "fake-ns" 1756 1757 for _, tc := range []struct { 1758 name string 1759 operatorGroups []*operatorsv1.OperatorGroup 1760 expectedTimeout time.Duration 1761 expectedError error 1762 }{ 1763 { 1764 name: "No operator groups exist", 1765 expectedTimeout: -1 * time.Minute, 1766 expectedError: errors.New("found 0 operatorGroups, expected 1"), 1767 }, 1768 { 1769 name: "Multiple operator groups exist", 1770 operatorGroups: []*operatorsv1.OperatorGroup{ 1771 { 1772 TypeMeta: metav1.TypeMeta{ 1773 Kind: operatorsv1.OperatorGroupKind, 1774 APIVersion: operatorsv1.GroupVersion.String(), 1775 }, 1776 ObjectMeta: metav1.ObjectMeta{ 1777 Name: "og1", 1778 Namespace: nsName, 1779 }, 1780 }, 1781 { 1782 TypeMeta: metav1.TypeMeta{ 1783 Kind: operatorsv1.OperatorGroupKind, 1784 APIVersion: operatorsv1.GroupVersion.String(), 1785 }, 1786 ObjectMeta: metav1.ObjectMeta{ 1787 Name: "og2", 1788 Namespace: nsName, 1789 }, 1790 }, 1791 }, 1792 expectedTimeout: -1 * time.Minute, 1793 expectedError: errors.New("found 2 operatorGroups, expected 1"), 1794 }, 1795 { 1796 name: "One operator group exists with valid timeout annotation", 1797 operatorGroups: []*operatorsv1.OperatorGroup{ 1798 { 1799 TypeMeta: metav1.TypeMeta{ 1800 Kind: operatorsv1.OperatorGroupKind, 1801 APIVersion: operatorsv1.GroupVersion.String(), 1802 }, 1803 ObjectMeta: metav1.ObjectMeta{ 1804 Name: "og", 1805 Namespace: nsName, 1806 Annotations: map[string]string{BundleUnpackTimeoutAnnotationKey: "1m"}, 1807 }, 1808 }, 1809 }, 1810 expectedTimeout: 1 * time.Minute, 1811 expectedError: nil, 1812 }, 1813 { 1814 name: "One operator group exists with no timeout annotation", 1815 operatorGroups: []*operatorsv1.OperatorGroup{ 1816 { 1817 TypeMeta: metav1.TypeMeta{ 1818 Kind: operatorsv1.OperatorGroupKind, 1819 APIVersion: operatorsv1.GroupVersion.String(), 1820 }, 1821 ObjectMeta: metav1.ObjectMeta{ 1822 Name: "og", 1823 Namespace: nsName, 1824 }, 1825 }, 1826 }, 1827 expectedTimeout: -1 * time.Minute, 1828 }, 1829 { 1830 name: "One operator group exists with invalid timeout annotation", 1831 operatorGroups: []*operatorsv1.OperatorGroup{ 1832 { 1833 TypeMeta: metav1.TypeMeta{ 1834 Kind: operatorsv1.OperatorGroupKind, 1835 APIVersion: operatorsv1.GroupVersion.String(), 1836 }, 1837 ObjectMeta: metav1.ObjectMeta{ 1838 Name: "og", 1839 Namespace: nsName, 1840 Annotations: map[string]string{BundleUnpackTimeoutAnnotationKey: "invalid"}, 1841 }, 1842 }, 1843 }, 1844 expectedTimeout: -1 * time.Minute, 1845 expectedError: fmt.Errorf("failed to parse unpack timeout annotation(operatorframework.io/bundle-unpack-timeout: invalid): %w", errors.New("time: invalid duration \"invalid\"")), 1846 }, 1847 } { 1848 t.Run(tc.name, func(t *testing.T) { 1849 ogIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) 1850 ogLister := v1listers.NewOperatorGroupLister(ogIndexer).OperatorGroups(nsName) 1851 1852 for _, og := range tc.operatorGroups { 1853 err := ogIndexer.Add(og) 1854 assert.NoError(t, err) 1855 } 1856 1857 timeout, err := OperatorGroupBundleUnpackTimeout(ogLister) 1858 1859 assert.Equal(t, tc.expectedTimeout, timeout) 1860 assert.Equal(t, tc.expectedError, err) 1861 }) 1862 } 1863 } 1864 1865 func TestOperatorGroupBundleUnpackRetryInterval(t *testing.T) { 1866 nsName := "fake-ns" 1867 1868 for _, tc := range []struct { 1869 name string 1870 operatorGroups []*operatorsv1.OperatorGroup 1871 expectedTimeout time.Duration 1872 expectedError error 1873 }{ 1874 { 1875 name: "No operator groups exist", 1876 expectedTimeout: 0, 1877 expectedError: errors.New("found 0 operatorGroups, expected 1"), 1878 }, 1879 { 1880 name: "Multiple operator groups exist", 1881 operatorGroups: []*operatorsv1.OperatorGroup{ 1882 { 1883 TypeMeta: metav1.TypeMeta{ 1884 Kind: operatorsv1.OperatorGroupKind, 1885 APIVersion: operatorsv1.GroupVersion.String(), 1886 }, 1887 ObjectMeta: metav1.ObjectMeta{ 1888 Name: "og1", 1889 Namespace: nsName, 1890 }, 1891 }, 1892 { 1893 TypeMeta: metav1.TypeMeta{ 1894 Kind: operatorsv1.OperatorGroupKind, 1895 APIVersion: operatorsv1.GroupVersion.String(), 1896 }, 1897 ObjectMeta: metav1.ObjectMeta{ 1898 Name: "og2", 1899 Namespace: nsName, 1900 }, 1901 }, 1902 }, 1903 expectedTimeout: 0, 1904 expectedError: errors.New("found 2 operatorGroups, expected 1"), 1905 }, 1906 { 1907 name: "One operator group exists with valid unpack retry annotation", 1908 operatorGroups: []*operatorsv1.OperatorGroup{ 1909 { 1910 TypeMeta: metav1.TypeMeta{ 1911 Kind: operatorsv1.OperatorGroupKind, 1912 APIVersion: operatorsv1.GroupVersion.String(), 1913 }, 1914 ObjectMeta: metav1.ObjectMeta{ 1915 Name: "og", 1916 Namespace: nsName, 1917 Annotations: map[string]string{BundleUnpackRetryMinimumIntervalAnnotationKey: "1m"}, 1918 }, 1919 }, 1920 }, 1921 expectedTimeout: 1 * time.Minute, 1922 expectedError: nil, 1923 }, 1924 { 1925 name: "One operator group exists with no unpack retry annotation", 1926 operatorGroups: []*operatorsv1.OperatorGroup{ 1927 { 1928 TypeMeta: metav1.TypeMeta{ 1929 Kind: operatorsv1.OperatorGroupKind, 1930 APIVersion: operatorsv1.GroupVersion.String(), 1931 }, 1932 ObjectMeta: metav1.ObjectMeta{ 1933 Name: "og", 1934 Namespace: nsName, 1935 }, 1936 }, 1937 }, 1938 expectedTimeout: 0, 1939 expectedError: nil, 1940 }, 1941 { 1942 name: "One operator group exists with invalid unpack retry annotation", 1943 operatorGroups: []*operatorsv1.OperatorGroup{ 1944 { 1945 TypeMeta: metav1.TypeMeta{ 1946 Kind: operatorsv1.OperatorGroupKind, 1947 APIVersion: operatorsv1.GroupVersion.String(), 1948 }, 1949 ObjectMeta: metav1.ObjectMeta{ 1950 Name: "og", 1951 Namespace: nsName, 1952 Annotations: map[string]string{BundleUnpackRetryMinimumIntervalAnnotationKey: "invalid"}, 1953 }, 1954 }, 1955 }, 1956 expectedTimeout: 0, 1957 expectedError: fmt.Errorf("failed to parse unpack retry annotation(operatorframework.io/bundle-unpack-min-retry-interval: invalid): %w", errors.New("time: invalid duration \"invalid\"")), 1958 }, 1959 } { 1960 t.Run(tc.name, func(t *testing.T) { 1961 ogIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) 1962 ogLister := v1listers.NewOperatorGroupLister(ogIndexer).OperatorGroups(nsName) 1963 1964 for _, og := range tc.operatorGroups { 1965 err := ogIndexer.Add(og) 1966 assert.NoError(t, err) 1967 } 1968 1969 timeout, err := OperatorGroupBundleUnpackRetryInterval(ogLister) 1970 1971 assert.Equal(t, tc.expectedTimeout, timeout) 1972 assert.Equal(t, tc.expectedError, err) 1973 }) 1974 } 1975 } 1976 1977 func TestSortUnpackJobs(t *testing.T) { 1978 // if there is a non-failed job, it should be first 1979 // otherwise, the latest job should be first 1980 //first n-1 jobs and oldest job are preserved 1981 testJob := func(name string, failed bool, ts int64) *batchv1.Job { 1982 conditions := []batchv1.JobCondition{} 1983 if failed { 1984 conditions = append(conditions, batchv1.JobCondition{ 1985 Type: batchv1.JobFailed, 1986 Status: corev1.ConditionTrue, 1987 LastTransitionTime: metav1.Time{Time: time.Unix(ts, 0)}, 1988 }) 1989 } 1990 return &batchv1.Job{ 1991 ObjectMeta: metav1.ObjectMeta{ 1992 Name: name, 1993 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue, bundleUnpackRefLabel: "test"}, 1994 }, 1995 Status: batchv1.JobStatus{ 1996 Conditions: conditions, 1997 }, 1998 } 1999 } 2000 nilConditionJob := &batchv1.Job{ 2001 ObjectMeta: metav1.ObjectMeta{ 2002 Name: "nc", 2003 Labels: map[string]string{install.OLMManagedLabelKey: install.OLMManagedLabelValue, bundleUnpackRefLabel: "test"}, 2004 }, 2005 Status: batchv1.JobStatus{ 2006 Conditions: nil, 2007 }, 2008 } 2009 failedJobs := []*batchv1.Job{ 2010 testJob("f-1", true, 1), 2011 testJob("f-2", true, 2), 2012 testJob("f-3", true, 3), 2013 testJob("f-4", true, 4), 2014 testJob("f-5", true, 5), 2015 } 2016 nonFailedJob := testJob("s-1", false, 1) 2017 for _, tc := range []struct { 2018 name string 2019 jobs []*batchv1.Job 2020 maxRetained int 2021 expectedLatest *batchv1.Job 2022 expectedToDelete []*batchv1.Job 2023 }{ 2024 { 2025 name: "no job history", 2026 maxRetained: 0, 2027 jobs: []*batchv1.Job{ 2028 failedJobs[1], 2029 failedJobs[2], 2030 failedJobs[0], 2031 }, 2032 expectedLatest: failedJobs[2], 2033 expectedToDelete: []*batchv1.Job{ 2034 failedJobs[1], 2035 failedJobs[0], 2036 }, 2037 }, { 2038 name: "empty job list", 2039 maxRetained: 1, 2040 }, { 2041 name: "nil job in list", 2042 maxRetained: 1, 2043 jobs: []*batchv1.Job{ 2044 failedJobs[2], 2045 nil, 2046 failedJobs[1], 2047 }, 2048 expectedLatest: failedJobs[2], 2049 }, { 2050 name: "nil condition", 2051 maxRetained: 3, 2052 jobs: []*batchv1.Job{ 2053 failedJobs[2], 2054 nilConditionJob, 2055 failedJobs[1], 2056 }, 2057 expectedLatest: nilConditionJob, 2058 }, { 2059 name: "retain oldest", 2060 maxRetained: 1, 2061 jobs: []*batchv1.Job{ 2062 failedJobs[2], 2063 failedJobs[0], 2064 failedJobs[1], 2065 }, 2066 expectedToDelete: []*batchv1.Job{ 2067 failedJobs[1], 2068 }, 2069 expectedLatest: failedJobs[2], 2070 }, { 2071 name: "multiple old jobs", 2072 maxRetained: 2, 2073 jobs: []*batchv1.Job{ 2074 failedJobs[1], 2075 failedJobs[0], 2076 failedJobs[2], 2077 failedJobs[3], 2078 failedJobs[4], 2079 }, 2080 expectedLatest: failedJobs[4], 2081 expectedToDelete: []*batchv1.Job{ 2082 failedJobs[1], 2083 failedJobs[2], 2084 }, 2085 }, { 2086 name: "select non-failed as latest", 2087 maxRetained: 3, 2088 jobs: []*batchv1.Job{ 2089 failedJobs[0], 2090 failedJobs[1], 2091 nonFailedJob, 2092 }, 2093 expectedLatest: nonFailedJob, 2094 }, 2095 } { 2096 latest, toDelete := sortUnpackJobs(tc.jobs, tc.maxRetained) 2097 assert.Equal(t, tc.expectedLatest, latest) 2098 assert.ElementsMatch(t, tc.expectedToDelete, toDelete) 2099 } 2100 }