Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Register Manifests during ucp startup sequence #8120

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/ucpd/ucp-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ initialization:
Applications.Datastores: "http://localhost:8080"
Microsoft.Resources: "http://localhost:5017"
kind: "UCPNative"
# This is the directory location which contains manifests to be registered.
manifestDirectory: ""
lakshmimsft marked this conversation as resolved.
Show resolved Hide resolved

identity:
authMethod: default
Expand Down
1 change: 1 addition & 0 deletions deploy/Chart/templates/ucp/configmaps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ data:
- id: "/planes/aws/aws"
properties:
kind: "AWS"
manifestDirectory: ""

identity:
authMethod: UCPCredential
Expand Down
98 changes: 37 additions & 61 deletions pkg/cli/cmd/resourceprovider/create/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,15 @@ 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/cmd/commonflags"
"github.com/radius-project/radius/pkg/cli/cmd/resourceprovider/common"
"github.com/radius-project/radius/pkg/cli/connections"
"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/to"
"github.com/radius-project/radius/pkg/sdk"
"github.com/radius-project/radius/pkg/ucp/api/v20231001preview"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -70,22 +69,26 @@ rad resource-provider create --from-file /path/to/input.json

// Runner is the Runner implementation for the `rad resource-provider 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
Logger func(format string, args ...any)
}

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

Expand Down Expand Up @@ -114,76 +117,49 @@ func (r *Runner) Validate(cmd *cobra.Command, args []string) error {

// Run runs the `rad resource-provider 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("Creating resource provider %s", r.ResourceProvider.Name)
_, err = client.CreateOrUpdateResourceProvider(ctx, "local", r.ResourceProvider.Name, &v20231001preview.ResourceProviderResource{
Location: to.Ptr(v1.LocationGlobal),
Properties: &v20231001preview.ResourceProviderProperties{},
})
if err != nil {
// Proceed with registering manifests
if err := manifest.RegisterFile(ctx, r.UCPClientFactory, "local", r.ResourceProviderManifestFilePath, 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{},
},
response, err := r.UCPClientFactory.NewResourceProvidersClient().Get(ctx, "local", r.ResourceProvider.Name, nil)
if err != nil {
return err
}

for resourceTypeName, resourceType := range r.ResourceProvider.Types {
r.Output.LogInfo("Creating resource type %s/%s", r.ResourceProvider.Name, resourceTypeName)
_, err := client.CreateOrUpdateResourceType(ctx, "local", r.ResourceProvider.Name, resourceTypeName, &v20231001preview.ResourceTypeResource{
Properties: &v20231001preview.ResourceTypeProperties{
DefaultAPIVersion: resourceType.DefaultAPIVersion,
},
})
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, resourceTypeName, apiVersionName)
_, err := client.CreateOrUpdateAPIVersion(ctx, "local", r.ResourceProvider.Name, resourceTypeName, apiVersionName, &v20231001preview.APIVersionResource{
Properties: &v20231001preview.APIVersionProperties{},
})
if err != nil {
return err
}

locationResourceType.APIVersions[apiVersionName] = map[string]any{}
}

locationResource.Properties.ResourceTypes[resourceTypeName] = locationResourceType
}
// Add a blank line before printing the result.
r.Output.LogInfo("")

r.Output.LogInfo("Creating location %s/%s", r.ResourceProvider.Name, v1.LocationGlobal)
_, err = client.CreateOrUpdateLocation(ctx, "local", r.ResourceProvider.Name, v1.LocationGlobal, &locationResource)
err = r.Output.WriteFormatted(r.Format, response, common.GetResourceProviderTableFormat())
if err != nil {
return err
}

response, err := client.GetResourceProvider(ctx, "local", r.ResourceProvider.Name)
return nil
}

func (r *Runner) initializeClientFactory(ctx context.Context, workspace *workspaces.Workspace) error {
connection, err := workspace.Connect(ctx)
if err != nil {
return err
}

// Add a blank line before printing the result.
r.Output.LogInfo("")
clientOptions := sdk.NewClientOptions(connection)

err = r.Output.WriteFormatted(r.Format, response, common.GetResourceProviderTableFormat())
clientFactory, err := v20231001preview.NewClientFactory(&aztoken.AnonymousCredential{}, clientOptions)
if err != nil {
return err
}

r.UCPClientFactory = clientFactory
return nil
}
109 changes: 23 additions & 86 deletions pkg/cli/cmd/resourceprovider/create/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,17 @@ limitations under the License.
package create

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

