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