Skip to content

Commit

Permalink
Add capabilities to resource type API
Browse files Browse the repository at this point in the history
This change adds the 'capabilities' concept to the resource type API.

- Capabilities enable resource types to indicate the schema and behaviors they support.
- Capabilities enable clients like the `rad` CLI to understand the behaviors of resource types dynamically.

For example, we're adding the `SupportsRecipes` capability.

- All resource types that support recipes should declare this capability. This is how a UDT will opt-in to recipe functionality during provisioning.
- The `rad` CLI functionality for `rad recipe register` can introspect the resource type to validate recipe support, rather than hardcoding which types have the support and which don't.

----

Description of the changes:

- The manifests previously supported capabilities as part of the API version, we're moving this to the resource type for a simplification.
- The manifest entry for capabilities wasn't sent to the server. Now it is.
- Updated API, converters, and UCP functionality.

Signed-off-by: Ryan Nowak <nowakra@gmail.com>
  • Loading branch information
rynowak committed Jan 3, 2025
1 parent 46dc40d commit 3ea3c72
Show file tree
Hide file tree
Showing 41 changed files with 191 additions and 41 deletions.
14 changes: 7 additions & 7 deletions deploy/manifest/built-in-providers/applications_core.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,34 +4,34 @@ types:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: []
capabilities: []
applications:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: []
capabilities: []
environments:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: []
capabilities: []
gateways:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: []
capabilities: []
secretStores:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: []
capabilities: []
extenders:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
volumes:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: []
capabilities: []
8 changes: 4 additions & 4 deletions deploy/manifest/built-in-providers/applications_dapr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ types:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
pubSubBrokers:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
secretStores:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
stateStores:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ types:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
sqlDatabases:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
redisCaches:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ types:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ types:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: []
capabilities: []

Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ types:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
2 changes: 1 addition & 1 deletion pkg/cli/cmd/resourceprovider/create/testdata/valid.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ types:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
6 changes: 3 additions & 3 deletions pkg/cli/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ type ResourceProvider struct {

// ResourceType represents a resource type in a resource provider manifest.
type ResourceType struct {
// Capabilities is a list of capabilities for the resource type.
Capabilities []string `yaml:"capabilities" validate:"dive,capability"`

// DefaultAPIVersion is the default API version for the resource type.
DefaultAPIVersion *string `yaml:"defaultApiVersion,omitempty" validate:"omitempty,apiVersion"`

Expand All @@ -40,7 +43,4 @@ type ResourceTypeAPIVersion struct {
// TODO: this allows anything right now, and will be ignored. We'll improve this in
// a future pull-request.
Schema any `yaml:"schema" validate:"required"`

// Capabilities is a list of capabilities for the resource type.
Capabilities []string `yaml:"capabilities" validate:"dive,capability"`
}
8 changes: 4 additions & 4 deletions pkg/cli/manifest/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ func TestReadFileYAML(t *testing.T) {
"testResources": {
APIVersions: map[string]*ResourceTypeAPIVersion{
"2025-01-01-preview": {
Schema: map[string]any{},
Capabilities: []string{"Recipes"},
Schema: map[string]any{},
},
},
Capabilities: []string{"SupportsRecipes"},
},
},
}
Expand Down Expand Up @@ -70,10 +70,10 @@ func TestReadFileJSON(t *testing.T) {
"testResources": {
APIVersions: map[string]*ResourceTypeAPIVersion{
"2025-01-01-preview": {
Schema: map[string]any{},
Capabilities: []string{"Recipes"},
Schema: map[string]any{},
},
},
Capabilities: []string{"SupportsRecipes"},
},
},
}
Expand Down
1 change: 1 addition & 0 deletions pkg/cli/manifest/registermanifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func RegisterFile(ctx context.Context, clientFactory *v20231001preview.ClientFac
logIfEnabled(logger, "Creating resource type %s/%s", resourceProvider.Name, resourceTypeName)
resourceTypePoller, err := clientFactory.NewResourceTypesClient().BeginCreateOrUpdate(ctx, planeName, resourceProvider.Name, resourceTypeName, v20231001preview.ResourceTypeResource{
Properties: &v20231001preview.ResourceTypeProperties{
Capabilities: to.SliceOfPtrs(resourceType.Capabilities...),
DefaultAPIVersion: resourceType.DefaultAPIVersion,
},
}, nil)
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/manifest/testdata/duplicate-key.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ types:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
types:
testResources:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
2 changes: 1 addition & 1 deletion pkg/cli/manifest/testdata/invalid-yaml.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ types:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
2 changes: 1 addition & 1 deletion pkg/cli/manifest/testdata/missing-required-field.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ types:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ types:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: []
capabilities: []
testResource2:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: []
capabilities: []
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ types:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
testResource4:
apiVersions:
"2025-01-01-preview":
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
6 changes: 3 additions & 3 deletions pkg/cli/manifest/testdata/valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"testResources": {
"apiVersions": {
"2025-01-01-preview": {
"schema": {},
"capabilities": ["Recipes"]
"schema": {}
}
}
},
"capabilities": ["SupportsRecipes"]
}
}
}
2 changes: 1 addition & 1 deletion pkg/cli/manifest/testdata/valid.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ types:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
capabilities: ["SupportsRecipes"]
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func (dst *ResourceProviderSummary) ConvertFrom(src v1.DataModelInterface) error
dst.ResourceTypes = map[string]*ResourceProviderSummaryResourceType{}
for resourceTypeName, resourceType := range dm.Properties.ResourceTypes {
dst.ResourceTypes[resourceTypeName] = &ResourceProviderSummaryResourceType{
Capabilities: to.SliceOfPtrs(resourceType.Capabilities...),
DefaultAPIVersion: resourceType.DefaultAPIVersion,
APIVersions: map[string]map[string]any{},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func Test_ResourceProviderSummary_DataModelToVersioned(t *testing.T) {
},
ResourceTypes: map[string]*ResourceProviderSummaryResourceType{
"testResources": {
Capabilities: []*string{to.Ptr("SupportsRecipes")},
DefaultAPIVersion: to.Ptr("2025-01-01"),
APIVersions: map[string]map[string]any{
"2025-01-01": {},
Expand Down
26 changes: 26 additions & 0 deletions pkg/ucp/api/v20231001preview/resourcetype_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package v20231001preview

import (
"fmt"

v1 "github.com/radius-project/radius/pkg/armrpc/api/v1"
"github.com/radius-project/radius/pkg/to"
"github.com/radius-project/radius/pkg/ucp/datamodel"
Expand All @@ -39,7 +41,18 @@ func (src *ResourceTypeResource) ConvertTo() (v1.DataModelInterface, error) {
},
}

capabilities := []string{}
for _, capability := range src.Properties.Capabilities {
err := validateCapability(capability)
if err != nil {
return nil, err
}

capabilities = append(capabilities, *capability)
}

dst.Properties = datamodel.ResourceTypeProperties{
Capabilities: capabilities,
DefaultAPIVersion: src.Properties.DefaultAPIVersion,
}

Expand All @@ -61,8 +74,21 @@ func (dst *ResourceTypeResource) ConvertFrom(src v1.DataModelInterface) error {

dst.Properties = &ResourceTypeProperties{
ProvisioningState: to.Ptr(ProvisioningState(dm.InternalMetadata.AsyncProvisioningState)),
Capabilities: to.SliceOfPtrs(dm.Properties.Capabilities...),
DefaultAPIVersion: dm.Properties.DefaultAPIVersion,
}

return nil
}

func validateCapability(input *string) error {
if input == nil {
return v1.NewClientErrInvalidRequest("capability cannot be null")
}

if *input == datamodel.CapabilitySupportsRecipes {
return nil
}

return v1.NewClientErrInvalidRequest(fmt.Sprintf("capability %q is not recognized. Supported capabilities: %s", *input, datamodel.CapabilitySupportsRecipes))
}
37 changes: 37 additions & 0 deletions pkg/ucp/api/v20231001preview/resourcetype_conversion_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func Test_ResourceType_VersionedToDataModel(t *testing.T) {
},
},
Properties: datamodel.ResourceTypeProperties{
Capabilities: []string{"SupportsRecipes"},
DefaultAPIVersion: to.Ptr("2025-01-01"),
},
},
Expand Down Expand Up @@ -87,6 +88,7 @@ func Test_ResourceType_DataModelToVersioned(t *testing.T) {
Name: to.Ptr("testResources"),
Properties: &ResourceTypeProperties{
ProvisioningState: to.Ptr(ProvisioningStateSucceeded),
Capabilities: []*string{to.Ptr("SupportsRecipes")},
DefaultAPIVersion: to.Ptr("2025-01-01"),
},
},
Expand All @@ -113,3 +115,38 @@ func Test_ResourceType_DataModelToVersioned(t *testing.T) {
})
}
}

