github.com/skyscape-cloud-services/terraform@v0.9.2-0.20170609144644-7ece028a1747/builtin/providers/azurerm/provider.go (about)

     1  package azurerm
     2  
     3  import (
     4  	"crypto/sha1"
     5  	"encoding/base64"
     6  	"encoding/hex"
     7  	"fmt"
     8  	"log"
     9  	"reflect"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
    14  	"github.com/hashicorp/go-multierror"
    15  	"github.com/hashicorp/terraform/helper/mutexkv"
    16  	"github.com/hashicorp/terraform/helper/resource"
    17  	"github.com/hashicorp/terraform/helper/schema"
    18  	"github.com/hashicorp/terraform/terraform"
    19  	riviera "github.com/jen20/riviera/azure"
    20  )
    21  
    22  // Provider returns a terraform.ResourceProvider.
    23  func Provider() terraform.ResourceProvider {
    24  	var p *schema.Provider
    25  	p = &schema.Provider{
    26  		Schema: map[string]*schema.Schema{
    27  			"subscription_id": {
    28  				Type:        schema.TypeString,
    29  				Required:    true,
    30  				DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""),
    31  			},
    32  
    33  			"client_id": {
    34  				Type:        schema.TypeString,
    35  				Required:    true,
    36  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
    37  			},
    38  
    39  			"client_secret": {
    40  				Type:        schema.TypeString,
    41  				Required:    true,
    42  				DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
    43  			},
    44  
    45  			"tenant_id": {
    46  				Type:        schema.TypeString,
    47  				Required:    true,
    48  				DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
    49  			},
    50  
    51  			"environment": {
    52  				Type:        schema.TypeString,
    53  				Required:    true,
    54  				DefaultFunc: schema.EnvDefaultFunc("ARM_ENVIRONMENT", "public"),
    55  			},
    56  
    57  			"skip_provider_registration": {
    58  				Type:        schema.TypeBool,
    59  				Optional:    true,
    60  				DefaultFunc: schema.EnvDefaultFunc("ARM_SKIP_PROVIDER_REGISTRATION", false),
    61  			},
    62  		},
    63  
    64  		DataSourcesMap: map[string]*schema.Resource{
    65  			"azurerm_client_config":  dataSourceArmClientConfig(),
    66  			"azurerm_resource_group": dataSourceArmResourceGroup(),
    67  			"azurerm_public_ip":      dataSourceArmPublicIP(),
    68  		},
    69  
    70  		ResourcesMap: map[string]*schema.Resource{
    71  			// These resources use the Azure ARM SDK
    72  			"azurerm_availability_set":   resourceArmAvailabilitySet(),
    73  			"azurerm_cdn_endpoint":       resourceArmCdnEndpoint(),
    74  			"azurerm_cdn_profile":        resourceArmCdnProfile(),
    75  			"azurerm_container_registry": resourceArmContainerRegistry(),
    76  			"azurerm_container_service":  resourceArmContainerService(),
    77  
    78  			"azurerm_eventhub":                    resourceArmEventHub(),
    79  			"azurerm_eventhub_authorization_rule": resourceArmEventHubAuthorizationRule(),
    80  			"azurerm_eventhub_consumer_group":     resourceArmEventHubConsumerGroup(),
    81  			"azurerm_eventhub_namespace":          resourceArmEventHubNamespace(),
    82  
    83  			"azurerm_express_route_circuit": resourceArmExpressRouteCircuit(),
    84  
    85  			"azurerm_lb":                      resourceArmLoadBalancer(),
    86  			"azurerm_lb_backend_address_pool": resourceArmLoadBalancerBackendAddressPool(),
    87  			"azurerm_lb_nat_rule":             resourceArmLoadBalancerNatRule(),
    88  			"azurerm_lb_nat_pool":             resourceArmLoadBalancerNatPool(),
    89  			"azurerm_lb_probe":                resourceArmLoadBalancerProbe(),
    90  			"azurerm_lb_rule":                 resourceArmLoadBalancerRule(),
    91  
    92  			"azurerm_managed_disk": resourceArmManagedDisk(),
    93  
    94  			"azurerm_key_vault":                 resourceArmKeyVault(),
    95  			"azurerm_local_network_gateway":     resourceArmLocalNetworkGateway(),
    96  			"azurerm_network_interface":         resourceArmNetworkInterface(),
    97  			"azurerm_network_security_group":    resourceArmNetworkSecurityGroup(),
    98  			"azurerm_network_security_rule":     resourceArmNetworkSecurityRule(),
    99  			"azurerm_public_ip":                 resourceArmPublicIp(),
   100  			"azurerm_redis_cache":               resourceArmRedisCache(),
   101  			"azurerm_route":                     resourceArmRoute(),
   102  			"azurerm_route_table":               resourceArmRouteTable(),
   103  			"azurerm_servicebus_namespace":      resourceArmServiceBusNamespace(),
   104  			"azurerm_servicebus_subscription":   resourceArmServiceBusSubscription(),
   105  			"azurerm_servicebus_topic":          resourceArmServiceBusTopic(),
   106  			"azurerm_sql_elasticpool":           resourceArmSqlElasticPool(),
   107  			"azurerm_storage_account":           resourceArmStorageAccount(),
   108  			"azurerm_storage_blob":              resourceArmStorageBlob(),
   109  			"azurerm_storage_container":         resourceArmStorageContainer(),
   110  			"azurerm_storage_share":             resourceArmStorageShare(),
   111  			"azurerm_storage_queue":             resourceArmStorageQueue(),
   112  			"azurerm_storage_table":             resourceArmStorageTable(),
   113  			"azurerm_subnet":                    resourceArmSubnet(),
   114  			"azurerm_template_deployment":       resourceArmTemplateDeployment(),
   115  			"azurerm_traffic_manager_endpoint":  resourceArmTrafficManagerEndpoint(),
   116  			"azurerm_traffic_manager_profile":   resourceArmTrafficManagerProfile(),
   117  			"azurerm_virtual_machine_extension": resourceArmVirtualMachineExtensions(),
   118  			"azurerm_virtual_machine":           resourceArmVirtualMachine(),
   119  			"azurerm_virtual_machine_scale_set": resourceArmVirtualMachineScaleSet(),
   120  			"azurerm_virtual_network":           resourceArmVirtualNetwork(),
   121  			"azurerm_virtual_network_peering":   resourceArmVirtualNetworkPeering(),
   122  
   123  			// These resources use the Riviera SDK
   124  			"azurerm_dns_a_record":      resourceArmDnsARecord(),
   125  			"azurerm_dns_aaaa_record":   resourceArmDnsAAAARecord(),
   126  			"azurerm_dns_cname_record":  resourceArmDnsCNameRecord(),
   127  			"azurerm_dns_mx_record":     resourceArmDnsMxRecord(),
   128  			"azurerm_dns_ns_record":     resourceArmDnsNsRecord(),
   129  			"azurerm_dns_srv_record":    resourceArmDnsSrvRecord(),
   130  			"azurerm_dns_txt_record":    resourceArmDnsTxtRecord(),
   131  			"azurerm_dns_zone":          resourceArmDnsZone(),
   132  			"azurerm_resource_group":    resourceArmResourceGroup(),
   133  			"azurerm_search_service":    resourceArmSearchService(),
   134  			"azurerm_sql_database":      resourceArmSqlDatabase(),
   135  			"azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(),
   136  			"azurerm_sql_server":        resourceArmSqlServer(),
   137  		},
   138  	}
   139  
   140  	p.ConfigureFunc = providerConfigure(p)
   141  
   142  	return p
   143  }
   144  
   145  // Config is the configuration structure used to instantiate a
   146  // new Azure management client.
   147  type Config struct {
   148  	ManagementURL string
   149  
   150  	SubscriptionID           string
   151  	ClientID                 string
   152  	ClientSecret             string
   153  	TenantID                 string
   154  	Environment              string
   155  	SkipProviderRegistration bool
   156  
   157  	validateCredentialsOnce sync.Once
   158  }
   159  
   160  func (c *Config) validate() error {
   161  	var err *multierror.Error
   162  
   163  	if c.SubscriptionID == "" {
   164  		err = multierror.Append(err, fmt.Errorf("Subscription ID must be configured for the AzureRM provider"))
   165  	}
   166  	if c.ClientID == "" {
   167  		err = multierror.Append(err, fmt.Errorf("Client ID must be configured for the AzureRM provider"))
   168  	}
   169  	if c.ClientSecret == "" {
   170  		err = multierror.Append(err, fmt.Errorf("Client Secret must be configured for the AzureRM provider"))
   171  	}
   172  	if c.TenantID == "" {
   173  		err = multierror.Append(err, fmt.Errorf("Tenant ID must be configured for the AzureRM provider"))
   174  	}
   175  	if c.Environment == "" {
   176  		err = multierror.Append(err, fmt.Errorf("Environment must be configured for the AzureRM provider"))
   177  	}
   178  
   179  	return err.ErrorOrNil()
   180  }
   181  
   182  func providerConfigure(p *schema.Provider) schema.ConfigureFunc {
   183  	return func(d *schema.ResourceData) (interface{}, error) {
   184  		config := &Config{
   185  			SubscriptionID:           d.Get("subscription_id").(string),
   186  			ClientID:                 d.Get("client_id").(string),
   187  			ClientSecret:             d.Get("client_secret").(string),
   188  			TenantID:                 d.Get("tenant_id").(string),
   189  			Environment:              d.Get("environment").(string),
   190  			SkipProviderRegistration: d.Get("skip_provider_registration").(bool),
   191  		}
   192  
   193  		if err := config.validate(); err != nil {
   194  			return nil, err
   195  		}
   196  
   197  		client, err := config.getArmClient()
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  
   202  		client.StopContext = p.StopContext()
   203  
   204  		// replaces the context between tests
   205  		p.MetaReset = func() error {
   206  			client.StopContext = p.StopContext()
   207  			return nil
   208  		}
   209  
   210  		// List all the available providers and their registration state to avoid unnecessary
   211  		// requests. This also lets us check if the provider credentials are correct.
   212  		providerList, err := client.providers.List(nil, "")
   213  		if err != nil {
   214  			return nil, fmt.Errorf("Unable to list provider registration status, it is possible that this is due to invalid "+
   215  				"credentials or the service principal does not have permission to use the Resource Manager API, Azure "+
   216  				"error: %s", err)
   217  		}
   218  
   219  		if !config.SkipProviderRegistration {
   220  			err = registerAzureResourceProvidersWithSubscription(*providerList.Value, client.providers)
   221  			if err != nil {
   222  				return nil, err
   223  			}
   224  		}
   225  
   226  		return client, nil
   227  	}
   228  }
   229  
   230  func registerProviderWithSubscription(providerName string, client resources.ProvidersClient) error {
   231  	_, err := client.Register(providerName)
   232  	if err != nil {
   233  		return fmt.Errorf("Cannot register provider %s with Azure Resource Manager: %s.", providerName, err)
   234  	}
   235  
   236  	return nil
   237  }
   238  
   239  var providerRegistrationOnce sync.Once
   240  
   241  // registerAzureResourceProvidersWithSubscription uses the providers client to register
   242  // all Azure resource providers which the Terraform provider may require (regardless of
   243  // whether they are actually used by the configuration or not). It was confirmed by Microsoft
   244  // that this is the approach their own internal tools also take.
   245  func registerAzureResourceProvidersWithSubscription(providerList []resources.Provider, client resources.ProvidersClient) error {
   246  	var err error
   247  	providerRegistrationOnce.Do(func() {
   248  		providers := map[string]struct{}{
   249  			"Microsoft.Compute":           struct{}{},
   250  			"Microsoft.Cache":             struct{}{},
   251  			"Microsoft.ContainerRegistry": struct{}{},
   252  			"Microsoft.ContainerService":  struct{}{},
   253  			"Microsoft.Network":           struct{}{},
   254  			"Microsoft.Cdn":               struct{}{},
   255  			"Microsoft.Storage":           struct{}{},
   256  			"Microsoft.Sql":               struct{}{},
   257  			"Microsoft.Search":            struct{}{},
   258  			"Microsoft.Resources":         struct{}{},
   259  			"Microsoft.ServiceBus":        struct{}{},
   260  			"Microsoft.KeyVault":          struct{}{},
   261  			"Microsoft.EventHub":          struct{}{},
   262  		}
   263  
   264  		// filter out any providers already registered
   265  		for _, p := range providerList {
   266  			if _, ok := providers[*p.Namespace]; !ok {
   267  				continue
   268  			}
   269  
   270  			if strings.ToLower(*p.RegistrationState) == "registered" {
   271  				log.Printf("[DEBUG] Skipping provider registration for namespace %s\n", *p.Namespace)
   272  				delete(providers, *p.Namespace)
   273  			}
   274  		}
   275  
   276  		var wg sync.WaitGroup
   277  		wg.Add(len(providers))
   278  		for providerName := range providers {
   279  			go func(p string) {
   280  				defer wg.Done()
   281  				log.Printf("[DEBUG] Registering provider with namespace %s\n", p)
   282  				if innerErr := registerProviderWithSubscription(p, client); err != nil {
   283  					err = innerErr
   284  				}
   285  			}(providerName)
   286  		}
   287  		wg.Wait()
   288  	})
   289  
   290  	return err
   291  }
   292  
   293  // armMutexKV is the instance of MutexKV for ARM resources
   294  var armMutexKV = mutexkv.NewMutexKV()
   295  
   296  func azureStateRefreshFunc(resourceURI string, client *ArmClient, command riviera.APICall) resource.StateRefreshFunc {
   297  	return func() (interface{}, string, error) {
   298  		req := client.rivieraClient.NewRequestForURI(resourceURI)
   299  		req.Command = command
   300  
   301  		res, err := req.Execute()
   302  		if err != nil {
   303  			return nil, "", fmt.Errorf("Error executing %T command in azureStateRefreshFunc", req.Command)
   304  		}
   305  
   306  		var value reflect.Value
   307  		if reflect.ValueOf(res.Parsed).Kind() == reflect.Ptr {
   308  			value = reflect.ValueOf(res.Parsed).Elem()
   309  		} else {
   310  			value = reflect.ValueOf(res.Parsed)
   311  		}
   312  
   313  		for i := 0; i < value.NumField(); i++ { // iterates through every struct type field
   314  			tag := value.Type().Field(i).Tag // returns the tag string
   315  			tagValue := tag.Get("mapstructure")
   316  			if tagValue == "provisioningState" {
   317  				return res.Parsed, value.Field(i).Elem().String(), nil
   318  			}
   319  		}
   320  
   321  		panic(fmt.Errorf("azureStateRefreshFunc called on structure %T with no mapstructure:provisioningState tag. This is a bug", res.Parsed))
   322  	}
   323  }
   324  
   325  // Resource group names can be capitalised, but we store them in lowercase.
   326  // Use a custom diff function to avoid creation of new resources.
   327  func resourceAzurermResourceGroupNameDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
   328  	return strings.ToLower(old) == strings.ToLower(new)
   329  }
   330  
   331  // ignoreCaseDiffSuppressFunc is a DiffSuppressFunc from helper/schema that is
   332  // used to ignore any case-changes in a return value.
   333  func ignoreCaseDiffSuppressFunc(k, old, new string, d *schema.ResourceData) bool {
   334  	return strings.ToLower(old) == strings.ToLower(new)
   335  }
   336  
   337  // ignoreCaseStateFunc is a StateFunc from helper/schema that converts the
   338  // supplied value to lower before saving to state for consistency.
   339  func ignoreCaseStateFunc(val interface{}) string {
   340  	return strings.ToLower(val.(string))
   341  }
   342  
   343  func userDataStateFunc(v interface{}) string {
   344  	switch s := v.(type) {
   345  	case string:
   346  		s = base64Encode(s)
   347  		hash := sha1.Sum([]byte(s))
   348  		return hex.EncodeToString(hash[:])
   349  	default:
   350  		return ""
   351  	}
   352  }
   353  
   354  // base64Encode encodes data if the input isn't already encoded using
   355  // base64.StdEncoding.EncodeToString. If the input is already base64 encoded,
   356  // return the original input unchanged.
   357  func base64Encode(data string) string {
   358  	// Check whether the data is already Base64 encoded; don't double-encode
   359  	if isBase64Encoded(data) {
   360  		return data
   361  	}
   362  	// data has not been encoded encode and return
   363  	return base64.StdEncoding.EncodeToString([]byte(data))
   364  }
   365  
   366  func isBase64Encoded(data string) bool {
   367  	_, err := base64.StdEncoding.DecodeString(data)
   368  	return err == nil
   369  }