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