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