Merge branch 'azurerm-provider-registration' of https://github.com/BedeGaming/terraform into BedeGaming-azurerm-provider-registration

This commit is contained in:
James Nugent 2017-01-02 11:51:26 -06:00
commit 81dc5ee328
3 changed files with 68 additions and 49 deletions

View File

@ -147,15 +147,6 @@ func (c *Config) getArmClient() (*ArmClient, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("Error creating Riviera client: %s", err) 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 client.rivieraClient = rivieraClient
oauthConfig, err := azure.PublicCloud.OAuthConfigForTenant(c.TenantID) oauthConfig, err := azure.PublicCloud.OAuthConfigForTenant(c.TenantID)

View File

@ -2,11 +2,12 @@ package azurerm
import ( import (
"fmt" "fmt"
"log"
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
"github.com/Azure/azure-sdk-for-go/arm/resources/resources"
"github.com/hashicorp/go-multierror" "github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/mutexkv" "github.com/hashicorp/terraform/helper/mutexkv"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
@ -43,6 +44,12 @@ func Provider() terraform.ResourceProvider {
Required: true, Required: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), 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{ DataSourcesMap: map[string]*schema.Resource{
@ -123,10 +130,11 @@ func Provider() terraform.ResourceProvider {
type Config struct { type Config struct {
ManagementURL string ManagementURL string
SubscriptionID string SubscriptionID string
ClientID string ClientID string
ClientSecret string ClientSecret string
TenantID string TenantID string
SkipProviderRegistration bool
validateCredentialsOnce sync.Once validateCredentialsOnce sync.Once
} }
@ -153,10 +161,11 @@ func (c *Config) validate() error {
func providerConfigure(p *schema.Provider) schema.ConfigureFunc { func providerConfigure(p *schema.Provider) schema.ConfigureFunc {
return func(d *schema.ResourceData) (interface{}, error) { return func(d *schema.ResourceData) (interface{}, error) {
config := &Config{ config := &Config{
SubscriptionID: d.Get("subscription_id").(string), SubscriptionID: d.Get("subscription_id").(string),
ClientID: d.Get("client_id").(string), ClientID: d.Get("client_id").(string),
ClientSecret: d.Get("client_secret").(string), ClientSecret: d.Get("client_secret").(string),
TenantID: d.Get("tenant_id").(string), TenantID: d.Get("tenant_id").(string),
SkipProviderRegistration: d.Get("skip_provider_registration").(bool),
} }
if err := config.validate(); err != nil { if err := config.validate(); err != nil {
@ -170,30 +179,30 @@ func providerConfigure(p *schema.Provider) schema.ConfigureFunc {
client.StopContext = p.StopContext() 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 { 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 return client, nil
} }
} }
func registerProviderWithSubscription(providerName string, client *riviera.Client) error { func registerProviderWithSubscription(providerName string, client resources.ProvidersClient) error {
request := client.NewRequest() _, err := client.Register(providerName)
request.Command = riviera.RegisterResourceProvider{
Namespace: providerName,
}
response, err := request.Execute()
if err != nil { if err != nil {
return fmt.Errorf("Cannot request provider registration for Azure Resource Manager: %s.", err) return fmt.Errorf("Cannot register provider %s with Azure Resource Manager: %s.", providerName, 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 nil return nil
@ -205,29 +214,42 @@ var providerRegistrationOnce sync.Once
// all Azure resource providers which the Terraform provider may require (regardless of // 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 // 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. // 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 var err error
providerRegistrationOnce.Do(func() { providerRegistrationOnce.Do(func() {
// We register Microsoft.Compute during client initialization providers := map[string]struct{}{
providers := []string{ "Microsoft.Compute": struct{}{},
"Microsoft.Cache", "Microsoft.Cache": struct{}{},
"Microsoft.ContainerRegistry", "Microsoft.ContainerRegistry": struct{}{},
"Microsoft.Network", "Microsoft.Network": struct{}{},
"Microsoft.Cdn", "Microsoft.Cdn": struct{}{},
"Microsoft.Storage", "Microsoft.Storage": struct{}{},
"Microsoft.Sql", "Microsoft.Sql": struct{}{},
"Microsoft.Search", "Microsoft.Search": struct{}{},
"Microsoft.Resources", "Microsoft.Resources": struct{}{},
"Microsoft.ServiceBus", "Microsoft.ServiceBus": struct{}{},
"Microsoft.KeyVault", "Microsoft.KeyVault": struct{}{},
"Microsoft.EventHub", "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 var wg sync.WaitGroup
wg.Add(len(providers)) wg.Add(len(providers))
for _, providerName := range providers { for providerName := range providers {
go func(p string) { go func(p string) {
defer wg.Done() defer wg.Done()
log.Printf("[DEBUG] Registering provider with namespace %s\n", p)
if innerErr := registerProviderWithSubscription(p, client); err != nil { if innerErr := registerProviderWithSubscription(p, client); err != nil {
err = innerErr err = innerErr
} }

View File

@ -76,6 +76,12 @@ The following arguments are supported:
* `tenant_id` - (Optional) The tenant ID to use. It can also be sourced from the * `tenant_id` - (Optional) The tenant ID to use. It can also be sourced from the
`ARM_TENANT_ID` environment variable. `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 ## 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). 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).