diff --git a/v2/api/containerinstance/v1api20211001/container_group_spec_arm_types_gen.go b/v2/api/containerinstance/v1api20211001/container_group_spec_arm_types_gen.go index 19cb6f97eba..bb24d388689 100644 --- a/v2/api/containerinstance/v1api20211001/container_group_spec_arm_types_gen.go +++ b/v2/api/containerinstance/v1api20211001/container_group_spec_arm_types_gen.go @@ -306,7 +306,7 @@ type LogAnalytics_ARM struct { WorkspaceId *string `json:"workspaceId,omitempty"` // WorkspaceKey: The workspace key for log analytics - WorkspaceKey string `json:"workspaceKey,omitempty"` + WorkspaceKey *string `json:"workspaceKey,omitempty"` WorkspaceResourceId *string `json:"workspaceResourceId,omitempty"` } diff --git a/v2/api/containerinstance/v1api20211001/container_group_spec_arm_types_gen_test.go b/v2/api/containerinstance/v1api20211001/container_group_spec_arm_types_gen_test.go index 9ad2945f431..cdc1e70476a 100644 --- a/v2/api/containerinstance/v1api20211001/container_group_spec_arm_types_gen_test.go +++ b/v2/api/containerinstance/v1api20211001/container_group_spec_arm_types_gen_test.go @@ -1280,7 +1280,7 @@ func AddIndependentPropertyGeneratorsForLogAnalytics_ARM(gens map[string]gopter. gens["LogType"] = gen.PtrOf(gen.OneConstOf(LogAnalytics_LogType_ContainerInsights, LogAnalytics_LogType_ContainerInstanceLogs)) gens["Metadata"] = gen.MapOf(gen.AlphaString(), gen.AlphaString()) gens["WorkspaceId"] = gen.PtrOf(gen.AlphaString()) - gens["WorkspaceKey"] = gen.AlphaString() + gens["WorkspaceKey"] = gen.PtrOf(gen.AlphaString()) gens["WorkspaceResourceId"] = gen.PtrOf(gen.AlphaString()) } diff --git a/v2/api/containerinstance/v1api20211001/container_group_types_gen.go b/v2/api/containerinstance/v1api20211001/container_group_types_gen.go index 5c1f73f3623..e199bec64d4 100644 --- a/v2/api/containerinstance/v1api20211001/container_group_types_gen.go +++ b/v2/api/containerinstance/v1api20211001/container_group_types_gen.go @@ -8246,7 +8246,7 @@ type LogAnalytics struct { // +kubebuilder:validation:Required // WorkspaceKey: The workspace key for log analytics - WorkspaceKey genruntime.SecretReference `json:"workspaceKey,omitempty"` + WorkspaceKey *genruntime.SecretReference `json:"workspaceKey,omitempty"` // WorkspaceResourceReference: The workspace resource id for log analytics WorkspaceResourceReference *genruntime.ResourceReference `armReference:"WorkspaceResourceId" json:"workspaceResourceReference,omitempty"` @@ -8282,11 +8282,14 @@ func (analytics *LogAnalytics) ConvertToARM(resolved genruntime.ConvertToARMReso } // Set property "WorkspaceKey": - workspaceKeySecret, err := resolved.ResolvedSecrets.Lookup(analytics.WorkspaceKey) - if err != nil { - return nil, errors.Wrap(err, "looking up secret for property WorkspaceKey") + if analytics.WorkspaceKey != nil { + workspaceKeySecret, err := resolved.ResolvedSecrets.Lookup(*analytics.WorkspaceKey) + if err != nil { + return nil, errors.Wrap(err, "looking up secret for property WorkspaceKey") + } + workspaceKey := workspaceKeySecret + result.WorkspaceKey = &workspaceKey } - result.WorkspaceKey = workspaceKeySecret // Set property "WorkspaceResourceId": if analytics.WorkspaceResourceReference != nil { @@ -8359,9 +8362,10 @@ func (analytics *LogAnalytics) AssignProperties_From_LogAnalytics(source *v20211 // WorkspaceKey if source.WorkspaceKey != nil { - analytics.WorkspaceKey = source.WorkspaceKey.Copy() + workspaceKey := source.WorkspaceKey.Copy() + analytics.WorkspaceKey = &workspaceKey } else { - analytics.WorkspaceKey = genruntime.SecretReference{} + analytics.WorkspaceKey = nil } // WorkspaceResourceReference @@ -8396,8 +8400,12 @@ func (analytics *LogAnalytics) AssignProperties_To_LogAnalytics(destination *v20 destination.WorkspaceId = genruntime.ClonePointerToString(analytics.WorkspaceId) // WorkspaceKey - workspaceKey := analytics.WorkspaceKey.Copy() - destination.WorkspaceKey = &workspaceKey + if analytics.WorkspaceKey != nil { + workspaceKey := analytics.WorkspaceKey.Copy() + destination.WorkspaceKey = &workspaceKey + } else { + destination.WorkspaceKey = nil + } // WorkspaceResourceReference if analytics.WorkspaceResourceReference != nil { diff --git a/v2/api/containerinstance/v1api20211001/structure.txt b/v2/api/containerinstance/v1api20211001/structure.txt index fa25440bd76..2c69ba68e27 100644 --- a/v2/api/containerinstance/v1api20211001/structure.txt +++ b/v2/api/containerinstance/v1api20211001/structure.txt @@ -83,7 +83,7 @@ github.com/Azure/azure-service-operator/v2/api/containerinstance/v1api20211001 │ │ │ │ └── "ContainerInstanceLogs" │ │ │ ├── Metadata: map[string]string │ │ │ ├── WorkspaceId: *string -│ │ │ ├── WorkspaceKey: genruntime.SecretReference +│ │ │ ├── WorkspaceKey: *genruntime.SecretReference │ │ │ └── WorkspaceResourceReference: *genruntime.ResourceReference │ │ ├── DnsConfig: *Object (3 properties) │ │ │ ├── NameServers: string[] @@ -687,7 +687,7 @@ github.com/Azure/azure-service-operator/v2/api/containerinstance/v1api20211001 │ │ │ └── "ContainerInstanceLogs" │ │ ├── Metadata: map[string]string │ │ ├── WorkspaceId: *string - │ │ ├── WorkspaceKey: string + │ │ ├── WorkspaceKey: *string │ │ └── WorkspaceResourceId: *string │ ├── DnsConfig: *Object (3 properties) │ │ ├── NameServers: string[] diff --git a/v2/api/dbformariadb/v1api20180601/server_spec_arm_types_gen.go b/v2/api/dbformariadb/v1api20180601/server_spec_arm_types_gen.go index 07612b3805b..cf28ea4e471 100644 --- a/v2/api/dbformariadb/v1api20180601/server_spec_arm_types_gen.go +++ b/v2/api/dbformariadb/v1api20180601/server_spec_arm_types_gen.go @@ -124,7 +124,7 @@ type ServerPropertiesForDefaultCreate_ARM struct { AdministratorLogin *string `json:"administratorLogin,omitempty"` // AdministratorLoginPassword: The password of the administrator login. - AdministratorLoginPassword string `json:"administratorLoginPassword,omitempty"` + AdministratorLoginPassword *string `json:"administratorLoginPassword,omitempty"` // CreateMode: The mode to create a new server. CreateMode ServerPropertiesForDefaultCreate_CreateMode `json:"createMode,omitempty"` diff --git a/v2/api/dbformariadb/v1api20180601/server_spec_arm_types_gen_test.go b/v2/api/dbformariadb/v1api20180601/server_spec_arm_types_gen_test.go index 3f2d920537d..068b81f250c 100644 --- a/v2/api/dbformariadb/v1api20180601/server_spec_arm_types_gen_test.go +++ b/v2/api/dbformariadb/v1api20180601/server_spec_arm_types_gen_test.go @@ -304,7 +304,7 @@ func ServerPropertiesForDefaultCreate_ARMGenerator() gopter.Gen { // AddIndependentPropertyGeneratorsForServerPropertiesForDefaultCreate_ARM is a factory method for creating gopter generators func AddIndependentPropertyGeneratorsForServerPropertiesForDefaultCreate_ARM(gens map[string]gopter.Gen) { gens["AdministratorLogin"] = gen.PtrOf(gen.AlphaString()) - gens["AdministratorLoginPassword"] = gen.AlphaString() + gens["AdministratorLoginPassword"] = gen.PtrOf(gen.AlphaString()) gens["CreateMode"] = gen.OneConstOf(ServerPropertiesForDefaultCreate_CreateMode_Default) gens["MinimalTlsVersion"] = gen.PtrOf(gen.OneConstOf( MinimalTlsVersion_TLS1_0, diff --git a/v2/api/dbformariadb/v1api20180601/server_types_gen.go b/v2/api/dbformariadb/v1api20180601/server_types_gen.go index 3666e50dae8..985b5d406c4 100644 --- a/v2/api/dbformariadb/v1api20180601/server_types_gen.go +++ b/v2/api/dbformariadb/v1api20180601/server_types_gen.go @@ -2361,7 +2361,7 @@ type ServerPropertiesForDefaultCreate struct { // +kubebuilder:validation:Required // AdministratorLoginPassword: The password of the administrator login. - AdministratorLoginPassword genruntime.SecretReference `json:"administratorLoginPassword,omitempty"` + AdministratorLoginPassword *genruntime.SecretReference `json:"administratorLoginPassword,omitempty"` // +kubebuilder:validation:Required // CreateMode: The mode to create a new server. @@ -2400,11 +2400,14 @@ func (create *ServerPropertiesForDefaultCreate) ConvertToARM(resolved genruntime } // Set property "AdministratorLoginPassword": - administratorLoginPasswordSecret, err := resolved.ResolvedSecrets.Lookup(create.AdministratorLoginPassword) - if err != nil { - return nil, errors.Wrap(err, "looking up secret for property AdministratorLoginPassword") + if create.AdministratorLoginPassword != nil { + administratorLoginPasswordSecret, err := resolved.ResolvedSecrets.Lookup(*create.AdministratorLoginPassword) + if err != nil { + return nil, errors.Wrap(err, "looking up secret for property AdministratorLoginPassword") + } + administratorLoginPassword := administratorLoginPasswordSecret + result.AdministratorLoginPassword = &administratorLoginPassword } - result.AdministratorLoginPassword = administratorLoginPasswordSecret // Set property "CreateMode": if create.CreateMode != nil { @@ -2517,9 +2520,10 @@ func (create *ServerPropertiesForDefaultCreate) AssignProperties_From_ServerProp // AdministratorLoginPassword if source.AdministratorLoginPassword != nil { - create.AdministratorLoginPassword = source.AdministratorLoginPassword.Copy() + administratorLoginPassword := source.AdministratorLoginPassword.Copy() + create.AdministratorLoginPassword = &administratorLoginPassword } else { - create.AdministratorLoginPassword = genruntime.SecretReference{} + create.AdministratorLoginPassword = nil } // CreateMode @@ -2587,8 +2591,12 @@ func (create *ServerPropertiesForDefaultCreate) AssignProperties_To_ServerProper destination.AdministratorLogin = genruntime.ClonePointerToString(create.AdministratorLogin) // AdministratorLoginPassword - administratorLoginPassword := create.AdministratorLoginPassword.Copy() - destination.AdministratorLoginPassword = &administratorLoginPassword + if create.AdministratorLoginPassword != nil { + administratorLoginPassword := create.AdministratorLoginPassword.Copy() + destination.AdministratorLoginPassword = &administratorLoginPassword + } else { + destination.AdministratorLoginPassword = nil + } // CreateMode if create.CreateMode != nil { diff --git a/v2/api/dbformariadb/v1api20180601/structure.txt b/v2/api/dbformariadb/v1api20180601/structure.txt index fd7bbfb4211..8cf20b875d3 100644 --- a/v2/api/dbformariadb/v1api20180601/structure.txt +++ b/v2/api/dbformariadb/v1api20180601/structure.txt @@ -46,7 +46,7 @@ github.com/Azure/azure-service-operator/v2/api/dbformariadb/v1api20180601 │ │ ├── Properties: *Object (4 properties) │ │ │ ├── Default: *Object (8 properties) │ │ │ │ ├── AdministratorLogin: *string -│ │ │ │ ├── AdministratorLoginPassword: genruntime.SecretReference +│ │ │ │ ├── AdministratorLoginPassword: *genruntime.SecretReference │ │ │ │ ├── CreateMode: *Enum (1 value) │ │ │ │ │ └── "Default" │ │ │ │ ├── MinimalTlsVersion: *Enum (4 values) @@ -309,7 +309,7 @@ github.com/Azure/azure-service-operator/v2/api/dbformariadb/v1api20180601 │ ├── Properties: *Object (4 properties) │ │ ├── Default: *Object (8 properties) │ │ │ ├── AdministratorLogin: *string -│ │ │ ├── AdministratorLoginPassword: string +│ │ │ ├── AdministratorLoginPassword: *string │ │ │ ├── CreateMode: Enum (1 value) │ │ │ │ └── "Default" │ │ │ ├── MinimalTlsVersion: *Enum (4 values) diff --git a/v2/api/devices/v1api20210702/iot_hub_spec_arm_types_gen.go b/v2/api/devices/v1api20210702/iot_hub_spec_arm_types_gen.go index 5e47737563e..69b8d8586cb 100644 --- a/v2/api/devices/v1api20210702/iot_hub_spec_arm_types_gen.go +++ b/v2/api/devices/v1api20210702/iot_hub_spec_arm_types_gen.go @@ -264,7 +264,7 @@ type StorageEndpointProperties_ARM struct { AuthenticationType *StorageEndpointProperties_AuthenticationType `json:"authenticationType,omitempty"` // ConnectionString: The connection string for the Azure Storage account to which files are uploaded. - ConnectionString string `json:"connectionString,omitempty"` + ConnectionString *string `json:"connectionString,omitempty"` // ContainerName: The name of the root container where you upload files. The container need not exist but should be // creatable using the connectionString specified. diff --git a/v2/api/devices/v1api20210702/iot_hub_spec_arm_types_gen_test.go b/v2/api/devices/v1api20210702/iot_hub_spec_arm_types_gen_test.go index 03f1c5e207c..23aec7d1295 100644 --- a/v2/api/devices/v1api20210702/iot_hub_spec_arm_types_gen_test.go +++ b/v2/api/devices/v1api20210702/iot_hub_spec_arm_types_gen_test.go @@ -881,7 +881,7 @@ func StorageEndpointProperties_ARMGenerator() gopter.Gen { // AddIndependentPropertyGeneratorsForStorageEndpointProperties_ARM is a factory method for creating gopter generators func AddIndependentPropertyGeneratorsForStorageEndpointProperties_ARM(gens map[string]gopter.Gen) { gens["AuthenticationType"] = gen.PtrOf(gen.OneConstOf(StorageEndpointProperties_AuthenticationType_IdentityBased, StorageEndpointProperties_AuthenticationType_KeyBased)) - gens["ConnectionString"] = gen.AlphaString() + gens["ConnectionString"] = gen.PtrOf(gen.AlphaString()) gens["ContainerName"] = gen.PtrOf(gen.AlphaString()) gens["SasTtlAsIso8601"] = gen.PtrOf(gen.AlphaString()) } diff --git a/v2/api/devices/v1api20210702/iot_hub_types_gen.go b/v2/api/devices/v1api20210702/iot_hub_types_gen.go index 9a20b34dfd5..f74240c1d77 100644 --- a/v2/api/devices/v1api20210702/iot_hub_types_gen.go +++ b/v2/api/devices/v1api20210702/iot_hub_types_gen.go @@ -6320,7 +6320,7 @@ type StorageEndpointProperties struct { // +kubebuilder:validation:Required // ConnectionString: The connection string for the Azure Storage account to which files are uploaded. - ConnectionString genruntime.SecretReference `json:"connectionString,omitempty"` + ConnectionString *genruntime.SecretReference `json:"connectionString,omitempty"` // +kubebuilder:validation:Required // ContainerName: The name of the root container where you upload files. The container need not exist but should be @@ -6351,11 +6351,14 @@ func (properties *StorageEndpointProperties) ConvertToARM(resolved genruntime.Co } // Set property "ConnectionString": - connectionStringSecret, err := resolved.ResolvedSecrets.Lookup(properties.ConnectionString) - if err != nil { - return nil, errors.Wrap(err, "looking up secret for property ConnectionString") + if properties.ConnectionString != nil { + connectionStringSecret, err := resolved.ResolvedSecrets.Lookup(*properties.ConnectionString) + if err != nil { + return nil, errors.Wrap(err, "looking up secret for property ConnectionString") + } + connectionString := connectionStringSecret + result.ConnectionString = &connectionString } - result.ConnectionString = connectionStringSecret // Set property "ContainerName": if properties.ContainerName != nil { @@ -6441,9 +6444,10 @@ func (properties *StorageEndpointProperties) AssignProperties_From_StorageEndpoi // ConnectionString if source.ConnectionString != nil { - properties.ConnectionString = source.ConnectionString.Copy() + connectionString := source.ConnectionString.Copy() + properties.ConnectionString = &connectionString } else { - properties.ConnectionString = genruntime.SecretReference{} + properties.ConnectionString = nil } // ContainerName @@ -6482,8 +6486,12 @@ func (properties *StorageEndpointProperties) AssignProperties_To_StorageEndpoint } // ConnectionString - connectionString := properties.ConnectionString.Copy() - destination.ConnectionString = &connectionString + if properties.ConnectionString != nil { + connectionString := properties.ConnectionString.Copy() + destination.ConnectionString = &connectionString + } else { + destination.ConnectionString = nil + } // ContainerName destination.ContainerName = genruntime.ClonePointerToString(properties.ContainerName) diff --git a/v2/api/devices/v1api20210702/structure.txt b/v2/api/devices/v1api20210702/structure.txt index 8f6b6074b71..bd7d258e0a7 100644 --- a/v2/api/devices/v1api20210702/structure.txt +++ b/v2/api/devices/v1api20210702/structure.txt @@ -200,7 +200,7 @@ github.com/Azure/azure-service-operator/v2/api/devices/v1api20210702 │ │ │ ├── AuthenticationType: *Enum (2 values) │ │ │ │ ├── "identityBased" │ │ │ │ └── "keyBased" -│ │ │ ├── ConnectionString: genruntime.SecretReference +│ │ │ ├── ConnectionString: *genruntime.SecretReference │ │ │ ├── ContainerName: *string │ │ │ ├── Identity: *Object (1 property) │ │ │ │ └── UserAssignedIdentity: *string @@ -803,7 +803,7 @@ github.com/Azure/azure-service-operator/v2/api/devices/v1api20210702 │ ├── AuthenticationType: *Enum (2 values) │ │ ├── "identityBased" │ │ └── "keyBased" - │ ├── ConnectionString: string + │ ├── ConnectionString: *string │ ├── ContainerName: *string │ ├── Identity: *Object (1 property) │ │ └── UserAssignedIdentity: *string diff --git a/v2/internal/reflecthelpers/reflect_helpers.go b/v2/internal/reflecthelpers/reflect_helpers.go index 847c7fc8fb6..ef300a65f6b 100644 --- a/v2/internal/reflecthelpers/reflect_helpers.go +++ b/v2/internal/reflecthelpers/reflect_helpers.go @@ -120,6 +120,11 @@ func FindSecretReferences(obj interface{}) (set.Set[genruntime.SecretReference], return Find[genruntime.SecretReference](obj) } +// FindSecretCollections finds all the genruntime.SecretCollectionReference's on the provided object +func FindSecretCollections(obj interface{}) (set.Set[genruntime.SecretCollectionReference], error) { + return Find[genruntime.SecretCollectionReference](obj) +} + // FindConfigMapReferences finds all the genruntime.ConfigMapReference's on the provided object func FindConfigMapReferences(obj interface{}) (set.Set[genruntime.ConfigMapReference], error) { return Find[genruntime.ConfigMapReference](obj) diff --git a/v2/internal/resolver/resolver.go b/v2/internal/resolver/resolver.go index d0c16e60f46..8a3de58add8 100644 --- a/v2/internal/resolver/resolver.go +++ b/v2/internal/resolver/resolver.go @@ -25,18 +25,20 @@ import ( ) type Resolver struct { - client kubeclient.Client - kubeSecretResolver SecretResolver - kubeConfigMapResolver ConfigMapResolver - reconciledResourceLookup map[schema.GroupKind]schema.GroupVersionKind + client kubeclient.Client + kubeSecretResolver SecretResolver + kubeSecretCollectionResolver SecretCollectionResolver + kubeConfigMapResolver ConfigMapResolver + reconciledResourceLookup map[schema.GroupKind]schema.GroupVersionKind } func NewResolver(client kubeclient.Client) *Resolver { return &Resolver{ - client: client, - kubeSecretResolver: NewKubeSecretResolver(client), - kubeConfigMapResolver: NewKubeConfigMapResolver(client), - reconciledResourceLookup: make(map[schema.GroupKind]schema.GroupVersionKind), + client: client, + kubeSecretResolver: NewKubeSecretResolver(client), + kubeSecretCollectionResolver: NewKubeSecretCollectionResolver(client), + kubeConfigMapResolver: NewKubeConfigMapResolver(client), + reconciledResourceLookup: make(map[schema.GroupKind]schema.GroupVersionKind), } } @@ -308,7 +310,40 @@ func (r *Resolver) ResolveResourceSecretReferences(ctx context.Context, metaObje // resolve them resolvedSecrets, err := r.ResolveSecretReferences(ctx, namespacedSecretRefs) if err != nil { - return genruntime.Resolved[genruntime.SecretReference]{}, errors.Wrapf(err, "failed resolving secret references") + return genruntime.Resolved[genruntime.SecretReference, string]{}, errors.Wrapf(err, "failed resolving secret references") + } + + return resolvedSecrets, nil +} + +// ResolveSecretCollectionReferences resolves all provided secret collection references +func (r *Resolver) ResolveSecretCollectionReferences( + ctx context.Context, + refs set.Set[genruntime.NamespacedSecretCollectionReference], +) (genruntime.Resolved[genruntime.SecretCollectionReference, map[string]string], error) { + return r.kubeSecretCollectionResolver.ResolveSecretCollectionReferences(ctx, refs) +} + +// ResolveResourceSecretCollectionReferences resolves all of the specified genruntime.MetaObject's secret collections. +func (r *Resolver) ResolveResourceSecretCollectionReferences( + ctx context.Context, + metaObject genruntime.MetaObject, +) (genruntime.Resolved[genruntime.SecretCollectionReference, map[string]string], error) { + refs, err := reflecthelpers.FindSecretCollections(metaObject) + if err != nil { + return genruntime.Resolved[genruntime.SecretCollectionReference, map[string]string]{}, errors.Wrapf(err, "finding secrets on %q", metaObject.GetName()) + } + + // Include the namespace + namespacedSecretRefs := set.Make[genruntime.NamespacedSecretCollectionReference]() + for ref := range refs { + namespacedSecretRefs.Add(ref.AsNamespacedRef(metaObject.GetNamespace())) + } + + // resolve them + resolvedSecrets, err := r.ResolveSecretCollectionReferences(ctx, namespacedSecretRefs) + if err != nil { + return genruntime.Resolved[genruntime.SecretCollectionReference, map[string]string]{}, errors.Wrapf(err, "failed resolving secret references") } return resolvedSecrets, nil @@ -365,6 +400,11 @@ func (r *Resolver) ResolveAll(ctx context.Context, metaObject genruntime.ARMMeta return nil, genruntime.ConvertToARMResolvedDetails{}, err } + resolvedSecretCollections, err := r.ResolveResourceSecretCollectionReferences(ctx, metaObject) + if err != nil { + return nil, genruntime.ConvertToARMResolvedDetails{}, err + } + // Resolve all configmaps resolvedConfigMaps, err := r.ResolveResourceConfigMapReferences(ctx, metaObject) if err != nil { @@ -372,10 +412,11 @@ func (r *Resolver) ResolveAll(ctx context.Context, metaObject genruntime.ARMMeta } resolvedDetails := genruntime.ConvertToARMResolvedDetails{ - Name: resourceHierarchy.AzureName(), - ResolvedReferences: resolvedRefs, - ResolvedSecrets: resolvedSecrets, - ResolvedConfigMaps: resolvedConfigMaps, + Name: resourceHierarchy.AzureName(), + ResolvedReferences: resolvedRefs, + ResolvedSecrets: resolvedSecrets, + ResolvedSecretCollections: resolvedSecretCollections, + ResolvedConfigMaps: resolvedConfigMaps, } return resourceHierarchy, resolvedDetails, nil diff --git a/v2/internal/resolver/resolver_test.go b/v2/internal/resolver/resolver_test.go index e0039b01cfa..60eb925909b 100644 --- a/v2/internal/resolver/resolver_test.go +++ b/v2/internal/resolver/resolver_test.go @@ -520,7 +520,7 @@ func Test_ResolveSecrets_ReturnsExpectedSecretValue(t *testing.T) { armID := "/subscriptions/00000000-0000-0000-000000000000/resources/resourceGroups/myrg" resourceGroup := createResourceGroup(resourceGroupName) - genruntime.SetResourceID(resourceGroup, armID) // TODO: Do I actually need this here? + genruntime.SetResourceID(resourceGroup, armID) err = test.client.Create(ctx, resourceGroup) g.Expect(err).ToNot(HaveOccurred()) @@ -573,6 +573,75 @@ func Test_ResolveSecrets_ReturnsReferenceNotFound(t *testing.T) { g.Expect(errors.Unwrap(err)).To(BeAssignableToTypeOf(&core.SecretNotFound{})) } +func Test_ResolveSecretCollections_ReturnsExpectedSecretValues(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + ctx := context.TODO() + + test, err := testSetup() + g.Expect(err).ToNot(HaveOccurred()) + + resourceGroupName := "myrg" + armID := "/subscriptions/00000000-0000-0000-000000000000/resources/resourceGroups/myrg" + + resourceGroup := createResourceGroup(resourceGroupName) + genruntime.SetResourceID(resourceGroup, armID) + + err = test.client.Create(ctx, resourceGroup) + g.Expect(err).ToNot(HaveOccurred()) + + stringData := map[string]string{ + "mysecretkey": "myPinIs1234", + "myotherkey": "justKiddingIts5678", + } + + data := make(map[string][]byte, len(stringData)) + for k, v := range stringData { + data[k] = []byte(v) + } + + secretName := "testsecret" + // Create a secret + secret := &v1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretName, + Namespace: testNamespace, + }, + Data: data, + Type: "Opaque", + } + + err = test.client.Create(ctx, secret) + g.Expect(err).ToNot(HaveOccurred()) + + ref := genruntime.SecretCollectionReference{Name: secretName} + namespacedRef := ref.AsNamespacedRef(testNamespace) + + resolvedSecrets, err := test.resolver.ResolveSecretCollectionReferences(ctx, set.Make(namespacedRef)) + g.Expect(err).ToNot(HaveOccurred()) + + actualSecret, err := resolvedSecrets.Lookup(ref) + g.Expect(err).ToNot(HaveOccurred()) + g.Expect(actualSecret).To(Equal(stringData)) +} + +func Test_ResolveSecretCollections_ReturnsReferenceNotFound(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + ctx := context.TODO() + + test, err := testSetup() + g.Expect(err).ToNot(HaveOccurred()) + + secretName := "testsecret" + ref := genruntime.SecretCollectionReference{Name: secretName} + namespacedRef := ref.AsNamespacedRef(testNamespace) + + _, err = test.resolver.ResolveSecretCollectionReferences(ctx, set.Make(namespacedRef)) + g.Expect(err).To(HaveOccurred()) + g.Expect(errors.Unwrap(err)).To(BeAssignableToTypeOf(&core.SecretNotFound{})) +} + func Test_ResolveConfigMaps_ReturnsExpectedValue(t *testing.T) { t.Parallel() g := NewGomegaWithT(t) diff --git a/v2/internal/resolver/secret_collection_resolver.go b/v2/internal/resolver/secret_collection_resolver.go new file mode 100644 index 00000000000..0a63e264a07 --- /dev/null +++ b/v2/internal/resolver/secret_collection_resolver.go @@ -0,0 +1,89 @@ +/* +Copyright (c) Microsoft Corporation. +Licensed under the MIT license. +*/ + +package resolver + +import ( + "context" + + "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" + + "github.com/Azure/azure-service-operator/v2/internal/set" + "github.com/Azure/azure-service-operator/v2/internal/util/kubeclient" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime/core" +) + +// SecretCollectionResolver is a secret collection resolver +type SecretCollectionResolver interface { + ResolveSecretCollectionReference(ctx context.Context, ref genruntime.NamespacedSecretCollectionReference) (map[string]string, error) + ResolveSecretCollectionReferences(ctx context.Context, refs set.Set[genruntime.NamespacedSecretCollectionReference]) (genruntime.Resolved[genruntime.SecretCollectionReference, map[string]string], error) +} + +// kubeSecretResolver resolves Kubernetes secrets +type kubeSecretCollectionResolver struct { + client kubeclient.Client +} + +var _ SecretCollectionResolver = &kubeSecretCollectionResolver{} + +func NewKubeSecretCollectionResolver(client kubeclient.Client) SecretCollectionResolver { + return &kubeSecretCollectionResolver{ + client: client, + } +} + +// ResolveSecretCollectionReference resolves the secret collection and returns the corresponding secret collection, or an error +// if it could not be found +func (r *kubeSecretCollectionResolver) ResolveSecretCollectionReference( + ctx context.Context, + ref genruntime.NamespacedSecretCollectionReference, +) (map[string]string, error) { + refNamespacedName := types.NamespacedName{ + Namespace: ref.Namespace, + Name: ref.Name, + } + + secret := &v1.Secret{} + err := r.client.Get(ctx, refNamespacedName, secret) + if err != nil { + if apierrors.IsNotFound(err) { + err := core.NewSecretNotFoundError(refNamespacedName, err) + return nil, errors.WithStack(err) + } + + return nil, errors.Wrapf(err, "couldn't resolve secret collection %s", ref.String()) + } + + // TODO: Do we want to confirm that the type is Opaque? + + result := make(map[string]string, len(secret.Data)) + for k, v := range secret.Data { + result[k] = string(v) + } + + return result, nil +} + +// ResolveSecretCollectionReferences resolves all provided secret collection references +func (r *kubeSecretCollectionResolver) ResolveSecretCollectionReferences( + ctx context.Context, + refs set.Set[genruntime.NamespacedSecretCollectionReference], +) (genruntime.Resolved[genruntime.SecretCollectionReference, map[string]string], error) { + result := make(map[genruntime.SecretCollectionReference]map[string]string, len(refs)) + + for ref := range refs { + value, err := r.ResolveSecretCollectionReference(ctx, ref) + if err != nil { + return genruntime.MakeResolved[genruntime.SecretCollectionReference, map[string]string](nil), err + } + result[ref.SecretCollectionReference] = value + } + + return genruntime.MakeResolved(result), nil +} diff --git a/v2/pkg/genruntime/arm_transformer.go b/v2/pkg/genruntime/arm_transformer.go index 2f42d93bc35..759ee685529 100644 --- a/v2/pkg/genruntime/arm_transformer.go +++ b/v2/pkg/genruntime/arm_transformer.go @@ -25,6 +25,9 @@ type ConvertToARMResolvedDetails struct { // secret value. ResolvedSecrets Resolved[SecretReference, string] + // ResolvedSecretCollections is a set of which have been resolved to the corresponding collection of secrets. + ResolvedSecretCollections Resolved[SecretCollectionReference, map[string]string] + // ResolvedConfigMaps is a set of config map references which have been resolved to the corresponding // config map value. ResolvedConfigMaps Resolved[ConfigMapReference, string] diff --git a/v2/pkg/genruntime/secrets.go b/v2/pkg/genruntime/secrets.go index 66c4af5bd33..058e3572811 100644 --- a/v2/pkg/genruntime/secrets.go +++ b/v2/pkg/genruntime/secrets.go @@ -65,6 +65,58 @@ func (s NamespacedSecretReference) String() string { return fmt.Sprintf("Namespace: %q, %s", s.Namespace, s.SecretReference) } +// SecretCollectionReference is a reference to a Kubernetes secret in the same namespace as +// the resource it is on. +// +kubebuilder:object:generate=true +type SecretCollectionReference struct { // TODO: Should we be calling this SecretMap instead? + // Name is the name of the Kubernetes secret being referenced. + // The secret must be in the same namespace as the resource + // +kubebuilder:validation:Required + Name string `json:"name"` + + // If we end up wanting to support secrets from KeyVault (or elsewhere) we should be able to add a + // Type *SecretType + // here and default it to Kubernetes if it's not set. See the secrets design for more details. + // TODO: If we wanted to do this, probably the secret would need to be JSON-formatted, as unlike + // TODO: Kubernetes secrets there's no map[string]string structure inside of the secret. I think + // TODO: that's still probably preferable to having a map[string]SecretReference. + + // TODO: Ooh... but we could just call it []SecretReference and then for some order matters and for others it doesn't? +} + +var _ Indexer = SecretCollectionReference{} + +func (c SecretCollectionReference) Index() []string { + return []string{c.Name} +} + +// Copy makes an independent copy of the SecretReference +func (s SecretCollectionReference) Copy() SecretCollectionReference { + return s +} + +func (s SecretCollectionReference) String() string { + return fmt.Sprintf("Name: %q", s.Name) +} + +// AsNamespacedRef creates a NamespacedSecretReference from this SecretReference in the given namespace +func (s SecretCollectionReference) AsNamespacedRef(namespace string) NamespacedSecretCollectionReference { + return NamespacedSecretCollectionReference{ + SecretCollectionReference: s, + Namespace: namespace, + } +} + +// NamespacedSecretCollectionReference is an SecretCollectionReference with namespace information included +type NamespacedSecretCollectionReference struct { + SecretCollectionReference + Namespace string +} + +func (s NamespacedSecretCollectionReference) String() string { + return fmt.Sprintf("Namespace: %q, %s", s.Namespace, s.SecretCollectionReference) +} + // SecretDestination describes the location to store a single secret value. // Note: This is similar to ConfigMapDestination in configmaps.go. Changes to one should likely also be made to the other. type SecretDestination struct { diff --git a/v2/tools/generator/internal/armconversion/convert_from_arm_function_builder.go b/v2/tools/generator/internal/armconversion/convert_from_arm_function_builder.go index b52dcb51e63..f95e080be3f 100644 --- a/v2/tools/generator/internal/armconversion/convert_from_arm_function_builder.go +++ b/v2/tools/generator/internal/armconversion/convert_from_arm_function_builder.go @@ -220,10 +220,7 @@ func (builder *convertFromARMBuilder) secretPropertyHandler( toProp *astmodel.PropertyDefinition, _ *astmodel.ObjectType, ) (propertyConversionHandlerResult, error) { - isSecretReference := astmodel.TypeEquals(toProp.PropertyType(), astmodel.SecretReferenceType) - isOptionalSecretReference := astmodel.TypeEquals(toProp.PropertyType(), astmodel.OptionalSecretReferenceType) - - if !isSecretReference && !isOptionalSecretReference { + if !astmodel.IsTypeSecretReference(toProp.PropertyType()) { return notHandled, nil } diff --git a/v2/tools/generator/internal/armconversion/convert_to_arm_function_builder.go b/v2/tools/generator/internal/armconversion/convert_to_arm_function_builder.go index ccbcabbd97b..835f0491c02 100644 --- a/v2/tools/generator/internal/armconversion/convert_to_arm_function_builder.go +++ b/v2/tools/generator/internal/armconversion/convert_to_arm_function_builder.go @@ -63,6 +63,7 @@ func newConvertToARMFunctionBuilder( result.convertUserAssignedIdentitiesCollection, result.convertReferenceProperty, result.convertSecretProperty, + result.convertSecretCollectionProperty, result.convertConfigMapProperty, result.convertComplexTypeNameProperty) @@ -326,7 +327,7 @@ func (builder *convertToARMBuilder) referencePropertyHandler( isString := astmodel.TypeEquals(toProp.PropertyType(), astmodel.StringType) isOptionalString := astmodel.TypeEquals(toProp.PropertyType(), astmodel.OptionalStringType) isSliceString := astmodel.TypeEquals(toProp.PropertyType(), astmodel.NewArrayType(astmodel.StringType)) - isMapString := astmodel.TypeEquals(toProp.PropertyType(), astmodel.NewMapType(astmodel.StringType, astmodel.StringType)) + isMapString := astmodel.TypeEquals(toProp.PropertyType(), astmodel.MapOfStringStringType) if !isString && !isOptionalString && !isSliceString && !isMapString { return notHandled, nil @@ -767,6 +768,50 @@ func (builder *convertToARMBuilder) convertSecretProperty( return astbuilder.Statements(secretLookup, returnIfNotNil, result), nil } +// convertSecretCollectionProperty handles conversion of maps of secrets. +// This function generates code that looks like this: +// +// Secret, err := resolved.ResolvedSecretCollections.Lookup() +// if err != nil { +// return nil, errors.Wrap(err, "looking up secret for ") +// } +// = Secret +func (builder *convertToARMBuilder) convertSecretCollectionProperty( + _ *astmodel.ConversionFunctionBuilder, + params astmodel.ConversionParameters, +) ([]dst.Stmt, error) { + isMapStringString := astmodel.TypeEquals(params.DestinationType, astmodel.MapOfStringStringType) + if !isMapStringString { + return nil, nil + } + + isSecretCollectionReference := astmodel.TypeEquals(params.SourceType, astmodel.SecretCollectionReferenceType) + if !isSecretCollectionReference { + return nil, nil + } + + errorsPackage := builder.codeGenerationContext.MustGetImportedPackageName(astmodel.GitHubErrorsReference) + + localVarName := builder.idFactory.CreateLocal(params.NameHint + "Secret") + secretLookup := astbuilder.SimpleAssignmentWithErr( + dst.NewIdent(localVarName), + token.DEFINE, + astbuilder.CallExpr( + astbuilder.Selector(dst.NewIdent(resolvedParameterString), "ResolvedSecretCollections"), + "Lookup", + params.Source)) + + wrappedError := astbuilder.WrapError( + errorsPackage, + "err", + fmt.Sprintf("looking up secret for property %s", params.NameHint)) + returnIfNotNil := astbuilder.ReturnIfNotNil(dst.NewIdent("err"), astbuilder.Nil(), wrappedError) + + result := params.AssignmentHandlerOrDefault()(params.Destination, dst.NewIdent(localVarName)) + + return astbuilder.Statements(secretLookup, returnIfNotNil, result), nil +} + // convertConfigMapProperty handles conversion of configMap properties. // This function generates code that looks like this: // diff --git a/v2/tools/generator/internal/astmodel/secret_reference.go b/v2/tools/generator/internal/astmodel/secret_reference.go new file mode 100644 index 00000000000..ad732e80835 --- /dev/null +++ b/v2/tools/generator/internal/astmodel/secret_reference.go @@ -0,0 +1,25 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT license. + */ + +package astmodel + +var arraySecretReference = NewArrayType(SecretReferenceType) + +func IsTypeSecretReference(t Type) bool { + isSecretReference := TypeEquals(t, SecretReferenceType) + isOptionalSecretReference := TypeEquals(t, OptionalSecretReferenceType) + isSliceSecretReference := IsTypeSecretReferenceSlice(t) + isMapSecretReference := IsTypeSecretReferenceMap(t) + + return isSecretReference || isOptionalSecretReference || isSliceSecretReference || isMapSecretReference +} + +func IsTypeSecretReferenceSlice(t Type) bool { + return TypeEquals(t, arraySecretReference) +} + +func IsTypeSecretReferenceMap(t Type) bool { + return TypeEquals(t, SecretCollectionReferenceType) || TypeEquals(t, OptionalSecretCollectionReferenceType) +} diff --git a/v2/tools/generator/internal/astmodel/std_references.go b/v2/tools/generator/internal/astmodel/std_references.go index 41730a90dfe..58b87708e69 100644 --- a/v2/tools/generator/internal/astmodel/std_references.go +++ b/v2/tools/generator/internal/astmodel/std_references.go @@ -74,6 +74,7 @@ var ( ResourceScopeType = MakeExternalTypeName(GenRuntimeReference, "ResourceScope") ConvertToARMResolvedDetailsType = MakeExternalTypeName(GenRuntimeReference, "ConvertToARMResolvedDetails") SecretReferenceType = MakeExternalTypeName(GenRuntimeReference, "SecretReference") + SecretCollectionReferenceType = MakeExternalTypeName(GenRuntimeReference, "SecretCollectionReference") ResourceExtensionType = MakeExternalTypeName(GenRuntimeReference, "ResourceExtension") SecretDestinationType = MakeExternalTypeName(GenRuntimeReference, "SecretDestination") ConfigMapDestinationType = MakeExternalTypeName(GenRuntimeReference, "ConfigMapDestination") @@ -87,10 +88,14 @@ var ( ResourceOperationTypeArray = NewArrayType(ResourceOperationType) // Optional types - GenRuntime - OptionalConfigMapReferenceType = NewOptionalType(ConfigMapReferenceType) - OptionalKnownResourceReferenceType = NewOptionalType(KnownResourceReferenceType) - OptionalResourceReferenceType = NewOptionalType(ResourceReferenceType) - OptionalSecretReferenceType = NewOptionalType(SecretReferenceType) + OptionalConfigMapReferenceType = NewOptionalType(ConfigMapReferenceType) + OptionalKnownResourceReferenceType = NewOptionalType(KnownResourceReferenceType) + OptionalResourceReferenceType = NewOptionalType(ResourceReferenceType) + OptionalSecretReferenceType = NewOptionalType(SecretReferenceType) + OptionalSecretCollectionReferenceType = NewOptionalType(SecretCollectionReferenceType) + + // Predeclared maps + MapOfStringStringType = NewMapType(StringType, StringType) // Type names - Generic ARM client GenericClientType = MakeExternalTypeName(GenericARMClientReference, "GenericClient") diff --git a/v2/tools/generator/internal/codegen/pipeline/add_secrets.go b/v2/tools/generator/internal/codegen/pipeline/add_secrets.go index a23e4177fbb..62d05f25147 100644 --- a/v2/tools/generator/internal/codegen/pipeline/add_secrets.go +++ b/v2/tools/generator/internal/codegen/pipeline/add_secrets.go @@ -144,13 +144,29 @@ func removeStatusSecrets(definitions astmodel.TypeDefinitionSet) (astmodel.TypeD return result, nil } +func isTypeSecretReferenceCandidate(t astmodel.Type) bool { + isStringOrOptionalString := astmodel.TypeEquals(astmodel.Unwrap(t), astmodel.StringType) + + isStringSlice := isTypeSecretSliceCandidate(t) + isStringMap := isTypeSecretMapCandidate(t) + + return isStringOrOptionalString || isStringSlice || isStringMap +} + +func isTypeSecretSliceCandidate(t astmodel.Type) bool { + return astmodel.TypeEquals(t, astmodel.NewArrayType(astmodel.StringType)) +} + +func isTypeSecretMapCandidate(t astmodel.Type) bool { + return astmodel.TypeEquals(t, astmodel.MapOfStringStringType) +} + func removeSecretProperties(_ *astmodel.TypeVisitor[any], it *astmodel.ObjectType, _ any) (astmodel.Type, error) { for _, prop := range it.Properties().Copy() { if prop.IsSecret() { - // The expectation is that this is a string propType := prop.PropertyType() - if !astmodel.Unwrap(propType).Equals(astmodel.StringType, astmodel.EqualityOverrides{}) { - return nil, errors.Errorf("expected property %q to be a string, but was: %T", prop.PropertyName(), propType) + if !isTypeSecretReferenceCandidate(propType) { + return nil, errors.Errorf("expected property %q to be a string, optional string, map[string]string, or []string, but was: %q", prop.PropertyName(), astmodel.DebugDescription(propType)) } it = it.WithoutProperty(prop.PropertyName()) @@ -163,22 +179,25 @@ func removeSecretProperties(_ *astmodel.TypeVisitor[any], it *astmodel.ObjectTyp func transformSecretProperties(_ *astmodel.TypeVisitor[any], it *astmodel.ObjectType, _ any) (astmodel.Type, error) { for _, prop := range it.Properties().Copy() { if prop.IsSecret() { - // The expectation is that this is a string propType := prop.PropertyType() - if !astmodel.Unwrap(propType).Equals(astmodel.StringType, astmodel.EqualityOverrides{}) { - return nil, errors.Errorf("expected property %q to be a string, but was: %T", prop.PropertyName(), propType) - } - // check if it's optional - required := prop.IsRequired() + if !isTypeSecretReferenceCandidate(propType) { + return nil, errors.Errorf("expected property %q to be a string, optional string, map[string]string, or []string, but was: %T", prop.PropertyName(), astmodel.DebugDescription(propType)) + } var newType astmodel.Type - if required { - newType = astmodel.SecretReferenceType - } else { + if isTypeSecretSliceCandidate(prop.PropertyType()) { + newType = astmodel.NewArrayType(astmodel.SecretReferenceType) + } else if isTypeSecretMapCandidate(prop.PropertyType()) { + newType = astmodel.SecretCollectionReferenceType + } else if _, ok := astmodel.AsOptionalType(prop.PropertyType()); ok { newType = astmodel.NewOptionalType(astmodel.SecretReferenceType) + } else { + newType = astmodel.SecretReferenceType } - it = it.WithProperty(prop.WithType(newType)) + + updatedProp := prop.WithType(newType) + it = it.WithProperty(updatedProp) } } diff --git a/v2/tools/generator/internal/codegen/pipeline/apply_cross_resource_references_from_config.go b/v2/tools/generator/internal/codegen/pipeline/apply_cross_resource_references_from_config.go index 6ec46f42bdb..9aa6c86894b 100644 --- a/v2/tools/generator/internal/codegen/pipeline/apply_cross_resource_references_from_config.go +++ b/v2/tools/generator/internal/codegen/pipeline/apply_cross_resource_references_from_config.go @@ -181,7 +181,7 @@ func DoesPropertyLookLikeARMReference(prop *astmodel.PropertyDefinition) bool { isString := astmodel.TypeEquals(prop.PropertyType(), astmodel.StringType) isOptionalString := astmodel.TypeEquals(prop.PropertyType(), astmodel.OptionalStringType) isStringSlice := astmodel.TypeEquals(prop.PropertyType(), astmodel.NewArrayType(astmodel.StringType)) - isStringMap := astmodel.TypeEquals(prop.PropertyType(), astmodel.NewMapType(astmodel.StringType, astmodel.StringType)) + isStringMap := astmodel.TypeEquals(prop.PropertyType(), astmodel.MapOfStringStringType) if !isString && !isOptionalString && !isStringSlice && !isStringMap { return false diff --git a/v2/tools/generator/internal/codegen/pipeline/create_arm_types.go b/v2/tools/generator/internal/codegen/pipeline/create_arm_types.go index e37a5d003e3..5561ffe6471 100644 --- a/v2/tools/generator/internal/codegen/pipeline/create_arm_types.go +++ b/v2/tools/generator/internal/codegen/pipeline/create_arm_types.go @@ -329,7 +329,7 @@ func (c *armTypeCreator) createResourceReferenceProperty( if astmodel.IsTypeResourceReferenceSlice(prop.PropertyType()) { newPropType = astmodel.NewArrayType(astmodel.StringType) } else if astmodel.IsTypeResourceReferenceMap(prop.PropertyType()) { - newPropType = astmodel.NewMapType(astmodel.StringType, astmodel.StringType) + newPropType = astmodel.MapOfStringStringType } else { newPropType = astmodel.StringType } @@ -347,18 +347,19 @@ func (c *armTypeCreator) createSecretReferenceProperty( prop *astmodel.PropertyDefinition, _ *armPropertyTypeConversionContext, ) (*astmodel.PropertyDefinition, error) { - if !astmodel.TypeEquals(prop.PropertyType(), astmodel.SecretReferenceType) && - !astmodel.TypeEquals(prop.PropertyType(), astmodel.OptionalSecretReferenceType) { + if !astmodel.IsTypeSecretReference(prop.PropertyType()) { return nil, nil } - isRequired := astmodel.TypeEquals(prop.PropertyType(), astmodel.SecretReferenceType) - var newType astmodel.Type - if isRequired { - newType = astmodel.StringType - } else { + if astmodel.IsTypeSecretReferenceSlice(prop.PropertyType()) { + newType = astmodel.NewArrayType(astmodel.StringType) + } else if astmodel.IsTypeSecretReferenceMap(prop.PropertyType()) { + newType = astmodel.MapOfStringStringType + } else if astmodel.TypeEquals(prop.PropertyType(), astmodel.OptionalSecretReferenceType) { newType = astmodel.OptionalStringType + } else { + newType = astmodel.StringType } return prop.WithType(newType), nil diff --git a/v2/tools/generator/internal/codegen/pipeline/create_arm_types_test.go b/v2/tools/generator/internal/codegen/pipeline/create_arm_types_test.go index 1fce3715c2b..01ee5075dc8 100644 --- a/v2/tools/generator/internal/codegen/pipeline/create_arm_types_test.go +++ b/v2/tools/generator/internal/codegen/pipeline/create_arm_types_test.go @@ -260,3 +260,83 @@ func TestCreateARMTypeWithConfigMap_CreatesExpectedConversions(t *testing.T) { test.AssertPackagesGenerateExpectedCode(t, state.Definitions()) } + +func TestCreateARMTypeWithSecret_CreatesExpectedConversions(t *testing.T) { + t.Parallel() + g := NewGomegaWithT(t) + + secretDataProperty := astmodel.NewPropertyDefinition("SecretData", "secretData", astmodel.NewMapType(astmodel.StringType, astmodel.StringType)). + WithDescription("Secret data") + secretSliceProperty := astmodel.NewPropertyDefinition("SecretSlice", "secretSlice", astmodel.NewArrayType(astmodel.StringType)). + WithDescription("Secret data") + + // Define a test resource + specProperties := test.CreateObjectDefinition( + test.Pkg2020, + "PersonProperties", + test.FullNameProperty, + test.FamilyNameProperty, + test.KnownAsProperty, + secretDataProperty, + secretSliceProperty) + specPropertiesProp := astmodel.NewPropertyDefinition( + "Properties", + "properties", + specProperties.Name()).MakeTypeOptional() + spec := test.CreateSpec(test.Pkg2020, "Person", specPropertiesProp, test.NameProperty) + status := test.CreateStatus(test.Pkg2020, "Person") + resource := test.CreateARMResource(test.Pkg2020, "Person", spec, status, test.Pkg2020APIVersion) + + defs := make(astmodel.TypeDefinitionSet) + defs.AddAll(resource, status, spec, specProperties, test.Pkg2020APIVersion) + + idFactory := astmodel.NewIdentifierFactory() + omc := config.NewObjectModelConfiguration() + g.Expect( + omc.ModifyProperty( + specProperties.Name(), + test.FullNameProperty.PropertyName(), + func(pc *config.PropertyConfiguration) error { + pc.IsSecret.Set(true) + return nil + })). + To(Succeed()) + g.Expect( + omc.ModifyProperty( + specProperties.Name(), + secretDataProperty.PropertyName(), + func(pc *config.PropertyConfiguration) error { + pc.IsSecret.Set(true) + return nil + })). + To(Succeed()) + g.Expect( + omc.ModifyProperty( + specProperties.Name(), + secretSliceProperty.PropertyName(), + func(pc *config.PropertyConfiguration) error { + pc.IsSecret.Set(true) + return nil + })). + To(Succeed()) + + configuration := config.NewConfiguration() + configuration.ObjectModelConfiguration = omc + + addConfigMaps := AddSecrets(configuration) + createARMTypes := CreateARMTypes(omc, idFactory, logr.Discard()) + applyARMConversionInterface := ApplyARMConversionInterface(idFactory, omc) + simplify := SimplifyDefinitions() + strip := StripUnreferencedTypeDefinitions() + + state, err := RunTestPipeline( + NewState(defs), + addConfigMaps, + createARMTypes, + applyARMConversionInterface, + simplify, + strip) + g.Expect(err).ToNot(HaveOccurred()) + + test.AssertPackagesGenerateExpectedCode(t, state.Definitions()) +} diff --git a/v2/tools/generator/internal/codegen/pipeline/testdata/TestCreateARMTypeWithSecret_CreatesExpectedConversions/person-v20200101.golden b/v2/tools/generator/internal/codegen/pipeline/testdata/TestCreateARMTypeWithSecret_CreatesExpectedConversions/person-v20200101.golden new file mode 100644 index 00000000000..3c141676da9 --- /dev/null +++ b/v2/tools/generator/internal/codegen/pipeline/testdata/TestCreateARMTypeWithSecret_CreatesExpectedConversions/person-v20200101.golden @@ -0,0 +1,253 @@ +// Code generated by azure-service-operator-codegen. DO NOT EDIT. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +package v20200101 + +import ( + "fmt" + "github.com/Azure/azure-service-operator/v2/pkg/genruntime" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type Person struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec Person_Spec `json:"spec,omitempty"` + Status Person_STATUS `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type PersonList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Person `json:"items"` +} + +type Person_Spec_ARM struct { + // Name: The name of the resource + Name string `json:"name,omitempty"` + Properties *PersonProperties_ARM `json:"properties,omitempty"` +} + +var _ genruntime.ARMResourceSpec = &Person_Spec_ARM{} + +// GetAPIVersion returns the ARM API version of the resource. This is always v2020 +func (person Person_Spec_ARM) GetAPIVersion() string { + return string(APIVersion_v2020) +} + +// GetName returns the Name of the resource +func (person *Person_Spec_ARM) GetName() string { + return person.Name +} + +// GetType returns the ARM Type of the resource. This is always "" +func (person *Person_Spec_ARM) GetType() string { + return "" +} + +type Person_STATUS_ARM struct { + // Status: Current status + Status string `json:"status,omitempty"` +} + +// +kubebuilder:validation:Enum={v2020} +type APIVersion string + +const APIVersion_v2020 = APIVersion(v2020) + +type Person_Spec struct { + // AzureName: The name of the resource in Azure. This is often the same as the name of the resource in Kubernetes but it + // doesn't have to be. + AzureName string `json:"azureName,omitempty"` + Properties *PersonProperties `json:"properties,omitempty"` +} + +var _ genruntime.ARMTransformer = &Person_Spec{} + +// ConvertToARM converts from a Kubernetes CRD object to an ARM object +func (person *Person_Spec) ConvertToARM(resolved genruntime.ConvertToARMResolvedDetails) (interface{}, error) { + if person == nil { + return nil, nil + } + result := &Person_Spec_ARM{} + + // Set property "Name": + result.Name = resolved.Name + + // Set property "Properties": + if person.Properties != nil { + properties_ARM, err := (*person.Properties).ConvertToARM(resolved) + if err != nil { + return nil, err + } + properties := *properties_ARM.(*PersonProperties_ARM) + result.Properties = &properties + } + return result, nil +} + +// NewEmptyARMValue returns an empty ARM value suitable for deserializing into +func (person *Person_Spec) NewEmptyARMValue() genruntime.ARMResourceStatus { + return &Person_Spec_ARM{} +} + +// PopulateFromARM populates a Kubernetes CRD object from an Azure ARM object +func (person *Person_Spec) PopulateFromARM(owner genruntime.ArbitraryOwnerReference, armInput interface{}) error { + typedInput, ok := armInput.(Person_Spec_ARM) + if !ok { + return fmt.Errorf("unexpected type supplied for PopulateFromARM() function. Expected Person_Spec_ARM, got %T", armInput) + } + + // Set property "AzureName": + person.SetAzureName(genruntime.ExtractKubernetesResourceNameFromARMName(typedInput.Name)) + + // Set property "Properties": + if typedInput.Properties != nil { + var properties1 PersonProperties + err := properties1.PopulateFromARM(owner, *typedInput.Properties) + if err != nil { + return err + } + properties := properties1 + person.Properties = &properties + } + + // No error + return nil +} + +type Person_STATUS struct { + // Status: Current status + Status string `json:"status,omitempty"` +} + +var _ genruntime.FromARMConverter = &Person_STATUS{} + +// NewEmptyARMValue returns an empty ARM value suitable for deserializing into +func (person *Person_STATUS) NewEmptyARMValue() genruntime.ARMResourceStatus { + return &Person_STATUS_ARM{} +} + +// PopulateFromARM populates a Kubernetes CRD object from an Azure ARM object +func (person *Person_STATUS) PopulateFromARM(owner genruntime.ArbitraryOwnerReference, armInput interface{}) error { + typedInput, ok := armInput.(Person_STATUS_ARM) + if !ok { + return fmt.Errorf("unexpected type supplied for PopulateFromARM() function. Expected Person_STATUS_ARM, got %T", armInput) + } + + // Set property "Status": + person.Status = typedInput.Status + + // No error + return nil +} + +type PersonProperties_ARM struct { + // FamilyName: Shared name of the family + FamilyName string `json:"familyName,omitempty"` + + // FullName: As would be used to address mail + FullName string `json:"fullName,omitempty"` + + // KnownAs: How the person is generally known + KnownAs string `json:"knownAs,omitempty"` + + // SecretData: Secret data + SecretData map[string]string `json:"secretData,omitempty"` + + // SecretSlice: Secret data + SecretSlice []string `json:"secretSlice,omitempty"` +} + +type PersonProperties struct { + // FamilyName: Shared name of the family + FamilyName string `json:"familyName,omitempty"` + + // FullName: As would be used to address mail + FullName genruntime.SecretReference `json:"fullName,omitempty"` + + // KnownAs: How the person is generally known + KnownAs string `json:"knownAs,omitempty"` + + // SecretData: Secret data + SecretData genruntime.SecretCollectionReference `json:"secretData,omitempty"` + + // SecretSlice: Secret data + SecretSlice []genruntime.SecretReference `json:"secretSlice,omitempty"` +} + +var _ genruntime.ARMTransformer = &PersonProperties{} + +// ConvertToARM converts from a Kubernetes CRD object to an ARM object +func (properties *PersonProperties) ConvertToARM(resolved genruntime.ConvertToARMResolvedDetails) (interface{}, error) { + if properties == nil { + return nil, nil + } + result := &PersonProperties_ARM{} + + // Set property "FamilyName": + result.FamilyName = properties.FamilyName + + // Set property "FullName": + fullNameSecret, err := resolved.ResolvedSecrets.Lookup(properties.FullName) + if err != nil { + return nil, errors.Wrap(err, "looking up secret for property FullName") + } + result.FullName = fullNameSecret + + // Set property "KnownAs": + result.KnownAs = properties.KnownAs + + // Set property "SecretData": + secretDataSecret, err := resolved.ResolvedSecretCollections.Lookup(properties.SecretData) + if err != nil { + return nil, errors.Wrap(err, "looking up secret for property SecretData") + } + result.SecretData = secretDataSecret + + // Set property "SecretSlice": + for _, item := range properties.SecretSlice { + itemSecret, err := resolved.ResolvedSecrets.Lookup(item) + if err != nil { + return nil, errors.Wrap(err, "looking up secret for property item") + } + result.SecretSlice = append(result.SecretSlice, itemSecret) + } + return result, nil +} + +// NewEmptyARMValue returns an empty ARM value suitable for deserializing into +func (properties *PersonProperties) NewEmptyARMValue() genruntime.ARMResourceStatus { + return &PersonProperties_ARM{} +} + +// PopulateFromARM populates a Kubernetes CRD object from an Azure ARM object +func (properties *PersonProperties) PopulateFromARM(owner genruntime.ArbitraryOwnerReference, armInput interface{}) error { + typedInput, ok := armInput.(PersonProperties_ARM) + if !ok { + return fmt.Errorf("unexpected type supplied for PopulateFromARM() function. Expected PersonProperties_ARM, got %T", armInput) + } + + // Set property "FamilyName": + properties.FamilyName = typedInput.FamilyName + + // no assignment for property "FullName" + + // Set property "KnownAs": + properties.KnownAs = typedInput.KnownAs + + // no assignment for property "SecretData" + + // no assignment for property "SecretSlice" + + // No error + return nil +} + +func init() { + SchemeBuilder.Register(&Person{}, &PersonList{}) +}