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