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 }