diff --git a/v2/api/apimanagement/customizations/subscription_extensions.go b/v2/api/apimanagement/customizations/subscription_extensions.go index 52a671959b2..6405c32b25f 100644 --- a/v2/api/apimanagement/customizations/subscription_extensions.go +++ b/v2/api/apimanagement/customizations/subscription_extensions.go @@ -12,25 +12,31 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" apimanagement "github.com/Azure/azure-service-operator/v2/api/apimanagement/v1api20220801/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &SubscriptionExtension{} +const ( + primaryKey = "primaryKey" + secondaryKey = "secondaryKey" +) + +var _ genruntime.KubernetesSecretExporter = &SubscriptionExtension{} -func (ext *SubscriptionExtension) ExportKubernetesResources( +func (ext *SubscriptionExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*apimanagement.Subscription) @@ -42,8 +48,9 @@ func (ext *SubscriptionExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := secretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -60,7 +67,7 @@ func (ext *SubscriptionExtension) ExportKubernetesResources( // Only bother calling ListSecrets if there are secrets to retrieve var s armapimanagement.SubscriptionKeysContract - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -79,32 +86,45 @@ func (ext *SubscriptionExtension) ExportKubernetesResources( s = resp.SubscriptionKeysContract } + resolvedSecrets := map[string]string{} + if to.Value(s.PrimaryKey) != "" { + resolvedSecrets[primaryKey] = to.Value(s.PrimaryKey) + } + if to.Value(s.SecondaryKey) != "" { + resolvedSecrets[secondaryKey] = to.Value(s.SecondaryKey) + } secretSlice, err := secretsToWrite(typedObj, s) if err != nil { return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *apimanagement.Subscription) bool { +func secretsSpecified(obj *apimanagement.Subscription) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } - hasSecrets := false secrets := obj.Spec.OperatorSpec.Secrets - if secrets.PrimaryKey != nil || secrets.SecondaryKey != nil { - hasSecrets = true + result := make(set.Set[string]) + if secrets.PrimaryKey != nil { + result.Add(primaryKey) + } + if secrets.SecondaryKey != nil { + result.Add(secondaryKey) } - return hasSecrets + return result } func secretsToWrite(obj *apimanagement.Subscription, s armapimanagement.SubscriptionKeysContract) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/apimanagement/customizations/subscription_extensions_test.go b/v2/api/apimanagement/customizations/subscription_extensions_test.go new file mode 100644 index 00000000000..3168dc84fcb --- /dev/null +++ b/v2/api/apimanagement/customizations/subscription_extensions_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + apimanagement "github.com/Azure/azure-service-operator/v2/api/apimanagement/v1api20220801/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &apimanagement.SubscriptionOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &apimanagement.Subscription{ + Spec: apimanagement.Subscription_Spec{ + OperatorSpec: &apimanagement.SubscriptionOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := secretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(apimanagement.SubscriptionOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/appconfiguration/customizations/configuration_store_extensions.go b/v2/api/appconfiguration/customizations/configuration_store_extensions.go index aeda31980b9..e4dfdcd376b 100644 --- a/v2/api/appconfiguration/customizations/configuration_store_extensions.go +++ b/v2/api/appconfiguration/customizations/configuration_store_extensions.go @@ -13,24 +13,41 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" storage "github.com/Azure/azure-service-operator/v2/api/appconfiguration/v1api20220501/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" + "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &ConfigurationStoreExtension{} +const ( + primaryKeyID = "primaryKeyID" + secondaryKeyID = "secondaryKeyID" + primaryReadOnlyKeyID = "primaryReadOnlyKeyID" + secondaryReadOnlyKeyID = "secondaryReadOnlyKeyID" + primaryKey = "primaryKey" + secondaryKey = "secondaryKey" + primaryReadOnlyKey = "primaryReadOnlyKey" + secondaryReadOnlyKey = "secondaryReadOnlyKey" + primaryConnectionString = "primaryConnectionString" + secondaryConnectionString = "secondaryConnectionString" + primaryReadOnlyConnectionString = "primaryReadOnlyConnectionString" + secondaryReadOnlyConnectionString = "secondaryReadOnlyConnectionString" +) + +var _ genruntime.KubernetesSecretExporter = &ConfigurationStoreExtension{} -func (ext *ConfigurationStoreExtension) ExportKubernetesResources( +func (ext *ConfigurationStoreExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*storage.ConfigurationStore) @@ -42,8 +59,9 @@ func (ext *ConfigurationStoreExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := secretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -55,7 +73,7 @@ func (ext *ConfigurationStoreExtension) ExportKubernetesResources( keys := make(map[string]armappconfiguration.APIKey) // Only bother calling ListKeys if there are secrets to retrieve - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -70,16 +88,11 @@ func (ext *ConfigurationStoreExtension) ExportKubernetesResources( pager = confClient.NewListKeysPager(id.ResourceGroupName, typedObj.AzureName(), nil) for pager.More() { resp, err = pager.NextPage(ctx) + if err != nil { + return nil, errors.Wrapf(err, "failed to retreive response") + } addSecretsToMap(resp.Value, keys) - - } - if err != nil { - return nil, errors.Wrapf(err, "failed to retreive response") } - if err != nil { - return nil, errors.Wrapf(err, "failed listing keys") - } - } secretSlice, err := secretsToWrite(typedObj, keys) @@ -87,32 +100,59 @@ func (ext *ConfigurationStoreExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := makeResolvedSecretsMap(keys) + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *storage.ConfigurationStore) bool { +func secretsSpecified(obj *storage.ConfigurationStore) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } secrets := obj.Spec.OperatorSpec.Secrets - if secrets.PrimaryKeyID != nil || - secrets.SecondaryKeyID != nil || - secrets.PrimaryReadOnlyKeyID != nil || - secrets.SecondaryReadOnlyKeyID != nil || - secrets.PrimaryKey != nil || - secrets.SecondaryKey != nil || - secrets.PrimaryReadOnlyKey != nil || - secrets.SecondaryReadOnlyKey != nil || - secrets.PrimaryConnectionString != nil || - secrets.SecondaryConnectionString != nil || - secrets.PrimaryReadOnlyConnectionString != nil || - secrets.SecondaryReadOnlyConnectionString != nil { - return true - } - - return false + result := make(set.Set[string]) + if secrets.PrimaryKeyID != nil { + result.Add(primaryKeyID) + } + if secrets.SecondaryKeyID != nil { + result.Add(secondaryKeyID) + } + if secrets.PrimaryReadOnlyKeyID != nil { + result.Add(primaryReadOnlyKeyID) + } + if secrets.SecondaryReadOnlyKeyID != nil { + result.Add(secondaryReadOnlyKeyID) + } + if secrets.PrimaryKey != nil { + result.Add(primaryKey) + } + if secrets.SecondaryKey != nil { + result.Add(secondaryKey) + } + if secrets.PrimaryReadOnlyKey != nil { + result.Add(primaryReadOnlyKey) + } + if secrets.SecondaryReadOnlyKey != nil { + result.Add(secondaryReadOnlyKey) + } + if secrets.PrimaryConnectionString != nil { + result.Add(primaryConnectionString) + } + if secrets.SecondaryConnectionString != nil { + result.Add(secondaryConnectionString) + } + if secrets.PrimaryReadOnlyConnectionString != nil { + result.Add(primaryReadOnlyConnectionString) + } + if secrets.SecondaryReadOnlyConnectionString != nil { + result.Add(secondaryReadOnlyConnectionString) + } + return result } func addSecretsToMap(keys []*armappconfiguration.APIKey, result map[string]armappconfiguration.APIKey) { @@ -127,37 +167,70 @@ func addSecretsToMap(keys []*armappconfiguration.APIKey, result map[string]armap func secretsToWrite(obj *storage.ConfigurationStore, keys map[string]armappconfiguration.APIKey) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) primary, ok := keys["Primary"] if ok { - collector.AddValue(operatorSpecSecrets.PrimaryConnectionString, *primary.ConnectionString) - collector.AddValue(operatorSpecSecrets.PrimaryKeyID, *primary.ID) - collector.AddValue(operatorSpecSecrets.PrimaryKey, *primary.Value) + collector.AddValue(operatorSpecSecrets.PrimaryConnectionString, to.Value(primary.ConnectionString)) + collector.AddValue(operatorSpecSecrets.PrimaryKeyID, to.Value(primary.ID)) + collector.AddValue(operatorSpecSecrets.PrimaryKey, to.Value(primary.Value)) } primaryReadOnly, ok := keys["Primary Read Only"] if ok { - collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyConnectionString, *primaryReadOnly.ConnectionString) - collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyKeyID, *primaryReadOnly.ID) - collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyKey, *primaryReadOnly.Value) + collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyConnectionString, to.Value(primaryReadOnly.ConnectionString)) + collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyKeyID, to.Value(primaryReadOnly.ID)) + collector.AddValue(operatorSpecSecrets.PrimaryReadOnlyKey, to.Value(primaryReadOnly.Value)) } secondary, ok := keys["Secondary"] if ok { - collector.AddValue(operatorSpecSecrets.SecondaryConnectionString, *secondary.ConnectionString) - collector.AddValue(operatorSpecSecrets.SecondaryKeyID, *secondary.ID) - collector.AddValue(operatorSpecSecrets.SecondaryKey, *secondary.Value) + collector.AddValue(operatorSpecSecrets.SecondaryConnectionString, to.Value(secondary.ConnectionString)) + collector.AddValue(operatorSpecSecrets.SecondaryKeyID, to.Value(secondary.ID)) + collector.AddValue(operatorSpecSecrets.SecondaryKey, to.Value(secondary.Value)) } secondaryReadOnly, ok := keys["Secondary Read Only"] if ok { - collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyConnectionString, *secondaryReadOnly.ConnectionString) - collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyKeyID, *secondaryReadOnly.ID) - collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyKey, *secondaryReadOnly.Value) + collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyConnectionString, to.Value(secondaryReadOnly.ConnectionString)) + collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyKeyID, to.Value(secondaryReadOnly.ID)) + collector.AddValue(operatorSpecSecrets.SecondaryReadOnlyKey, to.Value(secondaryReadOnly.Value)) } return collector.Values() } + +func makeResolvedSecretsMap(keys map[string]armappconfiguration.APIKey) map[string]string { + result := make(map[string]string) + primary, ok := keys["Primary"] + if ok { + result[primaryConnectionString] = to.Value(primary.ConnectionString) + result[primaryKeyID] = to.Value(primary.ID) + result[primaryKey] = to.Value(primary.Value) + } + + primaryReadOnly, ok := keys["Primary Read Only"] + if ok { + result[primaryReadOnlyConnectionString] = to.Value(primaryReadOnly.ConnectionString) + result[primaryReadOnlyKeyID] = to.Value(primaryReadOnly.ID) + result[primaryReadOnlyKey] = to.Value(primaryReadOnly.Value) + } + + secondary, ok := keys["Secondary"] + if ok { + result[secondaryConnectionString] = to.Value(secondary.ConnectionString) + result[secondaryKeyID] = to.Value(secondary.ID) + result[secondaryKey] = to.Value(secondary.Value) + } + + secondaryReadOnly, ok := keys["Secondary Read Only"] + if ok { + result[secondaryReadOnlyConnectionString] = to.Value(secondaryReadOnly.ConnectionString) + result[secondaryReadOnlyKeyID] = to.Value(secondaryReadOnly.ID) + result[secondaryReadOnlyKey] = to.Value(secondaryReadOnly.Value) + } + + return result +} diff --git a/v2/api/appconfiguration/customizations/configuration_store_extensions_test.go b/v2/api/appconfiguration/customizations/configuration_store_extensions_test.go new file mode 100644 index 00000000000..cd6849aced2 --- /dev/null +++ b/v2/api/appconfiguration/customizations/configuration_store_extensions_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + appconfiguration "github.com/Azure/azure-service-operator/v2/api/appconfiguration/v1api20220501/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &appconfiguration.ConfigurationStoreOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &appconfiguration.ConfigurationStore{ + Spec: appconfiguration.ConfigurationStore_Spec{ + OperatorSpec: &appconfiguration.ConfigurationStoreOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := secretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(appconfiguration.ConfigurationStoreOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/cache/customizations/redis_extensions.go b/v2/api/cache/customizations/redis_extensions.go index 809692cfd2e..7f802308a76 100644 --- a/v2/api/cache/customizations/redis_extensions.go +++ b/v2/api/cache/customizations/redis_extensions.go @@ -13,26 +13,31 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" redis "github.com/Azure/azure-service-operator/v2/api/cache/v1api20230801/storage" - "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &RedisExtension{} +const ( + primaryKey = "primaryKey" + secondaryKey = "secondaryKey" +) + +var _ genruntime.KubernetesSecretExporter = &RedisExtension{} -func (ext *RedisExtension) ExportKubernetesResources( +func (ext *RedisExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*redis.Redis) @@ -44,8 +49,9 @@ func (ext *RedisExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets, hasEndpoints := secretsSpecified(typedObj) - if !hasSecrets && !hasEndpoints { + primarySecrets, hasEndpoints := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 && !hasEndpoints { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -57,7 +63,7 @@ func (ext *RedisExtension) ExportKubernetesResources( var accessKeys armredis.AccessKeys // Only bother calling ListKeys if there are secrets to retrieve - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -79,21 +85,34 @@ func (ext *RedisExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := map[string]string{} + if to.Value(accessKeys.PrimaryKey) != "" { + resolvedSecrets[primaryKey] = to.Value(accessKeys.PrimaryKey) + } + if to.Value(accessKeys.SecondaryKey) != "" { + resolvedSecrets[secondaryKey] = to.Value(accessKeys.SecondaryKey) + } + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *redis.Redis) (bool, bool) { +func secretsSpecified(obj *redis.Redis) (set.Set[string], bool) { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false, false + return nil, false } secrets := obj.Spec.OperatorSpec.Secrets - hasSecrets := false + result := make(set.Set[string]) hasEndpoints := false - if secrets.PrimaryKey != nil || - secrets.SecondaryKey != nil { - hasSecrets = true + if secrets.PrimaryKey != nil { + result.Add(primaryKey) + } + if secrets.SecondaryKey != nil { + result.Add(secondaryKey) } if secrets.HostName != nil || @@ -102,13 +121,13 @@ func secretsSpecified(obj *redis.Redis) (bool, bool) { hasEndpoints = true } - return hasSecrets, hasEndpoints + return result, hasEndpoints } func secretsToWrite(obj *redis.Redis, accessKeys armredis.AccessKeys) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/cache/customizations/redis_extensions_test.go b/v2/api/cache/customizations/redis_extensions_test.go new file mode 100644 index 00000000000..e2cb3f19530 --- /dev/null +++ b/v2/api/cache/customizations/redis_extensions_test.go @@ -0,0 +1,39 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "testing" + + . "github.com/onsi/gomega" + + redis "github.com/Azure/azure-service-operator/v2/api/cache/v1api20230801/storage" + "github.com/Azure/azure-service-operator/v2/internal/set" + "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &redis.RedisOperatorSecrets{} + reflect.PopulateStruct(secrets) + + obj := &redis.Redis{ + Spec: redis.Redis_Spec{ + OperatorSpec: &redis.RedisOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames, _ := secretsSpecified(obj) + expectedTags := set.Set[string]{ + primaryKey: {}, + secondaryKey: {}, + } + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/containerservice/customizations/managed_cluster_extensions.go b/v2/api/containerservice/customizations/managed_cluster_extensions.go index 3cb95756dc3..93ed54e9dfa 100644 --- a/v2/api/containerservice/customizations/managed_cluster_extensions.go +++ b/v2/api/containerservice/customizations/managed_cluster_extensions.go @@ -14,13 +14,11 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" containerservice "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001/storage" - . "github.com/Azure/azure-service-operator/v2/internal/logging" - "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" + . "github.com/Azure/azure-service-operator/v2/internal/logging" "github.com/Azure/azure-service-operator/v2/internal/resolver" "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" @@ -29,14 +27,20 @@ import ( "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &ManagedClusterExtension{} +const ( + adminCredentialsKey = "adminCredentials" + userCredentialsKey = "userCredentials" +) + +var _ genruntime.KubernetesSecretExporter = &ManagedClusterExtension{} -func (ext *ManagedClusterExtension) ExportKubernetesResources( +func (ext *ManagedClusterExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*containerservice.ManagedCluster) @@ -48,8 +52,10 @@ func (ext *ManagedClusterExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasAdminCreds, hasUserCreds := secretsSpecified(typedObj) - if !hasAdminCreds && !hasUserCreds { + primarySecrets := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -71,7 +77,7 @@ func (ext *ManagedClusterExtension) ExportKubernetesResources( // TODO: In the future we may need variants of these secret properties that configure usage of the public FQDN rather than the private one, see: // TODO: https://docs.microsoft.com/en-us/answers/questions/670332/azure-aks-get-credentials-using-wrong-hostname-for.html var adminCredentials string - if hasAdminCreds { + if requestedSecrets.Contains(adminCredentialsKey) { var resp armcontainerservice.ManagedClustersClientListClusterAdminCredentialsResponse resp, err = mcClient.ListClusterAdminCredentials(ctx, id.ResourceGroupName, typedObj.AzureName(), nil) if err != nil { @@ -85,7 +91,7 @@ func (ext *ManagedClusterExtension) ExportKubernetesResources( } var userCredentials string - if hasUserCreds { + if requestedSecrets.Contains(userCredentialsKey) { var resp armcontainerservice.ManagedClustersClientListClusterUserCredentialsResponse resp, err = mcClient.ListClusterUserCredentials(ctx, id.ResourceGroupName, typedObj.AzureName(), nil) if err != nil { @@ -103,22 +109,40 @@ func (ext *ManagedClusterExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := map[string]string{} + if adminCredentials != "" { + resolvedSecrets[adminCredentialsKey] = adminCredentials + } + if userCredentials != "" { + resolvedSecrets[userCredentialsKey] = userCredentials + } + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *containerservice.ManagedCluster) (bool, bool) { +func secretsSpecified(obj *containerservice.ManagedCluster) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false, false + return nil } secrets := obj.Spec.OperatorSpec.Secrets - return secrets.AdminCredentials != nil, secrets.UserCredentials != nil + result := set.Set[string]{} + if secrets.AdminCredentials != nil { + result.Add(adminCredentialsKey) + } + if secrets.UserCredentials != nil { + result.Add(userCredentialsKey) + } + + return result } func secretsToWrite(obj *containerservice.ManagedCluster, adminCreds string, userCreds string) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/containerservice/customizations/managed_cluster_extensions_test.go b/v2/api/containerservice/customizations/managed_cluster_extensions_test.go new file mode 100644 index 00000000000..658e82f9c7c --- /dev/null +++ b/v2/api/containerservice/customizations/managed_cluster_extensions_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + containerservice "github.com/Azure/azure-service-operator/v2/api/containerservice/v1api20231001/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &containerservice.ManagedClusterOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &containerservice.ManagedCluster{ + Spec: containerservice.ManagedCluster_Spec{ + OperatorSpec: &containerservice.ManagedClusterOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := secretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(containerservice.ManagedClusterOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/containerservice/v1api20230201/managed_cluster_types_gen.go b/v2/api/containerservice/v1api20230201/managed_cluster_types_gen.go index 04e9ba577db..a2b761f0be6 100644 --- a/v2/api/containerservice/v1api20230201/managed_cluster_types_gen.go +++ b/v2/api/containerservice/v1api20230201/managed_cluster_types_gen.go @@ -111,10 +111,10 @@ func (cluster *ManagedCluster) defaultAzureName() { // defaultImpl applies the code generated defaults to the ManagedCluster resource func (cluster *ManagedCluster) defaultImpl() { cluster.defaultAzureName() } -var _ genruntime.KubernetesExporter = &ManagedCluster{} +var _ genruntime.KubernetesConfigExporter = &ManagedCluster{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (cluster *ManagedCluster) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (cluster *ManagedCluster) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(cluster.Namespace) if cluster.Spec.OperatorSpec != nil && cluster.Spec.OperatorSpec.ConfigMaps != nil { if cluster.Status.OidcIssuerProfile != nil { diff --git a/v2/api/containerservice/v1api20230201/storage/managed_cluster_types_gen.go b/v2/api/containerservice/v1api20230201/storage/managed_cluster_types_gen.go index c51ba39376a..07bcf372387 100644 --- a/v2/api/containerservice/v1api20230201/storage/managed_cluster_types_gen.go +++ b/v2/api/containerservice/v1api20230201/storage/managed_cluster_types_gen.go @@ -70,10 +70,10 @@ func (cluster *ManagedCluster) ConvertTo(hub conversion.Hub) error { return cluster.AssignProperties_To_ManagedCluster(destination) } -var _ genruntime.KubernetesExporter = &ManagedCluster{} +var _ genruntime.KubernetesConfigExporter = &ManagedCluster{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (cluster *ManagedCluster) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (cluster *ManagedCluster) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(cluster.Namespace) if cluster.Spec.OperatorSpec != nil && cluster.Spec.OperatorSpec.ConfigMaps != nil { if cluster.Status.OidcIssuerProfile != nil { diff --git a/v2/api/containerservice/v1api20231001/managed_cluster_types_gen.go b/v2/api/containerservice/v1api20231001/managed_cluster_types_gen.go index 2b3378cc2aa..0d3e1c12809 100644 --- a/v2/api/containerservice/v1api20231001/managed_cluster_types_gen.go +++ b/v2/api/containerservice/v1api20231001/managed_cluster_types_gen.go @@ -108,10 +108,10 @@ func (cluster *ManagedCluster) InitializeSpec(status genruntime.ConvertibleStatu return fmt.Errorf("expected Status of type ManagedCluster_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &ManagedCluster{} +var _ genruntime.KubernetesConfigExporter = &ManagedCluster{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (cluster *ManagedCluster) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (cluster *ManagedCluster) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(cluster.Namespace) if cluster.Spec.OperatorSpec != nil && cluster.Spec.OperatorSpec.ConfigMaps != nil { if cluster.Status.OidcIssuerProfile != nil { diff --git a/v2/api/containerservice/v1api20231001/storage/managed_cluster_types_gen.go b/v2/api/containerservice/v1api20231001/storage/managed_cluster_types_gen.go index b2ea8e5fb1e..a7d6c6294f7 100644 --- a/v2/api/containerservice/v1api20231001/storage/managed_cluster_types_gen.go +++ b/v2/api/containerservice/v1api20231001/storage/managed_cluster_types_gen.go @@ -49,10 +49,10 @@ func (cluster *ManagedCluster) SetConditions(conditions conditions.Conditions) { cluster.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &ManagedCluster{} +var _ genruntime.KubernetesConfigExporter = &ManagedCluster{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (cluster *ManagedCluster) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (cluster *ManagedCluster) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(cluster.Namespace) if cluster.Spec.OperatorSpec != nil && cluster.Spec.OperatorSpec.ConfigMaps != nil { if cluster.Status.OidcIssuerProfile != nil { diff --git a/v2/api/containerservice/v1api20231102preview/managed_cluster_types_gen.go b/v2/api/containerservice/v1api20231102preview/managed_cluster_types_gen.go index e2811124430..d12982fab21 100644 --- a/v2/api/containerservice/v1api20231102preview/managed_cluster_types_gen.go +++ b/v2/api/containerservice/v1api20231102preview/managed_cluster_types_gen.go @@ -111,10 +111,10 @@ func (cluster *ManagedCluster) defaultAzureName() { // defaultImpl applies the code generated defaults to the ManagedCluster resource func (cluster *ManagedCluster) defaultImpl() { cluster.defaultAzureName() } -var _ genruntime.KubernetesExporter = &ManagedCluster{} +var _ genruntime.KubernetesConfigExporter = &ManagedCluster{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (cluster *ManagedCluster) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (cluster *ManagedCluster) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(cluster.Namespace) if cluster.Spec.OperatorSpec != nil && cluster.Spec.OperatorSpec.ConfigMaps != nil { if cluster.Status.OidcIssuerProfile != nil { diff --git a/v2/api/containerservice/v1api20231102preview/storage/managed_cluster_types_gen.go b/v2/api/containerservice/v1api20231102preview/storage/managed_cluster_types_gen.go index ae6275303be..92b1e9b3e98 100644 --- a/v2/api/containerservice/v1api20231102preview/storage/managed_cluster_types_gen.go +++ b/v2/api/containerservice/v1api20231102preview/storage/managed_cluster_types_gen.go @@ -70,10 +70,10 @@ func (cluster *ManagedCluster) ConvertTo(hub conversion.Hub) error { return cluster.AssignProperties_To_ManagedCluster(destination) } -var _ genruntime.KubernetesExporter = &ManagedCluster{} +var _ genruntime.KubernetesConfigExporter = &ManagedCluster{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (cluster *ManagedCluster) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (cluster *ManagedCluster) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(cluster.Namespace) if cluster.Spec.OperatorSpec != nil && cluster.Spec.OperatorSpec.ConfigMaps != nil { if cluster.Status.OidcIssuerProfile != nil { diff --git a/v2/api/containerservice/v1api20240402preview/managed_cluster_types_gen.go b/v2/api/containerservice/v1api20240402preview/managed_cluster_types_gen.go index 86cd5bf7873..5674e227548 100644 --- a/v2/api/containerservice/v1api20240402preview/managed_cluster_types_gen.go +++ b/v2/api/containerservice/v1api20240402preview/managed_cluster_types_gen.go @@ -111,10 +111,10 @@ func (cluster *ManagedCluster) defaultAzureName() { // defaultImpl applies the code generated defaults to the ManagedCluster resource func (cluster *ManagedCluster) defaultImpl() { cluster.defaultAzureName() } -var _ genruntime.KubernetesExporter = &ManagedCluster{} +var _ genruntime.KubernetesConfigExporter = &ManagedCluster{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (cluster *ManagedCluster) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (cluster *ManagedCluster) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(cluster.Namespace) if cluster.Spec.OperatorSpec != nil && cluster.Spec.OperatorSpec.ConfigMaps != nil { if cluster.Status.OidcIssuerProfile != nil { diff --git a/v2/api/containerservice/v1api20240402preview/storage/managed_cluster_types_gen.go b/v2/api/containerservice/v1api20240402preview/storage/managed_cluster_types_gen.go index 7cd5c4935a0..7f98367804b 100644 --- a/v2/api/containerservice/v1api20240402preview/storage/managed_cluster_types_gen.go +++ b/v2/api/containerservice/v1api20240402preview/storage/managed_cluster_types_gen.go @@ -83,10 +83,10 @@ func (cluster *ManagedCluster) ConvertTo(hub conversion.Hub) error { return nil } -var _ genruntime.KubernetesExporter = &ManagedCluster{} +var _ genruntime.KubernetesConfigExporter = &ManagedCluster{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (cluster *ManagedCluster) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (cluster *ManagedCluster) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(cluster.Namespace) if cluster.Spec.OperatorSpec != nil && cluster.Spec.OperatorSpec.ConfigMaps != nil { if cluster.Status.OidcIssuerProfile != nil { diff --git a/v2/api/dataprotection/v1api20230101/backup_vault_types_gen.go b/v2/api/dataprotection/v1api20230101/backup_vault_types_gen.go index 98c0718f618..a9a6e26ebeb 100644 --- a/v2/api/dataprotection/v1api20230101/backup_vault_types_gen.go +++ b/v2/api/dataprotection/v1api20230101/backup_vault_types_gen.go @@ -109,10 +109,10 @@ func (vault *BackupVault) defaultAzureName() { // defaultImpl applies the code generated defaults to the BackupVault resource func (vault *BackupVault) defaultImpl() { vault.defaultAzureName() } -var _ genruntime.KubernetesExporter = &BackupVault{} +var _ genruntime.KubernetesConfigExporter = &BackupVault{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (vault *BackupVault) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (vault *BackupVault) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(vault.Namespace) if vault.Spec.OperatorSpec != nil && vault.Spec.OperatorSpec.ConfigMaps != nil { if vault.Status.Identity != nil { diff --git a/v2/api/dataprotection/v1api20230101/storage/backup_vault_types_gen.go b/v2/api/dataprotection/v1api20230101/storage/backup_vault_types_gen.go index b998292c092..75566e41229 100644 --- a/v2/api/dataprotection/v1api20230101/storage/backup_vault_types_gen.go +++ b/v2/api/dataprotection/v1api20230101/storage/backup_vault_types_gen.go @@ -70,10 +70,10 @@ func (vault *BackupVault) ConvertTo(hub conversion.Hub) error { return vault.AssignProperties_To_BackupVault(destination) } -var _ genruntime.KubernetesExporter = &BackupVault{} +var _ genruntime.KubernetesConfigExporter = &BackupVault{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (vault *BackupVault) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (vault *BackupVault) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(vault.Namespace) if vault.Spec.OperatorSpec != nil && vault.Spec.OperatorSpec.ConfigMaps != nil { if vault.Status.Identity != nil { diff --git a/v2/api/dataprotection/v1api20231101/backup_vault_types_gen.go b/v2/api/dataprotection/v1api20231101/backup_vault_types_gen.go index 85f6ec6e091..ca81e02df8b 100644 --- a/v2/api/dataprotection/v1api20231101/backup_vault_types_gen.go +++ b/v2/api/dataprotection/v1api20231101/backup_vault_types_gen.go @@ -106,10 +106,10 @@ func (vault *BackupVault) InitializeSpec(status genruntime.ConvertibleStatus) er return fmt.Errorf("expected Status of type BackupVaultResource_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &BackupVault{} +var _ genruntime.KubernetesConfigExporter = &BackupVault{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (vault *BackupVault) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (vault *BackupVault) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(vault.Namespace) if vault.Spec.OperatorSpec != nil && vault.Spec.OperatorSpec.ConfigMaps != nil { if vault.Status.Identity != nil { diff --git a/v2/api/dataprotection/v1api20231101/storage/backup_vault_types_gen.go b/v2/api/dataprotection/v1api20231101/storage/backup_vault_types_gen.go index ddca2b203b9..742b2b8a8c5 100644 --- a/v2/api/dataprotection/v1api20231101/storage/backup_vault_types_gen.go +++ b/v2/api/dataprotection/v1api20231101/storage/backup_vault_types_gen.go @@ -49,10 +49,10 @@ func (vault *BackupVault) SetConditions(conditions conditions.Conditions) { vault.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &BackupVault{} +var _ genruntime.KubernetesConfigExporter = &BackupVault{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (vault *BackupVault) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (vault *BackupVault) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(vault.Namespace) if vault.Spec.OperatorSpec != nil && vault.Spec.OperatorSpec.ConfigMaps != nil { if vault.Status.Identity != nil { diff --git a/v2/api/dbformariadb/customizations/server_extensions.go b/v2/api/dbformariadb/customizations/server_extensions.go index 8726b72edb1..92710d2f0b7 100644 --- a/v2/api/dbformariadb/customizations/server_extensions.go +++ b/v2/api/dbformariadb/customizations/server_extensions.go @@ -11,25 +11,26 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" mariadb "github.com/Azure/azure-service-operator/v2/api/dbformariadb/v1api20180601/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &ServerExtension{} +var _ genruntime.KubernetesSecretExporter = &ServerExtension{} -func (ext *ServerExtension) ExportKubernetesResources( +func (ext *ServerExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current storage version. It will need to be updated // if the storage version changes. typedObj, ok := obj.(*mariadb.Server) @@ -52,7 +53,10 @@ func (ext *ServerExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: nil, // No actual secrets to return so no raw secrets + }, nil } func secretsSpecified(obj *mariadb.Server) bool { @@ -67,7 +71,7 @@ func secretsSpecified(obj *mariadb.Server) bool { func secretsToWrite(obj *mariadb.Server) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil OperatorSpec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/dbformysql/customizations/flexible_server_extensions.go b/v2/api/dbformysql/customizations/flexible_server_extensions.go index ed3f5e1f28d..c1d5a461868 100644 --- a/v2/api/dbformysql/customizations/flexible_server_extensions.go +++ b/v2/api/dbformysql/customizations/flexible_server_extensions.go @@ -11,25 +11,26 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" mysql "github.com/Azure/azure-service-operator/v2/api/dbformysql/v1api20230630/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &FlexibleServerExtension{} +var _ genruntime.KubernetesSecretExporter = &FlexibleServerExtension{} -func (ext *FlexibleServerExtension) ExportKubernetesResources( +func (ext *FlexibleServerExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*mysql.FlexibleServer) @@ -52,7 +53,10 @@ func (ext *FlexibleServerExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: nil, // No RawSecrets as all secret values are coming from status (not real secrets) + }, nil } func secretsSpecified(obj *mysql.FlexibleServer) bool { @@ -67,7 +71,7 @@ func secretsSpecified(obj *mysql.FlexibleServer) bool { func secretsToWrite(obj *mysql.FlexibleServer) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/dbformysql/v1api20210501/flexible_server_types_gen.go b/v2/api/dbformysql/v1api20210501/flexible_server_types_gen.go index f52a10efe6b..e59be2bdda3 100644 --- a/v2/api/dbformysql/v1api20210501/flexible_server_types_gen.go +++ b/v2/api/dbformysql/v1api20210501/flexible_server_types_gen.go @@ -111,10 +111,10 @@ func (server *FlexibleServer) defaultAzureName() { // defaultImpl applies the code generated defaults to the FlexibleServer resource func (server *FlexibleServer) defaultImpl() { server.defaultAzureName() } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.AdministratorLogin != nil { diff --git a/v2/api/dbformysql/v1api20210501/storage/flexible_server_types_gen.go b/v2/api/dbformysql/v1api20210501/storage/flexible_server_types_gen.go index 6c754ff9601..719cbf0c5a3 100644 --- a/v2/api/dbformysql/v1api20210501/storage/flexible_server_types_gen.go +++ b/v2/api/dbformysql/v1api20210501/storage/flexible_server_types_gen.go @@ -72,10 +72,10 @@ func (server *FlexibleServer) ConvertTo(hub conversion.Hub) error { return server.AssignProperties_To_FlexibleServer(destination) } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.AdministratorLogin != nil { diff --git a/v2/api/dbformysql/v1api20230630/flexible_server_types_gen.go b/v2/api/dbformysql/v1api20230630/flexible_server_types_gen.go index 98c50912093..cae0af57e78 100644 --- a/v2/api/dbformysql/v1api20230630/flexible_server_types_gen.go +++ b/v2/api/dbformysql/v1api20230630/flexible_server_types_gen.go @@ -108,10 +108,10 @@ func (server *FlexibleServer) InitializeSpec(status genruntime.ConvertibleStatus return fmt.Errorf("expected Status of type FlexibleServer_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.AdministratorLogin != nil { diff --git a/v2/api/dbformysql/v1api20230630/storage/flexible_server_types_gen.go b/v2/api/dbformysql/v1api20230630/storage/flexible_server_types_gen.go index bc5026559ed..46fc133d523 100644 --- a/v2/api/dbformysql/v1api20230630/storage/flexible_server_types_gen.go +++ b/v2/api/dbformysql/v1api20230630/storage/flexible_server_types_gen.go @@ -50,10 +50,10 @@ func (server *FlexibleServer) SetConditions(conditions conditions.Conditions) { server.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.AdministratorLogin != nil { diff --git a/v2/api/dbforpostgresql/customizations/flexible_server_extensions.go b/v2/api/dbforpostgresql/customizations/flexible_server_extensions.go index 564d5dcfbe9..68050375704 100644 --- a/v2/api/dbforpostgresql/customizations/flexible_server_extensions.go +++ b/v2/api/dbforpostgresql/customizations/flexible_server_extensions.go @@ -13,13 +13,11 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" postgresql "github.com/Azure/azure-service-operator/v2/api/dbforpostgresql/v1api20221201/storage" - . "github.com/Azure/azure-service-operator/v2/internal/logging" - "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" + . "github.com/Azure/azure-service-operator/v2/internal/logging" "github.com/Azure/azure-service-operator/v2/internal/resolver" "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" @@ -28,14 +26,15 @@ import ( "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &FlexibleServerExtension{} +var _ genruntime.KubernetesSecretExporter = &FlexibleServerExtension{} -func (ext *FlexibleServerExtension) ExportKubernetesResources( - _ context.Context, +func (ext *FlexibleServerExtension) ExportKubernetesSecrets( + ctx context.Context, obj genruntime.MetaObject, - _ *genericarmclient.GenericClient, + additionalSecrets set.Set[string], + armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*postgresql.FlexibleServer) @@ -58,7 +57,10 @@ func (ext *FlexibleServerExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: nil, // No RawSecrets as all secret values are coming from status (not real secrets) + }, nil } func secretsSpecified(obj *postgresql.FlexibleServer) bool { @@ -73,7 +75,7 @@ func secretsSpecified(obj *postgresql.FlexibleServer) bool { func secretsToWrite(obj *postgresql.FlexibleServer) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/dbforpostgresql/v1api20220120preview/flexible_server_types_gen.go b/v2/api/dbforpostgresql/v1api20220120preview/flexible_server_types_gen.go index 9f773127662..c11da7f6c1f 100644 --- a/v2/api/dbforpostgresql/v1api20220120preview/flexible_server_types_gen.go +++ b/v2/api/dbforpostgresql/v1api20220120preview/flexible_server_types_gen.go @@ -111,10 +111,10 @@ func (server *FlexibleServer) defaultAzureName() { // defaultImpl applies the code generated defaults to the FlexibleServer resource func (server *FlexibleServer) defaultImpl() { server.defaultAzureName() } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.FullyQualifiedDomainName != nil { diff --git a/v2/api/dbforpostgresql/v1api20220120preview/storage/flexible_server_types_gen.go b/v2/api/dbforpostgresql/v1api20220120preview/storage/flexible_server_types_gen.go index 4e79dbcc812..8e93b7166dd 100644 --- a/v2/api/dbforpostgresql/v1api20220120preview/storage/flexible_server_types_gen.go +++ b/v2/api/dbforpostgresql/v1api20220120preview/storage/flexible_server_types_gen.go @@ -84,10 +84,10 @@ func (server *FlexibleServer) ConvertTo(hub conversion.Hub) error { return nil } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.FullyQualifiedDomainName != nil { diff --git a/v2/api/dbforpostgresql/v1api20221201/flexible_server_types_gen.go b/v2/api/dbforpostgresql/v1api20221201/flexible_server_types_gen.go index 64e83e85bf9..9309e92f051 100644 --- a/v2/api/dbforpostgresql/v1api20221201/flexible_server_types_gen.go +++ b/v2/api/dbforpostgresql/v1api20221201/flexible_server_types_gen.go @@ -108,10 +108,10 @@ func (server *FlexibleServer) InitializeSpec(status genruntime.ConvertibleStatus return fmt.Errorf("expected Status of type FlexibleServer_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.FullyQualifiedDomainName != nil { diff --git a/v2/api/dbforpostgresql/v1api20221201/storage/flexible_server_types_gen.go b/v2/api/dbforpostgresql/v1api20221201/storage/flexible_server_types_gen.go index c554bc63856..ff36036916b 100644 --- a/v2/api/dbforpostgresql/v1api20221201/storage/flexible_server_types_gen.go +++ b/v2/api/dbforpostgresql/v1api20221201/storage/flexible_server_types_gen.go @@ -49,10 +49,10 @@ func (server *FlexibleServer) SetConditions(conditions conditions.Conditions) { server.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.FullyQualifiedDomainName != nil { diff --git a/v2/api/dbforpostgresql/v1api20230601preview/flexible_server_types_gen.go b/v2/api/dbforpostgresql/v1api20230601preview/flexible_server_types_gen.go index 46e6f153d52..dee845d03ff 100644 --- a/v2/api/dbforpostgresql/v1api20230601preview/flexible_server_types_gen.go +++ b/v2/api/dbforpostgresql/v1api20230601preview/flexible_server_types_gen.go @@ -111,10 +111,10 @@ func (server *FlexibleServer) defaultAzureName() { // defaultImpl applies the code generated defaults to the FlexibleServer resource func (server *FlexibleServer) defaultImpl() { server.defaultAzureName() } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.FullyQualifiedDomainName != nil { diff --git a/v2/api/dbforpostgresql/v1api20230601preview/storage/flexible_server_types_gen.go b/v2/api/dbforpostgresql/v1api20230601preview/storage/flexible_server_types_gen.go index e8b8d3104ac..13ed49e93fe 100644 --- a/v2/api/dbforpostgresql/v1api20230601preview/storage/flexible_server_types_gen.go +++ b/v2/api/dbforpostgresql/v1api20230601preview/storage/flexible_server_types_gen.go @@ -70,10 +70,10 @@ func (server *FlexibleServer) ConvertTo(hub conversion.Hub) error { return server.AssignProperties_To_FlexibleServer(destination) } -var _ genruntime.KubernetesExporter = &FlexibleServer{} +var _ genruntime.KubernetesConfigExporter = &FlexibleServer{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *FlexibleServer) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *FlexibleServer) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.FullyQualifiedDomainName != nil { diff --git a/v2/api/devices/customizations/iot_hub_extension.go b/v2/api/devices/customizations/iot_hub_extensions.go similarity index 52% rename from v2/api/devices/customizations/iot_hub_extension.go rename to v2/api/devices/customizations/iot_hub_extensions.go index a34a5440bae..c1c741479b3 100644 --- a/v2/api/devices/customizations/iot_hub_extension.go +++ b/v2/api/devices/customizations/iot_hub_extensions.go @@ -13,25 +13,39 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" devices "github.com/Azure/azure-service-operator/v2/api/devices/v1api20210702/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &IotHubExtension{} +const ( + iotHubOwnerPrimaryKey = "iotHubOwnerPrimaryKey" + iotHubOwnerSecondaryKey = "iotHubOwnerSecondaryKey" + servicePrimaryKey = "servicePrimaryKey" + serviceSecondaryKey = "serviceSecondaryKey" + registryReadWritePrimaryKey = "registryReadWritePrimaryKey" + registryReadWriteSecondaryKey = "registryReadWriteSecondaryKey" + registryReadPrimaryKey = "registryReadPrimaryKey" + registryReadSecondaryKey = "registryReadSecondaryKey" + devicePrimaryKey = "devicePrimaryKey" + deviceSecondaryKey = "deviceSecondaryKey" +) + +var _ genruntime.KubernetesSecretExporter = &IotHubExtension{} -func (ext *IotHubExtension) ExportKubernetesResources( +func (ext *IotHubExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub devices version. It will need to be updated // if the hub devices version changes. typedObj, ok := obj.(*devices.IotHub) @@ -43,8 +57,9 @@ func (ext *IotHubExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := secretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -56,7 +71,7 @@ func (ext *IotHubExtension) ExportKubernetesResources( keys := make(map[string]armiothub.SharedAccessSignatureAuthorizationRule) // Only bother calling ListKeys if there are secrets to retrieve - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -71,15 +86,11 @@ func (ext *IotHubExtension) ExportKubernetesResources( pager = resClient.NewListKeysPager(id.ResourceGroupName, typedObj.AzureName(), nil) for pager.More() { resp, err = pager.NextPage(ctx) + if err != nil { + return nil, errors.Wrapf(err, "failed listing keys") + } addSecretsToMap(resp.Value, keys) } - if err != nil { - return nil, errors.Wrapf(err, "failed to retreive response") - } - if err != nil { - return nil, errors.Wrapf(err, "failed listing keys") - } - } secretSlice, err := secretsToWrite(typedObj, keys) @@ -87,30 +98,54 @@ func (ext *IotHubExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := makeResolvedSecretsMap(keys) + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *devices.IotHub) bool { +func secretsSpecified(obj *devices.IotHub) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } secrets := obj.Spec.OperatorSpec.Secrets - if secrets.IotHubOwnerPrimaryKey != nil || - secrets.IotHubOwnerSecondaryKey != nil || - secrets.ServicePrimaryKey != nil || - secrets.ServiceSecondaryKey != nil || - secrets.RegistryReadWritePrimaryKey != nil || - secrets.RegistryReadWriteSecondaryKey != nil || - secrets.RegistryReadPrimaryKey != nil || - secrets.RegistryReadSecondaryKey != nil || - secrets.DevicePrimaryKey != nil || - secrets.DeviceSecondaryKey != nil { - return true + result := make(set.Set[string]) + if secrets.IotHubOwnerPrimaryKey != nil { + result.Add(iotHubOwnerPrimaryKey) + } + if secrets.IotHubOwnerSecondaryKey != nil { + result.Add(iotHubOwnerSecondaryKey) + } + if secrets.ServicePrimaryKey != nil { + result.Add(servicePrimaryKey) + } + if secrets.ServiceSecondaryKey != nil { + result.Add(serviceSecondaryKey) + } + if secrets.RegistryReadWritePrimaryKey != nil { + result.Add(registryReadWritePrimaryKey) + } + if secrets.RegistryReadWriteSecondaryKey != nil { + result.Add(registryReadWriteSecondaryKey) + } + if secrets.RegistryReadPrimaryKey != nil { + result.Add(registryReadPrimaryKey) + } + if secrets.RegistryReadSecondaryKey != nil { + result.Add(registryReadSecondaryKey) + } + if secrets.DevicePrimaryKey != nil { + result.Add(devicePrimaryKey) + } + if secrets.DeviceSecondaryKey != nil { + result.Add(deviceSecondaryKey) } - return false + return result } func addSecretsToMap(keys []*armiothub.SharedAccessSignatureAuthorizationRule, result map[string]armiothub.SharedAccessSignatureAuthorizationRule) { @@ -125,15 +160,15 @@ func addSecretsToMap(keys []*armiothub.SharedAccessSignatureAuthorizationRule, r func secretsToWrite(obj *devices.IotHub, keys map[string]armiothub.SharedAccessSignatureAuthorizationRule) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } // Documentation for keys : https://learn.microsoft.com/en-us/rest/api/iothub/iot-hub-resource/list-keys?tabs=HTTP#sharedaccesssignatureauthorizationrule collector := secrets.NewCollector(obj.Namespace) - iothubowner, ok := keys["iothubowner"] + iothubOwner, ok := keys["iothubowner"] if ok { - collector.AddValue(operatorSpecSecrets.IotHubOwnerPrimaryKey, to.Value(iothubowner.PrimaryKey)) - collector.AddValue(operatorSpecSecrets.IotHubOwnerSecondaryKey, to.Value(iothubowner.SecondaryKey)) + collector.AddValue(operatorSpecSecrets.IotHubOwnerPrimaryKey, to.Value(iothubOwner.PrimaryKey)) + collector.AddValue(operatorSpecSecrets.IotHubOwnerSecondaryKey, to.Value(iothubOwner.SecondaryKey)) } service, ok := keys["service"] @@ -162,3 +197,59 @@ func secretsToWrite(obj *devices.IotHub, keys map[string]armiothub.SharedAccessS return collector.Values() } + +func makeResolvedSecretsMap(keys map[string]armiothub.SharedAccessSignatureAuthorizationRule) map[string]string { + result := make(map[string]string) + + iothubOwner, ok := keys["iothubowner"] + if ok { + if to.Value(iothubOwner.PrimaryKey) != "" { + result[iotHubOwnerPrimaryKey] = to.Value(iothubOwner.PrimaryKey) + } + if to.Value(iothubOwner.SecondaryKey) != "" { + result[iotHubOwnerSecondaryKey] = to.Value(iothubOwner.SecondaryKey) + } + } + + service, ok := keys["service"] + if ok { + if to.Value(service.PrimaryKey) != "" { + result[servicePrimaryKey] = to.Value(service.PrimaryKey) + } + if to.Value(service.SecondaryKey) != "" { + result[serviceSecondaryKey] = to.Value(service.SecondaryKey) + } + } + + device, ok := keys["device"] + if ok { + if to.Value(device.PrimaryKey) != "" { + result[devicePrimaryKey] = to.Value(device.PrimaryKey) + } + if to.Value(device.SecondaryKey) != "" { + result[deviceSecondaryKey] = to.Value(device.SecondaryKey) + } + } + + registryRead, ok := keys["registryRead"] + if ok { + if to.Value(registryRead.PrimaryKey) != "" { + result[registryReadPrimaryKey] = to.Value(registryRead.PrimaryKey) + } + if to.Value(registryRead.SecondaryKey) != "" { + result[registryReadSecondaryKey] = to.Value(registryRead.SecondaryKey) + } + } + + registryReadWrite, ok := keys["registryReadWrite"] + if ok { + if to.Value(registryReadWrite.PrimaryKey) != "" { + result[registryReadWritePrimaryKey] = to.Value(registryReadWrite.PrimaryKey) + } + if to.Value(registryReadWrite.SecondaryKey) != "" { + result[registryReadWriteSecondaryKey] = to.Value(registryReadWrite.SecondaryKey) + } + } + + return result +} diff --git a/v2/api/devices/customizations/iot_hub_extensions_test.go b/v2/api/devices/customizations/iot_hub_extensions_test.go new file mode 100644 index 00000000000..1234f3554ce --- /dev/null +++ b/v2/api/devices/customizations/iot_hub_extensions_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + devices "github.com/Azure/azure-service-operator/v2/api/devices/v1api20210702/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &devices.IotHubOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &devices.IotHub{ + Spec: devices.IotHub_Spec{ + OperatorSpec: &devices.IotHubOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := secretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(devices.IotHubOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/documentdb/customizations/database_account_extensions.go b/v2/api/documentdb/customizations/database_account_extensions.go index 52caa15a378..d00300a3a5b 100644 --- a/v2/api/documentdb/customizations/database_account_extensions.go +++ b/v2/api/documentdb/customizations/database_account_extensions.go @@ -13,13 +13,13 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" documentdb "github.com/Azure/azure-service-operator/v2/api/documentdb/v1api20231115/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" "github.com/Azure/azure-service-operator/v2/internal/resolver" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/core" @@ -27,14 +27,22 @@ import ( "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &DatabaseAccountExtension{} +const ( + primaryMasterKeyKey = "primaryMasterKey" + secondaryMasterKeyKey = "secondaryMasterKey" + primaryReadonlyMasterKeyKey = "primaryReadonlyMasterKey" + secondaryReadonlyMasterKeyKey = "secondaryReadonlyMasterKey" +) + +var _ genruntime.KubernetesSecretExporter = &DatabaseAccountExtension{} -func (ext *DatabaseAccountExtension) ExportKubernetesResources( +func (ext *DatabaseAccountExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*documentdb.DatabaseAccount) @@ -46,8 +54,9 @@ func (ext *DatabaseAccountExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets, hasEndpoints := secretsSpecified(typedObj) - if !hasSecrets && !hasEndpoints { + primarySecrets, hasEndpoints := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 && !hasEndpoints { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -59,7 +68,7 @@ func (ext *DatabaseAccountExtension) ExportKubernetesResources( var keys armcosmos.DatabaseAccountListKeysResult // Only bother calling ListKeys if there are secrets to retrieve - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -80,40 +89,63 @@ func (ext *DatabaseAccountExtension) ExportKubernetesResources( keys = resp.DatabaseAccountListKeysResult } + resolvedSecrets := map[string]string{} + if to.Value(keys.PrimaryMasterKey) != "" { + resolvedSecrets[primaryMasterKeyKey] = to.Value(keys.PrimaryMasterKey) + } + if to.Value(keys.SecondaryMasterKey) != "" { + resolvedSecrets[secondaryMasterKeyKey] = to.Value(keys.SecondaryMasterKey) + } + if to.Value(keys.PrimaryReadonlyMasterKey) != "" { + resolvedSecrets[primaryReadonlyMasterKeyKey] = to.Value(keys.PrimaryReadonlyMasterKey) + } + if to.Value(keys.SecondaryReadonlyMasterKey) != "" { + resolvedSecrets[secondaryReadonlyMasterKeyKey] = to.Value(keys.SecondaryReadonlyMasterKey) + } + secretSlice, err := secretsToWrite(typedObj, keys) if err != nil { return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *documentdb.DatabaseAccount) (bool, bool) { +func secretsSpecified(obj *documentdb.DatabaseAccount) (set.Set[string], bool) { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false, false + return nil, false } specSecrets := obj.Spec.OperatorSpec.Secrets - hasSecrets := false hasEndpoints := false - if specSecrets.PrimaryMasterKey != nil || - specSecrets.SecondaryMasterKey != nil || - specSecrets.PrimaryReadonlyMasterKey != nil || - specSecrets.SecondaryReadonlyMasterKey != nil { - hasSecrets = true + result := make(set.Set[string]) + if specSecrets.PrimaryMasterKey != nil { + result.Add(primaryMasterKeyKey) + } + if specSecrets.SecondaryMasterKey != nil { + result.Add(secondaryMasterKeyKey) + } + if specSecrets.PrimaryReadonlyMasterKey != nil { + result.Add(primaryReadonlyMasterKeyKey) + } + if specSecrets.SecondaryReadonlyMasterKey != nil { + result.Add(secondaryReadonlyMasterKeyKey) } if specSecrets.DocumentEndpoint != nil { hasEndpoints = true } - return hasSecrets, hasEndpoints + return result, hasEndpoints } func secretsToWrite(obj *documentdb.DatabaseAccount, accessKeys armcosmos.DatabaseAccountListKeysResult) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/documentdb/customizations/database_account_extensions_test.go b/v2/api/documentdb/customizations/database_account_extensions_test.go new file mode 100644 index 00000000000..3d758b511a8 --- /dev/null +++ b/v2/api/documentdb/customizations/database_account_extensions_test.go @@ -0,0 +1,39 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + documentdb "github.com/Azure/azure-service-operator/v2/api/documentdb/v1api20231115/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &documentdb.DatabaseAccountOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + acct := &documentdb.DatabaseAccount{ + Spec: documentdb.DatabaseAccount_Spec{ + OperatorSpec: &documentdb.DatabaseAccountOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames, _ := secretsSpecified(acct) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(documentdb.DatabaseAccountOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + expectedTags.Remove("documentEndpoint") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/eventgrid/customizations/topic_extension.go b/v2/api/eventgrid/customizations/topic_extension.go index 2397791e2b1..5db3d379929 100644 --- a/v2/api/eventgrid/customizations/topic_extension.go +++ b/v2/api/eventgrid/customizations/topic_extension.go @@ -11,25 +11,31 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" "github.com/Azure/azure-service-operator/v2/api/eventgrid/v1api20200601/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &TopicExtension{} +const ( + key1 = "key1" + key2 = "key2" +) + +var _ genruntime.KubernetesSecretExporter = &TopicExtension{} -func (ext *TopicExtension) ExportKubernetesResources( +func (ext *TopicExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*storage.Topic) @@ -41,8 +47,10 @@ func (ext *TopicExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := secretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -72,28 +80,41 @@ func (ext *TopicExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := map[string]string{} + if to.Value(resp.Key1) != "" { + resolvedSecrets[key1] = to.Value(resp.Key1) + } + if to.Value(resp.Key2) != "" { + resolvedSecrets[key2] = to.Value(resp.Key2) + } + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *storage.Topic) bool { +func secretsSpecified(obj *storage.Topic) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } secrets := obj.Spec.OperatorSpec.Secrets - - if secrets.Key1 != nil || - secrets.Key2 != nil { - return true + result := make(set.Set[string]) + if secrets.Key1 != nil { + result.Add(key1) + } + if secrets.Key2 != nil { + result.Add(key2) } - return false + return result } func secretsToWrite(obj *storage.Topic, keys armeventgrid.TopicsClientListSharedAccessKeysResponse) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/eventgrid/customizations/topic_extension_test.go b/v2/api/eventgrid/customizations/topic_extension_test.go new file mode 100644 index 00000000000..1c8699eb9fd --- /dev/null +++ b/v2/api/eventgrid/customizations/topic_extension_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + eventgrid "github.com/Azure/azure-service-operator/v2/api/eventgrid/v1api20200601/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &eventgrid.TopicOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &eventgrid.Topic{ + Spec: eventgrid.Topic_Spec{ + OperatorSpec: &eventgrid.TopicOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := secretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(eventgrid.TopicOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/eventgrid/v1api20200601/storage/topic_types_gen.go b/v2/api/eventgrid/v1api20200601/storage/topic_types_gen.go index b2efe167fc4..c03e7934b42 100644 --- a/v2/api/eventgrid/v1api20200601/storage/topic_types_gen.go +++ b/v2/api/eventgrid/v1api20200601/storage/topic_types_gen.go @@ -49,10 +49,10 @@ func (topic *Topic) SetConditions(conditions conditions.Conditions) { topic.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &Topic{} +var _ genruntime.KubernetesConfigExporter = &Topic{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (topic *Topic) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (topic *Topic) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(topic.Namespace) if topic.Spec.OperatorSpec != nil && topic.Spec.OperatorSpec.ConfigMaps != nil { if topic.Status.Endpoint != nil { diff --git a/v2/api/eventgrid/v1api20200601/topic_types_gen.go b/v2/api/eventgrid/v1api20200601/topic_types_gen.go index 0637004e180..d491635ea88 100644 --- a/v2/api/eventgrid/v1api20200601/topic_types_gen.go +++ b/v2/api/eventgrid/v1api20200601/topic_types_gen.go @@ -107,10 +107,10 @@ func (topic *Topic) InitializeSpec(status genruntime.ConvertibleStatus) error { return fmt.Errorf("expected Status of type Topic_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &Topic{} +var _ genruntime.KubernetesConfigExporter = &Topic{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (topic *Topic) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (topic *Topic) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(topic.Namespace) if topic.Spec.OperatorSpec != nil && topic.Spec.OperatorSpec.ConfigMaps != nil { if topic.Status.Endpoint != nil { diff --git a/v2/api/eventhub/customizations/namespace_extension.go b/v2/api/eventhub/customizations/namespace_extension.go index 141798920ed..0010c099882 100644 --- a/v2/api/eventhub/customizations/namespace_extension.go +++ b/v2/api/eventhub/customizations/namespace_extension.go @@ -12,25 +12,26 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" "github.com/Azure/azure-service-operator/v2/api/eventhub/v1api20211101/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &NamespaceExtension{} +var _ genruntime.KubernetesSecretExporter = &NamespaceExtension{} -func (ext *NamespaceExtension) ExportKubernetesResources( +func (ext *NamespaceExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*storage.Namespace) @@ -42,8 +43,10 @@ func (ext *NamespaceExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := namespaceSecretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := namespaceSecretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -55,7 +58,7 @@ func (ext *NamespaceExtension) ExportKubernetesResources( // Only bother calling ListKeys if there are secrets to retrieve var res armeventhub.NamespacesClientListKeysResponse - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -78,30 +81,42 @@ func (ext *NamespaceExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := makeNamespacesResolvedSecretsMap(res.AccessKeys) + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func namespaceSecretsSpecified(obj *storage.Namespace) bool { +func namespaceSecretsSpecified(obj *storage.Namespace) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } secrets := obj.Spec.OperatorSpec.Secrets - if secrets.PrimaryKey != nil || - secrets.SecondaryKey != nil || - secrets.PrimaryConnectionString != nil || - secrets.SecondaryConnectionString != nil { - return true + result := make(set.Set[string]) + if secrets.PrimaryKey != nil { + result.Add(primaryKey) + } + if secrets.SecondaryKey != nil { + result.Add(secondaryKey) + } + if secrets.PrimaryConnectionString != nil { + result.Add(primaryConnectionString) + } + if secrets.SecondaryConnectionString != nil { + result.Add(secondaryConnectionString) } - return false + return result } func namespaceSecretsToWrite(obj *storage.Namespace, keys armeventhub.AccessKeys) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) @@ -113,3 +128,21 @@ func namespaceSecretsToWrite(obj *storage.Namespace, keys armeventhub.AccessKeys return collector.Values() } + +func makeNamespacesResolvedSecretsMap(accessKeys armeventhub.AccessKeys) map[string]string { + resolvedSecrets := map[string]string{} + if to.Value(accessKeys.PrimaryKey) != "" { + resolvedSecrets[primaryKey] = to.Value(accessKeys.PrimaryKey) + } + if to.Value(accessKeys.SecondaryKey) != "" { + resolvedSecrets[secondaryKey] = to.Value(accessKeys.SecondaryKey) + } + if to.Value(accessKeys.PrimaryConnectionString) != "" { + resolvedSecrets[primaryConnectionString] = to.Value(accessKeys.PrimaryConnectionString) + } + if to.Value(accessKeys.SecondaryConnectionString) != "" { + resolvedSecrets[secondaryConnectionString] = to.Value(accessKeys.SecondaryConnectionString) + } + + return resolvedSecrets +} diff --git a/v2/api/eventhub/customizations/namespace_extension_test.go b/v2/api/eventhub/customizations/namespace_extension_test.go new file mode 100644 index 00000000000..0b949680c80 --- /dev/null +++ b/v2/api/eventhub/customizations/namespace_extension_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + eventhub "github.com/Azure/azure-service-operator/v2/api/eventhub/v1api20211101/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_NamespaceSecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &eventhub.NamespaceOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &eventhub.Namespace{ + Spec: eventhub.Namespace_Spec{ + OperatorSpec: &eventhub.NamespaceOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := namespaceSecretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(eventhub.NamespaceOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/eventhub/customizations/namespaces_authorization_rule_extension.go b/v2/api/eventhub/customizations/namespaces_authorization_rule_extension.go index 05c49616242..a86fe407bee 100644 --- a/v2/api/eventhub/customizations/namespaces_authorization_rule_extension.go +++ b/v2/api/eventhub/customizations/namespaces_authorization_rule_extension.go @@ -12,25 +12,26 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" "github.com/Azure/azure-service-operator/v2/api/eventhub/v1api20211101/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &NamespacesAuthorizationRuleExtension{} +var _ genruntime.KubernetesSecretExporter = &NamespacesAuthorizationRuleExtension{} -func (ext *NamespacesAuthorizationRuleExtension) ExportKubernetesResources( +func (ext *NamespacesAuthorizationRuleExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*storage.NamespacesAuthorizationRule) @@ -42,8 +43,9 @@ func (ext *NamespacesAuthorizationRuleExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := namespacesAuthorizationRuleSecretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := namespacesAuthorizationRuleSecretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -55,7 +57,7 @@ func (ext *NamespacesAuthorizationRuleExtension) ExportKubernetesResources( // Only bother calling ListKeys if there are secrets to retrieve var res armeventhub.NamespacesClientListKeysResponse - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -76,30 +78,42 @@ func (ext *NamespacesAuthorizationRuleExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := makeNamespacesAuthorizationRuleResolvedSecretsMap(res.AccessKeys) + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func namespacesAuthorizationRuleSecretsSpecified(obj *storage.NamespacesAuthorizationRule) bool { +func namespacesAuthorizationRuleSecretsSpecified(obj *storage.NamespacesAuthorizationRule) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } secrets := obj.Spec.OperatorSpec.Secrets - if secrets.PrimaryKey != nil || - secrets.SecondaryKey != nil || - secrets.PrimaryConnectionString != nil || - secrets.SecondaryConnectionString != nil { - return true + result := make(set.Set[string]) + if secrets.PrimaryKey != nil { + result.Add(primaryKey) + } + if secrets.SecondaryKey != nil { + result.Add(secondaryKey) + } + if secrets.PrimaryConnectionString != nil { + result.Add(primaryConnectionString) + } + if secrets.SecondaryConnectionString != nil { + result.Add(secondaryConnectionString) } - return false + return result } func namespacesAuthorizationRuleSecretsToWrite(obj *storage.NamespacesAuthorizationRule, keys armeventhub.AccessKeys) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) @@ -111,3 +125,21 @@ func namespacesAuthorizationRuleSecretsToWrite(obj *storage.NamespacesAuthorizat return collector.Values() } + +func makeNamespacesAuthorizationRuleResolvedSecretsMap(accessKeys armeventhub.AccessKeys) map[string]string { + resolvedSecrets := map[string]string{} + if to.Value(accessKeys.PrimaryKey) != "" { + resolvedSecrets[primaryKey] = to.Value(accessKeys.PrimaryKey) + } + if to.Value(accessKeys.SecondaryKey) != "" { + resolvedSecrets[secondaryKey] = to.Value(accessKeys.SecondaryKey) + } + if to.Value(accessKeys.PrimaryConnectionString) != "" { + resolvedSecrets[primaryConnectionString] = to.Value(accessKeys.PrimaryConnectionString) + } + if to.Value(accessKeys.SecondaryConnectionString) != "" { + resolvedSecrets[secondaryConnectionString] = to.Value(accessKeys.SecondaryConnectionString) + } + + return resolvedSecrets +} diff --git a/v2/api/eventhub/customizations/namespaces_authorization_rule_extension_test.go b/v2/api/eventhub/customizations/namespaces_authorization_rule_extension_test.go new file mode 100644 index 00000000000..760e0ffb99d --- /dev/null +++ b/v2/api/eventhub/customizations/namespaces_authorization_rule_extension_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + eventhub "github.com/Azure/azure-service-operator/v2/api/eventhub/v1api20211101/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_NamespaceAuthorizationRuleSecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &eventhub.NamespacesAuthorizationRuleOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &eventhub.NamespacesAuthorizationRule{ + Spec: eventhub.NamespacesAuthorizationRule_Spec{ + OperatorSpec: &eventhub.NamespacesAuthorizationRuleOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := namespacesAuthorizationRuleSecretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(eventhub.NamespacesAuthorizationRuleOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/eventhub/customizations/namespaces_eventhubs_authorization_rule_extension.go b/v2/api/eventhub/customizations/namespaces_eventhubs_authorization_rule_extension.go index 8698f6b34e3..962abc7a3e3 100644 --- a/v2/api/eventhub/customizations/namespaces_eventhubs_authorization_rule_extension.go +++ b/v2/api/eventhub/customizations/namespaces_eventhubs_authorization_rule_extension.go @@ -12,25 +12,33 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" "github.com/Azure/azure-service-operator/v2/api/eventhub/v1api20211101/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &NamespacesEventhubsAuthorizationRuleExtension{} +const ( + primaryKey = "primaryKey" + secondaryKey = "secondaryKey" + primaryConnectionString = "primaryConnectionString" + secondaryConnectionString = "secondaryConnectionString" +) + +var _ genruntime.KubernetesSecretExporter = &NamespacesEventhubsAuthorizationRuleExtension{} -func (ext *NamespacesEventhubsAuthorizationRuleExtension) ExportKubernetesResources( +func (ext *NamespacesEventhubsAuthorizationRuleExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*storage.NamespacesEventhubsAuthorizationRule) @@ -42,8 +50,9 @@ func (ext *NamespacesEventhubsAuthorizationRuleExtension) ExportKubernetesResour // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := namespacesEventHubAuthorizationRuleSecretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := namespacesEventHubAuthorizationRuleSecretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -55,7 +64,7 @@ func (ext *NamespacesEventhubsAuthorizationRuleExtension) ExportKubernetesResour // Only bother calling ListKeys if there are secrets to retrieve var res armeventhub.EventHubsClientListKeysResponse - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -76,30 +85,42 @@ func (ext *NamespacesEventhubsAuthorizationRuleExtension) ExportKubernetesResour return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := makeNamespacesEventHubAuthorizationRuleResolvedSecretsMap(res.AccessKeys) + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func namespacesEventHubAuthorizationRuleSecretsSpecified(obj *storage.NamespacesEventhubsAuthorizationRule) bool { +func namespacesEventHubAuthorizationRuleSecretsSpecified(obj *storage.NamespacesEventhubsAuthorizationRule) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } secrets := obj.Spec.OperatorSpec.Secrets - if secrets.PrimaryKey != nil || - secrets.SecondaryKey != nil || - secrets.PrimaryConnectionString != nil || - secrets.SecondaryConnectionString != nil { - return true + result := make(set.Set[string]) + if secrets.PrimaryKey != nil { + result.Add(primaryKey) + } + if secrets.SecondaryKey != nil { + result.Add(secondaryKey) + } + if secrets.PrimaryConnectionString != nil { + result.Add(primaryConnectionString) + } + if secrets.SecondaryConnectionString != nil { + result.Add(secondaryConnectionString) } - return false + return result } func namespacesEventHubAuthorizationRuleSecretsToWrite(obj *storage.NamespacesEventhubsAuthorizationRule, keys armeventhub.AccessKeys) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) @@ -111,3 +132,21 @@ func namespacesEventHubAuthorizationRuleSecretsToWrite(obj *storage.NamespacesEv return collector.Values() } + +func makeNamespacesEventHubAuthorizationRuleResolvedSecretsMap(accessKeys armeventhub.AccessKeys) map[string]string { + resolvedSecrets := map[string]string{} + if to.Value(accessKeys.PrimaryKey) != "" { + resolvedSecrets[primaryKey] = to.Value(accessKeys.PrimaryKey) + } + if to.Value(accessKeys.SecondaryKey) != "" { + resolvedSecrets[secondaryKey] = to.Value(accessKeys.SecondaryKey) + } + if to.Value(accessKeys.PrimaryConnectionString) != "" { + resolvedSecrets[primaryConnectionString] = to.Value(accessKeys.PrimaryConnectionString) + } + if to.Value(accessKeys.SecondaryConnectionString) != "" { + resolvedSecrets[secondaryConnectionString] = to.Value(accessKeys.SecondaryConnectionString) + } + + return resolvedSecrets +} diff --git a/v2/api/eventhub/customizations/namespaces_eventhubs_authorization_rule_extension_test.go b/v2/api/eventhub/customizations/namespaces_eventhubs_authorization_rule_extension_test.go new file mode 100644 index 00000000000..42a89478c02 --- /dev/null +++ b/v2/api/eventhub/customizations/namespaces_eventhubs_authorization_rule_extension_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + eventhub "github.com/Azure/azure-service-operator/v2/api/eventhub/v1api20211101/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_NamespaceEventHubsAuthorizationRuleSecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &eventhub.NamespacesEventhubsAuthorizationRuleOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &eventhub.NamespacesEventhubsAuthorizationRule{ + Spec: eventhub.NamespacesEventhubsAuthorizationRule_Spec{ + OperatorSpec: &eventhub.NamespacesEventhubsAuthorizationRuleOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := namespacesEventHubAuthorizationRuleSecretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(eventhub.NamespacesEventhubsAuthorizationRuleOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/insights/v1api20200202/component_types_gen.go b/v2/api/insights/v1api20200202/component_types_gen.go index 9204d905b4b..08a226e0d40 100644 --- a/v2/api/insights/v1api20200202/component_types_gen.go +++ b/v2/api/insights/v1api20200202/component_types_gen.go @@ -106,10 +106,10 @@ func (component *Component) InitializeSpec(status genruntime.ConvertibleStatus) return fmt.Errorf("expected Status of type Component_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &Component{} +var _ genruntime.KubernetesConfigExporter = &Component{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (component *Component) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (component *Component) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(component.Namespace) if component.Spec.OperatorSpec != nil && component.Spec.OperatorSpec.ConfigMaps != nil { if component.Status.ConnectionString != nil { diff --git a/v2/api/insights/v1api20200202/storage/component_types_gen.go b/v2/api/insights/v1api20200202/storage/component_types_gen.go index cb4a58deb13..b2e5e2b9332 100644 --- a/v2/api/insights/v1api20200202/storage/component_types_gen.go +++ b/v2/api/insights/v1api20200202/storage/component_types_gen.go @@ -49,10 +49,10 @@ func (component *Component) SetConditions(conditions conditions.Conditions) { component.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &Component{} +var _ genruntime.KubernetesConfigExporter = &Component{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (component *Component) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (component *Component) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(component.Namespace) if component.Spec.OperatorSpec != nil && component.Spec.OperatorSpec.ConfigMaps != nil { if component.Status.ConnectionString != nil { diff --git a/v2/api/kubernetesconfiguration/v1api20230501/extension_types_gen.go b/v2/api/kubernetesconfiguration/v1api20230501/extension_types_gen.go index aef3444baad..e30e0d801e2 100644 --- a/v2/api/kubernetesconfiguration/v1api20230501/extension_types_gen.go +++ b/v2/api/kubernetesconfiguration/v1api20230501/extension_types_gen.go @@ -108,10 +108,10 @@ func (extension *Extension) InitializeSpec(status genruntime.ConvertibleStatus) return fmt.Errorf("expected Status of type Extension_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &Extension{} +var _ genruntime.KubernetesConfigExporter = &Extension{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (extension *Extension) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (extension *Extension) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(extension.Namespace) if extension.Spec.OperatorSpec != nil && extension.Spec.OperatorSpec.ConfigMaps != nil { if extension.Status.AksAssignedIdentity != nil { diff --git a/v2/api/kubernetesconfiguration/v1api20230501/storage/extension_types_gen.go b/v2/api/kubernetesconfiguration/v1api20230501/storage/extension_types_gen.go index c84aee0b7e6..54c3782c08b 100644 --- a/v2/api/kubernetesconfiguration/v1api20230501/storage/extension_types_gen.go +++ b/v2/api/kubernetesconfiguration/v1api20230501/storage/extension_types_gen.go @@ -50,10 +50,10 @@ func (extension *Extension) SetConditions(conditions conditions.Conditions) { extension.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &Extension{} +var _ genruntime.KubernetesConfigExporter = &Extension{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (extension *Extension) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (extension *Extension) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(extension.Namespace) if extension.Spec.OperatorSpec != nil && extension.Spec.OperatorSpec.ConfigMaps != nil { if extension.Status.AksAssignedIdentity != nil { diff --git a/v2/api/machinelearningservices/customizations/workspace_extension.go b/v2/api/machinelearningservices/customizations/workspace_extension.go index c2739813887..6bc72f122a2 100644 --- a/v2/api/machinelearningservices/customizations/workspace_extension.go +++ b/v2/api/machinelearningservices/customizations/workspace_extension.go @@ -13,12 +13,12 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" "github.com/Azure/azure-service-operator/v2/api/machinelearningservices/v1api20240401/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/core" @@ -26,14 +26,25 @@ import ( "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &WorkspaceExtension{} +const ( + appInsightsInstrumentationKey = "appInsightsInstrumentationKey" + primaryNotebookAccessKey = "primaryNotebookAccessKey" + secondaryNotebookAccessKey = "secondaryNotebookAccessKey" + userStorageKey = "userStorageKey" + containerRegistryPassword = "containerRegistryPassword" + containerRegistryPassword2 = "containerRegistryPassword2" + containerRegistryUserName = "containerRegistryUserName" +) + +var _ genruntime.KubernetesSecretExporter = &WorkspaceExtension{} -func (ext *WorkspaceExtension) ExportKubernetesResources( +func (ext *WorkspaceExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*storage.Workspace) @@ -45,8 +56,9 @@ func (ext *WorkspaceExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := secretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -58,7 +70,7 @@ func (ext *WorkspaceExtension) ExportKubernetesResources( var keys armmachinelearning.ListWorkspaceKeysResult // Only bother calling ListKeys if there are secrets to retrieve - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -81,33 +93,50 @@ func (ext *WorkspaceExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := makeResolvedSecrets(keys) + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *storage.Workspace) bool { +func secretsSpecified(obj *storage.Workspace) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } operatorSecrets := obj.Spec.OperatorSpec.Secrets - if operatorSecrets.AppInsightsInstrumentationKey != nil || - operatorSecrets.PrimaryNotebookAccessKey != nil || - operatorSecrets.SecondaryNotebookAccessKey != nil || - operatorSecrets.UserStorageKey != nil || - operatorSecrets.ContainerRegistryPassword != nil || - operatorSecrets.ContainerRegistryPassword2 != nil || - operatorSecrets.ContainerRegistryUserName != nil { - return true + result := make(set.Set[string]) + if operatorSecrets.AppInsightsInstrumentationKey != nil { + result.Add(appInsightsInstrumentationKey) + } + if operatorSecrets.PrimaryNotebookAccessKey != nil { + result.Add(primaryNotebookAccessKey) + } + if operatorSecrets.SecondaryNotebookAccessKey != nil { + result.Add(secondaryNotebookAccessKey) + } + if operatorSecrets.UserStorageKey != nil { + result.Add(userStorageKey) + } + if operatorSecrets.ContainerRegistryPassword != nil { + result.Add(containerRegistryPassword) + } + if operatorSecrets.ContainerRegistryPassword2 != nil { + result.Add(containerRegistryPassword2) + } + if operatorSecrets.ContainerRegistryUserName != nil { + result.Add(containerRegistryUserName) } - return false + return result } func secretsToWrite(obj *storage.Workspace, keysResp armmachinelearning.ListWorkspaceKeysResult) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) @@ -128,6 +157,37 @@ func secretsToWrite(obj *storage.Workspace, keysResp armmachinelearning.ListWork return collector.Values() } +func makeResolvedSecrets(keys armmachinelearning.ListWorkspaceKeysResult) map[string]string { + resolvedSecrets := map[string]string{} + if to.Value(keys.AppInsightsInstrumentationKey) != "" { + resolvedSecrets[appInsightsInstrumentationKey] = to.Value(keys.AppInsightsInstrumentationKey) + } + if keys.NotebookAccessKeys != nil { + if to.Value(keys.NotebookAccessKeys.PrimaryAccessKey) != "" { + resolvedSecrets[primaryNotebookAccessKey] = to.Value(keys.NotebookAccessKeys.PrimaryAccessKey) + } + if to.Value(keys.NotebookAccessKeys.SecondaryAccessKey) != "" { + resolvedSecrets[secondaryNotebookAccessKey] = to.Value(keys.NotebookAccessKeys.SecondaryAccessKey) + } + } + creds, crUsername := getContainerRegCreds(keys) + if creds["password"] != "" { + resolvedSecrets[containerRegistryPassword] = creds["password"] + } + if creds["password2"] != "" { + resolvedSecrets[containerRegistryPassword2] = creds["password2"] + } + if crUsername != "" { + resolvedSecrets[containerRegistryUserName] = crUsername + } + + if to.Value(keys.UserStorageKey) != "" { + resolvedSecrets[userStorageKey] = to.Value(keys.UserStorageKey) + } + + return resolvedSecrets +} + func getContainerRegCreds(keysResp armmachinelearning.ListWorkspaceKeysResult) (map[string]string, string) { creds := make(map[string]string) diff --git a/v2/api/machinelearningservices/customizations/workspace_extension_test.go b/v2/api/machinelearningservices/customizations/workspace_extension_test.go new file mode 100644 index 00000000000..2bd863de2a5 --- /dev/null +++ b/v2/api/machinelearningservices/customizations/workspace_extension_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + machinelearning "github.com/Azure/azure-service-operator/v2/api/machinelearningservices/v1api20240401/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &machinelearning.WorkspaceOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &machinelearning.Workspace{ + Spec: machinelearning.Workspace_Spec{ + OperatorSpec: &machinelearning.WorkspaceOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := secretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(machinelearning.WorkspaceOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/machinelearningservices/v1api20240401/registry_types_gen.go b/v2/api/machinelearningservices/v1api20240401/registry_types_gen.go index 910a4d6ceae..57de6cbcff7 100644 --- a/v2/api/machinelearningservices/v1api20240401/registry_types_gen.go +++ b/v2/api/machinelearningservices/v1api20240401/registry_types_gen.go @@ -107,10 +107,10 @@ func (registry *Registry) InitializeSpec(status genruntime.ConvertibleStatus) er return fmt.Errorf("expected Status of type RegistryTrackedResource_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &Registry{} +var _ genruntime.KubernetesConfigExporter = &Registry{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (registry *Registry) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (registry *Registry) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(registry.Namespace) if registry.Spec.OperatorSpec != nil && registry.Spec.OperatorSpec.ConfigMaps != nil { if registry.Status.DiscoveryUrl != nil { diff --git a/v2/api/machinelearningservices/v1api20240401/storage/registry_types_gen.go b/v2/api/machinelearningservices/v1api20240401/storage/registry_types_gen.go index 1b21692172e..c39d8d8b6db 100644 --- a/v2/api/machinelearningservices/v1api20240401/storage/registry_types_gen.go +++ b/v2/api/machinelearningservices/v1api20240401/storage/registry_types_gen.go @@ -49,10 +49,10 @@ func (registry *Registry) SetConditions(conditions conditions.Conditions) { registry.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &Registry{} +var _ genruntime.KubernetesConfigExporter = &Registry{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (registry *Registry) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (registry *Registry) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(registry.Namespace) if registry.Spec.OperatorSpec != nil && registry.Spec.OperatorSpec.ConfigMaps != nil { if registry.Status.DiscoveryUrl != nil { diff --git a/v2/api/managedidentity/customizations/user_assigned_identity_extention_authorization.go b/v2/api/managedidentity/customizations/user_assigned_identity_extention_authorization.go index 15c951ffbed..8c6b8b899c7 100644 --- a/v2/api/managedidentity/customizations/user_assigned_identity_extention_authorization.go +++ b/v2/api/managedidentity/customizations/user_assigned_identity_extention_authorization.go @@ -9,26 +9,27 @@ import ( "context" "fmt" + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/conversion" + v20230131s "github.com/Azure/azure-service-operator/v2/api/managedidentity/v1api20230131/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/conversion" ) -var _ genruntime.KubernetesExporter = &UserAssignedIdentityExtension{} +var _ genruntime.KubernetesSecretExporter = &UserAssignedIdentityExtension{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (identity *UserAssignedIdentityExtension) ExportKubernetesResources( +func (ext *UserAssignedIdentityExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + _ set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { typedObj, ok := obj.(*v20230131s.UserAssignedIdentity) if !ok { return nil, fmt.Errorf( @@ -56,7 +57,9 @@ func (identity *UserAssignedIdentityExtension) ExportKubernetesResources( if err != nil { return nil, err } - return secrets.SliceToClientObjectSlice(result), nil + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(result), + }, nil } func secretsSpecified(obj *v20230131s.UserAssignedIdentity) bool { diff --git a/v2/api/managedidentity/v1api20181130/storage/user_assigned_identity_types_gen.go b/v2/api/managedidentity/v1api20181130/storage/user_assigned_identity_types_gen.go index c1b8a333478..1c193abc8e3 100644 --- a/v2/api/managedidentity/v1api20181130/storage/user_assigned_identity_types_gen.go +++ b/v2/api/managedidentity/v1api20181130/storage/user_assigned_identity_types_gen.go @@ -70,10 +70,10 @@ func (identity *UserAssignedIdentity) ConvertTo(hub conversion.Hub) error { return identity.AssignProperties_To_UserAssignedIdentity(destination) } -var _ genruntime.KubernetesExporter = &UserAssignedIdentity{} +var _ genruntime.KubernetesConfigExporter = &UserAssignedIdentity{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (identity *UserAssignedIdentity) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (identity *UserAssignedIdentity) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(identity.Namespace) if identity.Spec.OperatorSpec != nil && identity.Spec.OperatorSpec.ConfigMaps != nil { if identity.Status.ClientId != nil { diff --git a/v2/api/managedidentity/v1api20181130/user_assigned_identity_types_gen.go b/v2/api/managedidentity/v1api20181130/user_assigned_identity_types_gen.go index 0eb94dbf6e9..64b3c43ef14 100644 --- a/v2/api/managedidentity/v1api20181130/user_assigned_identity_types_gen.go +++ b/v2/api/managedidentity/v1api20181130/user_assigned_identity_types_gen.go @@ -109,10 +109,10 @@ func (identity *UserAssignedIdentity) defaultAzureName() { // defaultImpl applies the code generated defaults to the UserAssignedIdentity resource func (identity *UserAssignedIdentity) defaultImpl() { identity.defaultAzureName() } -var _ genruntime.KubernetesExporter = &UserAssignedIdentity{} +var _ genruntime.KubernetesConfigExporter = &UserAssignedIdentity{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (identity *UserAssignedIdentity) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (identity *UserAssignedIdentity) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(identity.Namespace) if identity.Spec.OperatorSpec != nil && identity.Spec.OperatorSpec.ConfigMaps != nil { if identity.Status.ClientId != nil { diff --git a/v2/api/managedidentity/v1api20230131/storage/user_assigned_identity_types_gen.go b/v2/api/managedidentity/v1api20230131/storage/user_assigned_identity_types_gen.go index b3ca9631e1e..3a87b72e75e 100644 --- a/v2/api/managedidentity/v1api20230131/storage/user_assigned_identity_types_gen.go +++ b/v2/api/managedidentity/v1api20230131/storage/user_assigned_identity_types_gen.go @@ -49,10 +49,10 @@ func (identity *UserAssignedIdentity) SetConditions(conditions conditions.Condit identity.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &UserAssignedIdentity{} +var _ genruntime.KubernetesConfigExporter = &UserAssignedIdentity{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (identity *UserAssignedIdentity) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (identity *UserAssignedIdentity) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(identity.Namespace) if identity.Spec.OperatorSpec != nil && identity.Spec.OperatorSpec.ConfigMaps != nil { if identity.Status.ClientId != nil { diff --git a/v2/api/managedidentity/v1api20230131/user_assigned_identity_types_gen.go b/v2/api/managedidentity/v1api20230131/user_assigned_identity_types_gen.go index 2cdab0cbc18..64fee3ff772 100644 --- a/v2/api/managedidentity/v1api20230131/user_assigned_identity_types_gen.go +++ b/v2/api/managedidentity/v1api20230131/user_assigned_identity_types_gen.go @@ -107,10 +107,10 @@ func (identity *UserAssignedIdentity) InitializeSpec(status genruntime.Convertib return fmt.Errorf("expected Status of type UserAssignedIdentity_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &UserAssignedIdentity{} +var _ genruntime.KubernetesConfigExporter = &UserAssignedIdentity{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (identity *UserAssignedIdentity) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (identity *UserAssignedIdentity) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(identity.Namespace) if identity.Spec.OperatorSpec != nil && identity.Spec.OperatorSpec.ConfigMaps != nil { if identity.Status.ClientId != nil { diff --git a/v2/api/network/customizations/private_endpoints_extensions.go b/v2/api/network/customizations/private_endpoints_extensions.go index 979746ce6d4..7c062ba9472 100644 --- a/v2/api/network/customizations/private_endpoints_extensions.go +++ b/v2/api/network/customizations/private_endpoints_extensions.go @@ -67,7 +67,9 @@ func (extension *PrivateEndpointExtension) PostReconcileCheck( return extensions.PostReconcileCheckResultSuccess(), nil } -func (extension *PrivateEndpointExtension) ExportKubernetesResources( +var _ genruntime.KubernetesConfigExporter = &PrivateEndpointExtension{} + +func (extension *PrivateEndpointExtension) ExportKubernetesConfigMaps( ctx context.Context, obj genruntime.MetaObject, armClient *genericarmclient.GenericClient, diff --git a/v2/api/network/v1api20220401/storage/traffic_manager_profile_types_gen.go b/v2/api/network/v1api20220401/storage/traffic_manager_profile_types_gen.go index 1e2468dcb32..a98f8cf5e76 100644 --- a/v2/api/network/v1api20220401/storage/traffic_manager_profile_types_gen.go +++ b/v2/api/network/v1api20220401/storage/traffic_manager_profile_types_gen.go @@ -49,10 +49,10 @@ func (profile *TrafficManagerProfile) SetConditions(conditions conditions.Condit profile.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &TrafficManagerProfile{} +var _ genruntime.KubernetesConfigExporter = &TrafficManagerProfile{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (profile *TrafficManagerProfile) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (profile *TrafficManagerProfile) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(profile.Namespace) if profile.Spec.OperatorSpec != nil && profile.Spec.OperatorSpec.ConfigMaps != nil { if profile.Status.DnsConfig != nil { diff --git a/v2/api/network/v1api20220401/traffic_manager_profile_types_gen.go b/v2/api/network/v1api20220401/traffic_manager_profile_types_gen.go index 5c55db78576..74c71de9ab2 100644 --- a/v2/api/network/v1api20220401/traffic_manager_profile_types_gen.go +++ b/v2/api/network/v1api20220401/traffic_manager_profile_types_gen.go @@ -107,10 +107,10 @@ func (profile *TrafficManagerProfile) InitializeSpec(status genruntime.Convertib return fmt.Errorf("expected Status of type TrafficManagerProfile_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &TrafficManagerProfile{} +var _ genruntime.KubernetesConfigExporter = &TrafficManagerProfile{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (profile *TrafficManagerProfile) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (profile *TrafficManagerProfile) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(profile.Namespace) if profile.Spec.OperatorSpec != nil && profile.Spec.OperatorSpec.ConfigMaps != nil { if profile.Status.DnsConfig != nil { diff --git a/v2/api/network/v1api20220701/private_link_service_types_gen.go b/v2/api/network/v1api20220701/private_link_service_types_gen.go index c93d6e7ea4c..80193ab668b 100644 --- a/v2/api/network/v1api20220701/private_link_service_types_gen.go +++ b/v2/api/network/v1api20220701/private_link_service_types_gen.go @@ -107,10 +107,10 @@ func (service *PrivateLinkService) InitializeSpec(status genruntime.ConvertibleS return fmt.Errorf("expected Status of type PrivateLinkService_STATUS_PrivateLinkService_SubResourceEmbedded but received %T instead", status) } -var _ genruntime.KubernetesExporter = &PrivateLinkService{} +var _ genruntime.KubernetesConfigExporter = &PrivateLinkService{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (service *PrivateLinkService) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (service *PrivateLinkService) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(service.Namespace) if service.Spec.OperatorSpec != nil && service.Spec.OperatorSpec.ConfigMaps != nil { if service.Status.Alias != nil { diff --git a/v2/api/network/v1api20220701/storage/private_link_service_types_gen.go b/v2/api/network/v1api20220701/storage/private_link_service_types_gen.go index 72b2834b743..67071e302f9 100644 --- a/v2/api/network/v1api20220701/storage/private_link_service_types_gen.go +++ b/v2/api/network/v1api20220701/storage/private_link_service_types_gen.go @@ -49,10 +49,10 @@ func (service *PrivateLinkService) SetConditions(conditions conditions.Condition service.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &PrivateLinkService{} +var _ genruntime.KubernetesConfigExporter = &PrivateLinkService{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (service *PrivateLinkService) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (service *PrivateLinkService) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(service.Namespace) if service.Spec.OperatorSpec != nil && service.Spec.OperatorSpec.ConfigMaps != nil { if service.Status.Alias != nil { diff --git a/v2/api/search/customizations/search_service_extension.go b/v2/api/search/customizations/search_service_extension.go index 9e5b679f551..52c0312012d 100644 --- a/v2/api/search/customizations/search_service_extension.go +++ b/v2/api/search/customizations/search_service_extension.go @@ -13,25 +13,32 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" search "github.com/Azure/azure-service-operator/v2/api/search/v1api20220901/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &SearchServiceExtension{} +const ( + adminPrimaryKey = "adminPrimaryKey" + adminSecondaryKey = "adminSecondaryKey" + queryKey = "queryKey" +) + +var _ genruntime.KubernetesSecretExporter = &SearchServiceExtension{} -func (ext *SearchServiceExtension) ExportKubernetesResources( +func (ext *SearchServiceExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub devices version. It will need to be updated // if the hub devices version changes. typedObj, ok := obj.(*search.SearchService) @@ -43,8 +50,10 @@ func (ext *SearchServiceExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := secretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -57,7 +66,7 @@ func (ext *SearchServiceExtension) ExportKubernetesResources( queryKeys := make(map[string]armsearch.QueryKey) var adminKeys armsearch.AdminKeysClientGetResponse // Only bother calling ListKeys if there are secrets to retrieve - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -72,13 +81,12 @@ func (ext *SearchServiceExtension) ExportKubernetesResources( pager = queryKeysClient.NewListBySearchServicePager(id.ResourceGroupName, typedObj.AzureName(), nil, nil) for pager.More() { resp, err = pager.NextPage(ctx) + if err != nil { + return nil, errors.Wrapf(err, "failed listing query keys") + } addSecretsToMap(resp.Value, queryKeys) } - if err != nil { - return nil, errors.Wrapf(err, "failed listing query keys") - } - var adminKeysClient *armsearch.AdminKeysClient adminKeysClient, err = armsearch.NewAdminKeysClient(subscription, armClient.Creds(), armClient.ClientOptions()) if err != nil { @@ -96,23 +104,44 @@ func (ext *SearchServiceExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := map[string]string{} + defaultQueryKey, ok := queryKeys["default"] + if ok { + if to.Value(defaultQueryKey.Key) == "" { + resolvedSecrets[queryKey] = to.Value(defaultQueryKey.Key) + } + } + if to.Value(adminKeys.PrimaryKey) != "" { + resolvedSecrets[adminPrimaryKey] = to.Value(adminKeys.PrimaryKey) + } + if to.Value(adminKeys.SecondaryKey) != "" { + resolvedSecrets[adminSecondaryKey] = to.Value(adminKeys.PrimaryKey) + } + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *search.SearchService) bool { +func secretsSpecified(obj *search.SearchService) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } secrets := obj.Spec.OperatorSpec.Secrets - if secrets.AdminPrimaryKey != nil || - secrets.AdminSecondaryKey != nil || - secrets.QueryKey != nil { - return true + result := make(set.Set[string]) + if secrets.AdminPrimaryKey != nil { + result.Add(adminPrimaryKey) } - - return false + if secrets.AdminSecondaryKey != nil { + result.Add(adminSecondaryKey) + } + if secrets.QueryKey != nil { + result.Add(queryKey) + } + return result } func addSecretsToMap(keys []*armsearch.QueryKey, result map[string]armsearch.QueryKey) { @@ -134,7 +163,7 @@ func addSecretsToMap(keys []*armsearch.QueryKey, result map[string]armsearch.Que func secretsToWrite(obj *search.SearchService, queryKeys map[string]armsearch.QueryKey, adminKeys armsearch.AdminKeysClientGetResponse) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/search/customizations/search_service_extension_test.go b/v2/api/search/customizations/search_service_extension_test.go new file mode 100644 index 00000000000..c74f9ecc763 --- /dev/null +++ b/v2/api/search/customizations/search_service_extension_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + search "github.com/Azure/azure-service-operator/v2/api/search/v1api20220901/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &search.SearchServiceOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &search.SearchService{ + Spec: search.SearchService_Spec{ + OperatorSpec: &search.SearchServiceOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := secretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(search.SearchServiceOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/servicebus/customizations/namespace_extensions.go b/v2/api/servicebus/customizations/namespace_extensions.go index f258dc625f5..11700fb0628 100644 --- a/v2/api/servicebus/customizations/namespace_extensions.go +++ b/v2/api/servicebus/customizations/namespace_extensions.go @@ -8,32 +8,38 @@ package customizations import ( "context" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/servicebus/armservicebus" "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - - servicebus "github.com/Azure/azure-service-operator/v2/api/servicebus/v1api20211101/storage" - . "github.com/Azure/azure-service-operator/v2/internal/logging" - - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/servicebus/armservicebus" - + servicebus "github.com/Azure/azure-service-operator/v2/api/servicebus/v1api20211101/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" + . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &NamespaceExtension{} +const ( + endpoint = "endpoint" + primaryKey = "primaryKey" + primaryConnectionString = "primaryConnectionString" + secondaryKey = "secondaryKey" + secondaryConnectionString = "secondaryConnectionString" +) + +var _ genruntime.KubernetesSecretExporter = &NamespaceExtension{} -func (ext *NamespaceExtension) ExportKubernetesResources( +func (ext *NamespaceExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. namespace, ok := obj.(*servicebus.Namespace) @@ -45,8 +51,10 @@ func (ext *NamespaceExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = namespace - hasSecrets := namespaceSecretsSpecified(namespace) - if !hasSecrets { + primarySecrets := namespaceSecretsSpecified(namespace) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -90,21 +98,53 @@ func (ext *NamespaceExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := map[string]string{} + if to.Value(namespace.Status.ServiceBusEndpoint) != "" { + resolvedSecrets[endpoint] = to.Value(namespace.Status.ServiceBusEndpoint) + } + if to.Value(response.PrimaryKey) != "" { + resolvedSecrets[primaryKey] = to.Value(response.PrimaryKey) + } + if to.Value(response.PrimaryConnectionString) != "" { + resolvedSecrets[primaryConnectionString] = to.Value(response.PrimaryConnectionString) + } + if to.Value(response.SecondaryKey) != "" { + resolvedSecrets[secondaryKey] = to.Value(response.SecondaryKey) + } + if to.Value(response.SecondaryConnectionString) != "" { + resolvedSecrets[secondaryConnectionString] = to.Value(response.SecondaryConnectionString) + } + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func namespaceSecretsSpecified(obj *servicebus.Namespace) bool { +func namespaceSecretsSpecified(obj *servicebus.Namespace) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } specSecrets := obj.Spec.OperatorSpec.Secrets - return specSecrets.Endpoint != nil || - specSecrets.PrimaryKey != nil || - specSecrets.PrimaryConnectionString != nil || - specSecrets.SecondaryKey != nil || - specSecrets.SecondaryConnectionString != nil + result := make(set.Set[string]) + if specSecrets.Endpoint != nil { + result.Add(endpoint) + } + if specSecrets.PrimaryKey != nil { + result.Add(primaryKey) + } + if specSecrets.PrimaryConnectionString != nil { + result.Add(primaryConnectionString) + } + if specSecrets.SecondaryKey != nil { + result.Add(secondaryKey) + } + if specSecrets.SecondaryConnectionString != nil { + result.Add(secondaryConnectionString) + } + return result } func namespaceSecretsToWrite( @@ -113,15 +153,15 @@ func namespaceSecretsToWrite( ) ([]*v1.Secret, error) { specSecrets := obj.Spec.OperatorSpec.Secrets if specSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) collector.AddValue(specSecrets.Endpoint, to.Value(obj.Status.ServiceBusEndpoint)) - collector.AddValue(specSecrets.PrimaryKey, *response.PrimaryKey) - collector.AddValue(specSecrets.PrimaryConnectionString, *response.PrimaryConnectionString) - collector.AddValue(specSecrets.SecondaryKey, *response.SecondaryKey) - collector.AddValue(specSecrets.SecondaryConnectionString, *response.SecondaryConnectionString) + collector.AddValue(specSecrets.PrimaryKey, to.Value(response.PrimaryKey)) + collector.AddValue(specSecrets.PrimaryConnectionString, to.Value(response.PrimaryConnectionString)) + collector.AddValue(specSecrets.SecondaryKey, to.Value(response.SecondaryKey)) + collector.AddValue(specSecrets.SecondaryConnectionString, to.Value(response.SecondaryConnectionString)) return collector.Values() } diff --git a/v2/api/servicebus/customizations/namespace_extensions_test.go b/v2/api/servicebus/customizations/namespace_extensions_test.go new file mode 100644 index 00000000000..4ccd49c1609 --- /dev/null +++ b/v2/api/servicebus/customizations/namespace_extensions_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + servicebus "github.com/Azure/azure-service-operator/v2/api/servicebus/v1api20211101/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_NamespaceSecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &servicebus.NamespaceOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &servicebus.Namespace{ + Spec: servicebus.Namespace_Spec{ + OperatorSpec: &servicebus.NamespaceOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := namespaceSecretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(servicebus.NamespaceOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/servicebus/customizations/namespaces_authorization_rule_extensions.go b/v2/api/servicebus/customizations/namespaces_authorization_rule_extensions.go index d1e85351f2c..56b183f4c34 100644 --- a/v2/api/servicebus/customizations/namespaces_authorization_rule_extensions.go +++ b/v2/api/servicebus/customizations/namespaces_authorization_rule_extensions.go @@ -9,30 +9,29 @@ import ( "context" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/servicebus/armservicebus" - v1 "k8s.io/api/core/v1" - "github.com/go-logr/logr" "github.com/pkg/errors" - "sigs.k8s.io/controller-runtime/pkg/client" + v1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/conversion" servicebus "github.com/Azure/azure-service-operator/v2/api/servicebus/v1api20211101/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" + "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -// Ensure that NamespacesAuthorizationRuleExtension implements the KubernetesExporter interface -var _ genruntime.KubernetesExporter = &NamespacesAuthorizationRuleExtension{} +var _ genruntime.KubernetesSecretExporter = &NamespacesAuthorizationRuleExtension{} -// ExportKubernetesResources implements genruntime.KubernetesExporter -func (*NamespacesAuthorizationRuleExtension) ExportKubernetesResources( +func (ext *NamespacesAuthorizationRuleExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // Make sure we're working with the current hub version of the resource // This will need to be updated if the hub version changes rule, ok := obj.(*servicebus.NamespacesAuthorizationRule) @@ -46,8 +45,9 @@ func (*NamespacesAuthorizationRuleExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = rule - hasSecrets := authorizationRuleSecretsSpecified(rule) - if !hasSecrets { + primarySecrets := authorizationRuleSecretsSpecified(rule) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -85,21 +85,49 @@ func (*NamespacesAuthorizationRuleExtension) ExportKubernetesResources( rule.Name) } - return secrets.SliceToClientObjectSlice(ruleSecrets), nil + resolvedSecrets := map[string]string{} + if to.Value(response.PrimaryKey) != "" { + resolvedSecrets[primaryKey] = to.Value(response.PrimaryKey) + } + if to.Value(response.PrimaryConnectionString) != "" { + resolvedSecrets[primaryConnectionString] = to.Value(response.PrimaryConnectionString) + } + if to.Value(response.SecondaryKey) != "" { + resolvedSecrets[secondaryKey] = to.Value(response.SecondaryKey) + } + if to.Value(response.SecondaryConnectionString) != "" { + resolvedSecrets[secondaryConnectionString] = to.Value(response.SecondaryConnectionString) + } + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(ruleSecrets), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func authorizationRuleSecretsSpecified(rule *servicebus.NamespacesAuthorizationRule) bool { +func authorizationRuleSecretsSpecified(rule *servicebus.NamespacesAuthorizationRule) set.Set[string] { if rule.Spec.OperatorSpec == nil || rule.Spec.OperatorSpec.Secrets == nil { - return false + return nil } secrets := rule.Spec.OperatorSpec.Secrets - return secrets.PrimaryKey != nil || - secrets.PrimaryConnectionString != nil || - secrets.SecondaryKey != nil || - secrets.SecondaryConnectionString != nil + result := make(set.Set[string]) + if secrets.PrimaryKey != nil { + result.Add(primaryKey) + } + if secrets.PrimaryConnectionString != nil { + result.Add(primaryConnectionString) + } + if secrets.SecondaryKey != nil { + result.Add(secondaryKey) + } + if secrets.SecondaryConnectionString != nil { + result.Add(secondaryConnectionString) + } + + return result } func authorizationRuleSecretsToWrite( diff --git a/v2/api/servicebus/customizations/namespaces_authorization_rule_extensions_test.go b/v2/api/servicebus/customizations/namespaces_authorization_rule_extensions_test.go new file mode 100644 index 00000000000..12e9f6698d1 --- /dev/null +++ b/v2/api/servicebus/customizations/namespaces_authorization_rule_extensions_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + servicebus "github.com/Azure/azure-service-operator/v2/api/servicebus/v1api20211101/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_NamespaceAuthorizationRuleSecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &servicebus.NamespacesAuthorizationRuleOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &servicebus.NamespacesAuthorizationRule{ + Spec: servicebus.NamespacesAuthorizationRule_Spec{ + OperatorSpec: &servicebus.NamespacesAuthorizationRuleOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := authorizationRuleSecretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(servicebus.NamespacesAuthorizationRuleOperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/signalrservice/customizations/signal_r_extention_authorization.go b/v2/api/signalrservice/customizations/signal_r_extension_authorization.go similarity index 59% rename from v2/api/signalrservice/customizations/signal_r_extention_authorization.go rename to v2/api/signalrservice/customizations/signal_r_extension_authorization.go index 340c8844d7b..5cb52a7b472 100644 --- a/v2/api/signalrservice/customizations/signal_r_extention_authorization.go +++ b/v2/api/signalrservice/customizations/signal_r_extension_authorization.go @@ -12,27 +12,33 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" signalr "github.com/Azure/azure-service-operator/v2/api/signalrservice/v1api20211001/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -// Ensure that SignalRAuthorizationExtension implements the KubernetesExporter interface -var _ genruntime.KubernetesExporter = &SignalRExtension{} +const ( + primaryKey = "primaryKey" + secondaryKey = "secondaryKey" + primaryConnectionString = "primaryConnectionString" + secondaryConnectionString = "secondaryConnectionString" +) + +var _ genruntime.KubernetesSecretExporter = &SignalRExtension{} -// ExportKubernetesResources implements genruntime.KubernetesExporter -func (*SignalRExtension) ExportKubernetesResources( +func (ext *SignalRExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // Make sure we're working with the current hub version of the resource // This will need to be updated if the hub version changes typedObj, ok := obj.(*signalr.SignalR) @@ -45,8 +51,10 @@ func (*SignalRExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets := secretsSpecified(typedObj) - if !hasSecrets { + primarySecrets := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + + if len(requestedSecrets) == 0 { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -73,30 +81,41 @@ func (*SignalRExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + resolvedSecrets := makeResolvedSecretsMap(res.Keys) + + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, resolvedSecrets), + }, nil } -func secretsSpecified(obj *signalr.SignalR) bool { +func secretsSpecified(obj *signalr.SignalR) set.Set[string] { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false + return nil } specSecrets := obj.Spec.OperatorSpec.Secrets - hasSecrets := false - if specSecrets.PrimaryKey != nil || - specSecrets.SecondaryKey != nil || - specSecrets.PrimaryConnectionString != nil || - specSecrets.SecondaryConnectionString != nil { - hasSecrets = true + result := make(set.Set[string]) + if specSecrets.PrimaryKey != nil { + result.Add(primaryKey) + } + if specSecrets.SecondaryKey != nil { + result.Add(secondaryKey) + } + if specSecrets.PrimaryConnectionString != nil { + result.Add(primaryConnectionString) + } + if specSecrets.SecondaryConnectionString != nil { + result.Add(secondaryConnectionString) } - return hasSecrets + return result } func secretsToWrite(obj *signalr.SignalR, accessKeys armsignalr.Keys) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) @@ -107,3 +126,21 @@ func secretsToWrite(obj *signalr.SignalR, accessKeys armsignalr.Keys) ([]*v1.Sec return collector.Values() } + +func makeResolvedSecretsMap(accessKeys armsignalr.Keys) map[string]string { + resolvedSecrets := map[string]string{} + if to.Value(accessKeys.PrimaryKey) != "" { + resolvedSecrets[primaryKey] = to.Value(accessKeys.PrimaryKey) + } + if to.Value(accessKeys.SecondaryKey) != "" { + resolvedSecrets[secondaryKey] = to.Value(accessKeys.SecondaryKey) + } + if to.Value(accessKeys.PrimaryConnectionString) != "" { + resolvedSecrets[primaryConnectionString] = to.Value(accessKeys.PrimaryConnectionString) + } + if to.Value(accessKeys.SecondaryConnectionString) != "" { + resolvedSecrets[secondaryConnectionString] = to.Value(accessKeys.SecondaryConnectionString) + } + + return resolvedSecrets +} diff --git a/v2/api/signalrservice/customizations/signal_r_extension_authorization_test.go b/v2/api/signalrservice/customizations/signal_r_extension_authorization_test.go new file mode 100644 index 00000000000..25fe8762ebe --- /dev/null +++ b/v2/api/signalrservice/customizations/signal_r_extension_authorization_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "reflect" + "testing" + + . "github.com/onsi/gomega" + + signalr "github.com/Azure/azure-service-operator/v2/api/signalrservice/v1api20211001/storage" + "github.com/Azure/azure-service-operator/v2/internal/reflecthelpers" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &signalr.SignalROperatorSecrets{} + testreflect.PopulateStruct(secrets) + + obj := &signalr.SignalR{ + Spec: signalr.SignalR_Spec{ + OperatorSpec: &signalr.SignalROperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames := secretsSpecified(obj) + expectedTags := reflecthelpers.GetJSONTags(reflect.TypeOf(signalr.SignalROperatorSecrets{})) + expectedTags.Remove("$propertyBag") + + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/sql/v1api20211101/server_types_gen.go b/v2/api/sql/v1api20211101/server_types_gen.go index aea31a9f798..04aa2ad43c8 100644 --- a/v2/api/sql/v1api20211101/server_types_gen.go +++ b/v2/api/sql/v1api20211101/server_types_gen.go @@ -106,10 +106,10 @@ func (server *Server) InitializeSpec(status genruntime.ConvertibleStatus) error return fmt.Errorf("expected Status of type Server_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &Server{} +var _ genruntime.KubernetesConfigExporter = &Server{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *Server) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *Server) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.FullyQualifiedDomainName != nil { diff --git a/v2/api/sql/v1api20211101/storage/server_types_gen.go b/v2/api/sql/v1api20211101/storage/server_types_gen.go index e46f133aa1f..6c7e85d49c7 100644 --- a/v2/api/sql/v1api20211101/storage/server_types_gen.go +++ b/v2/api/sql/v1api20211101/storage/server_types_gen.go @@ -49,10 +49,10 @@ func (server *Server) SetConditions(conditions conditions.Conditions) { server.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &Server{} +var _ genruntime.KubernetesConfigExporter = &Server{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (server *Server) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (server *Server) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(server.Namespace) if server.Spec.OperatorSpec != nil && server.Spec.OperatorSpec.ConfigMaps != nil { if server.Status.FullyQualifiedDomainName != nil { diff --git a/v2/api/storage/customizations/storage_account_extensions.go b/v2/api/storage/customizations/storage_account_extensions.go index 16f0d558677..ed32788cdf4 100644 --- a/v2/api/storage/customizations/storage_account_extensions.go +++ b/v2/api/storage/customizations/storage_account_extensions.go @@ -12,25 +12,31 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/conversion" "github.com/Azure/azure-service-operator/v2/api/storage/v1api20230101/storage" "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" "github.com/Azure/azure-service-operator/v2/internal/util/to" "github.com/Azure/azure-service-operator/v2/pkg/genruntime" "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" ) -var _ genruntime.KubernetesExporter = &StorageAccountExtension{} +const ( + key1 = "key1" + key2 = "key2" +) + +var _ genruntime.KubernetesSecretExporter = &StorageAccountExtension{} -func (ext *StorageAccountExtension) ExportKubernetesResources( +func (ext *StorageAccountExtension) ExportKubernetesSecrets( ctx context.Context, obj genruntime.MetaObject, + additionalSecrets set.Set[string], armClient *genericarmclient.GenericClient, log logr.Logger, -) ([]client.Object, error) { +) (*genruntime.KubernetesSecretExportResult, error) { // This has to be the current hub storage version. It will need to be updated // if the hub storage version changes. typedObj, ok := obj.(*storage.StorageAccount) @@ -42,8 +48,9 @@ func (ext *StorageAccountExtension) ExportKubernetesResources( // the hub type has been changed but this extension has not var _ conversion.Hub = typedObj - hasSecrets, hasEndpoints := secretsSpecified(typedObj) - if !hasSecrets && !hasEndpoints { + primarySecrets, hasEndpoints := secretsSpecified(typedObj) + requestedSecrets := set.Union(primarySecrets, additionalSecrets) + if len(requestedSecrets) == 0 && !hasEndpoints { log.V(Debug).Info("No secrets retrieval to perform as operatorSpec is empty") return nil, nil } @@ -55,7 +62,7 @@ func (ext *StorageAccountExtension) ExportKubernetesResources( keys := make(map[string]string) // Only bother calling ListKeys if there are secrets to retrieve - if hasSecrets { + if len(requestedSecrets) > 0 { subscription := id.SubscriptionID // Using armClient.ClientOptions() here ensures we share the same HTTP connection, so this is not opening a new // connection each time through @@ -79,19 +86,25 @@ func (ext *StorageAccountExtension) ExportKubernetesResources( return nil, err } - return secrets.SliceToClientObjectSlice(secretSlice), nil + return &genruntime.KubernetesSecretExportResult{ + Objs: secrets.SliceToClientObjectSlice(secretSlice), + RawSecrets: secrets.SelectSecrets(additionalSecrets, keys), + }, nil } -func secretsSpecified(obj *storage.StorageAccount) (bool, bool) { +func secretsSpecified(obj *storage.StorageAccount) (set.Set[string], bool) { if obj.Spec.OperatorSpec == nil || obj.Spec.OperatorSpec.Secrets == nil { - return false, false + return nil, false } - hasSecrets := false hasEndpoints := false secrets := obj.Spec.OperatorSpec.Secrets - if secrets.Key1 != nil || secrets.Key2 != nil { - hasSecrets = true + result := make(set.Set[string]) + if secrets.Key1 != nil { + result.Add(key1) + } + if secrets.Key2 != nil { + result.Add(key2) } if secrets.BlobEndpoint != nil || @@ -103,7 +116,7 @@ func secretsSpecified(obj *storage.StorageAccount) (bool, bool) { hasEndpoints = true } - return hasSecrets, hasEndpoints + return result, hasEndpoints } func secretsByName(keys []*armstorage.AccountKey) map[string]string { @@ -122,7 +135,7 @@ func secretsByName(keys []*armstorage.AccountKey) map[string]string { func secretsToWrite(obj *storage.StorageAccount, keys map[string]string) ([]*v1.Secret, error) { operatorSpecSecrets := obj.Spec.OperatorSpec.Secrets if operatorSpecSecrets == nil { - return nil, errors.Errorf("unexpected nil operatorspec") + return nil, nil } collector := secrets.NewCollector(obj.Namespace) diff --git a/v2/api/storage/customizations/storage_account_extensions_test.go b/v2/api/storage/customizations/storage_account_extensions_test.go new file mode 100644 index 00000000000..631605b2ec7 --- /dev/null +++ b/v2/api/storage/customizations/storage_account_extensions_test.go @@ -0,0 +1,38 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package customizations + +import ( + "testing" + + . "github.com/onsi/gomega" + + "github.com/Azure/azure-service-operator/v2/api/storage/v1api20230101/storage" + "github.com/Azure/azure-service-operator/v2/internal/set" + testreflect "github.com/Azure/azure-service-operator/v2/internal/testcommon/reflect" +) + +func Test_SecretsSpecified_AllSecretsSpecifiedAllSecretsReturned(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secrets := &storage.StorageAccountOperatorSecrets{} + testreflect.PopulateStruct(secrets) + + acct := &storage.StorageAccount{ + Spec: storage.StorageAccount_Spec{ + OperatorSpec: &storage.StorageAccountOperatorSpec{ + Secrets: secrets, + }, + }, + } + secretNames, _ := secretsSpecified(acct) + expectedTags := set.Set[string]{ + key1: {}, + key2: {}, + } + g.Expect(expectedTags).To(Equal(secretNames)) +} diff --git a/v2/api/storage/v1api20210401/storage/storage_account_types_gen.go b/v2/api/storage/v1api20210401/storage/storage_account_types_gen.go index 9e55194b447..84baba9037c 100644 --- a/v2/api/storage/v1api20210401/storage/storage_account_types_gen.go +++ b/v2/api/storage/v1api20210401/storage/storage_account_types_gen.go @@ -83,10 +83,10 @@ func (account *StorageAccount) ConvertTo(hub conversion.Hub) error { return nil } -var _ genruntime.KubernetesExporter = &StorageAccount{} +var _ genruntime.KubernetesConfigExporter = &StorageAccount{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (account *StorageAccount) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (account *StorageAccount) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(account.Namespace) if account.Spec.OperatorSpec != nil && account.Spec.OperatorSpec.ConfigMaps != nil { if account.Status.PrimaryEndpoints != nil { diff --git a/v2/api/storage/v1api20210401/storage_account_types_gen.go b/v2/api/storage/v1api20210401/storage_account_types_gen.go index b5ada0e1867..00be6f01abb 100644 --- a/v2/api/storage/v1api20210401/storage_account_types_gen.go +++ b/v2/api/storage/v1api20210401/storage_account_types_gen.go @@ -110,10 +110,10 @@ func (account *StorageAccount) defaultAzureName() { // defaultImpl applies the code generated defaults to the StorageAccount resource func (account *StorageAccount) defaultImpl() { account.defaultAzureName() } -var _ genruntime.KubernetesExporter = &StorageAccount{} +var _ genruntime.KubernetesConfigExporter = &StorageAccount{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (account *StorageAccount) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (account *StorageAccount) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(account.Namespace) if account.Spec.OperatorSpec != nil && account.Spec.OperatorSpec.ConfigMaps != nil { if account.Status.PrimaryEndpoints != nil { diff --git a/v2/api/storage/v1api20220901/storage/storage_account_types_gen.go b/v2/api/storage/v1api20220901/storage/storage_account_types_gen.go index 29b8ff02285..521af2df8d8 100644 --- a/v2/api/storage/v1api20220901/storage/storage_account_types_gen.go +++ b/v2/api/storage/v1api20220901/storage/storage_account_types_gen.go @@ -70,10 +70,10 @@ func (account *StorageAccount) ConvertTo(hub conversion.Hub) error { return account.AssignProperties_To_StorageAccount(destination) } -var _ genruntime.KubernetesExporter = &StorageAccount{} +var _ genruntime.KubernetesConfigExporter = &StorageAccount{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (account *StorageAccount) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (account *StorageAccount) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(account.Namespace) if account.Spec.OperatorSpec != nil && account.Spec.OperatorSpec.ConfigMaps != nil { if account.Status.PrimaryEndpoints != nil { diff --git a/v2/api/storage/v1api20220901/storage_account_types_gen.go b/v2/api/storage/v1api20220901/storage_account_types_gen.go index d52aa161ab2..b0a2aed463e 100644 --- a/v2/api/storage/v1api20220901/storage_account_types_gen.go +++ b/v2/api/storage/v1api20220901/storage_account_types_gen.go @@ -110,10 +110,10 @@ func (account *StorageAccount) defaultAzureName() { // defaultImpl applies the code generated defaults to the StorageAccount resource func (account *StorageAccount) defaultImpl() { account.defaultAzureName() } -var _ genruntime.KubernetesExporter = &StorageAccount{} +var _ genruntime.KubernetesConfigExporter = &StorageAccount{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (account *StorageAccount) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (account *StorageAccount) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(account.Namespace) if account.Spec.OperatorSpec != nil && account.Spec.OperatorSpec.ConfigMaps != nil { if account.Status.PrimaryEndpoints != nil { diff --git a/v2/api/storage/v1api20230101/storage/storage_account_types_gen.go b/v2/api/storage/v1api20230101/storage/storage_account_types_gen.go index ab4c3116cc8..6f65b350d17 100644 --- a/v2/api/storage/v1api20230101/storage/storage_account_types_gen.go +++ b/v2/api/storage/v1api20230101/storage/storage_account_types_gen.go @@ -49,10 +49,10 @@ func (account *StorageAccount) SetConditions(conditions conditions.Conditions) { account.Status.Conditions = conditions } -var _ genruntime.KubernetesExporter = &StorageAccount{} +var _ genruntime.KubernetesConfigExporter = &StorageAccount{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (account *StorageAccount) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (account *StorageAccount) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(account.Namespace) if account.Spec.OperatorSpec != nil && account.Spec.OperatorSpec.ConfigMaps != nil { if account.Status.PrimaryEndpoints != nil { diff --git a/v2/api/storage/v1api20230101/storage_account_types_gen.go b/v2/api/storage/v1api20230101/storage_account_types_gen.go index 139c64aaad9..c86680cdbed 100644 --- a/v2/api/storage/v1api20230101/storage_account_types_gen.go +++ b/v2/api/storage/v1api20230101/storage_account_types_gen.go @@ -107,10 +107,10 @@ func (account *StorageAccount) InitializeSpec(status genruntime.ConvertibleStatu return fmt.Errorf("expected Status of type StorageAccount_STATUS but received %T instead", status) } -var _ genruntime.KubernetesExporter = &StorageAccount{} +var _ genruntime.KubernetesConfigExporter = &StorageAccount{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (account *StorageAccount) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (account *StorageAccount) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(account.Namespace) if account.Spec.OperatorSpec != nil && account.Spec.OperatorSpec.ConfigMaps != nil { if account.Status.PrimaryEndpoints != nil { diff --git a/v2/internal/reconcilers/arm/azure_generic_arm_reconciler_instance.go b/v2/internal/reconcilers/arm/azure_generic_arm_reconciler_instance.go index 4e993be5ca7..2242a83f317 100644 --- a/v2/internal/reconcilers/arm/azure_generic_arm_reconciler_instance.go +++ b/v2/internal/reconcilers/arm/azure_generic_arm_reconciler_instance.go @@ -661,23 +661,44 @@ func (r *azureDeploymentReconcilerInstance) updateStatus(ctx context.Context) er // saveAssociatedKubernetesResources retrieves Kubernetes resources to create and saves them to Kubernetes. // If there are no resources to save this method is a no-op. func (r *azureDeploymentReconcilerInstance) saveAssociatedKubernetesResources(ctx context.Context) error { - // Check if this resource has a handcrafted extension for exporting - retriever := extensions.CreateKubernetesExporter(ctx, r.Extension, r.ARMConnection.Client(), r.Log) - resources, err := retriever(r.Obj) + originalVersion, err := genruntime.ObjAsOriginalVersion(r.Obj, r.ResourceResolver.Scheme()) if err != nil { - return errors.Wrap(err, "extension failed to produce resources for export") + return err + } + + var resources []client.Object + + secretExporter := &kubernetesSecretExporter{ + obj: r.Obj, + connection: r.ARMConnection, + log: r.Log, + extension: r.Extension, + additionalSecrets: nil, // TODO: Will be filled in in a subsequent PR } - // Also check if the resource itself implements KubernetesExporter - versionedMeta, err := genruntime.ObjAsOriginalVersion(r.Obj, r.ResourceResolver.Scheme()) + var additionalResources []client.Object + additionalResources, err = secretExporter.Export(ctx) if err != nil { return err } + resources = append(resources, additionalResources...) + + exporters := []kubernetesResourceExporter{ + &autoGeneratedConfigExporter{ + versionedObj: originalVersion, + log: r.Log, + connection: r.ARMConnection, + }, + &manualConfigExporter{ + obj: r.Obj, + extension: r.Extension, + log: r.Log, + connection: r.ARMConnection, + }, + } - exporter, ok := versionedMeta.(genruntime.KubernetesExporter) - if ok { - var additionalResources []client.Object - additionalResources, err = exporter.ExportKubernetesResources(ctx, r.Obj, r.ARMConnection.Client(), r.Log) + for _, exporter := range exporters { + additionalResources, err = exporter.Export(ctx) if err != nil { return errors.Wrap(err, "failed to produce resources for export") } diff --git a/v2/internal/reconcilers/arm/kubernetes_resource_exporter.go b/v2/internal/reconcilers/arm/kubernetes_resource_exporter.go new file mode 100644 index 00000000000..9265066a063 --- /dev/null +++ b/v2/internal/reconcilers/arm/kubernetes_resource_exporter.go @@ -0,0 +1,256 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package arm + +import ( + "context" + + "github.com/go-logr/logr" + "github.com/pkg/errors" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/Azure/azure-service-operator/v2/internal/set" + asocel "github.com/Azure/azure-service-operator/v2/internal/util/cel" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime/configmaps" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime/core" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime/extensions" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime/secrets" +) + +type kubernetesResourceExporter interface { + Export(ctx context.Context) ([]client.Object, error) +} + +var _ kubernetesResourceExporter = &configMapExpressionExporter{} + +type configMapExpressionExporter struct { + obj genruntime.ARMMetaObject + versionedObj genruntime.ARMMetaObject + expressionEvaluator asocel.ExpressionEvaluator +} + +func (c *configMapExpressionExporter) Export(ctx context.Context) ([]client.Object, error) { + cmExporter, ok := c.obj.(configmaps.Exporter) + if !ok { + return nil, nil + } + + cmExpressions := cmExporter.ConfigMapDestinationExpressions() + if len(cmExpressions) == 0 { + return nil, nil + } + + resources, err := c.parseConfigMaps(cmExpressions) + if err != nil { + return nil, errors.Wrap(err, "failed to parse configmap expressions for export") + } + return resources, err +} + +func (c *configMapExpressionExporter) parseConfigMaps(expressions []*core.DestinationExpression) ([]client.Object, error) { + collector := configmaps.NewCollector(c.obj.GetNamespace()) + + for _, expression := range expressions { + value, err := c.expressionEvaluator.CompileAndRun(expression.Value, c.versionedObj, nil) + if err != nil { + return nil, err + } + + if value.Value != "" { + collector.AddValue( + &genruntime.ConfigMapDestination{ + Name: expression.Name, + Key: expression.Key, + }, value.Value) + } else if len(value.Values) > 0 { + for k, v := range value.Values { + collector.AddValue( + &genruntime.ConfigMapDestination{ + Name: expression.Name, + Key: k, + }, v) + } + } else { + return nil, errors.Errorf("unexpected expression output") + } + } + + result, err := collector.Values() + if err != nil { + return nil, err + } + return configmaps.SliceToClientObjectSlice(result), nil +} + +var _ kubernetesResourceExporter = &secretExpressionExporter{} + +type secretExpressionExporter struct { + obj genruntime.ARMMetaObject + versionedObj genruntime.ARMMetaObject + rawSecrets map[string]string + expressionEvaluator asocel.ExpressionEvaluator +} + +func (s *secretExpressionExporter) Export(ctx context.Context) ([]client.Object, error) { + secretExporter, ok := s.obj.(secrets.Exporter) + if !ok { + return nil, nil + } + + secretExpressions := secretExporter.SecretDestinationExpressions() + if len(secretExpressions) == 0 { + return nil, nil + } + resources, err := s.parseSecrets(secretExpressions, s.versionedObj, s.rawSecrets) + if err != nil { + return nil, errors.Wrap(err, "failed to parse secret expressions for export") + } + + return resources, nil +} + +func (s *secretExpressionExporter) parseSecrets( + expressions []*core.DestinationExpression, + versionedObj genruntime.ARMMetaObject, + rawSecrets map[string]string, +) ([]client.Object, error) { + collector := secrets.NewCollector(versionedObj.GetNamespace()) + + for _, expression := range expressions { + value, err := s.expressionEvaluator.CompileAndRun(expression.Value, versionedObj, rawSecrets) + if err != nil { + return nil, err + } + + if value.Value != "" { + collector.AddValue( + &genruntime.SecretDestination{ + Name: expression.Name, + Key: expression.Key, + }, value.Value) + } else if len(value.Values) > 0 { + for k, v := range value.Values { + collector.AddValue( + &genruntime.SecretDestination{ + Name: expression.Name, + Key: k, + }, v) + } + } else { + return nil, errors.Errorf("unexpected expression output") + } + } + + result, err := collector.Values() + if err != nil { + return nil, err + } + return secrets.SliceToClientObjectSlice(result), nil +} + +// TODO: This will be used in a followup PR +// +//nolint:unused +func findRequiredSecrets( + expressionEvaluator asocel.ExpressionEvaluator, + obj genruntime.ARMMetaObject, + versionedObj genruntime.ARMMetaObject, +) (set.Set[string], error) { + secretExporter, ok := obj.(secrets.Exporter) + if !ok { + return nil, nil + } + expressions := secretExporter.SecretDestinationExpressions() + if len(expressions) == 0 { + return nil, nil + } + + result := make(set.Set[string]) + for _, expression := range expressions { + secretsNeeded, err := expressionEvaluator.FindSecretUsage(expression.Value, versionedObj) + if err != nil { + return nil, err + } + + result.AddAll(secretsNeeded) + } + + return result, nil +} + +var _ kubernetesResourceExporter = &kubernetesSecretExporter{} + +type kubernetesSecretExporter struct { + obj genruntime.ARMMetaObject + log logr.Logger + extension genruntime.ResourceExtension + additionalSecrets set.Set[string] + connection Connection + + // This is a bit hacky but we stash the RawSecrets here for use by other exporters later + rawSecrets map[string]string +} + +func (e *kubernetesSecretExporter) Export(ctx context.Context) ([]client.Object, error) { + retriever := extensions.CreateKubernetesSecretExporter(ctx, e.extension, e.connection.Client(), e.log) + result, err := retriever(e.obj, e.additionalSecrets) + if err != nil { + return nil, errors.Wrap(err, "extension failed to produce resources for export") + } + if result == nil { + return nil, nil + } + + e.rawSecrets = result.RawSecrets + + return result.Objs, nil +} + +var _ kubernetesResourceExporter = &autoGeneratedConfigExporter{} + +type autoGeneratedConfigExporter struct { + versionedObj genruntime.ARMMetaObject + log logr.Logger + connection Connection +} + +func (a *autoGeneratedConfigExporter) Export(ctx context.Context) ([]client.Object, error) { + exporter, ok := a.versionedObj.(genruntime.KubernetesConfigExporter) + if !ok { + return nil, nil + } + + resources, err := exporter.ExportKubernetesConfigMaps(ctx, a.versionedObj, a.connection.Client(), a.log) + if err != nil { + return nil, errors.Wrap(err, "failed to produce resources for export") + } + + return resources, nil +} + +var _ kubernetesResourceExporter = &manualConfigExporter{} + +type manualConfigExporter struct { + obj genruntime.ARMMetaObject + extension genruntime.ResourceExtension + log logr.Logger + connection Connection +} + +func (a *manualConfigExporter) Export(ctx context.Context) ([]client.Object, error) { + exporter, ok := a.extension.(genruntime.KubernetesConfigExporter) + if !ok { + return nil, nil + } + + resources, err := exporter.ExportKubernetesConfigMaps(ctx, a.obj, a.connection.Client(), a.log) + if err != nil { + return nil, errors.Wrap(err, "failed to produce resources for export") + } + + return resources, nil +} diff --git a/v2/internal/reflecthelpers/reflect_helpers.go b/v2/internal/reflecthelpers/reflect_helpers.go index 2bb94e769b3..28f192021ec 100644 --- a/v2/internal/reflecthelpers/reflect_helpers.go +++ b/v2/internal/reflecthelpers/reflect_helpers.go @@ -379,3 +379,19 @@ func setPropertyCore(obj any, propertyPath []string, value any) (err error) { field.Set(reflect.ValueOf(value)) return nil } + +// GetJSONTags returns a set of JSON keys used in the `json` annotation of a struct +func GetJSONTags(t reflect.Type) set.Set[string] { + tags := set.Make[string]() + + for i := 0; i < t.NumField(); i++ { + fieldType := t.Field(i) + tag := fieldType.Tag.Get("json") + if tag != "" { + // Split the tag to handle omitempty and other options + tags.Add(strings.Split(tag, ",")[0]) + } + } + + return tags +} diff --git a/v2/internal/set/set.go b/v2/internal/set/set.go index e89ac8184ba..d645ec0b10e 100644 --- a/v2/internal/set/set.go +++ b/v2/internal/set/set.go @@ -107,3 +107,18 @@ func AsSortedSlice[T constraints.Ordered](set Set[T]) []T { slices.Sort(result) return result } + +// Union returns the union of the specified sets +func Union[T comparable](sets ...Set[T]) Set[T] { + size := 0 + for _, set := range sets { + size += len(set) + } + result := make(Set[T], size) + + for _, set := range sets { + result.AddAll(set) + } + + return result +} diff --git a/v2/internal/set/set_test.go b/v2/internal/set/set_test.go index 5111b4f4910..4a2a244f9aa 100644 --- a/v2/internal/set/set_test.go +++ b/v2/internal/set/set_test.go @@ -104,7 +104,7 @@ func TestSet_Where_GivenPredicate_ReturnsSetOfMatchingItems(t *testing.T) { g.Expect(evenSet).To(Equal(Make(2, 4))) } -func TestSet_Except_GivenSetOfVues_ReturnsRemainingValues(t *testing.T) { +func TestSet_Except_GivenSetOfValues_ReturnsRemainingValues(t *testing.T) { t.Parallel() g := NewGomegaWithT(t) @@ -112,3 +112,25 @@ func TestSet_Except_GivenSetOfVues_ReturnsRemainingValues(t *testing.T) { remainingSet := set.Except(Make(2, 4)) g.Expect(remainingSet).To(Equal(Make(1, 3, 5))) } + +func TestSet_Union_ReturnsUnionOfTwoSets(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + set := Make(1, 2, 3, 4, 5) + set2 := Make(5, 6, 7, 8, 9, 10) + + g.Expect(Union(set, set2)).To(Equal(Make(1, 2, 3, 4, 5, 6, 7, 8, 9, 10))) +} + +func TestSet_Union_ReturnsUnionOfFourSets(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + set := Make(1, 2, 3, 4, 5) + set2 := Make(5, 6, 7, 8, 9, 10) + set3 := Make(5, 8, 1, 11, 15, 2) + set4 := Make(12, 2, 2, 13, 7, 14) + + g.Expect(Union(set, set2, set3, set4)).To(Equal(Make(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15))) +} diff --git a/v2/internal/testcommon/reflect/reflect.go b/v2/internal/testcommon/reflect/reflect.go new file mode 100644 index 00000000000..61a279e2b8f --- /dev/null +++ b/v2/internal/testcommon/reflect/reflect.go @@ -0,0 +1,39 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package reflect + +import ( + "fmt" + "reflect" +) + +// PopulateStruct is far from complete/perfect and shouldn't be used in production code. +// Test usage is OK. +// deprecated: Don't use it in production code. +func PopulateStruct(s any) { + val := reflect.ValueOf(s).Elem() + valType := val.Type() + + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + fieldType := valType.Field(i) + + switch field.Kind() { + case reflect.Ptr: + newVal := reflect.New(fieldType.Type.Elem()) + field.Set(newVal) + PopulateStruct(field.Interface()) + case reflect.Struct: + PopulateStruct(field.Addr().Interface()) + case reflect.String: + field.Set(reflect.ValueOf("exampleValue")) + case reflect.Map: + // skip + default: + panic(fmt.Sprintf("unsupported field type: %s", field.Kind())) + } + } +} diff --git a/v2/pkg/genruntime/configmaps/configmaps.go b/v2/pkg/genruntime/configmaps/configmaps.go index 66b6df24c02..571e55a7367 100644 --- a/v2/pkg/genruntime/configmaps/configmaps.go +++ b/v2/pkg/genruntime/configmaps/configmaps.go @@ -11,6 +11,10 @@ import ( ) func SliceToClientObjectSlice(s []*v1.ConfigMap) []client.Object { + if s == nil { + return nil + } + result := make([]client.Object, 0, len(s)) for _, s := range s { result = append(result, s) diff --git a/v2/pkg/genruntime/extensions/kubernetes_exporter.go b/v2/pkg/genruntime/extensions/kubernetes_exporter.go deleted file mode 100644 index 74ab09c362a..00000000000 --- a/v2/pkg/genruntime/extensions/kubernetes_exporter.go +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT license. - */ - -package extensions - -import ( - "context" - - "github.com/go-logr/logr" - "sigs.k8s.io/controller-runtime/pkg/client" - - . "github.com/Azure/azure-service-operator/v2/internal/logging" - - "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" - "github.com/Azure/azure-service-operator/v2/pkg/genruntime" -) - -// This file contains helpers for exporting Kubernetes resources via extensions - -type KubernetesExportFunc = func(obj genruntime.MetaObject) ([]client.Object, error) - -// CreateKubernetesExporter creates a function to create Kubernetes resources. If the resource -// in question has not been configured with the genruntime.KubernetesExporter interface, the returned function -// is a no-op. -func CreateKubernetesExporter( - ctx context.Context, - host genruntime.ResourceExtension, - armClient *genericarmclient.GenericClient, - log logr.Logger, -) KubernetesExportFunc { - impl, ok := host.(genruntime.KubernetesExporter) - if !ok { - return func(obj genruntime.MetaObject) ([]client.Object, error) { - return nil, nil - } - } - - return func(obj genruntime.MetaObject) ([]client.Object, error) { - log.V(Info).Info("Getting Kubernetes resources for export") - resources, err := impl.ExportKubernetesResources(ctx, obj, armClient, log) - if err != nil { - return resources, err - } - - log.V(Info).Info( - "Successfully retrieved Kubernetes resources for export", - "ResourcesToWrite", len(resources)) - - return resources, nil - } -} diff --git a/v2/pkg/genruntime/extensions/kubernetes_secret_exporter.go b/v2/pkg/genruntime/extensions/kubernetes_secret_exporter.go new file mode 100644 index 00000000000..b1480cdfd22 --- /dev/null +++ b/v2/pkg/genruntime/extensions/kubernetes_secret_exporter.go @@ -0,0 +1,60 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package extensions + +import ( + "context" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" + . "github.com/Azure/azure-service-operator/v2/internal/logging" + "github.com/Azure/azure-service-operator/v2/internal/set" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime" +) + +// This file contains helpers for exporting Kubernetes resources via extensions + +type KubernetesSecretExportFunc = func(obj genruntime.MetaObject, additionalSecrets set.Set[string]) (*genruntime.KubernetesSecretExportResult, error) + +// CreateKubernetesSecretExporter creates a function to create Kubernetes secrets. If the resource +// in question has not been configured with the genruntime.KubernetesSecretExporter interface, the returned function +// is a no-op. +func CreateKubernetesSecretExporter( + ctx context.Context, + host genruntime.ResourceExtension, + armClient *genericarmclient.GenericClient, + log logr.Logger, +) KubernetesSecretExportFunc { + impl, ok := host.(genruntime.KubernetesSecretExporter) + if !ok { + return func(obj genruntime.MetaObject, additionalSecrets set.Set[string]) (*genruntime.KubernetesSecretExportResult, error) { + return nil, nil + } + } + + return func(obj genruntime.MetaObject, additionalSecrets set.Set[string]) (*genruntime.KubernetesSecretExportResult, error) { + log.V(Info).Info("Getting Kubernetes secrets for export") + result, err := impl.ExportKubernetesSecrets(ctx, obj, additionalSecrets, armClient, log) + if err != nil { + return result, err + } + + var objs []client.Object + var rawSecrets map[string]string + if result != nil { + objs = result.Objs + rawSecrets = result.RawSecrets + } + + log.V(Info).Info( + "Successfully retrieved Kubernetes secrets for export", + "ResourcesToWrite", len(objs), "RawSecrets", len(rawSecrets)) + + return result, nil + } +} diff --git a/v2/pkg/genruntime/kubernetes_exporter.go b/v2/pkg/genruntime/kubernetes_config_exporter.go similarity index 68% rename from v2/pkg/genruntime/kubernetes_exporter.go rename to v2/pkg/genruntime/kubernetes_config_exporter.go index 8bf9d86098a..73a0bbefe31 100644 --- a/v2/pkg/genruntime/kubernetes_exporter.go +++ b/v2/pkg/genruntime/kubernetes_config_exporter.go @@ -14,12 +14,12 @@ import ( "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" ) -// KubernetesExporter defines a resource which can create other resources in Kubernetes. -type KubernetesExporter interface { - // ExportKubernetesResources provides a list of Kubernetes resource for the operator to create once the resource which +// KubernetesConfigExporter defines a resource which can create configmaps in Kubernetes +type KubernetesConfigExporter interface { + // ExportKubernetesConfigMaps provides a list of Kubernetes ConfigMaps for the operator to create once the resource which // implements this interface is successfully provisioned. This method is invoked once a resource has been // successfully created in Azure, but before the Ready condition has been marked successful. - ExportKubernetesResources( + ExportKubernetesConfigMaps( ctx context.Context, obj MetaObject, armClient *genericarmclient.GenericClient, diff --git a/v2/pkg/genruntime/kubernetes_secret_exporter.go b/v2/pkg/genruntime/kubernetes_secret_exporter.go new file mode 100644 index 00000000000..f0d4103102a --- /dev/null +++ b/v2/pkg/genruntime/kubernetes_secret_exporter.go @@ -0,0 +1,45 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package genruntime + +import ( + "context" + + "github.com/go-logr/logr" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/Azure/azure-service-operator/v2/internal/genericarmclient" + "github.com/Azure/azure-service-operator/v2/internal/set" +) + +type KubernetesSecretExportResult struct { + // Objs is the set of objects (secrets) to export. + // Only secrets defined on the operatorSpec.secrets are included here. Secrets referenced via a "secret expression" + // in operatorSpec.secretExpressions are returned in RawSecrets for later use. + Objs []client.Object + + // RawSecrets contains the raw secret values from Azure. + // The keys are the "names" of the secrets as defined on operatorSpec.secrets (JSON-cased), and the + // values are the actual secrets. So for example ManagedCluster has "adminCredentials" and "userCredentials". + // This will ONLY contain secrets that were requested via additionalSecrets, NOT secrets requested via + // self.spec.operatorSpec.secrets. + RawSecrets map[string]string +} + +// KubernetesSecretExporter defines a resource which can create retrieve secrets from Azure and export them to +// Kubernetes secrets. +type KubernetesSecretExporter interface { + // ExportKubernetesSecrets provides a list of Kubernetes resource for the operator to create once the resource which + // implements this interface is successfully provisioned. This method is invoked once a resource has been + // successfully created in Azure, but before the Ready condition has been marked successful. + ExportKubernetesSecrets( + ctx context.Context, + obj MetaObject, + additionalSecrets set.Set[string], // This exists to avoid making multiple calls to the secrets API - instead we capture all the secrets we need and then get them + armClient *genericarmclient.GenericClient, + log logr.Logger, + ) (*KubernetesSecretExportResult, error) +} diff --git a/v2/pkg/genruntime/secrets/secrets.go b/v2/pkg/genruntime/secrets/secrets.go index b7fbc375f89..d4d651bb3a7 100644 --- a/v2/pkg/genruntime/secrets/secrets.go +++ b/v2/pkg/genruntime/secrets/secrets.go @@ -8,9 +8,15 @@ package secrets import ( v1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/Azure/azure-service-operator/v2/internal/set" ) func SliceToClientObjectSlice(s []*v1.Secret) []client.Object { + if s == nil { + return nil + } + result := make([]client.Object, 0, len(s)) for _, s := range s { result = append(result, s) @@ -18,3 +24,14 @@ func SliceToClientObjectSlice(s []*v1.Secret) []client.Object { return result } + +func SelectSecrets(requested set.Set[string], retrieved map[string]string) map[string]string { + result := make(map[string]string, len(requested)) + for key, value := range retrieved { + if requested.Contains(key) { + result[key] = value + } + } + + return result +} diff --git a/v2/tools/generator/internal/astmodel/std_references.go b/v2/tools/generator/internal/astmodel/std_references.go index afc1d06495b..ff82223046f 100644 --- a/v2/tools/generator/internal/astmodel/std_references.go +++ b/v2/tools/generator/internal/astmodel/std_references.go @@ -62,7 +62,7 @@ var ( // Type names - GenRuntime KubernetesResourceType = MakeExternalTypeName(GenRuntimeReference, "KubernetesResource") - KubernetesExporterType = MakeExternalTypeName(GenRuntimeReference, "KubernetesExporter") + KuberentesConfigExporterType = MakeExternalTypeName(GenRuntimeReference, "KubernetesConfigExporter") TenantResourceType = MakeExternalTypeName(GenRuntimeReference, "TenantResource") ConvertibleSpecInterfaceType = MakeExternalTypeName(GenRuntimeReference, "ConvertibleSpec") ConvertibleStatusInterfaceType = MakeExternalTypeName(GenRuntimeReference, "ConvertibleStatus") diff --git a/v2/tools/generator/internal/codegen/pipeline/add_kubernetes_exporter.go b/v2/tools/generator/internal/codegen/pipeline/add_kubernetes_exporter.go index 836c00dadda..e1c94eaf6f7 100644 --- a/v2/tools/generator/internal/codegen/pipeline/add_kubernetes_exporter.go +++ b/v2/tools/generator/internal/codegen/pipeline/add_kubernetes_exporter.go @@ -40,7 +40,7 @@ func AddKubernetesExporter(idFactory astmodel.IdentifierFactory) *Stage { continue } - builder := functions.NewKubernetesExporterBuilder(def.Name(), resourceType, idFactory, configMapMappings) + builder := functions.NewKubernetesConfigExporterBuilder(def.Name(), resourceType, idFactory, configMapMappings) resourceType = resourceType.WithInterface(builder.ToInterfaceImplementation()) updatedDef := def.WithType(resourceType) diff --git a/v2/tools/generator/internal/codegen/pipeline/testdata/TestAddKubernetesExporter_AutomaticallyGeneratesExportedConfigMaps/person-v20200101.golden b/v2/tools/generator/internal/codegen/pipeline/testdata/TestAddKubernetesExporter_AutomaticallyGeneratesExportedConfigMaps/person-v20200101.golden index 909f625c4a7..115ad9c9b1f 100644 --- a/v2/tools/generator/internal/codegen/pipeline/testdata/TestAddKubernetesExporter_AutomaticallyGeneratesExportedConfigMaps/person-v20200101.golden +++ b/v2/tools/generator/internal/codegen/pipeline/testdata/TestAddKubernetesExporter_AutomaticallyGeneratesExportedConfigMaps/person-v20200101.golden @@ -22,10 +22,10 @@ type Person struct { Status Person_STATUS `json:"status,omitempty"` } -var _ genruntime.KubernetesExporter = &Person{} +var _ genruntime.KubernetesConfigExporter = &Person{} -// ExportKubernetesResources defines a resource which can create other resources in Kubernetes. -func (person *Person) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// ExportKubernetesConfigMaps defines a resource which can create ConfigMaps in Kubernetes. +func (person *Person) ExportKubernetesConfigMaps(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { collector := configmaps.NewCollector(person.Namespace) if person.Spec.OperatorSpec != nil && person.Spec.OperatorSpec.ConfigMaps != nil { if person.Status.OptionalString != nil { diff --git a/v2/tools/generator/internal/functions/kubernetes_exporter_function.go b/v2/tools/generator/internal/functions/kubernetes_config_exporter_function.go similarity index 87% rename from v2/tools/generator/internal/functions/kubernetes_exporter_function.go rename to v2/tools/generator/internal/functions/kubernetes_config_exporter_function.go index dd487c95b56..c04020f73bf 100644 --- a/v2/tools/generator/internal/functions/kubernetes_exporter_function.go +++ b/v2/tools/generator/internal/functions/kubernetes_config_exporter_function.go @@ -9,16 +9,15 @@ import ( "go/token" "sort" - "github.com/pkg/errors" - "github.com/dave/dst" + "github.com/pkg/errors" "github.com/Azure/azure-service-operator/v2/tools/generator/internal/astbuilder" "github.com/Azure/azure-service-operator/v2/tools/generator/internal/astmodel" ) -// KubernetesExporterBuilder is a builder for creating genruntime.KubernetesExporter interface implementations. -type KubernetesExporterBuilder struct { +// KubernetesConfigExporterBuilder is a builder for creating genruntime.KubernetesConfigExporter interface implementations. +type KubernetesConfigExporterBuilder struct { resourceName astmodel.TypeName resource *astmodel.ResourceType idFactory astmodel.IdentifierFactory @@ -26,14 +25,14 @@ type KubernetesExporterBuilder struct { mappings map[string][]*astmodel.PropertyDefinition } -// NewKubernetesExporterBuilder creates a new KubernetesExporterBuilder for the specified resource -func NewKubernetesExporterBuilder( +// NewKubernetesConfigExporterBuilder creates a new KubernetesConfigExporterBuilder for the specified resource +func NewKubernetesConfigExporterBuilder( resourceName astmodel.TypeName, resource *astmodel.ResourceType, idFactory astmodel.IdentifierFactory, mappings map[string][]*astmodel.PropertyDefinition, -) *KubernetesExporterBuilder { - return &KubernetesExporterBuilder{ +) *KubernetesConfigExporterBuilder { + return &KubernetesConfigExporterBuilder{ resourceName: resourceName, resource: resource, idFactory: idFactory, @@ -41,24 +40,24 @@ func NewKubernetesExporterBuilder( } } -// ToInterfaceImplementation creates an InterfaceImplementation from the KubernetesExporterBuilder -func (d *KubernetesExporterBuilder) ToInterfaceImplementation() *astmodel.InterfaceImplementation { +// ToInterfaceImplementation creates an InterfaceImplementation from the KubernetesConfigExporterBuilder +func (d *KubernetesConfigExporterBuilder) ToInterfaceImplementation() *astmodel.InterfaceImplementation { funcs := []astmodel.Function{ NewResourceFunction( - "ExportKubernetesResources", + "ExportKubernetesConfigMaps", d.resource, d.idFactory, - d.exportKubernetesResources, + d.exportKubernetesConfigMaps, astmodel.NewPackageReferenceSet( astmodel.GenRuntimeReference, astmodel.GenRuntimeConfigMapsReference, astmodel.GenericARMClientReference, + astmodel.LogrReference, astmodel.ControllerRuntimeClient, - astmodel.ContextReference, - astmodel.LogrReference)), + astmodel.ContextReference)), } return astmodel.NewInterfaceImplementation( - astmodel.KubernetesExporterType, + astmodel.KuberentesConfigExporterType, funcs...) } @@ -66,7 +65,7 @@ func (d *KubernetesExporterBuilder) ToInterfaceImplementation() *astmodel.Interf // the genruntime.KubernetesExporter interface. // Generates code like: // -// func ( *) ExportKubernetesResources(_ context.Context, _ genruntime.MetaObject, _ *genericarmclient.GenericClient, _ logr.Logger) ([]client.Object, error) { +// func ( *) ExportKubernetesConfigMaps(ctx context.Context, obj MetaObject, armClient *genericarmclient.GenericClient, log logr.Logger) ([]client.Object, error) { // collector := configmaps.NewCollector(.Namespace) // if .Spec.OperatorSpec != nil && .Spec.OperatorSpec.ConfigMaps != nil { // if . != nil { @@ -80,7 +79,7 @@ func (d *KubernetesExporterBuilder) ToInterfaceImplementation() *astmodel.Interf // } // return configmaps.SliceToClientObjectSlice(result), nil // } -func (d *KubernetesExporterBuilder) exportKubernetesResources( +func (d *KubernetesConfigExporterBuilder) exportKubernetesConfigMaps( k *ResourceFunction, codeGenerationContext *astmodel.CodeGenerationContext, receiver astmodel.TypeName, @@ -214,28 +213,24 @@ func (d *KubernetesExporterBuilder) exportKubernetesResources( if err != nil { return nil, errors.Wrap(err, "creating context type expression") } - fn.AddParameter("_", contextTypeExpr) metaObjectTypeExpr, err := astmodel.GenRuntimeMetaObjectType.AsTypeExpr(codeGenerationContext) if err != nil { return nil, errors.Wrap(err, "creating meta object type expression") } - fn.AddParameter("_", metaObjectTypeExpr) clientTypeExpr, err := astmodel.NewOptionalType(astmodel.GenericClientType).AsTypeExpr(codeGenerationContext) if err != nil { return nil, errors.Wrap(err, "creating client type expression") } - fn.AddParameter("_", clientTypeExpr) logrTypeExpr, err := astmodel.LogrType.AsTypeExpr(codeGenerationContext) if err != nil { return nil, errors.Wrap(err, "creating logr type expression") } - fn.AddParameter("_", logrTypeExpr) objectArrayType, err := astmodel.NewArrayType(astmodel.ControllerRuntimeObjectType). @@ -247,11 +242,11 @@ func (d *KubernetesExporterBuilder) exportKubernetesResources( fn.AddReturn(objectArrayType) fn.AddReturn(dst.NewIdent("error")) - fn.AddComments("defines a resource which can create other resources in Kubernetes.") + fn.AddComments("defines a resource which can create ConfigMaps in Kubernetes.") return fn.DefineFunc(), nil } -func (d *KubernetesExporterBuilder) addCollectorStmt( +func (d *KubernetesConfigExporterBuilder) addCollectorStmt( receiverIdent string, collectorIdent string, propertyPath []*astmodel.PropertyDefinition,