From cad9f8ba425a169555ecdf985ca065d72f3c532f Mon Sep 17 00:00:00 2001 From: Mykola Morhun Date: Tue, 26 Sep 2023 12:55:04 +0300 Subject: [PATCH] Use Remote Secret for push token --- api/v1alpha1/imagerepository_types.go | 9 +- ...ppstudio.redhat.com_imagerepositories.yaml | 14 +- controllers/imagerepository_controller.go | 216 +++++++++------ .../imagerepository_controller_test.go | 254 +++++++++++------- .../imagerepository_controller_unit_test.go | 48 ++-- controllers/suite_util_test.go | 17 ++ 6 files changed, 353 insertions(+), 205 deletions(-) diff --git a/api/v1alpha1/imagerepository_types.go b/api/v1alpha1/imagerepository_types.go index be45e07..ea4d215 100644 --- a/api/v1alpha1/imagerepository_types.go +++ b/api/v1alpha1/imagerepository_types.go @@ -108,7 +108,7 @@ type CredentialsStatus struct { // PullSecretName is present only if ImageRepository has labels that connect it to Application and Component. // Holds name of the dockerconfig secret with credentials to pull only from the generated repository. - // The secret is not present in the same namespace as ImageRepository, but created in + // The secret might not be present in the same namespace as ImageRepository, but created in PullSecretName string `json:"pull-secret,omitempty"` // PushRobotAccountName holds name of the quay robot account with write (push and pull) permissions into the generated repository. @@ -117,6 +117,13 @@ type CredentialsStatus struct { // PullRobotAccountName is present only if ImageRepository has labels that connect it to Application and Component. // Holds name of the quay robot account with real (pull only) permissions from the generated repository. PullRobotAccountName string `json:"pull-robot-account,omitempty"` + + // PushRemoteSecretName holds name of RemoteSecret object that manages push Secret and its linking to pipeline Service Account. + PushRemoteSecretName string `json:"push-remote-secret,omitempty"` + + // PullRemoteSecretName is present only if ImageRepository has labels that connect it to Application and Component. + // Holds the name of the RemoteSecret object that manages pull Secret. + PullRemoteSecretName string `json:"pull-remote-secret,omitempty"` } //+kubebuilder:object:root=true diff --git a/config/crd/bases/appstudio.redhat.com_imagerepositories.yaml b/config/crd/bases/appstudio.redhat.com_imagerepositories.yaml index 543c006..f61de01 100644 --- a/config/crd/bases/appstudio.redhat.com_imagerepositories.yaml +++ b/config/crd/bases/appstudio.redhat.com_imagerepositories.yaml @@ -82,6 +82,11 @@ spec: were generated. format: date-time type: string + pull-remote-secret: + description: PullRemoteSecretName is present only if ImageRepository + has labels that connect it to Application and Component. Holds + the name of the RemoteSecret object that manages pull Secret. + type: string pull-robot-account: description: PullRobotAccountName is present only if ImageRepository has labels that connect it to Application and Component. Holds @@ -92,8 +97,13 @@ spec: description: PullSecretName is present only if ImageRepository has labels that connect it to Application and Component. Holds name of the dockerconfig secret with credentials to pull only - from the generated repository. The secret is not present in - the same namespace as ImageRepository, but created in + from the generated repository. The secret might not be present + in the same namespace as ImageRepository, but created in + type: string + push-remote-secret: + description: PushRemoteSecretName holds name of RemoteSecret object + that manages push Secret and its linking to pipeline Service + Account. type: string push-robot-account: description: PushRobotAccountName holds name of the quay robot diff --git a/controllers/imagerepository_controller.go b/controllers/imagerepository_controller.go index 58b6c13..4f40a00 100644 --- a/controllers/imagerepository_controller.go +++ b/controllers/imagerepository_controller.go @@ -46,6 +46,8 @@ const ( InternalRemoteSecretLabelName = "appstudio.redhat.com/internal" ImageRepositoryFinalizer = "appstudio.openshift.io/image-repository" + + buildPipelineServiceAccountName = "appstudio-pipeline" ) // ImageRepositoryReconciler reconciles a ImageRepository object @@ -202,6 +204,10 @@ func (r *ImageRepositoryReconciler) ProvisionImageRepository(ctx context.Context } else { imageRepositoryName = imageRepository.Namespace + "/" + imageRepository.Spec.Image.Name } + imageRepository.Spec.Image.Name = imageRepositoryName + + quayImageURL := fmt.Sprintf("quay.io/%s/%s", r.QuayOrganization, imageRepositoryName) + imageRepository.Status.Image.URL = quayImageURL if imageRepository.Spec.Image.Visibility == "" { imageRepository.Spec.Image.Visibility = imagerepositoryv1alpha1.ImageVisibilityPublic @@ -233,84 +239,46 @@ func (r *ImageRepositoryReconciler) ProvisionImageRepository(ctx context.Context return err } - robotAccountName := generateQuayRobotAccountName(imageRepositoryName, false) - robotAccount, err := r.QuayClient.CreateRobotAccount(r.QuayOrganization, robotAccountName) + pushCredentialsInfo, err := r.ProvisionImageRepositoryAccess(ctx, imageRepository, false) if err != nil { - log.Error(err, "failed to create robot account", "RobotAccountName", robotAccountName, l.Action, l.ActionAdd, l.Audit, "true") - return err - } - if robotAccount == nil { - err := fmt.Errorf("unexpected response from Quay: robot account data object is nil") - log.Error(err, "nil robot account") return err } - err = r.QuayClient.AddPermissionsForRepositoryToRobotAccount(r.QuayOrganization, repository.Name, robotAccount.Name, true) - if err != nil { - log.Error(err, "failed to add permissions to robot account", "RobotAccountName", robotAccountName, l.Action, l.ActionUpdate, l.Audit, "true") - return err - } - - quayImageURL := fmt.Sprintf("quay.io/%s/%s", r.QuayOrganization, repository.Name) - secretName := strings.ReplaceAll(robotAccountName, "_", "-") - if err := r.EnsureDockerSecret(ctx, imageRepository, robotAccount, secretName, quayImageURL); err != nil { - return err - } - - status := imagerepositoryv1alpha1.ImageRepositoryStatus{} + var pullCredentialsInfo *imageRepositoryAccessData if isComponentLinked(imageRepository) { - // Pull secret provision and propagation - pullRobotAccountName := generateQuayRobotAccountName(imageRepositoryName, true) - pullRobotAccount, err := r.QuayClient.CreateRobotAccount(r.QuayOrganization, pullRobotAccountName) + pullCredentialsInfo, err = r.ProvisionImageRepositoryAccess(ctx, imageRepository, true) if err != nil { - log.Error(err, "failed to create pull robot account", "RobotAccountName", pullRobotAccountName, l.Action, l.ActionAdd, l.Audit, "true") - return err - } - if robotAccount == nil { - err := fmt.Errorf("unexpected response from Quay: pull robot account data object is nil") - log.Error(err, "nil pull robot account") return err } - - err = r.QuayClient.AddPermissionsForRepositoryToRobotAccount(r.QuayOrganization, repository.Name, pullRobotAccount.Name, false) - if err != nil { - log.Error(err, "failed to add permissions to pull robot account", "RobotAccountName", robotAccountName, l.Action, l.ActionUpdate, l.Audit, "true") - return err - } - - remoteSecretName := getRemoteSecretName(imageRepository) - if err := r.EnsureRemotePullSecret(ctx, imageRepository, remoteSecretName); err != nil { - return err - } - - if err := r.CreateRemotePullSecretUploadSecret(ctx, pullRobotAccount, imageRepository.Namespace, remoteSecretName, quayImageURL); err != nil { - return err - } - - status.Credentials.PullRobotAccountName = pullRobotAccountName - status.Credentials.PullSecretName = remoteSecretName } + status := imagerepositoryv1alpha1.ImageRepositoryStatus{} status.State = imagerepositoryv1alpha1.ImageRepositoryStateReady status.Image.URL = quayImageURL status.Image.Visibility = imageRepository.Spec.Image.Visibility - status.Credentials.PushRobotAccountName = robotAccountName - status.Credentials.PushSecretName = secretName status.Credentials.GenerationTimestamp = &metav1.Time{Time: time.Now()} + status.Credentials.PushRobotAccountName = pushCredentialsInfo.RobotAccountName + status.Credentials.PushRemoteSecretName = pushCredentialsInfo.RemoteSecretName + status.Credentials.PushSecretName = pushCredentialsInfo.SecretName + if isComponentLinked(imageRepository) { + status.Credentials.PullRobotAccountName = pullCredentialsInfo.RobotAccountName + status.Credentials.PullRemoteSecretName = pullCredentialsInfo.RemoteSecretName + status.Credentials.PullSecretName = pullCredentialsInfo.SecretName + } imageRepository.Spec.Image.Name = imageRepositoryName controllerutil.AddFinalizer(imageRepository, ImageRepositoryFinalizer) if isComponentLinked(imageRepository) { if err := controllerutil.SetOwnerReference(component, imageRepository, r.Scheme); err != nil { log.Error(err, "failed to set component as owner", "ComponentName", component.Name) - // Do not brake provision because of faile owner reference + // Do not brake provision because of failed owner reference } } if err := r.Client.Update(ctx, imageRepository); err != nil { log.Error(err, "failed to update CR after provision") return err } else { - log.Info("provisioned image repository and added finalizer") + log.Info("Finished provision of image repository and added finalizer") } imageRepository.Status = status @@ -322,37 +290,68 @@ func (r *ImageRepositoryReconciler) ProvisionImageRepository(ctx context.Context return nil } -// RegenerateImageRepositoryCredentials rotates robot account(s) token and updates corresponding secret(s) -func (r *ImageRepositoryReconciler) RegenerateImageRepositoryCredentials(ctx context.Context, imageRepository *imagerepositoryv1alpha1.ImageRepository) error { - log := ctrllog.FromContext(ctx) +type imageRepositoryAccessData struct { + RobotAccountName string + RemoteSecretName string + SecretName string +} + +// ProvisionImageRepositoryAccess makes existing quay image repository accessible +// by creating robot account and storing its token in a RemoteSecret. +func (r *ImageRepositoryReconciler) ProvisionImageRepositoryAccess(ctx context.Context, imageRepository *imagerepositoryv1alpha1.ImageRepository, isPullOnly bool) (*imageRepositoryAccessData, error) { + log := ctrllog.FromContext(ctx).WithName("ProvisionImageRepositoryAccess").WithValues("IsPullOnly", isPullOnly) + ctx = ctrllog.IntoContext(ctx, log) + imageRepositoryName := imageRepository.Spec.Image.Name quayImageURL := imageRepository.Status.Image.URL - robotAccountName := imageRepository.Status.Credentials.PushRobotAccountName - robotAccount, err := r.QuayClient.RegenerateRobotAccountToken(r.QuayOrganization, robotAccountName) + robotAccountName := generateQuayRobotAccountName(imageRepositoryName, isPullOnly) + robotAccount, err := r.QuayClient.CreateRobotAccount(r.QuayOrganization, robotAccountName) if err != nil { - log.Error(err, "failed to refresh push token") - return err + log.Error(err, "failed to create robot account", "RobotAccountName", robotAccountName, l.Action, l.ActionAdd, l.Audit, "true") + return nil, err } - secretName := strings.ReplaceAll(robotAccountName, "_", "-") - if err := r.EnsureDockerSecret(ctx, imageRepository, robotAccount, secretName, quayImageURL); err != nil { + if robotAccount == nil { + err := fmt.Errorf("unexpected response from Quay: robot account data object is nil") + log.Error(err, "nil robot account") + return nil, err + } + + err = r.QuayClient.AddPermissionsForRepositoryToRobotAccount(r.QuayOrganization, imageRepositoryName, robotAccount.Name, !isPullOnly) + if err != nil { + log.Error(err, "failed to add permissions to robot account", "RobotAccountName", robotAccountName, l.Action, l.ActionUpdate, l.Audit, "true") + return nil, err + } + + remoteSecretName := getRemoteSecretName(imageRepository, isPullOnly) + if err := r.EnsureRemoteSecret(ctx, imageRepository, remoteSecretName, isPullOnly); err != nil { + return nil, err + } + + if err := r.CreateRemoteSecretUploadSecret(ctx, robotAccount, imageRepository.Namespace, remoteSecretName, quayImageURL); err != nil { + return nil, err + } + + data := &imageRepositoryAccessData{ + RobotAccountName: robotAccountName, + RemoteSecretName: remoteSecretName, + SecretName: remoteSecretName, + } + return data, nil +} + +// RegenerateImageRepositoryCredentials rotates robot account(s) token and updates corresponding secret(s) +func (r *ImageRepositoryReconciler) RegenerateImageRepositoryCredentials(ctx context.Context, imageRepository *imagerepositoryv1alpha1.ImageRepository) error { + log := ctrllog.FromContext(ctx) + + if err := r.RegenerateImageRepositoryAccessToken(ctx, imageRepository, false); err != nil { return err } - log.Info("Regenerated push token", "RobotAccountName", robotAccountName) if isComponentLinked(imageRepository) { - pullRobotAccountName := imageRepository.Status.Credentials.PullRobotAccountName - pullRobotAccount, err := r.QuayClient.RegenerateRobotAccountToken(r.QuayOrganization, pullRobotAccountName) - if err != nil { - log.Error(err, "failed to refresh pull token") + if err := r.RegenerateImageRepositoryAccessToken(ctx, imageRepository, true); err != nil { return err } - - remoteSecretName := getRemoteSecretName(imageRepository) - if err := r.CreateRemotePullSecretUploadSecret(ctx, pullRobotAccount, imageRepository.Namespace, remoteSecretName, quayImageURL); err != nil { - return err - } - log.Info("Regenerated pull token", "RobotAccountName", pullRobotAccountName) } imageRepository.Spec.Credentials.RegenerateToken = nil @@ -370,6 +369,39 @@ func (r *ImageRepositoryReconciler) RegenerateImageRepositoryCredentials(ctx con return nil } +// RegenerateImageRepositoryAccessToken rotates robot account token and updates new one to the corresponding Remote Secret. +func (r *ImageRepositoryReconciler) RegenerateImageRepositoryAccessToken(ctx context.Context, imageRepository *imagerepositoryv1alpha1.ImageRepository, isPullOnly bool) error { + log := ctrllog.FromContext(ctx).WithName("RegenerateImageRepositoryAccessToken").WithValues("IsPullOnly", isPullOnly) + ctx = ctrllog.IntoContext(ctx, log) + + quayImageURL := imageRepository.Status.Image.URL + + robotAccountName := imageRepository.Status.Credentials.PushRobotAccountName + if isPullOnly { + robotAccountName = imageRepository.Status.Credentials.PullRobotAccountName + } + robotAccount, err := r.QuayClient.RegenerateRobotAccountToken(r.QuayOrganization, robotAccountName) + if err != nil { + log.Error(err, "failed to refresh robot account token") + return err + } else { + log.Info("Refreshed quay robot account token") + } + + remoteSecretName := imageRepository.Status.Credentials.PushRemoteSecretName + if isPullOnly { + remoteSecretName = imageRepository.Status.Credentials.PullRemoteSecretName + } + if err := r.EnsureRemoteSecret(ctx, imageRepository, remoteSecretName, isPullOnly); err != nil { + return err + } + if err := r.CreateRemoteSecretUploadSecret(ctx, robotAccount, imageRepository.Namespace, remoteSecretName, quayImageURL); err != nil { + return err + } + + return nil +} + // CleanupImageRepository deletes image repository and corresponding robot account(s). func (r *ImageRepositoryReconciler) CleanupImageRepository(ctx context.Context, imageRepository *imagerepositoryv1alpha1.ImageRepository) { log := ctrllog.FromContext(ctx).WithName("RepositoryCleanup") @@ -489,7 +521,7 @@ func (r *ImageRepositoryReconciler) EnsureDockerSecret(ctx context.Context, imag return nil } -func (r *ImageRepositoryReconciler) EnsureRemotePullSecret(ctx context.Context, imageRepository *imagerepositoryv1alpha1.ImageRepository, remoteSecretName string) error { +func (r *ImageRepositoryReconciler) EnsureRemoteSecret(ctx context.Context, imageRepository *imagerepositoryv1alpha1.ImageRepository, remoteSecretName string, isPull bool) error { log := ctrllog.FromContext(ctx).WithValues("RemoteSecretName", remoteSecretName) remoteSecret := &remotesecretv1beta1.RemoteSecret{} @@ -500,13 +532,16 @@ func (r *ImageRepositoryReconciler) EnsureRemotePullSecret(ctx context.Context, return err } + serviceAccountName := buildPipelineServiceAccountName + if isPull { + serviceAccountName = defaultServiceAccountName + } + remoteSecret := &remotesecretv1beta1.RemoteSecret{ ObjectMeta: metav1.ObjectMeta{ Name: remoteSecretName, Namespace: imageRepository.Namespace, Labels: map[string]string{ - ApplicationNameLabelName: imageRepository.Labels[ApplicationNameLabelName], - ComponentNameLabelName: imageRepository.Labels[ComponentNameLabelName], InternalRemoteSecretLabelName: "true", }, }, @@ -518,7 +553,7 @@ func (r *ImageRepositoryReconciler) EnsureRemotePullSecret(ctx context.Context, { ServiceAccount: remotesecretv1beta1.ServiceAccountLink{ Reference: corev1.LocalObjectReference{ - Name: defaultServiceAccountName, + Name: serviceAccountName, }, }, }, @@ -526,6 +561,14 @@ func (r *ImageRepositoryReconciler) EnsureRemotePullSecret(ctx context.Context, }, }, } + + if isPull { + remoteSecret.Labels[ApplicationNameLabelName] = imageRepository.Labels[ApplicationNameLabelName] + remoteSecret.Labels[ComponentNameLabelName] = imageRepository.Labels[ComponentNameLabelName] + } else { + remoteSecret.Spec.Targets = []remotesecretv1beta1.RemoteSecretTarget{{Namespace: imageRepository.Namespace}} + } + if err := controllerutil.SetOwnerReference(imageRepository, remoteSecret, r.Scheme); err != nil { log.Error(err, "failed to set owner for remote secret") return err @@ -534,14 +577,16 @@ func (r *ImageRepositoryReconciler) EnsureRemotePullSecret(ctx context.Context, if err := r.Client.Create(ctx, remoteSecret); err != nil { log.Error(err, "failed to create remote secret", l.Action, l.ActionAdd, l.Audit, "true") return err + } else { + log.Info("Remote Secret created") } } return nil } -// CreateRemotePullSecretUploadSecret propagates credentials from given robot account to corresponding remote secret. -func (r *ImageRepositoryReconciler) CreateRemotePullSecretUploadSecret(ctx context.Context, robotAccount *quay.RobotAccount, namespace, remoteSecretName, imageURL string) error { +// CreateRemoteSecretUploadSecret propagates credentials from given robot account to corresponding remote secret. +func (r *ImageRepositoryReconciler) CreateRemoteSecretUploadSecret(ctx context.Context, robotAccount *quay.RobotAccount, namespace, remoteSecretName, imageURL string) error { uploadSecretName := "upload-secret-" + remoteSecretName log := ctrllog.FromContext(ctx).WithValues("RemoteSecretName", remoteSecretName).WithValues("UploadSecretName", uploadSecretName) @@ -562,6 +607,8 @@ func (r *ImageRepositoryReconciler) CreateRemotePullSecretUploadSecret(ctx conte if err := r.Client.Create(ctx, uploadSecret); err != nil { log.Error(err, "failed to create upload secret", l.Action, l.ActionAdd, l.Audit, "true") return err + } else { + log.Info("Created upload Secret for Remote Secret") } return nil @@ -588,12 +635,15 @@ func generateQuayRobotAccountName(imageRepositoryName string, isPullOnly bool) s return robotAccountName } -func getRemoteSecretName(imageRepository *imagerepositoryv1alpha1.ImageRepository) string { - componentName := imageRepository.Labels[ComponentNameLabelName] - if len(componentName) > 220 { - componentName = componentName[:220] +func getRemoteSecretName(imageRepository *imagerepositoryv1alpha1.ImageRepository, isPullOnly bool) string { + remoteSecretName := imageRepository.Name + if len(remoteSecretName) > 220 { + remoteSecretName = remoteSecretName[:220] + } + if isPullOnly { + remoteSecretName += "-image-pull" } - return componentName + "-image-pull" + return remoteSecretName } func isComponentLinked(imageRepository *imagerepositoryv1alpha1.ImageRepository) bool { diff --git a/controllers/imagerepository_controller_test.go b/controllers/imagerepository_controller_test.go index 9868a79..fc46ef8 100644 --- a/controllers/imagerepository_controller_test.go +++ b/controllers/imagerepository_controller_test.go @@ -44,13 +44,17 @@ var _ = Describe("Image repository controller", func() { pushToken string pullToken string expectedRobotAccountPrefix string - expectedRemoteSecretName string expectedImageName string expectedImage string ) Context("Image repository provision", func() { + BeforeEach(func() { + ResetTestQuayClientToFails() + deleteUploadSecrets(defaultNamespace) + }) + It("should prepare environment", func() { createNamespace(defaultNamespace) @@ -61,8 +65,6 @@ var _ = Describe("Image repository controller", func() { }) It("should provision image repository", func() { - ResetTestQuayClientToFails() - isCreateRepositoryInvoked := false CreateRepositoryFunc = func(repository quay.RepositoryRequest) (*quay.Repository, error) { defer GinkgoRecover() @@ -94,6 +96,9 @@ var _ = Describe("Image repository controller", func() { createImageRepository(imageRepositoryConfig{}) + uploadSecretKey := types.NamespacedName{Name: "upload-secret-" + resourceKey.Name, Namespace: resourceKey.Namespace} + defer deleteSecret(uploadSecretKey) + Eventually(func() bool { return isCreateRepositoryInvoked }, timeout, interval).Should(BeTrue()) Eventually(func() bool { return isCreateRobotAccountInvoked }, timeout, interval).Should(BeTrue()) Eventually(func() bool { return isAddPushPermissionsToRobotAccountInvoked }, timeout, interval).Should(BeTrue()) @@ -109,26 +114,42 @@ var _ = Describe("Image repository controller", func() { Expect(imageRepository.Status.Image.URL).To(Equal(expectedImage)) Expect(imageRepository.Status.Image.Visibility).To(Equal(imagerepositoryv1alpha1.ImageVisibilityPublic)) Expect(imageRepository.Status.Credentials.PushRobotAccountName).To(HavePrefix(expectedRobotAccountPrefix)) - Expect(imageRepository.Status.Credentials.PushSecretName).To(HavePrefix(strings.ReplaceAll(expectedImageName, "/", "-"))) + Expect(imageRepository.Status.Credentials.PushRemoteSecretName).To(Equal(imageRepository.Name)) + Expect(imageRepository.Status.Credentials.PushSecretName).To(Equal(imageRepository.Name)) Expect(imageRepository.Status.Credentials.GenerationTimestamp).ToNot(BeNil()) - secretName := imageRepository.Status.Credentials.PushSecretName - secretKey := types.NamespacedName{Name: secretName, Namespace: defaultNamespace} - secret := waitSecretExist(secretKey) - dockerconfigJson := string(secret.Data[corev1.DockerConfigJsonKey]) + remoteSecretKey := types.NamespacedName{Name: imageRepository.Status.Credentials.PushRemoteSecretName, Namespace: imageRepository.Namespace} + remoteSecret := waitRemoteSecretExist(remoteSecretKey) + Expect(remoteSecret.OwnerReferences).To(HaveLen(1)) + Expect(remoteSecret.OwnerReferences[0].Kind).To(Equal("ImageRepository")) + Expect(remoteSecret.OwnerReferences[0].Name).To(Equal(imageRepository.GetName())) + Expect(remoteSecret.Labels[InternalRemoteSecretLabelName]).To(Equal("true")) + Expect(remoteSecret.Spec.Secret.Name).To(Equal(remoteSecret.Name)) + Expect(remoteSecret.Spec.Secret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) + Expect(remoteSecret.Spec.Secret.LinkedTo).To(HaveLen(1)) + Expect(remoteSecret.Spec.Secret.LinkedTo[0].ServiceAccount.Reference.Name).To(Equal(buildPipelineServiceAccountName)) + Expect(remoteSecret.Spec.Targets).To(HaveLen(1)) + Expect(remoteSecret.Spec.Targets[0].Namespace).To(Equal(imageRepository.Namespace)) + + uploadSecret := waitSecretExist(uploadSecretKey) + Expect(uploadSecret.Labels[remotesecretv1beta1.UploadSecretLabel]).To(Equal("remotesecret")) + Expect(uploadSecret.Annotations[remotesecretv1beta1.RemoteSecretNameAnnotation]).To(Equal(remoteSecret.Name)) + Expect(uploadSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) + uploadSecretDockerconfigJson := string(uploadSecret.Data[corev1.DockerConfigJsonKey]) var authDataJson interface{} - Expect(json.Unmarshal([]byte(dockerconfigJson), &authDataJson)).To(Succeed()) - Expect(dockerconfigJson).To(ContainSubstring(expectedImage)) - authString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(dockerconfigJson)[1]) + Expect(json.Unmarshal([]byte(uploadSecretDockerconfigJson), &authDataJson)).To(Succeed()) + Expect(uploadSecretDockerconfigJson).To(ContainSubstring(expectedImage)) + uploadSecretAuthString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(uploadSecretDockerconfigJson)[1]) Expect(err).To(Succeed()) pushRobotAccountName := imageRepository.Status.Credentials.PushRobotAccountName - Expect(string(authString)).To(Equal(fmt.Sprintf("%s:%s", pushRobotAccountName, pushToken))) + Expect(string(uploadSecretAuthString)).To(Equal(fmt.Sprintf("%s:%s", pushRobotAccountName, pushToken))) }) It("should regenerate token", func() { newToken := "push-token5678" - ResetTestQuayClientToFails() + uploadSecretKey := types.NamespacedName{Name: "upload-secret-" + resourceKey.Name, Namespace: resourceKey.Namespace} + // Wait just for case it takes less than a second to regenerate credentials time.Sleep(time.Second) @@ -146,6 +167,7 @@ var _ = Describe("Image repository controller", func() { regenerateToken := true imageRepository.Spec.Credentials = &imagerepositoryv1alpha1.ImageCredentials{RegenerateToken: ®enerateToken} Expect(k8sClient.Update(ctx, imageRepository)).To(Succeed()) + defer deleteSecret(uploadSecretKey) Eventually(func() bool { return isRegenerateRobotAccountTokenInvoked }, timeout, interval).Should(BeTrue()) Eventually(func() bool { @@ -155,23 +177,23 @@ var _ = Describe("Image repository controller", func() { *imageRepository.Status.Credentials.GenerationTimestamp != oldTokenGenerationTimestamp }, timeout, interval).Should(BeTrue()) - secret := &corev1.Secret{} - secretName := imageRepository.Status.Credentials.PushSecretName - secretKey := types.NamespacedName{Name: secretName, Namespace: defaultNamespace} - Expect(k8sClient.Get(ctx, secretKey, secret)).To(Succeed()) - dockerconfigJson := string(secret.Data[corev1.DockerConfigJsonKey]) + remoteSecret := waitRemoteSecretExist(resourceKey) + + uploadSecret := waitSecretExist(uploadSecretKey) + Expect(uploadSecret.Labels[remotesecretv1beta1.UploadSecretLabel]).To(Equal("remotesecret")) + Expect(uploadSecret.Annotations[remotesecretv1beta1.RemoteSecretNameAnnotation]).To(Equal(remoteSecret.Name)) + Expect(uploadSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) + uploadSecretDockerconfigJson := string(uploadSecret.Data[corev1.DockerConfigJsonKey]) var authDataJson interface{} - Expect(json.Unmarshal([]byte(dockerconfigJson), &authDataJson)).To(Succeed()) - Expect(dockerconfigJson).To(ContainSubstring(expectedImage)) - authString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(dockerconfigJson)[1]) + Expect(json.Unmarshal([]byte(uploadSecretDockerconfigJson), &authDataJson)).To(Succeed()) + Expect(uploadSecretDockerconfigJson).To(ContainSubstring(expectedImage)) + uploadSecretAuthString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(uploadSecretDockerconfigJson)[1]) Expect(err).To(Succeed()) - Expect(string(authString)).To(HavePrefix(expectedRobotAccountPrefix)) - Expect(string(authString)).To(HaveSuffix(newToken)) + pushRobotAccountName := imageRepository.Status.Credentials.PushRobotAccountName + Expect(string(uploadSecretAuthString)).To(Equal(fmt.Sprintf("%s:%s", pushRobotAccountName, newToken))) }) It("should update image visibility", func() { - ResetTestQuayClientToFails() - isChangeRepositoryVisibilityInvoked := false ChangeRepositoryVisibilityFunc = func(organization, imageRepository, visibility string) error { defer GinkgoRecover() @@ -196,8 +218,6 @@ var _ = Describe("Image repository controller", func() { }) It("should revert image name if edited", func() { - ResetTestQuayClientToFails() - imageRepository := getImageRepository(resourceKey) imageRepository.Spec.Image.Name = "renamed" Expect(k8sClient.Update(ctx, imageRepository)).To(Succeed()) @@ -209,8 +229,6 @@ var _ = Describe("Image repository controller", func() { }) It("should cleanup repository", func() { - ResetTestQuayClientToFails() - isDeleteRobotAccountInvoked := false DeleteRobotAccountFunc = func(organization, robotAccountName string) (bool, error) { defer GinkgoRecover() @@ -237,20 +255,22 @@ var _ = Describe("Image repository controller", func() { Context("Image repository for component provision", func() { + BeforeEach(func() { + ResetTestQuayClientToFails() + deleteUploadSecrets(defaultNamespace) + }) + It("should prepare environment", func() { pushToken = "push-token1234" pullToken = "pull-token1234" expectedImageName = fmt.Sprintf("%s/%s/%s", defaultNamespace, defaultComponentApplication, defaultComponentName) expectedImage = fmt.Sprintf("quay.io/%s/%s", testQuayOrg, expectedImageName) expectedRobotAccountPrefix = strings.ReplaceAll(strings.ReplaceAll(expectedImageName, "-", "_"), "/", "_") - expectedRemoteSecretName = defaultComponentName + "-image-pull" createComponent(componentConfig{}) }) It("should provision image repository for component", func() { - ResetTestQuayClientToFails() - isCreateRepositoryInvoked := false CreateRepositoryFunc = func(repository quay.RepositoryRequest) (*quay.Repository, error) { defer GinkgoRecover() @@ -298,6 +318,11 @@ var _ = Describe("Image repository controller", func() { }, }) + pushUploadSecretKey := types.NamespacedName{Name: "upload-secret-" + resourceKey.Name, Namespace: resourceKey.Namespace} + pullUploadSecretKey := types.NamespacedName{Name: "upload-secret-" + resourceKey.Name + "-image-pull", Namespace: resourceKey.Namespace} + defer deleteSecret(pushUploadSecretKey) + defer deleteSecret(pullUploadSecretKey) + Eventually(func() bool { return isCreateRepositoryInvoked }, timeout, interval).Should(BeTrue()) Eventually(func() bool { return isCreatePushRobotAccountInvoked }, timeout, interval).Should(BeTrue()) Eventually(func() bool { return isCreatePullRobotAccountInvoked }, timeout, interval).Should(BeTrue()) @@ -318,58 +343,77 @@ var _ = Describe("Image repository controller", func() { Expect(imageRepository.Status.Image.URL).To(Equal(expectedImage)) Expect(imageRepository.Status.Image.Visibility).To(Equal(imagerepositoryv1alpha1.ImageVisibilityPublic)) Expect(imageRepository.Status.Credentials.PushRobotAccountName).To(HavePrefix(expectedRobotAccountPrefix)) - Expect(imageRepository.Status.Credentials.PushSecretName).To(HavePrefix(strings.ReplaceAll(expectedImageName, "/", "-"))) + Expect(imageRepository.Status.Credentials.PushRemoteSecretName).To(Equal(imageRepository.Name)) + Expect(imageRepository.Status.Credentials.PushSecretName).To(Equal(imageRepository.Name)) Expect(imageRepository.Status.Credentials.PullRobotAccountName).To(HavePrefix(expectedRobotAccountPrefix)) Expect(imageRepository.Status.Credentials.PullRobotAccountName).To(HaveSuffix("_pull")) - Expect(imageRepository.Status.Credentials.PullSecretName).To(Equal(expectedRemoteSecretName)) + Expect(imageRepository.Status.Credentials.PullRemoteSecretName).To(Equal(imageRepository.Name + "-image-pull")) + Expect(imageRepository.Status.Credentials.PullSecretName).To(Equal(imageRepository.Name + "-image-pull")) Expect(imageRepository.Status.Credentials.GenerationTimestamp).ToNot(BeNil()) + pushRemoteSecretKey := types.NamespacedName{Name: imageRepository.Status.Credentials.PushRemoteSecretName, Namespace: imageRepository.Namespace} + pushRemoteSecret := waitRemoteSecretExist(pushRemoteSecretKey) + Expect(pushRemoteSecret.Labels[InternalRemoteSecretLabelName]).To(Equal("true")) + Expect(pushRemoteSecret.OwnerReferences).To(HaveLen(1)) + Expect(pushRemoteSecret.OwnerReferences[0].Kind).To(Equal("ImageRepository")) + Expect(pushRemoteSecret.OwnerReferences[0].Name).To(Equal(imageRepository.GetName())) + Expect(pushRemoteSecret.Spec.Secret.Name).To(Equal(pushRemoteSecret.Name)) + Expect(pushRemoteSecret.Spec.Secret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) + Expect(pushRemoteSecret.Spec.Secret.LinkedTo).To(HaveLen(1)) + Expect(pushRemoteSecret.Spec.Secret.LinkedTo[0].ServiceAccount.Reference.Name).To(Equal(buildPipelineServiceAccountName)) + Expect(pushRemoteSecret.Spec.Targets).To(HaveLen(1)) + Expect(pushRemoteSecret.Spec.Targets[0].Namespace).To(Equal(imageRepository.Namespace)) + + pullRemoteSecretKey := types.NamespacedName{Name: imageRepository.Status.Credentials.PullRemoteSecretName, Namespace: imageRepository.Namespace} + pullRemoteSecret := waitRemoteSecretExist(pullRemoteSecretKey) + Expect(pullRemoteSecret.Labels[ApplicationNameLabelName]).To(Equal(defaultComponentApplication)) + Expect(pullRemoteSecret.Labels[ComponentNameLabelName]).To(Equal(defaultComponentName)) + Expect(pullRemoteSecret.Labels[InternalRemoteSecretLabelName]).To(Equal("true")) + Expect(pullRemoteSecret.OwnerReferences).To(HaveLen(1)) + Expect(pullRemoteSecret.OwnerReferences[0].Name).To(Equal(imageRepository.Name)) + Expect(pullRemoteSecret.OwnerReferences[0].Kind).To(Equal("ImageRepository")) + Expect(pullRemoteSecret.Spec.Secret.Name).To(Equal(pullRemoteSecretKey.Name)) + Expect(pullRemoteSecret.Spec.Secret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) + Expect(pullRemoteSecret.Spec.Secret.LinkedTo).To(HaveLen(1)) + Expect(pullRemoteSecret.Spec.Secret.LinkedTo[0].ServiceAccount.Reference.Name).To(Equal(defaultServiceAccountName)) + Expect(pullRemoteSecret.Spec.Targets).To(HaveLen(0)) + var authDataJson interface{} - secretName := imageRepository.Status.Credentials.PushSecretName - secretKey := types.NamespacedName{Name: secretName, Namespace: defaultNamespace} - secret := waitSecretExist(secretKey) - dockerconfigJson := string(secret.Data[corev1.DockerConfigJsonKey]) - Expect(json.Unmarshal([]byte(dockerconfigJson), &authDataJson)).To(Succeed()) - Expect(dockerconfigJson).To(ContainSubstring(expectedImage)) - authString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(dockerconfigJson)[1]) + + pushUploadSecret := waitSecretExist(pushUploadSecretKey) + Expect(pushUploadSecret.Labels[remotesecretv1beta1.UploadSecretLabel]).To(Equal("remotesecret")) + Expect(pushUploadSecret.Annotations[remotesecretv1beta1.RemoteSecretNameAnnotation]).To(Equal(pushRemoteSecret.Name)) + Expect(pushUploadSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) + pushUploadSecretDockerconfigJson := string(pushUploadSecret.Data[corev1.DockerConfigJsonKey]) + Expect(json.Unmarshal([]byte(pushUploadSecretDockerconfigJson), &authDataJson)).To(Succeed()) + Expect(pushUploadSecretDockerconfigJson).To(ContainSubstring(expectedImage)) + pushUploadSecretAuthString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(pushUploadSecretDockerconfigJson)[1]) Expect(err).To(Succeed()) pushRobotAccountName := imageRepository.Status.Credentials.PushRobotAccountName - Expect(string(authString)).To(Equal(fmt.Sprintf("%s:%s", pushRobotAccountName, pushToken))) - - remoteSecretKey := types.NamespacedName{Name: expectedRemoteSecretName, Namespace: defaultNamespace} - remoteSecret := waitRemoteSecretExist(remoteSecretKey) - Expect(remoteSecret.Labels[ApplicationNameLabelName]).To(Equal(defaultComponentApplication)) - Expect(remoteSecret.Labels[ComponentNameLabelName]).To(Equal(defaultComponentName)) - Expect(remoteSecret.Labels[InternalRemoteSecretLabelName]).To(Equal("true")) - Expect(remoteSecret.OwnerReferences).To(HaveLen(1)) - Expect(remoteSecret.OwnerReferences[0].Name).To(Equal(imageRepository.Name)) - Expect(remoteSecret.OwnerReferences[0].Kind).To(Equal("ImageRepository")) - Expect(remoteSecret.Spec.Secret.Name).To(Equal(remoteSecretKey.Name)) - Expect(remoteSecret.Spec.Secret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) - Expect(remoteSecret.Spec.Secret.LinkedTo).To(HaveLen(1)) - Expect(remoteSecret.Spec.Secret.LinkedTo[0].ServiceAccount.Reference.Name).To(Equal(defaultServiceAccountName)) - - uploadSecretKey := types.NamespacedName{Name: "upload-secret-" + expectedRemoteSecretName, Namespace: defaultNamespace} - uploadSecret := waitSecretExist(uploadSecretKey) - Expect(uploadSecret.Labels[remotesecretv1beta1.UploadSecretLabel]).To(Equal("remotesecret")) - Expect(uploadSecret.Annotations[remotesecretv1beta1.RemoteSecretNameAnnotation]).To(Equal(expectedRemoteSecretName)) - Expect(uploadSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) - uploadSecretDockerconfigJson := string(uploadSecret.Data[corev1.DockerConfigJsonKey]) - Expect(json.Unmarshal([]byte(uploadSecretDockerconfigJson), &authDataJson)).To(Succeed()) - Expect(uploadSecretDockerconfigJson).To(ContainSubstring(expectedImage)) - uploadSecretAuthString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(uploadSecretDockerconfigJson)[1]) + Expect(string(pushUploadSecretAuthString)).To(Equal(fmt.Sprintf("%s:%s", pushRobotAccountName, pushToken))) + + pullUploadSecret := waitSecretExist(pullUploadSecretKey) + Expect(pullUploadSecret.Labels[remotesecretv1beta1.UploadSecretLabel]).To(Equal("remotesecret")) + Expect(pullUploadSecret.Annotations[remotesecretv1beta1.RemoteSecretNameAnnotation]).To(Equal(pullRemoteSecret.Name)) + Expect(pullUploadSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) + pullUploadSecretDockerconfigJson := string(pullUploadSecret.Data[corev1.DockerConfigJsonKey]) + Expect(json.Unmarshal([]byte(pullUploadSecretDockerconfigJson), &authDataJson)).To(Succeed()) + Expect(pullUploadSecretDockerconfigJson).To(ContainSubstring(expectedImage)) + pullUploadSecretAuthString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(pullUploadSecretDockerconfigJson)[1]) Expect(err).To(Succeed()) pullRobotAccountName := imageRepository.Status.Credentials.PullRobotAccountName - Expect(string(uploadSecretAuthString)).To(Equal(fmt.Sprintf("%s:%s", pullRobotAccountName, pullToken))) - - deleteSecret(uploadSecretKey) + Expect(string(pullUploadSecretAuthString)).To(Equal(fmt.Sprintf("%s:%s", pullRobotAccountName, pullToken))) }) It("should regenerate tokens and update remote secret", func() { newPushToken := "push-token5678" newPullToken := "pull-token5678" - ResetTestQuayClientToFails() + pushUploadSecretKey := types.NamespacedName{Name: "upload-secret-" + resourceKey.Name, Namespace: resourceKey.Namespace} + pullUploadSecretKey := types.NamespacedName{Name: "upload-secret-" + resourceKey.Name + "-image-pull", Namespace: resourceKey.Namespace} + defer deleteSecret(pushUploadSecretKey) + defer deleteSecret(pullUploadSecretKey) + // Wait just for case it takes less than a second to regenerate credentials time.Sleep(time.Second) @@ -402,38 +446,40 @@ var _ = Describe("Image repository controller", func() { *imageRepository.Status.Credentials.GenerationTimestamp != oldTokenGenerationTimestamp }, timeout, interval).Should(BeTrue()) - secret := &corev1.Secret{} - secretName := imageRepository.Status.Credentials.PushSecretName - secretKey := types.NamespacedName{Name: secretName, Namespace: defaultNamespace} - Expect(k8sClient.Get(ctx, secretKey, secret)).To(Succeed()) - dockerconfigJson := string(secret.Data[corev1.DockerConfigJsonKey]) + pushRemoteSecretKey := types.NamespacedName{Name: imageRepository.Status.Credentials.PushRemoteSecretName, Namespace: imageRepository.Namespace} + pushRemoteSecret := waitRemoteSecretExist(pushRemoteSecretKey) + + pullRemoteSecretKey := types.NamespacedName{Name: imageRepository.Status.Credentials.PullRemoteSecretName, Namespace: imageRepository.Namespace} + pullRemoteSecret := waitRemoteSecretExist(pullRemoteSecretKey) + var authDataJson interface{} - Expect(json.Unmarshal([]byte(dockerconfigJson), &authDataJson)).To(Succeed()) - Expect(dockerconfigJson).To(ContainSubstring(expectedImage)) - authString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(dockerconfigJson)[1]) + + pushUploadSecret := waitSecretExist(pushUploadSecretKey) + Expect(pushUploadSecret.Labels[remotesecretv1beta1.UploadSecretLabel]).To(Equal("remotesecret")) + Expect(pushUploadSecret.Annotations[remotesecretv1beta1.RemoteSecretNameAnnotation]).To(Equal(pushRemoteSecret.Name)) + Expect(pushUploadSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) + pushUploadSecretDockerconfigJson := string(pushUploadSecret.Data[corev1.DockerConfigJsonKey]) + Expect(json.Unmarshal([]byte(pushUploadSecretDockerconfigJson), &authDataJson)).To(Succeed()) + Expect(pushUploadSecretDockerconfigJson).To(ContainSubstring(expectedImage)) + pushUploadSecretAuthString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(pushUploadSecretDockerconfigJson)[1]) Expect(err).To(Succeed()) pushRobotAccountName := imageRepository.Status.Credentials.PushRobotAccountName - Expect(string(authString)).To(Equal(fmt.Sprintf("%s:%s", pushRobotAccountName, newPushToken))) - - uploadSecretKey := types.NamespacedName{Name: "upload-secret-" + expectedRemoteSecretName, Namespace: defaultNamespace} - uploadSecret := waitSecretExist(uploadSecretKey) - Expect(uploadSecret.Labels[remotesecretv1beta1.UploadSecretLabel]).To(Equal("remotesecret")) - Expect(uploadSecret.Annotations[remotesecretv1beta1.RemoteSecretNameAnnotation]).To(Equal(expectedRemoteSecretName)) - Expect(uploadSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) - uploadSecretDockerconfigJson := string(uploadSecret.Data[corev1.DockerConfigJsonKey]) - Expect(json.Unmarshal([]byte(uploadSecretDockerconfigJson), &authDataJson)).To(Succeed()) - Expect(uploadSecretDockerconfigJson).To(ContainSubstring(expectedImage)) - uploadSecretAuthString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(uploadSecretDockerconfigJson)[1]) + Expect(string(pushUploadSecretAuthString)).To(Equal(fmt.Sprintf("%s:%s", pushRobotAccountName, newPushToken))) + + pullUploadSecret := waitSecretExist(pullUploadSecretKey) + Expect(pullUploadSecret.Labels[remotesecretv1beta1.UploadSecretLabel]).To(Equal("remotesecret")) + Expect(pullUploadSecret.Annotations[remotesecretv1beta1.RemoteSecretNameAnnotation]).To(Equal(pullRemoteSecret.Name)) + Expect(pullUploadSecret.Type).To(Equal(corev1.SecretTypeDockerConfigJson)) + pullUploadSecretDockerconfigJson := string(pullUploadSecret.Data[corev1.DockerConfigJsonKey]) + Expect(json.Unmarshal([]byte(pullUploadSecretDockerconfigJson), &authDataJson)).To(Succeed()) + Expect(pullUploadSecretDockerconfigJson).To(ContainSubstring(expectedImage)) + pullUploadSecretAuthString, err := base64.StdEncoding.DecodeString(authRegexp.FindStringSubmatch(pullUploadSecretDockerconfigJson)[1]) Expect(err).To(Succeed()) pullRobotAccountName := imageRepository.Status.Credentials.PullRobotAccountName - Expect(string(uploadSecretAuthString)).To(Equal(fmt.Sprintf("%s:%s", pullRobotAccountName, newPullToken))) - - deleteSecret(uploadSecretKey) + Expect(string(pullUploadSecretAuthString)).To(Equal(fmt.Sprintf("%s:%s", pullRobotAccountName, newPullToken))) }) It("should cleanup component repository", func() { - ResetTestQuayClientToFails() - isDeleteRobotAccountForPushInvoked := false isDeleteRobotAccountForPullInvoked := false DeleteRobotAccountFunc = func(organization, robotAccountName string) (bool, error) { @@ -469,12 +515,16 @@ var _ = Describe("Image repository controller", func() { Context("Other image repository scenarios", func() { + BeforeEach(func() { + ResetTestQuayClient() + deleteImageRepository(resourceKey) + deleteUploadSecrets(defaultNamespace) + }) + It("should create image repository with requested name", func() { customImageName := "my-image" expectedImageName = defaultNamespace + "/" + customImageName - ResetTestQuayClient() - isCreateRepositoryInvoked := false CreateRepositoryFunc = func(repository quay.RepositoryRequest) (*quay.Repository, error) { defer GinkgoRecover() @@ -500,6 +550,12 @@ var _ = Describe("Image repository controller", func() { Context("Image repository error scenarios", func() { + BeforeEach(func() { + ResetTestQuayClient() + deleteImageRepository(resourceKey) + deleteUploadSecrets(defaultNamespace) + }) + It("should prepare environment", func() { pushToken = "push-token1234" expectedImageName = fmt.Sprintf("%s/%s", defaultNamespace, defaultImageRepositoryName) @@ -508,8 +564,6 @@ var _ = Describe("Image repository controller", func() { }) It("should permanently fail if private image repository requested on creation but quota exceeded", func() { - ResetTestQuayClient() - isCreateRepositoryInvoked := false CreateRepositoryFunc = func(repository quay.RepositoryRequest) (*quay.Repository, error) { defer GinkgoRecover() @@ -538,7 +592,6 @@ var _ = Describe("Image repository controller", func() { }) It("should add error message and revert visibility in spec if private visibility requested after provision but quota exceeded", func() { - deleteImageRepository(resourceKey) ResetTestQuayClient() CreateRepositoryFunc = func(repository quay.RepositoryRequest) (*quay.Repository, error) { @@ -548,6 +601,8 @@ var _ = Describe("Image repository controller", func() { return &quay.RobotAccount{Name: robotName, Token: pushToken}, nil } createImageRepository(imageRepositoryConfig{}) + defer deleteImageRepository(resourceKey) + waitImageRepositoryFinalizerOnImageRepository(resourceKey) ResetTestQuayClientToFails() @@ -598,9 +653,6 @@ var _ = Describe("Image repository controller", func() { }) It("should fail if invalid image repository name given", func() { - deleteImageRepository(resourceKey) - ResetTestQuayClient() - imageRepository := getImageRepositoryConfig(imageRepositoryConfig{ ImageName: "wrong&name", }) diff --git a/controllers/imagerepository_controller_unit_test.go b/controllers/imagerepository_controller_unit_test.go index 07aa031..54763a0 100644 --- a/controllers/imagerepository_controller_unit_test.go +++ b/controllers/imagerepository_controller_unit_test.go @@ -80,23 +80,38 @@ func TestGenerateQuayRobotAccountName(t *testing.T) { } func TestGetRemoteSecretName(t *testing.T) { - longComponentName := getRandomString(300) - expectedRemoteSecretLongPrefix := longComponentName[0:220] + longImageRepositoryCrName := getRandomString(300) + expectedRemoteSecretLongPrefix := longImageRepositoryCrName[0:220] testCases := []struct { - name string - componentName string - expectedRemoteSecretNamePrefix string + name string + imageRepositoryCrName string + IsPullOnly bool + expectedRemoteSecretName string }{ { - name: "Should generate remote secret name", - componentName: "my-component-name", - expectedRemoteSecretNamePrefix: "my-component-name", + name: "Should generate push remote secret name", + imageRepositoryCrName: "my-image-repo", + IsPullOnly: false, + expectedRemoteSecretName: "my-image-repo", + }, + { + name: "Should generate push remote secret name if component name is too long", + imageRepositoryCrName: longImageRepositoryCrName, + IsPullOnly: false, + expectedRemoteSecretName: expectedRemoteSecretLongPrefix, + }, + { + name: "Should generate pull remote secret name", + imageRepositoryCrName: "my-image-repo", + IsPullOnly: true, + expectedRemoteSecretName: "my-image-repo-image-pull", }, { - name: "Should generate remote secret name if component name is too long", - componentName: longComponentName, - expectedRemoteSecretNamePrefix: expectedRemoteSecretLongPrefix, + name: "Should generate pull remote secret name if component name is too long", + imageRepositoryCrName: longImageRepositoryCrName, + IsPullOnly: true, + expectedRemoteSecretName: expectedRemoteSecretLongPrefix + "-image-pull", }, } @@ -104,20 +119,17 @@ func TestGetRemoteSecretName(t *testing.T) { t.Run(tc.name, func(t *testing.T) { imageRepository := &imagerepositoryv1alpha1.ImageRepository{ ObjectMeta: v1.ObjectMeta{ - Labels: map[string]string{ - ComponentNameLabelName: tc.componentName, - }, + Name: tc.imageRepositoryCrName, }, } - expectedRemoteSecretName := tc.expectedRemoteSecretNamePrefix + "-image-pull" - remoteSecretName := getRemoteSecretName(imageRepository) + remoteSecretName := getRemoteSecretName(imageRepository, tc.IsPullOnly) if len(remoteSecretName) > 253 { t.Error("remote secret name is longer than allowed") } - if remoteSecretName != expectedRemoteSecretName { - t.Errorf("Expected remote secret name %s, but got %s", expectedRemoteSecretName, remoteSecretName) + if remoteSecretName != tc.expectedRemoteSecretName { + t.Errorf("Expected remote secret name %s, but got %s", tc.expectedRemoteSecretName, remoteSecretName) } }) } diff --git a/controllers/suite_util_test.go b/controllers/suite_util_test.go index e914bdf..8034a89 100644 --- a/controllers/suite_util_test.go +++ b/controllers/suite_util_test.go @@ -21,11 +21,14 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" corev1 "k8s.io/api/core/v1" k8sErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" "k8s.io/apimachinery/pkg/types" appstudioapiv1alpha1 "github.com/redhat-appstudio/application-api/api/v1alpha1" @@ -360,6 +363,20 @@ func deleteSecret(resourceKey types.NamespacedName) { }, timeout, interval).Should(BeTrue()) } +func deleteUploadSecrets(namesapce string) { + uploadSecretRequirement, err := labels.NewRequirement(remotesecretv1beta1.UploadSecretLabel, selection.Equals, []string{"remotesecret"}) + Expect(err).ToNot(HaveOccurred()) + uploadSecretsSelector := labels.NewSelector().Add(*uploadSecretRequirement) + uploadSecretsListOptions := client.ListOptions{ + LabelSelector: uploadSecretsSelector, + Namespace: namesapce, + } + deleteOptions := &client.DeleteAllOfOptions{ + ListOptions: uploadSecretsListOptions, + } + Expect(k8sClient.DeleteAllOf(ctx, &corev1.Secret{}, deleteOptions)).To(Succeed()) +} + func waitRemoteSecretExist(remoteSecretKey types.NamespacedName) *remotesecretv1beta1.RemoteSecret { remoteSecret := &remotesecretv1beta1.RemoteSecret{} Eventually(func() bool {