github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/builtin/providers/azurerm/provider.go (about)

     1  package azurerm
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net/http"
     7  	"reflect"
     8  	"strings"
     9  
    10  	"github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest"
    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  	return &schema.Provider{
    22  		Schema: map[string]*schema.Schema{
    23  			"subscription_id": &schema.Schema{
    24  				Type:        schema.TypeString,
    25  				Required:    true,
    26  				DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""),
    27  			},
    28  
    29  			"client_id": &schema.Schema{
    30  				Type:        schema.TypeString,
    31  				Required:    true,
    32  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
    33  			},
    34  
    35  			"client_secret": &schema.Schema{
    36  				Type:        schema.TypeString,
    37  				Required:    true,
    38  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
    39  			},
    40  
    41  			"tenant_id": &schema.Schema{
    42  				Type:        schema.TypeString,
    43  				Required:    true,
    44  				DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
    45  			},
    46  		},
    47  
    48  		ResourcesMap: map[string]*schema.Resource{
    49  			"azurerm_availability_set":       resourceArmAvailabilitySet(),
    50  			"azurerm_cdn_endpoint":           resourceArmCdnEndpoint(),
    51  			"azurerm_cdn_profile":            resourceArmCdnProfile(),
    52  			"azurerm_dns_a_record":           resourceArmDnsARecord(),
    53  			"azurerm_dns_aaaa_record":        resourceArmDnsAAAARecord(),
    54  			"azurerm_dns_cname_record":       resourceArmDnsCNameRecord(),
    55  			"azurerm_dns_mx_record":          resourceArmDnsMxRecord(),
    56  			"azurerm_dns_ns_record":          resourceArmDnsNsRecord(),
    57  			"azurerm_dns_srv_record":         resourceArmDnsSrvRecord(),
    58  			"azurerm_dns_txt_record":         resourceArmDnsTxtRecord(),
    59  			"azurerm_dns_zone":               resourceArmDnsZone(),
    60  			"azurerm_local_network_gateway":  resourceArmLocalNetworkGateway(),
    61  			"azurerm_network_interface":      resourceArmNetworkInterface(),
    62  			"azurerm_network_security_group": resourceArmNetworkSecurityGroup(),
    63  			"azurerm_network_security_rule":  resourceArmNetworkSecurityRule(),
    64  			"azurerm_public_ip":              resourceArmPublicIp(),
    65  			"azurerm_resource_group":         resourceArmResourceGroup(),
    66  			"azurerm_route":                  resourceArmRoute(),
    67  			"azurerm_route_table":            resourceArmRouteTable(),
    68  			"azurerm_search_service":         resourceArmSearchService(),
    69  			"azurerm_sql_database":           resourceArmSqlDatabase(),
    70  			"azurerm_sql_firewall_rule":      resourceArmSqlFirewallRule(),
    71  			"azurerm_sql_server":             resourceArmSqlServer(),
    72  			"azurerm_storage_account":        resourceArmStorageAccount(),
    73  			"azurerm_storage_blob":           resourceArmStorageBlob(),
    74  			"azurerm_storage_container":      resourceArmStorageContainer(),
    75  			"azurerm_storage_queue":          resourceArmStorageQueue(),
    76  			"azurerm_subnet":                 resourceArmSubnet(),
    77  			"azurerm_template_deployment":    resourceArmTemplateDeployment(),
    78  			"azurerm_virtual_machine":        resourceArmVirtualMachine(),
    79  			"azurerm_virtual_network":        resourceArmVirtualNetwork(),
    80  		},
    81  		ConfigureFunc: providerConfigure,
    82  	}
    83  }
    84  
    85  // Config is the configuration structure used to instantiate a
    86  // new Azure management client.
    87  type Config struct {
    88  	ManagementURL string
    89  
    90  	SubscriptionID string
    91  	ClientID       string
    92  	ClientSecret   string
    93  	TenantID       string
    94  }
    95  
    96  func (c Config) validate() error {
    97  	var err *multierror.Error
    98  
    99  	if c.SubscriptionID == "" {
   100  		err = multierror.Append(err, fmt.Errorf("Subscription ID must be configured for the AzureRM provider"))
   101  	}
   102  	if c.ClientID == "" {
   103  		err = multierror.Append(err, fmt.Errorf("Client ID must be configured for the AzureRM provider"))
   104  	}
   105  	if c.ClientSecret == "" {
   106  		err = multierror.Append(err, fmt.Errorf("Client Secret must be configured for the AzureRM provider"))
   107  	}
   108  	if c.TenantID == "" {
   109  		err = multierror.Append(err, fmt.Errorf("Tenant ID must be configured for the AzureRM provider"))
   110  	}
   111  
   112  	return err.ErrorOrNil()
   113  }
   114  
   115  func providerConfigure(d *schema.ResourceData) (interface{}, error) {
   116  	config := Config{
   117  		SubscriptionID: d.Get("subscription_id").(string),
   118  		ClientID:       d.Get("client_id").(string),
   119  		ClientSecret:   d.Get("client_secret").(string),
   120  		TenantID:       d.Get("tenant_id").(string),
   121  	}
   122  
   123  	if err := config.validate(); err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	client, err := config.getArmClient()
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  
   132  	err = registerAzureResourceProvidersWithSubscription(&config, client)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	return client, nil
   138  }
   139  
   140  // registerAzureResourceProvidersWithSubscription uses the providers client to register
   141  // all Azure resource providers which the Terraform provider may require (regardless of
   142  // whether they are actually used by the configuration or not). It was confirmed by Microsoft
   143  // that this is the approach their own internal tools also take.
   144  func registerAzureResourceProvidersWithSubscription(config *Config, client *ArmClient) error {
   145  	providerClient := client.providers
   146  
   147  	providers := []string{"Microsoft.Network", "Microsoft.Compute", "Microsoft.Cdn", "Microsoft.Storage", "Microsoft.Sql", "Microsoft.Search", "Microsoft.Resources"}
   148  
   149  	for _, v := range providers {
   150  		res, err := providerClient.Register(v)
   151  		if err != nil {
   152  			return err
   153  		}
   154  
   155  		if res.StatusCode != http.StatusOK {
   156  			return fmt.Errorf("Error registering provider %q with subscription %q", v, config.SubscriptionID)
   157  		}
   158  	}
   159  
   160  	return nil
   161  }
   162  
   163  // azureRMNormalizeLocation is a function which normalises human-readable region/location
   164  // names (e.g. "West US") to the values used and returned by the Azure API (e.g. "westus").
   165  // In state we track the API internal version as it is easier to go from the human form
   166  // to the canonical form than the other way around.
   167  func azureRMNormalizeLocation(location interface{}) string {
   168  	input := location.(string)
   169  	return strings.Replace(strings.ToLower(input), " ", "", -1)
   170  }
   171  
   172  // pollIndefinitelyAsNeeded is a terrible hack which is necessary because the Azure
   173  // Storage API (and perhaps others) can have response times way beyond the default
   174  // retry timeouts, with no apparent upper bound. This effectively causes the client
   175  // to continue polling when it reaches the configured timeout. My investigations
   176  // suggest that this is neccesary when deleting and recreating a storage account with
   177  // the same name in a short (though undetermined) time period.
   178  //
   179  // It is possible that this will give Terraform the appearance of being slow in
   180  // future: I have attempted to mitigate this by logging whenever this happens. We
   181  // may want to revisit this with configurable timeouts in the future as clearly
   182  // unbounded wait loops is not ideal. It does seem preferable to the current situation
   183  // where our polling loop will time out _with an operation in progress_, but no ID
   184  // for the resource - so the state will not know about it, and conflicts will occur
   185  // on the next run.
   186  func pollIndefinitelyAsNeeded(client autorest.Client, response *http.Response, acceptableCodes ...int) (*http.Response, error) {
   187  	var resp *http.Response
   188  	var err error
   189  
   190  	for {
   191  		resp, err = client.PollAsNeeded(response, acceptableCodes...)
   192  		if err != nil {
   193  			if resp.StatusCode != http.StatusAccepted {
   194  				log.Printf("[DEBUG] Starting new polling loop for %q", response.Request.URL.Path)
   195  				continue
   196  			}
   197  
   198  			return resp, err
   199  		}
   200  
   201  		return resp, nil
   202  	}
   203  }
   204  
   205  // armMutexKV is the instance of MutexKV for ARM resources
   206  var armMutexKV = mutexkv.NewMutexKV()
   207  
   208  func azureStateRefreshFunc(resourceURI string, client *ArmClient, command riviera.APICall) resource.StateRefreshFunc {
   209  	return func() (interface{}, string, error) {
   210  		req := client.rivieraClient.NewRequestForURI(resourceURI)
   211  		req.Command = command
   212  
   213  		res, err := req.Execute()
   214  		if err != nil {
   215  			return nil, "", fmt.Errorf("Error executing %T command in azureStateRefreshFunc", req.Command)
   216  		}
   217  
   218  		var value reflect.Value
   219  		if reflect.ValueOf(res.Parsed).Kind() == reflect.Ptr {
   220  			value = reflect.ValueOf(res.Parsed).Elem()
   221  		} else {
   222  			value = reflect.ValueOf(res.Parsed)
   223  		}
   224  
   225  		for i := 0; i < value.NumField(); i++ { // iterates through every struct type field
   226  			tag := value.Type().Field(i).Tag // returns the tag string
   227  			tagValue := tag.Get("mapstructure")
   228  			if tagValue == "provisioningState" {
   229  				return res.Parsed, value.Field(i).Elem().String(), nil
   230  			}
   231  		}
   232  
   233  		panic(fmt.Errorf("azureStateRefreshFunc called on structure %T with no mapstructure:provisioningState tag. This is a bug", res.Parsed))
   234  	}
   235  }