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