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