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 }