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