github.com/jonasi/terraform@v0.6.10-0.20160125170522-e865c342cc1f/builtin/providers/azurerm/provider.go (about) 1 package azurerm 2 3 import ( 4 "fmt" 5 "log" 6 "net/http" 7 "strings" 8 9 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest" 10 "github.com/hashicorp/terraform/helper/mutexkv" 11 "github.com/hashicorp/terraform/helper/schema" 12 "github.com/hashicorp/terraform/terraform" 13 ) 14 15 // Provider returns a terraform.ResourceProvider. 16 func Provider() terraform.ResourceProvider { 17 return &schema.Provider{ 18 Schema: map[string]*schema.Schema{ 19 "subscription_id": &schema.Schema{ 20 Type: schema.TypeString, 21 Required: true, 22 DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""), 23 }, 24 25 "client_id": &schema.Schema{ 26 Type: schema.TypeString, 27 Required: true, 28 DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""), 29 }, 30 31 "client_secret": &schema.Schema{ 32 Type: schema.TypeString, 33 Required: true, 34 DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""), 35 }, 36 37 "tenant_id": &schema.Schema{ 38 Type: schema.TypeString, 39 Required: true, 40 DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), 41 }, 42 }, 43 44 ResourcesMap: map[string]*schema.Resource{ 45 "azurerm_resource_group": resourceArmResourceGroup(), 46 "azurerm_virtual_network": resourceArmVirtualNetwork(), 47 "azurerm_local_network_gateway": resourceArmLocalNetworkGateway(), 48 "azurerm_availability_set": resourceArmAvailabilitySet(), 49 "azurerm_network_security_group": resourceArmNetworkSecurityGroup(), 50 "azurerm_network_security_rule": resourceArmNetworkSecurityRule(), 51 "azurerm_public_ip": resourceArmPublicIp(), 52 "azurerm_subnet": resourceArmSubnet(), 53 "azurerm_network_interface": resourceArmNetworkInterface(), 54 "azurerm_route_table": resourceArmRouteTable(), 55 "azurerm_route": resourceArmRoute(), 56 "azurerm_cdn_profile": resourceArmCdnProfile(), 57 "azurerm_cdn_endpoint": resourceArmCdnEndpoint(), 58 "azurerm_storage_account": resourceArmStorageAccount(), 59 }, 60 ConfigureFunc: providerConfigure, 61 } 62 } 63 64 // Config is the configuration structure used to instantiate a 65 // new Azure management client. 66 type Config struct { 67 ManagementURL string 68 69 SubscriptionID string 70 ClientID string 71 ClientSecret string 72 TenantID string 73 } 74 75 func providerConfigure(d *schema.ResourceData) (interface{}, error) { 76 config := Config{ 77 SubscriptionID: d.Get("subscription_id").(string), 78 ClientID: d.Get("client_id").(string), 79 ClientSecret: d.Get("client_secret").(string), 80 TenantID: d.Get("tenant_id").(string), 81 } 82 83 client, err := config.getArmClient() 84 if err != nil { 85 return nil, err 86 } 87 88 err = registerAzureResourceProvidersWithSubscription(&config, client) 89 if err != nil { 90 return nil, err 91 } 92 93 return client, nil 94 } 95 96 // registerAzureResourceProvidersWithSubscription uses the providers client to register 97 // all Azure resource providers which the Terraform provider may require (regardless of 98 // whether they are actually used by the configuration or not). It was confirmed by Microsoft 99 // that this is the approach their own internal tools also take. 100 func registerAzureResourceProvidersWithSubscription(config *Config, client *ArmClient) error { 101 providerClient := client.providers 102 103 providers := []string{"Microsoft.Network", "Microsoft.Compute", "Microsoft.Cdn", "Microsoft.Storage"} 104 105 for _, v := range providers { 106 res, err := providerClient.Register(v) 107 if err != nil { 108 return err 109 } 110 111 if res.StatusCode != http.StatusOK { 112 return fmt.Errorf("Error registering provider %q with subscription %q", v, config.SubscriptionID) 113 } 114 } 115 116 return nil 117 } 118 119 // azureRMNormalizeLocation is a function which normalises human-readable region/location 120 // names (e.g. "West US") to the values used and returned by the Azure API (e.g. "westus"). 121 // In state we track the API internal version as it is easier to go from the human form 122 // to the canonical form than the other way around. 123 func azureRMNormalizeLocation(location interface{}) string { 124 input := location.(string) 125 return strings.Replace(strings.ToLower(input), " ", "", -1) 126 } 127 128 // pollIndefinitelyAsNeeded is a terrible hack which is necessary because the Azure 129 // Storage API (and perhaps others) can have response times way beyond the default 130 // retry timeouts, with no apparent upper bound. This effectively causes the client 131 // to continue polling when it reaches the configured timeout. My investigations 132 // suggest that this is neccesary when deleting and recreating a storage account with 133 // the same name in a short (though undetermined) time period. 134 // 135 // It is possible that this will give Terraform the appearance of being slow in 136 // future: I have attempted to mitigate this by logging whenever this happens. We 137 // may want to revisit this with configurable timeouts in the future as clearly 138 // unbounded wait loops is not ideal. It does seem preferable to the current situation 139 // where our polling loop will time out _with an operation in progress_, but no ID 140 // for the resource - so the state will not know about it, and conflicts will occur 141 // on the next run. 142 func pollIndefinitelyAsNeeded(client autorest.Client, response *http.Response, acceptableCodes ...int) (*http.Response, error) { 143 var resp *http.Response 144 var err error 145 146 for { 147 resp, err = client.PollAsNeeded(response, acceptableCodes...) 148 if err != nil { 149 if resp.StatusCode != http.StatusAccepted { 150 log.Printf("[DEBUG] Starting new polling loop for %q", response.Request.URL.Path) 151 continue 152 } 153 154 return resp, err 155 } 156 157 return resp, nil 158 } 159 } 160 161 // armMutexKV is the instance of MutexKV for ARM resources 162 var armMutexKV = mutexkv.NewMutexKV()