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 1f75fe7024..334868768c 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 @@ -4,7 +4,7 @@ }, { "$type": "StringLiteralType", - "value": "Applications.Dapr/configurationStores" + "value": "Applications.Dapr/bindings" }, { "$type": "StringLiteralType", @@ -12,7 +12,7 @@ }, { "$type": "ObjectType", - "name": "Applications.Dapr/configurationStores", + "name": "Applications.Dapr/bindings", "properties": { "id": { "type": { @@ -47,7 +47,7 @@ "$ref": "#/4" }, "flags": 1, - "description": "Dapr configuration store portable resource properties" + "description": "Dapr binding portable resource properties" }, "tags": { "type": { @@ -74,7 +74,7 @@ }, { "$type": "ObjectType", - "name": "DaprConfigurationStoreProperties", + "name": "DaprBindingProperties", "properties": { "environment": { "type": { @@ -144,7 +144,7 @@ "$ref": "#/31" }, "flags": 0, - "description": "A collection of references to resources associated with the configuration store" + "description": "A collection of references to resources associated with the binding" }, "recipe": { "type": { @@ -451,7 +451,7 @@ }, { "$type": "ObjectType", - "name": "DaprConfigurationStorePropertiesMetadata", + "name": "DaprBindingPropertiesMetadata", "properties": {}, "additionalProperties": { "$ref": "#/26" @@ -655,7 +655,7 @@ }, { "$type": "ResourceType", - "name": "Applications.Dapr/configurationStores@2023-10-01-preview", + "name": "Applications.Dapr/bindings@2023-10-01-preview", "scopeType": 0, "body": { "$ref": "#/3" @@ -665,7 +665,7 @@ }, { "$type": "StringLiteralType", - "value": "Applications.Dapr/pubSubBrokers" + "value": "Applications.Dapr/configurationStores" }, { "$type": "StringLiteralType", @@ -673,7 +673,7 @@ }, { "$type": "ObjectType", - "name": "Applications.Dapr/pubSubBrokers", + "name": "Applications.Dapr/configurationStores", "properties": { "id": { "type": { @@ -708,7 +708,7 @@ "$ref": "#/53" }, "flags": 1, - "description": "Dapr PubSubBroker portable resource properties" + "description": "Dapr configuration store portable resource properties" }, "tags": { "type": { @@ -735,7 +735,7 @@ }, { "$type": "ObjectType", - "name": "DaprPubSubBrokerProperties", + "name": "DaprConfigurationStoreProperties", "properties": { "environment": { "type": { @@ -805,7 +805,7 @@ "$ref": "#/64" }, "flags": 0, - "description": "A collection of references to resources associated with the pubSubBroker" + "description": "A collection of references to resources associated with the configuration store" }, "recipe": { "type": { @@ -886,7 +886,7 @@ }, { "$type": "ObjectType", - "name": "DaprPubSubBrokerPropertiesMetadata", + "name": "DaprConfigurationStorePropertiesMetadata", "properties": {}, "additionalProperties": { "$ref": "#/26" @@ -927,7 +927,7 @@ }, { "$type": "ResourceType", - "name": "Applications.Dapr/pubSubBrokers@2023-10-01-preview", + "name": "Applications.Dapr/configurationStores@2023-10-01-preview", "scopeType": 0, "body": { "$ref": "#/52" @@ -937,7 +937,7 @@ }, { "$type": "StringLiteralType", - "value": "Applications.Dapr/secretStores" + "value": "Applications.Dapr/pubSubBrokers" }, { "$type": "StringLiteralType", @@ -945,7 +945,7 @@ }, { "$type": "ObjectType", - "name": "Applications.Dapr/secretStores", + "name": "Applications.Dapr/pubSubBrokers", "properties": { "id": { "type": { @@ -980,11 +980,11 @@ "$ref": "#/73" }, "flags": 1, - "description": "Dapr SecretStore portable resource properties" + "description": "Dapr PubSubBroker portable resource properties" }, "tags": { "type": { - "$ref": "#/87" + "$ref": "#/88" }, "flags": 0, "description": "Resource tags." @@ -1007,7 +1007,7 @@ }, { "$type": "ObjectType", - "name": "DaprSecretStoreProperties", + "name": "DaprPubSubBrokerProperties", "properties": { "environment": { "type": { @@ -1065,6 +1065,20 @@ "flags": 0, "description": "Dapr component version" }, + "auth": { + "type": { + "$ref": "#/29" + }, + "flags": 0, + "description": "Authentication properties for a Dapr component object" + }, + "resources": { + "type": { + "$ref": "#/84" + }, + "flags": 0, + "description": "A collection of references to resources associated with the pubSubBroker" + }, "recipe": { "type": { "$ref": "#/32" @@ -1074,7 +1088,7 @@ }, "resourceProvisioning": { "type": { - "$ref": "#/86" + "$ref": "#/87" }, "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." @@ -1142,6 +1156,264 @@ } ] }, + { + "$type": "ObjectType", + "name": "DaprPubSubBrokerPropertiesMetadata", + "properties": {}, + "additionalProperties": { + "$ref": "#/26" + } + }, + { + "$type": "ArrayType", + "itemType": { + "$ref": "#/30" + } + }, + { + "$type": "StringLiteralType", + "value": "recipe" + }, + { + "$type": "StringLiteralType", + "value": "manual" + }, + { + "$type": "UnionType", + "elements": [ + { + "$ref": "#/85" + }, + { + "$ref": "#/86" + } + ] + }, + { + "$type": "ObjectType", + "name": "TrackedResourceTags", + "properties": {}, + "additionalProperties": { + "$ref": "#/0" + } + }, + { + "$type": "ResourceType", + "name": "Applications.Dapr/pubSubBrokers@2023-10-01-preview", + "scopeType": 0, + "body": { + "$ref": "#/72" + }, + "flags": 0, + "functions": {} + }, + { + "$type": "StringLiteralType", + "value": "Applications.Dapr/secretStores" + }, + { + "$type": "StringLiteralType", + "value": "2023-10-01-preview" + }, + { + "$type": "ObjectType", + "name": "Applications.Dapr/secretStores", + "properties": { + "id": { + "type": { + "$ref": "#/0" + }, + "flags": 10, + "description": "The resource id" + }, + "name": { + "type": { + "$ref": "#/0" + }, + "flags": 25, + "description": "The resource name" + }, + "type": { + "type": { + "$ref": "#/90" + }, + "flags": 10, + "description": "The resource type" + }, + "apiVersion": { + "type": { + "$ref": "#/91" + }, + "flags": 10, + "description": "The resource api version" + }, + "properties": { + "type": { + "$ref": "#/93" + }, + "flags": 1, + "description": "Dapr SecretStore portable resource properties" + }, + "tags": { + "type": { + "$ref": "#/107" + }, + "flags": 0, + "description": "Resource tags." + }, + "location": { + "type": { + "$ref": "#/0" + }, + "flags": 0, + "description": "The geo-location where the resource lives" + }, + "systemData": { + "type": { + "$ref": "#/38" + }, + "flags": 2, + "description": "Metadata pertaining to creation and last modification of the resource." + } + } + }, + { + "$type": "ObjectType", + "name": "DaprSecretStoreProperties", + "properties": { + "environment": { + "type": { + "$ref": "#/0" + }, + "flags": 1, + "description": "Fully qualified resource ID for the environment that the portable resource is linked to" + }, + "application": { + "type": { + "$ref": "#/0" + }, + "flags": 0, + "description": "Fully qualified resource ID for the application that the portable resource is consumed by (if applicable)" + }, + "provisioningState": { + "type": { + "$ref": "#/102" + }, + "flags": 2, + "description": "Provisioning state of the resource at the time the operation was called" + }, + "status": { + "type": { + "$ref": "#/14" + }, + "flags": 2, + "description": "Status of a resource." + }, + "componentName": { + "type": { + "$ref": "#/0" + }, + "flags": 2, + "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." + }, + "metadata": { + "type": { + "$ref": "#/103" + }, + "flags": 0, + "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec" + }, + "type": { + "type": { + "$ref": "#/0" + }, + "flags": 0, + "description": "Dapr component type which must matches the format used by Dapr Kubernetes configuration format" + }, + "version": { + "type": { + "$ref": "#/0" + }, + "flags": 0, + "description": "Dapr component version" + }, + "recipe": { + "type": { + "$ref": "#/32" + }, + "flags": 0, + "description": "The recipe used to automatically deploy underlying infrastructure for a portable resource" + }, + "resourceProvisioning": { + "type": { + "$ref": "#/106" + }, + "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." + } + } + }, + { + "$type": "StringLiteralType", + "value": "Creating" + }, + { + "$type": "StringLiteralType", + "value": "Updating" + }, + { + "$type": "StringLiteralType", + "value": "Deleting" + }, + { + "$type": "StringLiteralType", + "value": "Accepted" + }, + { + "$type": "StringLiteralType", + "value": "Provisioning" + }, + { + "$type": "StringLiteralType", + "value": "Succeeded" + }, + { + "$type": "StringLiteralType", + "value": "Failed" + }, + { + "$type": "StringLiteralType", + "value": "Canceled" + }, + { + "$type": "UnionType", + "elements": [ + { + "$ref": "#/94" + }, + { + "$ref": "#/95" + }, + { + "$ref": "#/96" + }, + { + "$ref": "#/97" + }, + { + "$ref": "#/98" + }, + { + "$ref": "#/99" + }, + { + "$ref": "#/100" + }, + { + "$ref": "#/101" + } + ] + }, { "$type": "ObjectType", "name": "DaprSecretStorePropertiesMetadata", @@ -1162,10 +1434,10 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/84" + "$ref": "#/104" }, { - "$ref": "#/85" + "$ref": "#/105" } ] }, @@ -1182,7 +1454,7 @@ "name": "Applications.Dapr/secretStores@2023-10-01-preview", "scopeType": 0, "body": { - "$ref": "#/72" + "$ref": "#/92" }, "flags": 0, "functions": {} @@ -1215,28 +1487,28 @@ }, "type": { "type": { - "$ref": "#/89" + "$ref": "#/109" }, "flags": 10, "description": "The resource type" }, "apiVersion": { "type": { - "$ref": "#/90" + "$ref": "#/110" }, "flags": 10, "description": "The resource api version" }, "properties": { "type": { - "$ref": "#/92" + "$ref": "#/112" }, "flags": 1, "description": "Dapr StateStore portable resource properties" }, "tags": { "type": { - "$ref": "#/107" + "$ref": "#/127" }, "flags": 0, "description": "Resource tags." @@ -1277,7 +1549,7 @@ }, "provisioningState": { "type": { - "$ref": "#/101" + "$ref": "#/121" }, "flags": 2, "description": "Provisioning state of the resource at the time the operation was called" @@ -1298,7 +1570,7 @@ }, "metadata": { "type": { - "$ref": "#/102" + "$ref": "#/122" }, "flags": 0, "description": "The metadata for Dapr resource which must match the values specified in Dapr component spec" @@ -1326,7 +1598,7 @@ }, "resources": { "type": { - "$ref": "#/103" + "$ref": "#/123" }, "flags": 0, "description": "A collection of references to resources associated with the state store" @@ -1340,7 +1612,7 @@ }, "resourceProvisioning": { "type": { - "$ref": "#/106" + "$ref": "#/126" }, "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." @@ -1383,28 +1655,28 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/93" + "$ref": "#/113" }, { - "$ref": "#/94" + "$ref": "#/114" }, { - "$ref": "#/95" + "$ref": "#/115" }, { - "$ref": "#/96" + "$ref": "#/116" }, { - "$ref": "#/97" + "$ref": "#/117" }, { - "$ref": "#/98" + "$ref": "#/118" }, { - "$ref": "#/99" + "$ref": "#/119" }, { - "$ref": "#/100" + "$ref": "#/120" } ] }, @@ -1434,10 +1706,10 @@ "$type": "UnionType", "elements": [ { - "$ref": "#/104" + "$ref": "#/124" }, { - "$ref": "#/105" + "$ref": "#/125" } ] }, @@ -1454,7 +1726,7 @@ "name": "Applications.Dapr/stateStores@2023-10-01-preview", "scopeType": 0, "body": { - "$ref": "#/91" + "$ref": "#/111" }, "flags": 0, "functions": {} diff --git a/hack/bicep-types-radius/generated/index.json b/hack/bicep-types-radius/generated/index.json index 68a264cd11..04293e6143 100644 --- a/hack/bicep-types-radius/generated/index.json +++ b/hack/bicep-types-radius/generated/index.json @@ -21,17 +21,20 @@ "Applications.Core/volumes@2023-10-01-preview": { "$ref": "applications/applications.core/2023-10-01-preview/types.json#/284" }, - "Applications.Dapr/configurationStores@2023-10-01-preview": { + "Applications.Dapr/bindings@2023-10-01-preview": { "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/49" }, - "Applications.Dapr/pubSubBrokers@2023-10-01-preview": { + "Applications.Dapr/configurationStores@2023-10-01-preview": { "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/69" }, + "Applications.Dapr/pubSubBrokers@2023-10-01-preview": { + "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/89" + }, "Applications.Dapr/secretStores@2023-10-01-preview": { - "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/88" + "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/108" }, "Applications.Dapr/stateStores@2023-10-01-preview": { - "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/108" + "$ref": "applications/applications.dapr/2023-10-01-preview/types.json#/128" }, "Applications.Datastores/mongoDatabases@2023-10-01-preview": { "$ref": "applications/applications.datastores/2023-10-01-preview/types.json#/49" diff --git a/pkg/cli/clients/management.go b/pkg/cli/clients/management.go index 0e1023fe7e..377cd22e3b 100644 --- a/pkg/cli/clients/management.go +++ b/pkg/cli/clients/management.go @@ -67,6 +67,7 @@ var ( dapr_ctrl.DaprSecretStoresResourceType, dapr_ctrl.DaprPubSubBrokersResourceType, dapr_ctrl.DaprConfigurationStoresResourceType, + dapr_ctrl.DaprBindingsResourceType, ext_ctrl.ResourceTypeName, gtwy_ctrl.ResourceTypeName, cntr_ctrl.ResourceTypeName, diff --git a/pkg/corerp/backend/deployment/deploymentprocessor.go b/pkg/corerp/backend/deployment/deploymentprocessor.go index 80392e48bc..92ba13318f 100644 --- a/pkg/corerp/backend/deployment/deploymentprocessor.go +++ b/pkg/corerp/backend/deployment/deploymentprocessor.go @@ -568,6 +568,12 @@ func (dp *deploymentProcessor) getResourceDataByID(ctx context.Context, resource return ResourceData{}, fmt.Errorf(errMsg, resourceID.String(), err) } return dp.buildResourceDependency(resourceID, obj.Properties.Application, obj, obj.Properties.Status.OutputResources, obj.ComputedValues, obj.SecretValues, portableresources.RecipeData{}) + case strings.ToLower(dapr_ctrl.DaprBindingsResourceType): + obj := &dapr_dm.DaprBinding{} + if err = resource.As(obj); err != nil { + return ResourceData{}, fmt.Errorf(errMsg, resourceID.String(), err) + } + return dp.buildResourceDependency(resourceID, obj.Properties.Application, obj, obj.Properties.Status.OutputResources, obj.ComputedValues, obj.SecretValues, portableresources.RecipeData{}) default: return ResourceData{}, fmt.Errorf("unsupported resource type: %q for resource ID: %q", resourceType, resourceID.String()) } diff --git a/pkg/daprrp/api/v20231001preview/binding_conversion.go b/pkg/daprrp/api/v20231001preview/binding_conversion.go new file mode 100644 index 0000000000..1f128cf921 --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/binding_conversion.go @@ -0,0 +1,149 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v20231001preview + +import ( + "fmt" + "reflect" + "strings" + + v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" + "github.com/radius-project/radius/pkg/daprrp/datamodel" + "github.com/radius-project/radius/pkg/portableresources" + rpv1 "github.com/radius-project/radius/pkg/rp/v1" + "github.com/radius-project/radius/pkg/to" +) + +// ConvertTo converts a versioned DaprBindingResource to a version-agnostic DaprBinding. It returns an error +// if the mode is not specified or if the required properties for the mode are not specified. +func (src *DaprBindingResource) ConvertTo() (v1.DataModelInterface, error) { + daprBindingProperties := datamodel.DaprBindingProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Environment: to.String(src.Properties.Environment), + Application: to.String(src.Properties.Application), + }, + } + + trackedResource := v1.TrackedResource{ + ID: to.String(src.ID), + Name: to.String(src.Name), + Type: to.String(src.Type), + Location: to.String(src.Location), + Tags: to.StringMap(src.Tags), + } + + internalMetadata := v1.InternalMetadata{ + UpdatedAPIVersion: Version, + AsyncProvisioningState: toProvisioningStateDataModel(src.Properties.ProvisioningState), + } + + converted := &datamodel.DaprBinding{} + converted.TrackedResource = trackedResource + converted.InternalMetadata = internalMetadata + converted.Properties = daprBindingProperties + + var err error + converted.Properties.ResourceProvisioning, err = toResourceProvisiongDataModel(src.Properties.ResourceProvisioning) + if err != nil { + return nil, err + } + + 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 + // when resourceProvisioning is set to manual. + msgs := []string{} + if converted.Properties.ResourceProvisioning == portableresources.ResourceProvisioningManual { + if src.Properties.Recipe != nil && (!reflect.ValueOf(*src.Properties.Recipe).IsZero()) { + msgs = append(msgs, "recipe details cannot be specified when resourceProvisioning is set to manual") + } + if len(src.Properties.Metadata) == 0 { + msgs = append(msgs, "metadata must be specified when resourceProvisioning is set to manual") + } + if src.Properties.Type == nil || *src.Properties.Type == "" { + msgs = append(msgs, "type must be specified when resourceProvisioning is set to manual") + } + if src.Properties.Version == nil || *src.Properties.Version == "" { + msgs = append(msgs, "version must be specified when resourceProvisioning is set to manual") + } + converted.Properties.Metadata = toMetadataDataModel(src.Properties.Metadata) + converted.Properties.Type = to.String(src.Properties.Type) + converted.Properties.Version = to.String(src.Properties.Version) + } else { + if src.Properties.Metadata != nil && (!reflect.ValueOf(src.Properties.Metadata).IsZero()) { + msgs = append(msgs, "metadata cannot be specified when resourceProvisioning is set to recipe (default)") + } + if src.Properties.Type != nil && (!reflect.ValueOf(*src.Properties.Type).IsZero()) { + msgs = append(msgs, "type cannot be specified when resourceProvisioning is set to recipe (default)") + } + if src.Properties.Version != nil && (!reflect.ValueOf(*src.Properties.Version).IsZero()) { + msgs = append(msgs, "version cannot be specified when resourceProvisioning is set to recipe (default)") + } + + converted.Properties.Recipe = toRecipeDataModel(src.Properties.Recipe) + } + if len(msgs) > 0 { + return nil, &v1.ErrClientRP{ + Code: v1.CodeInvalid, + Message: fmt.Sprintf("error(s) found:\n\t%v", strings.Join(msgs, "\n\t")), + } + } + + return converted, nil +} + +// ConvertFrom converts from version-agnostic datamodel to the versioned DaprBinding resource. +// If the DataModelInterface is not of the correct type, an error is returned. +func (dst *DaprBindingResource) ConvertFrom(src v1.DataModelInterface) error { + daprBinding, ok := src.(*datamodel.DaprBinding) + if !ok { + return v1.ErrInvalidModelConversion + } + + dst.ID = to.Ptr(daprBinding.ID) + dst.Name = to.Ptr(daprBinding.Name) + dst.Type = to.Ptr(daprBinding.Type) + dst.SystemData = fromSystemDataModel(daprBinding.SystemData) + dst.Location = to.Ptr(daprBinding.Location) + dst.Tags = *to.StringMapPtr(daprBinding.Tags) + + dst.Properties = &DaprBindingProperties{ + Environment: to.Ptr(daprBinding.Properties.Environment), + Application: to.Ptr(daprBinding.Properties.Application), + ResourceProvisioning: fromResourceProvisioningDataModel(daprBinding.Properties.ResourceProvisioning), + Resources: fromResourcesDataModel(daprBinding.Properties.Resources), + ComponentName: to.Ptr(daprBinding.Properties.ComponentName), + ProvisioningState: fromProvisioningStateDataModel(daprBinding.InternalMetadata.AsyncProvisioningState), + Status: &ResourceStatus{ + OutputResources: toOutputResources(daprBinding.Properties.Status.OutputResources), + Recipe: fromRecipeStatus(daprBinding.Properties.Status.Recipe), + }, + Auth: fromAuthDataModel(daprBinding.Properties.Auth), + } + + if daprBinding.Properties.ResourceProvisioning == portableresources.ResourceProvisioningManual { + dst.Properties.Metadata = fromMetadataDataModel(daprBinding.Properties.Metadata) + dst.Properties.Type = to.Ptr(daprBinding.Properties.Type) + dst.Properties.Version = to.Ptr(daprBinding.Properties.Version) + } else { + dst.Properties.Recipe = fromRecipeDataModel(daprBinding.Properties.Recipe) + } + + return nil +} diff --git a/pkg/daprrp/api/v20231001preview/binding_conversion_test.go b/pkg/daprrp/api/v20231001preview/binding_conversion_test.go new file mode 100644 index 0000000000..b08cecec8b --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/binding_conversion_test.go @@ -0,0 +1,271 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v20231001preview + +import ( + "encoding/json" + "testing" + + v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" + "github.com/radius-project/radius/pkg/daprrp/datamodel" + "github.com/radius-project/radius/pkg/daprrp/frontend/controller" + "github.com/radius-project/radius/pkg/portableresources" + rpv1 "github.com/radius-project/radius/pkg/rp/v1" + "github.com/radius-project/radius/pkg/to" + "github.com/radius-project/radius/test/testutil" + "github.com/radius-project/radius/test/testutil/resourcetypeutil" + "github.com/stretchr/testify/require" +) + +func TestDaprBinding_ConvertVersionedToDataModel(t *testing.T) { + testCases := []struct { + desc string + file string + expected *datamodel.DaprBinding + }{ + { + desc: "Manual provisioning of a DaprBinding", + file: "binding_manual_resource.json", + expected: &datamodel.DaprBinding{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd", + Name: "test-dbd", + Type: controller.DaprBindingsResourceType, + Location: v1.LocationGlobal, + Tags: map[string]string{ + "env": "dev", + }, + }, + InternalMetadata: v1.InternalMetadata{ + CreatedAPIVersion: "", + UpdatedAPIVersion: "2023-10-01-preview", + AsyncProvisioningState: v1.ProvisioningStateAccepted, + }, + SystemData: v1.SystemData{}, + }, + Properties: datamodel.DaprBindingProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Application: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app", + Environment: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", + }, + ResourceProvisioning: portableresources.ResourceProvisioningManual, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "foo": { + Value: "bar", + }, + }, + Resources: []*portableresources.ResourceReference{ + { + ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn", + }, + }, + Type: "bindings.azure.blobstorage", + Version: "v1", + }, + }, + }, + { + desc: "Provisioning by a Recipe of a binding", + file: "binding_recipe_resource.json", + expected: &datamodel.DaprBinding{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + ID: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd", + Name: "test-dbd", + Type: controller.DaprBindingsResourceType, + Location: v1.LocationGlobal, + Tags: map[string]string{ + "env": "dev", + }, + }, + InternalMetadata: v1.InternalMetadata{ + CreatedAPIVersion: "", + UpdatedAPIVersion: "2023-10-01-preview", + AsyncProvisioningState: v1.ProvisioningStateAccepted, + }, + SystemData: v1.SystemData{}, + }, + Properties: datamodel.DaprBindingProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Application: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app", + Environment: "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", + }, + ResourceProvisioning: portableresources.ResourceProvisioningRecipe, + Recipe: portableresources.ResourceRecipe{ + Name: "dbd-recipe", + }, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + // arrange + rawPayload := testutil.ReadFixture(tc.file) + versionedResource := &DaprBindingResource{} + err := json.Unmarshal(rawPayload, versionedResource) + require.NoError(t, err) + + // act + dm, err := versionedResource.ConvertTo() + + // assert + require.NoError(t, err) + convertedResource := dm.(*datamodel.DaprBinding) + + require.Equal(t, tc.expected, convertedResource) + }) + } +} + +func TestDaprBinding_ConvertVersionedToDataModel_Invalid(t *testing.T) { + testset := []struct { + payload string + errType error + message string + }{ + { + "binding_invalidmanual_resource.json", + &v1.ErrClientRP{}, + "code BadRequest: err error(s) found:\n\trecipe details cannot be specified when resourceProvisioning is set to manual\n\tmetadata must be specified when resourceProvisioning is set to manual\n\ttype must be specified when resourceProvisioning is set to manual\n\tversion must be specified when resourceProvisioning is set to manual", + }, + { + "binding_invalidrecipe_resource.json", + &v1.ErrClientRP{}, + "code BadRequest: err error(s) found:\n\tmetadata cannot be specified when resourceProvisioning is set to recipe (default)\n\ttype cannot be specified when resourceProvisioning is set to recipe (default)\n\tversion cannot be specified when resourceProvisioning is set to recipe (default)", + }, + } + + for _, test := range testset { + t.Run(test.payload, func(t *testing.T) { + rawPayload := testutil.ReadFixture(test.payload) + versionedResource := &DaprBindingResource{} + err := json.Unmarshal(rawPayload, versionedResource) + require.NoError(t, err) + + dm, err := versionedResource.ConvertTo() + require.Error(t, err) + require.Nil(t, dm) + require.IsType(t, test.errType, err) + require.Equal(t, test.message, err.Error()) + }) + } +} + +func TestDaprBinding_ConvertDataModelToVersioned(t *testing.T) { + testCases := []struct { + desc string + file string + expected *DaprBindingResource + }{ + { + desc: "Convert manually provisioned DaprBinding datamodel to versioned resource", + file: "binding_manual_datamodel.json", + expected: &DaprBindingResource{ + Location: to.Ptr(v1.LocationGlobal), + Properties: &DaprBindingProperties{ + 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]*MetadataValue{ + "foo": { + Value: to.Ptr("bar"), + }, + }, + ResourceProvisioning: to.Ptr(ResourceProvisioningManual), + Resources: []*ResourceReference{ + { + ID: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn"), + }, + }, + Type: to.Ptr("bindings.azure.blobstorage"), + Version: to.Ptr("v1"), + ComponentName: to.Ptr("test-dbd"), + ProvisioningState: to.Ptr(ProvisioningStateAccepted), + Status: resourcetypeutil.MustPopulateResourceStatus(&ResourceStatus{}), + Auth: &DaprResourceAuth{SecretStore: to.Ptr("test-secret-store")}, + }, + Tags: map[string]*string{ + "env": to.Ptr("dev"), + }, + ID: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd"), + Name: to.Ptr("test-dbd"), + Type: to.Ptr(controller.DaprBindingsResourceType), + }, + }, + { + desc: "Convert DaprBinding datamodel provisioned by a recipe to versioned resource", + file: "binding_recipe_datamodel.json", + expected: &DaprBindingResource{ + Location: to.Ptr(v1.LocationGlobal), + Properties: &DaprBindingProperties{ + 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"), + Recipe: &Recipe{ + Name: to.Ptr("dbd-recipe"), + }, + ResourceProvisioning: to.Ptr(ResourceProvisioningRecipe), + ComponentName: to.Ptr("test-dbd"), + ProvisioningState: to.Ptr(ProvisioningStateAccepted), + Status: resourcetypeutil.MustPopulateResourceStatusWithRecipe(&ResourceStatus{}), + Auth: nil, + }, + Tags: map[string]*string{ + "env": to.Ptr("dev"), + }, + ID: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd"), + Name: to.Ptr("test-dbd"), + Type: to.Ptr(controller.DaprBindingsResourceType), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + rawPayload := testutil.ReadFixture(tc.file) + resource := &datamodel.DaprBinding{} + err := json.Unmarshal(rawPayload, resource) + require.NoError(t, err) + + versionedResource := &DaprBindingResource{} + err = versionedResource.ConvertFrom(resource) + require.NoError(t, err) + + // Skip system data comparison + versionedResource.SystemData = nil + + require.Equal(t, tc.expected, versionedResource) + }) + } +} + +func TestDaprBinding_ConvertFromValidation(t *testing.T) { + validationTests := []struct { + src v1.DataModelInterface + err error + }{ + {&resourcetypeutil.FakeResource{}, v1.ErrInvalidModelConversion}, + {nil, v1.ErrInvalidModelConversion}, + } + + for _, tc := range validationTests { + versioned := &DaprBindingResource{} + err := versioned.ConvertFrom(tc.src) + require.ErrorAs(t, tc.err, &err) + } +} diff --git a/pkg/daprrp/api/v20231001preview/testdata/binding_invalidmanual_resource.json b/pkg/daprrp/api/v20231001preview/testdata/binding_invalidmanual_resource.json new file mode 100644 index 0000000000..c3d6975be6 --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/testdata/binding_invalidmanual_resource.json @@ -0,0 +1,17 @@ +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd", + "name": "test-dbd", + "type": "Applications.Dapr/bindings", + "location": "global", + "tags": { + "env": "dev" + }, + "properties": { + "application": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app", + "environment": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", + "resourceProvisioning": "manual", + "recipe": { + "name": "test-recipe" + } + } +} diff --git a/pkg/daprrp/api/v20231001preview/testdata/binding_invalidrecipe_resource.json b/pkg/daprrp/api/v20231001preview/testdata/binding_invalidrecipe_resource.json new file mode 100644 index 0000000000..3eda2f1f0e --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/testdata/binding_invalidrecipe_resource.json @@ -0,0 +1,20 @@ +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd", + "name": "test-dbd", + "type": "Applications.Dapr/bindings", + "location": "global", + "tags": { + "env": "dev" + }, + "properties": { + "application": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app", + "environment": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": { + "value": "bar" + } + } + } +} diff --git a/pkg/daprrp/api/v20231001preview/testdata/binding_manual_datamodel.json b/pkg/daprrp/api/v20231001preview/testdata/binding_manual_datamodel.json new file mode 100644 index 0000000000..9bc8c7c91e --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/testdata/binding_manual_datamodel.json @@ -0,0 +1,45 @@ +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd", + "name": "test-dbd", + "type": "Applications.Dapr/bindings", + "location": "global", + "tags": { + "env": "dev" + }, + "systemData": { + "createdBy": "fakeid@live.com", + "createdByType": "User", + "createdAt": "2021-09-24T19:09:54.2403864Z", + "lastModifiedBy": "fakeid@live.com", + "lastModifiedByType": "User", + "lastModifiedAt": "2021-09-24T20:09:54.2403864Z" + }, + "properties": { + "auth": { + "secretStore": "test-secret-store" + }, + "componentName": "test-dbd", + "status": { + "outputResources": [ + { + "id": "/planes/test/local/providers/Test.Namespace/testResources/test-resource" + } + ] + }, + "application": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app", + "environment": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", + "resourceProvisioning": "manual", + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": { + "value": "bar" + } + }, + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ] + } +} diff --git a/pkg/daprrp/api/v20231001preview/testdata/binding_manual_generic_datamodel.json b/pkg/daprrp/api/v20231001preview/testdata/binding_manual_generic_datamodel.json new file mode 100644 index 0000000000..56d6460234 --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/testdata/binding_manual_generic_datamodel.json @@ -0,0 +1,41 @@ +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd", + "name": "test-dbd", + "type": "Applications.Dapr/bindings", + "location": "global", + "systemData": { + "createdBy": "fakeid@live.com", + "createdByType": "User", + "createdAt": "2021-09-24T19:09:54.2403864Z", + "lastModifiedBy": "fakeid@live.com", + "lastModifiedByType": "User", + "lastModifiedAt": "2021-09-24T20:09:54.2403864Z" + }, + "tags": { + "env": "dev" + }, + "properties": { + "auth": { + "secretStore": "test-secret-store" + }, + "componentName": "test-dbd", + "status": { + "outputResources": [ + { + "id": "/planes/test/local/providers/Test.Namespace/testResources/test-resource" + } + ] + }, + "application": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app", + "environment": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", + "kind": "generic", + "resourceProvisioning": "manual", + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": { + "value": "bar" + } + } + } +} diff --git a/pkg/daprrp/api/v20231001preview/testdata/binding_manual_resource.json b/pkg/daprrp/api/v20231001preview/testdata/binding_manual_resource.json new file mode 100644 index 0000000000..65709a8063 --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/testdata/binding_manual_resource.json @@ -0,0 +1,26 @@ +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd", + "name": "test-dbd", + "type": "Applications.Dapr/bindings", + "location": "global", + "tags": { + "env": "dev" + }, + "properties": { + "application": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app", + "environment": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", + "resourceProvisioning": "manual", + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": { + "value": "bar" + } + }, + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ] + } +} diff --git a/pkg/daprrp/api/v20231001preview/testdata/binding_recipe_datamodel.json b/pkg/daprrp/api/v20231001preview/testdata/binding_recipe_datamodel.json new file mode 100644 index 0000000000..b28b4cdfa6 --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/testdata/binding_recipe_datamodel.json @@ -0,0 +1,37 @@ +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd", + "name": "test-dbd", + "type": "Applications.Dapr/bindings", + "location": "global", + "tags": { + "env": "dev" + }, + "systemData": { + "createdBy": "fakeid@live.com", + "createdByType": "User", + "createdAt": "2021-09-24T19:09:54.2403864Z", + "lastModifiedBy": "fakeid@live.com", + "lastModifiedByType": "User", + "lastModifiedAt": "2021-09-24T20:09:54.2403864Z" + }, + "properties": { + "componentName": "test-dbd", + "status": { + "outputResources": [ + { + "id": "/planes/test/local/providers/Test.Namespace/testResources/test-resource" + } + ], + "recipe": { + "templateKind": "bicep", + "templatePath": "br:sampleregistry.azureacr.io/radius/recipes/abc" + } + }, + "application": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app", + "environment": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", + "resourceProvisioning": "recipe", + "recipe": { + "name": "dbd-recipe" + } + } +} diff --git a/pkg/daprrp/api/v20231001preview/testdata/binding_recipe_resource.json b/pkg/daprrp/api/v20231001preview/testdata/binding_recipe_resource.json new file mode 100644 index 0000000000..ff0c68b7ee --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/testdata/binding_recipe_resource.json @@ -0,0 +1,17 @@ +{ + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd", + "name": "test-dbd", + "type": "Applications.Dapr/bindings", + "location": "global", + "tags": { + "env": "dev" + }, + "properties": { + "application": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/applications/test-app", + "environment": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Core/environments/test-env", + "resourceProvisioning": "recipe", + "recipe": { + "name": "dbd-recipe" + } + } +} diff --git a/pkg/daprrp/api/v20231001preview/zz_generated_bindings_client.go b/pkg/daprrp/api/v20231001preview/zz_generated_bindings_client.go new file mode 100644 index 0000000000..6434fe981f --- /dev/null +++ b/pkg/daprrp/api/v20231001preview/zz_generated_bindings_client.go @@ -0,0 +1,355 @@ +// Licensed under the Apache License, Version 2.0 . See LICENSE in the repository root for license information. +// Code generated by Microsoft (R) AutoRest Code Generator. DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package v20231001preview + +import ( + "context" + "errors" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" + "net/http" + "net/url" + "strings" +) + +// BindingsClient contains the methods for the Bindings group. +// Don't use this type directly, use NewBindingsClient() instead. +type BindingsClient struct { + internal *arm.Client + rootScope string +} + +// NewBindingsClient creates a new instance of BindingsClient with the specified values. +// - rootScope - The scope in which the resource is present. UCP Scope is /planes/{planeType}/{planeName}/resourceGroup/{resourcegroupID} +// and Azure resource scope is +// /subscriptions/{subscriptionID}/resourceGroup/{resourcegroupID} +// - credential - used to authorize requests. Usually a credential from azidentity. +// - options - pass nil to accept the default values. +func NewBindingsClient(rootScope string, credential azcore.TokenCredential, options *arm.ClientOptions) (*BindingsClient, error) { + cl, err := arm.NewClient(moduleName, moduleVersion, credential, options) + if err != nil { + return nil, err + } + client := &BindingsClient{ + rootScope: rootScope, + internal: cl, + } + return client, nil +} + +// BeginCreateOrUpdate - Create a DaprBindingResource +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-10-01-preview +// - bindingName - Binding name +// - resource - Resource create parameters. +// - options - BindingsClientBeginCreateOrUpdateOptions contains the optional parameters for the BindingsClient.BeginCreateOrUpdate +// method. +func (client *BindingsClient) BeginCreateOrUpdate(ctx context.Context, bindingName string, resource DaprBindingResource, options *BindingsClientBeginCreateOrUpdateOptions) (*runtime.Poller[BindingsClientCreateOrUpdateResponse], error) { + if options == nil || options.ResumeToken == "" { + resp, err := client.createOrUpdate(ctx, bindingName, resource, options) + if err != nil { + return nil, err + } + poller, err := runtime.NewPoller(resp, client.internal.Pipeline(), &runtime.NewPollerOptions[BindingsClientCreateOrUpdateResponse]{ + FinalStateVia: runtime.FinalStateViaAzureAsyncOp, + Tracer: client.internal.Tracer(), + }) + return poller, err + } else { + return runtime.NewPollerFromResumeToken(options.ResumeToken, client.internal.Pipeline(), &runtime.NewPollerFromResumeTokenOptions[BindingsClientCreateOrUpdateResponse]{ + Tracer: client.internal.Tracer(), + }) + } +} + +// CreateOrUpdate - Create a DaprBindingResource +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-10-01-preview +func (client *BindingsClient) createOrUpdate(ctx context.Context, bindingName string, resource DaprBindingResource, options *BindingsClientBeginCreateOrUpdateOptions) (*http.Response, error) { + var err error + ctx, endSpan := runtime.StartSpan(ctx, "BindingsClient.BeginCreateOrUpdate", client.internal.Tracer(), nil) + defer func() { endSpan(err) }() + req, err := client.createOrUpdateCreateRequest(ctx, bindingName, resource, options) + if err != nil { + return nil, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return nil, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK, http.StatusCreated) { + err = runtime.NewResponseError(httpResp) + return nil, err + } + return httpResp, nil +} + +// createOrUpdateCreateRequest creates the CreateOrUpdate request. +func (client *BindingsClient) createOrUpdateCreateRequest(ctx context.Context, bindingName string, resource DaprBindingResource, _ *BindingsClientBeginCreateOrUpdateOptions) (*policy.Request, error) { + urlPath := "/{rootScope}/providers/Applications.Dapr/bindings/{bindingName}" + urlPath = strings.ReplaceAll(urlPath, "{rootScope}", client.rootScope) + if bindingName == "" { + return nil, errors.New("parameter bindingName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{bindingName}", url.PathEscape(bindingName)) + req, err := runtime.NewRequest(ctx, http.MethodPut, runtime.JoinPaths(client.internal.Endpoint(), urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-10-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, resource); err != nil { + return nil, err +} +; return req, nil +} + +// BeginDelete - Delete a DaprBindingResource +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-10-01-preview +// - bindingName - Binding name +// - options - BindingsClientBeginDeleteOptions contains the optional parameters for the BindingsClient.BeginDelete method. +func (client *BindingsClient) BeginDelete(ctx context.Context, bindingName string, options *BindingsClientBeginDeleteOptions) (*runtime.Poller[BindingsClientDeleteResponse], error) { + if options == nil || options.ResumeToken == "" { + resp, err := client.deleteOperation(ctx, bindingName, options) + if err != nil { + return nil, err + } + poller, err := runtime.NewPoller(resp, client.internal.Pipeline(), &runtime.NewPollerOptions[BindingsClientDeleteResponse]{ + FinalStateVia: runtime.FinalStateViaLocation, + Tracer: client.internal.Tracer(), + }) + return poller, err + } else { + return runtime.NewPollerFromResumeToken(options.ResumeToken, client.internal.Pipeline(), &runtime.NewPollerFromResumeTokenOptions[BindingsClientDeleteResponse]{ + Tracer: client.internal.Tracer(), + }) + } +} + +// Delete - Delete a DaprBindingResource +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-10-01-preview +func (client *BindingsClient) deleteOperation(ctx context.Context, bindingName string, options *BindingsClientBeginDeleteOptions) (*http.Response, error) { + var err error + ctx, endSpan := runtime.StartSpan(ctx, "BindingsClient.BeginDelete", client.internal.Tracer(), nil) + defer func() { endSpan(err) }() + req, err := client.deleteCreateRequest(ctx, bindingName, options) + if err != nil { + return nil, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return nil, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK, http.StatusAccepted, http.StatusNoContent) { + err = runtime.NewResponseError(httpResp) + return nil, err + } + return httpResp, nil +} + +// deleteCreateRequest creates the Delete request. +func (client *BindingsClient) deleteCreateRequest(ctx context.Context, bindingName string, _ *BindingsClientBeginDeleteOptions) (*policy.Request, error) { + urlPath := "/{rootScope}/providers/Applications.Dapr/bindings/{bindingName}" + urlPath = strings.ReplaceAll(urlPath, "{rootScope}", client.rootScope) + if bindingName == "" { + return nil, errors.New("parameter bindingName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{bindingName}", url.PathEscape(bindingName)) + req, err := runtime.NewRequest(ctx, http.MethodDelete, runtime.JoinPaths(client.internal.Endpoint(), urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-10-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// Get - Get a DaprBindingResource +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-10-01-preview +// - bindingName - Binding name +// - options - BindingsClientGetOptions contains the optional parameters for the BindingsClient.Get method. +func (client *BindingsClient) Get(ctx context.Context, bindingName string, options *BindingsClientGetOptions) (BindingsClientGetResponse, error) { + var err error + ctx, endSpan := runtime.StartSpan(ctx, "BindingsClient.Get", client.internal.Tracer(), nil) + defer func() { endSpan(err) }() + req, err := client.getCreateRequest(ctx, bindingName, options) + if err != nil { + return BindingsClientGetResponse{}, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return BindingsClientGetResponse{}, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK) { + err = runtime.NewResponseError(httpResp) + return BindingsClientGetResponse{}, err + } + resp, err := client.getHandleResponse(httpResp) + return resp, err +} + +// getCreateRequest creates the Get request. +func (client *BindingsClient) getCreateRequest(ctx context.Context, bindingName string, _ *BindingsClientGetOptions) (*policy.Request, error) { + urlPath := "/{rootScope}/providers/Applications.Dapr/bindings/{bindingName}" + urlPath = strings.ReplaceAll(urlPath, "{rootScope}", client.rootScope) + if bindingName == "" { + return nil, errors.New("parameter bindingName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{bindingName}", url.PathEscape(bindingName)) + req, err := runtime.NewRequest(ctx, http.MethodGet, runtime.JoinPaths(client.internal.Endpoint(), urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-10-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// getHandleResponse handles the Get response. +func (client *BindingsClient) getHandleResponse(resp *http.Response) (BindingsClientGetResponse, error) { + result := BindingsClientGetResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.DaprBindingResource); err != nil { + return BindingsClientGetResponse{}, err + } + return result, nil +} + +// NewListByScopePager - List DaprBindingResource resources by Scope +// +// Generated from API version 2023-10-01-preview +// - options - BindingsClientListByScopeOptions contains the optional parameters for the BindingsClient.NewListByScopePager +// method. +func (client *BindingsClient) NewListByScopePager(options *BindingsClientListByScopeOptions) (*runtime.Pager[BindingsClientListByScopeResponse]) { + return runtime.NewPager(runtime.PagingHandler[BindingsClientListByScopeResponse]{ + More: func(page BindingsClientListByScopeResponse) bool { + return page.NextLink != nil && len(*page.NextLink) > 0 + }, + Fetcher: func(ctx context.Context, page *BindingsClientListByScopeResponse) (BindingsClientListByScopeResponse, error) { + nextLink := "" + if page != nil { + nextLink = *page.NextLink + } + resp, err := runtime.FetcherForNextLink(ctx, client.internal.Pipeline(), nextLink, func(ctx context.Context) (*policy.Request, error) { + return client.listByScopeCreateRequest(ctx, options) + }, nil) + if err != nil { + return BindingsClientListByScopeResponse{}, err + } + return client.listByScopeHandleResponse(resp) + }, + Tracer: client.internal.Tracer(), + }) +} + +// listByScopeCreateRequest creates the ListByScope request. +func (client *BindingsClient) listByScopeCreateRequest(ctx context.Context, _ *BindingsClientListByScopeOptions) (*policy.Request, error) { + urlPath := "/{rootScope}/providers/Applications.Dapr/bindings" + urlPath = strings.ReplaceAll(urlPath, "{rootScope}", client.rootScope) + req, err := runtime.NewRequest(ctx, http.MethodGet, runtime.JoinPaths(client.internal.Endpoint(), urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-10-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + return req, nil +} + +// listByScopeHandleResponse handles the ListByScope response. +func (client *BindingsClient) listByScopeHandleResponse(resp *http.Response) (BindingsClientListByScopeResponse, error) { + result := BindingsClientListByScopeResponse{} + if err := runtime.UnmarshalAsJSON(resp, &result.DaprBindingResourceListResult); err != nil { + return BindingsClientListByScopeResponse{}, err + } + return result, nil +} + +// BeginUpdate - Update a DaprBindingResource +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-10-01-preview +// - bindingName - Binding name +// - properties - The resource properties to be updated. +// - options - BindingsClientBeginUpdateOptions contains the optional parameters for the BindingsClient.BeginUpdate method. +func (client *BindingsClient) BeginUpdate(ctx context.Context, bindingName string, properties DaprBindingResourceUpdate, options *BindingsClientBeginUpdateOptions) (*runtime.Poller[BindingsClientUpdateResponse], error) { + if options == nil || options.ResumeToken == "" { + resp, err := client.update(ctx, bindingName, properties, options) + if err != nil { + return nil, err + } + poller, err := runtime.NewPoller(resp, client.internal.Pipeline(), &runtime.NewPollerOptions[BindingsClientUpdateResponse]{ + FinalStateVia: runtime.FinalStateViaLocation, + Tracer: client.internal.Tracer(), + }) + return poller, err + } else { + return runtime.NewPollerFromResumeToken(options.ResumeToken, client.internal.Pipeline(), &runtime.NewPollerFromResumeTokenOptions[BindingsClientUpdateResponse]{ + Tracer: client.internal.Tracer(), + }) + } +} + +// Update - Update a DaprBindingResource +// If the operation fails it returns an *azcore.ResponseError type. +// +// Generated from API version 2023-10-01-preview +func (client *BindingsClient) update(ctx context.Context, bindingName string, properties DaprBindingResourceUpdate, options *BindingsClientBeginUpdateOptions) (*http.Response, error) { + var err error + ctx, endSpan := runtime.StartSpan(ctx, "BindingsClient.BeginUpdate", client.internal.Tracer(), nil) + defer func() { endSpan(err) }() + req, err := client.updateCreateRequest(ctx, bindingName, properties, options) + if err != nil { + return nil, err + } + httpResp, err := client.internal.Pipeline().Do(req) + if err != nil { + return nil, err + } + if !runtime.HasStatusCode(httpResp, http.StatusOK, http.StatusAccepted) { + err = runtime.NewResponseError(httpResp) + return nil, err + } + return httpResp, nil +} + +// updateCreateRequest creates the Update request. +func (client *BindingsClient) updateCreateRequest(ctx context.Context, bindingName string, properties DaprBindingResourceUpdate, _ *BindingsClientBeginUpdateOptions) (*policy.Request, error) { + urlPath := "/{rootScope}/providers/Applications.Dapr/bindings/{bindingName}" + urlPath = strings.ReplaceAll(urlPath, "{rootScope}", client.rootScope) + if bindingName == "" { + return nil, errors.New("parameter bindingName cannot be empty") + } + urlPath = strings.ReplaceAll(urlPath, "{bindingName}", url.PathEscape(bindingName)) + req, err := runtime.NewRequest(ctx, http.MethodPatch, runtime.JoinPaths(client.internal.Endpoint(), urlPath)) + if err != nil { + return nil, err + } + reqQP := req.Raw().URL.Query() + reqQP.Set("api-version", "2023-10-01-preview") + req.Raw().URL.RawQuery = reqQP.Encode() + req.Raw().Header["Accept"] = []string{"application/json"} + if err := runtime.MarshalAsJSON(req, properties); err != nil { + return nil, err +} +; return req, nil +} + diff --git a/pkg/daprrp/api/v20231001preview/zz_generated_client_factory.go b/pkg/daprrp/api/v20231001preview/zz_generated_client_factory.go index 1431c737b5..9e7cff695e 100644 --- a/pkg/daprrp/api/v20231001preview/zz_generated_client_factory.go +++ b/pkg/daprrp/api/v20231001preview/zz_generated_client_factory.go @@ -34,6 +34,14 @@ func NewClientFactory(rootScope string, credential azcore.TokenCredential, optio }, nil } +// NewBindingsClient creates a new instance of BindingsClient. +func (c *ClientFactory) NewBindingsClient() *BindingsClient { + return &BindingsClient{ + rootScope: c.rootScope, + internal: c.internal, + } +} + // NewConfigurationStoresClient creates a new instance of ConfigurationStoresClient. func (c *ClientFactory) NewConfigurationStoresClient() *ConfigurationStoresClient { return &ConfigurationStoresClient{ diff --git a/pkg/daprrp/api/v20231001preview/zz_generated_models.go b/pkg/daprrp/api/v20231001preview/zz_generated_models.go index c4b400ff08..b373bed5e8 100644 --- a/pkg/daprrp/api/v20231001preview/zz_generated_models.go +++ b/pkg/daprrp/api/v20231001preview/zz_generated_models.go @@ -25,6 +25,97 @@ type AzureResourceManagerCommonTypesTrackedResourceUpdate struct { Type *string } +// DaprBindingProperties - Dapr binding portable resource properties +type DaprBindingProperties struct { +// REQUIRED; Fully qualified resource ID for the environment that the portable resource is linked to + Environment *string + +// 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]*MetadataValue + +// The recipe used to automatically deploy underlying infrastructure for the resource + Recipe *Recipe + +// Specifies how the underlying service/resource is provisioned and managed. + ResourceProvisioning *ResourceProvisioning + +// A collection of references to resources associated with the binding + Resources []*ResourceReference + +// 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 + +// READ-ONLY; The status of the asynchronous operation. + ProvisioningState *ProvisioningState + +// READ-ONLY; Status of a resource. + Status *ResourceStatus +} + +// DaprBindingResource - Dapr binding portable resource +type DaprBindingResource struct { +// REQUIRED; The geo-location where the resource lives + Location *string + +// REQUIRED; The resource-specific properties for this resource. + Properties *DaprBindingProperties + +// Resource tags. + Tags map[string]*string + +// READ-ONLY; Fully qualified resource ID for the resource. Ex - /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName} + ID *string + +// READ-ONLY; The name of the resource + Name *string + +// READ-ONLY; Azure Resource Manager metadata containing createdBy and modifiedBy information. + SystemData *SystemData + +// READ-ONLY; The type of the resource. E.g. "Microsoft.Compute/virtualMachines" or "Microsoft.Storage/storageAccounts" + Type *string +} + +// DaprBindingResourceListResult - The response of a DaprBindingResource list operation. +type DaprBindingResourceListResult struct { +// REQUIRED; The DaprBindingResource items on this page + Value []*DaprBindingResource + +// The link to the next page of items + NextLink *string +} + +// DaprBindingResourceUpdate - Dapr binding portable resource +type DaprBindingResourceUpdate struct { +// Resource tags. + Tags map[string]*string + +// READ-ONLY; Fully qualified resource ID for the resource. Ex - /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/{resourceProviderNamespace}/{resourceType}/{resourceName} + ID *string + +// READ-ONLY; The name of the resource + Name *string + +// READ-ONLY; Azure Resource Manager metadata containing createdBy and modifiedBy information. + SystemData *SystemData + +// READ-ONLY; The type of the resource. E.g. "Microsoft.Compute/virtualMachines" or "Microsoft.Storage/storageAccounts" + Type *string +} + // DaprConfigurationStoreProperties - Dapr configuration store portable resource properties type DaprConfigurationStoreProperties struct { // REQUIRED; Fully qualified resource ID for the environment that the portable resource is linked to diff --git a/pkg/daprrp/api/v20231001preview/zz_generated_models_serde.go b/pkg/daprrp/api/v20231001preview/zz_generated_models_serde.go index 72a246e0dc..7697194af1 100644 --- a/pkg/daprrp/api/v20231001preview/zz_generated_models_serde.go +++ b/pkg/daprrp/api/v20231001preview/zz_generated_models_serde.go @@ -54,6 +54,202 @@ func (a *AzureResourceManagerCommonTypesTrackedResourceUpdate) UnmarshalJSON(dat return nil } +// MarshalJSON implements the json.Marshaller interface for type DaprBindingProperties. +func (d DaprBindingProperties) 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) + populate(objectMap, "provisioningState", d.ProvisioningState) + populate(objectMap, "recipe", d.Recipe) + populate(objectMap, "resourceProvisioning", d.ResourceProvisioning) + populate(objectMap, "resources", d.Resources) + populate(objectMap, "status", d.Status) + populate(objectMap, "type", d.Type) + populate(objectMap, "version", d.Version) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type DaprBindingProperties. +func (d *DaprBindingProperties) 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 "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) + case "environment": + err = unpopulate(val, "Environment", &d.Environment) + delete(rawMsg, key) + case "metadata": + err = unpopulate(val, "Metadata", &d.Metadata) + delete(rawMsg, key) + case "provisioningState": + err = unpopulate(val, "ProvisioningState", &d.ProvisioningState) + delete(rawMsg, key) + case "recipe": + err = unpopulate(val, "Recipe", &d.Recipe) + delete(rawMsg, key) + case "resourceProvisioning": + err = unpopulate(val, "ResourceProvisioning", &d.ResourceProvisioning) + delete(rawMsg, key) + case "resources": + err = unpopulate(val, "Resources", &d.Resources) + delete(rawMsg, key) + case "status": + err = unpopulate(val, "Status", &d.Status) + delete(rawMsg, key) + case "type": + err = unpopulate(val, "Type", &d.Type) + delete(rawMsg, key) + case "version": + err = unpopulate(val, "Version", &d.Version) + 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 DaprBindingResource. +func (d DaprBindingResource) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "id", d.ID) + populate(objectMap, "location", d.Location) + populate(objectMap, "name", d.Name) + populate(objectMap, "properties", d.Properties) + populate(objectMap, "systemData", d.SystemData) + populate(objectMap, "tags", d.Tags) + populate(objectMap, "type", d.Type) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type DaprBindingResource. +func (d *DaprBindingResource) 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 "id": + err = unpopulate(val, "ID", &d.ID) + delete(rawMsg, key) + case "location": + err = unpopulate(val, "Location", &d.Location) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &d.Name) + delete(rawMsg, key) + case "properties": + err = unpopulate(val, "Properties", &d.Properties) + delete(rawMsg, key) + case "systemData": + err = unpopulate(val, "SystemData", &d.SystemData) + delete(rawMsg, key) + case "tags": + err = unpopulate(val, "Tags", &d.Tags) + delete(rawMsg, key) + case "type": + err = unpopulate(val, "Type", &d.Type) + 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 DaprBindingResourceListResult. +func (d DaprBindingResourceListResult) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "nextLink", d.NextLink) + populate(objectMap, "value", d.Value) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type DaprBindingResourceListResult. +func (d *DaprBindingResourceListResult) 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 "nextLink": + err = unpopulate(val, "NextLink", &d.NextLink) + delete(rawMsg, key) + case "value": + err = unpopulate(val, "Value", &d.Value) + 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 DaprBindingResourceUpdate. +func (d DaprBindingResourceUpdate) MarshalJSON() ([]byte, error) { + objectMap := make(map[string]any) + populate(objectMap, "id", d.ID) + populate(objectMap, "name", d.Name) + populate(objectMap, "systemData", d.SystemData) + populate(objectMap, "tags", d.Tags) + populate(objectMap, "type", d.Type) + return json.Marshal(objectMap) +} + +// UnmarshalJSON implements the json.Unmarshaller interface for type DaprBindingResourceUpdate. +func (d *DaprBindingResourceUpdate) 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 "id": + err = unpopulate(val, "ID", &d.ID) + delete(rawMsg, key) + case "name": + err = unpopulate(val, "Name", &d.Name) + delete(rawMsg, key) + case "systemData": + err = unpopulate(val, "SystemData", &d.SystemData) + delete(rawMsg, key) + case "tags": + err = unpopulate(val, "Tags", &d.Tags) + delete(rawMsg, key) + case "type": + err = unpopulate(val, "Type", &d.Type) + 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 DaprConfigurationStoreProperties. func (d DaprConfigurationStoreProperties) MarshalJSON() ([]byte, error) { objectMap := make(map[string]any) diff --git a/pkg/daprrp/api/v20231001preview/zz_generated_options.go b/pkg/daprrp/api/v20231001preview/zz_generated_options.go index 768444955c..66c4a2aaf8 100644 --- a/pkg/daprrp/api/v20231001preview/zz_generated_options.go +++ b/pkg/daprrp/api/v20231001preview/zz_generated_options.go @@ -4,6 +4,34 @@ package v20231001preview +// BindingsClientBeginCreateOrUpdateOptions contains the optional parameters for the BindingsClient.BeginCreateOrUpdate method. +type BindingsClientBeginCreateOrUpdateOptions struct { +// Resumes the long-running operation from the provided token. + ResumeToken string +} + +// BindingsClientBeginDeleteOptions contains the optional parameters for the BindingsClient.BeginDelete method. +type BindingsClientBeginDeleteOptions struct { +// Resumes the long-running operation from the provided token. + ResumeToken string +} + +// BindingsClientBeginUpdateOptions contains the optional parameters for the BindingsClient.BeginUpdate method. +type BindingsClientBeginUpdateOptions struct { +// Resumes the long-running operation from the provided token. + ResumeToken string +} + +// BindingsClientGetOptions contains the optional parameters for the BindingsClient.Get method. +type BindingsClientGetOptions struct { + // placeholder for future optional parameters +} + +// BindingsClientListByScopeOptions contains the optional parameters for the BindingsClient.NewListByScopePager method. +type BindingsClientListByScopeOptions struct { + // placeholder for future optional parameters +} + // ConfigurationStoresClientBeginCreateOrUpdateOptions contains the optional parameters for the ConfigurationStoresClient.BeginCreateOrUpdate // method. type ConfigurationStoresClientBeginCreateOrUpdateOptions struct { diff --git a/pkg/daprrp/api/v20231001preview/zz_generated_responses.go b/pkg/daprrp/api/v20231001preview/zz_generated_responses.go index f005a866ca..b5fe58ab58 100644 --- a/pkg/daprrp/api/v20231001preview/zz_generated_responses.go +++ b/pkg/daprrp/api/v20231001preview/zz_generated_responses.go @@ -4,6 +4,35 @@ package v20231001preview +// BindingsClientCreateOrUpdateResponse contains the response from method BindingsClient.BeginCreateOrUpdate. +type BindingsClientCreateOrUpdateResponse struct { +// Dapr binding portable resource + DaprBindingResource +} + +// BindingsClientDeleteResponse contains the response from method BindingsClient.BeginDelete. +type BindingsClientDeleteResponse struct { + // placeholder for future response values +} + +// BindingsClientGetResponse contains the response from method BindingsClient.Get. +type BindingsClientGetResponse struct { +// Dapr binding portable resource + DaprBindingResource +} + +// BindingsClientListByScopeResponse contains the response from method BindingsClient.NewListByScopePager. +type BindingsClientListByScopeResponse struct { +// The response of a DaprBindingResource list operation. + DaprBindingResourceListResult +} + +// BindingsClientUpdateResponse contains the response from method BindingsClient.BeginUpdate. +type BindingsClientUpdateResponse struct { +// Dapr binding portable resource + DaprBindingResource +} + // ConfigurationStoresClientCreateOrUpdateResponse contains the response from method ConfigurationStoresClient.BeginCreateOrUpdate. type ConfigurationStoresClientCreateOrUpdateResponse struct { // Dapr configuration store portable resource diff --git a/pkg/daprrp/datamodel/converter/binding_converter.go b/pkg/daprrp/datamodel/converter/binding_converter.go new file mode 100644 index 0000000000..c835271fa4 --- /dev/null +++ b/pkg/daprrp/datamodel/converter/binding_converter.go @@ -0,0 +1,60 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package converter + +import ( + "encoding/json" + + v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" + "github.com/radius-project/radius/pkg/daprrp/api/v20231001preview" + "github.com/radius-project/radius/pkg/daprrp/datamodel" +) + +// BindingDataModelToVersioned converts a version-agnostic datamodel.DaprBinding to a versioned model based on the version +// string, returning an error if the version is not supported. +func BindingDataModelToVersioned(model *datamodel.DaprBinding, version string) (v1.VersionedModelInterface, error) { + switch version { + case v20231001preview.Version: + versioned := &v20231001preview.DaprBindingResource{} + err := versioned.ConvertFrom(model) + return versioned, err + + default: + return nil, v1.ErrUnsupportedAPIVersion + } +} + +// BindingDataModelFromVersioned unmarshals a JSON byte slice into a versioned Binding resource and converts it +// to a version-agnostic datamodel Configuration Store, returning an error if either operation fails. +func BindingDataModelFromVersioned(content []byte, version string) (*datamodel.DaprBinding, error) { + switch version { + case v20231001preview.Version: + am := &v20231001preview.DaprBindingResource{} + if err := json.Unmarshal(content, am); err != nil { + return nil, err + } + dm, err := am.ConvertTo() + if err != nil { + return nil, err + } + + return dm.(*datamodel.DaprBinding), err + + default: + return nil, v1.ErrUnsupportedAPIVersion + } +} diff --git a/pkg/daprrp/datamodel/converter/binding_converter_test.go b/pkg/daprrp/datamodel/converter/binding_converter_test.go new file mode 100644 index 0000000000..02e80680da --- /dev/null +++ b/pkg/daprrp/datamodel/converter/binding_converter_test.go @@ -0,0 +1,217 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package converter + +import ( + "encoding/json" + "testing" + "time" + + v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" + "github.com/radius-project/radius/pkg/daprrp/api/v20231001preview" + "github.com/radius-project/radius/pkg/daprrp/datamodel" + "github.com/radius-project/radius/pkg/to" + "github.com/radius-project/radius/test/testutil" + "github.com/radius-project/radius/test/testutil/resourcetypeutil" + "github.com/stretchr/testify/require" +) + +// Validates type conversion between versioned client side data model and RP data model. +func TestBindingDataModelToVersioned(t *testing.T) { + createdAt, err := time.Parse(time.RFC3339Nano, "2021-09-24T19:09:54.2403864Z") + require.NoError(t, err) + + lastModifiedAt, err := time.Parse(time.RFC3339Nano, "2021-09-24T20:09:54.2403864Z") + require.NoError(t, err) + + testset := []struct { + dataModelFile string + apiVersion string + apiModelType any + expected *v20231001preview.DaprBindingResource + err error + }{ + { + "../../api/v20231001preview/testdata/binding_manual_datamodel.json", + "2023-10-01-preview", + &v20231001preview.DaprBindingResource{}, + &v20231001preview.DaprBindingResource{ + Location: to.Ptr("global"), + Properties: &v20231001preview.DaprBindingProperties{ + 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]*v20231001preview.MetadataValue{ + "foo": { + Value: to.Ptr("bar"), + }, + }, + Recipe: nil, + ResourceProvisioning: to.Ptr(v20231001preview.ResourceProvisioningManual), + Resources: []*v20231001preview.ResourceReference{ + { + ID: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn"), + }, + }, + Type: to.Ptr("bindings.azure.blobstorage"), + Version: to.Ptr("v1"), + ComponentName: to.Ptr("test-dbd"), + 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"), + }, + ID: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd"), + Name: to.Ptr("test-dbd"), + SystemData: &v20231001preview.SystemData{ + CreatedAt: &createdAt, + CreatedBy: to.Ptr("fakeid@live.com"), + CreatedByType: to.Ptr(v20231001preview.CreatedByTypeUser), + LastModifiedAt: &lastModifiedAt, + LastModifiedBy: to.Ptr("fakeid@live.com"), + LastModifiedByType: to.Ptr(v20231001preview.CreatedByTypeUser), + }, + Type: to.Ptr("Applications.Dapr/bindings"), + }, + nil, + }, + { + "../../api/v20231001preview/testdata/binding_manual_generic_datamodel.json", + "2023-10-01-preview", + &v20231001preview.DaprBindingResource{}, + &v20231001preview.DaprBindingResource{ + Location: to.Ptr("global"), + Properties: &v20231001preview.DaprBindingProperties{ + 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]*v20231001preview.MetadataValue{ + "foo": { + Value: to.Ptr("bar"), + }, + }, + Recipe: nil, + ResourceProvisioning: to.Ptr(v20231001preview.ResourceProvisioningManual), + Resources: nil, + Type: to.Ptr("bindings.azure.blobstorage"), + Version: to.Ptr("v1"), + ComponentName: to.Ptr("test-dbd"), + 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"), + }, + ID: to.Ptr("/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/radius-test-rg/providers/Applications.Dapr/bindings/test-dbd"), + Name: to.Ptr("test-dbd"), + SystemData: &v20231001preview.SystemData{ + CreatedAt: &createdAt, + CreatedBy: to.Ptr("fakeid@live.com"), + CreatedByType: to.Ptr(v20231001preview.CreatedByTypeUser), + LastModifiedAt: &lastModifiedAt, + LastModifiedBy: to.Ptr("fakeid@live.com"), + LastModifiedByType: to.Ptr(v20231001preview.CreatedByTypeUser), + }, + Type: to.Ptr("Applications.Dapr/bindings"), + }, + nil, + }, + { + "../../api/v20231001preview/testdata/binding_manual_generic_datamodel.json", + "unsupported", + nil, + nil, + v1.ErrUnsupportedAPIVersion, + }, + } + + for _, tc := range testset { + t.Run(tc.apiVersion, func(t *testing.T) { + c := testutil.ReadFixture("../" + tc.dataModelFile) + dm := &datamodel.DaprBinding{} + err = json.Unmarshal(c, dm) + require.NoError(t, err) + + am, err := BindingDataModelToVersioned(dm, tc.apiVersion) + if tc.err != nil { + require.ErrorAs(t, tc.err, &err) + } else { + require.NoError(t, err) + require.IsType(t, tc.apiModelType, am) + require.Equal(t, tc.expected, am) + } + }) + } +} + +func TestDaprBindingDataModelFromVersioned(t *testing.T) { + testset := []struct { + versionedModelFile string + apiVersion string + err error + }{ + { + "../../api/v20231001preview/testdata/binding_invalidrecipe_resource.json", + "2023-10-01-preview", + &v1.ErrClientRP{ + Code: v1.CodeInvalid, + Message: "error(s) found:\n\tmetadata cannot be specified when resourceProvisioning is set to recipe (default)\n\ttype cannot be specified when resourceProvisioning is set to recipe (default)\n\tversion cannot be specified when resourceProvisioning is set to recipe (default)", + }, + }, + { + "../../api/v20231001preview/testdata/binding_invalidmanual_resource.json", + "2023-10-01-preview", + &v1.ErrClientRP{ + Code: "BadRequest", + Message: "error(s) found:\n\trecipe details cannot be specified when resourceProvisioning is set to manual\n\tmetadata must be specified when resourceProvisioning is set to manual\n\ttype must be specified when resourceProvisioning is set to manual\n\tversion must be specified when resourceProvisioning is set to manual", + }, + }, + { + "../../api/v20231001preview/testdata/binding_recipe_resource.json", + "2023-10-01-preview", + nil, + }, + { + "../../api/v20231001preview/testdata/binding_manual_resource.json", + "2023-10-01-preview", + nil, + }, + { + "../../api/v20231001preview/testdata/binding_manual_resource.json", + "unsupported", + v1.ErrUnsupportedAPIVersion, + }, + } + + for _, tc := range testset { + t.Run(tc.apiVersion, func(t *testing.T) { + c := testutil.ReadFixture("../" + tc.versionedModelFile) + dm, err := BindingDataModelFromVersioned(c, tc.apiVersion) + if tc.err != nil { + require.Equal(t, tc.err, err) + } else { + require.NoError(t, err) + require.IsType(t, tc.apiVersion, dm.InternalMetadata.UpdatedAPIVersion) + } + }) + } +} diff --git a/pkg/daprrp/datamodel/daprbinding.go b/pkg/daprrp/datamodel/daprbinding.go new file mode 100644 index 0000000000..b6fdc79c68 --- /dev/null +++ b/pkg/daprrp/datamodel/daprbinding.go @@ -0,0 +1,91 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package datamodel + +import ( + v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" + "github.com/radius-project/radius/pkg/daprrp/frontend/controller" + "github.com/radius-project/radius/pkg/portableresources" + "github.com/radius-project/radius/pkg/portableresources/datamodel" + rpv1 "github.com/radius-project/radius/pkg/rp/v1" +) + +// DaprBinding represents Dapr binding portable resource. +type DaprBinding struct { + v1.BaseResource + + // Properties is the properties of the resource. + Properties DaprBindingProperties `json:"properties"` + + // ResourceMetadata represents internal DataModel properties common to all portable resource types. + datamodel.PortableResourceMetadata +} + +// ApplyDeploymentOutput updates the DaprBinding resource with the DeploymentOutput values. +func (r *DaprBinding) ApplyDeploymentOutput(do rpv1.DeploymentOutput) error { + return nil +} + +// OutputResources returns the OutputResources from the Properties of the DaprBinding instance. +func (r *DaprBinding) OutputResources() []rpv1.OutputResource { + return r.Properties.Status.OutputResources +} + +// ResourceMetadata returns the BasicResourceProperties of the Dapr Binding resource i.e. application resources metadata. +func (r *DaprBinding) ResourceMetadata() *rpv1.BasicResourceProperties { + return &r.Properties.BasicResourceProperties +} + +// ResourceTypeName returns a string representing the resource type. +func (r *DaprBinding) ResourceTypeName() string { + return controller.DaprBindingsResourceType +} + +// Recipe returns the recipe information of the resource. Returns nil if recipe execution is disabled. +func (r *DaprBinding) Recipe() *portableresources.ResourceRecipe { + if r.Properties.ResourceProvisioning == portableresources.ResourceProvisioningManual { + return nil + } + return &r.Properties.Recipe +} + +// DaprBindingProperties represents the properties of Dapr Binding resource. +type DaprBindingProperties struct { + rpv1.BasicResourceProperties + rpv1.BasicDaprResourceProperties + + // ResourceProvisioning specifies how the underlying service/resource is provisioned and managed + ResourceProvisioning portableresources.ResourceProvisioning `json:"resourceProvisioning,omitempty"` + + // Metadata of the Dapr Binding resource. + Metadata map[string]*rpv1.DaprComponentMetadataValue `json:"metadata,omitempty"` + + // The recipe used to automatically deploy underlying infrastructure for the Dapr Binding resource. + Recipe portableresources.ResourceRecipe `json:"recipe,omitempty"` + + // List of the resource IDs that support the Dapr Binding Broker resource. + Resources []*portableresources.ResourceReference `json:"resources,omitempty"` + + // Type of the Dapr Binding resource. + Type string `json:"type,omitempty"` + + // Version of the Dapr Binding resource. + Version string `json:"version,omitempty"` + + // Auth information for the Dapr Binding resource, mainly secret store name. + Auth *rpv1.DaprComponentAuth `json:"auth,omitempty"` +} diff --git a/pkg/daprrp/frontend/controller/types.go b/pkg/daprrp/frontend/controller/types.go index 481d84f2fc..a2b3bbe53a 100644 --- a/pkg/daprrp/frontend/controller/types.go +++ b/pkg/daprrp/frontend/controller/types.go @@ -48,4 +48,11 @@ const ( AsyncCreateOrUpdateDaprConfigurationStoreTimeout = time.Duration(60) * time.Minute // AsyncDeleteDaprConfigurationStoreTimeout is the timeout for async delete dapr configuration store AsyncDeleteDaprConfigurationStoreTimeout = time.Duration(60) * time.Minute + + // DaprBindingsResourceType represents the resource type for Dapr configuration store. + DaprBindingsResourceType = "Applications.Dapr/bindings" + // AsyncCreateOrUpdateDaprBindingTimeout is the timeout for async create or update Dapr Configuration Store + AsyncCreateOrUpdateDaprBindingTimeout = time.Duration(60) * time.Minute + // AsyncDeleteDaprBindingTimeout is the timeout for async delete dapr configuration store + AsyncDeleteDaprBindingTimeout = time.Duration(60) * time.Minute ) diff --git a/pkg/daprrp/processors/bindings/doc.go b/pkg/daprrp/processors/bindings/doc.go new file mode 100644 index 0000000000..75cc15fc7f --- /dev/null +++ b/pkg/daprrp/processors/bindings/doc.go @@ -0,0 +1,18 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// bindings contains the resource processor for Dapr Configuration Stores. See the processors package for more information. +package bindings diff --git a/pkg/daprrp/processors/bindings/processor.go b/pkg/daprrp/processors/bindings/processor.go new file mode 100644 index 0000000000..c379b2d02c --- /dev/null +++ b/pkg/daprrp/processors/bindings/processor.go @@ -0,0 +1,156 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bindings + +import ( + "context" + + "github.com/radius-project/radius/pkg/daprrp/datamodel" + "github.com/radius-project/radius/pkg/daprrp/frontend/controller" + "github.com/radius-project/radius/pkg/kubernetes" + "github.com/radius-project/radius/pkg/kubeutil" + "github.com/radius-project/radius/pkg/portableresources" + "github.com/radius-project/radius/pkg/portableresources/handlers" + "github.com/radius-project/radius/pkg/portableresources/processors" + "github.com/radius-project/radius/pkg/portableresources/renderers/dapr" + rpv1 "github.com/radius-project/radius/pkg/rp/v1" + "github.com/radius-project/radius/pkg/to" + "github.com/radius-project/radius/pkg/ucp/resources" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + runtime "sigs.k8s.io/controller-runtime/pkg/client" +) + +type Processor struct { + Client runtime.Client +} + +// Process validates resource properties, and applies output values from the recipe output. If the resource is +// being provisioned manually, it creates a Dapr component in Kubernetes. +func (p *Processor) Process(ctx context.Context, resource *datamodel.DaprBinding, options processors.Options) error { + validator := processors.NewValidator(&resource.ComputedValues, &resource.SecretValues, &resource.Properties.Status.OutputResources, resource.Properties.Status.Recipe) + validator.AddResourcesField(&resource.Properties.Resources) + validator.AddComputedStringField("componentName", &resource.Properties.ComponentName, func() (string, *processors.ValidationError) { + return kubernetes.NormalizeDaprResourceName(resource.Name), nil + }) + + err := validator.SetAndValidate(options.RecipeOutput) + if err != nil { + return err + } + + if resource.Properties.ResourceProvisioning != portableresources.ResourceProvisioningManual { + // If the resource is being provisioned by recipe then we expect the recipe to create the Dapr Component + // in Kubernetes. At this point we're done so we can just return. + return nil + } + + // If the resource is being provisioned manually then *we* are responsible for creating the Dapr Component. + // Let's do this now. + + // DaprBinding resources may or may not be application scoped. + // Some Dapr Components can be specific to a single application, they would be application scoped and have + // resource.Properties.Application populated, while others could be shared across multiple applications and + // would not have resource.Properties.Application populated. + var applicationID resources.ID + if resource.Properties.Application != "" { + applicationID, err = resources.ParseResource(resource.Properties.Application) + if err != nil { + return err // This should already be validated by this point. + } + } + + 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), + }, + options.RuntimeConfiguration.Kubernetes.Namespace, + resource.Properties.ComponentName, + applicationID.Name(), + resource.Name, + controller.DaprBindingsResourceType) + if err != nil { + return err + } + + err = kubeutil.PatchNamespace(ctx, p.Client, component.GetNamespace()) + if err != nil { + return &processors.ResourceError{Inner: err} + } + + err = handlers.CheckDaprResourceNameUniqueness(ctx, p.Client, resource.Properties.ComponentName, options.RuntimeConfiguration.Kubernetes.Namespace, resource.Name, controller.DaprBindingsResourceType) + if err != nil { + return &processors.ValidationError{Message: err.Error()} + } + + err = p.Client.Patch(ctx, &component, runtime.Apply, &runtime.PatchOptions{FieldManager: kubernetes.FieldManager}) + if err != nil { + return &processors.ResourceError{Inner: err} + } + + deployed := rpv1.NewKubernetesOutputResource("Component", &component, metav1.ObjectMeta{Name: component.GetName(), Namespace: component.GetNamespace()}) + deployed.RadiusManaged = to.Ptr(true) + resource.Properties.Status.OutputResources = append(resource.Properties.Status.OutputResources, deployed) + + return nil +} + +// Delete implements the processors.Processor interface for DaprBinding resources. If the resource is being +// provisioned manually, it deletes the Dapr component in Kubernetes. +func (p *Processor) Delete(ctx context.Context, resource *datamodel.DaprBinding, options processors.Options) error { + if resource.Properties.ResourceProvisioning != portableresources.ResourceProvisioningManual { + // If the resource was provisioned by recipe then we expect the recipe engine to delete the Dapr Component + // in Kubernetes. At this point we're done so we can just return. + return nil + } + + // DaprBinding resources may or may not be application scoped. + // Some Dapr Components can be specific to a single application, they would be application scoped and have + // resource.Properties.Application populated, while others could be shared across multiple applications and + // would not have resource.Properties.Application populated. + var err error + var applicationID resources.ID + if resource.Properties.Application != "" { + applicationID, err = resources.ParseResource(resource.Properties.Application) + if err != nil { + return err + } + } + + component := unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": dapr.DaprAPIVersion, + "kind": dapr.DaprKind, + "metadata": map[string]any{ + "namespace": options.RuntimeConfiguration.Kubernetes.Namespace, + "name": kubernetes.NormalizeDaprResourceName(resource.Properties.ComponentName), + "labels": kubernetes.MakeDescriptiveDaprLabels(applicationID.Name(), resource.Name, controller.DaprBindingsResourceType), + }, + }, + } + + err = p.Client.Delete(ctx, &component) + if err != nil { + return &processors.ResourceError{Inner: err} + } + + return nil +} diff --git a/pkg/daprrp/processors/bindings/processor_test.go b/pkg/daprrp/processors/bindings/processor_test.go new file mode 100644 index 0000000000..5e03562d84 --- /dev/null +++ b/pkg/daprrp/processors/bindings/processor_test.go @@ -0,0 +1,507 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package bindings + +import ( + "context" + "fmt" + "testing" + + v1 "github.com/radius-project/radius/pkg/armrpc/api/v1" + "github.com/radius-project/radius/pkg/daprrp/datamodel" + dapr_ctrl "github.com/radius-project/radius/pkg/daprrp/frontend/controller" + "github.com/radius-project/radius/pkg/kubernetes" + "github.com/radius-project/radius/pkg/portableresources" + "github.com/radius-project/radius/pkg/portableresources/processors" + "github.com/radius-project/radius/pkg/portableresources/renderers/dapr" + "github.com/radius-project/radius/pkg/recipes" + rpv1 "github.com/radius-project/radius/pkg/rp/v1" + "github.com/radius-project/radius/pkg/to" + "github.com/radius-project/radius/test/k8sutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/kubectl/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func Test_Process(t *testing.T) { + const externalResourceID1 = "/subscriptions/0000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc1" + const externalResourceID2 = "/subscriptions/0000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc2" + const kubernetesResource = "/planes/kubernetes/local/namespaces/test-namespace/providers/dapr.io/Component/test-component" + 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-binding" + const secretStoreComponentName = "test-dapr-secret-store" + + t.Run("success - recipe", func(t *testing.T) { + processor := Processor{ + Client: k8sutil.NewFakeKubeClient(scheme.Scheme), + } + + resource := &datamodel.DaprBinding{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: componentName, + }, + }, + Properties: datamodel.DaprBindingProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Application: appID, + }, + BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ + ComponentName: componentName, + }, + }, + } + + options := processors.Options{ + RuntimeConfiguration: recipes.RuntimeConfiguration{ + Kubernetes: &recipes.KubernetesRuntime{ + Namespace: "test-namespace", + }, + }, + RecipeOutput: &recipes.RecipeOutput{ + Resources: []string{ + externalResourceID1, + kubernetesResource, + }, + Values: map[string]any{}, // Component name will be computed for resource name. + Secrets: map[string]any{}, + }, + } + + 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.GetOutputResourcesFromRecipe(options.RecipeOutput) + 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") + + // No components created for a recipe + err = processor.Client.List(context.Background(), &components, + &client.ListOptions{ + Namespace: options.RuntimeConfiguration.Kubernetes.Namespace, + }, + ) + require.NoError(t, err) + require.Empty(t, components.Items) + }) + + t.Run("success - manual", func(t *testing.T) { + testset := []struct { + description string + properties *datamodel.DaprBindingProperties + generated *unstructured.Unstructured + }{ + { + description: "Raw values", + properties: &datamodel.DaprBindingProperties{ + 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: "bindings.azure.blobstorage", + Version: "v1", + }, + generated: &unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": dapr.DaprAPIVersion, + "kind": dapr.DaprKind, + "metadata": map[string]any{ + "namespace": "test-namespace", + "name": componentName, + "labels": kubernetes.MakeDescriptiveDaprLabels("test-app", "some-other-name", dapr_ctrl.DaprBindingsResourceType), + "resourceVersion": "1", + }, + "spec": map[string]any{ + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": []any{ + map[string]any{ + "name": "config", + "value": "extrasecure", + }, + }, + }, + }, + }, + }, + { + description: "With secret store", + properties: &datamodel.DaprBindingProperties{ + 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: "bindings.azure.blobstorage", + Version: "v1", + Auth: &rpv1.DaprComponentAuth{ + SecretStore: secretStoreComponentName, + }, + }, + generated: &unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": dapr.DaprAPIVersion, + "kind": dapr.DaprKind, + "metadata": map[string]any{ + "namespace": "test-namespace", + "name": componentName, + "labels": kubernetes.MakeDescriptiveDaprLabels("test-app", "some-other-name", dapr_ctrl.DaprBindingsResourceType), + "resourceVersion": "1", + }, + "spec": map[string]any{ + "type": "bindings.azure.blobstorage", + "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, + }, + }, + }, + }, + } + + 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.DaprBinding{ + 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) { + processor := Processor{ + Client: k8sutil.NewFakeKubeClient(scheme.Scheme, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}}), + } + + resource := &datamodel.DaprBinding{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: "some-other-name", + }, + }, + Properties: datamodel.DaprBindingProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Environment: envID, + }, + BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ + ComponentName: componentName, + }, + ResourceProvisioning: portableresources.ResourceProvisioningManual, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, + Type: "bindings.azure.blobstorage", + Version: "v1", + }, + } + + 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) + + generated := &unstructured.Unstructured{ + Object: map[string]any{ + "apiVersion": dapr.DaprAPIVersion, + "kind": dapr.DaprKind, + "metadata": map[string]any{ + "namespace": "test-namespace", + "name": componentName, + "labels": kubernetes.MakeDescriptiveDaprLabels("", "some-other-name", dapr_ctrl.DaprBindingsResourceType), + "resourceVersion": "1", + }, + "spec": map[string]any{ + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": []any{ + map[string]any{ + "name": "config", + "value": "extrasecure", + }, + }, + }, + }, + } + + 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) + }) + + t.Run("success - recipe with overrides", func(t *testing.T) { + processor := Processor{ + Client: k8sutil.NewFakeKubeClient(scheme.Scheme), + } + + resource := &datamodel.DaprBinding{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: "some-other-name", + }, + }, + Properties: datamodel.DaprBindingProperties{ + BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ + ComponentName: componentName, + }, + }, + } + + options := processors.Options{ + RuntimeConfiguration: recipes.RuntimeConfiguration{ + Kubernetes: &recipes.KubernetesRuntime{ + Namespace: "test-namespace", + }, + }, + RecipeOutput: &recipes.RecipeOutput{ + Resources: []string{ + externalResourceID2, + kubernetesResource, + }, + + // Values and secrets will be overridden by the resource. + Values: map[string]any{ + "componentName": "akskdf", + }, + Secrets: map[string]any{}, + }, + } + + 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 := []rpv1.OutputResource{} + + recipeOutputResources, err := processors.GetOutputResourcesFromRecipe(options.RecipeOutput) + require.NoError(t, err) + expectedOutputResources = append(expectedOutputResources, recipeOutputResources...) + + resourcesFieldOutputResources, err := processors.GetOutputResourcesFromResourcesField(resource.Properties.Resources) + require.NoError(t, err) + expectedOutputResources = append(expectedOutputResources, resourcesFieldOutputResources...) + + 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.Empty(t, components.Items) + }) + + t.Run("failure - duplicate component", func(t *testing.T) { + // Create a duplicate with the same component name. + existing, err := dapr.ConstructDaprGeneric( + dapr.DaprGeneric{ + Type: to.Ptr("bindings.azure.blobstorage"), + Version: to.Ptr("v1"), + Metadata: map[string]*rpv1.DaprComponentMetadataValue{}, + }, + "test-namespace", + componentName, + "test-app", + "some-other-other-name", + dapr_ctrl.DaprBindingsResourceType) + require.NoError(t, err) + + processor := Processor{ + Client: k8sutil.NewFakeKubeClient(scheme.Scheme, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "test-namespace"}}, &existing), + } + + resource := &datamodel.DaprBinding{ + BaseResource: v1.BaseResource{ + TrackedResource: v1.TrackedResource{ + Name: "some-other-name", + }, + }, + Properties: datamodel.DaprBindingProperties{ + BasicResourceProperties: rpv1.BasicResourceProperties{ + Application: appID, + }, + BasicDaprResourceProperties: rpv1.BasicDaprResourceProperties{ + ComponentName: componentName, + }, + ResourceProvisioning: portableresources.ResourceProvisioningManual, + Metadata: map[string]*rpv1.DaprComponentMetadataValue{ + "config": { + Value: "extrasecure", + }, + }, + Resources: []*portableresources.ResourceReference{{ID: externalResourceID1}}, + Type: "bindings.azure.blobstorage", + Version: "v1", + }, + } + + options := processors.Options{ + RuntimeConfiguration: recipes.RuntimeConfiguration{ + Kubernetes: &recipes.KubernetesRuntime{ + Namespace: "test-namespace", + }, + }, + } + + err = processor.Process(context.Background(), resource, options) + require.Error(t, err) + assert.IsType(t, &processors.ValidationError{}, err) + assert.Equal(t, fmt.Sprintf("the Dapr component name '\"%s\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, Bindings etc.). Please select a new name and try again.", componentName), err.Error()) + }) +} diff --git a/pkg/daprrp/processors/configurationstores/processor_test.go b/pkg/daprrp/processors/configurationstores/processor_test.go index 87ee8bd8f2..0c8b57b26c 100644 --- a/pkg/daprrp/processors/configurationstores/processor_test.go +++ b/pkg/daprrp/processors/configurationstores/processor_test.go @@ -502,6 +502,6 @@ func Test_Process(t *testing.T) { err = processor.Process(context.Background(), resource, options) require.Error(t, err) assert.IsType(t, &processors.ValidationError{}, err) - assert.Equal(t, fmt.Sprintf("the Dapr component name '\"%s\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, etc.). Please select a new name and try again.", componentName), err.Error()) + assert.Equal(t, fmt.Sprintf("the Dapr component name '\"%s\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, Bindings etc.). Please select a new name and try again.", componentName), err.Error()) }) } diff --git a/pkg/daprrp/processors/pubsubbrokers/processor_test.go b/pkg/daprrp/processors/pubsubbrokers/processor_test.go index e9286c7005..cfa2a2e7ea 100644 --- a/pkg/daprrp/processors/pubsubbrokers/processor_test.go +++ b/pkg/daprrp/processors/pubsubbrokers/processor_test.go @@ -500,6 +500,6 @@ func Test_Process(t *testing.T) { err = processor.Process(context.Background(), resource, options) require.Error(t, err) assert.IsType(t, &processors.ValidationError{}, err) - assert.Equal(t, "the Dapr component name '\"test-dapr-pubsub-broker\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, etc.). Please select a new name and try again.", err.Error()) + assert.Equal(t, "the Dapr component name '\"test-dapr-pubsub-broker\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, Bindings etc.). Please select a new name and try again.", err.Error()) }) } diff --git a/pkg/daprrp/processors/secretstores/processor_test.go b/pkg/daprrp/processors/secretstores/processor_test.go index 5170130fea..55c1c368e9 100644 --- a/pkg/daprrp/processors/secretstores/processor_test.go +++ b/pkg/daprrp/processors/secretstores/processor_test.go @@ -398,6 +398,6 @@ func Test_Process(t *testing.T) { err = processor.Process(context.Background(), resource, options) require.Error(t, err) assert.IsType(t, &processors.ValidationError{}, err) - assert.Equal(t, "the Dapr component name '\"test-component\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, etc.). Please select a new name and try again.", err.Error()) + assert.Equal(t, "the Dapr component name '\"test-component\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, Bindings etc.). Please select a new name and try again.", err.Error()) }) } diff --git a/pkg/daprrp/processors/statestores/processor_test.go b/pkg/daprrp/processors/statestores/processor_test.go index 884330cf2a..1cd9990c72 100644 --- a/pkg/daprrp/processors/statestores/processor_test.go +++ b/pkg/daprrp/processors/statestores/processor_test.go @@ -490,6 +490,6 @@ func Test_Process(t *testing.T) { err = processor.Process(context.Background(), resource, options) require.Error(t, err) assert.IsType(t, &processors.ValidationError{}, err) - assert.Equal(t, "the Dapr component name '\"test-component\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, etc.). Please select a new name and try again.", err.Error()) + assert.Equal(t, "the Dapr component name '\"test-component\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, Bindings etc.). Please select a new name and try again.", err.Error()) }) } diff --git a/pkg/daprrp/setup/operations.go b/pkg/daprrp/setup/operations.go index 8560005d3e..101f86af5c 100644 --- a/pkg/daprrp/setup/operations.go +++ b/pkg/daprrp/setup/operations.go @@ -169,4 +169,34 @@ var operationList = []v1.Operation{ }, IsDataAction: false, }, + { + Name: "Applications.Dapr/bindings/read", + Display: &v1.OperationDisplayProperties{ + Provider: "Applications.Dapr", + Resource: "bindings", + Operation: "Get/List Dapr bindings", + Description: "Gets/Lists Dapr bindings resource(s).", + }, + IsDataAction: false, + }, + { + Name: "Applications.Dapr/bindings/write", + Display: &v1.OperationDisplayProperties{ + Provider: "Applications.Dapr", + Resource: "bindings", + Operation: "Create/Update Dapr bindings", + Description: "Creates or updates a Dapr bindings resource.", + }, + IsDataAction: false, + }, + { + Name: "Applications.Dapr/bindings/delete", + Display: &v1.OperationDisplayProperties{ + Provider: "Applications.Dapr", + Resource: "bindings", + Operation: "Delete Dapr bindings", + Description: "Deletes a Dapr bindings resource.", + }, + IsDataAction: false, + }, } diff --git a/pkg/daprrp/setup/setup.go b/pkg/daprrp/setup/setup.go index 4470884c83..e7ac9af860 100644 --- a/pkg/daprrp/setup/setup.go +++ b/pkg/daprrp/setup/setup.go @@ -27,6 +27,7 @@ import ( "github.com/radius-project/radius/pkg/recipes/controllerconfig" dapr_ctrl "github.com/radius-project/radius/pkg/daprrp/frontend/controller" + bindings_proc "github.com/radius-project/radius/pkg/daprrp/processors/bindings" configurationstores_proc "github.com/radius-project/radius/pkg/daprrp/processors/configurationstores" pubsub_proc "github.com/radius-project/radius/pkg/daprrp/processors/pubsubbrokers" secretstore_proc "github.com/radius-project/radius/pkg/daprrp/processors/secretstores" @@ -176,6 +177,39 @@ func SetupNamespace(recipeControllerConfig *controllerconfig.RecipeControllerCon }, }) + _ = ns.AddResource("bindings", &builder.ResourceOption[*datamodel.DaprBinding, datamodel.DaprBinding]{ + RequestConverter: converter.BindingDataModelFromVersioned, + ResponseConverter: converter.BindingDataModelToVersioned, + + Put: builder.Operation[datamodel.DaprBinding]{ + UpdateFilters: []apictrl.UpdateFilter[datamodel.DaprBinding]{ + rp_frontend.PrepareRadiusResource[*datamodel.DaprBinding], + }, + AsyncJobController: func(options asyncctrl.Options) (asyncctrl.Controller, error) { + return pr_ctrl.NewCreateOrUpdateResource[*datamodel.DaprBinding, datamodel.DaprBinding](options, &bindings_proc.Processor{Client: options.KubeClient}, recipeControllerConfig.Engine, recipeControllerConfig.ResourceClient, recipeControllerConfig.ConfigLoader) + }, + AsyncOperationTimeout: dapr_ctrl.AsyncCreateOrUpdateDaprBindingTimeout, + AsyncOperationRetryAfter: AsyncOperationRetryAfter, + }, + Patch: builder.Operation[datamodel.DaprBinding]{ + UpdateFilters: []apictrl.UpdateFilter[datamodel.DaprBinding]{ + rp_frontend.PrepareRadiusResource[*datamodel.DaprBinding], + }, + AsyncJobController: func(options asyncctrl.Options) (asyncctrl.Controller, error) { + return pr_ctrl.NewCreateOrUpdateResource[*datamodel.DaprBinding, datamodel.DaprBinding](options, &bindings_proc.Processor{Client: options.KubeClient}, recipeControllerConfig.Engine, recipeControllerConfig.ResourceClient, recipeControllerConfig.ConfigLoader) + }, + AsyncOperationTimeout: dapr_ctrl.AsyncCreateOrUpdateDaprBindingTimeout, + AsyncOperationRetryAfter: AsyncOperationRetryAfter, + }, + Delete: builder.Operation[datamodel.DaprBinding]{ + AsyncJobController: func(options asyncctrl.Options) (asyncctrl.Controller, error) { + return pr_ctrl.NewDeleteResource[*datamodel.DaprBinding, datamodel.DaprBinding](options, &bindings_proc.Processor{Client: options.KubeClient}, recipeControllerConfig.Engine, recipeControllerConfig.ConfigLoader) + }, + AsyncOperationTimeout: dapr_ctrl.AsyncDeleteDaprBindingTimeout, + AsyncOperationRetryAfter: AsyncOperationRetryAfter, + }, + }) + // Optional ns.SetAvailableOperations(operationList) diff --git a/pkg/daprrp/setup/setup_test.go b/pkg/daprrp/setup/setup_test.go index b55cd010d6..9b80b4de68 100644 --- a/pkg/daprrp/setup/setup_test.go +++ b/pkg/daprrp/setup/setup_test.go @@ -134,6 +134,31 @@ var handlerTests = []rpctest.HandlerTestSpec{ Path: "/resourcegroups/testrg/providers/applications.dapr/configurationstores/configstore", Method: http.MethodDelete, }, + { + OperationType: v1.OperationType{Type: dapr_ctrl.DaprBindingsResourceType, Method: v1.OperationPlaneScopeList}, + Path: "/providers/applications.dapr/bindings", + Method: http.MethodGet, + }, { + OperationType: v1.OperationType{Type: dapr_ctrl.DaprBindingsResourceType, Method: v1.OperationList}, + Path: "/resourcegroups/testrg/providers/applications.dapr/bindings", + Method: http.MethodGet, + }, { + OperationType: v1.OperationType{Type: dapr_ctrl.DaprBindingsResourceType, Method: v1.OperationGet}, + Path: "/resourcegroups/testrg/providers/applications.dapr/bindings/configstore", + Method: http.MethodGet, + }, { + OperationType: v1.OperationType{Type: dapr_ctrl.DaprBindingsResourceType, Method: v1.OperationPut}, + Path: "/resourcegroups/testrg/providers/applications.dapr/bindings/configstore", + Method: http.MethodPut, + }, { + OperationType: v1.OperationType{Type: dapr_ctrl.DaprBindingsResourceType, Method: v1.OperationPatch}, + Path: "/resourcegroups/testrg/providers/applications.dapr/bindings/configstore", + Method: http.MethodPatch, + }, { + OperationType: v1.OperationType{Type: dapr_ctrl.DaprBindingsResourceType, Method: v1.OperationDelete}, + Path: "/resourcegroups/testrg/providers/applications.dapr/bindings/configstore", + Method: http.MethodDelete, + }, } func TestRouter(t *testing.T) { diff --git a/pkg/portableresources/handlers/util.go b/pkg/portableresources/handlers/util.go index 099155c500..0e59263a6d 100644 --- a/pkg/portableresources/handlers/util.go +++ b/pkg/portableresources/handlers/util.go @@ -29,7 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" ) -const daprConflictFmt = "the Dapr component name '%q' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, etc.). Please select a new name and try again." +const daprConflictFmt = "the Dapr component name '%q' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, Bindings etc.). Please select a new name and try again." // CheckDaprResourceNameUniqueness checks if the resource name is unique in the namespace. If the resource name is not unique, it returns an error. // diff --git a/pkg/portableresources/renderers/dapr/generic.go b/pkg/portableresources/renderers/dapr/generic.go index 7fbb070d0c..d996da759d 100644 --- a/pkg/portableresources/renderers/dapr/generic.go +++ b/pkg/portableresources/renderers/dapr/generic.go @@ -108,5 +108,6 @@ func ConstructDaprGeneric(daprGeneric DaprGeneric, namespace string, componentNa "secretStore": daprGeneric.Auth.SecretStore, } } + return item, nil } diff --git a/pkg/rp/portableresources/portableresources.go b/pkg/rp/portableresources/portableresources.go index a67b3a0c78..d7b9368e93 100644 --- a/pkg/rp/portableresources/portableresources.go +++ b/pkg/rp/portableresources/portableresources.go @@ -35,6 +35,7 @@ func IsValidPortableResourceType(resourceType string) bool { dapr_ctrl.DaprSecretStoresResourceType, dapr_ctrl.DaprStateStoresResourceType, dapr_ctrl.DaprConfigurationStoresResourceType, + dapr_ctrl.DaprBindingsResourceType, msg_ctrl.RabbitMQQueuesResourceType, ds_ctrl.MongoDatabasesResourceType, ds_ctrl.RedisCachesResourceType, @@ -58,6 +59,7 @@ func GetValidPortableResourceTypes() []string { dapr_ctrl.DaprSecretStoresResourceType, dapr_ctrl.DaprStateStoresResourceType, dapr_ctrl.DaprConfigurationStoresResourceType, + dapr_ctrl.DaprBindingsResourceType, msg_ctrl.RabbitMQQueuesResourceType, ds_ctrl.MongoDatabasesResourceType, ds_ctrl.RedisCachesResourceType, diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_CreateOrUpdate.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_CreateOrUpdate.json new file mode 100644 index 0000000000..d871bc2ded --- /dev/null +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_CreateOrUpdate.json @@ -0,0 +1,77 @@ +{ + "operationId": "Bindings_CreateOrUpdate", + "title": "Create or update a Binding resource", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "BindingName": "binding0", + "api-version": "2023-10-01-preview", + "BindingParameters": { + "location": "West US", + "properties": { + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + }, + "responses": { + "200": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + }, + "201": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Accepted", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + } + } +} diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_CreateOrUpdateWithRecipe.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_CreateOrUpdateWithRecipe.json new file mode 100644 index 0000000000..84db744aa7 --- /dev/null +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_CreateOrUpdateWithRecipe.json @@ -0,0 +1,66 @@ +{ + "operationId": "Bindings_CreateOrUpdate", + "title": "Create or update a binding resource with recipe", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "bindingName": "binding0", + "api-version": "2023-10-01-preview", + "bindingParameters": { + "location": "West US", + "properties": { + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "recipe": { + "name": "binding-test", + "parameters": { + "port": 6081 + } + } + } + } + }, + "responses": { + "200": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "recipe", + "type": "bindings.azure.blobstorage", + "recipe": { + "name": "binding-test", + "parameters": { + "port": 6081 + } + } + } + } + }, + "201": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Accepted", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "recipe", + "type": "bindings.azure.blobstorage", + "recipe": { + "name": "binding-test", + "parameters": { + "port": 6081 + } + } + } + } + } + } +} diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_Delete.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_Delete.json new file mode 100644 index 0000000000..7eaa7c4b97 --- /dev/null +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_Delete.json @@ -0,0 +1,14 @@ +{ + "operationId": "Bindings_Delete", + "title": "Delete a Binding resource", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup/resourceGroups/testGroup", + "bindingName": "binding0", + "api-version": "2023-10-01-preview" + }, + "responses": { + "200": {}, + "202": {}, + "204": {} + } +} diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_Get.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_Get.json new file mode 100644 index 0000000000..633b166deb --- /dev/null +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_Get.json @@ -0,0 +1,35 @@ +{ + "operationId": "Bindings_Get", + "title": "Get a Binding resource", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "api-version": "2023-10-01-preview", + "bindingName": "binding0" + }, + "responses": { + "200": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn\n" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + } + } +} diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_List.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_List.json new file mode 100644 index 0000000000..99ffc773a2 --- /dev/null +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_List.json @@ -0,0 +1,73 @@ +{ + "operationId": "Bindings_ListByScope", + "title": "List a Binding resource by resource group", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "api-version": "2023-10-01-preview" + }, + "responses": { + "200": { + "body": { + "value": [ + { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.AppConfiguration/bindings/testbinding" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + }, + { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding1", + "name": "binding1", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "type": "bindings.http", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + }, + { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding2", + "name": "binding2", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "recipe": { + "name": "binding-test", + "parameters": { + "port": 6081 + } + } + } + } + ], + "nextLink": "https://serviceRoot/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings?api-version=2023-10-01-preview&$skipToken=X'12345'" + } + } + } +} diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_ListByRootScope.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_ListByRootScope.json new file mode 100644 index 0000000000..e24be4e1a9 --- /dev/null +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_ListByRootScope.json @@ -0,0 +1,56 @@ +{ + "operationId": "Bindings_ListByScope", + "title": "List a Bindings resource by rootScope", + "parameters": { + "rootScope": "/planes/radius/local", + "api-version": "2023-10-01-preview" + }, + "responses": { + "200": { + "body": { + "value": [ + { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + }, + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup1/providers/Applications.Dapr/bindings/binding1", + "name": "binding1", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "type": "bindings.http", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + ], + "nextLink": "https://serviceRoot/planes/radius/local/providers/Applications.Dapr/bindings?api-version=2023-10-01-preview&$skipToken=X'12345'" + } + } + } +} diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_Update.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_Update.json new file mode 100644 index 0000000000..62db118592 --- /dev/null +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/Bindings_Update.json @@ -0,0 +1,77 @@ +{ + "operationId": "Bindings_Update", + "title": "Update a Binding resource", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "bindingName": "binding0", + "api-version": "2023-10-01-preview", + "bindingParameters": { + "location": "West US", + "properties": { + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + }, + "responses": { + "200": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + }, + "201": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Accepted", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + } + } +} diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/ConfigurationStores_Delete.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/ConfigurationStores_Delete.json index 40c2172e4d..b1beee8f3e 100644 --- a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/ConfigurationStores_Delete.json +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/ConfigurationStores_Delete.json @@ -3,7 +3,7 @@ "title": "Delete a ConfigurationStore resource", "parameters": { "rootScope": "/planes/radius/local/resourceGroups/testGroup/resourceGroups/testGroup", - "pubSubBrokerName": "configstore0", + "configurationStoreName": "configstore0", "api-version": "2023-10-01-preview" }, "responses": { diff --git a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/ConfigurationStores_Get.json b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/ConfigurationStores_Get.json index 36521872b7..e325c43bf8 100644 --- a/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/ConfigurationStores_Get.json +++ b/swagger/specification/applications/resource-manager/Applications.Dapr/preview/2023-10-01-preview/examples/ConfigurationStores_Get.json @@ -1,10 +1,10 @@ { "operationId": "ConfigurationStores_Get", - "title": "Get a PubSubBroker resource", + "title": "Get a ConfigurationStore resource", "parameters": { "rootScope": "/planes/radius/local/resourceGroups/testGroup", "api-version": "2023-10-01-preview", - "pubSubBrokerName": "configstore0" + "configurationStoreName": "configstore0" }, "responses": { "200": { 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 ab4a6ff8b4..03231195a8 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 @@ -53,9 +53,306 @@ }, { "name": "ConfigurationStores" + }, + { + "name": "Bindings" } ], "paths": { + "/{rootScope}/providers/Applications.Dapr/bindings": { + "get": { + "operationId": "Bindings_ListByScope", + "tags": [ + "Bindings" + ], + "description": "List DaprBindingResource resources by Scope", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/RootScopeParameter" + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "#/definitions/DaprBindingResourceListResult" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "List a Binding resource by resource group": { + "$ref": "./examples/Bindings_List.json" + }, + "List a Bindings resource by rootScope": { + "$ref": "./examples/Bindings_ListByRootScope.json" + } + }, + "x-ms-pageable": { + "nextLinkName": "nextLink" + } + } + }, + "/{rootScope}/providers/Applications.Dapr/bindings/{bindingName}": { + "get": { + "operationId": "Bindings_Get", + "tags": [ + "Bindings" + ], + "description": "Get a DaprBindingResource", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/RootScopeParameter" + }, + { + "name": "bindingName", + "in": "path", + "description": "Binding name", + "required": true, + "type": "string", + "maxLength": 63, + "pattern": "^[A-Za-z]([-A-Za-z0-9]*[A-Za-z0-9])?$" + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "#/definitions/DaprBindingResource" + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Get a Binding resource": { + "$ref": "./examples/Bindings_Get.json" + } + } + }, + "put": { + "operationId": "Bindings_CreateOrUpdate", + "tags": [ + "Bindings" + ], + "description": "Create a DaprBindingResource", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/RootScopeParameter" + }, + { + "name": "bindingName", + "in": "path", + "description": "Binding name", + "required": true, + "type": "string", + "maxLength": 63, + "pattern": "^[A-Za-z]([-A-Za-z0-9]*[A-Za-z0-9])?$" + }, + { + "name": "resource", + "in": "body", + "description": "Resource create parameters.", + "required": true, + "schema": { + "$ref": "#/definitions/DaprBindingResource" + } + } + ], + "responses": { + "200": { + "description": "Resource 'DaprBindingResource' update operation succeeded", + "schema": { + "$ref": "#/definitions/DaprBindingResource" + } + }, + "201": { + "description": "Resource 'DaprBindingResource' create operation succeeded", + "schema": { + "$ref": "#/definitions/DaprBindingResource" + }, + "headers": { + "Azure-AsyncOperation": { + "type": "string", + "description": "A link to the status monitor" + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Create or update a Binding resource": { + "$ref": "./examples/Bindings_CreateOrUpdate.json" + }, + "Create or update a binding resource with recipe": { + "$ref": "./examples/Bindings_CreateOrUpdateWithRecipe.json" + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "azure-async-operation" + }, + "x-ms-long-running-operation": true + }, + "patch": { + "operationId": "Bindings_Update", + "tags": [ + "Bindings" + ], + "description": "Update a DaprBindingResource", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/RootScopeParameter" + }, + { + "name": "bindingName", + "in": "path", + "description": "Binding name", + "required": true, + "type": "string", + "maxLength": 63, + "pattern": "^[A-Za-z]([-A-Za-z0-9]*[A-Za-z0-9])?$" + }, + { + "name": "properties", + "in": "body", + "description": "The resource properties to be updated.", + "required": true, + "schema": { + "$ref": "#/definitions/DaprBindingResourceUpdate" + } + } + ], + "responses": { + "200": { + "description": "Azure operation completed successfully.", + "schema": { + "$ref": "#/definitions/DaprBindingResource" + } + }, + "202": { + "description": "Resource update request accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Update a Binding resource": { + "$ref": "./examples/Bindings_Update.json" + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true + }, + "delete": { + "operationId": "Bindings_Delete", + "tags": [ + "Bindings" + ], + "description": "Delete a DaprBindingResource", + "parameters": [ + { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/parameters/ApiVersionParameter" + }, + { + "$ref": "#/parameters/RootScopeParameter" + }, + { + "name": "bindingName", + "in": "path", + "description": "Binding name", + "required": true, + "type": "string", + "maxLength": 63, + "pattern": "^[A-Za-z]([-A-Za-z0-9]*[A-Za-z0-9])?$" + } + ], + "responses": { + "200": { + "description": "Resource deleted successfully." + }, + "202": { + "description": "Resource deletion accepted.", + "headers": { + "Location": { + "type": "string", + "description": "The Location header contains the URL where the status of the long running operation can be checked." + }, + "Retry-After": { + "type": "integer", + "format": "int32", + "description": "The Retry-After header can indicate how long the client should wait before polling the operation status." + } + } + }, + "204": { + "description": "Resource does not exist." + }, + "default": { + "description": "An unexpected error response.", + "schema": { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/ErrorResponse" + } + } + }, + "x-ms-examples": { + "Delete a Binding resource": { + "$ref": "./examples/Bindings_Delete.json" + } + }, + "x-ms-long-running-operation-options": { + "final-state-via": "location" + }, + "x-ms-long-running-operation": true + } + }, "/{rootScope}/providers/Applications.Dapr/configurationStores": { "get": { "operationId": "ConfigurationStores_ListByScope", @@ -137,7 +434,7 @@ } }, "x-ms-examples": { - "Get a PubSubBroker resource": { + "Get a ConfigurationStore resource": { "$ref": "./examples/ConfigurationStores_Get.json" } } @@ -1268,6 +1565,125 @@ } ] }, + "DaprBindingProperties": { + "type": "object", + "description": "Dapr binding portable resource properties", + "properties": { + "environment": { + "type": "string", + "description": "Fully qualified resource ID for the environment that the portable resource is linked to" + }, + "application": { + "type": "string", + "description": "Fully qualified resource ID for the application that the portable resource is consumed by (if applicable)" + }, + "provisioningState": { + "$ref": "#/definitions/ProvisioningState", + "description": "The status of the asynchronous operation.", + "readOnly": true + }, + "status": { + "$ref": "#/definitions/ResourceStatus", + "description": "Status of a resource.", + "readOnly": true + }, + "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" + }, + "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 binding", + "items": { + "$ref": "#/definitions/ResourceReference" + } + }, + "recipe": { + "$ref": "#/definitions/Recipe", + "description": "The recipe used to automatically deploy underlying infrastructure for the resource" + }, + "resourceProvisioning": { + "$ref": "#/definitions/ResourceProvisioning", + "description": "Specifies how the underlying service/resource is provisioned and managed." + } + }, + "required": [ + "environment" + ] + }, + "DaprBindingResource": { + "type": "object", + "description": "Dapr binding portable resource", + "properties": { + "properties": { + "$ref": "#/definitions/DaprBindingProperties", + "description": "The resource-specific properties for this resource.", + "x-ms-client-flatten": true, + "x-ms-mutability": [ + "read", + "create" + ] + } + }, + "required": [ + "properties" + ], + "allOf": [ + { + "$ref": "../../../../../common-types/resource-management/v3/types.json#/definitions/TrackedResource" + } + ] + }, + "DaprBindingResourceListResult": { + "type": "object", + "description": "The response of a DaprBindingResource list operation.", + "properties": { + "value": { + "type": "array", + "description": "The DaprBindingResource items on this page", + "items": { + "$ref": "#/definitions/DaprBindingResource" + } + }, + "nextLink": { + "type": "string", + "format": "uri", + "description": "The link to the next page of items" + } + }, + "required": [ + "value" + ] + }, + "DaprBindingResourceUpdate": { + "type": "object", + "description": "Dapr binding portable resource", + "allOf": [ + { + "$ref": "#/definitions/Azure.ResourceManager.CommonTypes.TrackedResourceUpdate" + } + ] + }, "DaprConfigurationStoreProperties": { "type": "object", "description": "Dapr configuration store portable resource properties", diff --git a/test/functional-portable/daprrp/noncloud/resources/dapr_binding_test.go b/test/functional-portable/daprrp/noncloud/resources/dapr_binding_test.go new file mode 100644 index 0000000000..6b40395037 --- /dev/null +++ b/test/functional-portable/daprrp/noncloud/resources/dapr_binding_test.go @@ -0,0 +1,158 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package resource_test + +import ( + "context" + "fmt" + "testing" + + "github.com/radius-project/radius/test/rp" + "github.com/radius-project/radius/test/step" + "github.com/radius-project/radius/test/testutil" + "github.com/radius-project/radius/test/validation" +) + +func Test_Binding_Manual_Secret(t *testing.T) { + template := "testdata/daprrp-resources-binding-manual-secret.bicep" + name := "dbd-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-dbd", name), + Type: validation.DaprBindingsResource, + 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-dbd", 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/bindings", fmt.Sprintf("%s-dbd", name), appNamespace) + verifyDaprComponentsDeleted(ctx, t, test, "Applications.Dapr/secretStores", fmt.Sprintf("%s-scs", name), appNamespace) + + } + + test.Test(t) +} + +func Test_Binding_Manual(t *testing.T) { + template := "testdata/daprrp-resources-binding-manual.bicep" + name := "dbd-manual" + appNamespace := fmt.Sprintf("default-%s", name) + test := rp.NewRPTest(t, name, []rp.TestStep{ + { + Executor: step.NewDeployExecutor( + template, + testutil.GetMagpieImage(), + fmt.Sprintf("namespace=%s", appNamespace), + fmt.Sprintf("baseName=%s", name), + ), + 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-dbd", name), + Type: validation.DaprBindingsResource, + 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-dbd", name)). + ValidateLabels(false), + }, + }, + }, + }, + }) + + test.RequiredFeatures = []rp.RequiredFeature{rp.FeatureDapr} + + test.PostDeleteVerify = func(ctx context.Context, t *testing.T, test rp.RPTest) { + verifyDaprComponentsDeleted(ctx, t, test, "Applications.Dapr/bindings", fmt.Sprintf("%s-dbd", name), appNamespace) + } + + test.Test(t) +} diff --git a/test/functional-portable/daprrp/noncloud/resources/dapr_component_name_conflict_test.go b/test/functional-portable/daprrp/noncloud/resources/dapr_component_name_conflict_test.go index 5ef4f852cd..3a88dd8be4 100644 --- a/test/functional-portable/daprrp/noncloud/resources/dapr_component_name_conflict_test.go +++ b/test/functional-portable/daprrp/noncloud/resources/dapr_component_name_conflict_test.go @@ -34,7 +34,7 @@ func Test_DaprComponentNameConflict(t *testing.T) { Details: []step.DeploymentErrorDetail{ { Code: v1.CodeInternal, - MessageContains: "the Dapr component name '\"dapr-component\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, etc.). Please select a new name and try again.", + MessageContains: "the Dapr component name '\"dapr-component\"' is already in use by another resource. Dapr component and resource names must be unique across all Dapr types (e.g., StateStores, PubSubBrokers, SecretStores, ConfigurationStores, Bindings etc.). Please select a new name and try again.", }, }, }) diff --git a/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-binding-manual-secret.bicep b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-binding-manual-secret.bicep new file mode 100644 index 0000000000..52bdbae03d --- /dev/null +++ b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-binding-manual-secret.bicep @@ -0,0 +1,98 @@ +extension radius + +param magpieimage string +param environment string +param namespace string = 'default' +param baseName string = 'dbd-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: { + daprbinding: { + source: binding.id + } + } + container: { + image: magpieimage + readinessProbe: { + kind: 'httpGet' + containerPort: 3000 + path: '/healthz' + } + } + extensions: [ + { + kind: 'daprSidecar' + appId: 'dbd-manual-secret-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 binding 'Applications.Dapr/bindings@2023-10-01-preview' = { + name: '${baseName}-dbd' + properties: { + application: app.id + environment: environment + resourceProvisioning: 'manual' + type: 'bindings.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-binding-manual.bicep b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-binding-manual.bicep new file mode 100644 index 0000000000..81306837f7 --- /dev/null +++ b/test/functional-portable/daprrp/noncloud/resources/testdata/daprrp-resources-binding-manual.bicep @@ -0,0 +1,70 @@ +extension radius + +param magpieimage string +param environment string +param namespace string = 'default' +param baseName string = 'dbd-manual' + +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: { + daprbinding: { + source: binding.id + } + } + container: { + image: magpieimage + readinessProbe: { + kind: 'httpGet' + containerPort: 3000 + path: '/healthz' + } + } + extensions: [ + { + kind: 'daprSidecar' + appId: 'dbd-manual-ctnr' + appPort: 3000 + } + ] + } +} + + +module redis '../../../../../../test/testrecipes/modules/redis-selfhost.bicep' = { + name: '${baseName}-redis-deployment' + params: { + name: '${baseName}-redis' + namespace: namespace + application: app.name + } +} + + +resource binding 'Applications.Dapr/bindings@2023-10-01-preview' = { + name: '${baseName}-dbd' + properties: { + application: app.id + environment: environment + resourceProvisioning: 'manual' + type: 'bindings.redis' + metadata: { + redisHost: { + value: '${redis.outputs.host}:${redis.outputs.port}' + } + redisPassword: { + value: '' + } + } + version: 'v1' + } +} diff --git a/test/magpiego/bindings/daprbinding.go b/test/magpiego/bindings/daprbinding.go new file mode 100644 index 0000000000..f00130a496 --- /dev/null +++ b/test/magpiego/bindings/daprbinding.go @@ -0,0 +1,42 @@ +package bindings + +import ( + "context" + "log" + + dapr "github.com/dapr/go-sdk/client" + "os" +) + +// DaprBindingBinding checks if the environment parameter COMPONENTNAME is set and if so, creates a Dapr client and +// retrieves a sample key from the Dapr bound store +// +// Use this with a values like: +// - CONNECTION_DAPRBINDING_COMPONENTNAME +// - DAPR_GRPC_PORT +func DaprBindingBinding(envParams map[string]string) BindingStatus { + // From https://docs.dapr.io/getting-started/quickstarts/configuration-quickstart/ + componentName := envParams["COMPONENTNAME"] + if componentName == "" { + log.Println("COMPONENTNAME is required") + return BindingStatus{false, "COMPONENTNAME is required"} + } + client, err := dapr.NewClientWithPort(os.Getenv("DAPR_GRPC_PORT")) + if err != nil { + log.Println("failed to create Dapr client - ", err.Error()) + return BindingStatus{false, "Failed to retrieve value from binding"} + } + ctx := context.Background() + _, err = client.InvokeBinding(ctx, &dapr.InvokeBindingRequest{ + Name: componentName, + Operation: "get", + Metadata: map[string]string{ + "key": "test-binding", + }, + }) + if err != nil { + log.Println("failed to get Dapr binding item - ", componentName, " error - ", err.Error()) + } + defer client.Close() + return BindingStatus{true, "Binding value retrieved"} +} diff --git a/test/magpiego/server.go b/test/magpiego/server.go index 064c0262f0..ce12d0583b 100644 --- a/test/magpiego/server.go +++ b/test/magpiego/server.go @@ -31,6 +31,7 @@ var Providers = map[string]bindings.BindingProvider{ "DAPRHTTP": bindings.DaprHttpBinding, "STORAGE": bindings.StorageBinding, "DAPRCONFIGURATIONSTORE": bindings.DaprConfigurationStoreBinding, + "DAPRBINDING": bindings.DaprBindingBinding, } func startHTTPServer() error { diff --git a/test/validation/shared.go b/test/validation/shared.go index 70206effd0..63f80900c2 100644 --- a/test/validation/shared.go +++ b/test/validation/shared.go @@ -47,6 +47,7 @@ const ( DaprSecretStoresResource = "applications.dapr/secretStores" DaprStateStoresResource = "applications.dapr/stateStores" DaprConfigurationStoresResource = "applications.dapr/configurationStores" + DaprBindingsResource = "applications.dapr/bindings" MongoDatabasesResource = "applications.datastores/mongoDatabases" RedisCachesResource = "applications.datastores/redisCaches" SQLDatabasesResource = "applications.datastores/sqlDatabases" diff --git a/typespec/Applications.Dapr/bindings.tsp b/typespec/Applications.Dapr/bindings.tsp new file mode 100644 index 0000000000..5eeb04bef4 --- /dev/null +++ b/typespec/Applications.Dapr/bindings.tsp @@ -0,0 +1,90 @@ +/* +Copyright 2023 The Radius Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import "@typespec/rest"; +import "@typespec/versioning"; +import "@typespec/openapi"; +import "@azure-tools/typespec-autorest"; +import "@azure-tools/typespec-azure-core"; +import "@azure-tools/typespec-azure-resource-manager"; +import "@azure-tools/typespec-providerhub"; + +import "../radius/v1/ucprootscope.tsp"; +import "../radius/v1/resources.tsp"; +import "./common.tsp"; +import "../radius/v1/trackedresource.tsp"; + +using TypeSpec.Http; +using TypeSpec.Rest; +using TypeSpec.Versioning; +using Autorest; +using Azure.Core; +using Azure.ResourceManager; +using OpenAPI; + +namespace Applications.Dapr; + +@doc("Dapr binding portable resource") +model DaprBindingResource + is TrackedResourceRequired { + @doc("Binding name") + @key("bindingName") + @path + @segment("bindings") + name: ResourceNameString; +} + +@doc("Dapr binding portable resource properties") +model DaprBindingProperties { + ...EnvironmentScopedResource; + ...DaprResourceProperties; + + @doc("A collection of references to resources associated with the binding") + resources?: ResourceReference[]; + + ...RecipeBaseProperties; +} + +@armResourceOperations +interface Bindings { + get is ArmResourceRead< + DaprBindingResource, + UCPBaseParameters + >; + + createOrUpdate is ArmResourceCreateOrReplaceAsync< + DaprBindingResource, + UCPBaseParameters + >; + + update is ArmResourcePatchAsync< + DaprBindingResource, + DaprBindingProperties, + UCPBaseParameters + >; + + delete is ArmResourceDeleteAsync< + DaprBindingResource, + UCPBaseParameters + >; + + listByScope is ArmResourceListByParent< + DaprBindingResource, + UCPBaseParameters, + "Scope", + "Scope" + >; +} diff --git a/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_CreateOrUpdate.json b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_CreateOrUpdate.json new file mode 100644 index 0000000000..d871bc2ded --- /dev/null +++ b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_CreateOrUpdate.json @@ -0,0 +1,77 @@ +{ + "operationId": "Bindings_CreateOrUpdate", + "title": "Create or update a Binding resource", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "BindingName": "binding0", + "api-version": "2023-10-01-preview", + "BindingParameters": { + "location": "West US", + "properties": { + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + }, + "responses": { + "200": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + }, + "201": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Accepted", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + } + } +} diff --git a/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_CreateOrUpdateWithRecipe.json b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_CreateOrUpdateWithRecipe.json new file mode 100644 index 0000000000..84db744aa7 --- /dev/null +++ b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_CreateOrUpdateWithRecipe.json @@ -0,0 +1,66 @@ +{ + "operationId": "Bindings_CreateOrUpdate", + "title": "Create or update a binding resource with recipe", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "bindingName": "binding0", + "api-version": "2023-10-01-preview", + "bindingParameters": { + "location": "West US", + "properties": { + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "recipe": { + "name": "binding-test", + "parameters": { + "port": 6081 + } + } + } + } + }, + "responses": { + "200": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "recipe", + "type": "bindings.azure.blobstorage", + "recipe": { + "name": "binding-test", + "parameters": { + "port": 6081 + } + } + } + } + }, + "201": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Accepted", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "recipe", + "type": "bindings.azure.blobstorage", + "recipe": { + "name": "binding-test", + "parameters": { + "port": 6081 + } + } + } + } + } + } +} diff --git a/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_Delete.json b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_Delete.json new file mode 100644 index 0000000000..7eaa7c4b97 --- /dev/null +++ b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_Delete.json @@ -0,0 +1,14 @@ +{ + "operationId": "Bindings_Delete", + "title": "Delete a Binding resource", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup/resourceGroups/testGroup", + "bindingName": "binding0", + "api-version": "2023-10-01-preview" + }, + "responses": { + "200": {}, + "202": {}, + "204": {} + } +} diff --git a/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_Get.json b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_Get.json new file mode 100644 index 0000000000..633b166deb --- /dev/null +++ b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_Get.json @@ -0,0 +1,35 @@ +{ + "operationId": "Bindings_Get", + "title": "Get a Binding resource", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "api-version": "2023-10-01-preview", + "bindingName": "binding0" + }, + "responses": { + "200": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn\n" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + } + } +} diff --git a/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_List.json b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_List.json new file mode 100644 index 0000000000..99ffc773a2 --- /dev/null +++ b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_List.json @@ -0,0 +1,73 @@ +{ + "operationId": "Bindings_ListByScope", + "title": "List a Binding resource by resource group", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "api-version": "2023-10-01-preview" + }, + "responses": { + "200": { + "body": { + "value": [ + { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.AppConfiguration/bindings/testbinding" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + }, + { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding1", + "name": "binding1", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "type": "bindings.http", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + }, + { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding2", + "name": "binding2", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "recipe": { + "name": "binding-test", + "parameters": { + "port": 6081 + } + } + } + } + ], + "nextLink": "https://serviceRoot/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings?api-version=2023-10-01-preview&$skipToken=X'12345'" + } + } + } +} diff --git a/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_ListByRootScope.json b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_ListByRootScope.json new file mode 100644 index 0000000000..e24be4e1a9 --- /dev/null +++ b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_ListByRootScope.json @@ -0,0 +1,56 @@ +{ + "operationId": "Bindings_ListByScope", + "title": "List a Bindings resource by rootScope", + "parameters": { + "rootScope": "/planes/radius/local", + "api-version": "2023-10-01-preview" + }, + "responses": { + "200": { + "body": { + "value": [ + { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + }, + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup1/providers/Applications.Dapr/bindings/binding1", + "name": "binding1", + "type": "Applications.Dapr/bindings", + "location": "global", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "type": "bindings.http", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + ], + "nextLink": "https://serviceRoot/planes/radius/local/providers/Applications.Dapr/bindings?api-version=2023-10-01-preview&$skipToken=X'12345'" + } + } + } +} diff --git a/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_Update.json b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_Update.json new file mode 100644 index 0000000000..62db118592 --- /dev/null +++ b/typespec/Applications.Dapr/examples/2023-10-01-preview/Bindings_Update.json @@ -0,0 +1,77 @@ +{ + "operationId": "Bindings_Update", + "title": "Update a Binding resource", + "parameters": { + "rootScope": "/planes/radius/local/resourceGroups/testGroup", + "bindingName": "binding0", + "api-version": "2023-10-01-preview", + "bindingParameters": { + "location": "West US", + "properties": { + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + }, + "responses": { + "200": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Succeeded", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + }, + "201": { + "body": { + "id": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Dapr/bindings/binding0", + "name": "binding0", + "type": "Applications.Dapr/bindings", + "location": "West US", + "properties": { + "provisioningState": "Accepted", + "application": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/applications/testApplication", + "environment": "/planes/radius/local/resourceGroups/testGroup/providers/Applications.Core/environments/env0", + "resourceProvisioning": "manual", + "resources": [ + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/testGroup/providers/Microsoft.Storage/storageAccounts/testAcc/blobServices/containers/testCtn" + } + ], + "type": "bindings.azure.blobstorage", + "version": "v1", + "metadata": { + "foo": "bar" + } + } + } + } + } +} diff --git a/typespec/Applications.Dapr/examples/2023-10-01-preview/ConfigurationStores_Delete.json b/typespec/Applications.Dapr/examples/2023-10-01-preview/ConfigurationStores_Delete.json index 40c2172e4d..b1beee8f3e 100644 --- a/typespec/Applications.Dapr/examples/2023-10-01-preview/ConfigurationStores_Delete.json +++ b/typespec/Applications.Dapr/examples/2023-10-01-preview/ConfigurationStores_Delete.json @@ -3,7 +3,7 @@ "title": "Delete a ConfigurationStore resource", "parameters": { "rootScope": "/planes/radius/local/resourceGroups/testGroup/resourceGroups/testGroup", - "pubSubBrokerName": "configstore0", + "configurationStoreName": "configstore0", "api-version": "2023-10-01-preview" }, "responses": { diff --git a/typespec/Applications.Dapr/examples/2023-10-01-preview/ConfigurationStores_Get.json b/typespec/Applications.Dapr/examples/2023-10-01-preview/ConfigurationStores_Get.json index 36521872b7..e325c43bf8 100644 --- a/typespec/Applications.Dapr/examples/2023-10-01-preview/ConfigurationStores_Get.json +++ b/typespec/Applications.Dapr/examples/2023-10-01-preview/ConfigurationStores_Get.json @@ -1,10 +1,10 @@ { "operationId": "ConfigurationStores_Get", - "title": "Get a PubSubBroker resource", + "title": "Get a ConfigurationStore resource", "parameters": { "rootScope": "/planes/radius/local/resourceGroups/testGroup", "api-version": "2023-10-01-preview", - "pubSubBrokerName": "configstore0" + "configurationStoreName": "configstore0" }, "responses": { "200": { diff --git a/typespec/Applications.Dapr/main.tsp b/typespec/Applications.Dapr/main.tsp index 136db3e46f..37385eb500 100644 --- a/typespec/Applications.Dapr/main.tsp +++ b/typespec/Applications.Dapr/main.tsp @@ -21,6 +21,7 @@ import "./secretStores.tsp"; import "./stateStores.tsp"; import "./pubSubBrokers.tsp"; import "./configurationStores.tsp"; +import "./bindings.tsp"; using TypeSpec.Versioning; using Azure.ResourceManager;