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