diff --git a/builtin/providers/azurerm/config.go b/builtin/providers/azurerm/config.go index 22a6bf977..6ac129252 100644 --- a/builtin/providers/azurerm/config.go +++ b/builtin/providers/azurerm/config.go @@ -147,15 +147,6 @@ func (c *Config) getArmClient() (*ArmClient, error) { if err != nil { return nil, fmt.Errorf("Error creating Riviera client: %s", err) } - - // validate that the credentials are correct using Riviera. Note that this must be - // done _before_ using the Microsoft SDK, because Riviera handles errors. Using a - // namespace registration instead of a simple OAuth token refresh guarantees that - // service delegation is correct. This has the effect of registering Microsoft.Compute - // which is neccessary anyway. - if err := registerProviderWithSubscription("Microsoft.Compute", rivieraClient); err != nil { - return nil, err - } client.rivieraClient = rivieraClient oauthConfig, err := azure.PublicCloud.OAuthConfigForTenant(c.TenantID) diff --git a/builtin/providers/azurerm/provider.go b/builtin/providers/azurerm/provider.go index 85a32165b..9b4be30dd 100644 --- a/builtin/providers/azurerm/provider.go +++ b/builtin/providers/azurerm/provider.go @@ -2,11 +2,12 @@ package azurerm import ( "fmt" + "log" "reflect" "strings" - "sync" + "github.com/Azure/azure-sdk-for-go/arm/resources/resources" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/helper/mutexkv" "github.com/hashicorp/terraform/helper/resource" @@ -43,6 +44,12 @@ func Provider() terraform.ResourceProvider { Required: true, DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), }, + + "skip_provider_registration": { + Type: schema.TypeBool, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("ARM_SKIP_PROVIDER_REGISTRATION", false), + }, }, DataSourcesMap: map[string]*schema.Resource{ @@ -123,10 +130,11 @@ func Provider() terraform.ResourceProvider { type Config struct { ManagementURL string - SubscriptionID string - ClientID string - ClientSecret string - TenantID string + SubscriptionID string + ClientID string + ClientSecret string + TenantID string + SkipProviderRegistration bool validateCredentialsOnce sync.Once } @@ -153,10 +161,11 @@ func (c *Config) validate() error { func providerConfigure(p *schema.Provider) schema.ConfigureFunc { return func(d *schema.ResourceData) (interface{}, error) { config := &Config{ - SubscriptionID: d.Get("subscription_id").(string), - ClientID: d.Get("client_id").(string), - ClientSecret: d.Get("client_secret").(string), - TenantID: d.Get("tenant_id").(string), + SubscriptionID: d.Get("subscription_id").(string), + ClientID: d.Get("client_id").(string), + ClientSecret: d.Get("client_secret").(string), + TenantID: d.Get("tenant_id").(string), + SkipProviderRegistration: d.Get("skip_provider_registration").(bool), } if err := config.validate(); err != nil { @@ -170,30 +179,30 @@ func providerConfigure(p *schema.Provider) schema.ConfigureFunc { client.StopContext = p.StopContext() - err = registerAzureResourceProvidersWithSubscription(client.rivieraClient) + // List all the available providers and their registration state to avoid unnecessary + // requests. This also lets us check if the provider credentials are correct. + providerList, err := client.providers.List(nil, "") if err != nil { - return nil, err + return nil, fmt.Errorf("Unable to list provider registration status, it is possible that this is due to invalid "+ + "credentials or the service principal does not have permission to use the Resource Manager API, Azure "+ + "error: %s", err) + } + + if !config.SkipProviderRegistration { + err = registerAzureResourceProvidersWithSubscription(*providerList.Value, client.providers) + if err != nil { + return nil, err + } } return client, nil } } -func registerProviderWithSubscription(providerName string, client *riviera.Client) error { - request := client.NewRequest() - request.Command = riviera.RegisterResourceProvider{ - Namespace: providerName, - } - - response, err := request.Execute() +func registerProviderWithSubscription(providerName string, client resources.ProvidersClient) error { + _, err := client.Register(providerName) if err != nil { - return fmt.Errorf("Cannot request provider registration for Azure Resource Manager: %s.", err) - } - - if !response.IsSuccessful() { - return fmt.Errorf("Credentials for accessing the Azure Resource Manager API are likely " + - "to be incorrect, or\n the service principal does not have permission to use " + - "the Azure Service Management\n API.") + return fmt.Errorf("Cannot register provider %s with Azure Resource Manager: %s.", providerName, err) } return nil @@ -205,29 +214,42 @@ var providerRegistrationOnce sync.Once // all Azure resource providers which the Terraform provider may require (regardless of // whether they are actually used by the configuration or not). It was confirmed by Microsoft // that this is the approach their own internal tools also take. -func registerAzureResourceProvidersWithSubscription(client *riviera.Client) error { +func registerAzureResourceProvidersWithSubscription(providerList []resources.Provider, client resources.ProvidersClient) error { var err error providerRegistrationOnce.Do(func() { - // We register Microsoft.Compute during client initialization - providers := []string{ - "Microsoft.Cache", - "Microsoft.ContainerRegistry", - "Microsoft.Network", - "Microsoft.Cdn", - "Microsoft.Storage", - "Microsoft.Sql", - "Microsoft.Search", - "Microsoft.Resources", - "Microsoft.ServiceBus", - "Microsoft.KeyVault", - "Microsoft.EventHub", + providers := map[string]struct{}{ + "Microsoft.Compute": struct{}{}, + "Microsoft.Cache": struct{}{}, + "Microsoft.ContainerRegistry": struct{}{}, + "Microsoft.Network": struct{}{}, + "Microsoft.Cdn": struct{}{}, + "Microsoft.Storage": struct{}{}, + "Microsoft.Sql": struct{}{}, + "Microsoft.Search": struct{}{}, + "Microsoft.Resources": struct{}{}, + "Microsoft.ServiceBus": struct{}{}, + "Microsoft.KeyVault": struct{}{}, + "Microsoft.EventHub": struct{}{}, + } + + // filter out any providers already registered + for _, p := range providerList { + if _, ok := providers[*p.Namespace]; !ok { + continue + } + + if strings.ToLower(*p.RegistrationState) == "registered" { + log.Printf("[DEBUG] Skipping provider registration for namespace %s\n", *p.Namespace) + delete(providers, *p.Namespace) + } } var wg sync.WaitGroup wg.Add(len(providers)) - for _, providerName := range providers { + for providerName := range providers { go func(p string) { defer wg.Done() + log.Printf("[DEBUG] Registering provider with namespace %s\n", p) if innerErr := registerProviderWithSubscription(p, client); err != nil { err = innerErr } diff --git a/website/source/docs/providers/azurerm/index.html.markdown b/website/source/docs/providers/azurerm/index.html.markdown index 610a2fd34..f6d9bc2af 100644 --- a/website/source/docs/providers/azurerm/index.html.markdown +++ b/website/source/docs/providers/azurerm/index.html.markdown @@ -76,6 +76,12 @@ The following arguments are supported: * `tenant_id` - (Optional) The tenant ID to use. It can also be sourced from the `ARM_TENANT_ID` environment variable. +* `skip_provider_registration` - (Optional) Prevents the provier from registering + the ARM provider namespaces, this can be used if you don't wish to give the Active + Directory Application permission to register resource providers. It can also be + sourced from the `ARM_SKIP_PROVIDER_REGISTRATION` environment variable, defaults + to `false`. + ## Creating Credentials Azure requires that an application is added to Azure Active Directory to generate the `client_id`, `client_secret`, and `tenant_id` needed by Terraform (`subscription_id` can be recovered from your Azure account details).