1// +build go1.7 2 3package management 4 5// Copyright 2017 Microsoft Corporation 6// 7// Licensed under the Apache License, Version 2.0 (the "License"); 8// you may not use this file except in compliance with the License. 9// You may obtain a copy of the License at 10// 11// http://www.apache.org/licenses/LICENSE-2.0 12// 13// Unless required by applicable law or agreed to in writing, software 14// distributed under the License is distributed on an "AS IS" BASIS, 15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16// See the License for the specific language governing permissions and 17// limitations under the License. 18 19import ( 20 "bytes" 21 "crypto/tls" 22 "fmt" 23 "net/http" 24 "time" 25) 26 27const ( 28 msVersionHeader = "x-ms-version" 29 requestIDHeader = "x-ms-request-id" 30 uaHeader = "User-Agent" 31 contentHeader = "Content-Type" 32 defaultContentHeaderValue = "application/xml" 33) 34 35func (client client) SendAzureGetRequest(url string) ([]byte, error) { 36 resp, err := client.sendAzureRequest("GET", url, "", nil) 37 if err != nil { 38 return nil, err 39 } 40 return getResponseBody(resp) 41} 42 43func (client client) SendAzurePostRequest(url string, data []byte) (OperationID, error) { 44 return client.doAzureOperation("POST", url, "", data) 45} 46 47func (client client) SendAzurePostRequestWithReturnedResponse(url string, data []byte) ([]byte, error) { 48 resp, err := client.sendAzureRequest("POST", url, "", data) 49 if err != nil { 50 return nil, err 51 } 52 53 return getResponseBody(resp) 54} 55 56func (client client) SendAzurePutRequest(url, contentType string, data []byte) (OperationID, error) { 57 return client.doAzureOperation("PUT", url, contentType, data) 58} 59 60func (client client) SendAzureDeleteRequest(url string) (OperationID, error) { 61 return client.doAzureOperation("DELETE", url, "", nil) 62} 63 64func (client client) doAzureOperation(method, url, contentType string, data []byte) (OperationID, error) { 65 response, err := client.sendAzureRequest(method, url, contentType, data) 66 if err != nil { 67 return "", err 68 } 69 return getOperationID(response) 70} 71 72func getOperationID(response *http.Response) (OperationID, error) { 73 requestID := response.Header.Get(requestIDHeader) 74 if requestID == "" { 75 return "", fmt.Errorf("Could not retrieve operation id from %q header", requestIDHeader) 76 } 77 return OperationID(requestID), nil 78} 79 80// sendAzureRequest constructs an HTTP client for the request, sends it to the 81// management API and returns the response or an error. 82func (client client) sendAzureRequest(method, url, contentType string, data []byte) (*http.Response, error) { 83 if method == "" { 84 return nil, fmt.Errorf(errParamNotSpecified, "method") 85 } 86 if url == "" { 87 return nil, fmt.Errorf(errParamNotSpecified, "url") 88 } 89 90 response, err := client.sendRequest(client.httpClient, url, method, contentType, data, 5) 91 if err != nil { 92 return nil, err 93 } 94 95 return response, nil 96} 97 98// createHTTPClient creates an HTTP Client configured with the key pair for 99// the subscription for this client. 100func (client client) createHTTPClient() (*http.Client, error) { 101 cert, err := tls.X509KeyPair(client.publishSettings.SubscriptionCert, client.publishSettings.SubscriptionKey) 102 if err != nil { 103 return nil, err 104 } 105 106 return &http.Client{ 107 Transport: &http.Transport{ 108 Proxy: http.ProxyFromEnvironment, 109 TLSClientConfig: &tls.Config{ 110 Renegotiation: tls.RenegotiateOnceAsClient, 111 Certificates: []tls.Certificate{cert}, 112 }, 113 }, 114 }, nil 115} 116 117// sendRequest sends a request to the Azure management API using the given 118// HTTP client and parameters. It returns the response from the call or an 119// error. 120func (client client) sendRequest(httpClient *http.Client, url, requestType, contentType string, data []byte, numberOfRetries int) (*http.Response, error) { 121 122 absURI := client.createAzureRequestURI(url) 123 124 for { 125 request, reqErr := client.createAzureRequest(absURI, requestType, contentType, data) 126 if reqErr != nil { 127 return nil, reqErr 128 } 129 130 response, err := httpClient.Do(request) 131 if err != nil { 132 if numberOfRetries == 0 { 133 return nil, err 134 } 135 136 return client.sendRequest(httpClient, url, requestType, contentType, data, numberOfRetries-1) 137 } 138 if response.StatusCode == http.StatusTemporaryRedirect { 139 // ASM's way of moving traffic around, see https://msdn.microsoft.com/en-us/library/azure/ee460801.aspx 140 // Only handled automatically for GET/HEAD requests. This is for the rest of the http verbs. 141 u, err := response.Location() 142 if err != nil { 143 return response, fmt.Errorf("Redirect requested but location header could not be retrieved: %v", err) 144 } 145 absURI = u.String() 146 continue // re-issue request 147 } 148 149 if response.StatusCode >= http.StatusBadRequest { 150 body, err := getResponseBody(response) 151 if err != nil { 152 // Failed to read the response body 153 return nil, err 154 } 155 azureErr := getAzureError(body) 156 if azureErr != nil { 157 if numberOfRetries == 0 { 158 return nil, azureErr 159 } 160 if response.StatusCode == http.StatusServiceUnavailable || response.StatusCode == http.StatusTooManyRequests { 161 // Wait before retrying the operation 162 time.Sleep(client.config.OperationPollInterval) 163 } 164 165 return client.sendRequest(httpClient, url, requestType, contentType, data, numberOfRetries-1) 166 } 167 } 168 169 return response, nil 170 } 171} 172 173// createAzureRequestURI constructs the request uri using the management API endpoint and 174// subscription ID associated with the client. 175func (client client) createAzureRequestURI(url string) string { 176 return fmt.Sprintf("%s/%s/%s", client.config.ManagementURL, client.publishSettings.SubscriptionID, url) 177} 178 179// createAzureRequest packages up the request with the correct set of headers and returns 180// the request object or an error. 181func (client client) createAzureRequest(url string, requestType string, contentType string, data []byte) (*http.Request, error) { 182 var request *http.Request 183 var err error 184 185 if data != nil { 186 body := bytes.NewBuffer(data) 187 request, err = http.NewRequest(requestType, url, body) 188 } else { 189 request, err = http.NewRequest(requestType, url, nil) 190 } 191 192 if err != nil { 193 return nil, err 194 } 195 196 request.Header.Set(msVersionHeader, client.config.APIVersion) 197 request.Header.Set(uaHeader, client.config.UserAgent) 198 199 if contentType != "" { 200 request.Header.Set(contentHeader, contentType) 201 } else { 202 request.Header.Set(contentHeader, defaultContentHeaderValue) 203 } 204 205 return request, nil 206} 207