func Test_validateCapability(t *testing.T) {
tests := []struct {
name string
input *string
expectedErr error
}{
{
name: "valid capability",
input: to.Ptr(datamodel.CapabilitySupportsRecipes),
},
{
name: "invalid capability",
input: to.Ptr("InvalidCapability"),
expectedErr: v1.NewClientErrInvalidRequest("capability \"InvalidCapability\" is not recognized. Supported capabilities: SupportsRecipes"),
},
{
name: "nil capability",
input: nil,
expectedErr: v1.NewClientErrInvalidRequest("capability cannot be null"),
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateCapability(tt.input)
if tt.expectedErr != nil {
require.Error(t, err)
require.Equal(t, tt.expectedErr, err)
} else {
require.NoError(t, err)
}
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
},
"resourceTypes": {
"testResources": {
"capabilities": ["SupportsRecipes"],
"defaultApiVersion": "2025-01-01",
"apiVersions": {
"2025-01-01": {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"type": "System.Resources/resourceProviders/resourceTypes",
"provisioningState": "Succeeded",
"properties": {
"capabilities": ["SupportsRecipes"],
"defaultApiVersion": "2025-01-01"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"id": "/planes/radius/local/providers/System.Resources/resourceProviders/Applications.Test/resourceTypes/testResources",
"name": "testResources",
"properties": {
"capabilities": ["SupportsRecipes"],
"defaultApiVersion": "2025-01-01"
}
}
Loading

0 comments on commit 3ea3c72

Please sign in to comment.