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