github.com/pgray/terraform@v0.5.4-0.20170822184730-b6a464c5214d/backend/remote-state/azure/backend.go (about)

     1  package azure
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  
     7  	armStorage "github.com/Azure/azure-sdk-for-go/arm/storage"
     8  	"github.com/Azure/azure-sdk-for-go/storage"
     9  	"github.com/Azure/go-autorest/autorest"
    10  	"github.com/Azure/go-autorest/autorest/adal"
    11  	"github.com/Azure/go-autorest/autorest/azure"
    12  	"github.com/hashicorp/terraform/backend"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  )
    15  
    16  // New creates a new backend for S3 remote state.
    17  func New() backend.Backend {
    18  	s := &schema.Backend{
    19  		Schema: map[string]*schema.Schema{
    20  			"storage_account_name": &schema.Schema{
    21  				Type:        schema.TypeString,
    22  				Required:    true,
    23  				Description: "The name of the storage account.",
    24  			},
    25  
    26  			"container_name": &schema.Schema{
    27  				Type:        schema.TypeString,
    28  				Required:    true,
    29  				Description: "The container name.",
    30  			},
    31  
    32  			"key": &schema.Schema{
    33  				Type:        schema.TypeString,
    34  				Required:    true,
    35  				Description: "The blob key.",
    36  			},
    37  
    38  			"environment": &schema.Schema{
    39  				Type:        schema.TypeString,
    40  				Optional:    true,
    41  				Description: "The Azure cloud environment.",
    42  				Default:     "",
    43  			},
    44  
    45  			"access_key": &schema.Schema{
    46  				Type:        schema.TypeString,
    47  				Optional:    true,
    48  				Description: "The access key.",
    49  				DefaultFunc: schema.EnvDefaultFunc("ARM_ACCESS_KEY", ""),
    50  			},
    51  
    52  			"resource_group_name": &schema.Schema{
    53  				Type:        schema.TypeString,
    54  				Optional:    true,
    55  				Description: "The resource group name.",
    56  			},
    57  
    58  			"arm_subscription_id": &schema.Schema{
    59  				Type:        schema.TypeString,
    60  				Optional:    true,
    61  				Description: "The Subscription ID.",
    62  				DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""),
    63  			},
    64  
    65  			"arm_client_id": &schema.Schema{
    66  				Type:        schema.TypeString,
    67  				Optional:    true,
    68  				Description: "The Client ID.",
    69  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
    70  			},
    71  
    72  			"arm_client_secret": &schema.Schema{
    73  				Type:        schema.TypeString,
    74  				Optional:    true,
    75  				Description: "The Client Secret.",
    76  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
    77  			},
    78  
    79  			"arm_tenant_id": &schema.Schema{
    80  				Type:        schema.TypeString,
    81  				Optional:    true,
    82  				Description: "The Tenant ID.",
    83  				DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
    84  			},
    85  		},
    86  	}
    87  
    88  	result := &Backend{Backend: s}
    89  	result.Backend.ConfigureFunc = result.configure
    90  	return result
    91  }
    92  
    93  type Backend struct {
    94  	*schema.Backend
    95  
    96  	// The fields below are set from configure
    97  	blobClient storage.BlobStorageClient
    98  
    99  	containerName string
   100  	keyName       string
   101  	leaseID       string
   102  }
   103  
   104  func (b *Backend) configure(ctx context.Context) error {
   105  	if b.containerName != "" {
   106  		return nil
   107  	}
   108  
   109  	// Grab the resource data
   110  	data := schema.FromContextBackendConfig(ctx)
   111  
   112  	b.containerName = data.Get("container_name").(string)
   113  	b.keyName = data.Get("key").(string)
   114  
   115  	blobClient, err := getBlobClient(data)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	b.blobClient = blobClient
   120  
   121  	return nil
   122  }
   123  
   124  func getBlobClient(d *schema.ResourceData) (storage.BlobStorageClient, error) {
   125  	var client storage.BlobStorageClient
   126  
   127  	env, err := getAzureEnvironment(d.Get("environment").(string))
   128  	if err != nil {
   129  		return client, err
   130  	}
   131  
   132  	storageAccountName := d.Get("storage_account_name").(string)
   133  
   134  	accessKey, err := getAccessKey(d, storageAccountName, env)
   135  	if err != nil {
   136  		return client, err
   137  	}
   138  
   139  	storageClient, err := storage.NewClient(storageAccountName, accessKey, env.StorageEndpointSuffix,
   140  		storage.DefaultAPIVersion, true)
   141  	if err != nil {
   142  		return client, fmt.Errorf("Error creating storage client for storage account %q: %s", storageAccountName, err)
   143  	}
   144  
   145  	client = storageClient.GetBlobService()
   146  	return client, nil
   147  }
   148  
   149  func getAccessKey(d *schema.ResourceData, storageAccountName string, env azure.Environment) (string, error) {
   150  	if key, ok := d.GetOk("access_key"); ok {
   151  		return key.(string), nil
   152  	}
   153  
   154  	resourceGroupName, rgOk := d.GetOk("resource_group_name")
   155  	subscriptionID, subOk := d.GetOk("arm_subscription_id")
   156  	clientID, clientIDOk := d.GetOk("arm_client_id")
   157  	clientSecret, clientSecretOK := d.GetOk("arm_client_secret")
   158  	tenantID, tenantIDOk := d.GetOk("arm_tenant_id")
   159  	if !rgOk || !subOk || !clientIDOk || !clientSecretOK || !tenantIDOk {
   160  		return "", fmt.Errorf("resource_group_name and credentials must be provided when access_key is absent")
   161  	}
   162  
   163  	oauthConfig, err := adal.NewOAuthConfig(env.ActiveDirectoryEndpoint, tenantID.(string))
   164  	if err != nil {
   165  		return "", err
   166  	}
   167  
   168  	spt, err := adal.NewServicePrincipalToken(*oauthConfig, clientID.(string), clientSecret.(string), env.ResourceManagerEndpoint)
   169  	if err != nil {
   170  		return "", err
   171  	}
   172  
   173  	accountsClient := armStorage.NewAccountsClientWithBaseURI(env.ResourceManagerEndpoint, subscriptionID.(string))
   174  	accountsClient.Authorizer = autorest.NewBearerAuthorizer(spt)
   175  
   176  	keys, err := accountsClient.ListKeys(resourceGroupName.(string), storageAccountName)
   177  	if err != nil {
   178  		return "", fmt.Errorf("Error retrieving keys for storage account %q: %s", storageAccountName, err)
   179  	}
   180  
   181  	if keys.Keys == nil {
   182  		return "", fmt.Errorf("Nil key returned for storage account %q", storageAccountName)
   183  	}
   184  
   185  	accessKeys := *keys.Keys
   186  	return *accessKeys[0].Value, nil
   187  }
   188  
   189  func getAzureEnvironment(environment string) (azure.Environment, error) {
   190  	if environment == "" {
   191  		return azure.PublicCloud, nil
   192  	}
   193  
   194  	env, err := azure.EnvironmentFromName(environment)
   195  	if err != nil {
   196  		// try again with wrapped value to support readable values like german instead of AZUREGERMANCLOUD
   197  		var innerErr error
   198  		env, innerErr = azure.EnvironmentFromName(fmt.Sprintf("AZURE%sCLOUD", environment))
   199  		if innerErr != nil {
   200  			return env, fmt.Errorf("invalid 'environment' configuration: %s", err)
   201  		}
   202  	}
   203  
   204  	return env, nil
   205  }