1// Copyright 2017 Microsoft Corporation 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package azure 16 17import ( 18 "errors" 19 "fmt" 20 "net/http" 21 "net/url" 22 "strings" 23 "time" 24 25 "github.com/Azure/go-autorest/autorest" 26) 27 28// DoRetryWithRegistration tries to register the resource provider in case it is unregistered. 29// It also handles request retries 30func DoRetryWithRegistration(client autorest.Client) autorest.SendDecorator { 31 return func(s autorest.Sender) autorest.Sender { 32 return autorest.SenderFunc(func(r *http.Request) (resp *http.Response, err error) { 33 rr := autorest.NewRetriableRequest(r) 34 for currentAttempt := 0; currentAttempt < client.RetryAttempts; currentAttempt++ { 35 err = rr.Prepare() 36 if err != nil { 37 return resp, err 38 } 39 40 resp, err = autorest.SendWithSender(s, rr.Request(), 41 autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...), 42 ) 43 if err != nil { 44 return resp, err 45 } 46 47 if resp.StatusCode != http.StatusConflict || client.SkipResourceProviderRegistration { 48 return resp, err 49 } 50 var re RequestError 51 err = autorest.Respond( 52 resp, 53 autorest.ByUnmarshallingJSON(&re), 54 ) 55 if err != nil { 56 return resp, err 57 } 58 err = re 59 60 if re.ServiceError != nil && re.ServiceError.Code == "MissingSubscriptionRegistration" { 61 regErr := register(client, r, re) 62 if regErr != nil { 63 return resp, fmt.Errorf("failed auto registering Resource Provider: %s. Original error: %s", regErr, err) 64 } 65 } 66 } 67 return resp, err 68 }) 69 } 70} 71 72func getProvider(re RequestError) (string, error) { 73 if re.ServiceError != nil && len(re.ServiceError.Details) > 0 { 74 return re.ServiceError.Details[0]["target"].(string), nil 75 } 76 return "", errors.New("provider was not found in the response") 77} 78 79func register(client autorest.Client, originalReq *http.Request, re RequestError) error { 80 subID := getSubscription(originalReq.URL.Path) 81 if subID == "" { 82 return errors.New("missing parameter subscriptionID to register resource provider") 83 } 84 providerName, err := getProvider(re) 85 if err != nil { 86 return fmt.Errorf("missing parameter provider to register resource provider: %s", err) 87 } 88 newURL := url.URL{ 89 Scheme: originalReq.URL.Scheme, 90 Host: originalReq.URL.Host, 91 } 92 93 // taken from the resources SDK 94 // with almost identical code, this sections are easier to mantain 95 // It is also not a good idea to import the SDK here 96 // https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L252 97 pathParameters := map[string]interface{}{ 98 "resourceProviderNamespace": autorest.Encode("path", providerName), 99 "subscriptionId": autorest.Encode("path", subID), 100 } 101 102 const APIVersion = "2016-09-01" 103 queryParameters := map[string]interface{}{ 104 "api-version": APIVersion, 105 } 106 107 preparer := autorest.CreatePreparer( 108 autorest.AsPost(), 109 autorest.WithBaseURL(newURL.String()), 110 autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}/register", pathParameters), 111 autorest.WithQueryParameters(queryParameters), 112 ) 113 114 req, err := preparer.Prepare(&http.Request{}) 115 if err != nil { 116 return err 117 } 118 req = req.WithContext(originalReq.Context()) 119 120 resp, err := autorest.SendWithSender(client, req, 121 autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...), 122 ) 123 if err != nil { 124 return err 125 } 126 127 type Provider struct { 128 RegistrationState *string `json:"registrationState,omitempty"` 129 } 130 var provider Provider 131 132 err = autorest.Respond( 133 resp, 134 WithErrorUnlessStatusCode(http.StatusOK), 135 autorest.ByUnmarshallingJSON(&provider), 136 autorest.ByClosing(), 137 ) 138 if err != nil { 139 return err 140 } 141 142 // poll for registered provisioning state 143 registrationStartTime := time.Now() 144 for err == nil && (client.PollingDuration == 0 || (client.PollingDuration != 0 && time.Since(registrationStartTime) < client.PollingDuration)) { 145 // taken from the resources SDK 146 // https://github.com/Azure/azure-sdk-for-go/blob/9f366792afa3e0ddaecdc860e793ba9d75e76c27/arm/resources/resources/providers.go#L45 147 preparer := autorest.CreatePreparer( 148 autorest.AsGet(), 149 autorest.WithBaseURL(newURL.String()), 150 autorest.WithPathParameters("/subscriptions/{subscriptionId}/providers/{resourceProviderNamespace}", pathParameters), 151 autorest.WithQueryParameters(queryParameters), 152 ) 153 req, err = preparer.Prepare(&http.Request{}) 154 if err != nil { 155 return err 156 } 157 req = req.WithContext(originalReq.Context()) 158 159 resp, err := autorest.SendWithSender(client, req, 160 autorest.DoRetryForStatusCodes(client.RetryAttempts, client.RetryDuration, autorest.StatusCodesForRetry...), 161 ) 162 if err != nil { 163 return err 164 } 165 166 err = autorest.Respond( 167 resp, 168 WithErrorUnlessStatusCode(http.StatusOK), 169 autorest.ByUnmarshallingJSON(&provider), 170 autorest.ByClosing(), 171 ) 172 if err != nil { 173 return err 174 } 175 176 if provider.RegistrationState != nil && 177 *provider.RegistrationState == "Registered" { 178 break 179 } 180 181 delayed := autorest.DelayWithRetryAfter(resp, originalReq.Context().Done()) 182 if !delayed && !autorest.DelayForBackoff(client.PollingDelay, 0, originalReq.Context().Done()) { 183 return originalReq.Context().Err() 184 } 185 } 186 if client.PollingDuration != 0 && !(time.Since(registrationStartTime) < client.PollingDuration) { 187 return errors.New("polling for resource provider registration has exceeded the polling duration") 188 } 189 return err 190} 191 192func getSubscription(path string) string { 193 parts := strings.Split(path, "/") 194 for i, v := range parts { 195 if v == "subscriptions" && (i+1) < len(parts) { 196 return parts[i+1] 197 } 198 } 199 return "" 200} 201