diff --git a/config/manager/nephe-controller.yaml b/config/manager/nephe-controller.yaml index 8e1c7c8b..bb868461 100644 --- a/config/manager/nephe-controller.yaml +++ b/config/manager/nephe-controller.yaml @@ -55,6 +55,11 @@ spec: image: "projects.registry.vmware.com/antrea/nephe:latest" imagePullPolicy: IfNotPresent name: nephe-controller + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace resources: limits: cpu: 1000m diff --git a/config/nephe.yml b/config/nephe.yml index 94b0bcb5..3ba520b2 100644 --- a/config/nephe.yml +++ b/config/nephe.yml @@ -647,6 +647,11 @@ spec: - --enable-debug-log command: - /nephe-controller + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace image: projects.registry.vmware.com/antrea/nephe:latest imagePullPolicy: IfNotPresent name: nephe-controller @@ -844,7 +849,6 @@ webhooks: apiVersions: - v1 operations: - - UPDATE - DELETE resources: - secrets diff --git a/config/webhook/manifests-new.yaml b/config/webhook/manifests-new.yaml index 75223dd9..67fca309 100644 --- a/config/webhook/manifests-new.yaml +++ b/config/webhook/manifests-new.yaml @@ -107,7 +107,6 @@ webhooks: apiVersions: - v1 operations: - - UPDATE - DELETE resources: - secrets diff --git a/pkg/apiserver/webhook/secret_webhook.go b/pkg/apiserver/webhook/secret_webhook.go index 4c52c3f3..2e519c2e 100644 --- a/pkg/apiserver/webhook/secret_webhook.go +++ b/pkg/apiserver/webhook/secret_webhook.go @@ -44,9 +44,9 @@ func (v *SecretValidator) Handle(ctx context.Context, req admission.Request) adm v.Log.V(1).Info("Received admission webhook request", "Name", req.Name, "Operation", req.Operation) switch req.Operation { case admissionv1.Create: - return v.validateCreate(req) + return v.validateCreate() case admissionv1.Update: - return v.validateUpdate(req) + return v.validateUpdate() case admissionv1.Delete: return v.validateDelete(req) default: @@ -60,14 +60,6 @@ func (v *SecretValidator) InjectDecoder(d *admission.Decoder) error { //nolint:u return nil } -// allowSecretUpdate returns true only when Secret data key is unchanged. -func (v *SecretValidator) allowSecretUpdate(new *corev1.Secret, old *corev1.Secret, key string) bool { - if changed := string(new.Data[key]) != string(old.Data[key]); changed { - return false - } - return true -} - // getCPABySecret returns nil only when the Secret is not used by any CloudProvideAccount CR, // otherwise the dependent CloudProvideAccount CR will be returned. func (v *SecretValidator) getCPABySecret(s *corev1.Secret) (error, *crdv1alpha1.CloudProviderAccount) { @@ -96,44 +88,12 @@ func (v *SecretValidator) getCPABySecret(s *corev1.Secret) (error, *crdv1alpha1. } // validateCreate does not deny Secret creation. -func (v *SecretValidator) validateCreate(req admission.Request) admission.Response { // nolint: unparam +func (v *SecretValidator) validateCreate() admission.Response { // nolint: unparam return admission.Allowed("") } // validateUpdate denies Secret update, if the Secret key is referred in a CloudProviderAccount. -func (v *SecretValidator) validateUpdate(req admission.Request) admission.Response { - newSecret := &corev1.Secret{} - err := v.decoder.Decode(req, newSecret) - if err != nil { - v.Log.Error(err, "Failed to decode Secret", "SecretValidator", req.Name) - return admission.Errored(http.StatusBadRequest, err) - } - oldSecret := &corev1.Secret{} - if req.OldObject.Raw != nil { - if err := json.Unmarshal(req.OldObject.Raw, &oldSecret); err != nil { - v.Log.Error(err, "Failed to decode old Secret", "SecretValidator", req.Name) - return admission.Errored(http.StatusBadRequest, err) - } - } - - err, cpa := v.getCPABySecret(oldSecret) - if err != nil { - return admission.Denied(err.Error()) - } - if cpa != nil { - var key string - if cpa.Spec.AWSConfig != nil { - key = cpa.Spec.AWSConfig.SecretRef.Key - } else if cpa.Spec.AzureConfig != nil { - key = cpa.Spec.AzureConfig.SecretRef.Key - } - if ok := v.allowSecretUpdate(newSecret, oldSecret, key); !ok { - v.Log.Error(nil, "The Secret is referred by a CloudProviderAccount. Cannot modify it,", - "Secret", oldSecret.Name, "CloudProviderAccount", cpa.Name) - return admission.Denied(fmt.Sprintf("the Secret %v is referred by a "+ - "CloudProviderAccount %s. The %s field 'value' cannot be changed", oldSecret.Name, cpa.Name, key)) - } - } +func (v *SecretValidator) validateUpdate() admission.Response { return admission.Allowed("") } diff --git a/pkg/apiserver/webhook/secret_webhook_test.go b/pkg/apiserver/webhook/secret_webhook_test.go index 1c00602b..211abfac 100644 --- a/pkg/apiserver/webhook/secret_webhook_test.go +++ b/pkg/apiserver/webhook/secret_webhook_test.go @@ -45,7 +45,6 @@ var _ = Describe("Webhook", func() { credential = `{"accessKeyId": "keyId","accessKeySecret": "keySecret"}` credentials = "credentials" invalidReqErrorMsg = "invalid admission webhook request" - decoderErrorMsg = "there is no content to decode" account *v1alpha1.CloudProviderAccount s1 *corev1.Secret encodedS1 []byte @@ -167,462 +166,12 @@ var _ = Describe("Webhook", func() { _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) Expect(response.Allowed).To(BeFalse()) }) - It("Validate Secret credentials update with dependent AWS CPA", func() { - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) - err = fakeClient.Create(context.Background(), s1) - Expect(err).Should(BeNil()) - var pollIntv uint = 1 - account = &v1alpha1.CloudProviderAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: testAccountNamespacedName.Name, - Namespace: testAccountNamespacedName.Namespace, - }, - Spec: v1alpha1.CloudProviderAccountSpec{ - PollIntervalInSeconds: &pollIntv, - AWSConfig: &v1alpha1.CloudProviderAccountAWSConfig{ - Region: "us-east-1", - SecretRef: &v1alpha1.SecretReference{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Key: credentials, - }, - }, - }, - } - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating CloudProviderAccount [%s, %s] with SecretRef [%s, %s]\n", - account.Name, account.Namespace, testSecretNamespacedName1.Name, - testSecretNamespacedName1.Namespace))) - err = fakeClient.Create(context.Background(), account) - Expect(err).Should(BeNil()) - newCredential := `{"accessKeyId": "keyId","accessKeySecret": "keySecret1"}` - newS1 := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - }, - Data: map[string][]byte{ - credentials: []byte(newCredential), - }, - } - encodedNewS1, _ := json.Marshal(newS1) - newS1Req := admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{ - Group: "", - Version: "corev1", - Kind: "Secret", - }, - Resource: metav1.GroupVersionResource{ - Group: "", - Version: "corev1", - Resource: "Secrets", - }, - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Operation: v1.Update, - Object: runtime.RawExtension{ - Raw: encodedNewS1, - }, - OldObject: runtime.RawExtension{ - Raw: encodedS1, - }, - }, - } - SecretValidatorTest1 := &SecretValidator{ - Client: fakeClient, - Log: logging.GetLogger("webhook").WithName("Secret")} - err = SecretValidatorTest1.InjectDecoder(decoder) - Expect(err).Should(BeNil()) - response := SecretValidatorTest1.Handle(context.Background(), newS1Req) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) - Expect(response.Allowed).To(BeFalse()) - }) - It("Validate Secret labels update with dependent AWS CPA", func() { - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) - err = fakeClient.Create(context.Background(), s1) - Expect(err).Should(BeNil()) - var pollIntv uint = 1 - account = &v1alpha1.CloudProviderAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: testAccountNamespacedName.Name, - Namespace: testAccountNamespacedName.Namespace, - }, - Spec: v1alpha1.CloudProviderAccountSpec{ - PollIntervalInSeconds: &pollIntv, - AWSConfig: &v1alpha1.CloudProviderAccountAWSConfig{ - Region: "us-east-1", - SecretRef: &v1alpha1.SecretReference{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Key: credentials, - }, - }, - }, - } - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating CloudProviderAccount [%s, %s] with SecretRef [%s, %s]\n", - account.Name, account.Namespace, testSecretNamespacedName1.Name, - testSecretNamespacedName1.Namespace))) - err = fakeClient.Create(context.Background(), account) - Expect(err).Should(BeNil()) - newLabel := make(map[string]string) - newLabel["test"] = "testLabel" - newS1 := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Labels: newLabel, - }, - Data: map[string][]byte{ - credentials: []byte(credential), - }, - } - encodedNewS1, _ := json.Marshal(newS1) - newS1Req := admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{ - Group: "", - Version: "corev1", - Kind: "Secret", - }, - Resource: metav1.GroupVersionResource{ - Group: "", - Version: "corev1", - Resource: "Secrets", - }, - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Operation: v1.Update, - Object: runtime.RawExtension{ - Raw: encodedNewS1, - }, - OldObject: runtime.RawExtension{ - Raw: encodedS1, - }, - }, - } - SecretValidatorTest1 := &SecretValidator{ - Client: fakeClient, - Log: logging.GetLogger("webhook").WithName("Secret")} - err = SecretValidatorTest1.InjectDecoder(decoder) - Expect(err).Should(BeNil()) - response := SecretValidatorTest1.Handle(context.Background(), newS1Req) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) - Expect(response.Allowed).To(BeTrue()) - }) - It("Validate Secret delete without dependent AWS CPA", func() { - credential := `{"accessKeyId": "keyId","accessKeySecret": "keySecret"}` - s1 = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName2.Name, - Namespace: testSecretNamespacedName2.Namespace, - }, - Data: map[string][]byte{ - credentials: []byte(credential), - }, - } - encodedS1, _ := json.Marshal(s1) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) - err = fakeClient.Create(context.Background(), s1) - Expect(err).Should(BeNil()) - var pollIntv uint = 1 - account := &v1alpha1.CloudProviderAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: testAccountNamespacedName.Name, - Namespace: testAccountNamespacedName.Namespace, - }, - Spec: v1alpha1.CloudProviderAccountSpec{ - PollIntervalInSeconds: &pollIntv, - AWSConfig: &v1alpha1.CloudProviderAccountAWSConfig{ - Region: "us-east-1", - SecretRef: &v1alpha1.SecretReference{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Key: credentials, - }, - }, - }, - } - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating CloudProviderAccount [%s, %s] with SecretRef [%s, %s]\n", - account.Name, account.Namespace, testSecretNamespacedName1.Name, - testSecretNamespacedName1.Namespace))) - err = fakeClient.Create(context.Background(), account) - Expect(err).Should(BeNil()) - newS1Req := admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{ - Group: "", - Version: "corev1", - Kind: "Secret", - }, - Resource: metav1.GroupVersionResource{ - Group: "", - Version: "corev1", - Resource: "Secrets", - }, - Name: testSecretNamespacedName2.Name, - Namespace: testSecretNamespacedName2.Namespace, - Operation: v1.Delete, - OldObject: runtime.RawExtension{ - Raw: encodedS1, - }, - }, - } - SecretValidatorTest1 := &SecretValidator{ - Client: fakeClient, - Log: logging.GetLogger("webhook").WithName("Secret")} - err = SecretValidatorTest1.InjectDecoder(decoder) - Expect(err).Should(BeNil()) - response := SecretValidatorTest1.Handle(context.Background(), newS1Req) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) - Expect(response.Allowed).To(BeTrue()) - }) - It("Validate Secret credentials update without dependent AWS CPA", func() { - credential := `{"accessKeyId": "keyId","accessKeySecret": "keySecret"}` - s1 = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName2.Name, - Namespace: testSecretNamespacedName2.Namespace, - }, - Data: map[string][]byte{ - credentials: []byte(credential), - }, - } - - encodedS1, _ := json.Marshal(s1) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) - err = fakeClient.Create(context.Background(), s1) - Expect(err).Should(BeNil()) - var pollIntv uint = 1 - account := &v1alpha1.CloudProviderAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: testAccountNamespacedName.Name, - Namespace: testAccountNamespacedName.Namespace, - }, - Spec: v1alpha1.CloudProviderAccountSpec{ - PollIntervalInSeconds: &pollIntv, - AWSConfig: &v1alpha1.CloudProviderAccountAWSConfig{ - Region: "us-east-1", - SecretRef: &v1alpha1.SecretReference{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Key: credentials, - }, - }, - }, - } - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating CloudProviderAccount [%s, %s] with SecretRef [%s, %s]\n", - account.Name, account.Namespace, testSecretNamespacedName1.Name, - testSecretNamespacedName1.Namespace))) - err = fakeClient.Create(context.Background(), account) - Expect(err).Should(BeNil()) - newCredential := `{"accessKeyId": "keyId","accessKeySecret": "keySecret1"}` - newS1 := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName2.Name, - Namespace: testSecretNamespacedName2.Namespace, - }, - Data: map[string][]byte{ - credentials: []byte(newCredential), - }, - } - encodedNewS1, _ := json.Marshal(newS1) - newS1Req := admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{ - Group: "", - Version: "corev1", - Kind: "Secret", - }, - Resource: metav1.GroupVersionResource{ - Group: "", - Version: "corev1", - Resource: "Secrets", - }, - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Operation: v1.Update, - Object: runtime.RawExtension{ - Raw: encodedNewS1, - }, - OldObject: runtime.RawExtension{ - Raw: encodedS1, - }, - }, - } - SecretValidatorTest1 := &SecretValidator{ - Client: fakeClient, - Log: logging.GetLogger("webhook").WithName("Secret")} - err = SecretValidatorTest1.InjectDecoder(decoder) - Expect(err).Should(BeNil()) - response := SecretValidatorTest1.Handle(context.Background(), newS1Req) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) - Expect(response.Allowed).To(BeTrue()) - }) - - // The below set of tests validate Secret update/delete for Azure config. - It("Validate Azure Secret delete with dependent Azure CPA", func() { - credential = `{"subscriptionId": "SubID", - "clientId": "ClientID", - "tenantId": "TenantID, - "clientKey": "ClientKey" - }` - s1 = &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - }, - Data: map[string][]byte{ - credentials: []byte(credential), - }, - } - encodedS1, _ := json.Marshal(s1) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) - err = fakeClient.Create(context.Background(), s1) - Expect(err).Should(BeNil()) - temp := &corev1.Secret{} - err = fakeClient.Get(context.TODO(), testSecretNamespacedName1, temp) - Expect(err).Should(BeNil()) - var pollIntv uint = 1 - account = &v1alpha1.CloudProviderAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: testAccountNamespacedName.Name, - Namespace: testAccountNamespacedName.Namespace, - }, - Spec: v1alpha1.CloudProviderAccountSpec{ - PollIntervalInSeconds: &pollIntv, - AzureConfig: &v1alpha1.CloudProviderAccountAzureConfig{ - Region: "us-east-1", - SecretRef: &v1alpha1.SecretReference{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Key: credentials, - }, - }, - }, - } - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating CloudProviderAccount [%s, %s] with SecretRef [%s, %s]\n", - account.Name, account.Namespace, testSecretNamespacedName1.Name, - testSecretNamespacedName1.Namespace))) - err = fakeClient.Create(context.Background(), account) - Expect(err).Should(BeNil()) - newS1Req := admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{ - Group: "", - Version: "corev1", - Kind: "Secret", - }, - Resource: metav1.GroupVersionResource{ - Group: "", - Version: "corev1", - Resource: "Secrets", - }, - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Operation: v1.Delete, - OldObject: runtime.RawExtension{ - Raw: encodedS1, - }, - }, - } - SecretValidatorTest1 := &SecretValidator{ - Client: fakeClient, - Log: logging.GetLogger("webhook").WithName("Secret")} - err = SecretValidatorTest1.InjectDecoder(decoder) - Expect(err).Should(BeNil()) - response := SecretValidatorTest1.Handle(context.Background(), newS1Req) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) - Expect(response.Allowed).To(BeFalse()) - }) - It("Validate Secret credentials update with dependent Azure CPA", func() { - credential = `{"subscriptionId": "SubID", - "clientId": "ClientID", - "tenantId": "TenantID, - "clientKey": "ClientKey" - }` - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) - err = fakeClient.Create(context.Background(), s1) - Expect(err).Should(BeNil()) - var pollIntv uint = 1 - account = &v1alpha1.CloudProviderAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: testAccountNamespacedName.Name, - Namespace: testAccountNamespacedName.Namespace, - }, - Spec: v1alpha1.CloudProviderAccountSpec{ - PollIntervalInSeconds: &pollIntv, - AzureConfig: &v1alpha1.CloudProviderAccountAzureConfig{ - Region: "us-east-1", - SecretRef: &v1alpha1.SecretReference{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Key: credentials, - }, - }, - }, - } - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating CloudProviderAccount [%s, %s] with SecretRef [%s, %s]\n", - account.Name, account.Namespace, testSecretNamespacedName1.Name, - testSecretNamespacedName1.Namespace))) - err = fakeClient.Create(context.Background(), account) - Expect(err).Should(BeNil()) - newCredential := `{"subscriptionId": "SubID", - "clientId": "ClientID", - "tenantId": "TenantID", - "clientKey": "ClientKey" - }` - newS1 := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - }, - Data: map[string][]byte{ - credentials: []byte(newCredential), - }, - } - encodedNewS1, _ := json.Marshal(newS1) - newS1Req := admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{ - Group: "", - Version: "corev1", - Kind: "Secret", - }, - Resource: metav1.GroupVersionResource{ - Group: "", - Version: "corev1", - Resource: "Secrets", - }, - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Operation: v1.Update, - Object: runtime.RawExtension{ - Raw: encodedNewS1, - }, - OldObject: runtime.RawExtension{ - Raw: encodedS1, - }, - }, - } - SecretValidatorTest1 := &SecretValidator{ - Client: fakeClient, - Log: logging.GetLogger("webhook").WithName("Secret")} - err = SecretValidatorTest1.InjectDecoder(decoder) - Expect(err).Should(BeNil()) - response := SecretValidatorTest1.Handle(context.Background(), newS1Req) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) - Expect(response.Allowed).To(BeFalse()) - }) - It("Validate Secret labels update with dependent Azure CPA", func() { - credential = `{"subscriptionId": "SubID", - "clientId": "ClientID", - "tenantId": "TenantID, - "clientKey": "ClientKey" - }` + It("Validate Secret delete without dependent AWS CPA", func() { + credential := `{"accessKeyId": "keyId","accessKeySecret": "keySecret"}` s1 = &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, + Name: testSecretNamespacedName2.Name, + Namespace: testSecretNamespacedName2.Namespace, }, Data: map[string][]byte{ credentials: []byte(credential), @@ -633,14 +182,14 @@ var _ = Describe("Webhook", func() { err = fakeClient.Create(context.Background(), s1) Expect(err).Should(BeNil()) var pollIntv uint = 1 - account = &v1alpha1.CloudProviderAccount{ + account := &v1alpha1.CloudProviderAccount{ ObjectMeta: metav1.ObjectMeta{ Name: testAccountNamespacedName.Name, Namespace: testAccountNamespacedName.Namespace, }, Spec: v1alpha1.CloudProviderAccountSpec{ PollIntervalInSeconds: &pollIntv, - AzureConfig: &v1alpha1.CloudProviderAccountAzureConfig{ + AWSConfig: &v1alpha1.CloudProviderAccountAWSConfig{ Region: "us-east-1", SecretRef: &v1alpha1.SecretReference{ Name: testSecretNamespacedName1.Name, @@ -655,19 +204,6 @@ var _ = Describe("Webhook", func() { testSecretNamespacedName1.Namespace))) err = fakeClient.Create(context.Background(), account) Expect(err).Should(BeNil()) - newLabel := make(map[string]string) - newLabel["test"] = "testLabel" - newS1 := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Labels: newLabel, - }, - Data: map[string][]byte{ - credentials: []byte(credential), - }, - } - encodedNewS1, _ := json.Marshal(newS1) newS1Req := admission.Request{ AdmissionRequest: v1.AdmissionRequest{ Kind: metav1.GroupVersionKind{ @@ -680,12 +216,9 @@ var _ = Describe("Webhook", func() { Version: "corev1", Resource: "Secrets", }, - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Operation: v1.Update, - Object: runtime.RawExtension{ - Raw: encodedNewS1, - }, + Name: testSecretNamespacedName2.Name, + Namespace: testSecretNamespacedName2.Namespace, + Operation: v1.Delete, OldObject: runtime.RawExtension{ Raw: encodedS1, }, @@ -700,7 +233,9 @@ var _ = Describe("Webhook", func() { _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) Expect(response.Allowed).To(BeTrue()) }) - It("Validate Secret delete without dependent Azure CPA", func() { + + // The below set of tests validate Secret update/delete for Azure config. + It("Validate Azure Secret delete with dependent Azure CPA", func() { credential = `{"subscriptionId": "SubID", "clientId": "ClientID", "tenantId": "TenantID, @@ -708,8 +243,8 @@ var _ = Describe("Webhook", func() { }` s1 = &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName2.Name, - Namespace: testSecretNamespacedName2.Namespace, + Name: testSecretNamespacedName1.Name, + Namespace: testSecretNamespacedName1.Namespace, }, Data: map[string][]byte{ credentials: []byte(credential), @@ -719,8 +254,11 @@ var _ = Describe("Webhook", func() { _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) err = fakeClient.Create(context.Background(), s1) Expect(err).Should(BeNil()) + temp := &corev1.Secret{} + err = fakeClient.Get(context.TODO(), testSecretNamespacedName1, temp) + Expect(err).Should(BeNil()) var pollIntv uint = 1 - account := &v1alpha1.CloudProviderAccount{ + account = &v1alpha1.CloudProviderAccount{ ObjectMeta: metav1.ObjectMeta{ Name: testAccountNamespacedName.Name, Namespace: testAccountNamespacedName.Namespace, @@ -754,8 +292,8 @@ var _ = Describe("Webhook", func() { Version: "corev1", Resource: "Secrets", }, - Name: testSecretNamespacedName2.Name, - Namespace: testSecretNamespacedName2.Namespace, + Name: testSecretNamespacedName1.Name, + Namespace: testSecretNamespacedName1.Namespace, Operation: v1.Delete, OldObject: runtime.RawExtension{ Raw: encodedS1, @@ -769,9 +307,10 @@ var _ = Describe("Webhook", func() { Expect(err).Should(BeNil()) response := SecretValidatorTest1.Handle(context.Background(), newS1Req) _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) - Expect(response.Allowed).To(BeTrue()) + Expect(response.Allowed).To(BeFalse()) }) - It("Validate Secret credentials update without Azure dependent CPA", func() { + + It("Validate Secret delete without dependent Azure CPA", func() { credential = `{"subscriptionId": "SubID", "clientId": "ClientID", "tenantId": "TenantID, @@ -786,7 +325,6 @@ var _ = Describe("Webhook", func() { credentials: []byte(credential), }, } - encodedS1, _ := json.Marshal(s1) _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) err = fakeClient.Create(context.Background(), s1) @@ -814,17 +352,6 @@ var _ = Describe("Webhook", func() { testSecretNamespacedName1.Namespace))) err = fakeClient.Create(context.Background(), account) Expect(err).Should(BeNil()) - newCredential := `{"accessKeyId": "keyId","accessKeySecret": "keySecret1"}` - newS1 := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName2.Name, - Namespace: testSecretNamespacedName2.Namespace, - }, - Data: map[string][]byte{ - credentials: []byte(newCredential), - }, - } - encodedNewS1, _ := json.Marshal(newS1) newS1Req := admission.Request{ AdmissionRequest: v1.AdmissionRequest{ Kind: metav1.GroupVersionKind{ @@ -837,12 +364,9 @@ var _ = Describe("Webhook", func() { Version: "corev1", Resource: "Secrets", }, - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Operation: v1.Update, - Object: runtime.RawExtension{ - Raw: encodedNewS1, - }, + Name: testSecretNamespacedName2.Name, + Namespace: testSecretNamespacedName2.Namespace, + Operation: v1.Delete, OldObject: runtime.RawExtension{ Raw: encodedS1, }, @@ -857,7 +381,6 @@ var _ = Describe("Webhook", func() { _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) Expect(response.Allowed).To(BeTrue()) }) - // Other tests. It("Validate Secret delete with json marshal error", func() { _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) @@ -921,138 +444,7 @@ var _ = Describe("Webhook", func() { Expect(response.Allowed).To(BeFalse()) Expect(response.Result.Code).Should(BeEquivalentTo(400)) }) - It("Validate Secret update with json marshal error", func() { - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) - err = fakeClient.Create(context.Background(), s1) - Expect(err).Should(BeNil()) - var pollIntv uint = 1 - account = &v1alpha1.CloudProviderAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: testAccountNamespacedName.Name, - Namespace: testAccountNamespacedName.Namespace, - }, - Spec: v1alpha1.CloudProviderAccountSpec{ - PollIntervalInSeconds: &pollIntv, - AWSConfig: &v1alpha1.CloudProviderAccountAWSConfig{ - Region: "us-east-1", - SecretRef: &v1alpha1.SecretReference{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Key: credentials, - }, - }, - }, - } - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating CloudProviderAccount [%s, %s] with SecretRef [%s, %s]\n", - account.Name, account.Namespace, testSecretNamespacedName1.Name, - testSecretNamespacedName1.Namespace))) - err = fakeClient.Create(context.Background(), account) - Expect(err).Should(BeNil()) - newCredential := `{"accessKeyId": "keyId","accessKeySecret": "keySecret1"}` - newS1 := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - }, - Data: map[string][]byte{ - credentials: []byte(newCredential), - }, - } - encodedNewS1, _ := json.Marshal(newS1) - dummyEncodeS1 := []byte("dummy") - newS1Req := admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{ - Group: "", - Version: "corev1", - Kind: "Secret", - }, - Resource: metav1.GroupVersionResource{ - Group: "", - Version: "corev1", - Resource: "Secrets", - }, - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Operation: v1.Update, - Object: runtime.RawExtension{ - Raw: encodedNewS1, - }, - OldObject: runtime.RawExtension{ - Raw: dummyEncodeS1, - }, - }, - } - SecretValidatorTest1 := &SecretValidator{ - Client: fakeClient, - Log: logging.GetLogger("webhook").WithName("Secret")} - err = SecretValidatorTest1.InjectDecoder(decoder) - Expect(err).Should(BeNil()) - response := SecretValidatorTest1.Handle(context.Background(), newS1Req) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) - Expect(response.Allowed).To(BeFalse()) - Expect(response.Result.Code).Should(BeEquivalentTo(400)) - }) - It("Validate Secret update with decoder error", func() { - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating Secret [%s, %s]\n", s1.Name, s1.Namespace))) - err = fakeClient.Create(context.Background(), s1) - Expect(err).Should(BeNil()) - var pollIntv uint = 1 - account = &v1alpha1.CloudProviderAccount{ - ObjectMeta: metav1.ObjectMeta{ - Name: testAccountNamespacedName.Name, - Namespace: testAccountNamespacedName.Namespace, - }, - Spec: v1alpha1.CloudProviderAccountSpec{ - PollIntervalInSeconds: &pollIntv, - AWSConfig: &v1alpha1.CloudProviderAccountAWSConfig{ - Region: "us-east-1", - SecretRef: &v1alpha1.SecretReference{ - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Key: credentials, - }, - }, - }, - } - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Creating CloudProviderAccount [%s, %s] with SecretRef [%s, %s]\n", - account.Name, account.Namespace, testSecretNamespacedName1.Name, - testSecretNamespacedName1.Namespace))) - err = fakeClient.Create(context.Background(), account) - Expect(err).Should(BeNil()) - dummyEncodeS1 := []byte("dummy") - // Do not add Object field to simulate error while decoding. - newS1Req := admission.Request{ - AdmissionRequest: v1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{ - Group: "", - Version: "corev1", - Kind: "Secret", - }, - Resource: metav1.GroupVersionResource{ - Group: "", - Version: "corev1", - Resource: "Secrets", - }, - Name: testSecretNamespacedName1.Name, - Namespace: testSecretNamespacedName1.Namespace, - Operation: v1.Update, - OldObject: runtime.RawExtension{ - Raw: dummyEncodeS1, - }, - }, - } - SecretValidatorTest1 := &SecretValidator{ - Client: fakeClient, - Log: logging.GetLogger("webhook").WithName("Secret")} - err = SecretValidatorTest1.InjectDecoder(decoder) - Expect(err).Should(BeNil()) - response := SecretValidatorTest1.Handle(context.Background(), newS1Req) - _, _ = GinkgoWriter.Write([]byte(fmt.Sprintf("Got admission response %+v\n", response))) - Expect(response.Allowed).To(BeFalse()) - Expect(response.Result.Code).Should(BeEquivalentTo(400)) - Expect(response.Result.Message).Should(BeEquivalentTo(decoderErrorMsg)) - }) + It("Validate Secret invalid admission request", func() { // Set admission.Request type to connect to simulate invalid request. s1Req = admission.Request{ diff --git a/pkg/config/config.go b/pkg/config/config.go index ca531d08..5132db60 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -18,6 +18,7 @@ const ( DefaultCloudResourcePrefix = "nephe" DefaultCloudSyncInterval = 300 MinimumCloudSyncInterval = 60 + PodNamespaceEnvKey = "POD_NAMESPACE" ) type ControllerConfig struct { diff --git a/pkg/controllers/cloud/cloudprovideraccount_controller.go b/pkg/controllers/cloud/cloudprovideraccount_controller.go index 7cc62f6d..aa978d9f 100644 --- a/pkg/controllers/cloud/cloudprovideraccount_controller.go +++ b/pkg/controllers/cloud/cloudprovideraccount_controller.go @@ -17,20 +17,26 @@ package cloud import ( "context" "fmt" + "os" "sync" "time" "github.com/go-logr/logr" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/kubernetes" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" cloudv1alpha1 "antrea.io/nephe/apis/crd/v1alpha1" cloudprovider "antrea.io/nephe/pkg/cloud-provider" "antrea.io/nephe/pkg/cloud-provider/cloudapi/common" + "antrea.io/nephe/pkg/config" "antrea.io/nephe/pkg/controllers/inventory" "antrea.io/nephe/pkg/controllers/utils" ) @@ -49,6 +55,8 @@ type CloudProviderAccountReconciler struct { Poller *Poller pendingSyncCount int initialized bool + watcher watch.Interface + clientset kubernetes.Interface } // nolint:lll @@ -63,7 +71,8 @@ func (r *CloudProviderAccountReconciler) Reconcile(ctx context.Context, req ctrl return ctrl.Result{}, err } } - + r.mutex.Lock() + defer r.mutex.Unlock() providerAccount := &cloudv1alpha1.CloudProviderAccount{} err := r.Get(ctx, req.NamespacedName, providerAccount) if err != nil && !errors.IsNotFound(err) { @@ -84,8 +93,15 @@ func (r *CloudProviderAccountReconciler) Reconcile(ctx context.Context, req ctrl } func (r *CloudProviderAccountReconciler) SetupWithManager(mgr ctrl.Manager) error { + var err error r.accountProviderType = make(map[types.NamespacedName]common.ProviderType) - if err := ctrl.NewControllerManagedBy(mgr).For(&cloudv1alpha1.CloudProviderAccount{}).Complete(r); err != nil { + // Client in controller requires reconciler for each object that are under watch. So to avoid reconciler and use only + // watch, that can be implemented by clientset. + if r.clientset, err = kubernetes.NewForConfig(ctrl.GetConfigOrDie()); err != nil { + r.Log.Error(err, "error while creating config") + return err + } + if err = ctrl.NewControllerManagedBy(mgr).For(&cloudv1alpha1.CloudProviderAccount{}).Complete(r); err != nil { return err } return mgr.Add(r) @@ -94,7 +110,7 @@ func (r *CloudProviderAccountReconciler) SetupWithManager(mgr ctrl.Manager) erro // Start performs the initialization of the controller. // A controller is said to be initialized only when the dependent controllers // are synced, and controller keeps a count of pending CRs to be reconciled. -func (r *CloudProviderAccountReconciler) Start(context.Context) error { +func (r *CloudProviderAccountReconciler) Start(ctx context.Context) error { r.Log.Info("Waiting for shared informer caches to be synced") // Blocking call to wait till the informer caches are synced by controller run-time // or the context is Done. @@ -109,7 +125,7 @@ func (r *CloudProviderAccountReconciler) Start(context.Context) error { r.pendingSyncCount = len(cpaList.Items) if r.pendingSyncCount == 0 { - GetControllerSyncStatusInstance().SetControllerSyncStatus(ControllerTypeCPA) + r.setSyncStatusAndSecretWatcher() } r.initialized = true r.Log.Info("Init done", "controller", ControllerTypeCPA.String()) @@ -122,11 +138,22 @@ func (r *CloudProviderAccountReconciler) updatePendingSyncCountAndStatus() { if r.pendingSyncCount > 0 { r.pendingSyncCount-- if r.pendingSyncCount == 0 { - GetControllerSyncStatusInstance().SetControllerSyncStatus(ControllerTypeCPA) + r.setSyncStatusAndSecretWatcher() } } } +// setWatcher sets the controller sync status and watcher. +func (r *CloudProviderAccountReconciler) setSyncStatusAndSecretWatcher() { + GetControllerSyncStatusInstance().SetControllerSyncStatus(ControllerTypeCPA) + go func() { + if err := r.setupSecretWatcher(); err != nil { + r.Log.Error(err, "exiting the controller") + os.Exit(1) + } + }() +} + func (r *CloudProviderAccountReconciler) processCreate(namespacedName *types.NamespacedName, account *cloudv1alpha1.CloudProviderAccount) error { r.Log.Info("Received request", "account", namespacedName, "operation", "create/update") @@ -192,25 +219,114 @@ func (r *CloudProviderAccountReconciler) processDelete(namespacedName *types.Nam func (r *CloudProviderAccountReconciler) addAccountProviderType(namespacedName *types.NamespacedName, provider cloudv1alpha1.CloudProvider) common.ProviderType { - r.mutex.Lock() - defer r.mutex.Unlock() - providerType := common.ProviderType(provider) r.accountProviderType[*namespacedName] = providerType - return providerType } func (r *CloudProviderAccountReconciler) removeAccountProviderType(namespacedName *types.NamespacedName) { - r.mutex.Lock() - defer r.mutex.Unlock() - delete(r.accountProviderType, *namespacedName) } func (r *CloudProviderAccountReconciler) getAccountProviderType(namespacedName *types.NamespacedName) common.ProviderType { - r.mutex.Lock() - defer r.mutex.Unlock() - return r.accountProviderType[*namespacedName] } + +// getCpaBySecret returns nil only when the Secret is not used by any CloudProvideAccount CR, +// otherwise the dependent CloudProvideAccount CR will be returned. +func (r *CloudProviderAccountReconciler) getCpaBySecret(s types.NamespacedName) (error, []cloudv1alpha1.CloudProviderAccount) { + cpaList := &cloudv1alpha1.CloudProviderAccountList{} + err := r.Client.List(context.TODO(), cpaList, &client.ListOptions{}) + var cpaItems []cloudv1alpha1.CloudProviderAccount + if err != nil { + return fmt.Errorf("failed to get CloudProviderAccount list, err:%v", err), nil + } + + for _, cpa := range cpaList.Items { + if cpa.Spec.AWSConfig != nil { + if cpa.Spec.AWSConfig.SecretRef.Name == s.Name && + cpa.Spec.AWSConfig.SecretRef.Namespace == s.Namespace { + cpaItems = append(cpaItems, cpa) + } + } + + if cpa.Spec.AzureConfig != nil { + if cpa.Spec.AzureConfig.SecretRef.Name == s.Name && + cpa.Spec.AzureConfig.SecretRef.Namespace == s.Namespace { + cpaItems = append(cpaItems, cpa) + } + } + } + return nil, cpaItems +} + +// watchSecret watch the Secret objects. +func (r *CloudProviderAccountReconciler) watchSecret() error { + for { + event, ok := <-r.watcher.ResultChan() + if !ok { + r.resetSecretWatcher() + } else { + switch event.Type { + case watch.Modified: + r.mutex.Lock() + secret := event.Object.(*v1.Secret) + namespacedName := types.NamespacedName{Namespace: secret.Namespace, Name: secret.Name} + r.Log.Info("Received request for update", "Secret", namespacedName) + err, cpaItems := r.getCpaBySecret(namespacedName) + if err != nil { + r.Log.Error(err, "error while getting CPA by Secret %v", namespacedName) + return err + } + for _, cpa := range cpaItems { + // If the credentials are updated to new cloud account then vpc cache is not updated. As a + // result vpc cache will contain vpcs from old and new accounts. + accountNamespacedName := types.NamespacedName{Namespace: cpa.Namespace, Name: cpa.Name} + err := r.processCreate(&accountNamespacedName, &cpa) + if err != nil { + r.Log.Error(err, "error while adding the secret in CPAs") + return err + } + } + r.Log.Info("Credentials updated or modified for the CPA", + "Secret", namespacedName) + r.mutex.Unlock() + case watch.Error: + r.resetSecretWatcher() + } + } + } +} + +// resetWatcher create a watcher for Secret +func (r *CloudProviderAccountReconciler) resetSecretWatcher() { + var err error + for { + if r.watcher, err = r.clientset.CoreV1().Secrets(r.getPodNamespace()).Watch(context.Background(), metav1.ListOptions{}); err != nil { + r.Log.Error(err, "error while creating Secret watcher") + time.Sleep(time.Second * 5) + continue + } + break + } +} + +// setupSecretWatcher set up a watcher for Secret objects. +func (r *CloudProviderAccountReconciler) setupSecretWatcher() error { + r.resetSecretWatcher() + if err := r.watchSecret(); err != nil { + return err + } + return nil +} + +// getPodNamespace gets the namespace of the pod. +func (r *CloudProviderAccountReconciler) getPodNamespace() string { + podNamespace := os.Getenv(config.PodNamespaceEnvKey) + if podNamespace == "" { + r.Log.Error(fmt.Errorf("environment variable %v not found", config.PodNamespaceEnvKey), + "Pod namespace is empty") + os.Exit(1) + } + return podNamespace +} diff --git a/pkg/controllers/cloud/cloudprovideraccount_controller_test.go b/pkg/controllers/cloud/cloudprovideraccount_controller_test.go index c7a76ba3..a0552268 100644 --- a/pkg/controllers/cloud/cloudprovideraccount_controller_test.go +++ b/pkg/controllers/cloud/cloudprovideraccount_controller_test.go @@ -16,7 +16,9 @@ package cloud import ( "context" + "os" "sync" + "time" mock "github.com/golang/mock/gomock" . "github.com/onsi/ginkgo" @@ -26,6 +28,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + fakewatch "k8s.io/client-go/kubernetes/fake" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -136,7 +139,6 @@ var _ = Describe("CloudProviderAccount Controller", func() { }) It("Account delete error due to no account config entry", func() { _ = fakeClient.Create(context.Background(), secret) - err := reconciler.processCreate(&testAccountNamespacedName, account) Expect(err).ShouldNot(HaveOccurred()) @@ -148,5 +150,57 @@ var _ = Describe("CloudProviderAccount Controller", func() { err = reconciler.processDelete(&testAccountNamespacedName) Expect(err).Should(HaveOccurred()) }) + It("Secret watcher", func() { + var err error + ch := make(chan error) + reconciler.clientset = fakewatch.NewSimpleClientset() + credential := `{"accessKeyId": "keyId","accessKeySecret": "Secret"}` + err = os.Setenv("POD_NAMESPACE", testSecretNamespacedName.Namespace) + Expect(err).ShouldNot(HaveOccurred()) + // Create secret + // Created a secret using clientset because watcher is created using clientset. So inorder to know + // watcher if the secret is modified secret should be created using clientset. Secret should be created using + // fakeclient as well. + _, err = reconciler.clientset.CoreV1().Secrets(testSecretNamespacedName.Namespace). + Create(context.Background(), secret, v1.CreateOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + _ = fakeClient.Create(context.Background(), secret) + // Create CPA + _ = fakeClient.Create(context.Background(), account) + go func() { + ch <- reconciler.setupSecretWatcher() + }() + time.Sleep(time.Second * 10) + _, err = reconciler.clientset.CoreV1().Secrets(testSecretNamespacedName.Namespace). + Get(context.Background(), testSecretNamespacedName.Name, v1.GetOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + secret = &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: testSecretNamespacedName.Name, + Namespace: testSecretNamespacedName.Namespace, + }, + Data: map[string][]byte{ + "credentials": []byte(credential), + }, + } + _, err = reconciler.clientset.CoreV1().Secrets(testSecretNamespacedName.Namespace). + Update(context.Background(), secret, v1.UpdateOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + secret = &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: testSecretNamespacedName.Name, + Namespace: testSecretNamespacedName.Namespace, + }, + Data: map[string][]byte{ + "credentials": []byte("credentialg"), + }, + } + _ = fakeClient.Update(context.Background(), secret) + _, err = reconciler.clientset.CoreV1().Secrets(testSecretNamespacedName.Namespace). + Update(context.Background(), secret, v1.UpdateOptions{}) + Expect(err).ShouldNot(HaveOccurred()) + errWatch := <-ch + Expect(errWatch).Should(HaveOccurred()) + }) }) })