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  }