From a1ab14639876fac7f2cf8dae4b7872a5aed692ca Mon Sep 17 00:00:00 2001 From: SoTrx <11771975+SoTrx@users.noreply.github.com> Date: Thu, 5 Sep 2024 07:50:20 +0200 Subject: [PATCH] Support for secret store reference in Dapr components (#7823) # Description Hey, This PR adds support for building Dapr components with an optional reference to a Dapr secret store. The new property is named _secretStoreComponentName_. This name is totally arbitrary but was chosen to try to be as clear as possible. A secret reference can be created using a _secretKeyRef_ object, following the same [naming convention as Dapr](https://docs.dapr.io/operations/components/component-secrets/). Example : ```yaml resource stateStore 'Applications.Dapr/stateStores@2023-10-01-preview' = { name: 'statestore' properties: { environment: environment application: application resourceProvisioning: 'manual' type: 'state.redis' version: 'v1' metadata: { redisHost: { secretKeyRef: { name: 'redisHost' key: 'redisHost' } } redisPassword: '' } secretStoreComponentName: daprSecretStore.name } } ``` ## Type of change - This pull request adds or changes features of Radius and has an approved issue (issue link required). Fixes: #7704 --------- Signed-off-by: SoTrx <11771975+SoTrx@users.noreply.github.com> --- .../2023-10-01-preview/types.json | 219 ++++++++++----- hack/bicep-types-radius/generated/index.json | 6 +- .../api/v20231001preview/datamodel_util.go | 62 +++++ .../v20231001preview/datamodel_util_test.go | 153 +++++++++++ .../pubsubbroker_conversion.go | 7 +- .../pubsubbroker_conversion_test.go | 14 +- .../secretstore_conversion.go | 6 +- .../secretstore_conversion_test.go | 22 +- .../v20231001preview/statestore_conversion.go | 6 +- .../statestore_conversion_test.go | 14 +- .../pubsubbroker_invalidrecipe_resource.json | 4 +- .../pubsubbroker_manual_datamodel.json | 7 +- ...pubsubbroker_manual_generic_datamodel.json | 7 +- .../pubsubbroker_manual_resource.json | 4 +- .../secretstore_invalidrecipe_resource.json | 4 +- .../testdata/secretstore_manual_resource.json | 4 +- .../secretstore_manual_resourcedatamodel.json | 4 +- .../secretstore_recipe_resourcedatamodel.json | 4 +- .../statestore_invalidrecipe_resource.json | 4 +- .../statestore_recipe_resourcedatamodel.json | 3 + .../testdata/statestore_values_resource.json | 7 +- .../statestore_values_resourcedatamodel.json | 7 +- .../v20231001preview/zz_generated_models.go | 82 +++++- .../zz_generated_models_serde.go | 206 ++++++++++++++ .../converter/pubsubbroker_converter_test.go | 18 +- pkg/daprrp/datamodel/daprpubsubbroker.go | 5 +- pkg/daprrp/datamodel/daprsecretstore.go | 10 +- pkg/daprrp/datamodel/daprstatestore.go | 14 +- .../processors/pubsubbrokers/processor.go | 1 + .../pubsubbrokers/processor_test.go | 250 +++++++++++------ .../processors/secretstores/processor_test.go | 32 ++- .../processors/statestores/processor.go | 1 + .../processors/statestores/processor_test.go | 253 ++++++++++++------ .../renderers/dapr/generic.go | 34 ++- pkg/rp/v1/types.go | 22 ++ .../preview/2023-10-01-preview/openapi.json | 136 +++++++++- .../noncloud/resources/dapr_pubsub_test.go | 73 ++++- .../resources/dapr_statestore_test.go | 73 +++++ ...rp-resources-component-name-conflict.bicep | 8 +- ...esources-pubsub-broker-manual-secret.bicep | 98 +++++++ ...aprrp-resources-pubsub-broker-manual.bicep | 8 +- .../daprrp-resources-secretstore-manual.bicep | 4 +- ...p-resources-statestore-manual-secret.bicep | 98 +++++++ .../daprrp-resources-statestore-manual.bicep | 8 +- test/testrecipes/modules/redis-selfhost.bicep | 19 +- typespec/Applications.Dapr/common.tsp | 29 +- typespec/Applications.Dapr/secretStores.tsp | 7 +- 47 files changed, 1731 insertions(+), 326 deletions(-) create mode 100644 test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-pubsub-broker-manual-secret.bicep create mode 100644 test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-statestore-manual-secret.bicep diff --git a/hack/bicep-types-radius/generated/applications/applications.dapr/2023-10-01-preview/types.json b/hack/bicep-types-radius/generated/applications/applications.dapr/2023-10-01-preview/types.json index 2abc60a5ec..1d3187139e 100644 --- a/hack/bicep-types-radius/generated/applications/applications.dapr/2023-10-01-preview/types.json +++ b/hack/bicep-types-radius/generated/applications/applications.dapr/2023-10-01-preview/types.json @@ -51,7 +51,7 @@ }, "tags": { "type": { - "$ref": "#/32" + "$ref": "#/36" }, "flags": 0, "description": "Resource tags." @@ -65,7 +65,7 @@ }, "systemData": { "type": { - "$ref": "#/33" + "$ref": "#/37" }, "flags": 2, "description": "Metadata pertaining to creation and last modification of the resource." @@ -113,10 +113,10 @@ }, "metadata": { "type": { - "$ref": "#/25" + "$ref": "#/27" }, "flags": 0, - "description": "Any object" + "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec" }, "type": { "type": { @@ -132,23 +132,30 @@ "flags": 0, "description": "Dapr component version" }, + "auth": { + "type": { + "$ref": "#/28" + }, + "flags": 0, + "description": "Authentication properties for a Dapr component object" + }, "resources": { "type": { - "$ref": "#/27" + "$ref": "#/30" }, "flags": 0, "description": "A collection of references to resources associated with the pubSubBroker" }, "recipe": { "type": { - "$ref": "#/28" + "$ref": "#/31" }, "flags": 0, "description": "The recipe used to automatically deploy underlying infrastructure for a portable resource" }, "resourceProvisioning": { "type": { - "$ref": "#/31" + "$ref": "#/35" }, "flags": 0, "description": "Specifies how the underlying service/resource is provisioned and managed. Available values are 'recipe', where Radius manages the lifecycle of the resource through a Recipe, and 'manual', where a user manages the resource and provides the values." @@ -396,7 +403,65 @@ } }, { - "$type": "AnyType" + "$type": "ObjectType", + "name": "MetadataValue", + "properties": { + "value": { + "type": { + "$ref": "#/0" + }, + "flags": 0, + "description": "The plain text value of the metadata" + }, + "secretKeyRef": { + "type": { + "$ref": "#/26" + }, + "flags": 0, + "description": "A reference of a value in a secret store component." + } + } + }, + { + "$type": "ObjectType", + "name": "MetadataValueFromSecret", + "properties": { + "name": { + "type": { + "$ref": "#/0" + }, + "flags": 1, + "description": "Secret name in the secret store component" + }, + "key": { + "type": { + "$ref": "#/0" + }, + "flags": 1, + "description": "The field to select in the secret value. If the secret value is a string, it should be equal to the secret name" + } + } + }, + { + "$type": "ObjectType", + "name": "DaprPubSubBrokerPropertiesMetadata", + "properties": {}, + "additionalProperties": { + "$ref": "#/25" + } + }, + { + "$type": "ObjectType", + "name": "DaprResourceAuth", + "properties": { + "secretStore": { + "type": { + "$ref": "#/0" + }, + "flags": 0, + "description": "Secret store to fetch secrets from" + } + } }, { "$type": "ObjectType", @@ -414,7 +479,7 @@ { "$type": "ArrayType", "itemType": { - "$ref": "#/26" + "$ref": "#/29" } }, { @@ -430,13 +495,16 @@ }, "parameters": { "type": { - "$ref": "#/25" + "$ref": "#/32" }, "flags": 0, "description": "Any object" } } }, + { + "$type": "AnyType" + }, { "$type": "StringLiteralType", "value": "recipe" @@ -449,10 +517,10 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/29" + "$ref": "#/33" }, { - "$ref": "#/30" + "$ref": "#/34" } ] }, @@ -477,7 +545,7 @@ }, "createdByType": { "type": { - "$ref": "#/38" + "$ref": "#/42" }, "flags": 0, "description": "The type of identity that created the resource." @@ -498,7 +566,7 @@ }, "lastModifiedByType": { "type": { - "$ref": "#/43" + "$ref": "#/47" }, "flags": 0, "description": "The type of identity that created the resource." @@ -532,16 +600,16 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/34" + "$ref": "#/38" }, { - "$ref": "#/35" + "$ref": "#/39" }, { - "$ref": "#/36" + "$ref": "#/40" }, { - "$ref": "#/37" + "$ref": "#/41" } ] }, @@ -565,16 +633,16 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/39" + "$ref": "#/43" }, { - "$ref": "#/40" + "$ref": "#/44" }, { - "$ref": "#/41" + "$ref": "#/45" }, { - "$ref": "#/42" + "$ref": "#/46" } ] }, @@ -616,28 +684,28 @@ }, "type": { "type": { - "$ref": "#/45" + "$ref": "#/49" }, "flags": 10, "description": "The resource type" }, "apiVersion": { "type": { - "$ref": "#/46" + "$ref": "#/50" }, "flags": 10, "description": "The resource api version" }, "properties": { "type": { - "$ref": "#/48" + "$ref": "#/52" }, "flags": 1, "description": "Dapr SecretStore portable resource properties" }, "tags": { "type": { - "$ref": "#/60" + "$ref": "#/65" }, "flags": 0, "description": "Resource tags." @@ -651,7 +719,7 @@ }, "systemData": { "type": { - "$ref": "#/33" + "$ref": "#/37" }, "flags": 2, "description": "Metadata pertaining to creation and last modification of the resource." @@ -678,7 +746,7 @@ }, "provisioningState": { "type": { - "$ref": "#/56" + "$ref": "#/60" }, "flags": 2, "description": "Provisioning state of the resource at the time the operation was called" @@ -699,10 +767,10 @@ }, "metadata": { "type": { - "$ref": "#/25" + "$ref": "#/61" }, "flags": 0, - "description": "Any object" + "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec" }, "type": { "type": { @@ -720,14 +788,14 @@ }, "recipe": { "type": { - "$ref": "#/28" + "$ref": "#/31" }, "flags": 0, "description": "The recipe used to automatically deploy underlying infrastructure for a portable resource" }, "resourceProvisioning": { "type": { - "$ref": "#/59" + "$ref": "#/64" }, "flags": 0, "description": "Specifies how the underlying service/resource is provisioned and managed. Available values are 'recipe', where Radius manages the lifecycle of the resource through a Recipe, and 'manual', where a user manages the resource and provides the values." @@ -766,28 +834,36 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/49" + "$ref": "#/53" }, { - "$ref": "#/50" + "$ref": "#/54" }, { - "$ref": "#/51" + "$ref": "#/55" }, { - "$ref": "#/52" + "$ref": "#/56" }, { - "$ref": "#/53" + "$ref": "#/57" }, { - "$ref": "#/54" + "$ref": "#/58" }, { - "$ref": "#/55" + "$ref": "#/59" } ] }, + { + "$type": "ObjectType", + "name": "DaprSecretStorePropertiesMetadata", + "properties": {}, + "additionalProperties": { + "$ref": "#/25" + } + }, { "$type": "StringLiteralType", "value": "recipe" @@ -800,10 +876,10 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/57" + "$ref": "#/62" }, { - "$ref": "#/58" + "$ref": "#/63" } ] }, @@ -820,7 +896,7 @@ "name": "Applications.Dapr/secretStores@2023-10-01-preview", "scopeType": 0, "body": { - "$ref": "#/47" + "$ref": "#/51" }, "flags": 0, "functions": {} @@ -853,28 +929,28 @@ }, "type": { "type": { - "$ref": "#/62" + "$ref": "#/67" }, "flags": 10, "description": "The resource type" }, "apiVersion": { "type": { - "$ref": "#/63" + "$ref": "#/68" }, "flags": 10, "description": "The resource api version" }, "properties": { "type": { - "$ref": "#/65" + "$ref": "#/70" }, "flags": 1, "description": "Dapr StateStore portable resource properties" }, "tags": { "type": { - "$ref": "#/78" + "$ref": "#/84" }, "flags": 0, "description": "Resource tags." @@ -888,7 +964,7 @@ }, "systemData": { "type": { - "$ref": "#/33" + "$ref": "#/37" }, "flags": 2, "description": "Metadata pertaining to creation and last modification of the resource." @@ -915,7 +991,7 @@ }, "provisioningState": { "type": { - "$ref": "#/73" + "$ref": "#/78" }, "flags": 2, "description": "Provisioning state of the resource at the time the operation was called" @@ -936,10 +1012,10 @@ }, "metadata": { "type": { - "$ref": "#/25" + "$ref": "#/79" }, "flags": 0, - "description": "Any object" + "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec" }, "type": { "type": { @@ -955,23 +1031,30 @@ "flags": 0, "description": "Dapr component version" }, + "auth": { + "type": { + "$ref": "#/28" + }, + "flags": 0, + "description": "Authentication properties for a Dapr component object" + }, "resources": { "type": { - "$ref": "#/74" + "$ref": "#/80" }, "flags": 0, "description": "A collection of references to resources associated with the state store" }, "recipe": { "type": { - "$ref": "#/28" + "$ref": "#/31" }, "flags": 0, "description": "The recipe used to automatically deploy underlying infrastructure for a portable resource" }, "resourceProvisioning": { "type": { - "$ref": "#/77" + "$ref": "#/83" }, "flags": 0, "description": "Specifies how the underlying service/resource is provisioned and managed. Available values are 'recipe', where Radius manages the lifecycle of the resource through a Recipe, and 'manual', where a user manages the resource and provides the values." @@ -1010,32 +1093,40 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/66" + "$ref": "#/71" }, { - "$ref": "#/67" + "$ref": "#/72" }, { - "$ref": "#/68" + "$ref": "#/73" }, { - "$ref": "#/69" + "$ref": "#/74" }, { - "$ref": "#/70" + "$ref": "#/75" }, { - "$ref": "#/71" + "$ref": "#/76" }, { - "$ref": "#/72" + "$ref": "#/77" } ] }, + { + "$type": "ObjectType", + "name": "DaprStateStorePropertiesMetadata", + "properties": {}, + "additionalProperties": { + "$ref": "#/25" + } + }, { "$type": "ArrayType", "itemType": { - "$ref": "#/26" + "$ref": "#/29" } }, { @@ -1050,10 +1141,10 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/75" + "$ref": "#/81" }, { - "$ref": "#/76" + "$ref": "#/82" } ] }, @@ -1070,7 +1161,7 @@ "name": "Applications.Dapr/stateStores@2023-10-01-preview", "scopeType": 0, "body": { - "$ref": "#/64" + "$ref": "#/69" }, "flags": 0, "functions": {} diff --git a/hack/bicep-types-radius/generated/index.json b/hack/bicep-types-radius/generated/index.json index 6657645e5d..394decc992 100644 --- a/hack/bicep-types-radius/generated/index.json +++ b/hack/bicep-types-radius/generated/index.json @@ -22,13 +22,13 @@ "$ref": "applications/applications.core/2023-10-01-preview/types.json#/271" }, "Applications.Dapr/pubSubBrokers@2023-10-01-preview": { - "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/44" + "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/48" }, "Applications.Dapr/secretStores@2023-10-01-preview": { - "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/61" + "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/66" }, "Applications.Dapr/stateStores@2023-10-01-preview": { - "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/79" + "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/85" }, "Applications.Datastores/mongoDatabases@2023-10-01-preview": { "$ref": "applications/applications.datastores/2023-10-01-preview/types.json#/48" diff --git a/pkg/daprrp/api/v20231001preview/datamodel_util.go b/pkg/daprrp/api/v20231001preview/datamodel_util.go index 180adf7ee0..6995665cc7 100644 --- a/pkg/daprrp/api/v20231001preview/datamodel_util.go +++ b/pkg/daprrp/api/v20231001preview/datamodel_util.go @@ -151,6 +151,68 @@ func fromRecipeDataModel(r portableresources.ResourceRecipe) *Recipe { } } +func toMetadataDataModel(metadata map[string]*MetadataValue) map[string]*rpv1.DaprComponentMetadataValue { + if metadata == nil { + return nil + } + + dmMeta := make(map[string]*rpv1.DaprComponentMetadataValue, len(metadata)) + for name, valueNode := range metadata { + dmMeta[name] = &rpv1.DaprComponentMetadataValue{ + Value: to.String(valueNode.Value), + } + + if valueNode.SecretKeyRef != nil { + dmMeta[name].SecretKeyRef = &rpv1.DaprComponentSecretRef{ + Name: to.String(valueNode.SecretKeyRef.Name), + Key: to.String(valueNode.SecretKeyRef.Key), + } + } + } + return dmMeta +} + +func fromMetadataDataModel(metadata map[string]*rpv1.DaprComponentMetadataValue) map[string]*MetadataValue { + if metadata == nil { + return nil + } + + meta := make(map[string]*MetadataValue, len(metadata)) + for name, valueNode := range metadata { + meta[name] = &MetadataValue{ + Value: to.Ptr(valueNode.Value), + } + + if valueNode.SecretKeyRef != nil { + meta[name].SecretKeyRef = &MetadataValueFromSecret{ + Name: to.Ptr(valueNode.SecretKeyRef.Name), + Key: to.Ptr(valueNode.SecretKeyRef.Key), + } + } + } + return meta +} + +func toAuthDataModel(auth *DaprResourceAuth) *rpv1.DaprComponentAuth { + if auth == nil { + return nil + } + + return &rpv1.DaprComponentAuth{ + SecretStore: to.String(auth.SecretStore), + } +} + +func fromAuthDataModel(auth *rpv1.DaprComponentAuth) *DaprResourceAuth { + if auth == nil { + return nil + } + + return &DaprResourceAuth{ + SecretStore: to.Ptr(auth.SecretStore), + } +} + func toResourcesDataModel(r []*ResourceReference) []*portableresources.ResourceReference { if r == nil { return nil diff --git a/pkg/daprrp/api/v20231001preview/datamodel_util_test.go b/pkg/daprrp/api/v20231001preview/datamodel_util_test.go index 414011547d..1f65fb94d5 100644 --- a/pkg/daprrp/api/v20231001preview/datamodel_util_test.go +++ b/pkg/daprrp/api/v20231001preview/datamodel_util_test.go @@ -293,3 +293,156 @@ func TestToRecipeDataModel(t *testing.T) { require.Equal(t, testCase.datamodel, sc) } } + +func TestToMetadataDataModel(t *testing.T) { + testCases := []struct { + metadata map[string]*MetadataValue + expected map[string]*rpv1.DaprComponentMetadataValue + }{ + { + metadata: nil, + expected: nil, + }, + { + metadata: map[string]*MetadataValue{"config": {Value: to.Ptr("extrasecure")}}, + expected: map[string]*rpv1.DaprComponentMetadataValue{"config": {Value: "extrasecure"}}, + }, + { + metadata: map[string]*MetadataValue{ + "secret": { + SecretKeyRef: &MetadataValueFromSecret{ + Key: to.Ptr("secretKey"), + Name: to.Ptr("secretValue"), + }, + }, + }, + expected: map[string]*rpv1.DaprComponentMetadataValue{ + "secret": { + SecretKeyRef: &rpv1.DaprComponentSecretRef{ + Key: "secretKey", + Name: "secretValue", + }, + }, + }, + }, + } + + for _, tt := range testCases { + actual := toMetadataDataModel(tt.metadata) + require.Equal(t, tt.expected, actual) + } +} + +func TestFromMetadataDataModel(t *testing.T) { + testCases := []struct { + metadata map[string]*rpv1.DaprComponentMetadataValue + expected map[string]*MetadataValue + }{ + { + metadata: nil, + expected: nil, + }, + { + metadata: map[string]*rpv1.DaprComponentMetadataValue{"config": {Value: "extrasecure"}}, + expected: map[string]*MetadataValue{"config": {Value: to.Ptr("extrasecure")}}, + }, + { + metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "secret": { + SecretKeyRef: &rpv1.DaprComponentSecretRef{ + Key: "secretKey", + Name: "secretValue", + }, + }, + }, + expected: map[string]*MetadataValue{ + "secret": { + Value: to.Ptr(""), + SecretKeyRef: &MetadataValueFromSecret{ + Key: to.Ptr("secretKey"), + Name: to.Ptr("secretValue"), + }, + }, + }, + }, + } + + for _, tt := range testCases { + actual := fromMetadataDataModel(tt.metadata) + require.Equal(t, tt.expected, actual) + } +} + +func TestToAuthDataModel(t *testing.T) { + testCases := []struct { + auth *DaprResourceAuth + expected *rpv1.DaprComponentAuth + }{ + { + auth: nil, + expected: nil, + }, + { + auth: &DaprResourceAuth{ + SecretStore: to.Ptr("test-secretstore"), + }, + expected: &rpv1.DaprComponentAuth{ + SecretStore: "test-secretstore", + }, + }, + { + auth: &DaprResourceAuth{ + SecretStore: nil, + }, + expected: &rpv1.DaprComponentAuth{ + SecretStore: "", + }, + }, + { + auth: &DaprResourceAuth{ + SecretStore: to.Ptr(""), + }, + expected: &rpv1.DaprComponentAuth{ + SecretStore: "", + }, + }, + } + + for _, tt := range testCases { + actual := toAuthDataModel(tt.auth) + require.Equal(t, tt.expected, actual) + } +} + +func TestFromAuthDataModel(t *testing.T) { + testCases := []struct { + auth *rpv1.DaprComponentAuth + expected *DaprResourceAuth + }{ + { + auth: nil, + expected: nil, + }, + { + auth: &rpv1.DaprComponentAuth{ + SecretStore: "test-secretstore", + }, + expected: &DaprResourceAuth{ + SecretStore: to.Ptr("test-secretstore"), + }, + }, + { + auth: &rpv1.DaprComponentAuth{ + SecretStore: "", + }, + expected: &DaprResourceAuth{ + SecretStore: to.Ptr(""), + }, + }, + } + + for _, tt := range testCases { + actual := fromAuthDataModel(tt.auth) + require.Equal(t, tt.expected, actual) + } +} diff --git a/pkg/daprrp/api/v20231001preview/pubsubbroker_conversion.go b/pkg/daprrp/api/v20231001preview/pubsubbroker_conversion.go index a0acf41755..fdfb746b2c 100644 --- a/pkg/daprrp/api/v20231001preview/pubsubbroker_conversion.go +++ b/pkg/daprrp/api/v20231001preview/pubsubbroker_conversion.go @@ -61,6 +61,7 @@ func (src *DaprPubSubBrokerResource) ConvertTo() (v1.DataModelInterface, error) } converted.Properties.Resources = toResourcesDataModel(src.Properties.Resources) + converted.Properties.Auth = toAuthDataModel(src.Properties.Auth) // Note: The metadata, type, and version fields cannot be specified when using recipes since // the recipe is expected to create the Dapr Component manifest. However, they are required @@ -79,8 +80,7 @@ func (src *DaprPubSubBrokerResource) ConvertTo() (v1.DataModelInterface, error) if src.Properties.Version == nil || *src.Properties.Version == "" { msgs = append(msgs, "version must be specified when resourceProvisioning is set to manual") } - - converted.Properties.Metadata = src.Properties.Metadata + converted.Properties.Metadata = toMetadataDataModel(src.Properties.Metadata) converted.Properties.Type = to.String(src.Properties.Type) converted.Properties.Version = to.String(src.Properties.Version) } else { @@ -132,10 +132,11 @@ func (dst *DaprPubSubBrokerResource) ConvertFrom(src v1.DataModelInterface) erro OutputResources: toOutputResources(daprPubSub.Properties.Status.OutputResources), Recipe: fromRecipeStatus(daprPubSub.Properties.Status.Recipe), }, + Auth: fromAuthDataModel(daprPubSub.Properties.Auth), } if daprPubSub.Properties.ResourceProvisioning == portableresources.ResourceProvisioningManual { - dst.Properties.Metadata = daprPubSub.Properties.Metadata + dst.Properties.Metadata = fromMetadataDataModel(daprPubSub.Properties.Metadata) dst.Properties.Type = to.Ptr(daprPubSub.Properties.Type) dst.Properties.Version = to.Ptr(daprPubSub.Properties.Version) } else { diff --git a/pkg/daprrp/api/v20231001preview/pubsubbroker_conversion_test.go b/pkg/daprrp/api/v20231001preview/pubsubbroker_conversion_test.go index 543129e9a0..20be95fb2a 100644 --- a/pkg/daprrp/api/v20231001preview/pubsubbroker_conversion_test.go +++ b/pkg/daprrp/api/v20231001preview/pubsubbroker_conversion_test.go @@ -64,8 +64,10 @@ func TestDaprPubSubBroker_ConvertVersionedToDataModel(t *testing.T) { Environment: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", }, ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{ - "foo": "bar", + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "foo": { + Value: "bar", + }, }, Resources: []*portableresources.ResourceReference{ { @@ -180,8 +182,10 @@ func TestDaprPubSubBroker_ConvertDataModelToVersioned(t *testing.T) { Properties: &DaprPubSubBrokerProperties{ Environment: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env"), Application: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app"), - Metadata: map[string]any{ - "foo": "bar", + Metadata: map[string]*MetadataValue{ + "foo": { + Value: to.Ptr("bar"), + }, }, ResourceProvisioning: to.Ptr(ResourceProvisioningManual), Resources: []*ResourceReference{ @@ -194,6 +198,7 @@ func TestDaprPubSubBroker_ConvertDataModelToVersioned(t *testing.T) { ComponentName: to.Ptr("test-dpsb"), ProvisioningState: to.Ptr(ProvisioningStateAccepted), Status: resourcetypeutil.MustPopulateResourceStatus(&ResourceStatus{}), + Auth: &DaprResourceAuth{SecretStore: to.Ptr("test-secret-store")}, }, Tags: map[string]*string{ "env": to.Ptr("dev"), @@ -218,6 +223,7 @@ func TestDaprPubSubBroker_ConvertDataModelToVersioned(t *testing.T) { ComponentName: to.Ptr("test-dpsb"), ProvisioningState: to.Ptr(ProvisioningStateAccepted), Status: resourcetypeutil.MustPopulateResourceStatusWithRecipe(&ResourceStatus{}), + Auth: nil, }, Tags: map[string]*string{ "env": to.Ptr("dev"), diff --git a/pkg/daprrp/api/v20231001preview/secretstore_conversion.go b/pkg/daprrp/api/v20231001preview/secretstore_conversion.go index 7b49a01559..65621ef255 100644 --- a/pkg/daprrp/api/v20231001preview/secretstore_conversion.go +++ b/pkg/daprrp/api/v20231001preview/secretstore_conversion.go @@ -73,7 +73,7 @@ func (src *DaprSecretStoreResource) ConvertTo() (v1.DataModelInterface, error) { msgs = append(msgs, "version must be specified when resourceProvisioning is set to manual") } - converted.Properties.Metadata = src.Properties.Metadata + converted.Properties.Metadata = toMetadataDataModel(src.Properties.Metadata) converted.Properties.Type = to.String(src.Properties.Type) converted.Properties.Version = to.String(src.Properties.Version) } else { @@ -120,7 +120,7 @@ func (dst *DaprSecretStoreResource) ConvertFrom(src v1.DataModelInterface) error Application: to.Ptr(daprSecretStore.Properties.Application), Type: to.Ptr(daprSecretStore.Properties.Type), Version: to.Ptr(daprSecretStore.Properties.Version), - Metadata: daprSecretStore.Properties.Metadata, + Metadata: fromMetadataDataModel(daprSecretStore.Properties.Metadata), ComponentName: to.Ptr(daprSecretStore.Properties.ComponentName), Status: &ResourceStatus{ OutputResources: toOutputResources(daprSecretStore.Properties.Status.OutputResources), @@ -128,7 +128,7 @@ func (dst *DaprSecretStoreResource) ConvertFrom(src v1.DataModelInterface) error }, } if daprSecretStore.Properties.ResourceProvisioning == portableresources.ResourceProvisioningManual { - dst.Properties.Metadata = daprSecretStore.Properties.Metadata + dst.Properties.Metadata = fromMetadataDataModel(daprSecretStore.Properties.Metadata) dst.Properties.Type = to.Ptr(daprSecretStore.Properties.Type) dst.Properties.Version = to.Ptr(daprSecretStore.Properties.Version) } else { diff --git a/pkg/daprrp/api/v20231001preview/secretstore_conversion_test.go b/pkg/daprrp/api/v20231001preview/secretstore_conversion_test.go index deb65db41e..e2bd95a4ff 100644 --- a/pkg/daprrp/api/v20231001preview/secretstore_conversion_test.go +++ b/pkg/daprrp/api/v20231001preview/secretstore_conversion_test.go @@ -64,8 +64,10 @@ func TestDaprSecretStore_ConvertVersionedToDataModel(t *testing.T) { Environment: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", }, ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{ - "foo": "bar", + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "foo": { + Value: "bar", + }, }, Type: "secretstores.hashicorp.vault", Version: "v1", @@ -143,8 +145,10 @@ func TestDaprSecretStore_ConvertDataModelToVersioned(t *testing.T) { Properties: &DaprSecretStoreProperties{ Environment: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env"), Application: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app"), - Metadata: map[string]any{ - "foo": "bar", + Metadata: map[string]*MetadataValue{ + "foo": { + Value: to.Ptr("bar"), + }, }, ResourceProvisioning: to.Ptr(ResourceProvisioningManual), Type: to.Ptr("secretstores.hashicorp.vault"), @@ -176,9 +180,13 @@ func TestDaprSecretStore_ConvertDataModelToVersioned(t *testing.T) { "foo": "bar", }, }, - Type: to.Ptr("secretstores.hashicorp.vault"), - Version: to.Ptr("v1"), - Metadata: map[string]any{"foo": "bar"}, + Type: to.Ptr("secretstores.hashicorp.vault"), + Version: to.Ptr("v1"), + Metadata: map[string]*MetadataValue{ + "foo": { + Value: to.Ptr("bar"), + }, + }, ComponentName: to.Ptr("test-dss"), ProvisioningState: to.Ptr(ProvisioningStateAccepted), Status: resourcetypeutil.MustPopulateResourceStatus(&ResourceStatus{ diff --git a/pkg/daprrp/api/v20231001preview/statestore_conversion.go b/pkg/daprrp/api/v20231001preview/statestore_conversion.go index d634fe54eb..eef3019cdf 100644 --- a/pkg/daprrp/api/v20231001preview/statestore_conversion.go +++ b/pkg/daprrp/api/v20231001preview/statestore_conversion.go @@ -45,6 +45,7 @@ func (src *DaprStateStoreResource) ConvertTo() (v1.DataModelInterface, error) { } converted.Properties.Resources = toResourcesDataModel(src.Properties.Resources) + converted.Properties.Auth = toAuthDataModel(src.Properties.Auth) // Note: The metadata, type, and version fields cannot be specified when using recipes since // the recipe is expected to create the Dapr Component manifest. However, they are required @@ -64,7 +65,7 @@ func (src *DaprStateStoreResource) ConvertTo() (v1.DataModelInterface, error) { msgs = append(msgs, "version must be specified when resourceProvisioning is set to manual") } - converted.Properties.Metadata = src.Properties.Metadata + converted.Properties.Metadata = toMetadataDataModel(src.Properties.Metadata) converted.Properties.Type = to.String(src.Properties.Type) converted.Properties.Version = to.String(src.Properties.Version) } else { @@ -115,12 +116,13 @@ func (dst *DaprStateStoreResource) ConvertFrom(src v1.DataModelInterface) error ComponentName: to.Ptr(daprStateStore.Properties.ComponentName), ResourceProvisioning: fromResourceProvisioningDataModel(daprStateStore.Properties.ResourceProvisioning), Resources: fromResourcesDataModel(daprStateStore.Properties.Resources), + Auth: fromAuthDataModel(daprStateStore.Properties.Auth), } if daprStateStore.Properties.ResourceProvisioning == portableresources.ResourceProvisioningManual { dst.Properties.Type = to.Ptr(daprStateStore.Properties.Type) dst.Properties.Version = to.Ptr(daprStateStore.Properties.Version) - dst.Properties.Metadata = daprStateStore.Properties.Metadata + dst.Properties.Metadata = fromMetadataDataModel(daprStateStore.Properties.Metadata) } else { dst.Properties.Recipe = fromRecipeDataModel(daprStateStore.Properties.Recipe) } diff --git a/pkg/daprrp/api/v20231001preview/statestore_conversion_test.go b/pkg/daprrp/api/v20231001preview/statestore_conversion_test.go index 32d7d3b9a9..551d955453 100644 --- a/pkg/daprrp/api/v20231001preview/statestore_conversion_test.go +++ b/pkg/daprrp/api/v20231001preview/statestore_conversion_test.go @@ -78,9 +78,12 @@ func TestDaprStateStore_ConvertVersionedToDataModel(t *testing.T) { expected.Properties.ResourceProvisioning = portableresources.ResourceProvisioningManual expected.Properties.Type = "state.zookeeper" expected.Properties.Version = "v1" - expected.Properties.Metadata = map[string]any{ - "foo": "bar", + expected.Properties.Metadata = map[string]*rpv1.DaprComponentMetadataValue{ + "foo": { + Value: "bar", + }, } + expected.Properties.Auth = &rpv1.DaprComponentAuth{SecretStore: "test-secret-store"} expected.Properties.Resources = []*portableresources.ResourceReference{ { ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Sql/servers/testServer/databases/testDatabase", @@ -155,6 +158,7 @@ func TestDaprStateStore_ConvertDataModelToVersioned(t *testing.T) { Environment: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/env0"), ComponentName: to.Ptr("stateStore0"), ProvisioningState: to.Ptr(ProvisioningStateAccepted), + Auth: &DaprResourceAuth{SecretStore: to.Ptr("test-secret-store")}, Status: resourcetypeutil.MustPopulateResourceStatusWithRecipe(&ResourceStatus{}), }, } @@ -163,8 +167,10 @@ func TestDaprStateStore_ConvertDataModelToVersioned(t *testing.T) { expected.Properties.ResourceProvisioning = to.Ptr(ResourceProvisioningManual) expected.Properties.Type = to.Ptr("state.zookeeper") expected.Properties.Version = to.Ptr("v1") - expected.Properties.Metadata = map[string]any{ - "foo": "bar", + expected.Properties.Metadata = map[string]*MetadataValue{ + "foo": { + Value: to.Ptr("bar"), + }, } expected.Properties.Resources = []*ResourceReference{ { diff --git a/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_invalidrecipe_resource.json b/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_invalidrecipe_resource.json index 05bc4a4375..6f65b15fd8 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_invalidrecipe_resource.json +++ b/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_invalidrecipe_resource.json @@ -12,7 +12,9 @@ "type": "pubsub.azure.servicebus", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } } } } \ No newline at end of file diff --git a/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_datamodel.json b/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_datamodel.json index 74714f0903..65033dfc94 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_datamodel.json +++ b/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_datamodel.json @@ -15,6 +15,9 @@ "lastModifiedAt": "2021-09-24T20:09:54.2403864Z" }, "properties": { + "auth": { + "secretStore": "test-secret-store" + }, "componentName": "test-dpsb", "status": { "outputResources": [ @@ -29,7 +32,9 @@ "type": "pubsub.azure.servicebus", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } }, "resources": [ { diff --git a/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_generic_datamodel.json b/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_generic_datamodel.json index bbd2688472..764f03e0a5 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_generic_datamodel.json +++ b/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_generic_datamodel.json @@ -15,6 +15,9 @@ "env": "dev" }, "properties": { + "auth": { + "secretStore": "test-secret-store" + }, "componentName": "test-dpsb", "status": { "outputResources": [ @@ -30,7 +33,9 @@ "type": "pubsub.kafka", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } } } } \ No newline at end of file diff --git a/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_resource.json b/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_resource.json index 2a8147fc30..7c58598f14 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_resource.json +++ b/pkg/daprrp/api/v20231001preview/testdata/pubsubbroker_manual_resource.json @@ -13,7 +13,9 @@ "type": "pubsub.azure.servicebus", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } }, "resources": [ { diff --git a/pkg/daprrp/api/v20231001preview/testdata/secretstore_invalidrecipe_resource.json b/pkg/daprrp/api/v20231001preview/testdata/secretstore_invalidrecipe_resource.json index c8a7b3b926..e551f9bbde 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/secretstore_invalidrecipe_resource.json +++ b/pkg/daprrp/api/v20231001preview/testdata/secretstore_invalidrecipe_resource.json @@ -12,7 +12,9 @@ "type": "secretstores.kubernetes", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } } } } \ No newline at end of file diff --git a/pkg/daprrp/api/v20231001preview/testdata/secretstore_manual_resource.json b/pkg/daprrp/api/v20231001preview/testdata/secretstore_manual_resource.json index 2d1f29241b..196a793898 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/secretstore_manual_resource.json +++ b/pkg/daprrp/api/v20231001preview/testdata/secretstore_manual_resource.json @@ -13,7 +13,9 @@ "type": "secretstores.hashicorp.vault", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } }, "resourceProvisioning":"manual" } diff --git a/pkg/daprrp/api/v20231001preview/testdata/secretstore_manual_resourcedatamodel.json b/pkg/daprrp/api/v20231001preview/testdata/secretstore_manual_resourcedatamodel.json index 69db9f7141..25420b24c0 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/secretstore_manual_resourcedatamodel.json +++ b/pkg/daprrp/api/v20231001preview/testdata/secretstore_manual_resourcedatamodel.json @@ -29,7 +29,9 @@ "type": "secretstores.hashicorp.vault", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } }, "resourceProvisioning": "manual" } diff --git a/pkg/daprrp/api/v20231001preview/testdata/secretstore_recipe_resourcedatamodel.json b/pkg/daprrp/api/v20231001preview/testdata/secretstore_recipe_resourcedatamodel.json index 822d6a96af..7b99fc4cf9 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/secretstore_recipe_resourcedatamodel.json +++ b/pkg/daprrp/api/v20231001preview/testdata/secretstore_recipe_resourcedatamodel.json @@ -32,7 +32,9 @@ "type": "secretstores.hashicorp.vault", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } }, "recipe": { "name": "daprSecretStore", diff --git a/pkg/daprrp/api/v20231001preview/testdata/statestore_invalidrecipe_resource.json b/pkg/daprrp/api/v20231001preview/testdata/statestore_invalidrecipe_resource.json index ed4a3529b9..4bd97c928b 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/statestore_invalidrecipe_resource.json +++ b/pkg/daprrp/api/v20231001preview/testdata/statestore_invalidrecipe_resource.json @@ -12,7 +12,9 @@ "type": "state.zookeeper", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } } } } \ No newline at end of file diff --git a/pkg/daprrp/api/v20231001preview/testdata/statestore_recipe_resourcedatamodel.json b/pkg/daprrp/api/v20231001preview/testdata/statestore_recipe_resourcedatamodel.json index 0ef9f96000..ce1be3c3aa 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/statestore_recipe_resourcedatamodel.json +++ b/pkg/daprrp/api/v20231001preview/testdata/statestore_recipe_resourcedatamodel.json @@ -16,6 +16,9 @@ }, "properties": { "componentName": "stateStore0", + "auth": { + "secretStore": "test-secret-store" + }, "status": { "outputResources": [ { diff --git a/pkg/daprrp/api/v20231001preview/testdata/statestore_values_resource.json b/pkg/daprrp/api/v20231001preview/testdata/statestore_values_resource.json index ddf99dc05f..ed8cb2fc19 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/statestore_values_resource.json +++ b/pkg/daprrp/api/v20231001preview/testdata/statestore_values_resource.json @@ -10,10 +10,15 @@ "application": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/testApplication", "environment": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/env0", "resourceProvisioning": "manual", + "auth": { + "secretStore": "test-secret-store" + }, "type": "state.zookeeper", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } }, "resources": [ { diff --git a/pkg/daprrp/api/v20231001preview/testdata/statestore_values_resourcedatamodel.json b/pkg/daprrp/api/v20231001preview/testdata/statestore_values_resourcedatamodel.json index fb6009836e..068af84570 100644 --- a/pkg/daprrp/api/v20231001preview/testdata/statestore_values_resourcedatamodel.json +++ b/pkg/daprrp/api/v20231001preview/testdata/statestore_values_resourcedatamodel.json @@ -16,6 +16,9 @@ }, "properties": { "componentName": "stateStore0", + "auth": { + "secretStore": "test-secret-store" + }, "status": { "outputResources": [ { @@ -29,7 +32,9 @@ "type": "state.zookeeper", "version": "v1", "metadata": { - "foo": "bar" + "foo": { + "value": "bar" + } }, "resources": [ { diff --git a/pkg/daprrp/api/v20231001preview/zz_generated_models.go b/pkg/daprrp/api/v20231001preview/zz_generated_models.go index 51706e3638..46a78a2cbe 100644 --- a/pkg/daprrp/api/v20231001preview/zz_generated_models.go +++ b/pkg/daprrp/api/v20231001preview/zz_generated_models.go @@ -17,8 +17,11 @@ type DaprPubSubBrokerProperties struct { // Fully qualified resource ID for the application that the portable resource is consumed by (if applicable) Application *string + // The name of the Dapr component to be used as a secret store + Auth *DaprResourceAuth + // The metadata for Dapr resource which must match the values specified in Dapr component spec - Metadata map[string]any + Metadata map[string]*MetadataValue // The recipe used to automatically deploy underlying infrastructure for the resource Recipe *Recipe @@ -93,11 +96,14 @@ type DaprPubSubBrokerResourceUpdateProperties struct { // Fully qualified resource ID for the application that the portable resource is consumed by (if applicable) Application *string + // The name of the Dapr component to be used as a secret store + Auth *DaprResourceAuth + // Fully qualified resource ID for the environment that the portable resource is linked to Environment *string // The metadata for Dapr resource which must match the values specified in Dapr component spec - Metadata map[string]any + Metadata map[string]*MetadataValueUpdate // The recipe used to automatically deploy underlying infrastructure for the resource Recipe *RecipeUpdate @@ -115,6 +121,12 @@ type DaprPubSubBrokerResourceUpdateProperties struct { Version *string } +// DaprResourceAuth - Authentication properties for a Dapr component object +type DaprResourceAuth struct { + // Secret store to fetch secrets from + SecretStore *string +} + // DaprSecretStoreProperties - Dapr SecretStore portable resource properties type DaprSecretStoreProperties struct { // REQUIRED; Fully qualified resource ID for the environment that the portable resource is linked to @@ -124,7 +136,7 @@ type DaprSecretStoreProperties struct { Application *string // The metadata for Dapr resource which must match the values specified in Dapr component spec - Metadata map[string]any + Metadata map[string]*MetadataValue // The recipe used to automatically deploy underlying infrastructure for the resource Recipe *Recipe @@ -200,7 +212,7 @@ type DaprSecretStoreResourceUpdateProperties struct { Environment *string // The metadata for Dapr resource which must match the values specified in Dapr component spec - Metadata map[string]any + Metadata map[string]*MetadataValueUpdate // The recipe used to automatically deploy underlying infrastructure for the resource Recipe *RecipeUpdate @@ -223,8 +235,11 @@ type DaprStateStoreProperties struct { // Fully qualified resource ID for the application that the portable resource is consumed by (if applicable) Application *string + // The name of the Dapr component to be used as a secret store + Auth *DaprResourceAuth + // The metadata for Dapr resource which must match the values specified in Dapr component spec - Metadata map[string]any + Metadata map[string]*MetadataValue // The recipe used to automatically deploy underlying infrastructure for the resource Recipe *Recipe @@ -299,11 +314,14 @@ type DaprStateStoreResourceUpdateProperties struct { // Fully qualified resource ID for the application that the portable resource is consumed by (if applicable) Application *string + // The name of the Dapr component to be used as a secret store + Auth *DaprResourceAuth + // Fully qualified resource ID for the environment that the portable resource is linked to Environment *string // The metadata for Dapr resource which must match the values specified in Dapr component spec - Metadata map[string]any + Metadata map[string]*MetadataValueUpdate // The recipe used to automatically deploy underlying infrastructure for the resource Recipe *RecipeUpdate @@ -406,6 +424,58 @@ func (k *KubernetesCompute) GetEnvironmentCompute() *EnvironmentCompute { } } +// MetadataValue - A single metadata for a Dapr component object +type MetadataValue struct { + // A reference of a value in a secret store component + SecretKeyRef *MetadataValueFromSecret + + // The plain text value of the metadata + Value *string +} + +// MetadataValueFromSecret - A reference of a value in a secret store component. +type MetadataValueFromSecret struct { + // REQUIRED; The field to select in the secret value. If the secret value is a string, it should be equal to the secret name + Key *string + + // REQUIRED; Secret name in the secret store component + Name *string +} + +// MetadataValueFromSecretUpdate - A reference of a value in a secret store component. +type MetadataValueFromSecretUpdate struct { + // The field to select in the secret value. If the secret value is a string, it should be equal to the secret name + Key *string + + // Secret name in the secret store component + Name *string +} + +// MetadataValueUpdate - A single metadata for a Dapr component object +type MetadataValueUpdate struct { + // A reference of a value in a secret store component + SecretKeyRef *MetadataValueFromSecretUpdate + + // The plain text value of the metadata + Value *string +} + +// NonRedundantDaprResourceProperties - The base properties of a Dapr component object. +type NonRedundantDaprResourceProperties struct { + // The metadata for Dapr resource which must match the values specified in Dapr component spec + Metadata map[string]*MetadataValue + + // Dapr component type which must matches the format used by Dapr Kubernetes configuration format + Type *string + + // Dapr component version + Version *string + + // READ-ONLY; The name of the Dapr component object. Use this value in your code when interacting with the Dapr client to +// use the Dapr component. + ComponentName *string +} + // Operation - Details of a REST API operation, returned from the Resource Provider Operations API type Operation struct { // Localized display information for this particular operation. diff --git a/pkg/daprrp/api/v20231001preview/zz_generated_models_serde.go b/pkg/daprrp/api/v20231001preview/zz_generated_models_serde.go index d33e1b130e..0ac475aaea 100644 --- a/pkg/daprrp/api/v20231001preview/zz_generated_models_serde.go +++ b/pkg/daprrp/api/v20231001preview/zz_generated_models_serde.go @@ -18,6 +18,7 @@ import ( func (d DaprPubSubBrokerProperties) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) populate(objectMap, "application", d.Application) + populate(objectMap, "auth", d.Auth) populate(objectMap, "componentName", d.ComponentName) populate(objectMap, "environment", d.Environment) populate(objectMap, "metadata", d.Metadata) @@ -43,6 +44,9 @@ func (d *DaprPubSubBrokerProperties) UnmarshalJSON(data []byte) error { case "application": err = unpopulate(val, "Application", &d.Application) delete(rawMsg, key) + case "auth": + err = unpopulate(val, "Auth", &d.Auth) + delete(rawMsg, key) case "componentName": err = unpopulate(val, "ComponentName", &d.ComponentName) delete(rawMsg, key) @@ -198,6 +202,7 @@ func (d *DaprPubSubBrokerResourceUpdate) UnmarshalJSON(data []byte) error { func (d DaprPubSubBrokerResourceUpdateProperties) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) populate(objectMap, "application", d.Application) + populate(objectMap, "auth", d.Auth) populate(objectMap, "environment", d.Environment) populate(objectMap, "metadata", d.Metadata) populate(objectMap, "recipe", d.Recipe) @@ -220,6 +225,9 @@ func (d *DaprPubSubBrokerResourceUpdateProperties) UnmarshalJSON(data []byte) er case "application": err = unpopulate(val, "Application", &d.Application) delete(rawMsg, key) + case "auth": + err = unpopulate(val, "Auth", &d.Auth) + delete(rawMsg, key) case "environment": err = unpopulate(val, "Environment", &d.Environment) delete(rawMsg, key) @@ -249,6 +257,33 @@ func (d *DaprPubSubBrokerResourceUpdateProperties) UnmarshalJSON(data []byte) er return nil } +// MarshalJSON implements the json.Marshaller interface for type DaprResourceAuth. +func (d DaprResourceAuth) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "secretStore", d.SecretStore) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type DaprResourceAuth. +func (d *DaprResourceAuth) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", d, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "secretStore": + err = unpopulate(val, "SecretStore", &d.SecretStore) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", d, err) + } + } + return nil +} + // MarshalJSON implements the json.Marshaller interface for type DaprSecretStoreProperties. func (d DaprSecretStoreProperties) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) @@ -480,6 +515,7 @@ func (d *DaprSecretStoreResourceUpdateProperties) UnmarshalJSON(data []byte) err func (d DaprStateStoreProperties) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) populate(objectMap, "application", d.Application) + populate(objectMap, "auth", d.Auth) populate(objectMap, "componentName", d.ComponentName) populate(objectMap, "environment", d.Environment) populate(objectMap, "metadata", d.Metadata) @@ -505,6 +541,9 @@ func (d *DaprStateStoreProperties) UnmarshalJSON(data []byte) error { case "application": err = unpopulate(val, "Application", &d.Application) delete(rawMsg, key) + case "auth": + err = unpopulate(val, "Auth", &d.Auth) + delete(rawMsg, key) case "componentName": err = unpopulate(val, "ComponentName", &d.ComponentName) delete(rawMsg, key) @@ -660,6 +699,7 @@ func (d *DaprStateStoreResourceUpdate) UnmarshalJSON(data []byte) error { func (d DaprStateStoreResourceUpdateProperties) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) populate(objectMap, "application", d.Application) + populate(objectMap, "auth", d.Auth) populate(objectMap, "environment", d.Environment) populate(objectMap, "metadata", d.Metadata) populate(objectMap, "recipe", d.Recipe) @@ -682,6 +722,9 @@ func (d *DaprStateStoreResourceUpdateProperties) UnmarshalJSON(data []byte) erro case "application": err = unpopulate(val, "Application", &d.Application) delete(rawMsg, key) + case "auth": + err = unpopulate(val, "Auth", &d.Auth) + delete(rawMsg, key) case "environment": err = unpopulate(val, "Environment", &d.Environment) delete(rawMsg, key) @@ -921,6 +964,169 @@ func (k *KubernetesCompute) UnmarshalJSON(data []byte) error { return nil } +// MarshalJSON implements the json.Marshaller interface for type MetadataValue. +func (m MetadataValue) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "secretKeyRef", m.SecretKeyRef) + populate(objectMap, "value", m.Value) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type MetadataValue. +func (m *MetadataValue) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "secretKeyRef": + err = unpopulate(val, "SecretKeyRef", &m.SecretKeyRef) + delete(rawMsg, key) + case "value": + err = unpopulate(val, "Value", &m.Value) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type MetadataValueFromSecret. +func (m MetadataValueFromSecret) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "key", m.Key) + populate(objectMap, "name", m.Name) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type MetadataValueFromSecret. +func (m *MetadataValueFromSecret) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "key": + err = unpopulate(val, "Key", &m.Key) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &m.Name) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type MetadataValueFromSecretUpdate. +func (m MetadataValueFromSecretUpdate) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "key", m.Key) + populate(objectMap, "name", m.Name) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type MetadataValueFromSecretUpdate. +func (m *MetadataValueFromSecretUpdate) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "key": + err = unpopulate(val, "Key", &m.Key) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &m.Name) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type MetadataValueUpdate. +func (m MetadataValueUpdate) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "secretKeyRef", m.SecretKeyRef) + populate(objectMap, "value", m.Value) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type MetadataValueUpdate. +func (m *MetadataValueUpdate) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "secretKeyRef": + err = unpopulate(val, "SecretKeyRef", &m.SecretKeyRef) + delete(rawMsg, key) + case "value": + err = unpopulate(val, "Value", &m.Value) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", m, err) + } + } + return nil +} + +// MarshalJSON implements the json.Marshaller interface for type NonRedundantDaprResourceProperties. +func (n NonRedundantDaprResourceProperties) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "componentName", n.ComponentName) + populate(objectMap, "metadata", n.Metadata) + populate(objectMap, "type", n.Type) + populate(objectMap, "version", n.Version) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type NonRedundantDaprResourceProperties. +func (n *NonRedundantDaprResourceProperties) UnmarshalJSON(data []byte) error { + var rawMsg map[string]json.RawMessage + if err := json.Unmarshal(data, &rawMsg); err != nil { + return fmt.Errorf("unmarshalling type %T: %v", n, err) + } + for key, val := range rawMsg { + var err error + switch key { + case "componentName": + err = unpopulate(val, "ComponentName", &n.ComponentName) + delete(rawMsg, key) + case "metadata": + err = unpopulate(val, "Metadata", &n.Metadata) + delete(rawMsg, key) + case "type": + err = unpopulate(val, "Type", &n.Type) + delete(rawMsg, key) + case "version": + err = unpopulate(val, "Version", &n.Version) + delete(rawMsg, key) + } + if err != nil { + return fmt.Errorf("unmarshalling type %T: %v", n, err) + } + } + return nil +} + // MarshalJSON implements the json.Marshaller interface for type Operation. func (o Operation) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) diff --git a/pkg/daprrp/datamodel/converter/pubsubbroker_converter_test.go b/pkg/daprrp/datamodel/converter/pubsubbroker_converter_test.go index 9c69621898..a914b87e9d 100644 --- a/pkg/daprrp/datamodel/converter/pubsubbroker_converter_test.go +++ b/pkg/daprrp/datamodel/converter/pubsubbroker_converter_test.go @@ -54,8 +54,10 @@ func TestPubSubBrokerDataModelToVersioned(t *testing.T) { Properties: &v20231001preview.DaprPubSubBrokerProperties{ Environment: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env"), Application: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app"), - Metadata: map[string]interface{}{ - "foo": "bar", + Metadata: map[string]*v20231001preview.MetadataValue{ + "foo": { + Value: to.Ptr("bar"), + }, }, Recipe: nil, ResourceProvisioning: to.Ptr(v20231001preview.ResourceProvisioningManual), @@ -69,6 +71,9 @@ func TestPubSubBrokerDataModelToVersioned(t *testing.T) { ComponentName: to.Ptr("test-dpsb"), ProvisioningState: to.Ptr(v20231001preview.ProvisioningStateAccepted), Status: resourcetypeutil.MustPopulateResourceStatus(&v20231001preview.ResourceStatus{}), + Auth: &v20231001preview.DaprResourceAuth{ + SecretStore: to.Ptr("test-secret-store"), + }, }, Tags: map[string]*string{ "env": to.Ptr("dev"), @@ -96,8 +101,10 @@ func TestPubSubBrokerDataModelToVersioned(t *testing.T) { Properties: &v20231001preview.DaprPubSubBrokerProperties{ Environment: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env"), Application: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app"), - Metadata: map[string]interface{}{ - "foo": "bar", + Metadata: map[string]*v20231001preview.MetadataValue{ + "foo": { + Value: to.Ptr("bar"), + }, }, Recipe: nil, ResourceProvisioning: to.Ptr(v20231001preview.ResourceProvisioningManual), @@ -107,6 +114,9 @@ func TestPubSubBrokerDataModelToVersioned(t *testing.T) { ComponentName: to.Ptr("test-dpsb"), ProvisioningState: to.Ptr(v20231001preview.ProvisioningStateAccepted), Status: resourcetypeutil.MustPopulateResourceStatus(&v20231001preview.ResourceStatus{}), + Auth: &v20231001preview.DaprResourceAuth{ + SecretStore: to.Ptr("test-secret-store"), + }, }, Tags: map[string]*string{ "env": to.Ptr("dev"), diff --git a/pkg/daprrp/datamodel/daprpubsubbroker.go b/pkg/daprrp/datamodel/daprpubsubbroker.go index 4e081317ae..8e22537c6e 100644 --- a/pkg/daprrp/datamodel/daprpubsubbroker.go +++ b/pkg/daprrp/datamodel/daprpubsubbroker.go @@ -72,7 +72,7 @@ type DaprPubSubBrokerProperties struct { ResourceProvisioning portableresources.ResourceProvisioning `json:"resourceProvisioning,omitempty"` // Metadata of the Dapr Pub/Sub Broker resource. - Metadata map[string]any `json:"metadata,omitempty"` + Metadata map[string]*rpv1.DaprComponentMetadataValue `json:"metadata,omitempty"` // The recipe used to automatically deploy underlying infrastructure for the Dapr Pub/Sub Broker resource. Recipe portableresources.ResourceRecipe `json:"recipe,omitempty"` @@ -85,4 +85,7 @@ type DaprPubSubBrokerProperties struct { // Version of the Dapr Pub/Sub Broker resource. Version string `json:"version,omitempty"` + + // Authentication information for the Dapr Pub/Sub Broker resource, mainly secret store name. + Auth *rpv1.DaprComponentAuth `json:"auth,omitempty"` } diff --git a/pkg/daprrp/datamodel/daprsecretstore.go b/pkg/daprrp/datamodel/daprsecretstore.go index bdd196c482..3e4da47f12 100644 --- a/pkg/daprrp/datamodel/daprsecretstore.go +++ b/pkg/daprrp/datamodel/daprsecretstore.go @@ -59,11 +59,11 @@ func (r *DaprSecretStore) ResourceTypeName() string { type DaprSecretStoreProperties struct { rpv1.BasicResourceProperties rpv1.BasicDaprResourceProperties - Type string `json:"type,omitempty"` - Version string `json:"version,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` - Recipe portableresources.ResourceRecipe `json:"recipe,omitempty"` - ResourceProvisioning portableresources.ResourceProvisioning `json:"resourceProvisioning,omitempty"` + Type string `json:"type,omitempty"` + Version string `json:"version,omitempty"` + Metadata map[string]*rpv1.DaprComponentMetadataValue `json:"metadata,omitempty"` + Recipe portableresources.ResourceRecipe `json:"recipe,omitempty"` + ResourceProvisioning portableresources.ResourceProvisioning `json:"resourceProvisioning,omitempty"` } // Recipe returns the Recipe from the DaprSecretStore Properties if ResourceProvisioning is not set to Manual, diff --git a/pkg/daprrp/datamodel/daprstatestore.go b/pkg/daprrp/datamodel/daprstatestore.go index 45f59011cb..cae08c2d5f 100644 --- a/pkg/daprrp/datamodel/daprstatestore.go +++ b/pkg/daprrp/datamodel/daprstatestore.go @@ -68,10 +68,12 @@ type DaprStateStoreProperties struct { rpv1.BasicResourceProperties rpv1.BasicDaprResourceProperties // Specifies how the underlying service/resource is provisioned and managed - ResourceProvisioning portableresources.ResourceProvisioning `json:"resourceProvisioning,omitempty"` - Metadata map[string]any `json:"metadata,omitempty"` - Recipe portableresources.ResourceRecipe `json:"recipe,omitempty"` - Resources []*portableresources.ResourceReference `json:"resources,omitempty"` - Type string `json:"type,omitempty"` - Version string `json:"version,omitempty"` + ResourceProvisioning portableresources.ResourceProvisioning `json:"resourceProvisioning,omitempty"` + Metadata map[string]*rpv1.DaprComponentMetadataValue `json:"metadata,omitempty"` + Recipe portableresources.ResourceRecipe `json:"recipe,omitempty"` + Resources []*portableresources.ResourceReference `json:"resources,omitempty"` + Type string `json:"type,omitempty"` + Version string `json:"version,omitempty"` + // Authentication information for the Dapr Pub/Sub Broker resource, mainly secret store name. + Auth *rpv1.DaprComponentAuth `json:"auth,omitempty"` } diff --git a/pkg/daprrp/processors/pubsubbrokers/processor.go b/pkg/daprrp/processors/pubsubbrokers/processor.go index 5b90cd724f..a821024b4b 100644 --- a/pkg/daprrp/processors/pubsubbrokers/processor.go +++ b/pkg/daprrp/processors/pubsubbrokers/processor.go @@ -77,6 +77,7 @@ func (p *Processor) Process(ctx context.Context, resource *datamodel.DaprPubSubB component, err := dapr.ConstructDaprGeneric( dapr.DaprGeneric{ + Auth: resource.Properties.Auth, Metadata: resource.Properties.Metadata, Type: to.Ptr(resource.Properties.Type), Version: to.Ptr(resource.Properties.Version), diff --git a/pkg/daprrp/processors/pubsubbrokers/processor_test.go b/pkg/daprrp/processors/pubsubbrokers/processor_test.go index e398252783..909514a752 100644 --- a/pkg/daprrp/processors/pubsubbrokers/processor_test.go +++ b/pkg/daprrp/processors/pubsubbrokers/processor_test.go @@ -47,6 +47,7 @@ func Test_Process(t *testing.T) { const appID = "/planes/radius/local/resourceGroups/test-rg/providers/Applications.Core/applications/test-app" const envID = "/planes/radius/local/resourceGroups/test-rg/providers/Applications.Core/environments/test-env" const componentName = "test-dapr-pubsub-broker" + const secretStoreComponentName = "test-dapr-secret-store" t.Run("success - recipe", func(t *testing.T) { processor := Processor{ @@ -117,93 +118,168 @@ func Test_Process(t *testing.T) { }) t.Run("success - manual", func(t *testing.T) { - processor := Processor{ - Client: k8sutil.NewFakeKubeClient(scheme.Scheme, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}}), - } - - resource := &datamodel.DaprPubSubBroker{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - Name: "some-other-name", - }, - }, - Properties: datamodel.DaprPubSubBrokerProperties{ - BasicResourceProperties: rpv1.BasicResourceProperties{ - Application: appID, - Environment: envID, - }, - BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ - ComponentName: componentName, - }, - ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{ - "config": "extrasecure", + testset := []struct { + description string + properties *datamodel.DaprPubSubBrokerProperties + generated *unstructured.Unstructured + }{ + { + description: "Raw values", + properties: &datamodel.DaprPubSubBrokerProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Application: appID, + Environment: envID, + }, + BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ + ComponentName: componentName, + }, + ResourceProvisioning: portableresources.ResourceProvisioningManual, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, + Type: "pubsub.redis", + Version: "v1", }, - Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, - Type: "pubsub.redis", - Version: "v1", - }, - } - - options := processors.Options{ - RuntimeConfiguration: recipes.RuntimeConfiguration{ - Kubernetes: &recipes.KubernetesRuntime{ - Namespace: "test-namespace", + generated: &unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": dapr.DaprAPIVersion, + "kind": dapr.DaprKind, + "metadata": map[string]any{ + "namespace": "test-namespace", + "name": "test-dapr-pubsub-broker", + "labels": kubernetes.MakeDescriptiveDaprLabels("test-app", "some-other-name", dapr_ctrl.DaprPubSubBrokersResourceType), + "resourceVersion": "1", + }, + "spec": map[string]any{ + "type": "pubsub.redis", + "version": "v1", + "metadata": []any{ + map[string]any{ + "name": "config", + "value": "extrasecure", + }, + }, + }, + }, }, }, - } - - err := processor.Process(context.Background(), resource, options) - require.NoError(t, err) - - require.Equal(t, componentName, resource.Properties.ComponentName) - - expectedValues := map[string]any{ - "componentName": componentName, - } - expectedSecrets := map[string]rpv1.SecretValueReference{} - - expectedOutputResources, err := processors.GetOutputResourcesFromResourcesField(resource.Properties.Resources) - - generated := &unstructured.Unstructured{ - Object: map[string]any{ - "apiVersion": dapr.DaprAPIVersion, - "kind": dapr.DaprKind, - "metadata": map[string]any{ - "namespace": "test-namespace", - "name": "test-dapr-pubsub-broker", - "labels": kubernetes.MakeDescriptiveDaprLabels("test-app", "some-other-name", dapr_ctrl.DaprPubSubBrokersResourceType), - "resourceVersion": "1", + { + description: "With secret store", + properties: &datamodel.DaprPubSubBrokerProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Application: appID, + Environment: envID, + }, + BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ + ComponentName: componentName, + }, + ResourceProvisioning: portableresources.ResourceProvisioningManual, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + "connectionString": { + SecretKeyRef: &rpv1.DaprComponentSecretRef{ + Name: "secretStoreName", + Key: "secretStoreKey", + }, + }, + }, + Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, + Type: "pubsub.redis", + Version: "v1", + Auth: &rpv1.DaprComponentAuth{ + SecretStore: secretStoreComponentName, + }, }, - "spec": map[string]any{ - "type": "pubsub.redis", - "version": "v1", - "metadata": []any{ - map[string]any{ - "name": "config", - "value": "extrasecure", + generated: &unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": dapr.DaprAPIVersion, + "kind": dapr.DaprKind, + "metadata": map[string]any{ + "namespace": "test-namespace", + "name": "test-dapr-pubsub-broker", + "labels": kubernetes.MakeDescriptiveDaprLabels("test-app", "some-other-name", dapr_ctrl.DaprPubSubBrokersResourceType), + "resourceVersion": "1", + }, + "spec": map[string]any{ + "type": "pubsub.redis", + "version": "v1", + "metadata": []any{ + map[string]any{ + "name": "config", + "value": "extrasecure", + }, + map[string]any{ + "name": "connectionString", + "secretKeyRef": map[string]any{ + "name": "secretStoreName", + "key": "secretStoreKey", + }, + }, + }, + }, + "auth": map[string]any{ + "secretStore": secretStoreComponentName, }, }, }, }, } - component := rpv1.NewKubernetesOutputResource("Component", generated, metav1.ObjectMeta{Name: generated.GetName(), Namespace: generated.GetNamespace()}) - component.RadiusManaged = to.Ptr(true) - expectedOutputResources = append(expectedOutputResources, component) - require.NoError(t, err) - - require.Equal(t, expectedValues, resource.ComputedValues) - require.Equal(t, expectedSecrets, resource.SecretValues) - require.Equal(t, expectedOutputResources, resource.Properties.Status.OutputResources) - - components := unstructured.UnstructuredList{} - components.SetAPIVersion("dapr.io/v1alpha1") - components.SetKind("Component") - err = processor.Client.List(context.Background(), &components, &client.ListOptions{Namespace: options.RuntimeConfiguration.Kubernetes.Namespace}) - require.NoError(t, err) - require.NotEmpty(t, components.Items) - require.Equal(t, []unstructured.Unstructured{*generated}, components.Items) + for _, tc := range testset { + t.Run(tc.description, func(t *testing.T) { + processor := Processor{ + Client: k8sutil.NewFakeKubeClient(scheme.Scheme, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}}), + } + resource := &datamodel.DaprPubSubBroker{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: "some-other-name", + }, + }, + Properties: *tc.properties, + } + options := processors.Options{ + RuntimeConfiguration: recipes.RuntimeConfiguration{ + Kubernetes: &recipes.KubernetesRuntime{ + Namespace: "test-namespace", + }, + }, + } + err := processor.Process(context.Background(), resource, options) + require.NoError(t, err) + + require.Equal(t, componentName, resource.Properties.ComponentName) + + expectedValues := map[string]any{ + "componentName": componentName, + } + expectedSecrets := map[string]rpv1.SecretValueReference{} + + expectedOutputResources, err := processors.GetOutputResourcesFromResourcesField(resource.Properties.Resources) + component := rpv1.NewKubernetesOutputResource("Component", tc.generated, metav1.ObjectMeta{Name: tc.generated.GetName(), Namespace: tc.generated.GetNamespace()}) + component.RadiusManaged = to.Ptr(true) + expectedOutputResources = append(expectedOutputResources, component) + require.NoError(t, err) + + require.Equal(t, expectedValues, resource.ComputedValues) + require.Equal(t, expectedSecrets, resource.SecretValues) + require.Equal(t, expectedOutputResources, resource.Properties.Status.OutputResources) + + components := unstructured.UnstructuredList{} + components.SetAPIVersion("dapr.io/v1alpha1") + components.SetKind("Component") + err = processor.Client.List(context.Background(), &components, &client.ListOptions{Namespace: options.RuntimeConfiguration.Kubernetes.Namespace}) + require.NoError(t, err) + require.NotEmpty(t, components.Items) + require.Equal(t, []unstructured.Unstructured{*tc.generated}, components.Items) + + }) + } }) t.Run("success - manual (no application)", func(t *testing.T) { @@ -225,8 +301,10 @@ func Test_Process(t *testing.T) { ComponentName: componentName, }, ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{ - "config": "extrasecure", + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, }, Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, Type: "pubsub.redis", @@ -374,7 +452,7 @@ func Test_Process(t *testing.T) { dapr.DaprGeneric{ Type: to.Ptr("pubsub.redis"), Version: to.Ptr("v1"), - Metadata: map[string]any{}, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{}, }, "test-namespace", "test-dapr-pubsub-broker", @@ -400,10 +478,14 @@ func Test_Process(t *testing.T) { ComponentName: componentName, }, ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{"config": "extrasecure"}, - Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, - Type: "pubsub.redis", - Version: "v1", + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, + Type: "pubsub.redis", + Version: "v1", }, } diff --git a/pkg/daprrp/processors/secretstores/processor_test.go b/pkg/daprrp/processors/secretstores/processor_test.go index 89713b7508..d65842fa1a 100644 --- a/pkg/daprrp/processors/secretstores/processor_test.go +++ b/pkg/daprrp/processors/secretstores/processor_test.go @@ -127,9 +127,13 @@ func Test_Process(t *testing.T) { ComponentName: componentName, }, ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{"config": "extrasecure"}, - Type: "secretstores.kubernetes", - Version: "v1", + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Type: "secretstores.kubernetes", + Version: "v1", }, } @@ -210,9 +214,13 @@ func Test_Process(t *testing.T) { ComponentName: componentName, }, ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{"config": "extrasecure"}, - Type: "secretstores.kubernetes", - Version: "v1", + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Type: "secretstores.kubernetes", + Version: "v1", }, } @@ -343,7 +351,7 @@ func Test_Process(t *testing.T) { dapr.DaprGeneric{ Type: to.Ptr("secretstores.kubernetes"), Version: to.Ptr("v1"), - Metadata: map[string]any{}, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{}, }, "test-namespace", "test-component", @@ -369,9 +377,13 @@ func Test_Process(t *testing.T) { ComponentName: componentName, }, ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{"config": "extrasecure"}, - Type: "secretstores.kubernetes", - Version: "v1", + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Type: "secretstores.kubernetes", + Version: "v1", }, } diff --git a/pkg/daprrp/processors/statestores/processor.go b/pkg/daprrp/processors/statestores/processor.go index be9e439423..96e3c24901 100644 --- a/pkg/daprrp/processors/statestores/processor.go +++ b/pkg/daprrp/processors/statestores/processor.go @@ -77,6 +77,7 @@ func (p *Processor) Process(ctx context.Context, resource *datamodel.DaprStateSt component, err := dapr.ConstructDaprGeneric( dapr.DaprGeneric{ + Auth: resource.Properties.Auth, Metadata: resource.Properties.Metadata, Type: to.Ptr(resource.Properties.Type), Version: to.Ptr(resource.Properties.Version), diff --git a/pkg/daprrp/processors/statestores/processor_test.go b/pkg/daprrp/processors/statestores/processor_test.go index 1e6e9516e2..2e347c3ff2 100644 --- a/pkg/daprrp/processors/statestores/processor_test.go +++ b/pkg/daprrp/processors/statestores/processor_test.go @@ -48,6 +48,7 @@ func Test_Process(t *testing.T) { const applicationID = "/planes/radius/local/resourceGroups/test-rg/providers/Applications.Core/applications/test-app" const envID = "/planes/radius/local/resourceGroups/test-rg/providers/Applications.Core/environments/test-env" const componentName = "test-component" + const secretStoreComponentName = "test-dapr-secret-store" t.Run("success - recipe", func(t *testing.T) { processor := Processor{ @@ -113,90 +114,166 @@ func Test_Process(t *testing.T) { }) t.Run("success - manual", func(t *testing.T) { - processor := Processor{ - Client: k8sutil.NewFakeKubeClient(scheme.Scheme, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}}), - } - - resource := &datamodel.DaprStateStore{ - BaseResource: v1.BaseResource{ - TrackedResource: v1.TrackedResource{ - Name: "some-other-name", - }, - }, - Properties: datamodel.DaprStateStoreProperties{ - BasicResourceProperties: rpv1.BasicResourceProperties{ - Application: applicationID, - }, - BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ - ComponentName: componentName, + testset := []struct { + description string + properties *datamodel.DaprStateStoreProperties + generated *unstructured.Unstructured + }{ + { + description: "Raw values", + properties: &datamodel.DaprStateStoreProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Application: applicationID, + }, + BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ + ComponentName: componentName, + }, + ResourceProvisioning: portableresources.ResourceProvisioningManual, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, + Type: "state.redis", + Version: "v1", }, - ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{"config": "extrasecure"}, - Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, - Type: "state.redis", - Version: "v1", - }, - } - - options := processors.Options{ - RuntimeConfiguration: recipes.RuntimeConfiguration{ - Kubernetes: &recipes.KubernetesRuntime{ - Namespace: "test-namespace", + generated: &unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": dapr.DaprAPIVersion, + "kind": dapr.DaprKind, + "metadata": map[string]any{ + "namespace": "test-namespace", + "name": "test-component", + "labels": kubernetes.MakeDescriptiveDaprLabels("test-app", "some-other-name", dapr_ctrl.DaprStateStoresResourceType), + "resourceVersion": "1", + }, + "spec": map[string]any{ + "type": "state.redis", + "version": "v1", + "metadata": []any{ + map[string]any{ + "name": "config", + "value": "extrasecure", + }, + }, + }, + }, }, }, - } - - err := processor.Process(context.Background(), resource, options) - require.NoError(t, err) - - require.Equal(t, componentName, resource.Properties.ComponentName) - - expectedValues := map[string]any{ - "componentName": componentName, - } - expectedSecrets := map[string]rpv1.SecretValueReference{} - - expectedOutputResources, err := processors.GetOutputResourcesFromResourcesField(resource.Properties.Resources) - - generated := &unstructured.Unstructured{ - Object: map[string]any{ - "apiVersion": dapr.DaprAPIVersion, - "kind": dapr.DaprKind, - "metadata": map[string]any{ - "namespace": "test-namespace", - "name": "test-component", - "labels": kubernetes.MakeDescriptiveDaprLabels("test-app", "some-other-name", dapr_ctrl.DaprStateStoresResourceType), - "resourceVersion": "1", + { + description: "With secret store", + properties: &datamodel.DaprStateStoreProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Application: applicationID, + }, + BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ + ComponentName: componentName, + }, + ResourceProvisioning: portableresources.ResourceProvisioningManual, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + "connectionString": { + SecretKeyRef: &rpv1.DaprComponentSecretRef{ + Name: "secretStoreName", + Key: "secretStoreKey", + }, + }, + }, + Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, + Type: "state.redis", + Version: "v1", + Auth: &rpv1.DaprComponentAuth{ + SecretStore: secretStoreComponentName, + }, }, - "spec": map[string]any{ - "type": "state.redis", - "version": "v1", - "metadata": []any{ - map[string]any{ - "name": "config", - "value": "extrasecure", + generated: &unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": dapr.DaprAPIVersion, + "kind": dapr.DaprKind, + "metadata": map[string]any{ + "namespace": "test-namespace", + "name": "test-component", + "labels": kubernetes.MakeDescriptiveDaprLabels("test-app", "some-other-name", dapr_ctrl.DaprStateStoresResourceType), + "resourceVersion": "1", + }, + "spec": map[string]any{ + "type": "state.redis", + "version": "v1", + "metadata": []any{ + map[string]any{ + "name": "config", + "value": "extrasecure", + }, + map[string]any{ + "name": "connectionString", + "secretKeyRef": map[string]any{ + "name": "secretStoreName", + "key": "secretStoreKey", + }, + }, + }, + }, + "auth": map[string]any{ + "secretStore": secretStoreComponentName, }, }, }, }, } - - component := rpv1.NewKubernetesOutputResource("Component", generated, metav1.ObjectMeta{Name: generated.GetName(), Namespace: generated.GetNamespace()}) - component.RadiusManaged = to.Ptr(true) - expectedOutputResources = append(expectedOutputResources, component) - require.NoError(t, err) - - require.Equal(t, expectedValues, resource.ComputedValues) - require.Equal(t, expectedSecrets, resource.SecretValues) - require.Equal(t, expectedOutputResources, resource.Properties.Status.OutputResources) - - components := unstructured.UnstructuredList{} - components.SetAPIVersion("dapr.io/v1alpha1") - components.SetKind("Component") - err = processor.Client.List(context.Background(), &components, &client.ListOptions{Namespace: options.RuntimeConfiguration.Kubernetes.Namespace}) - require.NoError(t, err) - require.NotEmpty(t, components.Items) - require.Equal(t, []unstructured.Unstructured{*generated}, components.Items) + for _, tc := range testset { + t.Run(tc.description, func(t *testing.T) { + + processor := Processor{ + Client: k8sutil.NewFakeKubeClient(scheme.Scheme, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}}), + } + resource := &datamodel.DaprStateStore{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: "some-other-name", + }, + }, + Properties: *tc.properties, + } + options := processors.Options{ + RuntimeConfiguration: recipes.RuntimeConfiguration{ + Kubernetes: &recipes.KubernetesRuntime{ + Namespace: "test-namespace", + }, + }, + } + err := processor.Process(context.Background(), resource, options) + require.NoError(t, err) + + require.Equal(t, componentName, resource.Properties.ComponentName) + + expectedValues := map[string]any{ + "componentName": componentName, + } + expectedSecrets := map[string]rpv1.SecretValueReference{} + + expectedOutputResources, err := processors.GetOutputResourcesFromResourcesField(resource.Properties.Resources) + + component := rpv1.NewKubernetesOutputResource("Component", tc.generated, metav1.ObjectMeta{Name: tc.generated.GetName(), Namespace: tc.generated.GetNamespace()}) + component.RadiusManaged = to.Ptr(true) + expectedOutputResources = append(expectedOutputResources, component) + require.NoError(t, err) + + require.Equal(t, expectedValues, resource.ComputedValues) + require.Equal(t, expectedSecrets, resource.SecretValues) + require.Equal(t, expectedOutputResources, resource.Properties.Status.OutputResources) + + components := unstructured.UnstructuredList{} + components.SetAPIVersion("dapr.io/v1alpha1") + components.SetKind("Component") + err = processor.Client.List(context.Background(), &components, &client.ListOptions{Namespace: options.RuntimeConfiguration.Kubernetes.Namespace}) + require.NoError(t, err) + require.NotEmpty(t, components.Items) + require.Equal(t, []unstructured.Unstructured{*tc.generated}, components.Items) + }) + } }) t.Run("success - manual (no application)", func(t *testing.T) { @@ -218,10 +295,14 @@ func Test_Process(t *testing.T) { ComponentName: componentName, }, ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{"config": "extrasecure"}, - Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, - Type: "state.redis", - Version: "v1", + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, + Type: "state.redis", + Version: "v1", }, } @@ -361,7 +442,7 @@ func Test_Process(t *testing.T) { dapr.DaprGeneric{ Type: to.Ptr("state.redis"), Version: to.Ptr("v1"), - Metadata: map[string]any{}, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{}, }, "test-namespace", "test-component", @@ -387,10 +468,14 @@ func Test_Process(t *testing.T) { ComponentName: componentName, }, ResourceProvisioning: portableresources.ResourceProvisioningManual, - Metadata: map[string]any{"config": "extrasecure"}, - Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, - Type: "state.redis", - Version: "v1", + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, + Type: "state.redis", + Version: "v1", }, } diff --git a/pkg/portableresources/renderers/dapr/generic.go b/pkg/portableresources/renderers/dapr/generic.go index 4fe26dbc9c..7fbb070d0c 100644 --- a/pkg/portableresources/renderers/dapr/generic.go +++ b/pkg/portableresources/renderers/dapr/generic.go @@ -18,16 +18,19 @@ package dapr import ( "fmt" + rpv1 "github.com/radius-project/radius/pkg/rp/v1" v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" "github.com/radius-project/radius/pkg/kubernetes" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sort" ) type DaprGeneric struct { Type *string Version *string - Metadata map[string]any + Metadata map[string]*rpv1.DaprComponentMetadataValue + Auth *rpv1.DaprComponentAuth } // Validate checks if the required fields of a DaprGeneric struct are set and returns an error if any of them are not. @@ -56,13 +59,29 @@ func ConstructDaprGeneric(daprGeneric DaprGeneric, namespace string, componentNa // Dapr specs: https://docs.dapr.io/reference/components-reference/ yamlListItems := []any{} // K8s fake client requires this ..... :( for k, v := range daprGeneric.Metadata { + // v = {value : "value"} || {secretKeyRef : {name : "name", key : "key"}} yamlItem := map[string]any{ - "name": k, - "value": v, + "name": k, } + + if v.SecretKeyRef != nil { + yamlItem["secretKeyRef"] = map[string]any{ + "name": v.SecretKeyRef.Name, + "key": v.SecretKeyRef.Key, + } + } else { + yamlItem["value"] = v.Value + } + yamlListItems = append(yamlListItems, yamlItem) } + // Note : Prevents flakiness in tests, a slice is not guaranteed to be sorted + // Without this, all tests containing Dapr components with multiple metadata would have to use a custom match function + sort.Slice(yamlListItems, func(i, j int) bool { + return yamlListItems[i].(map[string]any)["name"].(string) < yamlListItems[j].(map[string]any)["name"].(string) + }) + // Translate into Dapr State Store schema item := unstructured.Unstructured{ Object: map[string]any{ @@ -80,5 +99,14 @@ func ConstructDaprGeneric(daprGeneric DaprGeneric, namespace string, componentNa }, }, } + + // Although an empty string value to the "secretStore" property is valid according to Dapr specs, + // meaning no secret store is used, it may cause confusion to users. + // Therefore, we only add the "auth" property if the secret store is specified. + if daprGeneric.Auth != nil && daprGeneric.Auth.SecretStore != "" { + item.Object["auth"] = map[string]any{ + "secretStore": daprGeneric.Auth.SecretStore, + } + } return item, nil } diff --git a/pkg/rp/v1/types.go b/pkg/rp/v1/types.go index ab7dcae945..9bc10bb49c 100644 --- a/pkg/rp/v1/types.go +++ b/pkg/rp/v1/types.go @@ -49,6 +49,28 @@ type BasicResourceProperties struct { Status ResourceStatus `json:"status,omitempty"` } +// DaprComponentMetadataValue is the value of a Dapr Metadata +type DaprComponentMetadataValue struct { + // Plain text value + Value string `json:"value,omitempty"` + // SecretKeyRef is a reference to a secret in a Dapr secret store + SecretKeyRef *DaprComponentSecretRef `json:"secretKeyRef,omitempty"` +} + +// DaprComponentSecretRef is a reference to a secret in a Dapr secret store +type DaprComponentSecretRef struct { + // Name of the secret in the secret store + Name string `json:"name,omitempty"` + // Key of the secret in the secret store + Key string `json:"key,omitempty"` +} + +// DaprComponentAuth represents the auth configuration for a Dapr component +type DaprComponentAuth struct { + // SecretStore is the name of the secret store to fetch secrets from + SecretStore string `json:"secretStore,omitempty"` +} + // Method EqualLinkedResource compares two BasicResourceProperties objects and returns true if their Application and // Environment fields are equal (i.e. resource belongs to the same env and app). func (b *BasicResourceProperties) EqualLinkedResource(prop *BasicResourceProperties) bool { diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/openapi.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/openapi.json index 62a8a9ebcb..9710bb828d 100644 --- a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/openapi.json +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/openapi.json @@ -983,7 +983,9 @@ "metadata": { "type": "object", "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec", - "properties": {} + "additionalProperties": { + "$ref": "#/definitions/MetadataValue" + } }, "type": { "type": "string", @@ -993,6 +995,10 @@ "type": "string", "description": "Dapr component version" }, + "auth": { + "$ref": "#/definitions/DaprResourceAuth", + "description": "The name of the Dapr component to be used as a secret store" + }, "resources": { "type": "array", "description": "A collection of references to resources associated with the pubSubBroker", @@ -1089,7 +1095,9 @@ "metadata": { "type": "object", "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec", - "properties": {} + "additionalProperties": { + "$ref": "#/definitions/MetadataValueUpdate" + } }, "type": { "type": "string", @@ -1099,6 +1107,10 @@ "type": "string", "description": "Dapr component version" }, + "auth": { + "$ref": "#/definitions/DaprResourceAuth", + "description": "The name of the Dapr component to be used as a secret store" + }, "resources": { "type": "array", "description": "A collection of references to resources associated with the pubSubBroker", @@ -1116,6 +1128,16 @@ } } }, + "DaprResourceAuth": { + "type": "object", + "description": "Authentication properties for a Dapr component object", + "properties": { + "secretStore": { + "type": "string", + "description": "Secret store to fetch secrets from" + } + } + }, "DaprSecretStoreProperties": { "type": "object", "description": "Dapr SecretStore portable resource properties", @@ -1146,7 +1168,9 @@ "metadata": { "type": "object", "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec", - "properties": {} + "additionalProperties": { + "$ref": "#/definitions/MetadataValue" + } }, "type": { "type": "string", @@ -1245,7 +1269,9 @@ "metadata": { "type": "object", "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec", - "properties": {} + "additionalProperties": { + "$ref": "#/definitions/MetadataValueUpdate" + } }, "type": { "type": "string", @@ -1295,7 +1321,9 @@ "metadata": { "type": "object", "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec", - "properties": {} + "additionalProperties": { + "$ref": "#/definitions/MetadataValue" + } }, "type": { "type": "string", @@ -1305,6 +1333,10 @@ "type": "string", "description": "Dapr component version" }, + "auth": { + "$ref": "#/definitions/DaprResourceAuth", + "description": "The name of the Dapr component to be used as a secret store" + }, "resources": { "type": "array", "description": "A collection of references to resources associated with the state store", @@ -1401,7 +1433,9 @@ "metadata": { "type": "object", "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec", - "properties": {} + "additionalProperties": { + "$ref": "#/definitions/MetadataValueUpdate" + } }, "type": { "type": "string", @@ -1411,6 +1445,10 @@ "type": "string", "description": "Dapr component version" }, + "auth": { + "$ref": "#/definitions/DaprResourceAuth", + "description": "The name of the Dapr component to be used as a secret store" + }, "resources": { "type": "array", "description": "A collection of references to resources associated with the state store", @@ -1514,6 +1552,92 @@ ], "x-ms-discriminator-value": "kubernetes" }, + "MetadataValue": { + "type": "object", + "description": "A single metadata for a Dapr component object", + "properties": { + "value": { + "type": "string", + "description": "The plain text value of the metadata" + }, + "secretKeyRef": { + "$ref": "#/definitions/MetadataValueFromSecret", + "description": "A reference of a value in a secret store component" + } + } + }, + "MetadataValueFromSecret": { + "type": "object", + "description": "A reference of a value in a secret store component.", + "properties": { + "name": { + "type": "string", + "description": "Secret name in the secret store component" + }, + "key": { + "type": "string", + "description": "The field to select in the secret value. If the secret value is a string, it should be equal to the secret name" + } + }, + "required": [ + "name", + "key" + ] + }, + "MetadataValueFromSecretUpdate": { + "type": "object", + "description": "A reference of a value in a secret store component.", + "properties": { + "name": { + "type": "string", + "description": "Secret name in the secret store component" + }, + "key": { + "type": "string", + "description": "The field to select in the secret value. If the secret value is a string, it should be equal to the secret name" + } + } + }, + "MetadataValueUpdate": { + "type": "object", + "description": "A single metadata for a Dapr component object", + "properties": { + "value": { + "type": "string", + "description": "The plain text value of the metadata" + }, + "secretKeyRef": { + "$ref": "#/definitions/MetadataValueFromSecretUpdate", + "description": "A reference of a value in a secret store component" + } + } + }, + "NonRedundantDaprResourceProperties": { + "type": "object", + "description": "The base properties of a Dapr component object.", + "properties": { + "componentName": { + "type": "string", + "description": "The name of the Dapr component object. Use this value in your code when interacting with the Dapr client to use the Dapr component.", + "readOnly": true + }, + "metadata": { + "type": "object", + "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec", + "additionalProperties": { + "$ref": "#/definitions/MetadataValue" + } + }, + "type": { + "type": "string", + "description": "Dapr component type which must matches the format used by Dapr Kubernetes configuration format" + }, + "version": { + "type": "string", + "description": "Dapr component version" + } + } + }, "OutputResource": { "type": "object", "description": "Properties of an output resource.", diff --git a/test/functional-portable/daprrp/noncloud/resources/dapr_pubsub_test.go b/test/functional-portable/daprrp/noncloud/resources/dapr_pubsub_test.go index ea49c3d0da..8341fb8cea 100644 --- a/test/functional-portable/daprrp/noncloud/resources/dapr_pubsub_test.go +++ b/test/functional-portable/daprrp/noncloud/resources/dapr_pubsub_test.go @@ -27,11 +27,82 @@ import ( "github.com/radius-project/radius/test/validation" ) +func Test_DaprPubSubBroker_Manual_Secret(t *testing.T) { + template := "testdata/daprrp-resources-pubsub-broker-manual-secret.bicep" + name := "dpsb-manual-secret" + appNamespace := fmt.Sprintf("default-%s", name) + redisPassword := "Password1234!" + secretName := "redisauth" + + test := rp.NewRPTest(t, name, []rp.TestStep{ + { + Executor: step.NewDeployExecutor( + template, + testutil.GetMagpieImage(), + fmt.Sprintf("namespace=%s", appNamespace), + fmt.Sprintf("baseName=%s", name), + fmt.Sprintf("redisPassword=%s", redisPassword), + fmt.Sprintf("secretName=%s", secretName), + ), + RPResources: &validation.RPResourceSet{ + Resources: []validation.RPResource{ + { + Name: name, + Type: validation.ApplicationsResource, + }, + { + Name: fmt.Sprintf("%s-ctnr", name), + Type: validation.ContainersResource, + App: name, + }, + { + Name: fmt.Sprintf("%s-dpsb", name), + Type: validation.DaprPubSubBrokersResource, + App: name, + }, + { + Name: fmt.Sprintf("%s-scs", name), + Type: validation.DaprSecretStoresResource, + App: name, + }, + }, + }, + K8sObjects: &validation.K8sObjectSet{ + Namespaces: map[string][]validation.K8sObject{ + appNamespace: { + validation.NewK8sPodForResource(name, fmt.Sprintf("%s-ctnr", name)), + + // Deployed as supporting resources using Kubernetes Bicep extensibility. + validation.NewK8sPodForResource(name, fmt.Sprintf("%s-redis", name)). + ValidateLabels(false), + validation.NewK8sServiceForResource(name, fmt.Sprintf("%s-redis", name)). + ValidateLabels(false), + + validation.NewDaprComponent(name, fmt.Sprintf("%s-dpsb", name)). + ValidateLabels(false), + validation.NewDaprComponent(name, fmt.Sprintf("%s-scs", name)). + ValidateLabels(false), + }, + }, + }, + }, + }, rp.K8sSecretResource(appNamespace, secretName, "", "password", redisPassword)) + + test.RequiredFeatures = []rp.RequiredFeature{rp.FeatureDapr} + + test.PostDeleteVerify = func(ctx context.Context, t *testing.T, test rp.RPTest) { + verifyDaprComponentsDeleted(ctx, t, test, "Applications.Dapr/pubSubBrokers", fmt.Sprintf("%s-dpsb", name), appNamespace) + verifyDaprComponentsDeleted(ctx, t, test, "Applications.Dapr/secretStores", fmt.Sprintf("%s-scs", name), appNamespace) + + } + + test.Test(t) +} + func Test_DaprPubSubBroker_Manual(t *testing.T) { template := "testdata/daprrp-resources-pubsub-broker-manual.bicep" name := "dpsb-manual-app" appNamespace := "default-dpsb-manual-app" - test := rp.NewRPTest(t, name, []rp.TestStep{ { Executor: step.NewDeployExecutor(template, testutil.GetMagpieImage(), fmt.Sprintf("namespace=%s", appNamespace)), diff --git a/test/functional-portable/daprrp/noncloud/resources/dapr_statestore_test.go b/test/functional-portable/daprrp/noncloud/resources/dapr_statestore_test.go index 448e298c9e..49f6ee6a87 100644 --- a/test/functional-portable/daprrp/noncloud/resources/dapr_statestore_test.go +++ b/test/functional-portable/daprrp/noncloud/resources/dapr_statestore_test.go @@ -81,6 +81,79 @@ func Test_DaprStateStore_Manual(t *testing.T) { test.Test(t) } +func Test_DaprStateStore_Manual_Secret(t *testing.T) { + template := "testdata/daprrp-resources-statestore-manual-secret.bicep" + + name := "dapr-sts-manual-secret" + appNamespace := fmt.Sprintf("default-%s", name) + redisPassword := "Password12345!" + secretName := "redisauth" + + test := rp.NewRPTest(t, name, []rp.TestStep{ + { + Executor: step.NewDeployExecutor( + template, + testutil.GetMagpieImage(), + fmt.Sprintf("namespace=%s", appNamespace), + fmt.Sprintf("baseName=%s", name), + fmt.Sprintf("redisPassword=%s", redisPassword), + fmt.Sprintf("secretName=%s", secretName), + ), + RPResources: &validation.RPResourceSet{ + Resources: []validation.RPResource{ + { + Name: name, + Type: validation.ApplicationsResource, + }, + { + Name: fmt.Sprintf("%s-ctnr", name), + Type: validation.ContainersResource, + App: name, + }, + { + Name: fmt.Sprintf("%s-sts", name), + Type: validation.DaprStateStoresResource, + App: name, + }, + { + Name: fmt.Sprintf("%s-scs", name), + Type: validation.DaprSecretStoresResource, + App: name, + }, + }, + }, + + K8sObjects: &validation.K8sObjectSet{ + Namespaces: map[string][]validation.K8sObject{ + appNamespace: { + validation.NewK8sPodForResource(name, fmt.Sprintf("%s-ctnr", name)), + + // Deployed as supporting resources using Kubernetes Bicep extensibility. + validation.NewK8sPodForResource(name, fmt.Sprintf("%s-redis", name)). + ValidateLabels(false), + validation.NewK8sServiceForResource(name, fmt.Sprintf("%s-redis", name)). + ValidateLabels(false), + + validation.NewDaprComponent(name, fmt.Sprintf("%s-sts", name)). + ValidateLabels(false), + validation.NewDaprComponent(name, fmt.Sprintf("%s-scs", name)). + ValidateLabels(false), + }, + }, + }, + }, + }, rp.K8sSecretResource(appNamespace, secretName, "", "password", redisPassword)) + + test.RequiredFeatures = []rp.RequiredFeature{rp.FeatureDapr} + + test.PostDeleteVerify = func(ctx context.Context, t *testing.T, test rp.RPTest) { + verifyDaprComponentsDeleted(ctx, t, test, "Applications.Dapr/stateStores", fmt.Sprintf("%s-sts", name), appNamespace) + verifyDaprComponentsDeleted(ctx, t, test, "Applications.Dapr/secretStores", fmt.Sprintf("%s-scs", name), appNamespace) + } + + test.Test(t) +} + func Test_DaprStateStore_Recipe(t *testing.T) { template := "testdata/daprrp-resources-statestore-recipe.bicep" name := "daprrp-rs-sts-recipe" diff --git a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-component-name-conflict.bicep b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-component-name-conflict.bicep index 9bdb5c7962..c4344f48c7 100644 --- a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-component-name-conflict.bicep +++ b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-component-name-conflict.bicep @@ -21,7 +21,9 @@ resource pubsub 'Applications.Dapr/pubSubBrokers@2023-10-01-preview' = { type: 'pubsub.azure.servicebus' version: 'v1' metadata: { - name: 'test' + name: { + value: 'test' + } } } } @@ -36,7 +38,9 @@ resource secretstore 'Applications.Dapr/secretStores@2023-10-01-preview' = { resourceProvisioning: 'manual' type: 'secretstores.kubernetes' metadata: { - vaultName: 'test' + vaultName: { + value: 'test' + } } version: 'v1' } diff --git a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-pubsub-broker-manual-secret.bicep b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-pubsub-broker-manual-secret.bicep new file mode 100644 index 0000000000..8f2d1dd811 --- /dev/null +++ b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-pubsub-broker-manual-secret.bicep @@ -0,0 +1,98 @@ +extension radius + +param magpieimage string +param environment string +param namespace string = 'default' +param baseName string = 'dpsb-manual-secret' +@secure() +param redisPassword string = '' +param secretName string = 'redisauth' +param location string = resourceGroup().location + +resource app 'Applications.Core/applications@2023-10-01-preview' = { + name: baseName + properties: { + environment: environment + } +} + +resource myapp 'Applications.Core/containers@2023-10-01-preview' = { + name: '${baseName}-ctnr' + properties: { + application: app.id + connections: { + daprpubsub: { + source: pubsubBroker.id + } + } + container: { + image: magpieimage + readinessProbe: { + kind: 'httpGet' + containerPort: 3000 + path: '/healthz' + } + } + extensions: [ + { + kind: 'daprSidecar' + appId: 'dpsb-manual-app-ctnr' + appPort: 3000 + } + ] + } +} + + +module redis '../../../../../../test/testrecipes/modules/redis-selfhost.bicep' = { + name: '${baseName}-redis-deployment' + params: { + name: '${baseName}-redis' + namespace: namespace + application: app.name + password: redisPassword + } +} + + +resource pubsubBroker 'Applications.Dapr/pubSubBrokers@2023-10-01-preview' = { + name: '${baseName}-dpsb' + properties: { + application: app.id + environment: environment + resourceProvisioning: 'manual' + type: 'pubsub.redis' + auth: { + secretStore: secretstore.name + } + metadata: { + redisHost: { + value: '${redis.outputs.host}:${redis.outputs.port}' + } + redisPassword: { + secretKeyRef: { + name: secretName + key: 'password' + } + } + } + version: 'v1' + } +} + +resource secretstore 'Applications.Dapr/secretStores@2023-10-01-preview' = { + name: '${baseName}-scs' + location: location + properties: { + environment: environment + application: app.id + resourceProvisioning: 'manual' + type: 'secretstores.kubernetes' + version: 'v1' + metadata: { + vaultName: { + value: 'test' + } + } + } +} diff --git a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-pubsub-broker-manual.bicep b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-pubsub-broker-manual.bicep index c96ce0991f..02aabd8600 100644 --- a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-pubsub-broker-manual.bicep +++ b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-pubsub-broker-manual.bicep @@ -57,8 +57,12 @@ resource pubsubBroker 'Applications.Dapr/pubSubBrokers@2023-10-01-preview' = { resourceProvisioning: 'manual' type: 'pubsub.redis' metadata: { - redisHost: '${redis.outputs.host}:${redis.outputs.port}' - redisPassword: '' + redisHost: { + value: '${redis.outputs.host}:${redis.outputs.port}' + } + redisPassword: { + value: '' + } } version: 'v1' } diff --git a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-secretstore-manual.bicep b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-secretstore-manual.bicep index 3792f745ea..e07841e05d 100644 --- a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-secretstore-manual.bicep +++ b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-secretstore-manual.bicep @@ -50,7 +50,9 @@ resource secretstore 'Applications.Dapr/secretStores@2023-10-01-preview' = { resourceProvisioning: 'manual' type: 'secretstores.kubernetes' metadata: { - vaultName: 'test' + vaultName: { + value: 'test' + } } version: 'v1' } diff --git a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-statestore-manual-secret.bicep b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-statestore-manual-secret.bicep new file mode 100644 index 0000000000..ff236f7606 --- /dev/null +++ b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-statestore-manual-secret.bicep @@ -0,0 +1,98 @@ +extension radius + +param magpieimage string +param environment string +param namespace string = 'default' +param baseName string = 'dapr-sts-manual-secret' +@secure() +param redisPassword string = '' +param secretName string = 'redisauth' +param location string = resourceGroup().location + +resource app 'Applications.Core/applications@2023-10-01-preview' = { + name: baseName + properties: { + environment: environment + } +} + +resource myapp 'Applications.Core/containers@2023-10-01-preview' = { + name: '${baseName}-ctnr' + properties: { + application: app.id + connections: { + daprstatestore: { + source: statestore.id + } + } + container: { + image: magpieimage + readinessProbe: { + kind: 'httpGet' + containerPort: 3000 + path: '/healthz' + } + } + extensions: [ + { + kind: 'daprSidecar' + appId: 'gnrc-sts-ctnr' + appPort: 3000 + } + ] + } +} + + +module redis '../../../../../../test/testrecipes/modules/redis-selfhost.bicep' = { + name: '${baseName}-redis-deployment' + params: { + name: '${baseName}-redis' + namespace: namespace + application: app.name + password: redisPassword + } +} + + +resource statestore 'Applications.Dapr/stateStores@2023-10-01-preview' = { + name: '${baseName}-sts' + properties: { + application: app.id + environment: environment + resourceProvisioning: 'manual' + type: 'state.redis' + auth: { + secretStore: secretstore.name + } + metadata: { + redisHost: { + value: '${redis.outputs.host}:${redis.outputs.port}' + } + redisPassword: { + secretKeyRef: { + name: secretName + key: 'password' + } + } + } + version: 'v1' + } +} + +resource secretstore 'Applications.Dapr/secretStores@2023-10-01-preview' = { + name: '${baseName}-scs' + location: location + properties: { + environment: environment + application: app.id + resourceProvisioning: 'manual' + type: 'secretstores.kubernetes' + version: 'v1' + metadata: { + vaultName: { + value: 'test' + } + } + } +} diff --git a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-statestore-manual.bicep b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-statestore-manual.bicep index c687cf3ee9..3f6f64ce22 100644 --- a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-statestore-manual.bicep +++ b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-statestore-manual.bicep @@ -57,8 +57,12 @@ resource statestore 'Applications.Dapr/stateStores@2023-10-01-preview' = { resourceProvisioning: 'manual' type: 'state.redis' metadata: { - redisHost: '${redis.outputs.host}:${redis.outputs.port}' - redisPassword: '' + redisHost: { + value: '${redis.outputs.host}:${redis.outputs.port}' + } + redisPassword: { + value: '' + } } version: 'v1' } diff --git a/test/testrecipes/modules/redis-selfhost.bicep b/test/testrecipes/modules/redis-selfhost.bicep index 80b9b88fff..e6ff269ded 100644 --- a/test/testrecipes/modules/redis-selfhost.bicep +++ b/test/testrecipes/modules/redis-selfhost.bicep @@ -6,6 +6,8 @@ extension kubernetes with { param namespace string param name string param application string = '' +@secure() +param password string = '' resource redis 'apps/Deployment@v1' = { metadata: { @@ -34,6 +36,12 @@ resource redis 'apps/Deployment@v1' = { // This container is the running redis instance. name: 'redis' image: 'ghcr.io/radius-project/mirror/redis:6.2' + // Note :Using --requirepass with an empty password is + // equivalent to setting no password + args: [ + '--requirepass' + password + ] ports: [ { containerPort: 6379 @@ -44,12 +52,11 @@ resource redis 'apps/Deployment@v1' = { // This container will connect to redis and stream logs to stdout for aid in development. name: 'redis-monitor' image: 'ghcr.io/radius-project/mirror/redis:6.2' - args: [ - 'redis-cli' - '-h' - 'localhost' - 'MONITOR' - ] + args: concat( + ['redis-cli'], + password != '' ? ['-a', password] : [], + ['-h', 'localhost', 'MONITOR'] + ) } ] } diff --git a/typespec/Applications.Dapr/common.tsp b/typespec/Applications.Dapr/common.tsp index c1543172d1..a473f97944 100644 --- a/typespec/Applications.Dapr/common.tsp +++ b/typespec/Applications.Dapr/common.tsp @@ -25,7 +25,7 @@ model DaprResourceProperties { componentName?: string; @doc("The metadata for Dapr resource which must match the values specified in Dapr component spec") - metadata?: {}; + metadata?: Record; #suppress "@azure-tools/typespec-azure-resource-manager/arm-resource-duplicate-property" @doc("Dapr component type which must matches the format used by Dapr Kubernetes configuration format") @@ -33,4 +33,31 @@ model DaprResourceProperties { @doc("Dapr component version") version?: string; + + @doc("The name of the Dapr component to be used as a secret store") + auth?: DaprResourceAuth; +} + +@doc("Authentication properties for a Dapr component object") +model DaprResourceAuth { + @doc("Secret store to fetch secrets from") + secretStore?: string; +} + +@doc("A single metadata for a Dapr component object") +model MetadataValue { + @doc("The plain text value of the metadata") + value?: string; + + @doc("A reference of a value in a secret store component") + secretKeyRef?: MetadataValueFromSecret; +} + +@doc("A reference of a value in a secret store component.") +model MetadataValueFromSecret { + @doc("Secret name in the secret store component") + name: string; + + @doc("The field to select in the secret value. If the secret value is a string, it should be equal to the secret name") + key: string; } diff --git a/typespec/Applications.Dapr/secretStores.tsp b/typespec/Applications.Dapr/secretStores.tsp index 7bbd893bda..b446143538 100644 --- a/typespec/Applications.Dapr/secretStores.tsp +++ b/typespec/Applications.Dapr/secretStores.tsp @@ -45,10 +45,15 @@ model DaprSecretStoreResource name: ResourceNameString; } +// This is to prevent a secret store from referencing another secret store +// This would be valid according to Dapr component specs, but may confuse users +@withoutOmittedProperties("auth") +model NonRedundantDaprResourceProperties is DaprResourceProperties; + @doc("Dapr SecretStore portable resource properties") model DaprSecretStoreProperties { ...EnvironmentScopedResource; - ...DaprResourceProperties; + ...NonRedundantDaprResourceProperties; ...RecipeBaseProperties; }