github.com/mitchellh/packer@v1.3.2/builder/azure/arm/azure_client.go (about)

     1  package arm
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"math"
     8  	"net/http"
     9  	"net/url"
    10  	"os"
    11  	"strconv"
    12  
    13  	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-04-01/compute"
    14  	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-01-01/network"
    15  	"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-02-01/resources"
    16  	armStorage "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2017-10-01/storage"
    17  	"github.com/Azure/azure-sdk-for-go/storage"
    18  	"github.com/Azure/go-autorest/autorest"
    19  	"github.com/Azure/go-autorest/autorest/adal"
    20  	"github.com/Azure/go-autorest/autorest/azure"
    21  	"github.com/hashicorp/packer/builder/azure/common"
    22  	"github.com/hashicorp/packer/helper/useragent"
    23  )
    24  
    25  const (
    26  	EnvPackerLogAzureMaxLen = "PACKER_LOG_AZURE_MAXLEN"
    27  )
    28  
    29  type AzureClient struct {
    30  	storage.BlobStorageClient
    31  	resources.DeploymentsClient
    32  	resources.DeploymentOperationsClient
    33  	resources.GroupsClient
    34  	network.PublicIPAddressesClient
    35  	network.InterfacesClient
    36  	network.SubnetsClient
    37  	network.VirtualNetworksClient
    38  	compute.ImagesClient
    39  	compute.VirtualMachinesClient
    40  	common.VaultClient
    41  	armStorage.AccountsClient
    42  	compute.DisksClient
    43  
    44  	InspectorMaxLength int
    45  	Template           *CaptureTemplate
    46  	LastError          azureErrorResponse
    47  	VaultClientDelete  common.VaultClient
    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 = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentsClient.UserAgent)
   137  
   138  	azureClient.DeploymentOperationsClient = resources.NewDeploymentOperationsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   139  	azureClient.DeploymentOperationsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   140  	azureClient.DeploymentOperationsClient.RequestInspector = withInspection(maxlen)
   141  	azureClient.DeploymentOperationsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   142  	azureClient.DeploymentOperationsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DeploymentOperationsClient.UserAgent)
   143  
   144  	azureClient.DisksClient = compute.NewDisksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   145  	azureClient.DisksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   146  	azureClient.DisksClient.RequestInspector = withInspection(maxlen)
   147  	azureClient.DisksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   148  	azureClient.DisksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.DisksClient.UserAgent)
   149  
   150  	azureClient.GroupsClient = resources.NewGroupsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   151  	azureClient.GroupsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   152  	azureClient.GroupsClient.RequestInspector = withInspection(maxlen)
   153  	azureClient.GroupsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   154  	azureClient.GroupsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.GroupsClient.UserAgent)
   155  
   156  	azureClient.ImagesClient = compute.NewImagesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   157  	azureClient.ImagesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   158  	azureClient.ImagesClient.RequestInspector = withInspection(maxlen)
   159  	azureClient.ImagesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   160  	azureClient.ImagesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.ImagesClient.UserAgent)
   161  
   162  	azureClient.InterfacesClient = network.NewInterfacesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   163  	azureClient.InterfacesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   164  	azureClient.InterfacesClient.RequestInspector = withInspection(maxlen)
   165  	azureClient.InterfacesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   166  	azureClient.InterfacesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.InterfacesClient.UserAgent)
   167  
   168  	azureClient.SubnetsClient = network.NewSubnetsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   169  	azureClient.SubnetsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   170  	azureClient.SubnetsClient.RequestInspector = withInspection(maxlen)
   171  	azureClient.SubnetsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   172  	azureClient.SubnetsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.SubnetsClient.UserAgent)
   173  
   174  	azureClient.VirtualNetworksClient = network.NewVirtualNetworksClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   175  	azureClient.VirtualNetworksClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   176  	azureClient.VirtualNetworksClient.RequestInspector = withInspection(maxlen)
   177  	azureClient.VirtualNetworksClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   178  	azureClient.VirtualNetworksClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualNetworksClient.UserAgent)
   179  
   180  	azureClient.PublicIPAddressesClient = network.NewPublicIPAddressesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   181  	azureClient.PublicIPAddressesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   182  	azureClient.PublicIPAddressesClient.RequestInspector = withInspection(maxlen)
   183  	azureClient.PublicIPAddressesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   184  	azureClient.PublicIPAddressesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.PublicIPAddressesClient.UserAgent)
   185  
   186  	azureClient.VirtualMachinesClient = compute.NewVirtualMachinesClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   187  	azureClient.VirtualMachinesClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   188  	azureClient.VirtualMachinesClient.RequestInspector = withInspection(maxlen)
   189  	azureClient.VirtualMachinesClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), templateCapture(azureClient), errorCapture(azureClient))
   190  	azureClient.VirtualMachinesClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VirtualMachinesClient.UserAgent)
   191  
   192  	azureClient.AccountsClient = armStorage.NewAccountsClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   193  	azureClient.AccountsClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   194  	azureClient.AccountsClient.RequestInspector = withInspection(maxlen)
   195  	azureClient.AccountsClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   196  	azureClient.AccountsClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.AccountsClient.UserAgent)
   197  
   198  	keyVaultURL, err := url.Parse(cloud.KeyVaultEndpoint)
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	azureClient.VaultClient = common.NewVaultClient(*keyVaultURL)
   204  	azureClient.VaultClient.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalTokenVault)
   205  	azureClient.VaultClient.RequestInspector = withInspection(maxlen)
   206  	azureClient.VaultClient.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   207  	azureClient.VaultClient.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClient.UserAgent)
   208  
   209  	// TODO(boumenot) - SDK still does not have a full KeyVault client.
   210  	// There are two ways that KeyVault has to be accessed, and each one has their own SPN.  An authenticated SPN
   211  	// is tied to the URL, and the URL associated with getting the secret is different than the URL
   212  	// associated with deleting the KeyVault.  As a result, I need to have *two* different clients to
   213  	// access KeyVault.  I did not want to split it into two separate files, so I am starting with this.
   214  	//
   215  	// I do not like this implementation.  It is getting long in the tooth, and should be re-examined now
   216  	// that we have a "working" solution.
   217  	azureClient.VaultClientDelete = common.NewVaultClientWithBaseURI(cloud.ResourceManagerEndpoint, subscriptionID)
   218  	azureClient.VaultClientDelete.Authorizer = autorest.NewBearerAuthorizer(servicePrincipalToken)
   219  	azureClient.VaultClientDelete.RequestInspector = withInspection(maxlen)
   220  	azureClient.VaultClientDelete.ResponseInspector = byConcatDecorators(byInspecting(maxlen), errorCapture(azureClient))
   221  	azureClient.VaultClientDelete.UserAgent = fmt.Sprintf("%s %s", useragent.String(), azureClient.VaultClientDelete.UserAgent)
   222  
   223  	// If this is a managed disk build, this should be ignored.
   224  	if resourceGroupName != "" && storageAccountName != "" {
   225  		accountKeys, err := azureClient.AccountsClient.ListKeys(context.TODO(), resourceGroupName, storageAccountName)
   226  		if err != nil {
   227  			return nil, err
   228  		}
   229  
   230  		storageClient, err := storage.NewClient(
   231  			storageAccountName,
   232  			*(*accountKeys.Keys)[0].Value,
   233  			cloud.StorageEndpointSuffix,
   234  			storage.DefaultAPIVersion,
   235  			true /*useHttps*/)
   236  
   237  		if err != nil {
   238  			return nil, err
   239  		}
   240  
   241  		azureClient.BlobStorageClient = storageClient.GetBlobService()
   242  	}
   243  
   244  	return azureClient, nil
   245  }
   246  
   247  func getInspectorMaxLength() int64 {
   248  	value, ok := os.LookupEnv(EnvPackerLogAzureMaxLen)
   249  	if !ok {
   250  		return math.MaxInt64
   251  	}
   252  
   253  	i, err := strconv.ParseInt(value, 10, 64)
   254  	if err != nil {
   255  		return 0
   256  	}
   257  
   258  	if i < 0 {
   259  		return 0
   260  	}
   261  
   262  	return i
   263  }