From 9f76a56d402ecf3c1ac539ade5d3da1b9aa071e4 Mon Sep 17 00:00:00 2001 From: Yetkin Timocin Date: Thu, 15 Dec 2022 10:59:33 -0800 Subject: [PATCH] Migrating custom action client for client v2 --- pkg/azure/armauth/auth.go | 8 +- pkg/azure/clientv2/clients.go | 41 ++++++- pkg/azure/clientv2/common.go | 9 +- pkg/azure/clientv2/customaction.go | 115 +++++------------- .../clientv2/resourcedeploymentclient.go | 16 ++- .../resourcedeploymentoperationsclient.go | 1 + pkg/azure/clientv2/types.go | 17 +-- .../handlers/azure_federatedidentity.go | 6 +- .../azure_userassigned_managedidentity.go | 11 +- pkg/rp/secretvalueclient.go | 10 +- 10 files changed, 99 insertions(+), 135 deletions(-) diff --git a/pkg/azure/armauth/auth.go b/pkg/azure/armauth/auth.go index d2e565d9fc4..a08dc4b0ac7 100644 --- a/pkg/azure/armauth/auth.go +++ b/pkg/azure/armauth/auth.go @@ -29,8 +29,8 @@ type ArmConfig struct { // TODO: Migrate authenticator and clients to new azure sdk - https://github.com/project-radius/radius/issues/4268 Auth autorest.Authorizer - // ClientOption is the client v2 option including new client credentials. - ClientOption clientv2.AzureClientOption + // ClientOptions is the client v2 options including new client credentials. + ClientOptions clientv2.Options } // GetArmConfig gets the configuration we use for managing ARM resources @@ -47,8 +47,8 @@ func GetArmConfig() (*ArmConfig, error) { } return &ArmConfig{ - Auth: auth, - ClientOption: clientv2.AzureClientOption{Cred: cred}, + Auth: auth, + ClientOptions: clientv2.Options{Cred: cred}, }, nil } diff --git a/pkg/azure/clientv2/clients.go b/pkg/azure/clientv2/clients.go index da6dc01cb09..640c67b9a72 100644 --- a/pkg/azure/clientv2/clients.go +++ b/pkg/azure/clientv2/clients.go @@ -8,8 +8,11 @@ package clientv2 import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" + armruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi" + "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" ) var defaultClientOptions = &arm.ClientOptions{ @@ -20,20 +23,46 @@ var defaultClientOptions = &arm.ClientOptions{ }, } -// AzureClientOption represents the client option for azure sdk client including authentication. -type AzureClientOption struct { +// Options represents the client option for azure sdk client including authentication. +type Options struct { // Cred represents a credential for OAuth token. Cred azcore.TokenCredential + + BaseURI string } // NewFederatedIdentityClient creates new federated identity client. -func NewFederatedIdentityClient(subscriptionID string, option *AzureClientOption) (*armmsi.FederatedIdentityCredentialsClient, error) { +func NewFederatedIdentityClient(subscriptionID string, options *Options) (*armmsi.FederatedIdentityCredentialsClient, error) { // TODO: Add LRU cache to maintain the clients. - return armmsi.NewFederatedIdentityCredentialsClient(subscriptionID, option.Cred, defaultClientOptions) + return armmsi.NewFederatedIdentityCredentialsClient(subscriptionID, options.Cred, defaultClientOptions) } // NewUserAssignedIdentityClient creates new user assigned managed identity client. -func NewUserAssignedIdentityClient(subscriptionID string, option *AzureClientOption) (*armmsi.UserAssignedIdentitiesClient, error) { +func NewUserAssignedIdentityClient(subscriptionID string, options *Options) (*armmsi.UserAssignedIdentitiesClient, error) { // TODO: Add LRU cache to maintain the clients. - return armmsi.NewUserAssignedIdentitiesClient(subscriptionID, option.Cred, defaultClientOptions) + return armmsi.NewUserAssignedIdentitiesClient(subscriptionID, options.Cred, defaultClientOptions) +} + +// NewCustomActionClient creates an instance of the CustomActionClient. +func NewCustomActionClient(subscriptionID string, options *Options) (*CustomActionClient, error) { + baseURI := DefaultBaseURI + if options.BaseURI != "" { + baseURI = options.BaseURI + } + + client, err := armresources.NewClient(subscriptionID, options.Cred, defaultClientOptions) + if err != nil { + return nil, err + } + + pipeline, err := armruntime.NewPipeline(ModuleName, ModuleVersion, options.Cred, runtime.PipelineOptions{}, defaultClientOptions) + if err != nil { + return nil, err + } + + return &CustomActionClient{ + client: client, + pipeline: &pipeline, + baseURI: baseURI, + }, nil } diff --git a/pkg/azure/clientv2/common.go b/pkg/azure/clientv2/common.go index 2d69b2d7f64..7b3a2ef2c2d 100644 --- a/pkg/azure/clientv2/common.go +++ b/pkg/azure/clientv2/common.go @@ -9,18 +9,13 @@ import ( "context" "fmt" - "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/resourcemanager/resources/armresources" ) // GetResourceGroupLocation returns the location of the resource group. -func GetResourceGroupLocation(ctx context.Context, credential azcore.TokenCredential, subscriptionID string, resourceGroupName string) (*string, error) { - client, err := armresources.NewResourceGroupsClient(subscriptionID, credential, &arm.ClientOptions{}) - if err != nil { - return nil, err - } - +func GetResourceGroupLocation(ctx context.Context, subscriptionID string, resourceGroupName string, options *Options) (*string, error) { + client, err := armresources.NewResourceGroupsClient(subscriptionID, options.Cred, &arm.ClientOptions{}) if err != nil { return nil, err } diff --git a/pkg/azure/clientv2/customaction.go b/pkg/azure/clientv2/customaction.go index f95da5c8401..4bde77b9464 100644 --- a/pkg/azure/clientv2/customaction.go +++ b/pkg/azure/clientv2/customaction.go @@ -7,136 +7,83 @@ package clientv2 import ( "context" + "encoding/json" "errors" "net/http" "net/url" "strings" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm" - armruntime "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm/runtime" - "github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" "github.com/project-radius/radius/pkg/ucp/resources" ) -type CustomActionClient struct { - *BaseClient +// ClientCustomActionResponse is the response we get from invoking a custom action. +type ClientCustomActionResponse struct { + // Body is the Custom Action response body. + Body map[string]any } -// NewCustomActionClient creates an instance of the CustomActionClient with the default Base URI. -func NewCustomActionClient(subscriptionID string, credential azcore.TokenCredential) (*BaseClient, error) { - client, err := NewCustomActionClientWithBaseURI(DefaultBaseURI, subscriptionID, credential) - if err != nil { - return nil, err - } - - return client, err +// CustomActionClient is the client to invoke custom actions on Azure resources. +// Ex: listSecrets on a MongoDatabase. +type CustomActionClient struct { + client *armresources.Client + pipeline *runtime.Pipeline + baseURI string } -// NewCustomActionClientWithBaseURI creates an instance of the CustomActionClient with a Base URI. -func NewCustomActionClientWithBaseURI(baseURI string, subscriptionID string, credential azcore.TokenCredential) (*BaseClient, error) { - options := &arm.ClientOptions{ - ClientOptions: azcore.ClientOptions{ - Cloud: cloud.Configuration{ - Services: map[cloud.ServiceName]cloud.ServiceConfiguration{ - cloud.ResourceManager: { - Endpoint: baseURI, - }, - }, - }, - }, - } - - client, err := armresources.NewClient(subscriptionID, credential, options) +// InvokeCustomAction invokes a custom action on the given resource. +func (client *CustomActionClient) InvokeCustomAction(ctx context.Context, resourceID, apiVersion, action string) (*ClientCustomActionResponse, error) { + req, err := client.customActionCreateRequest(ctx, resourceID, apiVersion, action) if err != nil { return nil, err } - pipeline, err := armruntime.NewPipeline(moduleName, moduleVersion, credential, runtime.PipelineOptions{}, options) + resp, err := client.pipeline.Do(req) if err != nil { return nil, err } - return &BaseClient{ - Client: client, - Pipeline: &pipeline, - BaseURI: baseURI, - }, nil -} - -type ClientCustomActionResponse struct { - armresources.GenericResource -} - -type ClientBeginCustomActionOptions struct { - resourceID string - action string - apiVersion string -} - -// New creates an instance of the CustomActionClient with the default Base URI. -func NewCustomActionRequestOptions(resourceID, action, apiVersion string) *ClientBeginCustomActionOptions { - // FIXME: This is to validate the resourceID. - _, err := resources.ParseResource(resourceID) - if err != nil { - return nil - } - - return &ClientBeginCustomActionOptions{ - resourceID: resourceID, - action: action, - apiVersion: apiVersion, + if !runtime.HasStatusCode(resp, http.StatusOK, http.StatusAccepted, http.StatusNoContent) { + return nil, runtime.NewResponseError(resp) } -} -func (client *CustomActionClient) BeginCustomAction(ctx context.Context, opts *ClientBeginCustomActionOptions) (*runtime.Poller[ClientCustomActionResponse], error) { - resp, err := client.customAction(ctx, opts) + body := map[string]any{} + err = json.NewDecoder(resp.Body).Decode(&body) if err != nil { return nil, err } - // FIXME: Is this the right way? - return runtime.NewPoller[ClientCustomActionResponse](resp, *client.Pipeline, nil) + return &ClientCustomActionResponse{ + Body: body, + }, nil } -func (client *CustomActionClient) customAction(ctx context.Context, opts *ClientBeginCustomActionOptions) (*http.Response, error) { - req, err := client.customActionCreateRequest(ctx, opts) +func (client *CustomActionClient) customActionCreateRequest(ctx context.Context, resourceID, apiVersion, action string) (*policy.Request, error) { + _, err := resources.ParseResource(resourceID) if err != nil { return nil, err } - resp, err := client.Pipeline.Do(req) - if err != nil { - return nil, err - } - if !runtime.HasStatusCode(resp, http.StatusAccepted, http.StatusNoContent) { - return nil, runtime.NewResponseError(resp) - } - return resp, nil -} + urlPath := "{resourceID}/{action}" -func (client *CustomActionClient) customActionCreateRequest(ctx context.Context, opts *ClientBeginCustomActionOptions) (*policy.Request, error) { - urlPath := "/{resourceID}/{action}" - if opts.resourceID == "" { + if resourceID == "" { return nil, errors.New("resourceID cannot be empty") } - urlPath = strings.ReplaceAll(urlPath, "{resourceID}", url.PathEscape(opts.resourceID)) + urlPath = strings.ReplaceAll(urlPath, "{resourceID}", url.PathEscape(resourceID)) - if opts.action == "" { + if action == "" { return nil, errors.New("action cannot be empty") } - urlPath = strings.ReplaceAll(urlPath, "{action}", url.PathEscape(opts.action)) + urlPath = strings.ReplaceAll(urlPath, "{action}", url.PathEscape(action)) - // FIXME: Is joining BaseURI and URLPath going to give us a wrong URL? - req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.BaseURI, urlPath)) + req, err := runtime.NewRequest(ctx, http.MethodPost, runtime.JoinPaths(client.baseURI, urlPath)) if err != nil { return nil, err } reqQP := req.Raw().URL.Query() - reqQP.Set("api-version", opts.apiVersion) + reqQP.Set("api-version", apiVersion) req.Raw().URL.RawQuery = reqQP.Encode() req.Raw().Header["Accept"] = []string{"application/json"} return req, runtime.MarshalAsJSON(req, nil) diff --git a/pkg/azure/clientv2/resourcedeploymentclient.go b/pkg/azure/clientv2/resourcedeploymentclient.go index ef84eeafafc..64719e759c1 100644 --- a/pkg/azure/clientv2/resourcedeploymentclient.go +++ b/pkg/azure/clientv2/resourcedeploymentclient.go @@ -111,6 +111,7 @@ func NewDeploymentsClientWithBaseURI(credential azcore.TokenCredential, subscrip Cloud: cloud.Configuration{ Services: map[cloud.ServiceName]cloud.ServiceConfiguration{ cloud.ResourceManager: { + Audience: "https://management.core.windows.net", Endpoint: baseURI, }, }, @@ -122,7 +123,7 @@ func NewDeploymentsClientWithBaseURI(credential azcore.TokenCredential, subscrip return nil, err } - pipeline, err := armruntime.NewPipeline(moduleName, moduleVersion, credential, runtime.PipelineOptions{}, options) + pipeline, err := armruntime.NewPipeline(ModuleName, ModuleVersion, credential, runtime.PipelineOptions{}, options) if err != nil { return nil, err } @@ -135,22 +136,19 @@ func NewDeploymentsClientWithBaseURI(credential azcore.TokenCredential, subscrip } type ClientBeginCreateOrUpdateOptions struct { - resourceID string - resumeToken string - apiVersion string + resourceID string + apiVersion string } -func NewClientBeginCreateOrUpdateOptions(resourceID, resumeToken, apiVersion string) *ClientBeginCreateOrUpdateOptions { - // FIXME: This is to validate the resourceID. +func NewClientBeginCreateOrUpdateOptions(resourceID, apiVersion string) *ClientBeginCreateOrUpdateOptions { _, err := resources.ParseResource(resourceID) if err != nil { return nil } return &ClientBeginCreateOrUpdateOptions{ - resourceID: resourceID, - resumeToken: resumeToken, - apiVersion: apiVersion, + resourceID: resourceID, + apiVersion: apiVersion, } } diff --git a/pkg/azure/clientv2/resourcedeploymentoperationsclient.go b/pkg/azure/clientv2/resourcedeploymentoperationsclient.go index 26ff49a6d56..facd246bda1 100644 --- a/pkg/azure/clientv2/resourcedeploymentoperationsclient.go +++ b/pkg/azure/clientv2/resourcedeploymentoperationsclient.go @@ -39,6 +39,7 @@ func NewResourceDeploymentOperationsClientWithBaseURI(cred azcore.TokenCredentia Cloud: cloud.Configuration{ Services: map[cloud.ServiceName]cloud.ServiceConfiguration{ cloud.ResourceManager: { + Audience: "https://management.core.windows.net", Endpoint: baseURI, }, }, diff --git a/pkg/azure/clientv2/types.go b/pkg/azure/clientv2/types.go index dbfb9e6b33d..0b87d426b1e 100644 --- a/pkg/azure/clientv2/types.go +++ b/pkg/azure/clientv2/types.go @@ -5,21 +5,8 @@ package clientv2 -import ( - "github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime" - "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources" -) - const ( DefaultBaseURI = "https://management.azure.com" - - // FIXM: Any ideas for moduleName and moduleVersion? - moduleName = "radius" - moduleVersion = "public-preview" + ModuleName = "radius" + ModuleVersion = "public-preview" ) - -type BaseClient struct { - Client *armresources.Client - Pipeline *runtime.Pipeline - BaseURI string -} diff --git a/pkg/corerp/handlers/azure_federatedidentity.go b/pkg/corerp/handlers/azure_federatedidentity.go index b12d5c8396c..9faae66c411 100644 --- a/pkg/corerp/handlers/azure_federatedidentity.go +++ b/pkg/corerp/handlers/azure_federatedidentity.go @@ -129,7 +129,7 @@ func (handler *azureFederatedIdentityHandler) Put(ctx context.Context, options * subID := rID.FindScope(resources.SubscriptionsSegment) rgName := rID.FindScope(resources.ResourceGroupsSegment) - client, err := clientv2.NewFederatedIdentityClient(subID, &handler.arm.ClientOption) + client, err := clientv2.NewFederatedIdentityClient(subID, &handler.arm.ClientOptions) if err != nil { return nil, err } @@ -179,7 +179,9 @@ func (handler *azureFederatedIdentityHandler) Delete(ctx context.Context, option return err } - client, err := clientv2.NewFederatedIdentityClient(rID.FindScope(resources.SubscriptionsSegment), &handler.arm.ClientOption) + subscriptionID := rID.FindScope(resources.SubscriptionsSegment) + + client, err := clientv2.NewFederatedIdentityClient(subscriptionID, &handler.arm.ClientOptions) if err != nil { return err } diff --git a/pkg/corerp/handlers/azure_userassigned_managedidentity.go b/pkg/corerp/handlers/azure_userassigned_managedidentity.go index a62dda7170a..34912733d89 100644 --- a/pkg/corerp/handlers/azure_userassigned_managedidentity.go +++ b/pkg/corerp/handlers/azure_userassigned_managedidentity.go @@ -13,7 +13,6 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/msi/armmsi" "github.com/Azure/go-autorest/autorest/to" "github.com/project-radius/radius/pkg/azure/armauth" - "github.com/project-radius/radius/pkg/azure/clients" "github.com/project-radius/radius/pkg/azure/clientv2" "github.com/project-radius/radius/pkg/radlogger" "github.com/project-radius/radius/pkg/resourcemodel" @@ -64,7 +63,7 @@ func (handler *azureUserAssignedManagedIdentityHandler) Put(ctx context.Context, return nil, err } - rgLocation, err := clients.GetResourceGroupLocation(ctx, *handler.arm, subID, rgName) + rgLocation, err := clientv2.GetResourceGroupLocation(ctx, subID, rgName, &handler.arm.ClientOptions) if err != nil { return properties, err } @@ -78,7 +77,7 @@ func (handler *azureUserAssignedManagedIdentityHandler) Put(ctx context.Context, return nil, fmt.Errorf("azure federated identity does not support %s region now. unsupported regions: %q", resourceLocation, federatedUnsupportedRegions) } - msiClient, err := clientv2.NewUserAssignedIdentityClient(subID, &handler.arm.ClientOption) + msiClient, err := clientv2.NewUserAssignedIdentityClient(subID, &handler.arm.ClientOptions) if err != nil { return nil, err } @@ -93,7 +92,7 @@ func (handler *azureUserAssignedManagedIdentityHandler) Put(ctx context.Context, properties[UserAssignedIdentityClientIDKey] = to.String(identity.Properties.ClientID) properties[UserAssignedIdentityTenantIDKey] = to.String(identity.Properties.TenantID) - options.Resource.Identity = resourcemodel.NewARMIdentity(&options.Resource.ResourceType, properties[UserAssignedIdentityIDKey], clients.GetAPIVersionFromUserAgent(msi.UserAgent())) + options.Resource.Identity = resourcemodel.NewARMIdentity(&options.Resource.ResourceType, properties[UserAssignedIdentityIDKey], clientv2.GetAPIVersionFromUserAgent(msi.UserAgent())) logger.WithValues( radlogger.LogFieldResourceID, *identity.ID, radlogger.LogFieldLocalID, outputresource.LocalIDUserAssignedManagedIdentity).Info("Created managed identity for KeyVault access") @@ -112,7 +111,9 @@ func (handler *azureUserAssignedManagedIdentityHandler) Delete(ctx context.Conte return err } - msiClient, err := clientv2.NewUserAssignedIdentityClient(parsed.FindScope(resources.SubscriptionsSegment), &handler.arm.ClientOption) + subscriptionID := parsed.FindScope(resources.SubscriptionsSegment) + + msiClient, err := clientv2.NewUserAssignedIdentityClient(subscriptionID, &handler.arm.ClientOptions) if err != nil { return err } diff --git a/pkg/rp/secretvalueclient.go b/pkg/rp/secretvalueclient.go index 9430c0eae49..dfbd42f70be 100644 --- a/pkg/rp/secretvalueclient.go +++ b/pkg/rp/secretvalueclient.go @@ -11,7 +11,7 @@ import ( "github.com/go-openapi/jsonpointer" "github.com/project-radius/radius/pkg/azure/armauth" - "github.com/project-radius/radius/pkg/azure/clients" + "github.com/project-radius/radius/pkg/azure/clientv2" "github.com/project-radius/radius/pkg/resourcemodel" resources "github.com/project-radius/radius/pkg/ucp/resources" "github.com/project-radius/radius/pkg/ucp/store" @@ -38,8 +38,12 @@ func (c *client) FetchSecret(ctx context.Context, identity resourcemodel.Resourc return nil, err } - custom := clients.NewCustomActionClient(parsed.FindScope(resources.SubscriptionsSegment), c.ARM.Auth) - response, err := custom.InvokeCustomAction(ctx, arm.ID, arm.APIVersion, action, nil) + client, err := clientv2.NewCustomActionClient(parsed.FindScope(resources.SubscriptionsSegment), &c.ARM.ClientOptions) + if err != nil { + return nil, err + } + + response, err := client.InvokeCustomAction(ctx, arm.ID, arm.APIVersion, action) if err != nil { return nil, err }