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