github.com/renier/terraform@v0.7.8-0.20161024133817-eb8a9ef5471a/builtin/providers/azurerm/provider.go (about) 1 package azurerm 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "sync" 9 10 "github.com/hashicorp/go-multierror" 11 "github.com/hashicorp/terraform/helper/mutexkv" 12 "github.com/hashicorp/terraform/helper/resource" 13 "github.com/hashicorp/terraform/helper/schema" 14 "github.com/hashicorp/terraform/terraform" 15 riviera "github.com/jen20/riviera/azure" 16 ) 17 18 // Provider returns a terraform.ResourceProvider. 19 func Provider() terraform.ResourceProvider { 20 return &schema.Provider{ 21 Schema: map[string]*schema.Schema{ 22 "subscription_id": { 23 Type: schema.TypeString, 24 Required: true, 25 DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""), 26 }, 27 28 "client_id": { 29 Type: schema.TypeString, 30 Required: true, 31 DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""), 32 }, 33 34 "client_secret": { 35 Type: schema.TypeString, 36 Required: true, 37 DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""), 38 }, 39 40 "tenant_id": { 41 Type: schema.TypeString, 42 Required: true, 43 DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), 44 }, 45 }, 46 47 ResourcesMap: map[string]*schema.Resource{ 48 // These resources use the Azure ARM SDK 49 "azurerm_availability_set": resourceArmAvailabilitySet(), 50 "azurerm_cdn_endpoint": resourceArmCdnEndpoint(), 51 "azurerm_cdn_profile": resourceArmCdnProfile(), 52 53 "azurerm_lb": resourceArmLoadBalancer(), 54 "azurerm_lb_backend_address_pool": resourceArmLoadBalancerBackendAddressPool(), 55 "azurerm_lb_nat_rule": resourceArmLoadBalancerNatRule(), 56 "azurerm_lb_nat_pool": resourceArmLoadBalancerNatPool(), 57 "azurerm_lb_probe": resourceArmLoadBalancerProbe(), 58 "azurerm_lb_rule": resourceArmLoadBalancerRule(), 59 60 "azurerm_local_network_gateway": resourceArmLocalNetworkGateway(), 61 "azurerm_network_interface": resourceArmNetworkInterface(), 62 "azurerm_network_security_group": resourceArmNetworkSecurityGroup(), 63 "azurerm_network_security_rule": resourceArmNetworkSecurityRule(), 64 "azurerm_public_ip": resourceArmPublicIp(), 65 "azurerm_route": resourceArmRoute(), 66 "azurerm_route_table": resourceArmRouteTable(), 67 "azurerm_servicebus_namespace": resourceArmServiceBusNamespace(), 68 "azurerm_servicebus_subscription": resourceArmServiceBusSubscription(), 69 "azurerm_servicebus_topic": resourceArmServiceBusTopic(), 70 "azurerm_storage_account": resourceArmStorageAccount(), 71 "azurerm_storage_blob": resourceArmStorageBlob(), 72 "azurerm_storage_container": resourceArmStorageContainer(), 73 "azurerm_storage_queue": resourceArmStorageQueue(), 74 "azurerm_storage_table": resourceArmStorageTable(), 75 "azurerm_subnet": resourceArmSubnet(), 76 "azurerm_template_deployment": resourceArmTemplateDeployment(), 77 "azurerm_traffic_manager_endpoint": resourceArmTrafficManagerEndpoint(), 78 "azurerm_traffic_manager_profile": resourceArmTrafficManagerProfile(), 79 "azurerm_virtual_machine": resourceArmVirtualMachine(), 80 "azurerm_virtual_machine_scale_set": resourceArmVirtualMachineScaleSet(), 81 "azurerm_virtual_network": resourceArmVirtualNetwork(), 82 "azurerm_virtual_network_peering": resourceArmVirtualNetworkPeering(), 83 84 // These resources use the Riviera SDK 85 "azurerm_dns_a_record": resourceArmDnsARecord(), 86 "azurerm_dns_aaaa_record": resourceArmDnsAAAARecord(), 87 "azurerm_dns_cname_record": resourceArmDnsCNameRecord(), 88 "azurerm_dns_mx_record": resourceArmDnsMxRecord(), 89 "azurerm_dns_ns_record": resourceArmDnsNsRecord(), 90 "azurerm_dns_srv_record": resourceArmDnsSrvRecord(), 91 "azurerm_dns_txt_record": resourceArmDnsTxtRecord(), 92 "azurerm_dns_zone": resourceArmDnsZone(), 93 "azurerm_resource_group": resourceArmResourceGroup(), 94 "azurerm_search_service": resourceArmSearchService(), 95 "azurerm_sql_database": resourceArmSqlDatabase(), 96 "azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(), 97 "azurerm_sql_server": resourceArmSqlServer(), 98 }, 99 ConfigureFunc: providerConfigure, 100 } 101 } 102 103 // Config is the configuration structure used to instantiate a 104 // new Azure management client. 105 type Config struct { 106 ManagementURL string 107 108 SubscriptionID string 109 ClientID string 110 ClientSecret string 111 TenantID string 112 113 validateCredentialsOnce sync.Once 114 } 115 116 func (c *Config) validate() error { 117 var err *multierror.Error 118 119 if c.SubscriptionID == "" { 120 err = multierror.Append(err, fmt.Errorf("Subscription ID must be configured for the AzureRM provider")) 121 } 122 if c.ClientID == "" { 123 err = multierror.Append(err, fmt.Errorf("Client ID must be configured for the AzureRM provider")) 124 } 125 if c.ClientSecret == "" { 126 err = multierror.Append(err, fmt.Errorf("Client Secret must be configured for the AzureRM provider")) 127 } 128 if c.TenantID == "" { 129 err = multierror.Append(err, fmt.Errorf("Tenant ID must be configured for the AzureRM provider")) 130 } 131 132 return err.ErrorOrNil() 133 } 134 135 func providerConfigure(d *schema.ResourceData) (interface{}, error) { 136 config := &Config{ 137 SubscriptionID: d.Get("subscription_id").(string), 138 ClientID: d.Get("client_id").(string), 139 ClientSecret: d.Get("client_secret").(string), 140 TenantID: d.Get("tenant_id").(string), 141 } 142 143 if err := config.validate(); err != nil { 144 return nil, err 145 } 146 147 client, err := config.getArmClient() 148 if err != nil { 149 return nil, err 150 } 151 152 err = registerAzureResourceProvidersWithSubscription(client.rivieraClient) 153 if err != nil { 154 return nil, err 155 } 156 157 return client, nil 158 } 159 160 func registerProviderWithSubscription(providerName string, client *riviera.Client) error { 161 request := client.NewRequest() 162 request.Command = riviera.RegisterResourceProvider{ 163 Namespace: providerName, 164 } 165 166 response, err := request.Execute() 167 if err != nil { 168 return fmt.Errorf("Cannot request provider registration for Azure Resource Manager: %s.", err) 169 } 170 171 if !response.IsSuccessful() { 172 return fmt.Errorf("Credentials for accessing the Azure Resource Manager API are likely " + 173 "to be incorrect, or\n the service principal does not have permission to use " + 174 "the Azure Service Management\n API.") 175 } 176 177 return nil 178 } 179 180 var providerRegistrationOnce sync.Once 181 182 // registerAzureResourceProvidersWithSubscription uses the providers client to register 183 // all Azure resource providers which the Terraform provider may require (regardless of 184 // whether they are actually used by the configuration or not). It was confirmed by Microsoft 185 // that this is the approach their own internal tools also take. 186 func registerAzureResourceProvidersWithSubscription(client *riviera.Client) error { 187 var err error 188 providerRegistrationOnce.Do(func() { 189 // We register Microsoft.Compute during client initialization 190 providers := []string{"Microsoft.Network", "Microsoft.Cdn", "Microsoft.Storage", "Microsoft.Sql", "Microsoft.Search", "Microsoft.Resources", "Microsoft.ServiceBus"} 191 192 var wg sync.WaitGroup 193 wg.Add(len(providers)) 194 for _, providerName := range providers { 195 go func(p string) { 196 defer wg.Done() 197 if innerErr := registerProviderWithSubscription(p, client); err != nil { 198 err = innerErr 199 } 200 }(providerName) 201 } 202 wg.Wait() 203 }) 204 205 return err 206 } 207 208 // azureRMNormalizeLocation is a function which normalises human-readable region/location 209 // names (e.g. "West US") to the values used and returned by the Azure API (e.g. "westus"). 210 // In state we track the API internal version as it is easier to go from the human form 211 // to the canonical form than the other way around. 212 func azureRMNormalizeLocation(location interface{}) string { 213 input := location.(string) 214 return strings.Replace(strings.ToLower(input), " ", "", -1) 215 } 216 217 // armMutexKV is the instance of MutexKV for ARM resources 218 var armMutexKV = mutexkv.NewMutexKV() 219 220 func azureStateRefreshFunc(resourceURI string, client *ArmClient, command riviera.APICall) resource.StateRefreshFunc { 221 return func() (interface{}, string, error) { 222 req := client.rivieraClient.NewRequestForURI(resourceURI) 223 req.Command = command 224 225 res, err := req.Execute() 226 if err != nil { 227 return nil, "", fmt.Errorf("Error executing %T command in azureStateRefreshFunc", req.Command) 228 } 229 230 var value reflect.Value 231 if reflect.ValueOf(res.Parsed).Kind() == reflect.Ptr { 232 value = reflect.ValueOf(res.Parsed).Elem() 233 } else { 234 value = reflect.ValueOf(res.Parsed) 235 } 236 237 for i := 0; i < value.NumField(); i++ { // iterates through every struct type field 238 tag := value.Type().Field(i).Tag // returns the tag string 239 tagValue := tag.Get("mapstructure") 240 if tagValue == "provisioningState" { 241 return res.Parsed, value.Field(i).Elem().String(), nil 242 } 243 } 244 245 panic(fmt.Errorf("azureStateRefreshFunc called on structure %T with no mapstructure:provisioningState tag. This is a bug", res.Parsed)) 246 } 247 } 248 249 // Resource group names can be capitalised, but we store them in lowercase. 250 // Use a custom diff function to avoid creation of new resources. 251 func resourceAzurermResourceGroupNameDiffSuppress(k, old, new string, d *schema.ResourceData) bool { 252 return strings.ToLower(old) == strings.ToLower(new) 253 }