2016-01-10 01:22:00 +01:00
package azurerm
2016-06-02 01:08:14 +02:00
import (
"fmt"
2016-07-27 23:57:02 +02:00
"log"
2016-06-02 01:08:14 +02:00
"net/http"
"regexp"
"strings"
2016-07-27 23:57:02 +02:00
"time"
2016-06-02 01:08:14 +02:00
"github.com/Azure/azure-sdk-for-go/arm/storage"
2016-07-27 23:57:02 +02:00
"github.com/hashicorp/terraform/helper/resource"
2016-06-02 01:08:14 +02:00
"github.com/hashicorp/terraform/helper/schema"
2016-08-16 06:12:32 +02:00
"github.com/hashicorp/terraform/helper/signalwrapper"
2016-06-02 01:08:14 +02:00
)
func resourceArmStorageAccount ( ) * schema . Resource {
return & schema . Resource {
Create : resourceArmStorageAccountCreate ,
Read : resourceArmStorageAccountRead ,
Update : resourceArmStorageAccountUpdate ,
Delete : resourceArmStorageAccountDelete ,
2016-07-13 13:08:44 +02:00
Importer : & schema . ResourceImporter {
State : schema . ImportStatePassthrough ,
} ,
2016-06-02 01:08:14 +02:00
Schema : map [ string ] * schema . Schema {
"name" : {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
ValidateFunc : validateArmStorageAccountName ,
} ,
"resource_group_name" : {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
} ,
"location" : {
Type : schema . TypeString ,
Required : true ,
ForceNew : true ,
StateFunc : azureRMNormalizeLocation ,
} ,
"account_type" : {
Type : schema . TypeString ,
Required : true ,
ValidateFunc : validateArmStorageAccountType ,
} ,
"primary_location" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"secondary_location" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"primary_blob_endpoint" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"secondary_blob_endpoint" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"primary_queue_endpoint" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"secondary_queue_endpoint" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"primary_table_endpoint" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"secondary_table_endpoint" : {
Type : schema . TypeString ,
Computed : true ,
} ,
// NOTE: The API does not appear to expose a secondary file endpoint
"primary_file_endpoint" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"primary_access_key" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"secondary_access_key" : {
Type : schema . TypeString ,
Computed : true ,
} ,
"tags" : tagsSchema ( ) ,
} ,
}
}
func resourceArmStorageAccountCreate ( d * schema . ResourceData , meta interface { } ) error {
2016-07-27 23:57:02 +02:00
client := meta . ( * ArmClient )
storageClient := client . storageServiceClient
2016-06-02 01:08:14 +02:00
resourceGroupName := d . Get ( "resource_group_name" ) . ( string )
storageAccountName := d . Get ( "name" ) . ( string )
accountType := d . Get ( "account_type" ) . ( string )
location := d . Get ( "location" ) . ( string )
tags := d . Get ( "tags" ) . ( map [ string ] interface { } )
2016-06-30 16:36:08 +02:00
sku := storage . Sku {
Name : storage . SkuName ( accountType ) ,
}
2016-06-02 01:08:14 +02:00
opts := storage . AccountCreateParameters {
Location : & location ,
2016-06-30 16:36:08 +02:00
Sku : & sku ,
Tags : expandTags ( tags ) ,
2016-06-02 01:08:14 +02:00
}
2016-08-16 06:12:32 +02:00
// Create the storage account. We wrap this so that it is cancellable
// with a Ctrl-C since this can take a LONG time.
wrap := signalwrapper . Run ( func ( cancelCh <- chan struct { } ) error {
_ , err := storageClient . Create ( resourceGroupName , storageAccountName , opts , cancelCh )
return err
} )
2016-08-16 18:42:05 +02:00
// Check the result of the wrapped function.
2016-08-16 18:18:00 +02:00
var createErr error
2016-08-16 06:12:32 +02:00
select {
2016-08-16 18:26:33 +02:00
case <- time . After ( 1 * time . Hour ) :
// An hour is way above the expected P99 for this API call so
// we premature cancel and error here.
createErr = wrap . Cancel ( )
2016-08-16 18:18:00 +02:00
case createErr = <- wrap . ErrCh :
2016-08-16 06:12:32 +02:00
// Successfully ran (but perhaps not successfully completed)
// the function.
}
2016-06-02 01:08:14 +02:00
// The only way to get the ID back apparently is to read the resource again
2016-07-27 23:57:02 +02:00
read , err := storageClient . GetProperties ( resourceGroupName , storageAccountName )
2016-08-16 18:18:00 +02:00
// Set the ID right away if we have one
2016-08-16 18:49:54 +02:00
if err == nil && read . ID != nil {
2016-08-16 18:18:00 +02:00
log . Printf ( "[INFO] storage account %q ID: %q" , storageAccountName , * read . ID )
d . SetId ( * read . ID )
}
// If we had a create error earlier then we return with that error now.
// We do this later here so that we can grab the ID above is possible.
if createErr != nil {
return fmt . Errorf (
"Error creating Azure Storage Account '%s': %s" ,
storageAccountName , createErr )
}
2016-08-16 18:42:05 +02:00
// Check the read error now that we know it would exist without a create err
if err != nil {
return err
}
2016-08-16 18:18:00 +02:00
// If we got no ID then the resource group doesn't yet exist
2016-06-02 01:08:14 +02:00
if read . ID == nil {
return fmt . Errorf ( "Cannot read Storage Account %s (resource group %s) ID" ,
storageAccountName , resourceGroupName )
}
2016-07-27 23:57:02 +02:00
log . Printf ( "[DEBUG] Waiting for Storage Account (%s) to become available" , storageAccountName )
stateConf := & resource . StateChangeConf {
Pending : [ ] string { "Updating" , "Creating" } ,
Target : [ ] string { "Succeeded" } ,
Refresh : storageAccountStateRefreshFunc ( client , resourceGroupName , storageAccountName ) ,
Timeout : 30 * time . Minute ,
MinTimeout : 15 * time . Second ,
}
if _ , err := stateConf . WaitForState ( ) ; err != nil {
return fmt . Errorf ( "Error waiting for Storage Account (%s) to become available: %s" , storageAccountName , err )
}
2016-06-02 01:08:14 +02:00
return resourceArmStorageAccountRead ( d , meta )
}
// resourceArmStorageAccountUpdate is unusual in the ARM API where most resources have a combined
// and idempotent operation for CreateOrUpdate. In particular updating all of the parameters
// available requires a call to Update per parameter...
func resourceArmStorageAccountUpdate ( d * schema . ResourceData , meta interface { } ) error {
client := meta . ( * ArmClient ) . storageServiceClient
id , err := parseAzureResourceID ( d . Id ( ) )
if err != nil {
return err
}
storageAccountName := id . Path [ "storageAccounts" ]
resourceGroupName := id . ResourceGroup
d . Partial ( true )
if d . HasChange ( "account_type" ) {
accountType := d . Get ( "account_type" ) . ( string )
2016-06-30 16:36:08 +02:00
sku := storage . Sku {
Name : storage . SkuName ( accountType ) ,
}
2016-06-02 01:08:14 +02:00
opts := storage . AccountUpdateParameters {
2016-06-30 16:36:08 +02:00
Sku : & sku ,
2016-06-02 01:08:14 +02:00
}
_ , err := client . Update ( resourceGroupName , storageAccountName , opts )
if err != nil {
return fmt . Errorf ( "Error updating Azure Storage Account type %q: %s" , storageAccountName , err )
}
d . SetPartial ( "account_type" )
}
if d . HasChange ( "tags" ) {
tags := d . Get ( "tags" ) . ( map [ string ] interface { } )
opts := storage . AccountUpdateParameters {
Tags : expandTags ( tags ) ,
}
_ , err := client . Update ( resourceGroupName , storageAccountName , opts )
if err != nil {
return fmt . Errorf ( "Error updating Azure Storage Account tags %q: %s" , storageAccountName , err )
}
d . SetPartial ( "tags" )
}
d . Partial ( false )
return nil
}
func resourceArmStorageAccountRead ( d * schema . ResourceData , meta interface { } ) error {
client := meta . ( * ArmClient ) . storageServiceClient
id , err := parseAzureResourceID ( d . Id ( ) )
if err != nil {
return err
}
name := id . Path [ "storageAccounts" ]
resGroup := id . ResourceGroup
resp , err := client . GetProperties ( resGroup , name )
if err != nil {
return fmt . Errorf ( "Error reading the state of AzureRM Storage Account %q: %s" , name , err )
}
provider/azurerm: Reordering the checks after an Azure API Get
We are receiving suggestions of a panic as follows:
```
2016/09/01 07:21:55 [DEBUG] plugin: terraform: panic: runtime error: invalid memory address or nil pointer dereference
2016/09/01 07:21:55 [DEBUG] plugin: terraform: [signal SIGSEGV: segmentation violation code=0x1 addr=0x10 pc=0xa3170f]
2016/09/01 07:21:55 [DEBUG] plugin: terraform:
2016/09/01 07:21:55 [DEBUG] plugin: terraform: goroutine 114 [running]:
2016/09/01 07:21:55 [DEBUG] plugin: terraform: panic(0x27f4e60, 0xc4200100e0)
2016/09/01 07:21:55 [DEBUG] plugin: terraform: /opt/go/src/runtime/panic.go:500 +0x1a1
2016/09/01 07:21:55 [DEBUG] plugin: terraform: github.com/hashicorp/terraform/builtin/providers/azurerm.resourceArmVirtualMachineRead(0xc4206d8060, 0x2995620, 0xc4204d0000, 0x0, 0x17)
2016/09/01 07:21:55 [DEBUG] plugin: terraform: /opt/gopath/src/github.com/hashicorp/terraform/builtin/providers/azurerm/resource_arm_virtual_machine.go:488 +0x1ff
2016/09/01 07:21:55 [DEBUG] plugin: terraform: github.com/hashicorp/terraform/helper/schema.(*Resource).Refresh(0xc420017a40, 0xc42040c780, 0x2995620, 0xc4204d0000, 0xc42019c990, 0x1, 0x0)
```
This is because the code is as follows:
```
resp, err := client.Get(resGroup, vnetName, name)
if resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
if err != nil {
return fmt.Errorf("Error making Read request on Azure virtual network peering %s: %s", name, err)
}
```
When a request throws an error, the response object isn't valid. Therefore, we need to flip that code to check the error first
```
resp, err := client.Get(resGroup, vnetName, name)
if err != nil {
return fmt.Errorf("Error making Read request on Azure virtual network peering %s: %s", name, err)
}
if resp.StatusCode == http.StatusNotFound {
d.SetId("")
return nil
}
```
2016-09-01 16:31:42 +02:00
if resp . StatusCode == http . StatusNotFound {
d . SetId ( "" )
return nil
}
2016-06-02 01:08:14 +02:00
keys , err := client . ListKeys ( resGroup , name )
if err != nil {
return err
}
2016-06-30 16:36:08 +02:00
accessKeys := * keys . Keys
2016-07-16 15:36:15 +02:00
d . Set ( "primary_access_key" , accessKeys [ 0 ] . Value )
d . Set ( "secondary_access_key" , accessKeys [ 1 ] . Value )
2016-06-02 01:08:14 +02:00
d . Set ( "location" , resp . Location )
2016-06-30 16:36:08 +02:00
d . Set ( "account_type" , resp . Sku . Name )
2016-06-02 01:08:14 +02:00
d . Set ( "primary_location" , resp . Properties . PrimaryLocation )
d . Set ( "secondary_location" , resp . Properties . SecondaryLocation )
if resp . Properties . PrimaryEndpoints != nil {
d . Set ( "primary_blob_endpoint" , resp . Properties . PrimaryEndpoints . Blob )
d . Set ( "primary_queue_endpoint" , resp . Properties . PrimaryEndpoints . Queue )
d . Set ( "primary_table_endpoint" , resp . Properties . PrimaryEndpoints . Table )
d . Set ( "primary_file_endpoint" , resp . Properties . PrimaryEndpoints . File )
}
if resp . Properties . SecondaryEndpoints != nil {
if resp . Properties . SecondaryEndpoints . Blob != nil {
d . Set ( "secondary_blob_endpoint" , resp . Properties . SecondaryEndpoints . Blob )
} else {
d . Set ( "secondary_blob_endpoint" , "" )
}
if resp . Properties . SecondaryEndpoints . Queue != nil {
d . Set ( "secondary_queue_endpoint" , resp . Properties . SecondaryEndpoints . Queue )
} else {
d . Set ( "secondary_queue_endpoint" , "" )
}
if resp . Properties . SecondaryEndpoints . Table != nil {
d . Set ( "secondary_table_endpoint" , resp . Properties . SecondaryEndpoints . Table )
} else {
d . Set ( "secondary_table_endpoint" , "" )
}
}
2016-07-13 13:08:44 +02:00
d . Set ( "name" , resp . Name )
2016-06-02 01:08:14 +02:00
flattenAndSetTags ( d , resp . Tags )
return nil
}
func resourceArmStorageAccountDelete ( d * schema . ResourceData , meta interface { } ) error {
client := meta . ( * ArmClient ) . storageServiceClient
id , err := parseAzureResourceID ( d . Id ( ) )
if err != nil {
return err
}
name := id . Path [ "storageAccounts" ]
resGroup := id . ResourceGroup
_ , err = client . Delete ( resGroup , name )
if err != nil {
return fmt . Errorf ( "Error issuing AzureRM delete request for storage account %q: %s" , name , err )
}
return nil
}
func validateArmStorageAccountName ( v interface { } , k string ) ( ws [ ] string , es [ ] error ) {
input := v . ( string )
if ! regexp . MustCompile ( ` \A([a-z0-9] { 3,24})\z ` ) . MatchString ( input ) {
es = append ( es , fmt . Errorf ( "name can only consist of lowercase letters and numbers, and must be between 3 and 24 characters long" ) )
}
return
}
func validateArmStorageAccountType ( v interface { } , k string ) ( ws [ ] string , es [ ] error ) {
validAccountTypes := [ ] string { "standard_lrs" , "standard_zrs" ,
"standard_grs" , "standard_ragrs" , "premium_lrs" }
input := strings . ToLower ( v . ( string ) )
for _ , valid := range validAccountTypes {
if valid == input {
return
}
}
es = append ( es , fmt . Errorf ( "Invalid storage account type %q" , input ) )
return
}
2016-07-27 23:57:02 +02:00
func storageAccountStateRefreshFunc ( client * ArmClient , resourceGroupName string , storageAccountName string ) resource . StateRefreshFunc {
return func ( ) ( interface { } , string , error ) {
res , err := client . storageServiceClient . GetProperties ( resourceGroupName , storageAccountName )
if err != nil {
return nil , "" , fmt . Errorf ( "Error issuing read request in storageAccountStateRefreshFunc to Azure ARM for Storage Account '%s' (RG: '%s'): %s" , storageAccountName , resourceGroupName , err )
}
return res , string ( res . Properties . ProvisioningState ) , nil
}
}