Skip to content

Commit

Permalink
add create resource-type
Browse files Browse the repository at this point in the history
  • Loading branch information
nithyatsu committed Jan 6, 2025
1 parent 49f088a commit 8b5d45f
Show file tree
Hide file tree
Showing 7 changed files with 197 additions and 59 deletions.
4 changes: 4 additions & 0 deletions cmd/rad/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import (
resourceprovider_delete "github.com/radius-project/radius/pkg/cli/cmd/resourceprovider/delete"
resourceprovider_list "github.com/radius-project/radius/pkg/cli/cmd/resourceprovider/list"
resourceprovider_show "github.com/radius-project/radius/pkg/cli/cmd/resourceprovider/show"
resourcetype_create "github.com/radius-project/radius/pkg/cli/cmd/resourcetype/create"
resourcetype_delete "github.com/radius-project/radius/pkg/cli/cmd/resourcetype/delete"
resourcetype_list "github.com/radius-project/radius/pkg/cli/cmd/resourcetype/list"
resourcetype_show "github.com/radius-project/radius/pkg/cli/cmd/resourcetype/show"
Expand Down Expand Up @@ -273,6 +274,9 @@ func initSubCommands() {
resourceTypeDeleteCmd, _ := resourcetype_delete.NewCommand(framework)
resourceTypeCmd.AddCommand(resourceTypeDeleteCmd)

resourceTypeCreateCmd, _ := resourcetype_create.NewCommand(framework)
resourceTypeCmd.AddCommand(resourceTypeCreateCmd)

listRecipeCmd, _ := recipe_list.NewCommand(framework)
recipeCmd.AddCommand(listRecipeCmd)

Expand Down
103 changes: 47 additions & 56 deletions pkg/cli/cmd/resourcetype/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,17 @@ package create
import (
"context"

v1 "github.com/radius-project/radius/pkg/armrpc/api/v1"
aztoken "github.com/radius-project/radius/pkg/azure/tokencredentials"

"github.com/radius-project/radius/pkg/cli"
"github.com/radius-project/radius/pkg/cli/clients"
"github.com/radius-project/radius/pkg/cli/clierrors"
"github.com/radius-project/radius/pkg/cli/cmd/commonflags"
"github.com/radius-project/radius/pkg/cli/connections"
"github.com/radius-project/radius/pkg/cli/cmd/resourcetype/common"
"github.com/radius-project/radius/pkg/cli/framework"
"github.com/radius-project/radius/pkg/cli/manifest"
"github.com/radius-project/radius/pkg/cli/output"
"github.com/radius-project/radius/pkg/cli/workspaces"
"github.com/radius-project/radius/pkg/sdk"
"github.com/radius-project/radius/pkg/ucp/api/v20231001preview"
"github.com/spf13/cobra"
)
Expand All @@ -55,38 +56,39 @@ rad resource-type create myType --from-file /path/to/input.yaml
# Create a resource type from JSON file
rad resource-type create myType --from-file /path/to/input.json
`,
Args: cobra.ExactArgs(0),
Args: cobra.ExactArgs(1),
RunE: framework.RunCommand(runner),
}

commonflags.AddOutputFlag(cmd)
commonflags.AddWorkspaceFlag(cmd)
commonflags.AddFromFileFlagVar(cmd, &runner.ResourceProviderManifestFilePath)
_ = cmd.MarkFlagRequired("from-file")
_ = cmd.MarkFlagFilename("from-file", "yaml", "json")

return cmd, runner
}

// Runner is the Runner implementation for the `rad resourceprovider create` command.
type Runner struct {
ConnectionFactory connections.Factory
ConfigHolder *framework.ConfigHolder
Output output.Interface
Format string
Workspace *workspaces.Workspace
UCPClientFactory *v20231001preview.ClientFactory
ConfigHolder *framework.ConfigHolder
Output output.Interface
Format string
Workspace *workspaces.Workspace

ResourceProviderManifestFilePath string
ResourceProvider *manifest.ResourceProvider
ResourceTypeName string
Logger func(format string, args ...any)
}

// NewRunner creates an instance of the runner for the `rad resourceprovider create` command.
func NewRunner(factory framework.Factory) *Runner {
return &Runner{
ConnectionFactory: factory.GetConnectionFactory(),
ConfigHolder: factory.GetConfigHolder(),
Output: factory.GetOutput(),
ConfigHolder: factory.GetConfigHolder(),
Output: factory.GetOutput(),
Logger: func(format string, args ...any) {
output.LogInfo(format, args...)
},
}
}

Expand Down Expand Up @@ -126,64 +128,53 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error {

// Run runs the `rad resourcetype create` command.
func (r *Runner) Run(ctx context.Context) error {
client, err := r.ConnectionFactory.CreateApplicationsManagementClient(ctx, *r.Workspace)
if err != nil {
return err
// Initialize the client factory if it hasn't been set externally.
// This allows for flexibility where a test UCPClientFactory can be set externally during testing.
if r.UCPClientFactory == nil {
err := r.initializeClientFactory(ctx, r.Workspace)
if err != nil {
return err
}
}

r.Output.LogInfo("Checking resource provider %q exists", r.ResourceProvider.Name)
_, err = client.GetResourceProvider(ctx, "local", r.ResourceProvider.Name)

if clients.Is404Error(err) {
return clierrors.Message("Resource Provider %q not found. Please create using `rad resorce-provider create` before creating a resource-type", r.ResourceProvider.Name)
} else if err != nil {
return err
//response, err := r.UCPClientFactory.NewResourceProvidersClient().Get(ctx, "local", r.ResourceProvider.Name, nil)
response, err := r.UCPClientFactory.NewResourceTypesClient().Get(ctx, "local", r.ResourceProvider.Name, r.ResourceTypeName, nil)
if err != nil {
r.Output.LogInfo("Resource provider %q not found.", r.ResourceProvider.Name)
if err := manifest.RegisterFile(ctx, r.UCPClientFactory, "local", r.ResourceProviderManifestFilePath, r.Logger); err != nil {
return err
}
} else {
r.Output.LogInfo("Resource provider %q found. Registering resource type %q.", r.ResourceProvider.Name, r.ResourceTypeName)
if err := manifest.RegisterType(ctx, r.UCPClientFactory, "local", r.ResourceProviderManifestFilePath, r.ResourceTypeName, r.Logger); err != nil {
return err
}
}

// The location resource contains references to all of the resource types and API versions that the resource provider supports.
// We're instantiating the struct here so we can update it as we loop.
locationResource := v20231001preview.LocationResource{
Properties: &v20231001preview.LocationProperties{
ResourceTypes: map[string]*v20231001preview.LocationResourceType{},
},
}
// Add a blank line before printing the result.
r.Output.LogInfo("")

resourceType := r.ResourceProvider.Types[r.ResourceTypeName]
r.Output.LogInfo("Creating resource type %s/%s", r.ResourceProvider.Name, r.ResourceTypeName)
_, err = client.CreateOrUpdateResourceType(ctx, "local", r.ResourceProvider.Name, r.ResourceTypeName, &v20231001preview.ResourceTypeResource{
Properties: &v20231001preview.ResourceTypeProperties{
DefaultAPIVersion: resourceType.DefaultAPIVersion,
},
})
err = r.Output.WriteFormatted(r.Format, response, common.GetResourceTypeTableFormat())
if err != nil {
return err
}

locationResourceType := &v20231001preview.LocationResourceType{
APIVersions: map[string]map[string]any{},
}

for apiVersionName := range resourceType.APIVersions {
r.Output.LogInfo("Creating API Version %s/%s@%s", r.ResourceProvider.Name, r.ResourceTypeName, apiVersionName)
_, err := client.CreateOrUpdateAPIVersion(ctx, "local", r.ResourceProvider.Name, r.ResourceTypeName, apiVersionName, &v20231001preview.APIVersionResource{
Properties: &v20231001preview.APIVersionProperties{},
})
if err != nil {
return err
}
return nil
}

locationResourceType.APIVersions[apiVersionName] = map[string]any{}
func (r *Runner) initializeClientFactory(ctx context.Context, workspace *workspaces.Workspace) error {
connection, err := workspace.Connect(ctx)
if err != nil {
return err
}

locationResource.Properties.ResourceTypes[r.ResourceTypeName] = locationResourceType
clientOptions := sdk.NewClientOptions(connection)

r.Output.LogInfo("Creating location %s/%s", r.ResourceProvider.Name, v1.LocationGlobal)
_, err = client.CreateOrUpdateLocation(ctx, "local", r.ResourceProvider.Name, v1.LocationGlobal, &locationResource)
clientFactory, err := v20231001preview.NewClientFactory(&aztoken.AnonymousCredential{}, clientOptions)
if err != nil {
return err
}

r.Output.LogInfo("Resource type %s/%s created successfully", r.ResourceProvider.Name, r.ResourceTypeName)

r.UCPClientFactory = clientFactory
return nil
}
97 changes: 97 additions & 0 deletions pkg/cli/cmd/resourcetype/create/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
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 create

import (
"bytes"
"context"
"fmt"
"testing"

"github.com/radius-project/radius/pkg/cli/framework"
"github.com/radius-project/radius/pkg/cli/manifest"
"github.com/radius-project/radius/pkg/cli/output"
"github.com/radius-project/radius/pkg/cli/workspaces"
"github.com/radius-project/radius/test/radcli"
"github.com/stretchr/testify/require"
)

func Test_CommandValidation(t *testing.T) {
radcli.SharedCommandValidation(t, NewCommand)
}

func Test_Validate(t *testing.T) {
config := radcli.LoadConfigWithWorkspace(t)

testcases := []radcli.ValidateInput{
{
Name: "Valid",
Input: []string{"coolResources", "--from-file", "testdata/valid.yaml"},
ExpectedValid: true,
ConfigHolder: framework.ConfigHolder{Config: config},
},
{
Name: "Invalid: Error in manifest",
Input: []string{"testResources", "--from-file", "testdata/missing-required-field.yaml"},
ExpectedValid: false,
ConfigHolder: framework.ConfigHolder{Config: config},
},
{
Name: "Invalid: missing arguments",
Input: []string{"--from-file", "testdata/valid.yaml"},
ExpectedValid: false,
ConfigHolder: framework.ConfigHolder{Config: config},
},
}

radcli.SharedValidateValidation(t, NewCommand, testcases)
}

func Test_Run(t *testing.T) {
t.Run("Success: resource type created", func(t *testing.T) {

resourceProviderData, err := manifest.ReadFile("testdata/valid.yaml")
require.NoError(t, err)

expectedResourceType := "testResources"

clientFactory, err := manifest.NewTestClientFactory()
require.NoError(t, err)

var logBuffer bytes.Buffer
logger := func(format string, args ...any) {
fmt.Fprintf(&logBuffer, format+"\n", args...)
}

runner := &Runner{
UCPClientFactory: clientFactory,
Output: &output.MockOutput{},
Workspace: &workspaces.Workspace{},
ResourceProvider: resourceProviderData,
Format: "table",
Logger: logger,
ResourceProviderManifestFilePath: "testdata/valid.yaml",
ResourceTypeName: expectedResourceType,
}

err = runner.Run(context.Background())
require.NoError(t, err)

logOutput := logBuffer.String()
require.Contains(t, logOutput, fmt.Sprintf("Resource type %s/%s created successfully", resourceProviderData.Name, expectedResourceType))
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
types:
testResources:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
12 changes: 12 additions & 0 deletions pkg/cli/cmd/resourcetype/create/testdata/valid.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: CoolCompany.Resources
types:
testResources:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
coolResources:
apiVersions:
'2025-01-01-preview':
schema: {}
capabilities: ["Recipes"]
11 changes: 9 additions & 2 deletions pkg/cli/manifest/registermanifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,15 +201,21 @@ func RegisterType(ctx context.Context, clientFactory *v20231001preview.ClientFac
return err
}

var defaultAPIVersion string
if resourceType.DefaultAPIVersion == nil {
defaultAPIVersion = "2023-10-01-preview"
} else {
defaultAPIVersion = *resourceType.DefaultAPIVersion
}
locationResource := locationResourceGetResponse.LocationResource
locationResource.Properties.ResourceTypes[typeName] = &v20231001preview.LocationResourceType{
APIVersions: map[string]map[string]any{
*resourceType.DefaultAPIVersion: {},
defaultAPIVersion: {},
},
}

//set it back to resource provider
logIfEnabled(logger, "Updating location %s/%s", resourceProvider.Name, v1.LocationGlobal)
logIfEnabled(logger, "Updating location %s/%s with new resource type", resourceProvider.Name, v1.LocationGlobal)
locationPoller, err := clientFactory.NewLocationsClient().BeginCreateOrUpdate(ctx, planeName, resourceProvider.Name, v1.LocationGlobal, locationResource, nil)
if err != nil {
return err
Expand All @@ -220,6 +226,7 @@ func RegisterType(ctx context.Context, clientFactory *v20231001preview.ClientFac
return err
}

logIfEnabled(logger, "Resource type %s/%s created successfully", resourceProvider.Name, typeName)
return nil
}

Expand Down
23 changes: 22 additions & 1 deletion pkg/cli/manifest/testclientfactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,29 @@ func NewTestClientFactory() (*v20231001preview.ClientFactory, error) {

return
},
Get: func(
ctx context.Context,
planeName string,
resourceProviderName string,
locationName string,
options *v20231001preview.LocationsClientGetOptions,
) (resp azfake.Responder[v20231001preview.LocationsClientGetResponse], errResp azfake.ErrorResponder) {
response := v20231001preview.LocationsClientGetResponse{
LocationResource: v20231001preview.LocationResource{
Name: to.Ptr(locationName),
ID: to.Ptr("id"),
Properties: &v20231001preview.LocationProperties{
ResourceTypes: map[string]*v20231001preview.LocationResourceType{},
},
},
}
resp.SetResponse(http.StatusOK, response, nil)
return
},
}

//func(ctx context.Context, planeName string, resourceProviderName string, locationName string, options *v20231001preview.LocationsClientGetOptions)
// (resp "github.com/Azure/azure-sdk-for-go/sdk/azcore/fake".Responder[v20231001preview.LocationsClientGetResponse], errResp "github.com/Azure/azure-sdk-for-go/sdk/azcore/fake".ErrorResponder)
//(ctx, planeName, resourceProvider.Name, v1.LocationGlobal, nil)
serverFactory := ucpfake.ServerFactory{
ResourceProvidersServer: resourceProvidersServer,
ResourceTypesServer: resourceTypesServer,
Expand Down

0 comments on commit 8b5d45f

Please sign in to comment.