github.com/rhenning/terraform@v0.8.0-beta2/builtin/providers/azurerm/resource_arm_storage_account.go (about)

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