diff --git a/.goreleaser.yml b/.goreleaser.yml index 5df328385b..fd6976614d 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -74,23 +74,6 @@ builds: ldflags: - -X {{ .Env.PKG }}/pkg/version.GitCommit={{ .FullCommit }} - -X {{ .Env.PKG }}/pkg/version.OLMVersion={{ .Tag }} - - id: copy-content - main: ./cmd/copy-content - binary: copy-content - goos: - - linux - goarch: - - amd64 - - arm64 - - ppc64le - - s390x - tags: - - json1 - flags: - - -mod=vendor - ldflags: - - -X {{ .Env.PKG }}/pkg/version.GitCommit={{ .FullCommit }} - - -X {{ .Env.PKG }}/pkg/version.OLMVersion={{ .Tag }} dockers: - image_templates: - "{{ .Env.IMAGE_REPO }}:{{ .Tag }}-amd64" diff --git a/AGENTS.md b/AGENTS.md index 1406828413..dfb3dea858 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,8 +67,7 @@ operator-lifecycle-manager/ ├── cmd/ # Entry point binaries │ ├── catalog/ # Catalog Operator main │ ├── olm/ # OLM Operator main -│ ├── package-server/ # Package API server -│ └── copy-content/ # Content copy utility +│ └── package-server/ # Package API server │ ├── pkg/ # Core implementation │ ├── api/ # API client and wrappers diff --git a/Dockerfile b/Dockerfile index 0f92435242..5308799547 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,6 @@ RUN ["/busybox/ln", "-s", "/busybox/cp", "/bin/cp"] COPY olm /bin/olm COPY catalog /bin/catalog COPY package-server /bin/package-server -COPY copy-content /bin/copy-content COPY cpb /bin/cpb EXPOSE 8080 EXPOSE 5443 diff --git a/cmd/copy-content/main.go b/cmd/copy-content/main.go deleted file mode 100644 index bd5fb44f7a..0000000000 --- a/cmd/copy-content/main.go +++ /dev/null @@ -1,55 +0,0 @@ -package main - -import ( - "fmt" - "os" - - "github.com/otiai10/copy" - "github.com/spf13/cobra" -) - -func main() { - cmd := newCmd() - cmd.Execute() -} - -func newCmd() *cobra.Command { - var ( - catalogFrom string - catalogTo string - cacheFrom string - cacheTo string - ) - cmd := &cobra.Command{ - Use: "copy-content", - Short: "Copy catalog and cache content", - Long: `Copy catalog and cache content`, - Run: func(cmd *cobra.Command, args []string) { - var contentMap = make(map[string]string, 2) - contentMap[catalogFrom] = catalogTo - if cmd.Flags().Changed("cache.from") { - contentMap[cacheFrom] = cacheTo - } - - for from, to := range contentMap { - if err := os.RemoveAll(to); err != nil { - fmt.Printf("failed to remove %s: %s", to, err) - os.Exit(1) - } - if err := copy.Copy(from, to); err != nil { - fmt.Printf("failed to copy %s to %s: %s\n", from, to, err) - os.Exit(1) - } - } - }, - } - - cmd.Flags().StringVar(&catalogFrom, "catalog.from", "", "Path to catalog contents to copy") - cmd.Flags().StringVar(&catalogTo, "catalog.to", "", "Path to where catalog contents should be copied") - cmd.Flags().StringVar(&cacheFrom, "cache.from", "", "Path to cache contents to copy (required if cache.to is set)") // optional - cmd.Flags().StringVar(&cacheTo, "cache.to", "", "Path to where cache contents should be copied (required if cache.from is set)") // optional - cmd.MarkFlagRequired("catalog.from") - cmd.MarkFlagRequired("catalog.to") - cmd.MarkFlagsRequiredTogether("cache.from", "cache.to") - return cmd -} diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go index 069e08ab76..58c5405de5 100644 --- a/pkg/controller/operators/catalog/operator.go +++ b/pkg/controller/operators/catalog/operator.go @@ -221,7 +221,7 @@ func NewOperator(ctx context.Context, kubeconfigPath string, clock utilclock.Clo op.sources = grpc.NewSourceStore(logger, 10*time.Second, 10*time.Minute, op.syncSourceState) op.resolverSourceProvider = resolver.SourceProviderFromRegistryClientProvider(op.sources, lister.OperatorsV1alpha1().CatalogSourceLister(), logger) op.operatorCacheProvider = resolver.NewOperatorCacheProvider(lister, crClient, op.resolverSourceProvider, logger) - op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, opClient, configmapRegistryImage, op.now, ssaClient, workloadUserID, opmImage, utilImage) + op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, opClient, configmapRegistryImage, op.now, ssaClient, workloadUserID, opmImage) res := resolver.NewOperatorStepResolver(lister, crClient, operatorNamespace, op.operatorCacheProvider, logger) op.resolver = resolver.NewInstrumentedResolver(res, metrics.RegisterDependencyResolutionSuccess, metrics.RegisterDependencyResolutionFailure) diff --git a/pkg/controller/operators/catalog/operator_test.go b/pkg/controller/operators/catalog/operator_test.go index 42083873d4..4f3679d319 100644 --- a/pkg/controller/operators/catalog/operator_test.go +++ b/pkg/controller/operators/catalog/operator_test.go @@ -2279,7 +2279,7 @@ func NewFakeOperator(ctx context.Context, namespace string, namespaces []string, return nil, err } applier := controllerclient.NewFakeApplier(s, "testowner") - op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, op.opClient, "test:pod", op.now, applier, 1001, "", "") + op.reconciler = reconciler.NewRegistryReconcilerFactory(lister, op.opClient, "test:pod", op.now, applier, 1001, "") } op.RunInformers(ctx) @@ -2447,7 +2447,7 @@ func pod(t *testing.T, s v1alpha1.CatalogSource) *corev1.Pod { Name: s.GetName(), }, } - pod, err := reconciler.Pod(&s, "registry-server", "central-opm", "central-util", s.Spec.Image, serviceAccount, s.GetLabels(), s.GetAnnotations(), 5, 10, 1001, v1alpha1.Legacy) + pod, err := reconciler.Pod(&s, "registry-server", "central-opm", s.Spec.Image, serviceAccount, s.GetLabels(), s.GetAnnotations(), 5, 10, 1001, v1alpha1.Legacy) if err != nil { t.Fatal(err) } diff --git a/pkg/controller/registry/reconciler/configmap.go b/pkg/controller/registry/reconciler/configmap.go index 5207265b17..04b1afcf79 100644 --- a/pkg/controller/registry/reconciler/configmap.go +++ b/pkg/controller/registry/reconciler/configmap.go @@ -113,7 +113,7 @@ func (s *configMapCatalogSourceDecorator) Service() (*corev1.Service, error) { } func (s *configMapCatalogSourceDecorator) Pod(image string, defaultPodSecurityConfig v1alpha1.SecurityConfig) (*corev1.Pod, error) { - pod, err := Pod(s.CatalogSource, "configmap-registry-server", "", "", image, nil, s.Labels(), s.Annotations(), 5, 5, s.runAsUser, defaultPodSecurityConfig) + pod, err := Pod(s.CatalogSource, "configmap-registry-server", "", image, nil, s.Labels(), s.Annotations(), 5, 5, s.runAsUser, defaultPodSecurityConfig) if err != nil { return nil, err } diff --git a/pkg/controller/registry/reconciler/grpc.go b/pkg/controller/registry/reconciler/grpc.go index 024e298691..b309fb1f6c 100644 --- a/pkg/controller/registry/reconciler/grpc.go +++ b/pkg/controller/registry/reconciler/grpc.go @@ -39,7 +39,6 @@ type grpcCatalogSourceDecorator struct { *v1alpha1.CatalogSource createPodAsUser int64 opmImage string - utilImage string } type UpdateNotReadyErr struct { @@ -144,7 +143,7 @@ func (s *grpcCatalogSourceDecorator) ServiceAccount() *corev1.ServiceAccount { } func (s *grpcCatalogSourceDecorator) Pod(serviceAccount *corev1.ServiceAccount, defaultPodSecurityConfig v1alpha1.SecurityConfig) (*corev1.Pod, error) { - pod, err := Pod(s.CatalogSource, "registry-server", s.opmImage, s.utilImage, s.Spec.Image, serviceAccount, s.Labels(), s.Annotations(), 5, 10, s.createPodAsUser, defaultPodSecurityConfig) + pod, err := Pod(s.CatalogSource, "registry-server", s.opmImage, s.Spec.Image, serviceAccount, s.Labels(), s.Annotations(), 5, 10, s.createPodAsUser, defaultPodSecurityConfig) if err != nil { return nil, err } @@ -159,7 +158,6 @@ type GrpcRegistryReconciler struct { SSAClient *controllerclient.ServerSideApplier createPodAsUser int64 opmImage string - utilImage string } var _ RegistryReconciler = &GrpcRegistryReconciler{} @@ -262,23 +260,26 @@ func (c *GrpcRegistryReconciler) currentPodsWithCorrectImageAndSpec(logger *logr } func correctImages(source grpcCatalogSourceDecorator, pod *corev1.Pod) bool { + if len(pod.Spec.InitContainers) != 0 || len(pod.Spec.Containers) != 1 { + return false + } + if pod.Spec.Containers[0].Image != source.CatalogSource.Spec.Image { + return false + } if source.CatalogSource.Spec.GrpcPodConfig != nil && source.CatalogSource.Spec.GrpcPodConfig.ExtractContent != nil { - if len(pod.Spec.InitContainers) != 2 { + if len(pod.Spec.Volumes) == 0 { return false } - if len(pod.Spec.Containers) != 1 { - return false - } - return pod.Spec.InitContainers[0].Image == source.utilImage && - pod.Spec.InitContainers[1].Image == source.CatalogSource.Spec.Image && - pod.Spec.Containers[0].Image == source.opmImage + return pod.Spec.Volumes[0].Name == opmVolumeName && + pod.Spec.Volumes[0].VolumeSource.Image != nil && + pod.Spec.Volumes[0].VolumeSource.Image.Reference == source.opmImage } - return pod.Spec.Containers[0].Image == source.CatalogSource.Spec.Image + return true } // EnsureRegistryServer ensures that all components of registry server are up to date. func (c *GrpcRegistryReconciler) EnsureRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) error { - source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage, utilImage: c.utilImage} + source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage} // if service status is nil, we force create every object to ensure they're created the first time valid, err := isRegistryServiceStatusValid(&source) @@ -618,21 +619,16 @@ func isPodDead(pod *corev1.Pod) bool { return false } -// imageID returns the ImageID of the primary catalog source container or an empty string if the image ID isn't available yet. +// imageID returns the ImageID of the catalog source container or an empty string if the image ID isn't available yet. // Note: the pod must be running and the container in a ready status to return a valid ImageID. func imageID(pod *corev1.Pod) string { - if len(pod.Status.InitContainerStatuses) == 2 && len(pod.Status.ContainerStatuses) == 1 { - // spec.grpcPodConfig.extractContent mode was used for this pod - return pod.Status.InitContainerStatuses[1].ImageID - } - if len(pod.Status.InitContainerStatuses) == 0 && len(pod.Status.ContainerStatuses) == 1 { - // spec.grpcPodConfig.extractContent mode was NOT used for this pod (i.e. we're just running the catalog image directly) + if len(pod.Status.ContainerStatuses) == 1 { return pod.Status.ContainerStatuses[0].ImageID } - if len(pod.Status.InitContainerStatuses) == 0 && len(pod.Status.ContainerStatuses) == 0 { - logrus.WithField("CatalogSource", pod.GetName()).Warn("pod status unknown; pod has not yet populated initContainer and container status") + if len(pod.Status.ContainerStatuses) == 0 { + logrus.WithField("CatalogSource", pod.GetName()).Warn("pod status unknown; pod has not yet populated container status") } else { - logrus.WithField("CatalogSource", pod.GetName()).Warn("pod status unknown; pod contains unexpected initContainer and container configuration") + logrus.WithField("CatalogSource", pod.GetName()).Warn("pod status unknown; pod contains unexpected container configuration") } return "" } @@ -648,7 +644,7 @@ func (c *GrpcRegistryReconciler) removePods(pods []*corev1.Pod, namespace string // CheckRegistryServer returns true if the given CatalogSource is considered healthy; false otherwise. func (c *GrpcRegistryReconciler) CheckRegistryServer(logger *logrus.Entry, catalogSource *v1alpha1.CatalogSource) (bool, error) { - source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage, utilImage: c.utilImage} + source := grpcCatalogSourceDecorator{CatalogSource: catalogSource, createPodAsUser: c.createPodAsUser, opmImage: c.opmImage} // The CheckRegistryServer function is called by the CatalogSoruce controller before the registry resources are created, // returning a IsNotFound error will cause the controller to exit and never create the resources, so we should diff --git a/pkg/controller/registry/reconciler/grpc_test.go b/pkg/controller/registry/reconciler/grpc_test.go index d528ea5b90..52177d1a99 100644 --- a/pkg/controller/registry/reconciler/grpc_test.go +++ b/pkg/controller/registry/reconciler/grpc_test.go @@ -722,25 +722,12 @@ func TestGetPodImageID(t *testing.T) { result string }{ { - description: "default pod has status: return status", + description: "pod has single container status: return imageID", pod: &corev1.Pod{Status: corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{{ImageID: "xyz123"}}}}, result: "xyz123", }, { - description: "extractConfig pod has status: return status", - pod: &corev1.Pod{Status: corev1.PodStatus{ - InitContainerStatuses: []corev1.ContainerStatus{ - {ImageID: "xyz123"}, - {ImageID: "abc456"}, - }, - ContainerStatuses: []corev1.ContainerStatus{ - {ImageID: "xyz123"}, - }, - }}, - result: "abc456", - }, - { - description: "pod has unexpected container config", + description: "pod has unexpected multiple container statuses", pod: &corev1.Pod{Status: corev1.PodStatus{ContainerStatuses: []corev1.ContainerStatus{ {ImageID: "xyz123"}, {ImageID: "abc456"}, diff --git a/pkg/controller/registry/reconciler/reconciler.go b/pkg/controller/registry/reconciler/reconciler.go index a4370df5f4..fe900bf4d4 100644 --- a/pkg/controller/registry/reconciler/reconciler.go +++ b/pkg/controller/registry/reconciler/reconciler.go @@ -32,6 +32,9 @@ const ( PodHashLabelKey = "olm.pod-spec-hash" //ClusterAutoscalingAnnotationKey is the annotation that enables the cluster autoscaler to evict catalog pods ClusterAutoscalingAnnotationKey string = "cluster-autoscaler.kubernetes.io/safe-to-evict" + + opmVolumeName = "opm" + opmMountPath = "/opm" ) // RegistryEnsurer describes methods for ensuring a registry exists. @@ -66,7 +69,6 @@ type registryReconcilerFactory struct { SSAClient *controllerclient.ServerSideApplier createPodAsUser int64 opmImage string - utilImage string } // ReconcilerForSource returns a RegistryReconciler based on the configuration of the given CatalogSource. @@ -90,7 +92,6 @@ func (r *registryReconcilerFactory) ReconcilerForSource(source *operatorsv1alpha SSAClient: r.SSAClient, createPodAsUser: r.createPodAsUser, opmImage: r.opmImage, - utilImage: r.utilImage, } } else if source.Spec.Address != "" { return &GrpcAddressRegistryReconciler{ @@ -102,7 +103,7 @@ func (r *registryReconcilerFactory) ReconcilerForSource(source *operatorsv1alpha } // NewRegistryReconcilerFactory returns an initialized RegistryReconcilerFactory. -func NewRegistryReconcilerFactory(lister operatorlister.OperatorLister, opClient operatorclient.ClientInterface, configMapServerImage string, now nowFunc, ssaClient *controllerclient.ServerSideApplier, createPodAsUser int64, opmImage, utilImage string) RegistryReconcilerFactory { +func NewRegistryReconcilerFactory(lister operatorlister.OperatorLister, opClient operatorclient.ClientInterface, configMapServerImage string, now nowFunc, ssaClient *controllerclient.ServerSideApplier, createPodAsUser int64, opmImage string) RegistryReconcilerFactory { return ®istryReconcilerFactory{ now: now, Lister: lister, @@ -111,11 +112,10 @@ func NewRegistryReconcilerFactory(lister operatorlister.OperatorLister, opClient SSAClient: ssaClient, createPodAsUser: createPodAsUser, opmImage: opmImage, - utilImage: utilImage, } } -func Pod(source *operatorsv1alpha1.CatalogSource, name, opmImg, utilImage, img string, serviceAccount *corev1.ServiceAccount, labels, annotations map[string]string, readinessDelay, livenessDelay int32, runAsUser int64, defaultSecurityConfig operatorsv1alpha1.SecurityConfig) (*corev1.Pod, error) { +func Pod(source *operatorsv1alpha1.CatalogSource, name, opmImg, img string, serviceAccount *corev1.ServiceAccount, labels, annotations map[string]string, readinessDelay, livenessDelay int32, runAsUser int64, defaultSecurityConfig operatorsv1alpha1.SecurityConfig) (*corev1.Pod, error) { // make a copy of the labels and annotations to avoid mutating the input parameters podLabels := make(map[string]string) podAnnotations := make(map[string]string) @@ -252,92 +252,44 @@ func Pod(source *operatorsv1alpha1.CatalogSource, name, opmImg, utilImage, img s }) } - // Reconfigure pod to extract content + // Mount the OLM-defined opm binary via image volume if grpcPodConfig.ExtractContent != nil { pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{ - Name: "utilities", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, corev1.Volume{ - Name: "catalog-content", + Name: opmVolumeName, VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, + Image: &corev1.ImageVolumeSource{ + Reference: opmImg, + PullPolicy: image.InferImagePullPolicy(opmImg), + }, }, }) - const utilitiesPath = "/utilities" - utilitiesVolumeMount := corev1.VolumeMount{ - Name: "utilities", - MountPath: utilitiesPath, - } - const catalogPath = "/extracted-catalog" - contentVolumeMount := corev1.VolumeMount{ - Name: "catalog-content", - MountPath: catalogPath, - } - // init container to copy catalog info. - // ExtractContent.CatalogDir is mandatory when ExtractContent is provided - // ExtractContent.CacheDir is optional, so we only add it if it is set - var extractArgs = []string{ - "--catalog.from=" + grpcPodConfig.ExtractContent.CatalogDir, - "--catalog.to=" + fmt.Sprintf("%s/catalog", catalogPath), - } - if grpcPodConfig.ExtractContent.CacheDir != "" { - extractArgs = append(extractArgs, "--cache.from="+grpcPodConfig.ExtractContent.CacheDir) - extractArgs = append(extractArgs, "--cache.to="+fmt.Sprintf("%s/cache", catalogPath)) - } - pod.Spec.InitContainers = append(pod.Spec.InitContainers, corev1.Container{ - Name: "extract-utilities", - Image: utilImage, - Command: []string{"cp"}, - Args: []string{"/bin/copy-content", fmt.Sprintf("%s/copy-content", utilitiesPath)}, - VolumeMounts: []corev1.VolumeMount{utilitiesVolumeMount}, - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - SecurityContext: &corev1.SecurityContext{ - ReadOnlyRootFilesystem: ptr.To(true), - }, - }, corev1.Container{ - Name: "extract-content", - Image: img, - ImagePullPolicy: image.InferImagePullPolicy(img), - Command: []string{utilitiesPath + "/copy-content"}, - Args: extractArgs, - VolumeMounts: []corev1.VolumeMount{utilitiesVolumeMount, contentVolumeMount}, - TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError, - SecurityContext: &corev1.SecurityContext{ - ReadOnlyRootFilesystem: ptr.To(true), - }, + + pod.Spec.Containers[0].Command = []string{filepath.Join(opmMountPath, "bin", "opm")} + pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: opmVolumeName, + MountPath: opmMountPath, }) - pod.Spec.Containers[0].SecurityContext.ReadOnlyRootFilesystem = ptr.To(true) - pod.Spec.Containers[0].Image = opmImg - pod.Spec.Containers[0].Command = []string{"/bin/opm"} - pod.Spec.Containers[0].ImagePullPolicy = image.InferImagePullPolicy(opmImg) - var containerArgs = []string{ + containerArgs := []string{ "serve", - filepath.Join(catalogPath, "catalog"), + grpcPodConfig.ExtractContent.CatalogDir, } if grpcPodConfig.ExtractContent.CacheDir != "" { - containerArgs = append(containerArgs, "--cache-dir="+filepath.Join(catalogPath, "cache")) + containerArgs = append(containerArgs, "--cache-dir="+grpcPodConfig.ExtractContent.CacheDir) } else { - // opm serve does not allow us to specify an empty cache directory, which means that it will - // only create new caches in /tmp/, so we need to provide adequate write access there const tmpdirName = "tmpdir" - tmpdirVolumeMount := corev1.VolumeMount{ - Name: tmpdirName, - MountPath: "/tmp/", - } pod.Spec.Volumes = append(pod.Spec.Volumes, corev1.Volume{ Name: tmpdirName, VolumeSource: corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{}, }, }) - - pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, tmpdirVolumeMount) + pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, corev1.VolumeMount{ + Name: tmpdirName, + MountPath: "/tmp/", + }) } pod.Spec.Containers[0].Args = containerArgs - pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, contentVolumeMount) } } diff --git a/pkg/controller/registry/reconciler/reconciler_test.go b/pkg/controller/registry/reconciler/reconciler_test.go index cfcf065dbe..a87c9a76d1 100644 --- a/pkg/controller/registry/reconciler/reconciler_test.go +++ b/pkg/controller/registry/reconciler/reconciler_test.go @@ -171,7 +171,7 @@ func TestPodMemoryTarget(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - pod, err := Pod(testCase.input, "name", "opmImage", "utilImage", "image", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) + pod, err := Pod(testCase.input, "name", "opmImage", "image", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) require.NoError(t, err) if diff := cmp.Diff(pod, testCase.expected); diff != "" { t.Errorf("got incorrect pod: %v", diff) @@ -285,59 +285,27 @@ func TestPodExtractContent(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-", Namespace: "testns", - Labels: map[string]string{"olm.pod-spec-hash": "9SbEBzbV5JmBIA6zza29T4lIo0ESVJ8SN6slOY", "olm.managed": "true"}, + Labels: map[string]string{"olm.pod-spec-hash": "oypQMgSPgD7YHYh2K0mVS2Q5xl9GODNUkvtd0", "olm.managed": "true"}, Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, }, Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ { - Name: "utilities", - VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, - }, - { - Name: "catalog-content", - VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, - }, - }, - InitContainers: []corev1.Container{ - { - Name: "extract-utilities", - Image: "utilImage", - Command: []string{"cp"}, - Args: []string{"/bin/copy-content", "/utilities/copy-content"}, - VolumeMounts: []corev1.VolumeMount{{Name: "utilities", MountPath: "/utilities"}}, - TerminationMessagePolicy: "FallbackToLogsOnError", - SecurityContext: &corev1.SecurityContext{ - ReadOnlyRootFilesystem: ptr.To(true), - }, - }, - { - Name: "extract-content", - Image: "image", - ImagePullPolicy: image.InferImagePullPolicy("image"), - Command: []string{"/utilities/copy-content"}, - Args: []string{ - "--catalog.from=/catalog", - "--catalog.to=/extracted-catalog/catalog", - "--cache.from=/tmp/cache", - "--cache.to=/extracted-catalog/cache", - }, - VolumeMounts: []corev1.VolumeMount{ - {Name: "utilities", MountPath: "/utilities"}, - {Name: "catalog-content", MountPath: "/extracted-catalog"}, - }, - TerminationMessagePolicy: "FallbackToLogsOnError", - SecurityContext: &corev1.SecurityContext{ - ReadOnlyRootFilesystem: ptr.To(true), + Name: "opm", + VolumeSource: corev1.VolumeSource{ + Image: &corev1.ImageVolumeSource{ + Reference: "opmImage", + PullPolicy: image.InferImagePullPolicy("opmImage"), + }, }, }, }, Containers: []corev1.Container{ { Name: "name", - Image: "opmImage", - Command: []string{"/bin/opm"}, - Args: []string{"serve", "/extracted-catalog/catalog", "--cache-dir=/extracted-catalog/cache"}, + Image: "image", + Command: []string{"/opm/bin/opm"}, + Args: []string{"serve", "/catalog", "--cache-dir=/tmp/cache"}, Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ @@ -374,11 +342,11 @@ func TestPodExtractContent(t *testing.T) { }, }, SecurityContext: &corev1.SecurityContext{ - ReadOnlyRootFilesystem: ptr.To(true), + ReadOnlyRootFilesystem: ptr.To(false), }, ImagePullPolicy: image.InferImagePullPolicy("image"), TerminationMessagePolicy: "FallbackToLogsOnError", - VolumeMounts: []corev1.VolumeMount{{Name: "catalog-content", MountPath: "/extracted-catalog"}}, + VolumeMounts: []corev1.VolumeMount{{Name: "opm", MountPath: "/opm"}}, }, }, NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, @@ -406,61 +374,31 @@ func TestPodExtractContent(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-", Namespace: "testns", - Labels: map[string]string{"olm.pod-spec-hash": "1B9AFU7EIoI0CgRaHbyBL3EnGzwrkBq968SSps", "olm.managed": "true"}, + Labels: map[string]string{"olm.pod-spec-hash": "2Y4jdn8rXTihrLwREPoY2TfbDOgfipzXjXRs4T", "olm.managed": "true"}, Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, }, Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ { - Name: "utilities", - VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, - }, - { - Name: "catalog-content", - VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, + Name: "opm", + VolumeSource: corev1.VolumeSource{ + Image: &corev1.ImageVolumeSource{ + Reference: "opmImage", + PullPolicy: image.InferImagePullPolicy("opmImage"), + }, + }, }, { Name: "tmpdir", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, }, }, - InitContainers: []corev1.Container{ - { - Name: "extract-utilities", - Image: "utilImage", - Command: []string{"cp"}, - Args: []string{"/bin/copy-content", "/utilities/copy-content"}, - VolumeMounts: []corev1.VolumeMount{{Name: "utilities", MountPath: "/utilities"}}, - TerminationMessagePolicy: "FallbackToLogsOnError", - SecurityContext: &corev1.SecurityContext{ - ReadOnlyRootFilesystem: ptr.To(true), - }, - }, - { - Name: "extract-content", - Image: "image", - ImagePullPolicy: image.InferImagePullPolicy("image"), - Command: []string{"/utilities/copy-content"}, - Args: []string{ - "--catalog.from=/catalog", - "--catalog.to=/extracted-catalog/catalog", - }, - VolumeMounts: []corev1.VolumeMount{ - {Name: "utilities", MountPath: "/utilities"}, - {Name: "catalog-content", MountPath: "/extracted-catalog"}, - }, - TerminationMessagePolicy: "FallbackToLogsOnError", - SecurityContext: &corev1.SecurityContext{ - ReadOnlyRootFilesystem: ptr.To(true), - }, - }, - }, Containers: []corev1.Container{ { Name: "name", - Image: "opmImage", - Command: []string{"/bin/opm"}, - Args: []string{"serve", "/extracted-catalog/catalog"}, + Image: "image", + Command: []string{"/opm/bin/opm"}, + Args: []string{"serve", "/catalog"}, Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ @@ -497,11 +435,11 @@ func TestPodExtractContent(t *testing.T) { }, }, SecurityContext: &corev1.SecurityContext{ - ReadOnlyRootFilesystem: ptr.To(true), + ReadOnlyRootFilesystem: ptr.To(false), }, ImagePullPolicy: image.InferImagePullPolicy("image"), TerminationMessagePolicy: "FallbackToLogsOnError", - VolumeMounts: []corev1.VolumeMount{{Name: "tmpdir", MountPath: "/tmp/"}, {Name: "catalog-content", MountPath: "/extracted-catalog"}}, + VolumeMounts: []corev1.VolumeMount{{Name: "opm", MountPath: "/opm"}, {Name: "tmpdir", MountPath: "/tmp/"}}, }, }, NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, @@ -605,63 +543,27 @@ func TestPodExtractContent(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-", Namespace: "testns", - Labels: map[string]string{"olm.pod-spec-hash": "3xuLPXGJ2pzekw21PFU68XUKOYc7PTuW45M521", "olm.managed": "true"}, + Labels: map[string]string{"olm.pod-spec-hash": "4aGeYEVJQco4GkczwKG64ZROxse7UhBLgBLSSV", "olm.managed": "true"}, Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, }, Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ { - Name: "utilities", - VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, - }, - { - Name: "catalog-content", - VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, - }, - }, - InitContainers: []corev1.Container{ - { - Name: "extract-utilities", - Image: "utilImage", - Command: []string{"cp"}, - Args: []string{"/bin/copy-content", "/utilities/copy-content"}, - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, - AllowPrivilegeEscalation: ptr.To(false), - ReadOnlyRootFilesystem: ptr.To(true), - }, - VolumeMounts: []corev1.VolumeMount{{Name: "utilities", MountPath: "/utilities"}}, - TerminationMessagePolicy: "FallbackToLogsOnError", - }, - { - Name: "extract-content", - Image: "image", - ImagePullPolicy: image.InferImagePullPolicy("image"), - Command: []string{"/utilities/copy-content"}, - Args: []string{ - "--catalog.from=/catalog", - "--catalog.to=/extracted-catalog/catalog", - "--cache.from=/tmp/cache", - "--cache.to=/extracted-catalog/cache", - }, - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, - AllowPrivilegeEscalation: ptr.To(false), - ReadOnlyRootFilesystem: ptr.To(true), - }, - VolumeMounts: []corev1.VolumeMount{ - {Name: "utilities", MountPath: "/utilities"}, - {Name: "catalog-content", MountPath: "/extracted-catalog"}, + Name: "opm", + VolumeSource: corev1.VolumeSource{ + Image: &corev1.ImageVolumeSource{ + Reference: "opmImage", + PullPolicy: image.InferImagePullPolicy("opmImage"), + }, }, - TerminationMessagePolicy: "FallbackToLogsOnError", }, }, Containers: []corev1.Container{ { Name: "name", - Image: "opmImage", - Command: []string{"/bin/opm"}, - Args: []string{"serve", "/extracted-catalog/catalog", "--cache-dir=/extracted-catalog/cache"}, + Image: "image", + Command: []string{"/opm/bin/opm"}, + Args: []string{"serve", "/catalog", "--cache-dir=/tmp/cache"}, Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ @@ -701,10 +603,10 @@ func TestPodExtractContent(t *testing.T) { SecurityContext: &corev1.SecurityContext{ Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, AllowPrivilegeEscalation: ptr.To(false), - ReadOnlyRootFilesystem: ptr.To(true), + ReadOnlyRootFilesystem: ptr.To(false), }, TerminationMessagePolicy: "FallbackToLogsOnError", - VolumeMounts: []corev1.VolumeMount{{Name: "catalog-content", MountPath: "/extracted-catalog"}}, + VolumeMounts: []corev1.VolumeMount{{Name: "opm", MountPath: "/opm"}}, }, }, NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, @@ -737,65 +639,31 @@ func TestPodExtractContent(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ GenerateName: "test-", Namespace: "testns", - Labels: map[string]string{"olm.pod-spec-hash": "7noQSgGmkI4BD1MPKe0pEFFfOE3jJtN2DUyZuD", "olm.managed": "true"}, + Labels: map[string]string{"olm.pod-spec-hash": "bTrmhhYLWOJPf71FBlse0nhy90Nr2OjdVBl8dv", "olm.managed": "true"}, Annotations: map[string]string{"cluster-autoscaler.kubernetes.io/safe-to-evict": "true"}, }, Spec: corev1.PodSpec{ Volumes: []corev1.Volume{ { - Name: "utilities", - VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, - }, - { - Name: "catalog-content", - VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, + Name: "opm", + VolumeSource: corev1.VolumeSource{ + Image: &corev1.ImageVolumeSource{ + Reference: "opmImage", + PullPolicy: image.InferImagePullPolicy("opmImage"), + }, + }, }, { Name: "tmpdir", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}, }, }, - InitContainers: []corev1.Container{ - { - Name: "extract-utilities", - Image: "utilImage", - Command: []string{"cp"}, - Args: []string{"/bin/copy-content", "/utilities/copy-content"}, - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, - AllowPrivilegeEscalation: ptr.To(false), - ReadOnlyRootFilesystem: ptr.To(true), - }, - VolumeMounts: []corev1.VolumeMount{{Name: "utilities", MountPath: "/utilities"}}, - TerminationMessagePolicy: "FallbackToLogsOnError", - }, - { - Name: "extract-content", - Image: "image", - ImagePullPolicy: image.InferImagePullPolicy("image"), - Command: []string{"/utilities/copy-content"}, - Args: []string{ - "--catalog.from=/catalog", - "--catalog.to=/extracted-catalog/catalog", - }, - SecurityContext: &corev1.SecurityContext{ - Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, - AllowPrivilegeEscalation: ptr.To(false), - ReadOnlyRootFilesystem: ptr.To(true), - }, - VolumeMounts: []corev1.VolumeMount{ - {Name: "utilities", MountPath: "/utilities"}, - {Name: "catalog-content", MountPath: "/extracted-catalog"}, - }, - TerminationMessagePolicy: "FallbackToLogsOnError", - }, - }, Containers: []corev1.Container{ { Name: "name", - Image: "opmImage", - Command: []string{"/bin/opm"}, - Args: []string{"serve", "/extracted-catalog/catalog"}, + Image: "image", + Command: []string{"/opm/bin/opm"}, + Args: []string{"serve", "/catalog"}, Ports: []corev1.ContainerPort{{Name: "grpc", ContainerPort: 50051}}, ReadinessProbe: &corev1.Probe{ ProbeHandler: corev1.ProbeHandler{ @@ -835,10 +703,10 @@ func TestPodExtractContent(t *testing.T) { SecurityContext: &corev1.SecurityContext{ Capabilities: &corev1.Capabilities{Drop: []corev1.Capability{"ALL"}}, AllowPrivilegeEscalation: ptr.To(false), - ReadOnlyRootFilesystem: ptr.To(true), + ReadOnlyRootFilesystem: ptr.To(false), }, TerminationMessagePolicy: "FallbackToLogsOnError", - VolumeMounts: []corev1.VolumeMount{{Name: "tmpdir", MountPath: "/tmp/"}, {Name: "catalog-content", MountPath: "/extracted-catalog"}}, + VolumeMounts: []corev1.VolumeMount{{Name: "opm", MountPath: "/opm"}, {Name: "tmpdir", MountPath: "/tmp/"}}, }, }, NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, @@ -855,7 +723,7 @@ func TestPodExtractContent(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - pod, err := Pod(testCase.input, "name", "opmImage", "utilImage", "image", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), testCase.securityContextConfig) + pod, err := Pod(testCase.input, "name", "opmImage", "image", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), testCase.securityContextConfig) require.NoError(t, err) if diff := cmp.Diff(testCase.expected, pod); diff != "" { t.Errorf("got incorrect pod: %v", diff) @@ -907,7 +775,7 @@ func TestPodServiceAccountImagePullSecrets(t *testing.T) { } for _, testCase := range testCases { - pod, err := Pod(catalogSource, "name", "opmImage", "utilImage", "image", testCase.serviceAccount, map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) + pod, err := Pod(catalogSource, "name", "opmImage", "image", testCase.serviceAccount, map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) require.NoError(t, err) if diff := cmp.Diff(testCase.serviceAccount.ImagePullSecrets, pod.Spec.ImagePullSecrets); diff != "" { t.Errorf("got incorrect pod: %v", diff) @@ -926,7 +794,7 @@ func TestPodNodeSelector(t *testing.T) { key := "kubernetes.io/os" value := "linux" - gotCatSrcPod, err := Pod(catsrc, "hello", "utilImage", "opmImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) + gotCatSrcPod, err := Pod(catsrc, "hello", "opmImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) require.NoError(t, err) gotCatSrcPodSelector := gotCatSrcPod.Spec.NodeSelector @@ -941,6 +809,7 @@ func TestPullPolicy(t *testing.T) { image string policy corev1.PullPolicy opmImage string + opmPolicy corev1.PullPolicy extractContent bool }{ { @@ -983,12 +852,14 @@ func TestPullPolicy(t *testing.T) { image: "quay.io/operator-framework/olm@sha256:b9d011c0fbfb65b387904f8fafc47ee1a9479d28d395473341288ee126ed993b", policy: corev1.PullIfNotPresent, opmImage: "quay.io/operator-framework/olm@sha256:b9d011c0fbfb65b387904f8fafc47ee1a9479d28d395473341288ee126ed993b", + opmPolicy: corev1.PullIfNotPresent, extractContent: true, }, { image: "quay.io/operator-framework/olm@sha256:b9d011c0fbfb65b387904f8fafc47ee1a9479d28d395473341288ee126ed993b", - policy: corev1.PullAlways, + policy: corev1.PullIfNotPresent, opmImage: "quay.io/operator-framework/olm:latest", + opmPolicy: corev1.PullAlways, extractContent: true, }, } @@ -1010,11 +881,18 @@ func TestPullPolicy(t *testing.T) { } source.Spec.GrpcPodConfig = grpcPodConfig } - p, err := Pod(source, "catalog", tt.opmImage, "utilImage", tt.image, serviceAccount("", "service-account"), nil, nil, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) + p, err := Pod(source, "catalog", tt.opmImage, tt.image, serviceAccount("", "service-account"), nil, nil, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) require.NoError(t, err) policy := p.Spec.Containers[0].ImagePullPolicy if policy != tt.policy { - t.Fatalf("expected pull policy %s for image %s", tt.policy, tt.image) + t.Fatalf("expected pull policy %s for image %s, got %s", tt.policy, tt.image, policy) + } + if tt.extractContent { + require.NotEmpty(t, p.Spec.Volumes, "expected at least one volume for extractContent") + require.NotNil(t, p.Spec.Volumes[0].VolumeSource.Image, "expected image volume source") + if p.Spec.Volumes[0].VolumeSource.Image.PullPolicy != tt.opmPolicy { + t.Fatalf("expected opm image volume pull policy %s for opm image %s, got %s", tt.opmPolicy, tt.opmImage, p.Spec.Volumes[0].VolumeSource.Image.PullPolicy) + } } } } @@ -1147,7 +1025,7 @@ func TestPodContainerSecurityContext(t *testing.T) { for _, testcase := range testcases { t.Run(testcase.title, func(t *testing.T) { - outputPod, err := Pod(testcase.inputCatsrc, "hello", "utilImage", "opmImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), workloadUserID, testcase.namespacePodSecurityConfig) + outputPod, err := Pod(testcase.inputCatsrc, "hello", "opmImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, map[string]string{}, int32(0), int32(0), workloadUserID, testcase.namespacePodSecurityConfig) require.NoError(t, err) // Assert PodSecurityContext @@ -1179,7 +1057,7 @@ func TestPodAvoidsConcurrentWrite(t *testing.T) { "annotation": "somethingelse", } - gotPod, err := Pod(catsrc, "hello", "opmImage", "utilImage", "busybox", serviceAccount("", "service-account"), labels, annotations, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) + gotPod, err := Pod(catsrc, "hello", "opmImage", "busybox", serviceAccount("", "service-account"), labels, annotations, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) require.NoError(t, err) // check labels and annotations point to different addresses between parameters and what's in the pod @@ -1409,7 +1287,7 @@ func TestPodSchedulingOverrides(t *testing.T) { } for _, testCase := range testCases { - pod, err := Pod(testCase.catalogSource, "hello", "opmImage", "utilImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, testCase.annotations, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) + pod, err := Pod(testCase.catalogSource, "hello", "opmImage", "busybox", serviceAccount("", "service-account"), map[string]string{}, testCase.annotations, int32(0), int32(0), int64(workloadUserID), v1alpha1.Legacy) require.NoError(t, err) require.Equal(t, testCase.expectedNodeSelectors, pod.Spec.NodeSelector) require.Equal(t, testCase.expectedPriorityClassName, pod.Spec.PriorityClassName)