github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/azurerm/resource_arm_storage_account.go (about)

     1  package azurerm
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net/http"
     7  	"regexp"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/Azure/azure-sdk-for-go/arm/storage"
    12  	"github.com/hashicorp/terraform/helper/resource"
    13  	"github.com/hashicorp/terraform/helper/schema"
    14  	"github.com/hashicorp/terraform/helper/validation"
    15  )
    16  
    17  // The KeySource of storage.Encryption appears to require this value
    18  // for Encryption services to work
    19  var storageAccountEncryptionSource = "Microsoft.Storage"
    20  
    21  const blobStorageAccountDefaultAccessTier = "Hot"
    22  
    23  func resourceArmStorageAccount() *schema.Resource {
    24  	return &schema.Resource{
    25  		Create: resourceArmStorageAccountCreate,
    26  		Read:   resourceArmStorageAccountRead,
    27  		Update: resourceArmStorageAccountUpdate,
    28  		Delete: resourceArmStorageAccountDelete,
    29  		Importer: &schema.ResourceImporter{
    30  			State: schema.ImportStatePassthrough,
    31  		},
    32  
    33  		Schema: map[string]*schema.Schema{
    34  			"name": {
    35  				Type:         schema.TypeString,
    36  				Required:     true,
    37  				ForceNew:     true,
    38  				ValidateFunc: validateArmStorageAccountName,
    39  			},
    40  
    41  			"resource_group_name": {
    42  				Type:             schema.TypeString,
    43  				Required:         true,
    44  				ForceNew:         true,
    45  				DiffSuppressFunc: resourceAzurermResourceGroupNameDiffSuppress,
    46  			},
    47  
    48  			"location": locationSchema(),
    49  
    50  			"account_kind": {
    51  				Type:     schema.TypeString,
    52  				Optional: true,
    53  				ForceNew: true,
    54  				ValidateFunc: validation.StringInSlice([]string{
    55  					string(storage.Storage),
    56  					string(storage.BlobStorage),
    57  				}, true),
    58  				Default: string(storage.Storage),
    59  			},
    60  
    61  			"account_type": {
    62  				Type:         schema.TypeString,
    63  				Required:     true,
    64  				ValidateFunc: validateArmStorageAccountType,
    65  			},
    66  
    67  			// Only valid for BlobStorage accounts, defaults to "Hot" in create function
    68  			"access_tier": {
    69  				Type:     schema.TypeString,
    70  				Optional: true,
    71  				Computed: true,
    72  				ValidateFunc: validation.StringInSlice([]string{
    73  					string(storage.Cool),
    74  					string(storage.Hot),
    75  				}, true),
    76  			},
    77  
    78  			"enable_blob_encryption": {
    79  				Type:     schema.TypeBool,
    80  				Optional: true,
    81  			},
    82  
    83  			"primary_location": {
    84  				Type:     schema.TypeString,
    85  				Computed: true,
    86  			},
    87  
    88  			"secondary_location": {
    89  				Type:     schema.TypeString,
    90  				Computed: true,
    91  			},
    92  
    93  			"primary_blob_endpoint": {
    94  				Type:     schema.TypeString,
    95  				Computed: true,
    96  			},
    97  
    98  			"secondary_blob_endpoint": {
    99  				Type:     schema.TypeString,
   100  				Computed: true,
   101  			},
   102  
   103  			"primary_queue_endpoint": {
   104  				Type:     schema.TypeString,
   105  				Computed: true,
   106  			},
   107  
   108  			"secondary_queue_endpoint": {
   109  				Type:     schema.TypeString,
   110  				Computed: true,
   111  			},
   112  
   113  			"primary_table_endpoint": {
   114  				Type:     schema.TypeString,
   115  				Computed: true,
   116  			},
   117  
   118  			"secondary_table_endpoint": {
   119  				Type:     schema.TypeString,
   120  				Computed: true,
   121  			},
   122  
   123  			// NOTE: The API does not appear to expose a secondary file endpoint
   124  			"primary_file_endpoint": {
   125  				Type:     schema.TypeString,
   126  				Computed: true,
   127  			},
   128  
   129  			"primary_access_key": {
   130  				Type:     schema.TypeString,
   131  				Computed: true,
   132  			},
   133  
   134  			"secondary_access_key": {
   135  				Type:     schema.TypeString,
   136  				Computed: true,
   137  			},
   138  
   139  			"tags": tagsSchema(),
   140  		},
   141  	}
   142  }
   143  
   144  func resourceArmStorageAccountCreate(d *schema.ResourceData, meta interface{}) error {
   145  	client := meta.(*ArmClient)
   146  	storageClient := client.storageServiceClient
   147  
   148  	resourceGroupName := d.Get("resource_group_name").(string)
   149  	storageAccountName := d.Get("name").(string)
   150  	accountKind := d.Get("account_kind").(string)
   151  	accountType := d.Get("account_type").(string)
   152  
   153  	location := d.Get("location").(string)
   154  	tags := d.Get("tags").(map[string]interface{})
   155  	enableBlobEncryption := d.Get("enable_blob_encryption").(bool)
   156  
   157  	sku := storage.Sku{
   158  		Name: storage.SkuName(accountType),
   159  	}
   160  
   161  	opts := storage.AccountCreateParameters{
   162  		Location: &location,
   163  		Sku:      &sku,
   164  		Tags:     expandTags(tags),
   165  		Kind:     storage.Kind(accountKind),
   166  		AccountPropertiesCreateParameters: &storage.AccountPropertiesCreateParameters{
   167  			Encryption: &storage.Encryption{
   168  				Services: &storage.EncryptionServices{
   169  					Blob: &storage.EncryptionService{
   170  						Enabled: &enableBlobEncryption,
   171  					},
   172  				},
   173  				KeySource: &storageAccountEncryptionSource,
   174  			},
   175  		},
   176  	}
   177  
   178  	// AccessTier is only valid for BlobStorage accounts
   179  	if accountKind == string(storage.BlobStorage) {
   180  		accessTier, ok := d.GetOk("access_tier")
   181  		if !ok {
   182  			// default to "Hot"
   183  			accessTier = blobStorageAccountDefaultAccessTier
   184  		}
   185  
   186  		opts.AccountPropertiesCreateParameters.AccessTier = storage.AccessTier(accessTier.(string))
   187  	}
   188  
   189  	// Create
   190  	_, createErr := storageClient.Create(
   191  		resourceGroupName, storageAccountName, opts, make(chan struct{}))
   192  
   193  	// The only way to get the ID back apparently is to read the resource again
   194  	read, err := storageClient.GetProperties(resourceGroupName, storageAccountName)
   195  
   196  	// Set the ID right away if we have one
   197  	if err == nil && read.ID != nil {
   198  		log.Printf("[INFO] storage account %q ID: %q", storageAccountName, *read.ID)
   199  		d.SetId(*read.ID)
   200  	}
   201  
   202  	// If we had a create error earlier then we return with that error now.
   203  	// We do this later here so that we can grab the ID above is possible.
   204  	if createErr != nil {
   205  		return fmt.Errorf(
   206  			"Error creating Azure Storage Account '%s': %s",
   207  			storageAccountName, createErr)
   208  	}
   209  
   210  	// Check the read error now that we know it would exist without a create err
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	// If we got no ID then the resource group doesn't yet exist
   216  	if read.ID == nil {
   217  		return fmt.Errorf("Cannot read Storage Account %s (resource group %s) ID",
   218  			storageAccountName, resourceGroupName)
   219  	}
   220  
   221  	log.Printf("[DEBUG] Waiting for Storage Account (%s) to become available", storageAccountName)
   222  	stateConf := &resource.StateChangeConf{
   223  		Pending:    []string{"Updating", "Creating"},
   224  		Target:     []string{"Succeeded"},
   225  		Refresh:    storageAccountStateRefreshFunc(client, resourceGroupName, storageAccountName),
   226  		Timeout:    30 * time.Minute,
   227  		MinTimeout: 15 * time.Second,
   228  	}
   229  	if _, err := stateConf.WaitForState(); err != nil {
   230  		return fmt.Errorf("Error waiting for Storage Account (%s) to become available: %s", storageAccountName, err)
   231  	}
   232  
   233  	return resourceArmStorageAccountRead(d, meta)
   234  }
   235  
   236  // resourceArmStorageAccountUpdate is unusual in the ARM API where most resources have a combined
   237  // and idempotent operation for CreateOrUpdate. In particular updating all of the parameters
   238  // available requires a call to Update per parameter...
   239  func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) error {
   240  	client := meta.(*ArmClient).storageServiceClient
   241  	id, err := parseAzureResourceID(d.Id())
   242  	if err != nil {
   243  		return err
   244  	}
   245  	storageAccountName := id.Path["storageAccounts"]
   246  	resourceGroupName := id.ResourceGroup
   247  
   248  	d.Partial(true)
   249  
   250  	if d.HasChange("account_type") {
   251  		accountType := d.Get("account_type").(string)
   252  
   253  		sku := storage.Sku{
   254  			Name: storage.SkuName(accountType),
   255  		}
   256  
   257  		opts := storage.AccountUpdateParameters{
   258  			Sku: &sku,
   259  		}
   260  		_, err := client.Update(resourceGroupName, storageAccountName, opts)
   261  		if err != nil {
   262  			return fmt.Errorf("Error updating Azure Storage Account type %q: %s", storageAccountName, err)
   263  		}
   264  
   265  		d.SetPartial("account_type")
   266  	}
   267  
   268  	if d.HasChange("access_tier") {
   269  		accessTier := d.Get("access_tier").(string)
   270  
   271  		opts := storage.AccountUpdateParameters{
   272  			AccountPropertiesUpdateParameters: &storage.AccountPropertiesUpdateParameters{
   273  				AccessTier: storage.AccessTier(accessTier),
   274  			},
   275  		}
   276  		_, err := client.Update(resourceGroupName, storageAccountName, opts)
   277  		if err != nil {
   278  			return fmt.Errorf("Error updating Azure Storage Account access_tier %q: %s", storageAccountName, err)
   279  		}
   280  
   281  		d.SetPartial("access_tier")
   282  	}
   283  
   284  	if d.HasChange("tags") {
   285  		tags := d.Get("tags").(map[string]interface{})
   286  
   287  		opts := storage.AccountUpdateParameters{
   288  			Tags: expandTags(tags),
   289  		}
   290  		_, err := client.Update(resourceGroupName, storageAccountName, opts)
   291  		if err != nil {
   292  			return fmt.Errorf("Error updating Azure Storage Account tags %q: %s", storageAccountName, err)
   293  		}
   294  
   295  		d.SetPartial("tags")
   296  	}
   297  
   298  	if d.HasChange("enable_blob_encryption") {
   299  		enableBlobEncryption := d.Get("enable_blob_encryption").(bool)
   300  
   301  		opts := storage.AccountUpdateParameters{
   302  			AccountPropertiesUpdateParameters: &storage.AccountPropertiesUpdateParameters{
   303  				Encryption: &storage.Encryption{
   304  					Services: &storage.EncryptionServices{
   305  						Blob: &storage.EncryptionService{
   306  							Enabled: &enableBlobEncryption,
   307  						},
   308  					},
   309  					KeySource: &storageAccountEncryptionSource,
   310  				},
   311  			},
   312  		}
   313  		_, err := client.Update(resourceGroupName, storageAccountName, opts)
   314  		if err != nil {
   315  			return fmt.Errorf("Error updating Azure Storage Account enable_blob_encryption %q: %s", storageAccountName, err)
   316  		}
   317  
   318  		d.SetPartial("enable_blob_encryption")
   319  	}
   320  
   321  	d.Partial(false)
   322  	return nil
   323  }
   324  
   325  func resourceArmStorageAccountRead(d *schema.ResourceData, meta interface{}) error {
   326  	client := meta.(*ArmClient).storageServiceClient
   327  
   328  	id, err := parseAzureResourceID(d.Id())
   329  	if err != nil {
   330  		return err
   331  	}
   332  	name := id.Path["storageAccounts"]
   333  	resGroup := id.ResourceGroup
   334  
   335  	resp, err := client.GetProperties(resGroup, name)
   336  	if err != nil {
   337  		if resp.StatusCode == http.StatusNotFound {
   338  			d.SetId("")
   339  			return nil
   340  		}
   341  		return fmt.Errorf("Error reading the state of AzureRM Storage Account %q: %s", name, err)
   342  	}
   343  
   344  	keys, err := client.ListKeys(resGroup, name)
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	accessKeys := *keys.Keys
   350  	d.Set("resource_group_name", resGroup)
   351  	d.Set("primary_access_key", accessKeys[0].Value)
   352  	d.Set("secondary_access_key", accessKeys[1].Value)
   353  	d.Set("location", resp.Location)
   354  	d.Set("account_kind", resp.Kind)
   355  	d.Set("account_type", resp.Sku.Name)
   356  	d.Set("primary_location", resp.AccountProperties.PrimaryLocation)
   357  	d.Set("secondary_location", resp.AccountProperties.SecondaryLocation)
   358  
   359  	if resp.AccountProperties.AccessTier != "" {
   360  		d.Set("access_tier", resp.AccountProperties.AccessTier)
   361  	}
   362  
   363  	if resp.AccountProperties.PrimaryEndpoints != nil {
   364  		d.Set("primary_blob_endpoint", resp.AccountProperties.PrimaryEndpoints.Blob)
   365  		d.Set("primary_queue_endpoint", resp.AccountProperties.PrimaryEndpoints.Queue)
   366  		d.Set("primary_table_endpoint", resp.AccountProperties.PrimaryEndpoints.Table)
   367  		d.Set("primary_file_endpoint", resp.AccountProperties.PrimaryEndpoints.File)
   368  	}
   369  
   370  	if resp.AccountProperties.SecondaryEndpoints != nil {
   371  		if resp.AccountProperties.SecondaryEndpoints.Blob != nil {
   372  			d.Set("secondary_blob_endpoint", resp.AccountProperties.SecondaryEndpoints.Blob)
   373  		} else {
   374  			d.Set("secondary_blob_endpoint", "")
   375  		}
   376  		if resp.AccountProperties.SecondaryEndpoints.Queue != nil {
   377  			d.Set("secondary_queue_endpoint", resp.AccountProperties.SecondaryEndpoints.Queue)
   378  		} else {
   379  			d.Set("secondary_queue_endpoint", "")
   380  		}
   381  		if resp.AccountProperties.SecondaryEndpoints.Table != nil {
   382  			d.Set("secondary_table_endpoint", resp.AccountProperties.SecondaryEndpoints.Table)
   383  		} else {
   384  			d.Set("secondary_table_endpoint", "")
   385  		}
   386  	}
   387  
   388  	if resp.AccountProperties.Encryption != nil {
   389  		if resp.AccountProperties.Encryption.Services.Blob != nil {
   390  			d.Set("enable_blob_encryption", resp.AccountProperties.Encryption.Services.Blob.Enabled)
   391  		}
   392  	}
   393  
   394  	d.Set("name", resp.Name)
   395  
   396  	flattenAndSetTags(d, resp.Tags)
   397  
   398  	return nil
   399  }
   400  
   401  func resourceArmStorageAccountDelete(d *schema.ResourceData, meta interface{}) error {
   402  	client := meta.(*ArmClient).storageServiceClient
   403  
   404  	id, err := parseAzureResourceID(d.Id())
   405  	if err != nil {
   406  		return err
   407  	}
   408  	name := id.Path["storageAccounts"]
   409  	resGroup := id.ResourceGroup
   410  
   411  	_, err = client.Delete(resGroup, name)
   412  	if err != nil {
   413  		return fmt.Errorf("Error issuing AzureRM delete request for storage account %q: %s", name, err)
   414  	}
   415  
   416  	return nil
   417  }
   418  
   419  func validateArmStorageAccountName(v interface{}, k string) (ws []string, es []error) {
   420  	input := v.(string)
   421  
   422  	if !regexp.MustCompile(`\A([a-z0-9]{3,24})\z`).MatchString(input) {
   423  		es = append(es, fmt.Errorf("name can only consist of lowercase letters and numbers, and must be between 3 and 24 characters long"))
   424  	}
   425  
   426  	return
   427  }
   428  
   429  func validateArmStorageAccountType(v interface{}, k string) (ws []string, es []error) {
   430  	validAccountTypes := []string{"standard_lrs", "standard_zrs",
   431  		"standard_grs", "standard_ragrs", "premium_lrs"}
   432  
   433  	input := strings.ToLower(v.(string))
   434  
   435  	for _, valid := range validAccountTypes {
   436  		if valid == input {
   437  			return
   438  		}
   439  	}
   440  
   441  	es = append(es, fmt.Errorf("Invalid storage account type %q", input))
   442  	return
   443  }
   444  
   445  func storageAccountStateRefreshFunc(client *ArmClient, resourceGroupName string, storageAccountName string) resource.StateRefreshFunc {
   446  	return func() (interface{}, string, error) {
   447  		res, err := client.storageServiceClient.GetProperties(resourceGroupName, storageAccountName)
   448  		if err != nil {
   449  			return nil, "", fmt.Errorf("Error issuing read request in storageAccountStateRefreshFunc to Azure ARM for Storage Account '%s' (RG: '%s'): %s", storageAccountName, resourceGroupName, err)
   450  		}
   451  
   452  		return res, string(res.AccountProperties.ProvisioningState), nil
   453  	}
   454  }