From bba47203207753c45adea313babb7ebf4d5b3196 Mon Sep 17 00:00:00 2001 From: Joel Hendrix Date: Fri, 13 Nov 2020 08:02:08 -0800 Subject: [PATCH] Add support for clouds other than public (#13496) * Add support for clouds other than public Renamed DefaultEndpoint to AzurePublicCloud. Added const for the sovereign clouds. Construct token scope based on the cloud. Updated NewRPRegistrationPolicy() to take an endpoint param. * rename func per feedback --- sdk/armcore/connection.go | 32 +++++++++++++++++++------- sdk/armcore/connection_test.go | 11 ++++++++- sdk/armcore/policy_register_rp.go | 16 +++++++------ sdk/armcore/policy_register_rp_test.go | 14 +++++------ 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/sdk/armcore/connection.go b/sdk/armcore/connection.go index bd91a0c00da1..962db5bebf82 100644 --- a/sdk/armcore/connection.go +++ b/sdk/armcore/connection.go @@ -9,9 +9,21 @@ import ( "github.com/Azure/azure-sdk-for-go/sdk/azcore" ) -const scope = "https://management.azure.com//.default" +const defaultScope = "/.default" + +const ( + // AzureChina is the Azure Resourece Manager China cloud endpoint. + AzureChina = "https://management.chinacloudapi.cn/" + // AzureGermany is the Azure Resourece Manager Germany cloud endpoint. + AzureGermany = "https://management.microsoftazure.de/" + // AzureGovernment is the Azure Resourece Manager US government cloud endpoint. + AzureGovernment = "https://management.usgovcloudapi.net/" + // AzurePublicCloud is the Azure Resourece Manager public cloud endpoint. + AzurePublicCloud = "https://management.azure.com/" +) // ConnectionOptions contains configuration settings for the connection's pipeline. +// Call DefaultConnectionOptions() to create an instance populated with default values. type ConnectionOptions struct { // HTTPClient sets the transport for making HTTP requests. HTTPClient azcore.Transport @@ -39,12 +51,9 @@ type Connection struct { p azcore.Pipeline } -// DefaultEndpoint is the Azure Resourece Manager public cloud endpoint. -const DefaultEndpoint = "https://management.azure.com" - -// NewDefaultConnection creates an instance of the Connection type using the DefaultEndpoint. +// NewDefaultConnection creates an instance of the Connection type using the AzurePublicCloud. func NewDefaultConnection(cred azcore.TokenCredential, options *ConnectionOptions) *Connection { - return NewConnection(DefaultEndpoint, cred, options) + return NewConnection(AzurePublicCloud, cred, options) } // NewConnection creates an instance of the Connection type with the specified endpoint. @@ -56,9 +65,9 @@ func NewConnection(endpoint string, cred azcore.TokenCredential, options *Connec } p := azcore.NewPipeline(options.HTTPClient, azcore.NewTelemetryPolicy(&options.Telemetry), - NewRPRegistrationPolicy(cred, &options.RegisterRPOptions), + NewRPRegistrationPolicy(endpoint, cred, &options.RegisterRPOptions), azcore.NewRetryPolicy(&options.Retry), - cred.AuthenticationPolicy(azcore.AuthenticationPolicyOptions{Options: azcore.TokenRequestOptions{Scopes: []string{scope}}}), + cred.AuthenticationPolicy(azcore.AuthenticationPolicyOptions{Options: azcore.TokenRequestOptions{Scopes: []string{endpointToScope(endpoint)}}}), azcore.NewLogPolicy(nil)) return NewConnectionWithPipeline(endpoint, p) } @@ -78,3 +87,10 @@ func (c *Connection) Endpoint() string { func (c *Connection) Pipeline() azcore.Pipeline { return c.p } + +func endpointToScope(endpoint string) string { + if endpoint[len(endpoint)-1] != '/' { + endpoint += "/" + } + return endpoint + defaultScope +} diff --git a/sdk/armcore/connection_test.go b/sdk/armcore/connection_test.go index c5da0b37da42..d2e2b491cf77 100644 --- a/sdk/armcore/connection_test.go +++ b/sdk/armcore/connection_test.go @@ -33,7 +33,7 @@ func (mockTokenCred) GetToken(context.Context, azcore.TokenRequestOptions) (*azc func TestNewDefaultConnection(t *testing.T) { opt := DefaultConnectionOptions() con := NewDefaultConnection(mockTokenCred{}, &opt) - if ep := con.Endpoint(); ep != DefaultEndpoint { + if ep := con.Endpoint(); ep != AzurePublicCloud { t.Fatalf("unexpected endpoint %s", ep) } } @@ -64,3 +64,12 @@ func TestNewConnectionWithPipeline(t *testing.T) { t.Fatalf("unexpected status code: %d", resp.StatusCode) } } + +func TestScope(t *testing.T) { + if s := endpointToScope(AzureGermany); s != "https://management.microsoftazure.de//.default" { + t.Fatalf("unexpected scope %s", s) + } + if s := endpointToScope("https://management.usgovcloudapi.net"); s != "https://management.usgovcloudapi.net//.default" { + t.Fatalf("unexpected scope %s", s) + } +} diff --git a/sdk/armcore/policy_register_rp.go b/sdk/armcore/policy_register_rp.go index 145e112ea0d6..fceb164dc35a 100644 --- a/sdk/armcore/policy_register_rp.go +++ b/sdk/armcore/policy_register_rp.go @@ -26,6 +26,7 @@ const ( ) // RegistrationOptions configures the registration policy's behavior. +// Call DefaultRegistrationOptions() to create an instance populated with default values. type RegistrationOptions struct { // MaxAttempts is the total number of times to attempt automatic registration // in the event that an attempt fails. @@ -60,24 +61,25 @@ func DefaultRegistrationOptions() RegistrationOptions { } } -// NewRPRegistrationPolicy creates a policy object configured using the specified pipeline -// and options. The policy controls if an unregistered resource provider should automatically -// be registered. See https://aka.ms/rps-not-found for more information. +// NewRPRegistrationPolicy creates a policy object configured using the specified endpoint, +// credentials and options. The policy controls if an unregistered resource provider should +// automatically be registered. See https://aka.ms/rps-not-found for more information. // Pass nil to accept the default options; this is the same as passing the result // from a call to DefaultRegistrationOptions(). -func NewRPRegistrationPolicy(cred azcore.Credential, o *RegistrationOptions) azcore.Policy { +func NewRPRegistrationPolicy(endpoint string, cred azcore.Credential, o *RegistrationOptions) azcore.Policy { if o == nil { def := DefaultRegistrationOptions() o = &def } p := azcore.NewPipeline(o.HTTPClient, azcore.NewRetryPolicy(&o.Retry), - cred.AuthenticationPolicy(azcore.AuthenticationPolicyOptions{Options: azcore.TokenRequestOptions{Scopes: []string{scope}}}), + cred.AuthenticationPolicy(azcore.AuthenticationPolicyOptions{Options: azcore.TokenRequestOptions{Scopes: []string{endpointToScope(endpoint)}}}), azcore.NewLogPolicy(nil)) - return &rpRegistrationPolicy{pipeline: p, options: *o} + return &rpRegistrationPolicy{endpoint: endpoint, pipeline: p, options: *o} } type rpRegistrationPolicy struct { + endpoint string pipeline azcore.Pipeline options RegistrationOptions } @@ -128,7 +130,7 @@ func (r *rpRegistrationPolicy) Do(req *azcore.Request) (*azcore.Response, error) // we use the scheme and host from the original request rpOps := &providersOperations{ p: r.pipeline, - u: fmt.Sprintf("%s://%s", req.URL.Scheme, req.URL.Host), + u: r.endpoint, subID: subID, } if _, err = rpOps.Register(req.Context(), rp); err != nil { diff --git a/sdk/armcore/policy_register_rp_test.go b/sdk/armcore/policy_register_rp_test.go index 5a3bc3a344ee..f0024a914796 100644 --- a/sdk/armcore/policy_register_rp_test.go +++ b/sdk/armcore/policy_register_rp_test.go @@ -68,7 +68,7 @@ func TestRPRegistrationPolicySuccess(t *testing.T) { srv.AppendResponse(mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteredResp))) // response for original request (different status code than any of the other responses) srv.AppendResponse(mock.WithStatusCode(http.StatusAccepted)) - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) + pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) req, err := azcore.NewRequest(context.Background(), http.MethodGet, azcore.JoinPaths(srv.URL(), requestEndpoint)) if err != nil { t.Fatal(err) @@ -108,7 +108,7 @@ func TestRPRegistrationPolicyNA(t *testing.T) { defer close() // response indicates no RP registration is required, policy does nothing srv.AppendResponse(mock.WithStatusCode(http.StatusOK)) - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) + pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) req, err := azcore.NewRequest(context.Background(), http.MethodGet, srv.URL()) if err != nil { t.Fatal(err) @@ -147,7 +147,7 @@ func TestRPRegistrationPolicy409Other(t *testing.T) { defer close() // test getting a 409 but not due to registration required srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(failedResp))) - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) + pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) req, err := azcore.NewRequest(context.Background(), http.MethodGet, srv.URL()) if err != nil { t.Fatal(err) @@ -178,7 +178,7 @@ func TestRPRegistrationPolicyTimesOut(t *testing.T) { // polling responses to Register() and Get(), in progress but slow // tests registration takes too long, times out srv.RepeatResponse(10, mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp)), mock.WithSlowResponse(400*time.Millisecond)) - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) + pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) req, err := azcore.NewRequest(context.Background(), http.MethodGet, azcore.JoinPaths(srv.URL(), requestEndpoint)) if err != nil { t.Fatal(err) @@ -222,7 +222,7 @@ func TestRPRegistrationPolicyExceedsAttempts(t *testing.T) { // polling response, successful registration srv.AppendResponse(mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteredResp))) } - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) + pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.AnonymousCredential(), testRPRegistrationOptions(srv))) req, err := azcore.NewRequest(context.Background(), http.MethodGet, azcore.JoinPaths(srv.URL(), requestEndpoint)) if err != nil { t.Fatal(err) @@ -270,7 +270,7 @@ func TestRPRegistrationPolicyCanCancel(t *testing.T) { srv.RepeatResponse(10, mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp)), mock.WithSlowResponse(300*time.Millisecond)) opts := DefaultRegistrationOptions() opts.HTTPClient = srv - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(azcore.AnonymousCredential(), &opts)) + pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.AnonymousCredential(), &opts)) // log only RP registration azcore.Log().SetClassifications(LogRPRegistration) defer func() { @@ -323,7 +323,7 @@ func TestRPRegistrationPolicyDisabled(t *testing.T) { srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp))) ops := testRPRegistrationOptions(srv) ops.MaxAttempts = 0 - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(azcore.AnonymousCredential(), ops)) + pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.AnonymousCredential(), ops)) req, err := azcore.NewRequest(context.Background(), http.MethodGet, azcore.JoinPaths(srv.URL(), requestEndpoint)) if err != nil { t.Fatal(err)