github.com/ottenhoff/terraform@v0.7.0-rc1.0.20160607213102-ac2d195cc560/builtin/providers/azurerm/provider.go (about)

     1  package azurerm
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/go-multierror"
     9  	"github.com/hashicorp/terraform/helper/mutexkv"
    10  	"github.com/hashicorp/terraform/helper/resource"
    11  	"github.com/hashicorp/terraform/helper/schema"
    12  	"github.com/hashicorp/terraform/terraform"
    13  	riviera "github.com/jen20/riviera/azure"
    14  	"sync"
    15  )
    16  
    17  // Provider returns a terraform.ResourceProvider.
    18  func Provider() terraform.ResourceProvider {
    19  	return &schema.Provider{
    20  		Schema: map[string]*schema.Schema{
    21  			"subscription_id": {
    22  				Type:        schema.TypeString,
    23  				Required:    true,
    24  				DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""),
    25  			},
    26  
    27  			"client_id": {
    28  				Type:        schema.TypeString,
    29  				Required:    true,
    30  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
    31  			},
    32  
    33  			"client_secret": {
    34  				Type:        schema.TypeString,
    35  				Required:    true,
    36  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
    37  			},
    38  
    39  			"tenant_id": {
    40  				Type:        schema.TypeString,
    41  				Required:    true,
    42  				DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
    43  			},
    44  		},
    45  
    46  		ResourcesMap: map[string]*schema.Resource{
    47  			// These resources use the Azure ARM SDK
    48  			"azurerm_availability_set":       resourceArmAvailabilitySet(),
    49  			"azurerm_cdn_endpoint":           resourceArmCdnEndpoint(),
    50  			"azurerm_cdn_profile":            resourceArmCdnProfile(),
    51  			"azurerm_local_network_gateway":  resourceArmLocalNetworkGateway(),
    52  			"azurerm_network_interface":      resourceArmNetworkInterface(),
    53  			"azurerm_network_security_group": resourceArmNetworkSecurityGroup(),
    54  			"azurerm_network_security_rule":  resourceArmNetworkSecurityRule(),
    55  			"azurerm_public_ip":              resourceArmPublicIp(),
    56  			"azurerm_route":                  resourceArmRoute(),
    57  			"azurerm_route_table":            resourceArmRouteTable(),
    58  			"azurerm_storage_account":        resourceArmStorageAccount(),
    59  			"azurerm_storage_blob":           resourceArmStorageBlob(),
    60  			"azurerm_storage_container":      resourceArmStorageContainer(),
    61  			"azurerm_storage_queue":          resourceArmStorageQueue(),
    62  			"azurerm_subnet":                 resourceArmSubnet(),
    63  			"azurerm_template_deployment":    resourceArmTemplateDeployment(),
    64  			"azurerm_virtual_machine":        resourceArmVirtualMachine(),
    65  			"azurerm_virtual_network":        resourceArmVirtualNetwork(),
    66  
    67  			// These resources use the Riviera SDK
    68  			"azurerm_dns_a_record":      resourceArmDnsARecord(),
    69  			"azurerm_dns_aaaa_record":   resourceArmDnsAAAARecord(),
    70  			"azurerm_dns_cname_record":  resourceArmDnsCNameRecord(),
    71  			"azurerm_dns_mx_record":     resourceArmDnsMxRecord(),
    72  			"azurerm_dns_ns_record":     resourceArmDnsNsRecord(),
    73  			"azurerm_dns_srv_record":    resourceArmDnsSrvRecord(),
    74  			"azurerm_dns_txt_record":    resourceArmDnsTxtRecord(),
    75  			"azurerm_dns_zone":          resourceArmDnsZone(),
    76  			"azurerm_resource_group":    resourceArmResourceGroup(),
    77  			"azurerm_search_service":    resourceArmSearchService(),
    78  			"azurerm_sql_database":      resourceArmSqlDatabase(),
    79  			"azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(),
    80  			"azurerm_sql_server":        resourceArmSqlServer(),
    81  		},
    82  		ConfigureFunc: providerConfigure,
    83  	}
    84  }
    85  
    86  // Config is the configuration structure used to instantiate a
    87  // new Azure management client.
    88  type Config struct {
    89  	ManagementURL string
    90  
    91  	SubscriptionID string
    92  	ClientID       string
    93  	ClientSecret   string
    94  	TenantID       string
    95  
    96  	validateCredentialsOnce sync.Once
    97  }
    98  
    99  func (c *Config) validate() error {
   100  	var err *multierror.Error
   101  
   102  	if c.SubscriptionID == "" {
   103  		err = multierror.Append(err, fmt.Errorf("Subscription ID must be configured for the AzureRM provider"))
   104  	}
   105  	if c.ClientID == "" {
   106  		err = multierror.Append(err, fmt.Errorf("Client ID must be configured for the AzureRM provider"))
   107  	}
   108  	if c.ClientSecret == "" {
   109  		err = multierror.Append(err, fmt.Errorf("Client Secret must be configured for the AzureRM provider"))
   110  	}
   111  	if c.TenantID == "" {
   112  		err = multierror.Append(err, fmt.Errorf("Tenant ID must be configured for the AzureRM provider"))
   113  	}
   114  
   115  	return err.ErrorOrNil()
   116  }
   117  
   118  func providerConfigure(d *schema.ResourceData) (interface{}, error) {
   119  	config := &Config{
   120  		SubscriptionID: d.Get("subscription_id").(string),
   121  		ClientID:       d.Get("client_id").(string),
   122  		ClientSecret:   d.Get("client_secret").(string),
   123  		TenantID:       d.Get("tenant_id").(string),
   124  	}
   125  
   126  	if err := config.validate(); err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	client, err := config.getArmClient()
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  
   135  	err = registerAzureResourceProvidersWithSubscription(client.rivieraClient)
   136  	if err != nil {
   137  		return nil, err
   138  	}
   139  
   140  	return client, nil
   141  }
   142  
   143  func registerProviderWithSubscription(providerName string, client *riviera.Client) error {
   144  	request := client.NewRequest()
   145  	request.Command = riviera.RegisterResourceProvider{
   146  		Namespace: providerName,
   147  	}
   148  
   149  	response, err := request.Execute()
   150  	if err != nil {
   151  		return fmt.Errorf("Cannot request provider registration for Azure Resource Manager: %s.", err)
   152  	}
   153  
   154  	if !response.IsSuccessful() {
   155  		return fmt.Errorf("Credentials for acessing the Azure Resource Manager API are likely " +
   156  			"to be incorrect, or\n  the service principal does not have permission to use " +
   157  			"the Azure Service Management\n  API.")
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  var providerRegistrationOnce sync.Once
   164  
   165  // registerAzureResourceProvidersWithSubscription uses the providers client to register
   166  // all Azure resource providers which the Terraform provider may require (regardless of
   167  // whether they are actually used by the configuration or not). It was confirmed by Microsoft
   168  // that this is the approach their own internal tools also take.
   169  func registerAzureResourceProvidersWithSubscription(client *riviera.Client) error {
   170  	var err error
   171  	providerRegistrationOnce.Do(func() {
   172  		// We register Microsoft.Compute during client initialization
   173  		providers := []string{"Microsoft.Network", "Microsoft.Cdn", "Microsoft.Storage", "Microsoft.Sql", "Microsoft.Search", "Microsoft.Resources"}
   174  
   175  		var wg sync.WaitGroup
   176  		wg.Add(len(providers))
   177  		for _, providerName := range providers {
   178  			go func(p string) {
   179  				defer wg.Done()
   180  				if innerErr := registerProviderWithSubscription(p, client); err != nil {
   181  					err = innerErr
   182  				}
   183  			}(providerName)
   184  		}
   185  		wg.Wait()
   186  	})
   187  
   188  	return err
   189  }
   190  
   191  // azureRMNormalizeLocation is a function which normalises human-readable region/location
   192  // names (e.g. "West US") to the values used and returned by the Azure API (e.g. "westus").
   193  // In state we track the API internal version as it is easier to go from the human form
   194  // to the canonical form than the other way around.
   195  func azureRMNormalizeLocation(location interface{}) string {
   196  	input := location.(string)
   197  	return strings.Replace(strings.ToLower(input), " ", "", -1)
   198  }
   199  
   200  // armMutexKV is the instance of MutexKV for ARM resources
   201  var armMutexKV = mutexkv.NewMutexKV()
   202  
   203  func azureStateRefreshFunc(resourceURI string, client *ArmClient, command riviera.APICall) resource.StateRefreshFunc {
   204  	return func() (interface{}, string, error) {
   205  		req := client.rivieraClient.NewRequestForURI(resourceURI)
   206  		req.Command = command
   207  
   208  		res, err := req.Execute()
   209  		if err != nil {
   210  			return nil, "", fmt.Errorf("Error executing %T command in azureStateRefreshFunc", req.Command)
   211  		}
   212  
   213  		var value reflect.Value
   214  		if reflect.ValueOf(res.Parsed).Kind() == reflect.Ptr {
   215  			value = reflect.ValueOf(res.Parsed).Elem()
   216  		} else {
   217  			value = reflect.ValueOf(res.Parsed)
   218  		}
   219  
   220  		for i := 0; i < value.NumField(); i++ { // iterates through every struct type field
   221  			tag := value.Type().Field(i).Tag // returns the tag string
   222  			tagValue := tag.Get("mapstructure")
   223  			if tagValue == "provisioningState" {
   224  				return res.Parsed, value.Field(i).Elem().String(), nil
   225  			}
   226  		}
   227  
   228  		panic(fmt.Errorf("azureStateRefreshFunc called on structure %T with no mapstructure:provisioningState tag. This is a bug", res.Parsed))
   229  	}
   230  }