diff --git a/Makefile b/Makefile index f92f6340700..5807017093c 100644 --- a/Makefile +++ b/Makefile @@ -19,22 +19,33 @@ TEST_RESOURCE_PREFIX ?= aso-$(BUILD_ID) # Go compiler builds tags: some parts of the test suite use these to selectively compile tests. BUILD_TAGS ?= all -# Temp directory variable, set by environment on macOS and set to default for everything else -TMPDIR ?= /tmp/ +ifdef TMPDIR +TMPDIR := $(realpath ${TMPDIR}) +else +TMPDIR := /tmp +endif all: manager # Generate test certs for development +generate-test-certs: CONFIGTXT := $(shell mktemp) +generate-test-certs: WEBHOOK_DIR := $(TMPDIR)/k8s-webhook-server +generate-test-certs: WEBHOOK_CERT_DIR := $(TMPDIR)/k8s-webhook-server/serving-certs generate-test-certs: - echo "[req]" > config.txt - echo "distinguished_name = req_distinguished_name" >> config.txt - echo "[req_distinguished_name]" >> config.txt - echo "[SAN]" >> config.txt - echo "subjectAltName=DNS:azureoperator-webhook-service.azureoperator-system.svc.cluster.local" >> config.txt - openssl req -x509 -days 730 -out tls.crt -keyout tls.key -newkey rsa:4096 -subj "/CN=azureoperator-webhook-service.azureoperator-system" -config config.txt -nodes - rm -rf $(TMPDIR)/k8s-webhook-server - mkdir -p $(TMPDIR)/k8s-webhook-server/serving-certs - mv tls.* $(TMPDIR)/k8s-webhook-server/serving-certs/ + rm -rf $(WEBHOOK_DIR) + mkdir -p $(WEBHOOK_CERT_DIR) + + @echo "[req]" > $(CONFIGTXT) + @echo "distinguished_name = req_distinguished_name" >> $(CONFIGTXT) + @echo "[req_distinguished_name]" >> $(CONFIGTXT) + @echo "[SAN]" >> $(CONFIGTXT) + @echo "subjectAltName=DNS:azureoperator-webhook-service.azureoperator-system.svc.cluster.local" >> $(CONFIGTXT) + + @echo "OpenSSL Config:" + @cat $(CONFIGTXT) + @echo + + openssl req -x509 -days 730 -out $(WEBHOOK_CERT_DIR)/tls.crt -keyout $(WEBHOOK_CERT_DIR)/tls.key -newkey rsa:4096 -subj "/CN=azureoperator-webhook-service.azureoperator-system" -config $(CONFIGTXT) -nodes # Run Controller tests against the configured cluster test-integration-controllers: generate fmt vet manifests diff --git a/PROJECT b/PROJECT index da6b7e45a7b..84c22ce98dd 100644 --- a/PROJECT +++ b/PROJECT @@ -1,127 +1,133 @@ +version: "2" domain: microsoft.com repo: github.com/Azure/azure-service-operator resources: - group: azure - kind: StorageAccount version: v1alpha1 + kind: StorageAccount - group: azure - kind: CosmosDB version: v1alpha1 + kind: CosmosDB - group: azure - kind: RedisCache version: v1alpha1 + kind: RedisCache - group: azure - kind: Eventhub version: v1alpha1 + kind: Eventhub - group: azure - kind: ResourceGroup version: v1alpha1 + kind: ResourceGroup - group: azure - kind: EventhubNamespace version: v1alpha1 + kind: EventhubNamespace - group: azure - kind: AzureSqlServer version: v1alpha1 + kind: AzureSqlServer - group: azure - kind: AzureSqlDatabase version: v1alpha1 + kind: AzureSqlDatabase - group: azure - kind: AzureSqlFirewallRule version: v1alpha1 + kind: AzureSqlFirewallRule - group: azure - kind: KeyVault version: v1alpha1 + kind: KeyVault - group: azure - kind: ConsumerGroup version: v1alpha1 + kind: ConsumerGroup - group: azure - kind: AzureSqlAction version: v1alpha1 + kind: AzureSqlAction - group: azure - kind: BlobContainer version: v1alpha1 + kind: BlobContainer - group: azure - kind: PostgreSQLServer version: v1alpha1 + kind: PostgreSQLServer - group: azure - kind: PostgreSQLDatabase version: v1alpha1 + kind: PostgreSQLDatabase - group: azure - kind: PostgreSQLVNetRule version: v1alpha1 + kind: PostgreSQLVNetRule - group: azure - kind: PostgreSQLFirewallRule version: v1alpha1 + kind: PostgreSQLFirewallRule - group: azure - kind: APIMgmtAPI version: v1alpha1 + kind: APIMgmtAPI - group: azure - kind: ApimService version: v1alpha1 + kind: ApimService - group: azure - kind: VirtualNetwork version: v1alpha1 + kind: VirtualNetwork - group: azure - kind: AzurePublicIPAddress version: v1alpha1 + kind: AzurePublicIPAddress - group: azure - kind: AzureNetworkInterface version: v1alpha1 + kind: AzureNetworkInterface - group: azure - kind: AppInsights version: v1alpha1 + kind: AppInsights - group: azure - kind: KeyVaultKey version: v1alpha1 + kind: KeyVaultKey - group: azure - kind: AzureSQLVNetRule version: v1alpha1 + kind: AzureSQLVNetRule - group: azure - kind: MySQLServer version: v1alpha1 + kind: MySQLServer - group: azure - kind: MySQLDatabase version: v1alpha1 + kind: MySQLDatabase - group: azure - kind: MySQLFirewallRule version: v1alpha1 + kind: MySQLFirewallRule - group: azure - kind: MySQLVNetRule version: v1alpha1 + kind: MySQLVNetRule - group: azure - kind: AzureVirtualMachine version: v1alpha1 + kind: AzureVirtualMachine - group: azure - kind: AzureSQLManagedUser version: v1alpha1 + kind: AzureSQLManagedUser - group: azure - kind: AzureLoadBalancer version: v1alpha1 + kind: AzureLoadBalancer - group: azure - kind: AzureVMScaleSet version: v1alpha1 + kind: AzureVMScaleSet - group: azure - kind: AzureSqlServer version: v1beta1 + kind: AzureSqlServer - group: azure - kind: AzureSqlDatabase version: v1beta1 + kind: AzureSqlDatabase - group: azure - kind: AzureSqlFirewallRule version: v1beta1 + kind: AzureSqlFirewallRule - group: azure - kind: AzureSqlFailoverGroup version: v1beta1 + kind: AzureSqlFailoverGroup - group: azure - kind: BlobContainer version: v1alpha2 + kind: BlobContainer - group: azure - kind: MySQLServer version: v1alpha2 + kind: MySQLServer - group: azure kind: RedisCacheFirewallRule version: v1alpha1 +- group: azure + kind: RedisCacheAction + version: v1alpha1 - group: azure kind: AzureVirtualMachineExtension version: v1alpha1 -version: "2" \ No newline at end of file +- group: azure + kind: AzureVirtualMachineExtension + version: v1alpha1 \ No newline at end of file diff --git a/api/v1alpha1/rediscacheaction_types.go b/api/v1alpha1/rediscacheaction_types.go new file mode 100644 index 00000000000..5522713e7ab --- /dev/null +++ b/api/v1alpha1/rediscacheaction_types.go @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:validation:Enum=rollallkeys;rollprimarykey;rollsecondarykey +type RedisCacheActionName string + +const ( + RedisCacheActionNameRollAllKeys RedisCacheActionName = "rollallkeys" + RedisCacheActionNameRollPrimaryKey RedisCacheActionName = "rollprimarykey" + RedisCacheActionNameRollSecondaryKey RedisCacheActionName = "rollsecondarykey" +) + +// RedisCacheActionSpec defines the desired state of RedisCacheAction +type RedisCacheActionSpec struct { + ResourceGroup string `json:"resourceGroup"` + CacheName string `json:"cacheName"` + ActionName RedisCacheActionName `json:"actionName"` + SecretName string `json:"secretName,omitempty"` + KeyVaultToStoreSecrets string `json:"keyVaultToStoreSecrets,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// RedisCacheAction is the Schema for the rediscacheactions API +// +kubebuilder:printcolumn:name="Provisioned",type="string",JSONPath=".status.provisioned" +// +kubebuilder:printcolumn:name="Message",type="string",JSONPath=".status.message" +type RedisCacheAction struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RedisCacheActionSpec `json:"spec,omitempty"` + Status ASOStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// RedisCacheActionList contains a list of RedisCacheAction +type RedisCacheActionList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []RedisCacheAction `json:"items"` +} + +func init() { + SchemeBuilder.Register(&RedisCacheAction{}, &RedisCacheActionList{}) +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index cbda21e97d5..a84cebabf62 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2991,6 +2991,80 @@ func (in *RedisCache) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisCacheAction) DeepCopyInto(out *RedisCacheAction) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisCacheAction. +func (in *RedisCacheAction) DeepCopy() *RedisCacheAction { + if in == nil { + return nil + } + out := new(RedisCacheAction) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RedisCacheAction) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisCacheActionList) DeepCopyInto(out *RedisCacheActionList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]RedisCacheAction, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisCacheActionList. +func (in *RedisCacheActionList) DeepCopy() *RedisCacheActionList { + if in == nil { + return nil + } + out := new(RedisCacheActionList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RedisCacheActionList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RedisCacheActionSpec) DeepCopyInto(out *RedisCacheActionSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RedisCacheActionSpec. +func (in *RedisCacheActionSpec) DeepCopy() *RedisCacheActionSpec { + if in == nil { + return nil + } + out := new(RedisCacheActionSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RedisCacheFirewallRule) DeepCopyInto(out *RedisCacheFirewallRule) { *out = *in diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 5663d5f26c2..55988cf27b4 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -38,6 +38,7 @@ resources: - bases/azure.microsoft.com_azuresqlmanagedusers.yaml - bases/azure.microsoft.com_azureloadbalancers.yaml - bases/azure.microsoft.com_azurevmscalesets.yaml +- bases/azure.microsoft.com_rediscacheactions.yaml - bases/azure.microsoft.com_rediscachefirewallrules.yaml - bases/azure.microsoft.com_azurevirtualmachineextensions.yaml # +kubebuilder:scaffold:crdkustomizeresource @@ -77,6 +78,7 @@ patches: #- patches/webhook_in_azuresqlmanagedusers.yaml #- patches/webhook_in_azureloadbalancers.yaml #- patches/webhook_in_azurevmscalesets.yaml +#- patches/webhook_in_rediscacheactions.yaml #- patches/webhook_in_rediscachefirewallrules.yaml #- patches/webhook_in_azurevirtualmachineextensions.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch @@ -115,6 +117,7 @@ patches: #- patches/cainjection_in_azuresqlmanagedusers.yaml #- patches/cainjection_in_azureloadbalancers.yaml #- patches/cainjection_in_azurevmscalesets.yaml +#- patches/cainjection_in_rediscacheactions.yaml #- patches/cainjection_in_rediscachefirewallrules.yaml #- patches/cainjection_in_azurevirtualmachineextensions.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch diff --git a/config/crd/patches/cainjection_in_rediscacheactions.yaml b/config/crd/patches/cainjection_in_rediscacheactions.yaml new file mode 100644 index 00000000000..fca6f70d2c3 --- /dev/null +++ b/config/crd/patches/cainjection_in_rediscacheactions.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: rediscacheactions.azure.microsoft.com diff --git a/config/crd/patches/webhook_in_rediscacheactions.yaml b/config/crd/patches/webhook_in_rediscacheactions.yaml new file mode 100644 index 00000000000..b7704636e00 --- /dev/null +++ b/config/crd/patches/webhook_in_rediscacheactions.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: rediscacheactions.azure.microsoft.com +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/samples/azure_v1alpha1_rediscacheaction.yaml b/config/samples/azure_v1alpha1_rediscacheaction.yaml new file mode 100644 index 00000000000..5042f016712 --- /dev/null +++ b/config/samples/azure_v1alpha1_rediscacheaction.yaml @@ -0,0 +1,9 @@ +apiVersion: azure.microsoft.com/v1alpha1 +kind: RedisCacheAction +metadata: + name: rediscacheaction-sample-1 +spec: + resourceGroup: resourcegroup-azure-operators + cacheName: rediscache-sample-1 + # possible values are 'rollallkeys', 'rollprimarykey', 'rollsecondarykey' + actionName: rollallkeys \ No newline at end of file diff --git a/controllers/rediscache_controller_test.go b/controllers/rediscache_controller_test.go index cdd352c8544..f2477120618 100644 --- a/controllers/rediscache_controller_test.go +++ b/controllers/rediscache_controller_test.go @@ -11,7 +11,6 @@ import ( "time" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" - "github.com/stretchr/testify/assert" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -22,14 +21,12 @@ func TestRedisCacheControllerHappyPath(t *testing.T) { t.Parallel() defer PanicRecover(t) ctx := context.Background() - assert := assert.New(t) var rgLocation string var rgName string var redisCacheName string - var err error - rgName = tc.resourceGroup + rgName = tc.resourceGroupName rgLocation = tc.resourceGroupLocation redisCacheName = GenerateTestResourceNameWithRandom("rediscache", 10) @@ -40,8 +37,8 @@ func TestRedisCacheControllerHappyPath(t *testing.T) { Namespace: "default", }, Spec: azurev1alpha1.RedisCacheSpec{ - Location: rgLocation, - ResourceGroup: rgName, + Location: rgLocation, + ResourceGroupName: rgName, Properties: azurev1alpha1.RedisCacheProperties{ Sku: azurev1alpha1.RedisCacheSku{ Name: "Basic", @@ -57,7 +54,7 @@ func TestRedisCacheControllerHappyPath(t *testing.T) { EnsureInstance(ctx, t, tc, redisCacheInstance) // verify secret exists in secretclient - EnsureSecrets(ctx, t, tc, redisCacheInstance, tc.SecretClient, redisCacheInstance.Name, redisCacheInstance.Namespace) + EnsureSecrets(ctx, t, tc, redisCacheInstance, tc.secretClient, redisCacheInstance.Name, redisCacheInstance.Namespace) // delete rc EnsureDelete(ctx, t, tc, redisCacheInstance) diff --git a/controllers/rediscacheaction_controller.go b/controllers/rediscacheaction_controller.go new file mode 100644 index 00000000000..471888ab628 --- /dev/null +++ b/controllers/rediscacheaction_controller.go @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package controllers + +import ( + ctrl "sigs.k8s.io/controller-runtime" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" +) + +// RedisCacheActionReconciler reconciles a RedisCacheAction object +type RedisCacheActionReconciler struct { + Reconciler *AsyncReconciler +} + +// +kubebuilder:rbac:groups=azure.microsoft.com,resources=rediscacheactions,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=azure.microsoft.com,resources=rediscacheactions/status,verbs=get;update;patch + +func (r *RedisCacheActionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + return r.Reconciler.Reconcile(req, &azurev1alpha1.RedisCacheAction{}) +} + +func (r *RedisCacheActionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&azurev1alpha1.RedisCacheAction{}). + Complete(r) +} diff --git a/controllers/rediscacheaction_controller_test.go b/controllers/rediscacheaction_controller_test.go new file mode 100644 index 00000000000..b1f2ec76fda --- /dev/null +++ b/controllers/rediscacheaction_controller_test.go @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// +build all rediscache + +package controllers + +import ( + "context" + "testing" + + azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/errhelp" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestRedisCacheActionControllerNoResourceGroup(t *testing.T) { + t.Parallel() + defer PanicRecover(t) + ctx := context.Background() + + var rgName string + var redisCache string + var redisCacheAction string + + rgName = GenerateTestResourceNameWithRandom("rcfwr-rg", 10) + redisCache = GenerateTestResourceNameWithRandom("rediscache", 10) + redisCacheAction = GenerateTestResourceNameWithRandom("rediscacheaction", 10) + + redisCacheActionInstance := &azurev1alpha1.RedisCacheAction{ + ObjectMeta: metav1.ObjectMeta{ + Name: redisCacheAction, + Namespace: "default", + }, + Spec: azurev1alpha1.RedisCacheActionSpec{ + ResourceGroup: rgName, + CacheName: redisCache, + ActionName: azurev1alpha1.RedisCacheActionNameRollAllKeys, + }, + } + + EnsureInstanceWithResult(ctx, t, tc, redisCacheActionInstance, errhelp.ResourceGroupNotFoundErrorCode, false) + EnsureDelete(ctx, t, tc, redisCacheActionInstance) +} + +func TestRedisCacheActionNoRedisCache(t *testing.T) { + t.Parallel() + defer PanicRecover(t) + ctx := context.Background() + + var rgName string + var redisCache string + var redisCacheAction string + + rgName = tc.resourceGroupName + redisCache = GenerateTestResourceNameWithRandom("rediscache", 10) + redisCacheAction = GenerateTestResourceNameWithRandom("rediscacheaction", 10) + + redisCacheActionInstance := &azurev1alpha1.RedisCacheAction{ + ObjectMeta: metav1.ObjectMeta{ + Name: redisCacheAction, + Namespace: "default", + }, + Spec: azurev1alpha1.RedisCacheActionSpec{ + ResourceGroup: rgName, + CacheName: redisCache, + ActionName: azurev1alpha1.RedisCacheActionNameRollAllKeys, + }, + } + EnsureInstanceWithResult(ctx, t, tc, redisCacheActionInstance, errhelp.ResourceNotFound, false) + EnsureDelete(ctx, t, tc, redisCacheActionInstance) +} diff --git a/controllers/rediscachefirewallrule_controller_test.go b/controllers/rediscachefirewallrule_controller_test.go index 802a53aefa1..60133420823 100644 --- a/controllers/rediscachefirewallrule_controller_test.go +++ b/controllers/rediscachefirewallrule_controller_test.go @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// +build all resourcegroup +// +build all rediscache package controllers diff --git a/controllers/suite_test.go b/controllers/suite_test.go index e709a4f3202..b6459364ee0 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -45,8 +45,9 @@ import ( resourcemanagerpsqldatabase "github.com/Azure/azure-service-operator/pkg/resourcemanager/psql/database" resourcemanagerpsqlfirewallrule "github.com/Azure/azure-service-operator/pkg/resourcemanager/psql/firewallrule" resourcemanagerpsqlserver "github.com/Azure/azure-service-operator/pkg/resourcemanager/psql/server" + rediscacheactions "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches/actions" rcfwr "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches/firewallrule" - resourcemanagerrediscaches "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches/redis" + rediscaches "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches/redis" resourcegroupsresourcemanager "github.com/Azure/azure-service-operator/pkg/resourcemanager/resourcegroups" resourcemanagerblobcontainer "github.com/Azure/azure-service-operator/pkg/resourcemanager/storages/blobcontainer" resourcemanagerstorageaccount "github.com/Azure/azure-service-operator/pkg/resourcemanager/storages/storageaccount" @@ -271,7 +272,7 @@ func setup() error { err = (&RedisCacheReconciler{ Reconciler: &AsyncReconciler{ Client: k8sManager.GetClient(), - AzureClient: resourcemanagerrediscaches.NewAzureRedisCacheManager( + AzureClient: rediscaches.NewAzureRedisCacheManager( secretClient, scheme.Scheme, ), @@ -287,6 +288,25 @@ func setup() error { return err } + err = (&RedisCacheActionReconciler{ + Reconciler: &AsyncReconciler{ + Client: k8sManager.GetClient(), + AzureClient: rediscacheactions.NewAzureRedisCacheActionManager( + secretClient, + scheme.Scheme, + ), + Telemetry: telemetry.InitializeTelemetryDefault( + "RedisCacheAction", + ctrl.Log.WithName("controllers").WithName("RedisCacheAction"), + ), + Recorder: k8sManager.GetEventRecorderFor("RedisCacheAction-controller"), + Scheme: k8sManager.GetScheme(), + }, + }).SetupWithManager(k8sManager) + if err != nil { + return err + } + err = (&RedisCacheFirewallRuleReconciler{ Reconciler: &AsyncReconciler{ Client: k8sManager.GetClient(), diff --git a/docs/howto/newoperatorguide.md b/docs/howto/newoperatorguide.md index a07810015d4..caa1e2ed48a 100644 --- a/docs/howto/newoperatorguide.md +++ b/docs/howto/newoperatorguide.md @@ -183,26 +183,28 @@ go build -o bin/manager main.go ```go func (p *AzureNewTypeClient) Ensure(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { - instance, err := p.convert(obj) - if err != nil { - return true, err - } - // Add logic to idempotently create the resource - - // successful return - return true, nil + instance, err := p.convert(obj) + if err != nil { + return true, err } - func (p *AzureNewTypeClient) Delete(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { - instance, err := p.convert(obj) - if err != nil { - return true, err - } - // Add logic to idempotently delete the resource + // Add logic to idempotently create the resource + + // successful return + return true, nil + } - // successful return - return false, nil + func (p *AzureNewTypeClient) Delete(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { + instance, err := p.convert(obj) + if err != nil { + return true, err } + + // Add logic to idempotently delete the resource + + // successful return + return false, nil + } ``` (ii) The `GetParents()` function returns the Azure Resource Manager (ARM) hierarchy of the resource. The order here matters - the immediate hierarchical resource should be returned first. For instance, for an Azure SQL database, the first parent should be Azure SQL server followed by the Resource Group. diff --git a/main.go b/main.go index 5340c2127cc..e8e667805c1 100644 --- a/main.go +++ b/main.go @@ -49,8 +49,9 @@ import ( psqlfirewallrule "github.com/Azure/azure-service-operator/pkg/resourcemanager/psql/firewallrule" psqlserver "github.com/Azure/azure-service-operator/pkg/resourcemanager/psql/server" psqlvnetrule "github.com/Azure/azure-service-operator/pkg/resourcemanager/psql/vnetrule" + rediscacheactions "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches/actions" rcfwr "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches/firewallrule" - resourcemanagerrediscache "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches/redis" + rediscache "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches/redis" resourcemanagerresourcegroup "github.com/Azure/azure-service-operator/pkg/resourcemanager/resourcegroups" blobContainerManager "github.com/Azure/azure-service-operator/pkg/resourcemanager/storages/blobcontainer" storageaccountManager "github.com/Azure/azure-service-operator/pkg/resourcemanager/storages/storageaccount" @@ -140,7 +141,11 @@ func main() { vnetManager := vnet.NewAzureVNetManager() resourceGroupManager := resourcemanagerresourcegroup.NewAzureResourceGroupManager() - redisCacheManager := resourcemanagerrediscache.NewAzureRedisCacheManager( + redisCacheManager := rediscache.NewAzureRedisCacheManager( + secretClient, + scheme, + ) + redisCacheActionManager := rediscacheactions.NewAzureRedisCacheActionManager( secretClient, scheme, ) @@ -228,6 +233,7 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "CosmosDB") os.Exit(1) } + err = (&controllers.RedisCacheReconciler{ Reconciler: &controllers.AsyncReconciler{ Client: mgr.GetClient(), @@ -245,6 +251,22 @@ func main() { os.Exit(1) } + if err = (&controllers.RedisCacheActionReconciler{ + Reconciler: &controllers.AsyncReconciler{ + Client: mgr.GetClient(), + AzureClient: redisCacheActionManager, + Telemetry: telemetry.InitializeTelemetryDefault( + "RedisCacheAction", + ctrl.Log.WithName("controllers").WithName("RedisCacheAction"), + ), + Recorder: mgr.GetEventRecorderFor("RedisCacheAction-controller"), + Scheme: scheme, + }, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "RedisCacheAction") + os.Exit(1) + } + if err = (&controllers.RedisCacheFirewallRuleReconciler{ Reconciler: &controllers.AsyncReconciler{ Client: mgr.GetClient(), diff --git a/pkg/resourcemanager/interfaces.go b/pkg/resourcemanager/interfaces.go index 592d5d41208..2b6fd7cf57d 100644 --- a/pkg/resourcemanager/interfaces.go +++ b/pkg/resourcemanager/interfaces.go @@ -24,7 +24,7 @@ type Options struct { // ConfigOption wraps a function that sets a value in the options struct type ConfigOption func(*Options) -// WithSecretClient can be used to pass aa KeyVault SecretClient +// WithSecretClient can be used to pass in a KeyVault SecretClient func WithSecretClient(secretClient secrets.SecretClient) ConfigOption { return func(op *Options) { op.SecretClient = secretClient diff --git a/pkg/resourcemanager/rediscaches/actions/rediscacheaction_manager.go b/pkg/resourcemanager/rediscaches/actions/rediscacheaction_manager.go new file mode 100644 index 00000000000..ff47d73fc08 --- /dev/null +++ b/pkg/resourcemanager/rediscaches/actions/rediscacheaction_manager.go @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package actions + +import ( + "context" + + "github.com/Azure/azure-service-operator/pkg/resourcemanager" +) + +// RedisCacheActionManager for RedisCache +type RedisCacheActionManager interface { + RegeneratePrimaryAccessKey(ctx context.Context, resourceGroup string, cacheName string) (string, error) + + RegenerateSecondaryAccessKey(ctx context.Context, resourceGroup string, cacheName string) (string, error) + + // also embed async client methods + resourcemanager.ARMClient +} diff --git a/pkg/resourcemanager/rediscaches/actions/rediscacheaction_reconcile.go b/pkg/resourcemanager/rediscaches/actions/rediscacheaction_reconcile.go new file mode 100644 index 00000000000..f4898558e98 --- /dev/null +++ b/pkg/resourcemanager/rediscaches/actions/rediscacheaction_reconcile.go @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package actions + +import ( + "context" + "fmt" + + "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/resourcemanager" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +func (m *AzureRedisCacheActionManager) Ensure(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { + options := &resourcemanager.Options{} + for _, opt := range opts { + opt(options) + } + + if options.SecretClient != nil { + m.SecretClient = options.SecretClient + } + + instance, err := m.convert(obj) + if err != nil { + return true, err + } + + // never re-provision an action + if instance.Status.Provisioned { + return true, nil + } + + rollAllKeys := instance.Spec.ActionName == v1alpha1.RedisCacheActionNameRollAllKeys + + if rollAllKeys || instance.Spec.ActionName == v1alpha1.RedisCacheActionNameRollPrimaryKey { + if err = m.RegeneratePrimaryAccessKey(ctx, instance.Spec.ResourceGroup, instance.Spec.CacheName); err != nil { + instance.Status.Message = err.Error() + return false, err + } + } + + if rollAllKeys || instance.Spec.ActionName == v1alpha1.RedisCacheActionNameRollSecondaryKey { + if err = m.RegenerateSecondaryAccessKey(ctx, instance.Spec.ResourceGroup, instance.Spec.CacheName); err != nil { + instance.Status.Message = err.Error() + return false, err + } + } + + // regenerate the secret + cacheInstance := &v1alpha1.RedisCache{ + ObjectMeta: metav1.ObjectMeta{ + Name: instance.Spec.CacheName, + Namespace: instance.Namespace, + }, + Spec: v1alpha1.RedisCacheSpec{ + SecretName: instance.Spec.SecretName, + ResourceGroupName: instance.Spec.ResourceGroup, + }, + } + if err = m.ListKeysAndCreateSecrets(ctx, cacheInstance); err != nil { + instance.Status.Provisioned = false + instance.Status.FailedProvisioning = true + instance.Status.Message = err.Error() + return false, err + } + + // successful return + instance.Status.Provisioned = true + instance.Status.FailedProvisioning = false + instance.Status.Message = resourcemanager.SuccessMsg + return true, nil +} + +func (m *AzureRedisCacheActionManager) Delete(ctx context.Context, obj runtime.Object, opts ...resourcemanager.ConfigOption) (bool, error) { + // no deletion necessary for deletion of action + return false, nil +} + +func (m *AzureRedisCacheActionManager) GetParents(obj runtime.Object) ([]resourcemanager.KubeParent, error) { + instance, err := m.convert(obj) + if err != nil { + return nil, err + } + + return []resourcemanager.KubeParent{ + { + Key: types.NamespacedName{ + Namespace: instance.Namespace, + Name: instance.Spec.CacheName, + }, + Target: &v1alpha1.RedisCache{}, + }, + { + Key: types.NamespacedName{ + Namespace: instance.Namespace, + Name: instance.Spec.ResourceGroup, + }, + Target: &v1alpha1.ResourceGroup{}, + }, + }, nil +} + +// GetStatus gets the ASOStatus +func (m *AzureRedisCacheActionManager) GetStatus(obj runtime.Object) (*v1alpha1.ASOStatus, error) { + instance, err := m.convert(obj) + if err != nil { + return nil, err + } + return &instance.Status, nil +} + +func (m *AzureRedisCacheActionManager) convert(obj runtime.Object) (*v1alpha1.RedisCacheAction, error) { + local, ok := obj.(*v1alpha1.RedisCacheAction) + if !ok { + return nil, fmt.Errorf("failed type assertion on kind: %s", obj.GetObjectKind().GroupVersionKind().String()) + } + return local, nil +} diff --git a/pkg/resourcemanager/rediscaches/actions/rediscacheactions.go b/pkg/resourcemanager/rediscaches/actions/rediscacheactions.go new file mode 100644 index 00000000000..bfc64a7595a --- /dev/null +++ b/pkg/resourcemanager/rediscaches/actions/rediscacheactions.go @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package actions + +import ( + "context" + + "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches" + "github.com/Azure/azure-service-operator/pkg/secrets" + + model "github.com/Azure/azure-sdk-for-go/services/redis/mgmt/2018-03-01/redis" + + "k8s.io/apimachinery/pkg/runtime" +) + +// AzureRedisCacheActionManager creates a new RedisCacheManager +type AzureRedisCacheActionManager struct { + rediscaches.AzureRedisManager +} + +// NewAzureRedisCacheActionManager creates a new RedisCacheManager +func NewAzureRedisCacheActionManager(secretClient secrets.SecretClient, scheme *runtime.Scheme) *AzureRedisCacheActionManager { + return &AzureRedisCacheActionManager{ + rediscaches.AzureRedisManager{ + SecretClient: secretClient, + Scheme: scheme, + }, + } +} + +// RegeneratePrimaryAccessKey regenerates either the primary or secondary access keys +func (r *AzureRedisCacheActionManager) RegeneratePrimaryAccessKey(ctx context.Context, resourceGroup string, cacheName string) error { + client, err := r.GetRedisCacheClient() + if err != nil { + return err + } + + _, err = client.RegenerateKey(ctx, resourceGroup, cacheName, model.RegenerateKeyParameters{ + KeyType: model.Primary, + }) + if err != nil { + return err + } + + return nil +} + +// RegenerateSecondaryAccessKey regenerates either the primary or secondary access keys +func (r *AzureRedisCacheActionManager) RegenerateSecondaryAccessKey(ctx context.Context, resourceGroup string, cacheName string) error { + client, err := r.GetRedisCacheClient() + if err != nil { + return err + } + + _, err = client.RegenerateKey(ctx, resourceGroup, cacheName, model.RegenerateKeyParameters{ + KeyType: model.Secondary, + }) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/resourcemanager/rediscaches/redis/rediscache_manager.go b/pkg/resourcemanager/rediscaches/redis/rediscache_manager.go index bc6b237cefe..4e573e2ebf1 100644 --- a/pkg/resourcemanager/rediscaches/redis/rediscache_manager.go +++ b/pkg/resourcemanager/rediscaches/redis/rediscache_manager.go @@ -14,11 +14,11 @@ import ( // RedisCacheManager for RedisCache type RedisCacheManager interface { // CreateRedisCache creates a new RedisCache - CreateRedisCache(ctx context.Context, - instance azurev1alpha1.RedisCache) (*redis.ResourceType, error) + CreateRedisCache(ctx context.Context, instance azurev1alpha1.RedisCache) (*redis.ResourceType, error) // DeleteRedisCache removes the resource group named by env var DeleteRedisCache(ctx context.Context, groupName string, redisCacheName string) (result redis.DeleteFuture, err error) + // also embed async client methods resourcemanager.ARMClient } diff --git a/pkg/resourcemanager/rediscaches/redis/rediscache_reconcile.go b/pkg/resourcemanager/rediscaches/redis/rediscache_reconcile.go index 72a5128cfdf..740170ac22b 100644 --- a/pkg/resourcemanager/rediscaches/redis/rediscache_reconcile.go +++ b/pkg/resourcemanager/rediscaches/redis/rediscache_reconcile.go @@ -33,21 +33,16 @@ func (rc *AzureRedisCacheManager) Ensure(ctx context.Context, obj runtime.Object return false, err } - redisName := instance.Name + name := types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace} groupName := instance.Spec.ResourceGroupName - name := instance.ObjectMeta.Name - - if len(instance.Spec.SecretName) == 0 { - instance.Spec.SecretName = redisName - } // if an error occurs thats ok as it means that it doesn't exist yet - newRc, err := rc.GetRedisCache(ctx, groupName, name) + newRc, err := rc.GetRedisCache(ctx, groupName, name.Name) if err == nil { // succeeded! so end reconcilliation successfully if newRc.ProvisioningState == "Succeeded" { - err = rc.ListKeysAndCreateSecrets(groupName, redisName, instance.Spec.SecretName, instance) + err = rc.ListKeysAndCreateSecrets(ctx, instance) if err != nil { instance.Status.Message = err.Error() return false, err diff --git a/pkg/resourcemanager/rediscaches/redis/rediscaches.go b/pkg/resourcemanager/rediscaches/redis/rediscaches.go index e7fd22bb484..a005e2e0057 100644 --- a/pkg/resourcemanager/rediscaches/redis/rediscaches.go +++ b/pkg/resourcemanager/rediscaches/redis/rediscaches.go @@ -10,41 +10,27 @@ import ( "log" "github.com/Azure/azure-sdk-for-go/services/redis/mgmt/2018-03-01/redis" - model "github.com/Azure/azure-sdk-for-go/services/redis/mgmt/2018-03-01/redis" azurev1alpha1 "github.com/Azure/azure-service-operator/api/v1alpha1" "github.com/Azure/azure-service-operator/pkg/helpers" - "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" - "github.com/Azure/azure-service-operator/pkg/resourcemanager/iam" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/rediscaches" "github.com/Azure/azure-service-operator/pkg/secrets" "github.com/Azure/go-autorest/autorest/to" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ) // AzureRedisCacheManager creates a new RedisCacheManager type AzureRedisCacheManager struct { - SecretClient secrets.SecretClient - Scheme *runtime.Scheme + rediscaches.AzureRedisManager } // NewAzureRedisCacheManager creates a new RedisCacheManager func NewAzureRedisCacheManager(secretClient secrets.SecretClient, scheme *runtime.Scheme) *AzureRedisCacheManager { return &AzureRedisCacheManager{ - SecretClient: secretClient, - Scheme: scheme, - } -} - -func getRedisCacheClient() (redis.Client, error) { - redisClient := redis.NewClientWithBaseURI(config.BaseURI(), config.SubscriptionID()) - a, err := iam.GetResourceManagementAuthorizer() - if err != nil { - log.Println("failed to initialize authorizer: " + err.Error()) - return redisClient, err + rediscaches.AzureRedisManager{ + SecretClient: secretClient, + Scheme: scheme, + }, } - redisClient.Authorizer = a - redisClient.AddToUserAgent(config.UserAgent()) - return redisClient, nil } // CreateRedisCache creates a new RedisCache @@ -57,7 +43,7 @@ func (r *AzureRedisCacheManager) CreateRedisCache( // convert kube labels to expected tag format tags := helpers.LabelsToTags(instance.GetLabels()) - redisClient, err := getRedisCacheClient() + redisClient, err := r.GetRedisCacheClient() if err != nil { return nil, err } @@ -125,7 +111,7 @@ func (r *AzureRedisCacheManager) CreateRedisCache( // GetRedisCache returns a redis cache object if it exists func (r *AzureRedisCacheManager) GetRedisCache(ctx context.Context, groupName string, redisCacheName string) (result redis.ResourceType, err error) { - redisClient, err := getRedisCacheClient() + redisClient, err := r.GetRedisCacheClient() if err != nil { return result, err } @@ -134,64 +120,9 @@ func (r *AzureRedisCacheManager) GetRedisCache(ctx context.Context, groupName st // DeleteRedisCache removes the resource group named by env var func (r *AzureRedisCacheManager) DeleteRedisCache(ctx context.Context, groupName string, redisCacheName string) (result redis.DeleteFuture, err error) { - redisClient, err := getRedisCacheClient() + redisClient, err := r.GetRedisCacheClient() if err != nil { return result, err } return redisClient.Delete(ctx, groupName, redisCacheName) } - -//ListKeys lists the keys for redis cache -func (r *AzureRedisCacheManager) ListKeys(ctx context.Context, resourceGroupName string, redisCacheName string) (result redis.AccessKeys, err error) { - redisClient, err := getRedisCacheClient() - if err != nil { - return result, err - } - return redisClient.ListKeys(ctx, resourceGroupName, redisCacheName) -} - -// CreateSecrets creates a secret for a redis cache -func (r *AzureRedisCacheManager) CreateSecrets(ctx context.Context, secretName string, instance *azurev1alpha1.RedisCache, data map[string][]byte) error { - key := types.NamespacedName{Name: secretName, Namespace: instance.Namespace} - - err := r.SecretClient.Upsert( - ctx, - key, - data, - secrets.WithOwner(instance), - secrets.WithScheme(r.Scheme), - ) - if err != nil { - return err - } - - return nil -} - -// ListKeysAndCreateSecrets lists keys and creates secrets -func (r *AzureRedisCacheManager) ListKeysAndCreateSecrets(resourceGroupName string, redisCacheName string, secretName string, instance *azurev1alpha1.RedisCache) error { - var err error - var result model.AccessKeys - ctx := context.Background() - - result, err = r.ListKeys(ctx, resourceGroupName, redisCacheName) - if err != nil { - return err - } - data := map[string][]byte{ - "primaryKey": []byte(*result.PrimaryKey), - "secondaryKey": []byte(*result.SecondaryKey), - } - - err = r.CreateSecrets( - ctx, - secretName, - instance, - data, - ) - if err != nil { - return err - } - - return nil -} diff --git a/pkg/resourcemanager/rediscaches/shared.go b/pkg/resourcemanager/rediscaches/shared.go new file mode 100644 index 00000000000..b6c3aaa4638 --- /dev/null +++ b/pkg/resourcemanager/rediscaches/shared.go @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package rediscaches + +import ( + "context" + "log" + + "github.com/Azure/azure-service-operator/api/v1alpha1" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/iam" + "github.com/Azure/azure-service-operator/pkg/secrets" + + "github.com/Azure/azure-sdk-for-go/services/redis/mgmt/2018-03-01/redis" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +// AzureRedisManager +type AzureRedisManager struct { + SecretClient secrets.SecretClient + Scheme *runtime.Scheme +} + +func (r *AzureRedisManager) GetRedisCacheClient() (redis.Client, error) { + redisClient := redis.NewClientWithBaseURI(config.BaseURI(), config.SubscriptionID()) + a, err := iam.GetResourceManagementAuthorizer() + if err != nil { + log.Println("failed to initialize authorizer: " + err.Error()) + return redisClient, err + } + redisClient.Authorizer = a + redisClient.AddToUserAgent(config.UserAgent()) + return redisClient, nil +} + +//ListKeys lists the keys for redis cache +func (r *AzureRedisManager) ListKeys(ctx context.Context, resourceGroupName string, redisCacheName string) (result redis.AccessKeys, err error) { + redisClient, err := r.GetRedisCacheClient() + if err != nil { + return result, err + } + return redisClient.ListKeys(ctx, resourceGroupName, redisCacheName) +} + +// CreateSecrets creates a secret for a redis cache +func (r *AzureRedisManager) CreateSecrets(ctx context.Context, instance *v1alpha1.RedisCache, data map[string][]byte) error { + secretName := instance.Spec.SecretName + if secretName == "" { + secretName = instance.Name + } + + key := types.NamespacedName{Name: secretName, Namespace: instance.Namespace} + + err := r.SecretClient.Upsert( + ctx, + key, + data, + secrets.WithOwner(instance), + secrets.WithScheme(r.Scheme), + ) + if err != nil { + return err + } + + return nil +} + +// ListKeysAndCreateSecrets lists keys and creates secrets +func (r *AzureRedisManager) ListKeysAndCreateSecrets(ctx context.Context, instance *v1alpha1.RedisCache) error { + var err error + var result redis.AccessKeys + + result, err = r.ListKeys(ctx, instance.Spec.ResourceGroupName, instance.Name) + if err != nil { + return err + } + data := map[string][]byte{ + "primaryKey": []byte(*result.PrimaryKey), + "secondaryKey": []byte(*result.SecondaryKey), + } + + err = r.CreateSecrets( + ctx, + instance, + data, + ) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/secrets/interface.go b/pkg/secrets/interface.go index 4d53da3ea1f..fce3cea915e 100644 --- a/pkg/secrets/interface.go +++ b/pkg/secrets/interface.go @@ -9,7 +9,6 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" ) @@ -20,9 +19,14 @@ type SecretClient interface { Get(ctx context.Context, key types.NamespacedName) (map[string][]byte, error) } +type SecretOwner interface { + runtime.Object + metav1.Object +} + // Options contains the inputs available for passing to some methods of the secret clients type Options struct { - Owner metav1.Object + Owner SecretOwner Scheme *runtime.Scheme Activates *time.Time Expires *time.Time @@ -47,11 +51,10 @@ func WithExpiration(expireAfter *time.Time) SecretOption { } // WithOwner allows setting an owning instance in the options struct -func WithOwner(owner metav1.Object) SecretOption { +func WithOwner(owner SecretOwner) SecretOption { return func(op *Options) { op.Owner = owner } - } // WithScheme allows setting a runtime.Scheme in the options diff --git a/pkg/secrets/kube/client.go b/pkg/secrets/kube/client.go index b84c00fe0cd..bee9d12264c 100644 --- a/pkg/secrets/kube/client.go +++ b/pkg/secrets/kube/client.go @@ -86,6 +86,14 @@ func (k *KubeSecretClient) Upsert(ctx context.Context, key types.NamespacedName, } if options.Owner != nil && options.Scheme != nil { + // the uid is required for SetControllerReference, try to populate it if it isn't + if options.Owner.GetUID() == "" { + ownerKey := types.NamespacedName{Name: options.Owner.GetName(), Namespace: options.Owner.GetNamespace()} + if err := k.KubeClient.Get(ctx, ownerKey, options.Owner); err != nil { + return client.IgnoreNotFound(err) + } + } + if err := controllerutil.SetControllerReference(options.Owner, secret, options.Scheme); err != nil { return err }