github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/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/signalwrapper"
    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 the storage account. We wrap this so that it is cancellable
   196  	// with a Ctrl-C since this can take a LONG time.
   197  	wrap := signalwrapper.Run(func(cancelCh <-chan struct{}) error {
   198  		_, err := storageClient.Create(resourceGroupName, storageAccountName, opts, cancelCh)
   199  		return err
   200  	})
   201  
   202  	// Check the result of the wrapped function.
   203  	var createErr error
   204  	select {
   205  	case <-time.After(1 * time.Hour):
   206  		// An hour is way above the expected P99 for this API call so
   207  		// we premature cancel and error here.
   208  		createErr = wrap.Cancel()
   209  	case createErr = <-wrap.ErrCh:
   210  		// Successfully ran (but perhaps not successfully completed)
   211  		// the function.
   212  	}
   213  
   214  	// The only way to get the ID back apparently is to read the resource again
   215  	read, err := storageClient.GetProperties(resourceGroupName, storageAccountName)
   216  
   217  	// Set the ID right away if we have one
   218  	if err == nil && read.ID != nil {
   219  		log.Printf("[INFO] storage account %q ID: %q", storageAccountName, *read.ID)
   220  		d.SetId(*read.ID)
   221  	}
   222  
   223  	// If we had a create error earlier then we return with that error now.
   224  	// We do this later here so that we can grab the ID above is possible.
   225  	if createErr != nil {
   226  		return fmt.Errorf(
   227  			"Error creating Azure Storage Account '%s': %s",
   228  			storageAccountName, createErr)
   229  	}
   230  
   231  	// Check the read error now that we know it would exist without a create err
   232  	if err != nil {
   233  		return err
   234  	}
   235  
   236  	// If we got no ID then the resource group doesn't yet exist
   237  	if read.ID == nil {
   238  		return fmt.Errorf("Cannot read Storage Account %s (resource group %s) ID",
   239  			storageAccountName, resourceGroupName)
   240  	}
   241  
   242  	log.Printf("[DEBUG] Waiting for Storage Account (%s) to become available", storageAccountName)
   243  	stateConf := &resource.StateChangeConf{
   244  		Pending:    []string{"Updating", "Creating"},
   245  		Target:     []string{"Succeeded"},
   246  		Refresh:    storageAccountStateRefreshFunc(client, resourceGroupName, storageAccountName),
   247  		Timeout:    30 * time.Minute,
   248  		MinTimeout: 15 * time.Second,
   249  	}
   250  	if _, err := stateConf.WaitForState(); err != nil {
   251  		return fmt.Errorf("Error waiting for Storage Account (%s) to become available: %s", storageAccountName, err)
   252  	}
   253  
   254  	return resourceArmStorageAccountRead(d, meta)
   255  }
   256  
   257  // resourceArmStorageAccountUpdate is unusual in the ARM API where most resources have a combined
   258  // and idempotent operation for CreateOrUpdate. In particular updating all of the parameters
   259  // available requires a call to Update per parameter...
   260  func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) error {
   261  	client := meta.(*ArmClient).storageServiceClient
   262  	id, err := parseAzureResourceID(d.Id())
   263  	if err != nil {
   264  		return err
   265  	}
   266  	storageAccountName := id.Path["storageAccounts"]
   267  	resourceGroupName := id.ResourceGroup
   268  
   269  	d.Partial(true)
   270  
   271  	if d.HasChange("account_type") {
   272  		accountType := d.Get("account_type").(string)
   273  
   274  		sku := storage.Sku{
   275  			Name: storage.SkuName(accountType),
   276  		}
   277  
   278  		opts := storage.AccountUpdateParameters{
   279  			Sku: &sku,
   280  		}
   281  		_, err := client.Update(resourceGroupName, storageAccountName, opts)
   282  		if err != nil {
   283  			return fmt.Errorf("Error updating Azure Storage Account type %q: %s", storageAccountName, err)
   284  		}
   285  
   286  		d.SetPartial("account_type")
   287  	}
   288  
   289  	if d.HasChange("access_tier") {
   290  		accessTier := d.Get("access_tier").(string)
   291  
   292  		opts := storage.AccountUpdateParameters{
   293  			Properties: &storage.AccountPropertiesUpdateParameters{
   294  				AccessTier: storage.AccessTier(accessTier),
   295  			},
   296  		}
   297  		_, err := client.Update(resourceGroupName, storageAccountName, opts)
   298  		if err != nil {
   299  			return fmt.Errorf("Error updating Azure Storage Account access_tier %q: %s", storageAccountName, err)
   300  		}
   301  
   302  		d.SetPartial("access_tier")
   303  	}
   304  
   305  	if d.HasChange("tags") {
   306  		tags := d.Get("tags").(map[string]interface{})
   307  
   308  		opts := storage.AccountUpdateParameters{
   309  			Tags: expandTags(tags),
   310  		}
   311  		_, err := client.Update(resourceGroupName, storageAccountName, opts)
   312  		if err != nil {
   313  			return fmt.Errorf("Error updating Azure Storage Account tags %q: %s", storageAccountName, err)
   314  		}
   315  
   316  		d.SetPartial("tags")
   317  	}
   318  
   319  	if d.HasChange("enable_blob_encryption") {
   320  		enableBlobEncryption := d.Get("enable_blob_encryption").(bool)
   321  
   322  		opts := storage.AccountUpdateParameters{
   323  			Properties: &storage.AccountPropertiesUpdateParameters{
   324  				Encryption: &storage.Encryption{
   325  					Services: &storage.EncryptionServices{
   326  						Blob: &storage.EncryptionService{
   327  							Enabled: &enableBlobEncryption,
   328  						},
   329  					},
   330  					KeySource: &storageAccountEncryptionSource,
   331  				},
   332  			},
   333  		}
   334  		_, err := client.Update(resourceGroupName, storageAccountName, opts)
   335  		if err != nil {
   336  			return fmt.Errorf("Error updating Azure Storage Account enable_blob_encryption %q: %s", storageAccountName, err)
   337  		}
   338  
   339  		d.SetPartial("enable_blob_encryption")
   340  	}
   341  
   342  	d.Partial(false)
   343  	return nil
   344  }
   345  
   346  func resourceArmStorageAccountRead(d *schema.ResourceData, meta interface{}) error {
   347  	client := meta.(*ArmClient).storageServiceClient
   348  
   349  	id, err := parseAzureResourceID(d.Id())
   350  	if err != nil {
   351  		return err
   352  	}
   353  	name := id.Path["storageAccounts"]
   354  	resGroup := id.ResourceGroup
   355  
   356  	resp, err := client.GetProperties(resGroup, name)
   357  	if err != nil {
   358  		if resp.StatusCode == http.StatusNotFound {
   359  			d.SetId("")
   360  			return nil
   361  		}
   362  		return fmt.Errorf("Error reading the state of AzureRM Storage Account %q: %s", name, err)
   363  	}
   364  
   365  	keys, err := client.ListKeys(resGroup, name)
   366  	if err != nil {
   367  		return err
   368  	}
   369  
   370  	accessKeys := *keys.Keys
   371  	d.Set("resource_group_name", resGroup)
   372  	d.Set("primary_access_key", accessKeys[0].Value)
   373  	d.Set("secondary_access_key", accessKeys[1].Value)
   374  	d.Set("location", resp.Location)
   375  	d.Set("account_kind", resp.Kind)
   376  	d.Set("account_type", resp.Sku.Name)
   377  	d.Set("primary_location", resp.Properties.PrimaryLocation)
   378  	d.Set("secondary_location", resp.Properties.SecondaryLocation)
   379  
   380  	if resp.Properties.AccessTier != "" {
   381  		d.Set("access_tier", resp.Properties.AccessTier)
   382  	}
   383  
   384  	if resp.Properties.PrimaryEndpoints != nil {
   385  		d.Set("primary_blob_endpoint", resp.Properties.PrimaryEndpoints.Blob)
   386  		d.Set("primary_queue_endpoint", resp.Properties.PrimaryEndpoints.Queue)
   387  		d.Set("primary_table_endpoint", resp.Properties.PrimaryEndpoints.Table)
   388  		d.Set("primary_file_endpoint", resp.Properties.PrimaryEndpoints.File)
   389  	}
   390  
   391  	if resp.Properties.SecondaryEndpoints != nil {
   392  		if resp.Properties.SecondaryEndpoints.Blob != nil {
   393  			d.Set("secondary_blob_endpoint", resp.Properties.SecondaryEndpoints.Blob)
   394  		} else {
   395  			d.Set("secondary_blob_endpoint", "")
   396  		}
   397  		if resp.Properties.SecondaryEndpoints.Queue != nil {
   398  			d.Set("secondary_queue_endpoint", resp.Properties.SecondaryEndpoints.Queue)
   399  		} else {
   400  			d.Set("secondary_queue_endpoint", "")
   401  		}
   402  		if resp.Properties.SecondaryEndpoints.Table != nil {
   403  			d.Set("secondary_table_endpoint", resp.Properties.SecondaryEndpoints.Table)
   404  		} else {
   405  			d.Set("secondary_table_endpoint", "")
   406  		}
   407  	}
   408  
   409  	if resp.Properties.Encryption != nil {
   410  		if resp.Properties.Encryption.Services.Blob != nil {
   411  			d.Set("enable_blob_encryption", resp.Properties.Encryption.Services.Blob.Enabled)
   412  		}
   413  	}
   414  
   415  	d.Set("name", resp.Name)
   416  
   417  	flattenAndSetTags(d, resp.Tags)
   418  
   419  	return nil
   420  }
   421  
   422  func resourceArmStorageAccountDelete(d *schema.ResourceData, meta interface{}) error {
   423  	client := meta.(*ArmClient).storageServiceClient
   424  
   425  	id, err := parseAzureResourceID(d.Id())
   426  	if err != nil {
   427  		return err
   428  	}
   429  	name := id.Path["storageAccounts"]
   430  	resGroup := id.ResourceGroup
   431  
   432  	_, err = client.Delete(resGroup, name)
   433  	if err != nil {
   434  		return fmt.Errorf("Error issuing AzureRM delete request for storage account %q: %s", name, err)
   435  	}
   436  
   437  	return nil
   438  }
   439  
   440  func validateArmStorageAccountName(v interface{}, k string) (ws []string, es []error) {
   441  	input := v.(string)
   442  
   443  	if !regexp.MustCompile(`\A([a-z0-9]{3,24})\z`).MatchString(input) {
   444  		es = append(es, fmt.Errorf("name can only consist of lowercase letters and numbers, and must be between 3 and 24 characters long"))
   445  	}
   446  
   447  	return
   448  }
   449  
   450  func validateArmStorageAccountType(v interface{}, k string) (ws []string, es []error) {
   451  	validAccountTypes := []string{"standard_lrs", "standard_zrs",
   452  		"standard_grs", "standard_ragrs", "premium_lrs"}
   453  
   454  	input := strings.ToLower(v.(string))
   455  
   456  	for _, valid := range validAccountTypes {
   457  		if valid == input {
   458  			return
   459  		}
   460  	}
   461  
   462  	es = append(es, fmt.Errorf("Invalid storage account type %q", input))
   463  	return
   464  }
   465  
   466  func storageAccountStateRefreshFunc(client *ArmClient, resourceGroupName string, storageAccountName string) resource.StateRefreshFunc {
   467  	return func() (interface{}, string, error) {
   468  		res, err := client.storageServiceClient.GetProperties(resourceGroupName, storageAccountName)
   469  		if err != nil {
   470  			return nil, "", fmt.Errorf("Error issuing read request in storageAccountStateRefreshFunc to Azure ARM for Storage Account '%s' (RG: '%s'): %s", storageAccountName, resourceGroupName, err)
   471  		}
   472  
   473  		return res, string(res.Properties.ProvisioningState), nil
   474  	}
   475  }