github.com/rhenning/terraform@v0.8.0-beta2/builtin/providers/azurerm/resource_arm_storage_account.go (about) 1 package azurerm 2 3 import ( 4 "context" 5 "fmt" 6 "log" 7 "net/http" 8 "regexp" 9 "strings" 10 "time" 11 12 "github.com/Azure/azure-sdk-for-go/arm/storage" 13 "github.com/hashicorp/terraform/helper/resource" 14 "github.com/hashicorp/terraform/helper/schema" 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 196 cancelCtx, cancelFunc := context.WithTimeout(client.StopContext, 1*time.Hour) 197 _, createErr := storageClient.Create( 198 resourceGroupName, storageAccountName, opts, cancelCtx.Done()) 199 cancelFunc() 200 201 // The only way to get the ID back apparently is to read the resource again 202 read, err := storageClient.GetProperties(resourceGroupName, storageAccountName) 203 204 // Set the ID right away if we have one 205 if err == nil && read.ID != nil { 206 log.Printf("[INFO] storage account %q ID: %q", storageAccountName, *read.ID) 207 d.SetId(*read.ID) 208 } 209 210 // If we had a create error earlier then we return with that error now. 211 // We do this later here so that we can grab the ID above is possible. 212 if createErr != nil { 213 return fmt.Errorf( 214 "Error creating Azure Storage Account '%s': %s", 215 storageAccountName, createErr) 216 } 217 218 // Check the read error now that we know it would exist without a create err 219 if err != nil { 220 return err 221 } 222 223 // If we got no ID then the resource group doesn't yet exist 224 if read.ID == nil { 225 return fmt.Errorf("Cannot read Storage Account %s (resource group %s) ID", 226 storageAccountName, resourceGroupName) 227 } 228 229 log.Printf("[DEBUG] Waiting for Storage Account (%s) to become available", storageAccountName) 230 stateConf := &resource.StateChangeConf{ 231 Pending: []string{"Updating", "Creating"}, 232 Target: []string{"Succeeded"}, 233 Refresh: storageAccountStateRefreshFunc(client, resourceGroupName, storageAccountName), 234 Timeout: 30 * time.Minute, 235 MinTimeout: 15 * time.Second, 236 } 237 if _, err := stateConf.WaitForState(); err != nil { 238 return fmt.Errorf("Error waiting for Storage Account (%s) to become available: %s", storageAccountName, err) 239 } 240 241 return resourceArmStorageAccountRead(d, meta) 242 } 243 244 // resourceArmStorageAccountUpdate is unusual in the ARM API where most resources have a combined 245 // and idempotent operation for CreateOrUpdate. In particular updating all of the parameters 246 // available requires a call to Update per parameter... 247 func resourceArmStorageAccountUpdate(d *schema.ResourceData, meta interface{}) error { 248 client := meta.(*ArmClient).storageServiceClient 249 id, err := parseAzureResourceID(d.Id()) 250 if err != nil { 251 return err 252 } 253 storageAccountName := id.Path["storageAccounts"] 254 resourceGroupName := id.ResourceGroup 255 256 d.Partial(true) 257 258 if d.HasChange("account_type") { 259 accountType := d.Get("account_type").(string) 260 261 sku := storage.Sku{ 262 Name: storage.SkuName(accountType), 263 } 264 265 opts := storage.AccountUpdateParameters{ 266 Sku: &sku, 267 } 268 _, err := client.Update(resourceGroupName, storageAccountName, opts) 269 if err != nil { 270 return fmt.Errorf("Error updating Azure Storage Account type %q: %s", storageAccountName, err) 271 } 272 273 d.SetPartial("account_type") 274 } 275 276 if d.HasChange("access_tier") { 277 accessTier := d.Get("access_tier").(string) 278 279 opts := storage.AccountUpdateParameters{ 280 Properties: &storage.AccountPropertiesUpdateParameters{ 281 AccessTier: storage.AccessTier(accessTier), 282 }, 283 } 284 _, err := client.Update(resourceGroupName, storageAccountName, opts) 285 if err != nil { 286 return fmt.Errorf("Error updating Azure Storage Account access_tier %q: %s", storageAccountName, err) 287 } 288 289 d.SetPartial("access_tier") 290 } 291 292 if d.HasChange("tags") { 293 tags := d.Get("tags").(map[string]interface{}) 294 295 opts := storage.AccountUpdateParameters{ 296 Tags: expandTags(tags), 297 } 298 _, err := client.Update(resourceGroupName, storageAccountName, opts) 299 if err != nil { 300 return fmt.Errorf("Error updating Azure Storage Account tags %q: %s", storageAccountName, err) 301 } 302 303 d.SetPartial("tags") 304 } 305 306 if d.HasChange("enable_blob_encryption") { 307 enableBlobEncryption := d.Get("enable_blob_encryption").(bool) 308 309 opts := storage.AccountUpdateParameters{ 310 Properties: &storage.AccountPropertiesUpdateParameters{ 311 Encryption: &storage.Encryption{ 312 Services: &storage.EncryptionServices{ 313 Blob: &storage.EncryptionService{ 314 Enabled: &enableBlobEncryption, 315 }, 316 }, 317 KeySource: &storageAccountEncryptionSource, 318 }, 319 }, 320 } 321 _, err := client.Update(resourceGroupName, storageAccountName, opts) 322 if err != nil { 323 return fmt.Errorf("Error updating Azure Storage Account enable_blob_encryption %q: %s", storageAccountName, err) 324 } 325 326 d.SetPartial("enable_blob_encryption") 327 } 328 329 d.Partial(false) 330 return nil 331 } 332 333 func resourceArmStorageAccountRead(d *schema.ResourceData, meta interface{}) error { 334 client := meta.(*ArmClient).storageServiceClient 335 336 id, err := parseAzureResourceID(d.Id()) 337 if err != nil { 338 return err 339 } 340 name := id.Path["storageAccounts"] 341 resGroup := id.ResourceGroup 342 343 resp, err := client.GetProperties(resGroup, name) 344 if err != nil { 345 if resp.StatusCode == http.StatusNotFound { 346 d.SetId("") 347 return nil 348 } 349 return fmt.Errorf("Error reading the state of AzureRM Storage Account %q: %s", name, err) 350 } 351 352 keys, err := client.ListKeys(resGroup, name) 353 if err != nil { 354 return err 355 } 356 357 accessKeys := *keys.Keys 358 d.Set("resource_group_name", resGroup) 359 d.Set("primary_access_key", accessKeys[0].Value) 360 d.Set("secondary_access_key", accessKeys[1].Value) 361 d.Set("location", resp.Location) 362 d.Set("account_kind", resp.Kind) 363 d.Set("account_type", resp.Sku.Name) 364 d.Set("primary_location", resp.Properties.PrimaryLocation) 365 d.Set("secondary_location", resp.Properties.SecondaryLocation) 366 367 if resp.Properties.AccessTier != "" { 368 d.Set("access_tier", resp.Properties.AccessTier) 369 } 370 371 if resp.Properties.PrimaryEndpoints != nil { 372 d.Set("primary_blob_endpoint", resp.Properties.PrimaryEndpoints.Blob) 373 d.Set("primary_queue_endpoint", resp.Properties.PrimaryEndpoints.Queue) 374 d.Set("primary_table_endpoint", resp.Properties.PrimaryEndpoints.Table) 375 d.Set("primary_file_endpoint", resp.Properties.PrimaryEndpoints.File) 376 } 377 378 if resp.Properties.SecondaryEndpoints != nil { 379 if resp.Properties.SecondaryEndpoints.Blob != nil { 380 d.Set("secondary_blob_endpoint", resp.Properties.SecondaryEndpoints.Blob) 381 } else { 382 d.Set("secondary_blob_endpoint", "") 383 } 384 if resp.Properties.SecondaryEndpoints.Queue != nil { 385 d.Set("secondary_queue_endpoint", resp.Properties.SecondaryEndpoints.Queue) 386 } else { 387 d.Set("secondary_queue_endpoint", "") 388 } 389 if resp.Properties.SecondaryEndpoints.Table != nil { 390 d.Set("secondary_table_endpoint", resp.Properties.SecondaryEndpoints.Table) 391 } else { 392 d.Set("secondary_table_endpoint", "") 393 } 394 } 395 396 if resp.Properties.Encryption != nil { 397 if resp.Properties.Encryption.Services.Blob != nil { 398 d.Set("enable_blob_encryption", resp.Properties.Encryption.Services.Blob.Enabled) 399 } 400 } 401 402 d.Set("name", resp.Name) 403 404 flattenAndSetTags(d, resp.Tags) 405 406 return nil 407 } 408 409 func resourceArmStorageAccountDelete(d *schema.ResourceData, meta interface{}) error { 410 client := meta.(*ArmClient).storageServiceClient 411 412 id, err := parseAzureResourceID(d.Id()) 413 if err != nil { 414 return err 415 } 416 name := id.Path["storageAccounts"] 417 resGroup := id.ResourceGroup 418 419 _, err = client.Delete(resGroup, name) 420 if err != nil { 421 return fmt.Errorf("Error issuing AzureRM delete request for storage account %q: %s", name, err) 422 } 423 424 return nil 425 } 426 427 func validateArmStorageAccountName(v interface{}, k string) (ws []string, es []error) { 428 input := v.(string) 429 430 if !regexp.MustCompile(`\A([a-z0-9]{3,24})\z`).MatchString(input) { 431 es = append(es, fmt.Errorf("name can only consist of lowercase letters and numbers, and must be between 3 and 24 characters long")) 432 } 433 434 return 435 } 436 437 func validateArmStorageAccountType(v interface{}, k string) (ws []string, es []error) { 438 validAccountTypes := []string{"standard_lrs", "standard_zrs", 439 "standard_grs", "standard_ragrs", "premium_lrs"} 440 441 input := strings.ToLower(v.(string)) 442 443 for _, valid := range validAccountTypes { 444 if valid == input { 445 return 446 } 447 } 448 449 es = append(es, fmt.Errorf("Invalid storage account type %q", input)) 450 return 451 } 452 453 func storageAccountStateRefreshFunc(client *ArmClient, resourceGroupName string, storageAccountName string) resource.StateRefreshFunc { 454 return func() (interface{}, string, error) { 455 res, err := client.storageServiceClient.GetProperties(resourceGroupName, storageAccountName) 456 if err != nil { 457 return nil, "", fmt.Errorf("Error issuing read request in storageAccountStateRefreshFunc to Azure ARM for Storage Account '%s' (RG: '%s'): %s", storageAccountName, resourceGroupName, err) 458 } 459 460 return res, string(res.Properties.ProvisioningState), nil 461 } 462 }