v1 "github.com/radius-project/radius/pkg/armrpc/api/v1"
"github.com/radius-project/radius/pkg/cli/clients"
"github.com/radius-project/radius/pkg/cli/cmd/resourceprovider/common"
"github.com/radius-project/radius/pkg/cli/connections"
"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/to"
"github.com/radius-project/radius/pkg/ucp/api/v20231001preview"
"github.com/radius-project/radius/test/radcli"
"github.com/stretchr/testify/require"
"go.uber.org/mock/gomock"
)

func Test_CommandValidation(t *testing.T) {
Expand Down Expand Up @@ -68,101 +63,43 @@ func Test_Validate(t *testing.T) {
ConfigHolder: framework.ConfigHolder{Config: config},
},
}

radcli.SharedValidateValidation(t, NewCommand, testcases)
}

func Test_Run(t *testing.T) {
t.Run("Success: resource provider created", func(t *testing.T) {
ctrl := gomock.NewController(t)

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

expectedResourceProvider := v20231001preview.ResourceProviderResource{
Location: to.Ptr(v1.LocationGlobal),
Properties: &v20231001preview.ResourceProviderProperties{},
}
expectedResourceType := v20231001preview.ResourceTypeResource{
Properties: &v20231001preview.ResourceTypeProperties{},
}
expectedAPIVersion := v20231001preview.APIVersionResource{
Properties: &v20231001preview.APIVersionProperties{},
}
expectedLocation := v20231001preview.LocationResource{
Properties: &v20231001preview.LocationProperties{
ResourceTypes: map[string]*v20231001preview.LocationResourceType{
"testResources": {
APIVersions: map[string]map[string]any{
"2025-01-01-preview": {},
},
},
},
},
}
expectedResourceType := "testResources"
expectedAPIVersion := "2025-01-01-preview"

appManagementClient := clients.NewMockApplicationsManagementClient(ctrl)
appManagementClient.EXPECT().
CreateOrUpdateResourceProvider(gomock.Any(), "local", "MyCompany.Resources", &expectedResourceProvider).
Return(expectedResourceProvider, nil).
Times(1)
appManagementClient.EXPECT().
CreateOrUpdateResourceType(gomock.Any(), "local", "MyCompany.Resources", "testResources", &expectedResourceType).
Return(expectedResourceType, nil).
Times(1)
appManagementClient.EXPECT().
CreateOrUpdateAPIVersion(gomock.Any(), "local", "MyCompany.Resources", "testResources", "2025-01-01-preview", &expectedAPIVersion).
Return(expectedAPIVersion, nil).
Times(1)
appManagementClient.EXPECT().
CreateOrUpdateLocation(gomock.Any(), "local", "MyCompany.Resources", v1.LocationGlobal, &expectedLocation).
Return(expectedLocation, nil).
Times(1)
appManagementClient.EXPECT().
GetResourceProvider(gomock.Any(), "local", "MyCompany.Resources").
Return(expectedResourceProvider, nil).
Times(1)

outputSink := &output.MockOutput{}
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{
ConnectionFactory: &connections.MockFactory{ApplicationsManagementClient: appManagementClient},
Output: outputSink,
Workspace: &workspaces.Workspace{},
ResourceProvider: resourceProviderData,
Format: "table",
UCPClientFactory: clientFactory,
Output: &output.MockOutput{},
Workspace: &workspaces.Workspace{},
ResourceProvider: resourceProviderData,
Format: "table",
Logger: logger,
ResourceProviderManifestFilePath: "testdata/valid.yaml",
}

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

expectedOutput := []any{
output.LogOutput{
Format: "Creating resource provider %s",
Params: []any{"MyCompany.Resources"},
},
output.LogOutput{
Format: "Creating resource type %s/%s",
Params: []any{"MyCompany.Resources", "testResources"},
},
output.LogOutput{
Format: "Creating API Version %s/%s@%s",
Params: []any{"MyCompany.Resources", "testResources", "2025-01-01-preview"},
},
output.LogOutput{
Format: "Creating location %s/%s",
Params: []any{"MyCompany.Resources", "global"},
},
output.LogOutput{
Format: "",
Params: nil,
},
output.FormattedOutput{
Format: "table",
Obj: expectedResourceProvider,
Options: common.GetResourceProviderTableFormat(),
},
}
require.Equal(t, expectedOutput, outputSink.Writes)
logOutput := logBuffer.String()
require.Contains(t, logOutput, fmt.Sprintf("Creating resource provider %s", resourceProviderData.Name))
require.Contains(t, logOutput, fmt.Sprintf("Creating resource type %s/%s", resourceProviderData.Name, expectedResourceType))
require.Contains(t, logOutput, fmt.Sprintf("Creating API Version %s/%s@%s", resourceProviderData.Name, expectedResourceType, expectedAPIVersion))
})

}
Loading
Loading