github.com/mmcquillan/packer@v1.1.1-0.20171009221028-c85cf0483a5d/builder/azure/arm/azure_client.go (about) 1 package arm 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "math" 7 "net/http" 8 "net/url" 9 "os" 10 "strconv" 11 12 "github.com/Azure/azure-sdk-for-go/arm/compute" 13 "github.com/Azure/azure-sdk-for-go/arm/network" 14 "github.com/Azure/azure-sdk-for-go/arm/resources/resources" 15 armStorage "github.com/Azure/azure-sdk-for-go/arm/storage" 16 "github.com/Azure/azure-sdk-for-go/storage" 17 "github.com/Azure/go-autorest/autorest" 18 "github.com/Azure/go-autorest/autorest/adal" 19 "github.com/Azure/go-autorest/autorest/azure" 20 "github.com/hashicorp/packer/builder/azure/common" 21 "github.com/hashicorp/packer/version" 22 ) 23 24 const ( 25 EnvPackerLogAzureMaxLen = "PACKER_LOG_AZURE_MAXLEN" 26 ) 27 28 var ( 29 packerUserAgent = fmt.Sprintf(";packer/%s", version.FormattedVersion()) 30 ) 31 32 type AzureClient struct { 33 storage.BlobStorageClient 34 resources.DeploymentsClient 35 resources.GroupsClient 36 network.PublicIPAddressesClient 37 network.InterfacesClient 38 network.SubnetsClient 39 network.VirtualNetworksClient 40 compute.ImagesClient 41 compute.VirtualMachinesClient 42 common.VaultClient 43 armStorage.AccountsClient 44 45 InspectorMaxLength int 46 Template *CaptureTemplate 47 LastError azureErrorResponse 48 } 49 50 func getCaptureResponse(body string) *CaptureTemplate { 51 var operation CaptureOperation 52 err := json.Unmarshal([]byte(body), &operation) 53 if err != nil { 54 return nil 55 } 56 57 if operation.Properties != nil && operation.Properties.Output != nil { 58 return operation.Properties.Output 59 } 60 61 return nil 62 } 63 64 // HACK(chrboum): This method is a hack. It was written to work around this issue 65 // (https://github.com/Azure/azure-sdk-for-go/issues/307) and to an extent this 66 // issue (https://github.com/Azure/azure-rest-api-specs/issues/188). 67 // 68 // Capturing a VM is a long running operation that requires polling. There are 69 // couple different forms of polling, and the end result of a poll operation is 70 // discarded by the SDK. It is expected that any discarded data can be re-fetched, 71 // so discarding it has minimal impact. Unfortunately, there is no way to re-fetch 72 // the template returned by a capture call that I am aware of. 73 // 74 // If the second issue were fixed the VM ID would be included when GET'ing a VM. The 75 // VM ID could be used to locate the captured VHD, and captured template. 76 // Unfortunately, the VM ID is not included so this method cannot be used either. 77 // 78 // This code captures the template and saves it to the client (the AzureClient type). 79 // It expects that the capture API is called only once, or rather you only care that the 80 // last call's value is important because subsequent requests are not persisted. There 81 // is no care given to multiple threads writing this value because for our use case 82 // it does not matter. 83 func templateCapture(client *AzureClient) autorest.RespondDecorator { 84 return func(r autorest.Responder) autorest.Responder { 85 return autorest.ResponderFunc(func(resp *http.Response) error { 86 body, bodyString := handleBody(resp.Body, math.MaxInt64) 87 resp.Body = body 88 89 captureTemplate := getCaptureResponse(bodyString) 90 if captureTemplate != nil { 91 client.Template = captureTemplate 92 } 93 94 return r.Respond(resp) 95 }) 96 } 97 } 98 99 func errorCapture(client *AzureClient) autorest.RespondDecorator { 100 return func(r autorest.Responder) autorest.Responder { 101 return autorest.ResponderFunc(func(resp *http.Response) error { 102 body, bodyString := handleBody(resp.Body, math.MaxInt64) 103 resp.Body = body 104 105 errorResponse := newAzureErrorResponse(bodyString) 106 if errorResponse != nil { 107 client.LastError = *errorResponse 108 } 109 110 return r.Respond(resp) 111 }) 112 } 113 } 114 115 // WAITING(chrboum): I have logged https://github.com/Azure/azure-sdk-for-go/issues/311 to get this 116 // method included in the SDK. It has been accepted, and I'll cut over to the official way 117 // once it ships. 118 func byConcatDecorators(decorators ...autorest.RespondDecorator) autorest.RespondDecorator { 119 return func(r autorest.Responder) autorest.Responder { 120 return autorest.DecorateResponder(r, decorators...) 121 } 122 } 123 124 func NewAzureClient(subscriptionID, resourceGroupName, storageAccountName string, 125 cloud *azure.Environment, 126 servicePrincipalToken, servicePrincipalTokenVault *adal.ServicePrincipalToken) (*AzureClient, error) { 127 128 var azureClient = &AzureClient{} 129 130 maxlen := getInspectorMaxLength() 131 132 azureClient.DeploymentsClient = resources.NewDeploymentsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) 133 azureClient.DeploymentsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) 134 azureClient.DeploymentsClient.RequestInspector = withInspection(maxlen) 135 azureClient.DeploymentsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) 136 azureClient.DeploymentsClient.UserAgent += packerUserAgent 137 138 azureClient.GroupsClient = resources.NewGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) 139 azureClient.GroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) 140 azureClient.GroupsClient.RequestInspector = withInspection(maxlen) 141 azureClient.GroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) 142 azureClient.GroupsClient.UserAgent += packerUserAgent 143 144 azureClient.ImagesClient = compute.NewImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) 145 azureClient.ImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) 146 azureClient.ImagesClient.RequestInspector = withInspection(maxlen) 147 azureClient.ImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) 148 azureClient.ImagesClient.UserAgent += packerUserAgent 149 150 azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) 151 azureClient.InterfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) 152 azureClient.InterfacesClient.RequestInspector = withInspection(maxlen) 153 azureClient.InterfacesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) 154 azureClient.InterfacesClient.UserAgent += packerUserAgent 155 156 azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) 157 azureClient.SubnetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) 158 azureClient.SubnetsClient.RequestInspector = withInspection(maxlen) 159 azureClient.SubnetsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) 160 azureClient.SubnetsClient.UserAgent += packerUserAgent 161 162 azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) 163 azureClient.VirtualNetworksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) 164 azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen) 165 azureClient.VirtualNetworksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) 166 azureClient.VirtualNetworksClient.UserAgent += packerUserAgent 167 168 azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) 169 azureClient.PublicIPAddressesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) 170 azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen) 171 azureClient.PublicIPAddressesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) 172 azureClient.PublicIPAddressesClient.UserAgent += packerUserAgent 173 174 azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) 175 azureClient.VirtualMachinesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) 176 azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen) 177 azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient)) 178 azureClient.VirtualMachinesClient.UserAgent += packerUserAgent 179 180 azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID) 181 azureClient.AccountsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken) 182 azureClient.AccountsClient.RequestInspector = withInspection(maxlen) 183 azureClient.AccountsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) 184 azureClient.AccountsClient.UserAgent += packerUserAgent 185 186 keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint) 187 if err != nil { 188 return nil, err 189 } 190 191 azureClient.VaultClient = common.NewVaultClient(*keyVaultURL) 192 azureClient.VaultClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalTokenVault) 193 azureClient.VaultClient.RequestInspector = withInspection(maxlen) 194 azureClient.VaultClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient)) 195 azureClient.VaultClient.UserAgent += packerUserAgent 196 197 // If this is a managed disk build, this should be ignored. 198 if resourceGroupName != "" && storageAccountName != "" { 199 accountKeys, err := azureClient.AccountsClient.ListKeys(resourceGroupName, storageAccountName) 200 if err != nil { 201 return nil, err 202 } 203 204 storageClient, err := storage.NewClient( 205 storageAccountName, 206 *(*accountKeys.Keys)[0].Value, 207 cloud.StorageEndpointSuffix, 208 storage.DefaultAPIVersion, 209 true /*useHttps*/) 210 211 if err != nil { 212 return nil, err 213 } 214 215 azureClient.BlobStorageClient = storageClient.GetBlobService() 216 } 217 218 return azureClient, nil 219 } 220 221 func getInspectorMaxLength() int64 { 222 value, ok := os.LookupEnv(EnvPackerLogAzureMaxLen) 223 if !ok { 224 return math.MaxInt64 225 } 226 227 i, err := strconv.ParseInt(value, 10, 64) 228 if err != nil { 229 return 0 230 } 231 232 if i < 0 { 233 return 0 234 } 235 236 return i 237 }