diff --git a/sdk/armcore/LICENSE.txt b/sdk/armcore/LICENSE.txt deleted file mode 100644 index ccb63b166732..000000000000 --- a/sdk/armcore/LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2021 Microsoft - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/sdk/armcore/README.md b/sdk/armcore/README.md deleted file mode 100644 index 5c218a455673..000000000000 --- a/sdk/armcore/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Azure Resource Manager Core Client Module for Go - -[![PkgGoDev](https://pkg.go.dev/badge/github.com/Azure/azure-sdk-for-go/sdk/armcore)](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/armcore) -[![Build Status](https://dev.azure.com/azure-sdk/public/_apis/build/status/go/go%20-%20armcore%20-%20ci?branchName=main)](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=1844&branchName=main) -[![Code Coverage](https://img.shields.io/azure-devops/coverage/azure-sdk/public/1844/main)](https://img.shields.io/azure-devops/coverage/azure-sdk/public/1844/main) - -The `armcore` module provides functions and types for Go SDK ARM client modules. -These modules follow the [Azure SDK Design Guidelines for Go](https://azure.github.io/azure-sdk/golang_introduction.html). - -## Getting started - -This project uses [Go modules](https://github.com/golang/go/wiki/Modules) for versioning and dependency management. - -Typically, you will not need to explicitly install `armcore` as it will be installed as an ARM client module dependency. -To add the latest version to your `go.mod` file, execute the following command. - -```bash -go get -u github.com/Azure/azure-sdk-for-go/sdk/armcore -``` - -General documentation and examples can be found on [pkg.go.dev](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/armcore). - -## Contributing -This project welcomes contributions and suggestions. Most contributions require -you to agree to a Contributor License Agreement (CLA) declaring that you have -the right to, and actually do, grant us the rights to use your contribution. -For details, visit [https://cla.microsoft.com](https://cla.microsoft.com). - -When you submit a pull request, a CLA-bot will automatically determine whether -you need to provide a CLA and decorate the PR appropriately (e.g., label, -comment). Simply follow the instructions provided by the bot. You will only -need to do this once across all repos using our CLA. - -This project has adopted the -[Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information, see the -[Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) -or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any -additional questions or comments. diff --git a/sdk/armcore/ci.yml b/sdk/armcore/ci.yml deleted file mode 100644 index 5354ce336205..000000000000 --- a/sdk/armcore/ci.yml +++ /dev/null @@ -1,16 +0,0 @@ -# NOTE: Please refer to https://aka.ms/azsdk/engsys/ci-yaml before editing this file. -trigger: - paths: - include: - - sdk/armcore/ - -pr: - paths: - include: - - sdk/armcore/ - -stages: -- template: ../../eng/pipelines/templates/jobs/archetype-sdk-client.yml - parameters: - ServiceDirectory: 'armcore' - diff --git a/sdk/armcore/connection.go b/sdk/armcore/connection.go deleted file mode 100644 index 67f8d44fc6b5..000000000000 --- a/sdk/armcore/connection.go +++ /dev/null @@ -1,131 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package armcore - -import ( - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -const defaultScope = "/.default" - -const ( - // AzureChina is the Azure Resource Manager China cloud endpoint. - AzureChina = "https://management.chinacloudapi.cn/" - // AzureGermany is the Azure Resource Manager Germany cloud endpoint. - AzureGermany = "https://management.microsoftazure.de/" - // AzureGovernment is the Azure Resource Manager US government cloud endpoint. - AzureGovernment = "https://management.usgovcloudapi.net/" - // AzurePublicCloud is the Azure Resource Manager public cloud endpoint. - AzurePublicCloud = "https://management.azure.com/" -) - -// ConnectionOptions contains configuration settings for the connection's pipeline. -// All zero-value fields will be initialized with their default values. -type ConnectionOptions struct { - // AuxiliaryTenants contains a list of additional tenants to be used to authenticate - // across multiple tenants. - AuxiliaryTenants []string - - // HTTPClient sets the transport for making HTTP requests. - HTTPClient azcore.Transport - - // Retry configures the built-in retry policy behavior. - Retry azcore.RetryOptions - - // Telemetry configures the built-in telemetry policy behavior. - Telemetry azcore.TelemetryOptions - - // Logging configures the built-in logging policy behavior. - Logging azcore.LogOptions - - // DisableRPRegistration disables the auto-RP registration policy. - // The default value is false. - DisableRPRegistration bool - - // PerCallPolicies contains custom policies to inject into the pipeline. - // Each policy is executed once per request. - PerCallPolicies []azcore.Policy - - // PerRetryPolicies contains custom policies to inject into the pipeline. - // Each policy is executed once per request, and for each retry request. - PerRetryPolicies []azcore.Policy -} - -// Connection is a connection to an Azure Resource Manager endpoint. -// It contains the base ARM endpoint and a pipeline for making requests. -type Connection struct { - u string - p azcore.Pipeline -} - -// NewDefaultConnection creates an instance of the Connection type using the AzurePublicCloud. -// Pass nil to accept the default options; this is the same as passing a zero-value options. -func NewDefaultConnection(cred azcore.TokenCredential, options *ConnectionOptions) *Connection { - return NewConnection(AzurePublicCloud, cred, options) -} - -// NewConnection creates an instance of the Connection type with the specified endpoint. -// Use this when connecting to clouds other than the Azure public cloud (stack/sovereign clouds). -// Pass nil to accept the default options; this is the same as passing a zero-value options. -func NewConnection(endpoint string, cred azcore.TokenCredential, options *ConnectionOptions) *Connection { - if options == nil { - options = &ConnectionOptions{} - } else { - // create a copy so we don't modify the original - cp := *options - options = &cp - } - if options.Telemetry.Value == "" { - options.Telemetry.Value = UserAgent - } else { - options.Telemetry.Value += " " + UserAgent - } - policies := []azcore.Policy{ - azcore.NewTelemetryPolicy(&options.Telemetry), - } - if !options.DisableRPRegistration { - regRPOpts := RegistrationOptions{ - HTTPClient: options.HTTPClient, - Logging: options.Logging, - Retry: options.Retry, - Telemetry: options.Telemetry, - } - policies = append(policies, NewRPRegistrationPolicy(endpoint, cred, ®RPOpts)) - } - policies = append(policies, options.PerCallPolicies...) - policies = append(policies, azcore.NewRetryPolicy(&options.Retry)) - policies = append(policies, options.PerRetryPolicies...) - policies = append(policies, - cred.NewAuthenticationPolicy( - azcore.AuthenticationOptions{ - TokenRequest: azcore.TokenRequestOptions{ - Scopes: []string{endpointToScope(endpoint)}, - }, - AuxiliaryTenants: options.AuxiliaryTenants, - }, - ), - azcore.NewLogPolicy(&options.Logging)) - p := azcore.NewPipeline(options.HTTPClient, policies...) - return &Connection{u: endpoint, p: p} -} - -// Endpoint returns the connection's ARM endpoint. -func (c *Connection) Endpoint() string { - return c.u -} - -// Pipeline returns the connection's pipeline. -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 deleted file mode 100644 index f2127763e5fd..000000000000 --- a/sdk/armcore/connection_test.go +++ /dev/null @@ -1,193 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package armcore - -import ( - "context" - "net/http" - "strings" - "testing" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -type mockTokenCred struct{} - -func (mockTokenCred) NewAuthenticationPolicy(azcore.AuthenticationOptions) azcore.Policy { - return azcore.PolicyFunc(func(req *azcore.Request) (*azcore.Response, error) { - return req.Next() - }) -} - -func (mockTokenCred) GetToken(context.Context, azcore.TokenRequestOptions) (*azcore.AccessToken, error) { - return &azcore.AccessToken{ - Token: "abc123", - ExpiresOn: time.Now().Add(1 * time.Hour), - }, nil -} - -func TestNewDefaultConnection(t *testing.T) { - opt := ConnectionOptions{} - con := NewDefaultConnection(mockTokenCred{}, &opt) - if ep := con.Endpoint(); ep != AzurePublicCloud { - t.Fatalf("unexpected endpoint %s", ep) - } -} - -func TestNewConnection(t *testing.T) { - const customEndpoint = "https://contoso.com/fake/endpoint" - con := NewConnection(customEndpoint, mockTokenCred{}, nil) - if ep := con.Endpoint(); ep != customEndpoint { - t.Fatalf("unexpected endpoint %s", ep) - } -} - -func TestNewConnectionWithOptions(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse() - opt := ConnectionOptions{} - opt.HTTPClient = srv - con := NewConnection(srv.URL(), mockTokenCred{}, &opt) - if ep := con.Endpoint(); ep != srv.URL() { - t.Fatalf("unexpected endpoint %s", ep) - } - req, err := azcore.NewRequest(context.Background(), http.MethodGet, srv.URL()) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - resp, err := con.Pipeline().Do(req) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if ua := resp.Request.Header.Get("User-Agent"); !strings.HasPrefix(ua, UserAgent) { - t.Fatalf("unexpected User-Agent %s", ua) - } -} - -func TestNewConnectionWithCustomTelemetry(t *testing.T) { - const myTelemetry = "something" - srv, close := mock.NewServer() - defer close() - srv.AppendResponse() - opt := ConnectionOptions{} - opt.HTTPClient = srv - opt.Telemetry.Value = myTelemetry - con := NewConnection(srv.URL(), mockTokenCred{}, &opt) - if ep := con.Endpoint(); ep != srv.URL() { - t.Fatalf("unexpected endpoint %s", ep) - } - if opt.Telemetry.Value != myTelemetry { - t.Fatalf("telemetry was modified: %s", opt.Telemetry.Value) - } - req, err := azcore.NewRequest(context.Background(), http.MethodGet, srv.URL()) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - resp, err := con.Pipeline().Do(req) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code: %d", resp.StatusCode) - } - if ua := resp.Request.Header.Get("User-Agent"); !strings.HasPrefix(ua, myTelemetry+" "+UserAgent) { - t.Fatalf("unexpected User-Agent %s", ua) - } -} - -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) - } -} - -func TestDisableAutoRPRegistration(t *testing.T) { - srv, close := mock.NewServer() - defer close() - // initial response that RP is unregistered - srv.SetResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp))) - con := NewConnection(srv.URL(), mockTokenCred{}, &ConnectionOptions{DisableRPRegistration: true}) - if ep := con.Endpoint(); ep != srv.URL() { - t.Fatalf("unexpected endpoint %s", ep) - } - req, err := azcore.NewRequest(context.Background(), http.MethodGet, srv.URL()) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - // log only RP registration - azcore.Log().SetClassifications(LogRPRegistration) - defer func() { - // reset logging - azcore.Log().SetClassifications() - }() - logEntries := 0 - azcore.Log().SetListener(func(cls azcore.LogClassification, msg string) { - logEntries++ - }) - resp, err := con.Pipeline().Do(req) - if err != nil { - t.Fatal(err) - } - if resp.StatusCode != http.StatusConflict { - t.Fatalf("unexpected status code %d:", resp.StatusCode) - } - // shouldn't be any log entries - if logEntries != 0 { - t.Fatalf("expected 0 log entries, got %d", logEntries) - } -} - -// policy that tracks the number of times it was invoked -type countingPolicy struct { - count int -} - -func (p *countingPolicy) Do(req *azcore.Request) (*azcore.Response, error) { - p.count++ - return req.Next() -} - -func TestConnectionWithCustomPolicies(t *testing.T) { - srv, close := mock.NewServer() - defer close() - // initial response is a failure to trigger retry - srv.AppendResponse(mock.WithStatusCode(http.StatusInternalServerError)) - srv.AppendResponse(mock.WithStatusCode(http.StatusOK)) - perCallPolicy := countingPolicy{} - perRetryPolicy := countingPolicy{} - con := NewConnection(srv.URL(), mockTokenCred{}, &ConnectionOptions{ - DisableRPRegistration: true, - PerCallPolicies: []azcore.Policy{&perCallPolicy}, - PerRetryPolicies: []azcore.Policy{&perRetryPolicy}, - }) - req, err := azcore.NewRequest(context.Background(), http.MethodGet, srv.URL()) - if err != nil { - t.Fatal(err) - } - resp, err := con.Pipeline().Do(req) - if err != nil { - t.Fatal(err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code %d", resp.StatusCode) - } - if perCallPolicy.count != 1 { - t.Fatalf("unexpected per call policy count %d", perCallPolicy.count) - } - if perRetryPolicy.count != 2 { - t.Fatalf("unexpected per retry policy count %d", perRetryPolicy.count) - } -} diff --git a/sdk/armcore/doc.go b/sdk/armcore/doc.go deleted file mode 100644 index 89b4fedca545..000000000000 --- a/sdk/armcore/doc.go +++ /dev/null @@ -1,31 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright 2017 Microsoft Corporation. All rights reserved. -// Use of this source code is governed by an MIT -// license that can be found in the LICENSE file. - -// Package armcore provides connections and utilities for Go SDK ARM client modules. -// -// All Azure Resource Manager clients require a Connection, which is simply a -// combination of the desired ARM endpoint and a pipeline for handling HTTP requests -// and responses. -// -// To access the Azure public cloud, use the NewDefaultConnection() constructor with -// the required token credential. Module azidentity provides several methods for -// obtaining token credentials. -// -// cred, _ := azidentity.NewDefaultAzureCredential(nil) -// con := armcore.NewDefaultConnection(cred, nil) -// -// When accessing clouds other than the Azure public cloud, use the NewConnection() -// constructor with the required ARM endpoint and token credential. The most common -// case is connecting to an Azure sovereign cloud or Azure Stack instance. -// -// NewDefaultConnection() and NewConnection() are configured with the same pipeline -// thus have the same pipeline configuration options. Use the NewConnectionWithPipeline() -// constructor to create a connection that uses a custom azcore.Pipeline. Note that -// any custom pipeline will require at minimum an authentication policy obtained from -// a token credential in order to authenticate with ARM. See the implementation of -// NewConnection() for how to obtain a credential's authentication policy. -package armcore diff --git a/sdk/armcore/go.mod b/sdk/armcore/go.mod deleted file mode 100644 index fbf6f63b63a2..000000000000 --- a/sdk/armcore/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -// Deprecated: The contents of this module have moved to github.com/Azure/azure-sdk-for-go/sdk/azcore/arm -module github.com/Azure/azure-sdk-for-go/sdk/armcore - -go 1.14 - -require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v0.17.0 - github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.1 -) diff --git a/sdk/armcore/go.sum b/sdk/armcore/go.sum deleted file mode 100644 index fa04cccc1563..000000000000 --- a/sdk/armcore/go.sum +++ /dev/null @@ -1,16 +0,0 @@ -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.17.0 h1:j9ra6YGWu3TqNmCprpWYFCqQ3aizqujxrqhI7KLu6qg= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.17.0/go.mod h1:MVdrcUC4Hup35qHym3VdzoW+NBgBxrta9Vei97jRtM8= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.1 h1:vx8McI56N5oLSQu8xa+xdiE0fjQq8W8Zt49vHP8Rygw= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.5.1/go.mod h1:k4KbFSunV/+0hOHL1vyFaPsiYQ1Vmvy1TBpmtvCDLZM= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb h1:mUVeFHoDKis5nxCAzoAi7E8Ghb86EXh/RK6wtvJIqRY= -golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/sdk/armcore/internal/pollers/async/async.go b/sdk/armcore/internal/pollers/async/async.go deleted file mode 100644 index 4736c648673b..000000000000 --- a/sdk/armcore/internal/pollers/async/async.go +++ /dev/null @@ -1,134 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package async - -import ( - "errors" - "fmt" - "net/http" - - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -const ( - finalStateAsync = "azure-async-operation" - finalStateLoc = "location" //nolint - finalStateOrig = "original-uri" -) - -// Applicable returns true if the LRO is using Azure-AsyncOperation. -func Applicable(resp *azcore.Response) bool { - return resp.Header.Get(pollers.HeaderAzureAsync) != "" -} - -// Poller is an LRO poller that uses the Azure-AsyncOperation pattern. -type Poller struct { - // The poller's type, used for resume token processing. - Type string `json:"type"` - - // The URL from Azure-AsyncOperation header. - AsyncURL string `json:"asyncURL"` - - // The URL from Location header. - LocURL string `json:"locURL"` - - // The URL from the initial LRO request. - OrigURL string `json:"origURL"` - - // The HTTP method from the initial LRO request. - Method string `json:"method"` - - // The value of final-state-via from swagger, can be the empty string. - FinalState string `json:"finalState"` - - // The LRO's current state. - CurState string `json:"state"` -} - -// New creates a new Poller from the provided initial response and final-state type. -func New(resp *azcore.Response, finalState string, pollerID string) (*Poller, error) { - azcore.Log().Write(azcore.LogLongRunningOperation, "Using Azure-AsyncOperation poller.") - asyncURL := resp.Header.Get(pollers.HeaderAzureAsync) - if asyncURL == "" { - return nil, errors.New("response is missing Azure-AsyncOperation header") - } - if !pollers.IsValidURL(asyncURL) { - return nil, fmt.Errorf("invalid polling URL %s", asyncURL) - } - p := &Poller{ - Type: pollers.MakeID(pollerID, "async"), - AsyncURL: asyncURL, - LocURL: resp.Header.Get(pollers.HeaderLocation), - OrigURL: resp.Request.URL.String(), - Method: resp.Request.Method, - FinalState: finalState, - } - // check for provisioning state - state, err := pollers.GetProvisioningState(resp) - if errors.Is(err, pollers.ErrNoBody) || state == "" { - // NOTE: the ARM RPC spec explicitly states that for async PUT the initial response MUST - // contain a provisioning state. to maintain compat with track 1 and other implementations - // we are explicitly relaxing this requirement. - /*if resp.Request.Method == http.MethodPut { - // initial response for a PUT requires a provisioning state - return nil, err - }*/ - // for DELETE/PATCH/POST, provisioning state is optional - state = pollers.StatusInProgress - } else if err != nil { - return nil, err - } - p.CurState = state - return p, nil -} - -// Done returns true if the LRO has reached a terminal state. -func (p *Poller) Done() bool { - return pollers.IsTerminalState(p.Status()) -} - -// Update updates the Poller from the polling response. -func (p *Poller) Update(resp *azcore.Response) error { - state, err := pollers.GetStatus(resp) - if err != nil { - return err - } else if state == "" { - return errors.New("the response did not contain a status") - } - p.CurState = state - return nil -} - -// FinalGetURL returns the URL to perform a final GET for the payload, or the empty string if not required. -func (p *Poller) FinalGetURL() string { - if p.Method == http.MethodPatch || p.Method == http.MethodPut { - // for PATCH and PUT, the final GET is on the original resource URL - return p.OrigURL - } else if p.Method == http.MethodPost { - if p.FinalState == finalStateAsync { - return "" - } else if p.FinalState == finalStateOrig { - return p.OrigURL - } else if p.LocURL != "" { - // ideally FinalState would be set to "location" but it isn't always. - // must check last due to more permissive condition. - return p.LocURL - } - } - return "" -} - -// URL returns the polling URL. -func (p *Poller) URL() string { - return p.AsyncURL -} - -// Status returns the status of the LRO. -func (p *Poller) Status() string { - return p.CurState -} diff --git a/sdk/armcore/internal/pollers/async/async_test.go b/sdk/armcore/internal/pollers/async/async_test.go deleted file mode 100644 index 0f861927a425..000000000000 --- a/sdk/armcore/internal/pollers/async/async_test.go +++ /dev/null @@ -1,187 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package async - -import ( - "io" - "io/ioutil" - "net/http" - "strings" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -const ( - fakePollingURL = "https://foo.bar.baz/status" - fakeResourceURL = "https://foo.bar.baz/resource" -) - -func initialResponse(method string, resp io.Reader) *azcore.Response { - req, err := http.NewRequest(method, fakeResourceURL, nil) - if err != nil { - panic(err) - } - return &azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(resp), - Header: http.Header{}, - Request: req, - }, - } -} - -func pollingResponse(resp io.Reader) *azcore.Response { - return &azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(resp), - Header: http.Header{}, - }, - } -} - -func TestApplicable(t *testing.T) { - resp := azcore.Response{ - Response: &http.Response{ - Header: http.Header{}, - }, - } - if Applicable(&resp) { - t.Fatal("missing Azure-AsyncOperation should not be applicable") - } - resp.Response.Header.Set(pollers.HeaderAzureAsync, fakePollingURL) - if !Applicable(&resp) { - t.Fatal("having Azure-AsyncOperation should be applicable") - } -} - -func TestNew(t *testing.T) { - const jsonBody = `{ "properties": { "provisioningState": "Started" } }` - resp := initialResponse(http.MethodPut, strings.NewReader(jsonBody)) - resp.Header.Set(pollers.HeaderAzureAsync, fakePollingURL) - poller, err := New(resp, "", "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if u := poller.FinalGetURL(); u != fakeResourceURL { - t.Fatalf("unexpected final get URL %s", u) - } - if s := poller.Status(); s != "Started" { - t.Fatalf("unexpected status %s", s) - } - if u := poller.URL(); u != fakePollingURL { - t.Fatalf("unexpected polling URL %s", u) - } - if err := poller.Update(pollingResponse(strings.NewReader(`{ "status": "InProgress" }`))); err != nil { - t.Fatal(err) - } - if s := poller.Status(); s != "InProgress" { - t.Fatalf("unexpected status %s", s) - } -} - -func TestNewDeleteNoProvState(t *testing.T) { - resp := initialResponse(http.MethodDelete, http.NoBody) - resp.Header.Set(pollers.HeaderAzureAsync, fakePollingURL) - poller, err := New(resp, "", "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if s := poller.Status(); s != "InProgress" { - t.Fatalf("unexpected status %s", s) - } -} - -func TestNewPutNoProvState(t *testing.T) { - // missing provisioning state on initial response - // NOTE: ARM RPC forbids this but we allow it for back-compat - resp := initialResponse(http.MethodPut, http.NoBody) - resp.Header.Set(pollers.HeaderAzureAsync, fakePollingURL) - poller, err := New(resp, "", "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if s := poller.Status(); s != "InProgress" { - t.Fatalf("unexpected status %s", s) - } -} - -func TestNewFinalGetLocation(t *testing.T) { - const ( - jsonBody = `{ "properties": { "provisioningState": "Started" } }` - locURL = "https://foo.bar.baz/location" - ) - resp := initialResponse(http.MethodPost, strings.NewReader(jsonBody)) - resp.Header.Set(pollers.HeaderAzureAsync, fakePollingURL) - resp.Header.Set(pollers.HeaderLocation, locURL) - poller, err := New(resp, "location", "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if u := poller.FinalGetURL(); u != locURL { - t.Fatalf("unexpected final get URL %s", u) - } - if u := poller.URL(); u != fakePollingURL { - t.Fatalf("unexpected polling URL %s", u) - } -} - -func TestNewFinalGetOrigin(t *testing.T) { - const ( - jsonBody = `{ "properties": { "provisioningState": "Started" } }` - locURL = "https://foo.bar.baz/location" - ) - resp := initialResponse(http.MethodPost, strings.NewReader(jsonBody)) - resp.Header.Set(pollers.HeaderAzureAsync, fakePollingURL) - resp.Header.Set(pollers.HeaderLocation, locURL) - poller, err := New(resp, "original-uri", "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if u := poller.FinalGetURL(); u != fakeResourceURL { - t.Fatalf("unexpected final get URL %s", u) - } - if u := poller.URL(); u != fakePollingURL { - t.Fatalf("unexpected polling URL %s", u) - } -} - -func TestNewPutNoProvStateOnUpdate(t *testing.T) { - // missing provisioning state on initial response - // NOTE: ARM RPC forbids this but we allow it for back-compat - resp := initialResponse(http.MethodPut, http.NoBody) - resp.Header.Set(pollers.HeaderAzureAsync, fakePollingURL) - poller, err := New(resp, "", "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if s := poller.Status(); s != "InProgress" { - t.Fatalf("unexpected status %s", s) - } - if err := poller.Update(pollingResponse(strings.NewReader("{}"))); err == nil { - t.Fatal("unexpected nil error") - } -} diff --git a/sdk/armcore/internal/pollers/body/body.go b/sdk/armcore/internal/pollers/body/body.go deleted file mode 100644 index 6402c07ba149..000000000000 --- a/sdk/armcore/internal/pollers/body/body.go +++ /dev/null @@ -1,106 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package body - -import ( - "errors" - "net/http" - - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -// Applicable returns true if the LRO is using no headers, just provisioning state. -// This is only applicable to PATCH and PUT methods and assumes no polling headers. -func Applicable(resp *azcore.Response) bool { - // we can't check for absense of headers due to some misbehaving services - // like redis that return a Location header but don't actually use that protocol - return resp.Request.Method == http.MethodPatch || resp.Request.Method == http.MethodPut -} - -// Poller is an LRO poller that uses the Body pattern. -type Poller struct { - // The poller's type, used for resume token processing. - Type string `json:"type"` - - // The URL for polling. - PollURL string `json:"pollURL"` - - // The LRO's current state. - CurState string `json:"state"` -} - -// New creates a new Poller from the provided initial response. -func New(resp *azcore.Response, pollerID string) (*Poller, error) { - azcore.Log().Write(azcore.LogLongRunningOperation, "Using Body poller.") - p := &Poller{ - Type: pollers.MakeID(pollerID, "body"), - PollURL: resp.Request.URL.String(), - } - // default initial state to InProgress. depending on the HTTP - // status code and provisioning state, we might change the value. - curState := pollers.StatusInProgress - provState, err := pollers.GetProvisioningState(resp) - if err != nil && !errors.Is(err, pollers.ErrNoBody) { - return nil, err - } - if resp.StatusCode == http.StatusCreated && provState != "" { - // absense of provisioning state is ok for a 201, means the operation is in progress - curState = provState - } else if resp.StatusCode == http.StatusOK { - if provState != "" { - curState = provState - } else if provState == "" { - // for a 200, absense of provisioning state indicates success - curState = pollers.StatusSucceeded - } - } else if resp.StatusCode == http.StatusNoContent { - curState = pollers.StatusSucceeded - } - p.CurState = curState - return p, nil -} - -// URL returns the polling URL. -func (p *Poller) URL() string { - return p.PollURL -} - -// Done returns true if the LRO has reached a terminal state. -func (p *Poller) Done() bool { - return pollers.IsTerminalState(p.Status()) -} - -// Update updates the Poller from the polling response. -func (p *Poller) Update(resp *azcore.Response) error { - if resp.StatusCode == http.StatusNoContent { - p.CurState = pollers.StatusSucceeded - return nil - } - state, err := pollers.GetProvisioningState(resp) - if errors.Is(err, pollers.ErrNoBody) { - // a missing response body in non-204 case is an error - return err - } else if state == "" { - // a response body without provisioning state is considered terminal success - state = pollers.StatusSucceeded - } else if err != nil { - return err - } - p.CurState = state - return nil -} - -// FinalGetURL returns the empty string as no final GET is required for this poller type. -func (*Poller) FinalGetURL() string { - return "" -} - -// Status returns the status of the LRO. -func (p *Poller) Status() string { - return p.CurState -} diff --git a/sdk/armcore/internal/pollers/body/body_test.go b/sdk/armcore/internal/pollers/body/body_test.go deleted file mode 100644 index 50f4211a80dd..000000000000 --- a/sdk/armcore/internal/pollers/body/body_test.go +++ /dev/null @@ -1,214 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package body - -import ( - "errors" - "io" - "io/ioutil" - "net/http" - "strings" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -const ( - fakeResourceURL = "https://foo.bar.baz/resource" -) - -func initialResponse(method string, resp io.Reader) *azcore.Response { - req, err := http.NewRequest(method, fakeResourceURL, nil) - if err != nil { - panic(err) - } - return &azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(resp), - Header: http.Header{}, - Request: req, - }, - } -} - -func pollingResponse(status int, resp io.Reader) *azcore.Response { - return &azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(resp), - Header: http.Header{}, - StatusCode: status, - }, - } -} - -func TestApplicable(t *testing.T) { - resp := azcore.Response{ - Response: &http.Response{ - Header: http.Header{}, - Request: &http.Request{ - Method: http.MethodDelete, - }, - }, - } - if Applicable(&resp) { - t.Fatal("method DELETE should not be applicable") - } - resp.Request.Method = http.MethodPatch - if !Applicable(&resp) { - t.Fatal("method PATCH should be applicable") - } - resp.Request.Method = http.MethodPut - if !Applicable(&resp) { - t.Fatal("method PUT should be applicable") - } -} - -func TestNew(t *testing.T) { - const jsonBody = `{ "properties": { "provisioningState": "Started" } }` - resp := initialResponse(http.MethodPut, strings.NewReader(jsonBody)) - resp.StatusCode = http.StatusCreated - poller, err := New(resp, "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if u := poller.FinalGetURL(); u != "" { - t.Fatal("expected empty final GET URL") - } - if s := poller.Status(); s != "Started" { - t.Fatalf("unexpected status %s", s) - } - if u := poller.URL(); u != fakeResourceURL { - t.Fatalf("unexpected polling URL %s", u) - } - if err := poller.Update(pollingResponse(http.StatusOK, strings.NewReader(`{ "properties": { "provisioningState": "InProgress" } }`))); err != nil { - t.Fatal(err) - } - if s := poller.Status(); s != "InProgress" { - t.Fatalf("unexpected status %s", s) - } -} - -func TestUpdateNoProvStateFail(t *testing.T) { - const jsonBody = `{ "properties": { "provisioningState": "Started" } }` - resp := initialResponse(http.MethodPut, strings.NewReader(jsonBody)) - resp.StatusCode = http.StatusOK - poller, err := New(resp, "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if u := poller.FinalGetURL(); u != "" { - t.Fatal("expected empty final GET URL") - } - if s := poller.Status(); s != "Started" { - t.Fatalf("unexpected status %s", s) - } - if u := poller.URL(); u != fakeResourceURL { - t.Fatalf("unexpected polling URL %s", u) - } - err = poller.Update(pollingResponse(http.StatusOK, http.NoBody)) - if err == nil { - t.Fatal("unexpected nil error") - } - if !errors.Is(err, pollers.ErrNoBody) { - t.Fatalf("unexpected error type %T", err) - } -} - -func TestUpdateNoProvStateSuccess(t *testing.T) { - const jsonBody = `{ "properties": { "provisioningState": "Started" } }` - resp := initialResponse(http.MethodPut, strings.NewReader(jsonBody)) - resp.StatusCode = http.StatusOK - poller, err := New(resp, "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if u := poller.FinalGetURL(); u != "" { - t.Fatal("expected empty final GET URL") - } - if s := poller.Status(); s != "Started" { - t.Fatalf("unexpected status %s", s) - } - if u := poller.URL(); u != fakeResourceURL { - t.Fatalf("unexpected polling URL %s", u) - } - err = poller.Update(pollingResponse(http.StatusOK, strings.NewReader(`{}`))) - if err != nil { - t.Fatal(err) - } -} - -func TestUpdateNoProvState204(t *testing.T) { - const jsonBody = `{ "properties": { "provisioningState": "Started" } }` - resp := initialResponse(http.MethodPut, strings.NewReader(jsonBody)) - resp.StatusCode = http.StatusOK - poller, err := New(resp, "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if u := poller.FinalGetURL(); u != "" { - t.Fatal("expected empty final GET URL") - } - if s := poller.Status(); s != "Started" { - t.Fatalf("unexpected status %s", s) - } - if u := poller.URL(); u != fakeResourceURL { - t.Fatalf("unexpected polling URL %s", u) - } - err = poller.Update(pollingResponse(http.StatusNoContent, http.NoBody)) - if err != nil { - t.Fatal(err) - } -} - -func TestNewNoInitialProvStateOK(t *testing.T) { - resp := initialResponse(http.MethodPut, http.NoBody) - resp.StatusCode = http.StatusOK - poller, err := New(resp, "pollerID") - if err != nil { - t.Fatal(err) - } - if !poller.Done() { - t.Fatal("poller not be done") - } - if u := poller.FinalGetURL(); u != "" { - t.Fatal("expected empty final GET URL") - } - if s := poller.Status(); s != "Succeeded" { - t.Fatalf("unexpected status %s", s) - } -} - -func TestNewNoInitialProvStateNC(t *testing.T) { - resp := initialResponse(http.MethodPut, http.NoBody) - resp.StatusCode = http.StatusNoContent - poller, err := New(resp, "pollerID") - if err != nil { - t.Fatal(err) - } - if !poller.Done() { - t.Fatal("poller not be done") - } - if u := poller.FinalGetURL(); u != "" { - t.Fatal("expected empty final GET URL") - } - if s := poller.Status(); s != "Succeeded" { - t.Fatalf("unexpected status %s", s) - } -} diff --git a/sdk/armcore/internal/pollers/loc/loc.go b/sdk/armcore/internal/pollers/loc/loc.go deleted file mode 100644 index 3946322cbd38..000000000000 --- a/sdk/armcore/internal/pollers/loc/loc.go +++ /dev/null @@ -1,98 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package loc - -import ( - "errors" - "fmt" - "net/http" - - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -// Applicable returns true if the LRO is using Location. -func Applicable(resp *azcore.Response) bool { - return resp.StatusCode == http.StatusAccepted && resp.Header.Get(pollers.HeaderLocation) != "" -} - -// Poller is an LRO poller that uses the Location pattern. -type Poller struct { - // The poller's type, used for resume token processing. - Type string `json:"type"` - - // The URL for polling. - PollURL string `json:"pollURL"` - - // The LRO's current state. - CurState string `json:"state"` -} - -// New creates a new Poller from the provided initial response. -func New(resp *azcore.Response, pollerID string) (*Poller, error) { - azcore.Log().Write(azcore.LogLongRunningOperation, "Using Location poller.") - locURL := resp.Header.Get(pollers.HeaderLocation) - if locURL == "" { - return nil, errors.New("response is missing Location header") - } - if !pollers.IsValidURL(locURL) { - return nil, fmt.Errorf("invalid polling URL %s", locURL) - } - p := &Poller{ - Type: pollers.MakeID(pollerID, "loc"), - PollURL: locURL, - CurState: pollers.StatusInProgress, - } - return p, nil -} - -// URL returns the polling URL. -func (p *Poller) URL() string { - return p.PollURL -} - -// Done returns true if the LRO has reached a terminal state. -func (p *Poller) Done() bool { - return pollers.IsTerminalState(p.Status()) -} - -// Update updates the Poller from the polling response. -func (p *Poller) Update(resp *azcore.Response) error { - // location polling can return an updated polling URL - if h := resp.Header.Get(pollers.HeaderLocation); h != "" { - p.PollURL = h - } - if resp.HasStatusCode(http.StatusOK, http.StatusCreated) { - // if a 200/201 returns a provisioning state, use that instead - state, err := pollers.GetProvisioningState(resp) - if err != nil && !errors.Is(err, pollers.ErrNoBody) { - return err - } - if state != "" { - p.CurState = state - } else { - // a 200/201 with no provisioning state indicates success - p.CurState = pollers.StatusSucceeded - } - } else if resp.StatusCode == http.StatusNoContent { - p.CurState = pollers.StatusSucceeded - } else if resp.StatusCode > 399 && resp.StatusCode < 500 { - p.CurState = pollers.StatusFailed - } - // a 202 falls through, means the LRO is still in progress and we don't check for provisioning state - return nil -} - -// FinalGetURL returns the empty string as no final GET is required for this poller type. -func (p *Poller) FinalGetURL() string { - return "" -} - -// Status returns the status of the LRO. -func (p *Poller) Status() string { - return p.CurState -} diff --git a/sdk/armcore/internal/pollers/loc/loc_test.go b/sdk/armcore/internal/pollers/loc/loc_test.go deleted file mode 100644 index 1cacefded4d4..000000000000 --- a/sdk/armcore/internal/pollers/loc/loc_test.go +++ /dev/null @@ -1,140 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package loc - -import ( - "io" - "io/ioutil" - "net/http" - "strings" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -const ( - fakePollingURL1 = "https://foo.bar.baz/status" - fakePollingURL2 = "https://foo.bar.baz/updated" -) - -func initialResponse(method string) *azcore.Response { - return &azcore.Response{ - Response: &http.Response{ - Header: http.Header{}, - StatusCode: http.StatusAccepted, - }, - } -} - -func pollingResponse(statusCode int, body io.Reader) *azcore.Response { - return &azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(body), - Header: http.Header{}, - StatusCode: statusCode, - }, - } -} - -func TestApplicable(t *testing.T) { - resp := azcore.Response{ - Response: &http.Response{ - Header: http.Header{}, - StatusCode: http.StatusAccepted, - }, - } - if Applicable(&resp) { - t.Fatal("missing Location should not be applicable") - } - resp.Response.Header.Set(pollers.HeaderLocation, fakePollingURL1) - if !Applicable(&resp) { - t.Fatal("having Location should be applicable") - } -} - -func TestNew(t *testing.T) { - resp := initialResponse(http.MethodPut) - resp.Header.Set(pollers.HeaderLocation, fakePollingURL1) - poller, err := New(resp, "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if u := poller.FinalGetURL(); u != "" { - t.Fatal("expected empty final GET URL") - } - if s := poller.Status(); s != "InProgress" { - t.Fatalf("unexpected status %s", s) - } - if u := poller.URL(); u != fakePollingURL1 { - t.Fatalf("unexpected polling URL %s", u) - } - pr := pollingResponse(http.StatusAccepted, http.NoBody) - pr.Header.Set(pollers.HeaderLocation, fakePollingURL2) - if err := poller.Update(pr); err != nil { - t.Fatal(err) - } - if u := poller.URL(); u != fakePollingURL2 { - t.Fatalf("unexpected polling URL %s", u) - } - if err := poller.Update(pollingResponse(http.StatusNoContent, http.NoBody)); err != nil { - t.Fatal(err) - } - if s := poller.Status(); s != "Succeeded" { - t.Fatalf("unexpected status %s", s) - } - if err := poller.Update(pollingResponse(http.StatusConflict, http.NoBody)); err != nil { - t.Fatal(err) - } - if s := poller.Status(); s != "Failed" { - t.Fatalf("unexpected status %s", s) - } -} - -func TestUpdateWithProvState(t *testing.T) { - resp := initialResponse(http.MethodPut) - resp.Header.Set(pollers.HeaderLocation, fakePollingURL1) - poller, err := New(resp, "pollerID") - if err != nil { - t.Fatal(err) - } - if poller.Done() { - t.Fatal("poller should not be done") - } - if u := poller.FinalGetURL(); u != "" { - t.Fatal("expected empty final GET URL") - } - if s := poller.Status(); s != "InProgress" { - t.Fatalf("unexpected status %s", s) - } - if u := poller.URL(); u != fakePollingURL1 { - t.Fatalf("unexpected polling URL %s", u) - } - pr := pollingResponse(http.StatusAccepted, http.NoBody) - pr.Header.Set(pollers.HeaderLocation, fakePollingURL2) - if err := poller.Update(pr); err != nil { - t.Fatal(err) - } - if u := poller.URL(); u != fakePollingURL2 { - t.Fatalf("unexpected polling URL %s", u) - } - if err := poller.Update(pollingResponse(http.StatusOK, strings.NewReader(`{ "properties": { "provisioningState": "Updating" } }`))); err != nil { - t.Fatal(err) - } - if s := poller.Status(); s != "Updating" { - t.Fatalf("unexpected status %s", s) - } - if err := poller.Update(pollingResponse(http.StatusOK, http.NoBody)); err != nil { - t.Fatal(err) - } - if s := poller.Status(); s != "Succeeded" { - t.Fatalf("unexpected status %s", s) - } -} diff --git a/sdk/armcore/internal/pollers/pollers.go b/sdk/armcore/internal/pollers/pollers.go deleted file mode 100644 index 955d7fc32251..000000000000 --- a/sdk/armcore/internal/pollers/pollers.go +++ /dev/null @@ -1,149 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package pollers - -import ( - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/url" - "strings" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -const ( - HeaderAzureAsync = "Azure-AsyncOperation" - HeaderLocation = "Location" -) - -const ( - StatusSucceeded = "Succeeded" - StatusCanceled = "Canceled" - StatusFailed = "Failed" - StatusInProgress = "InProgress" -) - -// reads the response body into a raw JSON object. -// returns ErrNoBody if there was no content. -func getJSON(resp *azcore.Response) (map[string]interface{}, error) { - body, err := ioutil.ReadAll(resp.Body) - defer resp.Body.Close() - if err != nil { - return nil, err - } - if len(body) == 0 { - return nil, ErrNoBody - } - // put the body back so it's available to others - resp.Body = ioutil.NopCloser(bytes.NewReader(body)) - // unmarshall the body to get the value - var jsonBody map[string]interface{} - if err = json.Unmarshal(body, &jsonBody); err != nil { - return nil, err - } - return jsonBody, nil -} - -// provisioningState returns the provisioning state from the response or the empty string. -func provisioningState(jsonBody map[string]interface{}) string { - jsonProps, ok := jsonBody["properties"] - if !ok { - return "" - } - props, ok := jsonProps.(map[string]interface{}) - if !ok { - return "" - } - rawPs, ok := props["provisioningState"] - if !ok { - return "" - } - ps, ok := rawPs.(string) - if !ok { - return "" - } - return ps -} - -// status returns the status from the response or the empty string. -func status(jsonBody map[string]interface{}) string { - rawStatus, ok := jsonBody["status"] - if !ok { - return "" - } - status, ok := rawStatus.(string) - if !ok { - return "" - } - return status -} - -// IsTerminalState returns true if the LRO's state is terminal. -func IsTerminalState(s string) bool { - return strings.EqualFold(s, StatusSucceeded) || strings.EqualFold(s, StatusFailed) || strings.EqualFold(s, StatusCanceled) -} - -// Failed returns true if the LRO's state is terminal failure. -func Failed(s string) bool { - return strings.EqualFold(s, StatusFailed) || strings.EqualFold(s, StatusCanceled) -} - -// GetStatus returns the LRO's status from the response body. -// Typically used for Azure-AsyncOperation flows. -// If there is no status in the response body the empty string is returned. -func GetStatus(resp *azcore.Response) (string, error) { - jsonBody, err := getJSON(resp) - if err != nil { - return "", err - } - return status(jsonBody), nil -} - -// GetProvisioningState returns the LRO's state from the response body. -// If there is no state in the response body the empty string is returned. -func GetProvisioningState(resp *azcore.Response) (string, error) { - jsonBody, err := getJSON(resp) - if err != nil { - return "", err - } - return provisioningState(jsonBody), nil -} - -// IsValidURL verifies that the URL is valid and absolute. -func IsValidURL(s string) bool { - u, err := url.Parse(s) - return err == nil && u.IsAbs() -} - -const idSeparator = ";" - -// MakeID returns the poller ID from the provided values. -func MakeID(pollerID string, kind string) string { - return fmt.Sprintf("%s%s%s", pollerID, idSeparator, kind) -} - -// DecodeID decodes the poller ID, returning [pollerID, kind] or an error. -func DecodeID(tk string) (string, string, error) { - raw := strings.Split(tk, idSeparator) - // strings.Split will include any/all whitespace strings, we want to omit those - parts := []string{} - for _, r := range raw { - if s := strings.TrimSpace(r); s != "" { - parts = append(parts, s) - } - } - if len(parts) != 2 { - return "", "", fmt.Errorf("invalid token %s", tk) - } - return parts[0], parts[1], nil -} - -// ErrNoBody is returned if the response didn't contain a body. -var ErrNoBody = errors.New("the response did not contain a body") diff --git a/sdk/armcore/internal/pollers/pollers_test.go b/sdk/armcore/internal/pollers/pollers_test.go deleted file mode 100644 index 34644f3d5df9..000000000000 --- a/sdk/armcore/internal/pollers/pollers_test.go +++ /dev/null @@ -1,185 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package pollers - -import ( - "errors" - "io/ioutil" - "net/http" - "strings" - "testing" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -func TestIsTerminalState(t *testing.T) { - if IsTerminalState("Updating") { - t.Fatal("Updating is not a terminal state") - } - if !IsTerminalState("Succeeded") { - t.Fatal("Succeeded is a terminal state") - } - if !IsTerminalState("failed") { - t.Fatal("failed is a terminal state") - } - if !IsTerminalState("canceled") { - t.Fatal("canceled is a terminal state") - } -} - -func TestGetStatusSuccess(t *testing.T) { - const jsonBody = `{ "status": "InProgress" }` - resp := azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(strings.NewReader(jsonBody)), - }, - } - status, err := GetStatus(&resp) - if err != nil { - t.Fatal(err) - } - if status != "InProgress" { - t.Fatalf("unexpected status %s", status) - } -} - -func TestGetNoBody(t *testing.T) { - resp := azcore.Response{ - Response: &http.Response{ - Body: http.NoBody, - }, - } - status, err := GetStatus(&resp) - if !errors.Is(err, ErrNoBody) { - t.Fatalf("unexpected error %T", err) - } - if status != "" { - t.Fatal("expected empty status") - } - status, err = GetProvisioningState(&resp) - if !errors.Is(err, ErrNoBody) { - t.Fatalf("unexpected error %T", err) - } - if status != "" { - t.Fatal("expected empty status") - } -} - -func TestGetStatusError(t *testing.T) { - resp := azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(strings.NewReader("{}")), - }, - } - status, err := GetStatus(&resp) - if err != nil { - t.Fatal(err) - } - if status != "" { - t.Fatalf("expected empty status, got %s", status) - } -} - -func TestGetProvisioningState(t *testing.T) { - const jsonBody = `{ "properties": { "provisioningState": "Canceled" } }` - resp := azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(strings.NewReader(jsonBody)), - }, - } - state, err := GetProvisioningState(&resp) - if err != nil { - t.Fatal(err) - } - if state != "Canceled" { - t.Fatalf("unexpected status %s", state) - } -} - -func TestGetProvisioningStateError(t *testing.T) { - resp := azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(strings.NewReader("{}")), - }, - } - state, err := GetProvisioningState(&resp) - if err != nil { - t.Fatal(err) - } - if state != "" { - t.Fatalf("expected empty provisioning state, got %s", state) - } -} - -func TestMakeID(t *testing.T) { - const ( - pollerID = "pollerID" - kind = "kind" - ) - id := MakeID(pollerID, kind) - parts := strings.Split(id, idSeparator) - if l := len(parts); l != 2 { - t.Fatalf("unexpected length %d", l) - } - if p := parts[0]; p != pollerID { - t.Fatalf("unexpected poller ID %s", p) - } - if p := parts[1]; p != kind { - t.Fatalf("unexpected poller kind %s", p) - } -} - -func TestDecodeID(t *testing.T) { - _, _, err := DecodeID("") - if err == nil { - t.Fatal("unexpected nil error") - } - _, _, err = DecodeID("invalid_token") - if err == nil { - t.Fatal("unexpected nil error") - } - _, _, err = DecodeID("invalid_token;") - if err == nil { - t.Fatal("unexpected nil error") - } - _, _, err = DecodeID(" ;invalid_token") - if err == nil { - t.Fatal("unexpected nil error") - } - _, _, err = DecodeID("invalid;token;too") - if err == nil { - t.Fatal("unexpected nil error") - } - id, kind, err := DecodeID("pollerID;kind") - if err != nil { - t.Fatal(err) - } - if id != "pollerID" { - t.Fatalf("unexpected ID %s", id) - } - if kind != "kind" { - t.Fatalf("unexpected kin %s", kind) - } -} - -func TestIsValidURL(t *testing.T) { - if IsValidURL("/foo") { - t.Fatal("unexpected valid URL") - } - if !IsValidURL("https://foo.bar/baz") { - t.Fatal("expected valid URL") - } -} - -func TestFailed(t *testing.T) { - if Failed("Succeeded") || Failed("Updating") { - t.Fatal("unexpected failure") - } - if !Failed("failed") { - t.Fatal("expected failure") - } -} diff --git a/sdk/armcore/policy_register_rp.go b/sdk/armcore/policy_register_rp.go deleted file mode 100644 index f7fdaa2c32a4..000000000000 --- a/sdk/armcore/policy_register_rp.go +++ /dev/null @@ -1,385 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package armcore - -import ( - "context" - "errors" - "fmt" - "io/ioutil" - "net/http" - "net/url" - "strings" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - sdkruntime "github.com/Azure/azure-sdk-for-go/sdk/internal/runtime" -) - -const ( - // LogRPRegistration entries contain information specific to the automatic registration of an RP. - // Entries of this classification are written IFF the policy needs to take any action. - LogRPRegistration azcore.LogClassification = "RPRegistration" -) - -// RegistrationOptions configures the registration policy's behavior. -// All zero-value fields will be initialized with their default values. -type RegistrationOptions struct { - // MaxAttempts is the total number of times to attempt automatic registration - // in the event that an attempt fails. - // The default value is 3. - // Set to a value less than zero to disable the policy. - MaxAttempts int - - // PollingDelay is the amount of time to sleep between polling intervals. - // The default value is 15 seconds. - // A value less than zero means no delay between polling intervals (not recommended). - PollingDelay time.Duration - - // PollingDuration is the amount of time to wait before abandoning polling. - // The default valule is 5 minutes. - // NOTE: Setting this to a small value might cause the policy to prematurely fail. - PollingDuration time.Duration - - // HTTPClient sets the transport for making HTTP requests. - HTTPClient azcore.Transport - - // Retry configures the built-in retry policy behavior. - Retry azcore.RetryOptions - - // Telemetry configures the built-in telemetry policy behavior. - Telemetry azcore.TelemetryOptions - - // Logging configures the built-in logging policy behavior. - Logging azcore.LogOptions -} - -// init sets any default values -func (r *RegistrationOptions) init() { - if r.MaxAttempts == 0 { - r.MaxAttempts = 3 - } else if r.MaxAttempts < 0 { - r.MaxAttempts = 0 - } - if r.PollingDelay == 0 { - r.PollingDelay = 15 * time.Second - } else if r.PollingDelay < 0 { - r.PollingDelay = 0 - } - if r.PollingDuration == 0 { - r.PollingDuration = 5 * time.Minute - } -} - -// 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 a zero-value options. -func NewRPRegistrationPolicy(endpoint string, cred azcore.Credential, o *RegistrationOptions) azcore.Policy { - if o == nil { - o = &RegistrationOptions{} - } - p := &rpRegistrationPolicy{ - endpoint: endpoint, - pipeline: azcore.NewPipeline(o.HTTPClient, - azcore.NewTelemetryPolicy(&o.Telemetry), - azcore.NewRetryPolicy(&o.Retry), - cred.NewAuthenticationPolicy(azcore.AuthenticationOptions{TokenRequest: azcore.TokenRequestOptions{Scopes: []string{endpointToScope(endpoint)}}}), - azcore.NewLogPolicy(&o.Logging)), - options: *o, - } - // init the copy - p.options.init() - return p -} - -type rpRegistrationPolicy struct { - endpoint string - pipeline azcore.Pipeline - options RegistrationOptions -} - -func (r *rpRegistrationPolicy) Do(req *azcore.Request) (*azcore.Response, error) { - if r.options.MaxAttempts == 0 { - // policy is disabled - return req.Next() - } - const unregisteredRPCode = "MissingSubscriptionRegistration" - const registeredState = "Registered" - var rp string - var resp *azcore.Response - for attempts := 0; attempts < r.options.MaxAttempts; attempts++ { - var err error - // make the original request - resp, err = req.Next() - // getting a 409 is the first indication that the RP might need to be registered, check error response - if err != nil || resp.StatusCode != http.StatusConflict { - return resp, err - } - var reqErr requestError - if err = resp.UnmarshalAsJSON(&reqErr); err != nil { - return resp, newFrameError(err) - } - if reqErr.ServiceError == nil { - return resp, newFrameError(errors.New("missing error information")) - } - if !strings.EqualFold(reqErr.ServiceError.Code, unregisteredRPCode) { - // not a 409 due to unregistered RP - return resp, err - } - // RP needs to be registered. start by getting the subscription ID from the original request - subID, err := getSubscription(req.URL.Path) - if err != nil { - return resp, newFrameError(err) - } - // now get the RP from the error - rp, err = getProvider(reqErr) - if err != nil { - return resp, newFrameError(err) - } - logRegistrationExit := func(v interface{}) { - azcore.Log().Writef(LogRPRegistration, "END registration for %s: %v", rp, v) - } - azcore.Log().Writef(LogRPRegistration, "BEGIN registration for %s", rp) - // create client and make the registration request - // we use the scheme and host from the original request - rpOps := &providersOperations{ - p: r.pipeline, - u: r.endpoint, - subID: subID, - } - if _, err = rpOps.Register(req.Context(), rp); err != nil { - logRegistrationExit(err) - return resp, err - } - // RP was registered, however we need to wait for the registration to complete - pollCtx, pollCancel := context.WithTimeout(req.Context(), r.options.PollingDuration) - var lastRegState string - for { - // get the current registration state - getResp, err := rpOps.Get(pollCtx, rp) - if err != nil { - pollCancel() - logRegistrationExit(err) - return resp, err - } - if getResp.Provider.RegistrationState != nil && !strings.EqualFold(*getResp.Provider.RegistrationState, lastRegState) { - // registration state has changed, or was updated for the first time - lastRegState = *getResp.Provider.RegistrationState - azcore.Log().Writef(LogRPRegistration, "registration state is %s", lastRegState) - } - if strings.EqualFold(lastRegState, registeredState) { - // registration complete - pollCancel() - logRegistrationExit(lastRegState) - break - } - // wait before trying again - select { - case <-time.After(r.options.PollingDelay): - // continue polling - case <-pollCtx.Done(): - pollCancel() - logRegistrationExit(pollCtx.Err()) - return resp, pollCtx.Err() - } - } - // RP was successfully registered, retry the original request - err = req.RewindBody() - if err != nil { - return resp, newFrameError(err) - } - } - // if we get here it means we exceeded the number of attempts - return resp, fmt.Errorf("exceeded attempts to register %s", rp) -} - -func newFrameError(inner error) error { - // skip ourselves - return sdkruntime.NewFrameError(inner, false, 1, azcore.StackFrameCount) -} - -func getSubscription(path string) (string, error) { - parts := strings.Split(path, "/") - for i, v := range parts { - if v == "subscriptions" && (i+1) < len(parts) { - return parts[i+1], nil - } - } - return "", fmt.Errorf("failed to obtain subscription ID from %s", path) -} - -func getProvider(re requestError) (string, error) { - if len(re.ServiceError.Details) > 0 { - return re.ServiceError.Details[0].Target, nil - } - return "", errors.New("unexpected empty Details") -} - -// minimal error definitions to simplify detection -type requestError struct { - ServiceError *serviceError `json:"error"` -} - -type serviceError struct { - Code string `json:"code"` - Details []serviceErrorDetails `json:"details"` -} - -type serviceErrorDetails struct { - Code string `json:"code"` - Target string `json:"target"` -} - -/////////////////////////////////////////////////////////////////////////////////////////////// -// the following code was copied from module armresources, providers.go and models.go -// only the minimum amount of code was copied to get this working and some edits were made. -/////////////////////////////////////////////////////////////////////////////////////////////// - -type providersOperations struct { - p azcore.Pipeline - u string - subID string -} - -// Get - Gets the specified resource provider. -func (client *providersOperations) Get(ctx context.Context, resourceProviderNamespace string) (*ProviderResponse, error) { - req, err := client.getCreateRequest(ctx, resourceProviderNamespace) - if err != nil { - return nil, err - } - resp, err := client.p.Do(req) - if err != nil { - return nil, err - } - result, err := client.getHandleResponse(resp) - if err != nil { - return nil, err - } - return result, nil -} - -// getCreateRequest creates the Get request. -func (client *providersOperations) getCreateRequest(ctx context.Context, resourceProviderNamespace string) (*azcore.Request, error) { - urlPath := "/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}" - urlPath = strings.ReplaceAll(urlPath, "{resourceProviderNamespace}", url.PathEscape(resourceProviderNamespace)) - urlPath = strings.ReplaceAll(urlPath, "{subscriptionId}", url.PathEscape(client.subID)) - req, err := azcore.NewRequest(ctx, http.MethodGet, azcore.JoinPaths(client.u, urlPath)) - if err != nil { - return nil, newFrameError(err) - } - query := req.URL.Query() - query.Set("api-version", "2019-05-01") - req.URL.RawQuery = query.Encode() - return req, nil -} - -// getHandleResponse handles the Get response. -func (client *providersOperations) getHandleResponse(resp *azcore.Response) (*ProviderResponse, error) { - if !resp.HasStatusCode(http.StatusOK) { - return nil, client.getHandleError(resp) - } - result := ProviderResponse{RawResponse: resp.Response} - err := resp.UnmarshalAsJSON(&result.Provider) - if err != nil { - err = newFrameError(err) - } - return &result, err -} - -// getHandleError handles the Get error response. -func (client *providersOperations) getHandleError(resp *azcore.Response) error { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return sdkruntime.NewResponseError(newFrameError(err), resp.Response) - } - if len(body) == 0 { - return sdkruntime.NewResponseError(errors.New(resp.Status), resp.Response) - } - return sdkruntime.NewResponseError(errors.New(string(body)), resp.Response) -} - -// Register - Registers a subscription with a resource provider. -func (client *providersOperations) Register(ctx context.Context, resourceProviderNamespace string) (*ProviderResponse, error) { - req, err := client.registerCreateRequest(ctx, resourceProviderNamespace) - if err != nil { - return nil, err - } - resp, err := client.p.Do(req) - if err != nil { - return nil, err - } - result, err := client.registerHandleResponse(resp) - if err != nil { - return nil, err - } - return result, nil -} - -// registerCreateRequest creates the Register request. -func (client *providersOperations) registerCreateRequest(ctx context.Context, resourceProviderNamespace string) (*azcore.Request, error) { - urlPath := "/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}/register" - urlPath = strings.ReplaceAll(urlPath, "{resourceProviderNamespace}", url.PathEscape(resourceProviderNamespace)) - urlPath = strings.ReplaceAll(urlPath, "{subscriptionId}", url.PathEscape(client.subID)) - req, err := azcore.NewRequest(ctx, http.MethodPost, azcore.JoinPaths(client.u, urlPath)) - if err != nil { - return nil, newFrameError(err) - } - query := req.URL.Query() - query.Set("api-version", "2019-05-01") - req.URL.RawQuery = query.Encode() - return req, nil -} - -// registerHandleResponse handles the Register response. -func (client *providersOperations) registerHandleResponse(resp *azcore.Response) (*ProviderResponse, error) { - if !resp.HasStatusCode(http.StatusOK) { - return nil, client.registerHandleError(resp) - } - result := ProviderResponse{RawResponse: resp.Response} - err := resp.UnmarshalAsJSON(&result.Provider) - if err != nil { - err = newFrameError(err) - } - return &result, err -} - -// registerHandleError handles the Register error response. -func (client *providersOperations) registerHandleError(resp *azcore.Response) error { - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return sdkruntime.NewResponseError(newFrameError(err), resp.Response) - } - if len(body) == 0 { - return sdkruntime.NewResponseError(errors.New(resp.Status), resp.Response) - } - return sdkruntime.NewResponseError(errors.New(string(body)), resp.Response) -} - -// ProviderResponse is the response envelope for operations that return a Provider type. -type ProviderResponse struct { - // Resource provider information. - Provider *Provider - - // RawResponse contains the underlying HTTP response. - RawResponse *http.Response -} - -// Provider - Resource provider information. -type Provider struct { - // The provider ID. - ID *string `json:"id,omitempty"` - - // The namespace of the resource provider. - Namespace *string `json:"namespace,omitempty"` - - // The registration policy of the resource provider. - RegistrationPolicy *string `json:"registrationPolicy,omitempty"` - - // The registration state of the resource provider. - RegistrationState *string `json:"registrationState,omitempty"` -} diff --git a/sdk/armcore/policy_register_rp_test.go b/sdk/armcore/policy_register_rp_test.go deleted file mode 100644 index 15650747c22e..000000000000 --- a/sdk/armcore/policy_register_rp_test.go +++ /dev/null @@ -1,353 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package armcore - -import ( - "context" - "errors" - "net/http" - "strings" - "sync" - "testing" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -const rpUnregisteredResp = `{ - "error":{ - "code":"MissingSubscriptionRegistration", - "message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.Storage'. See https://aka.ms/rps-not-found for how to register subscriptions.", - "details":[{ - "code":"MissingSubscriptionRegistration", - "target":"Microsoft.Storage", - "message":"The subscription registration is in 'Unregistered' state. The subscription must be registered to use namespace 'Microsoft.Storage'. See https://aka.ms/rps-not-found for how to register subscriptions." - } - ] - } -}` - -// some content was omitted here as it's not relevant -const rpRegisteringResp = `{ - "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Storage", - "namespace": "Microsoft.Storage", - "registrationState": "Registering", - "registrationPolicy": "RegistrationRequired" -}` - -// some content was omitted here as it's not relevant -const rpRegisteredResp = `{ - "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Storage", - "namespace": "Microsoft.Storage", - "registrationState": "Registered", - "registrationPolicy": "RegistrationRequired" -}` - -const requestEndpoint = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/fakeResourceGroupo/providers/Microsoft.Storage/storageAccounts/fakeAccountName" - -func testRPRegistrationOptions(t azcore.Transport) *RegistrationOptions { - def := RegistrationOptions{} - def.HTTPClient = t - def.PollingDelay = 100 * time.Millisecond - def.PollingDuration = 1 * time.Second - return &def -} - -func TestRPRegistrationPolicySuccess(t *testing.T) { - srv, close := mock.NewServer() - defer close() - // initial response that RP is unregistered - srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp))) - // polling responses to Register() and Get(), in progress - srv.RepeatResponse(5, mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp))) - // polling response, successful registration - 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(srv.URL(), mockTokenCred{}, testRPRegistrationOptions(srv))) - req, err := azcore.NewRequest(context.Background(), http.MethodGet, azcore.JoinPaths(srv.URL(), requestEndpoint)) - if err != nil { - t.Fatal(err) - } - // log only RP registration - azcore.Log().SetClassifications(LogRPRegistration) - defer func() { - // reset logging - azcore.Log().SetClassifications() - }() - logEntries := 0 - azcore.Log().SetListener(func(cls azcore.LogClassification, msg string) { - logEntries++ - }) - resp, err := pl.Do(req) - if err != nil { - t.Fatal(err) - } - if resp.StatusCode != http.StatusAccepted { - t.Fatalf("unexpected status code %d:", resp.StatusCode) - } - if resp.Request.URL.Path != requestEndpoint { - t.Fatalf("unexpected path in response %s", resp.Request.URL.Path) - } - // should be four entries - // 1st is for start - // 2nd is for first response to get state - // 3rd is when state transitions to success - // 4th is for end - if logEntries != 4 { - t.Fatalf("expected 4 log entries, got %d", logEntries) - } -} - -func TestRPRegistrationPolicyNA(t *testing.T) { - srv, close := mock.NewServer() - defer close() - // response indicates no RP registration is required, policy does nothing - srv.AppendResponse(mock.WithStatusCode(http.StatusOK)) - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.NewAnonymousCredential(), testRPRegistrationOptions(srv))) - req, err := azcore.NewRequest(context.Background(), http.MethodGet, srv.URL()) - if err != nil { - t.Fatal(err) - } - // log only RP registration - azcore.Log().SetClassifications(LogRPRegistration) - defer func() { - // reset logging - azcore.Log().SetClassifications() - }() - azcore.Log().SetListener(func(cls azcore.LogClassification, msg string) { - t.Fatalf("unexpected log entry %s: %s", cls, msg) - }) - resp, err := pl.Do(req) - if err != nil { - t.Fatal(err) - } - if resp.StatusCode != http.StatusOK { - t.Fatalf("unexpected status code %d", resp.StatusCode) - } -} - -func TestRPRegistrationPolicy409Other(t *testing.T) { - const failedResp = `{ - "error":{ - "code":"CannotDoTheThing", - "message":"Something failed in your API call.", - "details":[{ - "code":"ThisIsForTesting", - "message":"This is fake." - } - ] - } - }` - srv, close := mock.NewServer() - 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(srv.URL(), azcore.NewAnonymousCredential(), testRPRegistrationOptions(srv))) - req, err := azcore.NewRequest(context.Background(), http.MethodGet, srv.URL()) - if err != nil { - t.Fatal(err) - } - // log only RP registration - azcore.Log().SetClassifications(LogRPRegistration) - defer func() { - // reset logging - azcore.Log().SetClassifications() - }() - azcore.Log().SetListener(func(cls azcore.LogClassification, msg string) { - t.Fatalf("unexpected log entry %s: %s", cls, msg) - }) - resp, err := pl.Do(req) - if err != nil { - t.Fatal(err) - } - if resp.StatusCode != http.StatusConflict { - t.Fatalf("unexpected status code %d", resp.StatusCode) - } -} - -func TestRPRegistrationPolicyTimesOut(t *testing.T) { - srv, close := mock.NewServer() - defer close() - // initial response that RP is unregistered - srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp))) - // 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(srv.URL(), azcore.NewAnonymousCredential(), testRPRegistrationOptions(srv))) - req, err := azcore.NewRequest(context.Background(), http.MethodGet, azcore.JoinPaths(srv.URL(), requestEndpoint)) - if err != nil { - t.Fatal(err) - } - // log only RP registration - azcore.Log().SetClassifications(LogRPRegistration) - defer func() { - // reset logging - azcore.Log().SetClassifications() - }() - logEntries := 0 - azcore.Log().SetListener(func(cls azcore.LogClassification, msg string) { - logEntries++ - }) - resp, err := pl.Do(req) - if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("expected DeadlineExceeded, got %v", err) - } - // should be three entries - // 1st is for start - // 2nd is for first response to get state - // 3rd is the deadline exceeded error - if logEntries != 3 { - t.Fatalf("expected 3 log entries, got %d", logEntries) - } - // we should get the response from the original request - if resp.StatusCode != http.StatusConflict { - t.Fatalf("unexpected status code %d", resp.StatusCode) - } -} - -func TestRPRegistrationPolicyExceedsAttempts(t *testing.T) { - srv, close := mock.NewServer() - defer close() - // add a cycle of unregistered->registered so that we keep retrying and hit the cap - for i := 0; i < 4; i++ { - // initial response that RP is unregistered - srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp))) - // polling responses to Register() and Get(), in progress - srv.RepeatResponse(2, mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp))) - // polling response, successful registration - srv.AppendResponse(mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteredResp))) - } - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.NewAnonymousCredential(), testRPRegistrationOptions(srv))) - req, err := azcore.NewRequest(context.Background(), http.MethodGet, azcore.JoinPaths(srv.URL(), requestEndpoint)) - if err != nil { - t.Fatal(err) - } - // log only RP registration - azcore.Log().SetClassifications(LogRPRegistration) - defer func() { - // reset logging - azcore.Log().SetClassifications() - }() - logEntries := 0 - azcore.Log().SetListener(func(cls azcore.LogClassification, msg string) { - logEntries++ - }) - resp, err := pl.Do(req) - if err == nil { - t.Fatal("unexpected nil error") - } - if !strings.HasPrefix(err.Error(), "exceeded attempts to register Microsoft.Storage") { - t.Fatalf("unexpected error message %s", err.Error()) - } - if resp.StatusCode != http.StatusConflict { - t.Fatalf("unexpected status code %d:", resp.StatusCode) - } - if resp.Request.URL.Path != requestEndpoint { - t.Fatalf("unexpected path in response %s", resp.Request.URL.Path) - } - // should be 4 entries for each attempt, total 12 entries - // 1st is for start - // 2nd is for first response to get state - // 3rd is when state transitions to success - // 4th is for end - if logEntries != 12 { - t.Fatalf("expected 12 log entries, got %d", logEntries) - } -} - -// test cancelling registration -func TestRPRegistrationPolicyCanCancel(t *testing.T) { - srv, close := mock.NewServer() - defer close() - // initial response that RP is unregistered - srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp))) - // polling responses to Register() and Get(), in progress but slow so we have time to cancel - srv.RepeatResponse(10, mock.WithStatusCode(http.StatusOK), mock.WithBody([]byte(rpRegisteringResp)), mock.WithSlowResponse(300*time.Millisecond)) - opts := RegistrationOptions{} - opts.HTTPClient = srv - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.NewAnonymousCredential(), &opts)) - // log only RP registration - azcore.Log().SetClassifications(LogRPRegistration) - defer func() { - // reset logging - azcore.Log().SetClassifications() - }() - logEntries := 0 - azcore.Log().SetListener(func(cls azcore.LogClassification, msg string) { - logEntries++ - }) - - wg := &sync.WaitGroup{} - wg.Add(1) - - ctx, cancel := context.WithCancel(context.Background()) - var resp *azcore.Response - var err error - go func() { - defer wg.Done() - // create request and start pipeline - var req *azcore.Request - req, err = azcore.NewRequest(ctx, http.MethodGet, azcore.JoinPaths(srv.URL(), requestEndpoint)) - if err != nil { - return - } - resp, err = pl.Do(req) - }() - - // wait for a bit then cancel the operation - time.Sleep(500 * time.Millisecond) - cancel() - wg.Wait() - if !errors.Is(err, context.Canceled) { - t.Fatalf("expected Canceled error, got %v", err) - } - // there should be 1 or 2 entries depending on the timing - if logEntries == 0 { - t.Fatal("didn't get any log entries") - } - // should have original response - if resp.StatusCode != http.StatusConflict { - t.Fatalf("unexpected status code %d", resp.StatusCode) - } -} - -func TestRPRegistrationPolicyDisabled(t *testing.T) { - srv, close := mock.NewServer() - defer close() - // initial response that RP is unregistered - srv.AppendResponse(mock.WithStatusCode(http.StatusConflict), mock.WithBody([]byte(rpUnregisteredResp))) - ops := testRPRegistrationOptions(srv) - ops.MaxAttempts = -1 - pl := azcore.NewPipeline(srv, NewRPRegistrationPolicy(srv.URL(), azcore.NewAnonymousCredential(), ops)) - req, err := azcore.NewRequest(context.Background(), http.MethodGet, azcore.JoinPaths(srv.URL(), requestEndpoint)) - if err != nil { - t.Fatal(err) - } - // log only RP registration - azcore.Log().SetClassifications(LogRPRegistration) - defer func() { - // reset logging - azcore.Log().SetClassifications() - }() - logEntries := 0 - azcore.Log().SetListener(func(cls azcore.LogClassification, msg string) { - logEntries++ - }) - resp, err := pl.Do(req) - if err != nil { - t.Fatal(err) - } - if resp.StatusCode != http.StatusConflict { - t.Fatalf("unexpected status code %d:", resp.StatusCode) - } - // shouldn't be any log entries - if logEntries != 0 { - t.Fatalf("expected 0 log entries, got %d", logEntries) - } -} diff --git a/sdk/armcore/poller.go b/sdk/armcore/poller.go deleted file mode 100644 index 7f0bf643656b..000000000000 --- a/sdk/armcore/poller.go +++ /dev/null @@ -1,313 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package armcore - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers" - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers/async" - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers/body" - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers/loc" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" -) - -// ErrorUnmarshaller is the func to invoke when the endpoint returns an error response that requires unmarshalling. -type ErrorUnmarshaller func(*azcore.Response) error - -// NewLROPoller creates an LROPoller based on the provided initial response. -// pollerID - a unique identifier for an LRO. it's usually the client.Method string. -// NOTE: this is only meant for internal use in generated code. -func NewLROPoller(pollerID string, finalState string, resp *azcore.Response, pl azcore.Pipeline, eu ErrorUnmarshaller) (*LROPoller, error) { - // this is a back-stop in case the swagger is incorrect (i.e. missing one or more status codes for success). - // ideally the codegen should return an error if the initial response failed and not even create a poller. - if !lroStatusCodeValid(resp) { - return nil, errors.New("the LRO failed or was cancelled") - } - // determine the polling method - var lro lroPoller - var err error - if async.Applicable(resp) { - lro, err = async.New(resp, finalState, pollerID) - } else if loc.Applicable(resp) { - lro, err = loc.New(resp, pollerID) - } else if body.Applicable(resp) { - // must test body poller last as it's a subset of the other pollers. - // TODO: this is ambiguous for PATCH/PUT if it returns a 200 with no polling headers (sync completion) - lro, err = body.New(resp, pollerID) - } else if m := resp.Request.Method; resp.StatusCode == http.StatusAccepted && (m == http.MethodDelete || m == http.MethodPost) { - // if we get here it means we have a 202 with no polling headers. - // for DELETE and POST this is a hard error per ARM RPC spec. - return nil, errors.New("response is missing polling URL") - } else { - lro = &nopPoller{} - } - if err != nil { - return nil, err - } - return &LROPoller{lro: lro, Pipeline: pl, eu: eu, resp: resp}, nil -} - -// NewLROPollerFromResumeToken creates an LROPoller from a resume token string. -// pollerID - a unique identifier for an LRO. it's usually the client.Method string. -// NOTE: this is only meant for internal use in generated code. -func NewLROPollerFromResumeToken(pollerID string, token string, pl azcore.Pipeline, eu ErrorUnmarshaller) (*LROPoller, error) { - // unmarshal into JSON object to determine the poller type - obj := map[string]interface{}{} - err := json.Unmarshal([]byte(token), &obj) - if err != nil { - return nil, err - } - t, ok := obj["type"] - if !ok { - return nil, errors.New("missing type field") - } - tt, ok := t.(string) - if !ok { - return nil, fmt.Errorf("invalid type format %T", t) - } - ttID, ttKind, err := pollers.DecodeID(tt) - if err != nil { - return nil, err - } - // ensure poller types match - if ttID != pollerID { - return nil, fmt.Errorf("cannot resume from this poller token. expected %s, received %s", pollerID, ttID) - } - // now rehydrate the poller based on the encoded poller type - var lro lroPoller - switch ttKind { - case "async": - azcore.Log().Write(azcore.LogLongRunningOperation, "Resuming Azure-AsyncOperation poller.") - lro = &async.Poller{} - case "loc": - azcore.Log().Write(azcore.LogLongRunningOperation, "Resuming Location poller.") - lro = &loc.Poller{} - case "body": - azcore.Log().Write(azcore.LogLongRunningOperation, "Resuming Body poller.") - lro = &body.Poller{} - default: - return nil, fmt.Errorf("unhandled poller type %s", ttKind) - } - if err = json.Unmarshal([]byte(token), lro); err != nil { - return nil, err - } - return &LROPoller{lro: lro, Pipeline: pl, eu: eu}, nil -} - -// LROPoller encapsulates state and logic for polling on long-running operations. -// NOTE: this is only meant for internal use in generated code. -type LROPoller struct { - Pipeline azcore.Pipeline - lro lroPoller - eu ErrorUnmarshaller - resp *azcore.Response - err error -} - -// Done returns true if the LRO has reached a terminal state. -func (l *LROPoller) Done() bool { - if l.err != nil { - return true - } - return l.lro.Done() -} - -// Poll sends a polling request to the polling endpoint and returns the response or error. -func (l *LROPoller) Poll(ctx context.Context) (*http.Response, error) { - if l.Done() { - // the LRO has reached a terminal state, don't poll again - if l.resp != nil { - return l.resp.Response, nil - } - return nil, l.err - } - req, err := azcore.NewRequest(ctx, http.MethodGet, l.lro.URL()) - if err != nil { - return nil, err - } - resp, err := l.Pipeline.Do(req) - if err != nil { - // don't update the poller for failed requests - return nil, err - } - defer resp.Body.Close() - if !lroStatusCodeValid(resp) { - // the LRO failed. unmarshall the error and update state - l.err = l.eu(resp) - l.resp = nil - return nil, l.err - } - if err = l.lro.Update(resp); err != nil { - return nil, err - } - l.resp = resp - azcore.Log().Writef(azcore.LogLongRunningOperation, "Status %s", l.lro.Status()) - if pollers.Failed(l.lro.Status()) { - l.err = l.eu(resp) - l.resp = nil - return nil, l.err - } - return l.resp.Response, nil -} - -// ResumeToken returns a token string that can be used to resume a poller that has not yet reached a terminal state. -func (l *LROPoller) ResumeToken() (string, error) { - if l.Done() { - return "", errors.New("cannot create a ResumeToken from a poller in a terminal state") - } - b, err := json.Marshal(l.lro) - if err != nil { - return "", err - } - return string(b), nil -} - -// FinalResponse will perform a final GET request and return the final HTTP response for the polling -// operation and unmarshall the content of the payload into the respType interface that is provided. -func (l *LROPoller) FinalResponse(ctx context.Context, respType interface{}) (*http.Response, error) { - if !l.Done() { - return nil, errors.New("cannot return a final response from a poller in a non-terminal state") - } - // if there's nothing to unmarshall into or no response body just return the final response - if respType == nil { - return l.resp.Response, nil - } else if l.resp.StatusCode == http.StatusNoContent || l.resp.ContentLength == 0 { - azcore.Log().Write(azcore.LogLongRunningOperation, "final response specifies a response type but no payload was received") - return l.resp.Response, nil - } - if u := l.lro.FinalGetURL(); u != "" { - azcore.Log().Write(azcore.LogLongRunningOperation, "Performing final GET.") - req, err := azcore.NewRequest(ctx, http.MethodGet, u) - if err != nil { - return nil, err - } - resp, err := l.Pipeline.Do(req) - if err != nil { - return nil, err - } - if !lroStatusCodeValid(resp) { - return nil, l.eu(resp) - } - l.resp = resp - } - body, err := ioutil.ReadAll(l.resp.Body) - l.resp.Body.Close() - if err != nil { - return nil, err - } - if err = json.Unmarshal(body, respType); err != nil { - return nil, err - } - return l.resp.Response, nil -} - -// PollUntilDone will handle the entire span of the polling operation until a terminal state is reached, -// then return the final HTTP response for the polling operation and unmarshal the content of the payload -// into the respType interface that is provided. -// freq - the time to wait between polling intervals if the endpoint doesn't send a Retry-After header. -// A good starting value is 30 seconds. Note that some resources might benefit from a different value. -func (l *LROPoller) PollUntilDone(ctx context.Context, freq time.Duration, respType interface{}) (*http.Response, error) { - start := time.Now() - logPollUntilDoneExit := func(v interface{}) { - azcore.Log().Writef(azcore.LogLongRunningOperation, "END PollUntilDone() for %T: %v, total time: %s", l.lro, v, time.Since(start)) - } - azcore.Log().Writef(azcore.LogLongRunningOperation, "BEGIN PollUntilDone() for %T", l.lro) - if l.resp != nil { - // initial check for a retry-after header existing on the initial response - if retryAfter := azcore.RetryAfter(l.resp.Response); retryAfter > 0 { - azcore.Log().Writef(azcore.LogLongRunningOperation, "initial Retry-After delay for %s", retryAfter.String()) - if err := delay(ctx, retryAfter); err != nil { - logPollUntilDoneExit(err) - return nil, err - } - } - } - // begin polling the endpoint until a terminal state is reached - for { - resp, err := l.Poll(ctx) - if err != nil { - logPollUntilDoneExit(err) - return nil, err - } - if l.Done() { - logPollUntilDoneExit(l.lro.Status()) - return l.FinalResponse(ctx, respType) - } - d := freq - if retryAfter := azcore.RetryAfter(resp); retryAfter > 0 { - azcore.Log().Writef(azcore.LogLongRunningOperation, "Retry-After delay for %s", retryAfter.String()) - d = retryAfter - } else { - azcore.Log().Writef(azcore.LogLongRunningOperation, "delay for %s", d.String()) - } - if err = delay(ctx, d); err != nil { - logPollUntilDoneExit(err) - return nil, err - } - } -} - -var _ azcore.Poller = (*LROPoller)(nil) - -// abstracts the differences between concrete poller types -type lroPoller interface { - Done() bool - Update(resp *azcore.Response) error - FinalGetURL() string - URL() string - Status() string -} - -// ==================================================================================================== - -// used if the operation synchronously completed -type nopPoller struct{} - -func (*nopPoller) URL() string { - return "" -} - -func (*nopPoller) Done() bool { - return true -} - -func (*nopPoller) Succeeded() bool { - return true -} - -func (*nopPoller) Update(*azcore.Response) error { - return nil -} - -func (*nopPoller) FinalGetURL() string { - return "" -} - -func (*nopPoller) Status() string { - return pollers.StatusSucceeded -} - -// returns true if the LRO response contains a valid HTTP status code -func lroStatusCodeValid(resp *azcore.Response) bool { - return resp.HasStatusCode(http.StatusOK, http.StatusAccepted, http.StatusCreated, http.StatusNoContent) -} - -func delay(ctx context.Context, delay time.Duration) error { - select { - case <-time.After(delay): - return nil - case <-ctx.Done(): - return ctx.Err() - } -} diff --git a/sdk/armcore/poller_test.go b/sdk/armcore/poller_test.go deleted file mode 100644 index 46e0bb0cbb8a..000000000000 --- a/sdk/armcore/poller_test.go +++ /dev/null @@ -1,336 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package armcore - -import ( - "context" - "io" - "io/ioutil" - "net/http" - "strings" - "testing" - "time" - - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers" - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers/async" - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers/body" - "github.com/Azure/azure-sdk-for-go/sdk/armcore/internal/pollers/loc" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/internal/mock" -) - -const ( - provStateStarted = `{ "properties": { "provisioningState": "Started" } }` - provStateUpdating = `{ "properties": { "provisioningState": "Updating" } }` - provStateSucceeded = `{ "properties": { "provisioningState": "Succeeded" }, "field": "value" }` - provStateFailed = `{ "properties": { "provisioningState": "Failed" } }` //nolint - statusInProgress = `{ "status": "InProgress" }` - statusSucceeded = `{ "status": "Succeeded" }` - statusCanceled = `{ "status": "Canceled" }` - successResp = `{ "field": "value" }` - errorResp = `{ "error": "the operation failed" }` -) - -type mockType struct { - Field *string `json:"field,omitempty"` -} - -type mockError struct { - Msg string `json:"error"` -} - -func (m mockError) Error() string { - return m.Msg -} - -func getPipeline(srv *mock.Server) azcore.Pipeline { - return azcore.NewPipeline( - srv, - azcore.NewLogPolicy(nil)) -} - -func handleError(resp *azcore.Response) error { - var me mockError - if err := resp.UnmarshalAsJSON(&me); err != nil { - return err - } - return me -} - -func initialResponse(method, u string, resp io.Reader) *azcore.Response { - req, err := http.NewRequest(method, u, nil) - if err != nil { - panic(err) - } - return &azcore.Response{ - Response: &http.Response{ - Body: ioutil.NopCloser(resp), - ContentLength: -1, - Header: http.Header{}, - Request: req, - }, - } -} - -func TestNewLROPollerAsync(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithBody([]byte(statusInProgress))) - srv.AppendResponse(mock.WithBody([]byte(statusSucceeded))) - srv.AppendResponse(mock.WithBody([]byte(successResp))) - resp := initialResponse(http.MethodPut, srv.URL(), strings.NewReader(provStateStarted)) - resp.Header.Set(pollers.HeaderAzureAsync, srv.URL()) - resp.StatusCode = http.StatusCreated - pl := getPipeline(srv) - poller, err := NewLROPoller("pollerID", "", resp, pl, handleError) - if err != nil { - t.Fatal(err) - } - if _, ok := poller.lro.(*async.Poller); !ok { - t.Fatalf("unexpected poller type %T", poller.lro) - } - tk, err := poller.ResumeToken() - if err != nil { - t.Fatal(err) - } - poller, err = NewLROPollerFromResumeToken("pollerID", tk, pl, handleError) - if err != nil { - t.Fatal(err) - } - var result mockType - _, err = poller.PollUntilDone(context.Background(), 10*time.Millisecond, &result) - if err != nil { - t.Fatal(err) - } - if v := *result.Field; v != "value" { - t.Fatalf("unexpected value %s", v) - } -} - -func TestNewLROPollerBody(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithBody([]byte(provStateUpdating)), mock.WithHeader("Retry-After", "1")) - srv.AppendResponse(mock.WithBody([]byte(provStateSucceeded))) - resp := initialResponse(http.MethodPatch, srv.URL(), strings.NewReader(provStateStarted)) - resp.StatusCode = http.StatusCreated - pl := getPipeline(srv) - poller, err := NewLROPoller("pollerID", "", resp, pl, handleError) - if err != nil { - t.Fatal(err) - } - if _, ok := poller.lro.(*body.Poller); !ok { - t.Fatalf("unexpected poller type %T", poller.lro) - } - tk, err := poller.ResumeToken() - if err != nil { - t.Fatal(err) - } - poller, err = NewLROPollerFromResumeToken("pollerID", tk, pl, handleError) - if err != nil { - t.Fatal(err) - } - var result mockType - _, err = poller.PollUntilDone(context.Background(), 10*time.Millisecond, &result) - if err != nil { - t.Fatal(err) - } - if v := *result.Field; v != "value" { - t.Fatalf("unexpected value %s", v) - } -} - -func TestNewLROPollerLoc(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithStatusCode(http.StatusAccepted)) - srv.AppendResponse(mock.WithBody([]byte(successResp))) - resp := initialResponse(http.MethodPatch, srv.URL(), strings.NewReader(provStateStarted)) - resp.Header.Set(pollers.HeaderLocation, srv.URL()) - resp.StatusCode = http.StatusAccepted - pl := getPipeline(srv) - poller, err := NewLROPoller("pollerID", "", resp, pl, handleError) - if err != nil { - t.Fatal(err) - } - if _, ok := poller.lro.(*loc.Poller); !ok { - t.Fatalf("unexpected poller type %T", poller.lro) - } - tk, err := poller.ResumeToken() - if err != nil { - t.Fatal(err) - } - poller, err = NewLROPollerFromResumeToken("pollerID", tk, pl, handleError) - if err != nil { - t.Fatal(err) - } - var result mockType - _, err = poller.PollUntilDone(context.Background(), 10*time.Millisecond, &result) - if err != nil { - t.Fatal(err) - } - if v := *result.Field; v != "value" { - t.Fatalf("unexpected value %s", v) - } -} - -func TestNewLROPollerNop(t *testing.T) { - srv, close := mock.NewServer() - defer close() - resp := initialResponse(http.MethodPost, srv.URL(), strings.NewReader(successResp)) - resp.StatusCode = http.StatusOK - poller, err := NewLROPoller("pollerID", "", resp, getPipeline(srv), handleError) - if err != nil { - t.Fatal(err) - } - if _, ok := poller.lro.(*nopPoller); !ok { - t.Fatalf("unexpected poller type %T", poller.lro) - } - tk, err := poller.ResumeToken() - if err == nil { - t.Fatal("unexpected nil error") - } - if tk != "" { - t.Fatal("expected empty token") - } - var result mockType - _, err = poller.PollUntilDone(context.Background(), 10*time.Millisecond, &result) - if err != nil { - t.Fatal(err) - } - if v := *result.Field; v != "value" { - t.Fatalf("unexpected value %s", v) - } -} - -func TestNewLROPollerInitialRetryAfter(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithBody([]byte(statusInProgress))) - srv.AppendResponse(mock.WithBody([]byte(statusSucceeded))) - srv.AppendResponse(mock.WithBody([]byte(successResp))) - resp := initialResponse(http.MethodPut, srv.URL(), strings.NewReader(provStateStarted)) - resp.Header.Set(pollers.HeaderAzureAsync, srv.URL()) - resp.Header.Set("Retry-After", "1") - resp.StatusCode = http.StatusCreated - pl := getPipeline(srv) - poller, err := NewLROPoller("pollerID", "", resp, pl, handleError) - if err != nil { - t.Fatal(err) - } - if _, ok := poller.lro.(*async.Poller); !ok { - t.Fatalf("unexpected poller type %T", poller.lro) - } - var result mockType - _, err = poller.PollUntilDone(context.Background(), 10*time.Millisecond, &result) - if err != nil { - t.Fatal(err) - } - if v := *result.Field; v != "value" { - t.Fatalf("unexpected value %s", v) - } -} - -func TestNewLROPollerCanceled(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithBody([]byte(statusInProgress))) - srv.AppendResponse(mock.WithBody([]byte(statusCanceled)), mock.WithStatusCode(http.StatusOK)) - resp := initialResponse(http.MethodPut, srv.URL(), strings.NewReader(provStateStarted)) - resp.Header.Set(pollers.HeaderAzureAsync, srv.URL()) - resp.StatusCode = http.StatusCreated - pl := getPipeline(srv) - poller, err := NewLROPoller("pollerID", "", resp, pl, handleError) - if err != nil { - t.Fatal(err) - } - if _, ok := poller.lro.(*async.Poller); !ok { - t.Fatalf("unexpected poller type %T", poller.lro) - } - _, err = poller.Poll(context.Background()) - if err != nil { - t.Fatal(err) - } - _, err = poller.Poll(context.Background()) - if err == nil { - t.Fatal("unexpected nil error") - } -} - -func TestNewLROPollerFailedWithError(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithBody([]byte(statusInProgress))) - srv.AppendResponse(mock.WithBody([]byte(errorResp)), mock.WithStatusCode(http.StatusBadRequest)) - resp := initialResponse(http.MethodPut, srv.URL(), strings.NewReader(provStateStarted)) - resp.Header.Set(pollers.HeaderAzureAsync, srv.URL()) - resp.StatusCode = http.StatusCreated - pl := getPipeline(srv) - poller, err := NewLROPoller("pollerID", "", resp, pl, handleError) - if err != nil { - t.Fatal(err) - } - if _, ok := poller.lro.(*async.Poller); !ok { - t.Fatalf("unexpected poller type %T", poller.lro) - } - var result mockType - _, err = poller.PollUntilDone(context.Background(), 10*time.Millisecond, &result) - if err == nil { - t.Fatal(err) - } - if _, ok := err.(mockError); !ok { - t.Fatalf("unexpected error type %T", err) - } -} - -func TestNewLROPollerSuccessNoContent(t *testing.T) { - srv, close := mock.NewServer() - defer close() - srv.AppendResponse(mock.WithBody([]byte(provStateUpdating))) - srv.AppendResponse(mock.WithStatusCode(http.StatusNoContent)) - resp := initialResponse(http.MethodPatch, srv.URL(), strings.NewReader(provStateStarted)) - resp.StatusCode = http.StatusCreated - pl := getPipeline(srv) - poller, err := NewLROPoller("pollerID", "", resp, pl, handleError) - if err != nil { - t.Fatal(err) - } - if _, ok := poller.lro.(*body.Poller); !ok { - t.Fatalf("unexpected poller type %T", poller.lro) - } - tk, err := poller.ResumeToken() - if err != nil { - t.Fatal(err) - } - poller, err = NewLROPollerFromResumeToken("pollerID", tk, pl, handleError) - if err != nil { - t.Fatal(err) - } - var result mockType - _, err = poller.PollUntilDone(context.Background(), 10*time.Millisecond, &result) - if err != nil { - t.Fatal(err) - } - if result.Field != nil { - t.Fatal("expected nil result") - } -} - -func TestNewLROPollerFail202NoHeaders(t *testing.T) { - srv, close := mock.NewServer() - defer close() - resp := initialResponse(http.MethodDelete, srv.URL(), http.NoBody) - resp.StatusCode = http.StatusAccepted - pl := getPipeline(srv) - poller, err := NewLROPoller("pollerID", "", resp, pl, handleError) - if err == nil { - t.Fatal("unexpected nil error") - } - if poller != nil { - t.Fatal("expected nil poller") - } -} diff --git a/sdk/armcore/version.go b/sdk/armcore/version.go deleted file mode 100644 index 2a7c6793775d..000000000000 --- a/sdk/armcore/version.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package armcore - -const ( - // UserAgent is the string to be used in the user agent string when making requests. - UserAgent = "armcore/" + Version - - // Version is the semantic version (see http://semver.org) of this module. - Version = "v0.8.1" -) diff --git a/sdk/to/README.md b/sdk/to/README.md deleted file mode 100644 index 43a885f312ae..000000000000 --- a/sdk/to/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# Azure Type Conversion Module for Go - -[![PkgGoDev](https://pkg.go.dev/badge/github.com/Azure/azure-sdk-for-go/sdk/to)](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/to) -[![Build Status](https://dev.azure.com/azure-sdk/public/_apis/build/status/go/go%20-%20to%20-%20ci?branchName=main)](https://dev.azure.com/azure-sdk/public/_build/latest?definitionId=1845&branchName=main) -[![Code Coverage](https://img.shields.io/azure-devops/coverage/azure-sdk/public/1845/main)](https://img.shields.io/azure-devops/coverage/azure-sdk/public/1845/main) - -The `to` module contains various helper type-conversion functions. -These modules follow the [Azure SDK Design Guidelines for Go](https://azure.github.io/azure-sdk/golang_introduction.html). - -## Getting started - -This project uses [Go modules](https://github.com/golang/go/wiki/Modules) for versioning and dependency management. - -To add the latest version to your `go.mod` file, execute the following command. - -```bash -go get -u github.com/Azure/azure-sdk-for-go/sdk/to -``` - -General documentation and examples can be found on [pkg.go.dev](https://pkg.go.dev/github.com/Azure/azure-sdk-for-go/sdk/to). - -## Contributing -This project welcomes contributions and suggestions. Most contributions require -you to agree to a Contributor License Agreement (CLA) declaring that you have -the right to, and actually do, grant us the rights to use your contribution. -For details, visit [https://cla.microsoft.com](https://cla.microsoft.com). - -When you submit a pull request, a CLA-bot will automatically determine whether -you need to provide a CLA and decorate the PR appropriately (e.g., label, -comment). Simply follow the instructions provided by the bot. You will only -need to do this once across all repos using our CLA. - -This project has adopted the -[Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information, see the -[Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) -or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any -additional questions or comments. diff --git a/sdk/to/ci.yml b/sdk/to/ci.yml deleted file mode 100644 index 8fa2e11155a9..000000000000 --- a/sdk/to/ci.yml +++ /dev/null @@ -1,15 +0,0 @@ -# NOTE: Please refer to https://aka.ms/azsdk/engsys/ci-yaml before editing this file. -trigger: - paths: - include: - - sdk/to/ - -pr: - paths: - include: - - sdk/to/ - -stages: -- template: ../../eng/pipelines/templates/jobs/archetype-sdk-client.yml - parameters: - ServiceDirectory: 'to' diff --git a/sdk/to/doc.go b/sdk/to/doc.go deleted file mode 100644 index 17d445b7089c..000000000000 --- a/sdk/to/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright 2017 Microsoft Corporation. All rights reserved. -// Use of this source code is governed by an MIT -// license that can be found in the LICENSE file. - -// Package to contains various helper type-conversion functions. -package to diff --git a/sdk/to/go.mod b/sdk/to/go.mod deleted file mode 100644 index 78ef80d0beee..000000000000 --- a/sdk/to/go.mod +++ /dev/null @@ -1,4 +0,0 @@ -// Deprecated: The contents of this module have moved to github.com/Azure/azure-sdk-for-go/sdk/azcore/to -module github.com/Azure/azure-sdk-for-go/sdk/to - -go 1.13 diff --git a/sdk/to/to.go b/sdk/to/to.go deleted file mode 100644 index 9779778e07af..000000000000 --- a/sdk/to/to.go +++ /dev/null @@ -1,107 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package to - -import "time" - -// BoolPtr returns a pointer to the provided bool. -func BoolPtr(b bool) *bool { - return &b -} - -// Float32Ptr returns a pointer to the provided float32. -func Float32Ptr(i float32) *float32 { - return &i -} - -// Float64Ptr returns a pointer to the provided float64. -func Float64Ptr(i float64) *float64 { - return &i -} - -// Int32Ptr returns a pointer to the provided int32. -func Int32Ptr(i int32) *int32 { - return &i -} - -// Int64Ptr returns a pointer to the provided int64. -func Int64Ptr(i int64) *int64 { - return &i -} - -// StringPtr returns a pointer to the provided string. -func StringPtr(s string) *string { - return &s -} - -// TimePtr returns a pointer to the provided time.Time. -func TimePtr(t time.Time) *time.Time { - return &t -} - -// Int32PtrArray returns an array of *int32 from the specified values. -func Int32PtrArray(vals ...int32) []*int32 { - arr := make([]*int32, len(vals)) - for i := range vals { - arr[i] = Int32Ptr(vals[i]) - } - return arr -} - -// Int64PtrArray returns an array of *int64 from the specified values. -func Int64PtrArray(vals ...int64) []*int64 { - arr := make([]*int64, len(vals)) - for i := range vals { - arr[i] = Int64Ptr(vals[i]) - } - return arr -} - -// Float32PtrArray returns an array of *float32 from the specified values. -func Float32PtrArray(vals ...float32) []*float32 { - arr := make([]*float32, len(vals)) - for i := range vals { - arr[i] = Float32Ptr(vals[i]) - } - return arr -} - -// Float64PtrArray returns an array of *float64 from the specified values. -func Float64PtrArray(vals ...float64) []*float64 { - arr := make([]*float64, len(vals)) - for i := range vals { - arr[i] = Float64Ptr(vals[i]) - } - return arr -} - -// BoolPtrArray returns an array of *bool from the specified values. -func BoolPtrArray(vals ...bool) []*bool { - arr := make([]*bool, len(vals)) - for i := range vals { - arr[i] = BoolPtr(vals[i]) - } - return arr -} - -// StringPtrArray returns an array of *string from the specified values. -func StringPtrArray(vals ...string) []*string { - arr := make([]*string, len(vals)) - for i := range vals { - arr[i] = StringPtr(vals[i]) - } - return arr -} - -// TimePtrArray returns an array of *time.Time from the specified values. -func TimePtrArray(vals ...time.Time) []*time.Time { - arr := make([]*time.Time, len(vals)) - for i := range vals { - arr[i] = TimePtr(vals[i]) - } - return arr -} diff --git a/sdk/to/to_test.go b/sdk/to/to_test.go deleted file mode 100644 index c48dad5c1e4e..000000000000 --- a/sdk/to/to_test.go +++ /dev/null @@ -1,192 +0,0 @@ -//go:build go1.13 -// +build go1.13 - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package to - -import ( - "fmt" - "reflect" - "strconv" - "testing" - "time" -) - -func TestBoolPtr(t *testing.T) { - b := true - pb := BoolPtr(b) - if pb == nil { - t.Fatal("unexpected nil conversion") - } - if *pb != b { - t.Fatalf("got %v, want %v", *pb, b) - } -} - -func TestFloat32Ptr(t *testing.T) { - f32 := float32(3.1415926) - pf32 := Float32Ptr(f32) - if pf32 == nil { - t.Fatal("unexpected nil conversion") - } - if *pf32 != f32 { - t.Fatalf("got %v, want %v", *pf32, f32) - } -} - -func TestFloat64Ptr(t *testing.T) { - f64 := float64(2.71828182845904) - pf64 := Float64Ptr(f64) - if pf64 == nil { - t.Fatal("unexpected nil conversion") - } - if *pf64 != f64 { - t.Fatalf("got %v, want %v", *pf64, f64) - } -} - -func TestInt32Ptr(t *testing.T) { - i32 := int32(123456789) - pi32 := Int32Ptr(i32) - if pi32 == nil { - t.Fatal("unexpected nil conversion") - } - if *pi32 != i32 { - t.Fatalf("got %v, want %v", *pi32, i32) - } -} - -func TestInt64Ptr(t *testing.T) { - i64 := int64(9876543210) - pi64 := Int64Ptr(i64) - if pi64 == nil { - t.Fatal("unexpected nil conversion") - } - if *pi64 != i64 { - t.Fatalf("got %v, want %v", *pi64, i64) - } -} - -func TestStringPtr(t *testing.T) { - s := "the string" - ps := StringPtr(s) - if ps == nil { - t.Fatal("unexpected nil conversion") - } - if *ps != s { - t.Fatalf("got %v, want %v", *ps, s) - } -} - -func TestTimePtr(t *testing.T) { - tt := time.Now() - pt := TimePtr(tt) - if pt == nil { - t.Fatal("unexpected nil conversion") - } - if *pt != tt { - t.Fatalf("got %v, want %v", *pt, tt) - } -} - -func TestInt32PtrArray(t *testing.T) { - arr := Int32PtrArray() - if len(arr) != 0 { - t.Fatal("expected zero length") - } - arr = Int32PtrArray(1, 2, 3, 4, 5) - for i, v := range arr { - if *v != int32(i+1) { - t.Fatal("values don't match") - } - } -} - -func TestInt64PtrArray(t *testing.T) { - arr := Int64PtrArray() - if len(arr) != 0 { - t.Fatal("expected zero length") - } - arr = Int64PtrArray(1, 2, 3, 4, 5) - for i, v := range arr { - if *v != int64(i+1) { - t.Fatal("values don't match") - } - } -} - -func TestFloat32PtrArray(t *testing.T) { - arr := Float32PtrArray() - if len(arr) != 0 { - t.Fatal("expected zero length") - } - arr = Float32PtrArray(1.1, 2.2, 3.3, 4.4, 5.5) - for i, v := range arr { - f, err := strconv.ParseFloat(fmt.Sprintf("%d.%d", i+1, i+1), 32) - if err != nil { - t.Fatal(err) - } - if *v != float32(f) { - t.Fatal("values don't match") - } - } -} - -func TestFloat64PtrArray(t *testing.T) { - arr := Float64PtrArray() - if len(arr) != 0 { - t.Fatal("expected zero length") - } - arr = Float64PtrArray(1.1, 2.2, 3.3, 4.4, 5.5) - for i, v := range arr { - f, err := strconv.ParseFloat(fmt.Sprintf("%d.%d", i+1, i+1), 64) - if err != nil { - t.Fatal(err) - } - if *v != f { - t.Fatal("values don't match") - } - } -} - -func TestBoolPtrArray(t *testing.T) { - arr := BoolPtrArray() - if len(arr) != 0 { - t.Fatal("expected zero length") - } - arr = BoolPtrArray(true, false, true) - curr := true - for _, v := range arr { - if *v != curr { - t.Fatal("values don'p match") - } - curr = !curr - } -} - -func TestStringPtrArray(t *testing.T) { - arr := StringPtrArray() - if len(arr) != 0 { - t.Fatal("expected zero length") - } - arr = StringPtrArray("one", "", "three") - if !reflect.DeepEqual(arr, []*string{StringPtr("one"), StringPtr(""), StringPtr("three")}) { - t.Fatal("values don't match") - } -} - -func TestTimePtrArray(t *testing.T) { - arr := TimePtrArray() - if len(arr) != 0 { - t.Fatal("expected zero length") - } - t1 := time.Now() - t2 := time.Time{} - t3 := t1.Add(24 * time.Hour) - arr = TimePtrArray(t1, t2, t3) - if !reflect.DeepEqual(arr, []*time.Time{&t1, &t2, &t3}) { - t.Fatal("values don't match") - } -}