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