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