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