github.com/akazakov/terraform@v0.5.2-0.20160205142716-097441beafdf/builtin/providers/azurerm/provider.go (about)

     1  package azurerm
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"net/http"
     7  	"strings"
     8  
     9  	"github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest"
    10  	"github.com/hashicorp/go-multierror"
    11  	"github.com/hashicorp/terraform/helper/mutexkv"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/hashicorp/terraform/terraform"
    14  )
    15  
    16  // Provider returns a terraform.ResourceProvider.
    17  func Provider() terraform.ResourceProvider {
    18  	return &schema.Provider{
    19  		Schema: map[string]*schema.Schema{
    20  			"subscription_id": &schema.Schema{
    21  				Type:        schema.TypeString,
    22  				Required:    true,
    23  				DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""),
    24  			},
    25  
    26  			"client_id": &schema.Schema{
    27  				Type:        schema.TypeString,
    28  				Required:    true,
    29  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
    30  			},
    31  
    32  			"client_secret": &schema.Schema{
    33  				Type:        schema.TypeString,
    34  				Required:    true,
    35  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
    36  			},
    37  
    38  			"tenant_id": &schema.Schema{
    39  				Type:        schema.TypeString,
    40  				Required:    true,
    41  				DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
    42  			},
    43  		},
    44  
    45  		ResourcesMap: map[string]*schema.Resource{
    46  			"azurerm_resource_group":         resourceArmResourceGroup(),
    47  			"azurerm_virtual_network":        resourceArmVirtualNetwork(),
    48  			"azurerm_local_network_gateway":  resourceArmLocalNetworkGateway(),
    49  			"azurerm_availability_set":       resourceArmAvailabilitySet(),
    50  			"azurerm_network_security_group": resourceArmNetworkSecurityGroup(),
    51  			"azurerm_network_security_rule":  resourceArmNetworkSecurityRule(),
    52  			"azurerm_public_ip":              resourceArmPublicIp(),
    53  			"azurerm_subnet":                 resourceArmSubnet(),
    54  			"azurerm_network_interface":      resourceArmNetworkInterface(),
    55  			"azurerm_route_table":            resourceArmRouteTable(),
    56  			"azurerm_route":                  resourceArmRoute(),
    57  			"azurerm_cdn_profile":            resourceArmCdnProfile(),
    58  			"azurerm_cdn_endpoint":           resourceArmCdnEndpoint(),
    59  			"azurerm_storage_account":        resourceArmStorageAccount(),
    60  			"azurerm_storage_container":      resourceArmStorageContainer(),
    61  			"azurerm_storage_blob":           resourceArmStorageBlob(),
    62  			"azurerm_storage_queue":          resourceArmStorageQueue(),
    63  			"azurerm_dns_zone":               resourceArmDnsZone(),
    64  			"azurerm_sql_server":             resourceArmSqlServer(),
    65  		},
    66  		ConfigureFunc: providerConfigure,
    67  	}
    68  }
    69  
    70  // Config is the configuration structure used to instantiate a
    71  // new Azure management client.
    72  type Config struct {
    73  	ManagementURL string
    74  
    75  	SubscriptionID string
    76  	ClientID       string
    77  	ClientSecret   string
    78  	TenantID       string
    79  }
    80  
    81  func (c Config) validate() error {
    82  	var err *multierror.Error
    83  
    84  	if c.SubscriptionID == "" {
    85  		err = multierror.Append(err, fmt.Errorf("Subscription ID must be configured for the AzureRM provider"))
    86  	}
    87  	if c.ClientID == "" {
    88  		err = multierror.Append(err, fmt.Errorf("Client ID must be configured for the AzureRM provider"))
    89  	}
    90  	if c.ClientSecret == "" {
    91  		err = multierror.Append(err, fmt.Errorf("Client Secret must be configured for the AzureRM provider"))
    92  	}
    93  	if c.TenantID == "" {
    94  		err = multierror.Append(err, fmt.Errorf("Tenant ID must be configured for the AzureRM provider"))
    95  	}
    96  
    97  	return err.ErrorOrNil()
    98  }
    99  
   100  func providerConfigure(d *schema.ResourceData) (interface{}, error) {
   101  	config := Config{
   102  		SubscriptionID: d.Get("subscription_id").(string),
   103  		ClientID:       d.Get("client_id").(string),
   104  		ClientSecret:   d.Get("client_secret").(string),
   105  		TenantID:       d.Get("tenant_id").(string),
   106  	}
   107  
   108  	if err := config.validate(); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	client, err := config.getArmClient()
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	err = registerAzureResourceProvidersWithSubscription(&config, client)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	return client, nil
   123  }
   124  
   125  // registerAzureResourceProvidersWithSubscription uses the providers client to register
   126  // all Azure resource providers which the Terraform provider may require (regardless of
   127  // whether they are actually used by the configuration or not). It was confirmed by Microsoft
   128  // that this is the approach their own internal tools also take.
   129  func registerAzureResourceProvidersWithSubscription(config *Config, client *ArmClient) error {
   130  	providerClient := client.providers
   131  
   132  	providers := []string{"Microsoft.Network", "Microsoft.Compute", "Microsoft.Cdn", "Microsoft.Storage", "Microsoft.Sql"}
   133  
   134  	for _, v := range providers {
   135  		res, err := providerClient.Register(v)
   136  		if err != nil {
   137  			return err
   138  		}
   139  
   140  		if res.StatusCode != http.StatusOK {
   141  			return fmt.Errorf("Error registering provider %q with subscription %q", v, config.SubscriptionID)
   142  		}
   143  	}
   144  
   145  	return nil
   146  }
   147  
   148  // azureRMNormalizeLocation is a function which normalises human-readable region/location
   149  // names (e.g. "West US") to the values used and returned by the Azure API (e.g. "westus").
   150  // In state we track the API internal version as it is easier to go from the human form
   151  // to the canonical form than the other way around.
   152  func azureRMNormalizeLocation(location interface{}) string {
   153  	input := location.(string)
   154  	return strings.Replace(strings.ToLower(input), " ", "", -1)
   155  }
   156  
   157  // pollIndefinitelyAsNeeded is a terrible hack which is necessary because the Azure
   158  // Storage API (and perhaps others) can have response times way beyond the default
   159  // retry timeouts, with no apparent upper bound. This effectively causes the client
   160  // to continue polling when it reaches the configured timeout. My investigations
   161  // suggest that this is neccesary when deleting and recreating a storage account with
   162  // the same name in a short (though undetermined) time period.
   163  //
   164  // It is possible that this will give Terraform the appearance of being slow in
   165  // future: I have attempted to mitigate this by logging whenever this happens. We
   166  // may want to revisit this with configurable timeouts in the future as clearly
   167  // unbounded wait loops is not ideal. It does seem preferable to the current situation
   168  // where our polling loop will time out _with an operation in progress_, but no ID
   169  // for the resource - so the state will not know about it, and conflicts will occur
   170  // on the next run.
   171  func pollIndefinitelyAsNeeded(client autorest.Client, response *http.Response, acceptableCodes ...int) (*http.Response, error) {
   172  	var resp *http.Response
   173  	var err error
   174  
   175  	for {
   176  		resp, err = client.PollAsNeeded(response, acceptableCodes...)
   177  		if err != nil {
   178  			if resp.StatusCode != http.StatusAccepted {
   179  				log.Printf("[DEBUG] Starting new polling loop for %q", response.Request.URL.Path)
   180  				continue
   181  			}
   182  
   183  			return resp, err
   184  		}
   185  
   186  		return resp, nil
   187  	}
   188  }
   189  
   190  // armMutexKV is the instance of MutexKV for ARM resources
   191  var armMutexKV = mutexkv.NewMutexKV()