github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/builtin/providers/azurerm/provider.go (about) 1 package azurerm 2 3 import ( 4 "fmt" 5 "log" 6 "reflect" 7 "strings" 8 "sync" 9 10 "github.com/Azure/azure-sdk-for-go/arm/resources/resources" 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 var p *schema.Provider 22 p = &schema.Provider{ 23 Schema: map[string]*schema.Schema{ 24 "subscription_id": { 25 Type: schema.TypeString, 26 Required: true, 27 DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""), 28 }, 29 30 "client_id": { 31 Type: schema.TypeString, 32 Required: true, 33 DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""), 34 }, 35 36 "client_secret": { 37 Type: schema.TypeString, 38 Required: true, 39 DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""), 40 }, 41 42 "tenant_id": { 43 Type: schema.TypeString, 44 Required: true, 45 DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), 46 }, 47 48 "environment": { 49 Type: schema.TypeString, 50 Required: true, 51 DefaultFunc: schema.EnvDefaultFunc("ARM_ENVIRONMENT", "public"), 52 }, 53 54 "skip_provider_registration": { 55 Type: schema.TypeBool, 56 Optional: true, 57 DefaultFunc: schema.EnvDefaultFunc("ARM_SKIP_PROVIDER_REGISTRATION", false), 58 }, 59 }, 60 61 DataSourcesMap: map[string]*schema.Resource{ 62 "azurerm_client_config": dataSourceArmClientConfig(), 63 }, 64 65 ResourcesMap: map[string]*schema.Resource{ 66 // These resources use the Azure ARM SDK 67 "azurerm_availability_set": resourceArmAvailabilitySet(), 68 "azurerm_cdn_endpoint": resourceArmCdnEndpoint(), 69 "azurerm_cdn_profile": resourceArmCdnProfile(), 70 "azurerm_container_registry": resourceArmContainerRegistry(), 71 "azurerm_container_service": resourceArmContainerService(), 72 73 "azurerm_eventhub": resourceArmEventHub(), 74 "azurerm_eventhub_authorization_rule": resourceArmEventHubAuthorizationRule(), 75 "azurerm_eventhub_consumer_group": resourceArmEventHubConsumerGroup(), 76 "azurerm_eventhub_namespace": resourceArmEventHubNamespace(), 77 78 "azurerm_lb": resourceArmLoadBalancer(), 79 "azurerm_lb_backend_address_pool": resourceArmLoadBalancerBackendAddressPool(), 80 "azurerm_lb_nat_rule": resourceArmLoadBalancerNatRule(), 81 "azurerm_lb_nat_pool": resourceArmLoadBalancerNatPool(), 82 "azurerm_lb_probe": resourceArmLoadBalancerProbe(), 83 "azurerm_lb_rule": resourceArmLoadBalancerRule(), 84 85 "azurerm_key_vault": resourceArmKeyVault(), 86 "azurerm_local_network_gateway": resourceArmLocalNetworkGateway(), 87 "azurerm_network_interface": resourceArmNetworkInterface(), 88 "azurerm_network_security_group": resourceArmNetworkSecurityGroup(), 89 "azurerm_network_security_rule": resourceArmNetworkSecurityRule(), 90 "azurerm_public_ip": resourceArmPublicIp(), 91 "azurerm_redis_cache": resourceArmRedisCache(), 92 "azurerm_route": resourceArmRoute(), 93 "azurerm_route_table": resourceArmRouteTable(), 94 "azurerm_servicebus_namespace": resourceArmServiceBusNamespace(), 95 "azurerm_servicebus_subscription": resourceArmServiceBusSubscription(), 96 "azurerm_servicebus_topic": resourceArmServiceBusTopic(), 97 "azurerm_storage_account": resourceArmStorageAccount(), 98 "azurerm_storage_blob": resourceArmStorageBlob(), 99 "azurerm_storage_container": resourceArmStorageContainer(), 100 "azurerm_storage_share": resourceArmStorageShare(), 101 "azurerm_storage_queue": resourceArmStorageQueue(), 102 "azurerm_storage_table": resourceArmStorageTable(), 103 "azurerm_subnet": resourceArmSubnet(), 104 "azurerm_template_deployment": resourceArmTemplateDeployment(), 105 "azurerm_traffic_manager_endpoint": resourceArmTrafficManagerEndpoint(), 106 "azurerm_traffic_manager_profile": resourceArmTrafficManagerProfile(), 107 "azurerm_virtual_machine_extension": resourceArmVirtualMachineExtensions(), 108 "azurerm_virtual_machine": resourceArmVirtualMachine(), 109 "azurerm_virtual_machine_scale_set": resourceArmVirtualMachineScaleSet(), 110 "azurerm_virtual_network": resourceArmVirtualNetwork(), 111 "azurerm_virtual_network_peering": resourceArmVirtualNetworkPeering(), 112 113 // These resources use the Riviera SDK 114 "azurerm_dns_a_record": resourceArmDnsARecord(), 115 "azurerm_dns_aaaa_record": resourceArmDnsAAAARecord(), 116 "azurerm_dns_cname_record": resourceArmDnsCNameRecord(), 117 "azurerm_dns_mx_record": resourceArmDnsMxRecord(), 118 "azurerm_dns_ns_record": resourceArmDnsNsRecord(), 119 "azurerm_dns_srv_record": resourceArmDnsSrvRecord(), 120 "azurerm_dns_txt_record": resourceArmDnsTxtRecord(), 121 "azurerm_dns_zone": resourceArmDnsZone(), 122 "azurerm_resource_group": resourceArmResourceGroup(), 123 "azurerm_search_service": resourceArmSearchService(), 124 "azurerm_sql_database": resourceArmSqlDatabase(), 125 "azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(), 126 "azurerm_sql_server": resourceArmSqlServer(), 127 }, 128 } 129 130 p.ConfigureFunc = providerConfigure(p) 131 132 return p 133 } 134 135 // Config is the configuration structure used to instantiate a 136 // new Azure management client. 137 type Config struct { 138 ManagementURL string 139 140 SubscriptionID string 141 ClientID string 142 ClientSecret string 143 TenantID string 144 Environment string 145 SkipProviderRegistration bool 146 147 validateCredentialsOnce sync.Once 148 } 149 150 func (c *Config) validate() error { 151 var err *multierror.Error 152 153 if c.SubscriptionID == "" { 154 err = multierror.Append(err, fmt.Errorf("Subscription ID must be configured for the AzureRM provider")) 155 } 156 if c.ClientID == "" { 157 err = multierror.Append(err, fmt.Errorf("Client ID must be configured for the AzureRM provider")) 158 } 159 if c.ClientSecret == "" { 160 err = multierror.Append(err, fmt.Errorf("Client Secret must be configured for the AzureRM provider")) 161 } 162 if c.TenantID == "" { 163 err = multierror.Append(err, fmt.Errorf("Tenant ID must be configured for the AzureRM provider")) 164 } 165 if c.Environment == "" { 166 err = multierror.Append(err, fmt.Errorf("Environment must be configured for the AzureRM provider")) 167 } 168 169 return err.ErrorOrNil() 170 } 171 172 func providerConfigure(p *schema.Provider) schema.ConfigureFunc { 173 return func(d *schema.ResourceData) (interface{}, error) { 174 config := &Config{ 175 SubscriptionID: d.Get("subscription_id").(string), 176 ClientID: d.Get("client_id").(string), 177 ClientSecret: d.Get("client_secret").(string), 178 TenantID: d.Get("tenant_id").(string), 179 Environment: d.Get("environment").(string), 180 SkipProviderRegistration: d.Get("skip_provider_registration").(bool), 181 } 182 183 if err := config.validate(); err != nil { 184 return nil, err 185 } 186 187 client, err := config.getArmClient() 188 if err != nil { 189 return nil, err 190 } 191 192 client.StopContext = p.StopContext() 193 194 // List all the available providers and their registration state to avoid unnecessary 195 // requests. This also lets us check if the provider credentials are correct. 196 providerList, err := client.providers.List(nil, "") 197 if err != nil { 198 return nil, fmt.Errorf("Unable to list provider registration status, it is possible that this is due to invalid "+ 199 "credentials or the service principal does not have permission to use the Resource Manager API, Azure "+ 200 "error: %s", err) 201 } 202 203 if !config.SkipProviderRegistration { 204 err = registerAzureResourceProvidersWithSubscription(*providerList.Value, client.providers) 205 if err != nil { 206 return nil, err 207 } 208 } 209 210 return client, nil 211 } 212 } 213 214 func registerProviderWithSubscription(providerName string, client resources.ProvidersClient) error { 215 _, err := client.Register(providerName) 216 if err != nil { 217 return fmt.Errorf("Cannot register provider %s with Azure Resource Manager: %s.", providerName, err) 218 } 219 220 return nil 221 } 222 223 var providerRegistrationOnce sync.Once 224 225 // registerAzureResourceProvidersWithSubscription uses the providers client to register 226 // all Azure resource providers which the Terraform provider may require (regardless of 227 // whether they are actually used by the configuration or not). It was confirmed by Microsoft 228 // that this is the approach their own internal tools also take. 229 func registerAzureResourceProvidersWithSubscription(providerList []resources.Provider, client resources.ProvidersClient) error { 230 var err error 231 providerRegistrationOnce.Do(func() { 232 providers := map[string]struct{}{ 233 "Microsoft.Compute": struct{}{}, 234 "Microsoft.Cache": struct{}{}, 235 "Microsoft.ContainerRegistry": struct{}{}, 236 "Microsoft.ContainerService": struct{}{}, 237 "Microsoft.Network": struct{}{}, 238 "Microsoft.Cdn": struct{}{}, 239 "Microsoft.Storage": struct{}{}, 240 "Microsoft.Sql": struct{}{}, 241 "Microsoft.Search": struct{}{}, 242 "Microsoft.Resources": struct{}{}, 243 "Microsoft.ServiceBus": struct{}{}, 244 "Microsoft.KeyVault": struct{}{}, 245 "Microsoft.EventHub": struct{}{}, 246 } 247 248 // filter out any providers already registered 249 for _, p := range providerList { 250 if _, ok := providers[*p.Namespace]; !ok { 251 continue 252 } 253 254 if strings.ToLower(*p.RegistrationState) == "registered" { 255 log.Printf("[DEBUG] Skipping provider registration for namespace %s\n", *p.Namespace) 256 delete(providers, *p.Namespace) 257 } 258 } 259 260 var wg sync.WaitGroup 261 wg.Add(len(providers)) 262 for providerName := range providers { 263 go func(p string) { 264 defer wg.Done() 265 log.Printf("[DEBUG] Registering provider with namespace %s\n", p) 266 if innerErr := registerProviderWithSubscription(p, client); err != nil { 267 err = innerErr 268 } 269 }(providerName) 270 } 271 wg.Wait() 272 }) 273 274 return err 275 } 276 277 // armMutexKV is the instance of MutexKV for ARM resources 278 var armMutexKV = mutexkv.NewMutexKV() 279 280 func azureStateRefreshFunc(resourceURI string, client *ArmClient, command riviera.APICall) resource.StateRefreshFunc { 281 return func() (interface{}, string, error) { 282 req := client.rivieraClient.NewRequestForURI(resourceURI) 283 req.Command = command 284 285 res, err := req.Execute() 286 if err != nil { 287 return nil, "", fmt.Errorf("Error executing %T command in azureStateRefreshFunc", req.Command) 288 } 289 290 var value reflect.Value 291 if reflect.ValueOf(res.Parsed).Kind() == reflect.Ptr { 292 value = reflect.ValueOf(res.Parsed).Elem() 293 } else { 294 value = reflect.ValueOf(res.Parsed) 295 } 296 297 for i := 0; i < value.NumField(); i++ { // iterates through every struct type field 298 tag := value.Type().Field(i).Tag // returns the tag string 299 tagValue := tag.Get("mapstructure") 300 if tagValue == "provisioningState" { 301 return res.Parsed, value.Field(i).Elem().String(), nil 302 } 303 } 304 305 panic(fmt.Errorf("azureStateRefreshFunc called on structure %T with no mapstructure:provisioningState tag. This is a bug", res.Parsed)) 306 } 307 } 308 309 // Resource group names can be capitalised, but we store them in lowercase. 310 // Use a custom diff function to avoid creation of new resources. 311 func resourceAzurermResourceGroupNameDiffSuppress(k, old, new string, d *schema.ResourceData) bool { 312 return strings.ToLower(old) == strings.ToLower(new) 313 } 314 315 // ignoreCaseDiffSuppressFunc is a DiffSuppressFunc from helper/schema that is 316 // used to ignore any case-changes in a return value. 317 func ignoreCaseDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool { 318 return strings.ToLower(old) == strings.ToLower(new) 319 } 320 321 // ignoreCaseStateFunc is a StateFunc from helper/schema that converts the 322 // supplied value to lower before saving to state for consistency. 323 func ignoreCaseStateFunc(val interface{}) string { 324 return strings.ToLower(val.(string)) 325 }