github.com/ottenhoff/terraform@v0.7.0-rc1.0.20160607213102-ac2d195cc560/builtin/providers/azurerm/provider.go (about) 1 package azurerm 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 8 "github.com/hashicorp/go-multierror" 9 "github.com/hashicorp/terraform/helper/mutexkv" 10 "github.com/hashicorp/terraform/helper/resource" 11 "github.com/hashicorp/terraform/helper/schema" 12 "github.com/hashicorp/terraform/terraform" 13 riviera "github.com/jen20/riviera/azure" 14 "sync" 15 ) 16 17 // Provider returns a terraform.ResourceProvider. 18 func Provider() terraform.ResourceProvider { 19 return &schema.Provider{ 20 Schema: map[string]*schema.Schema{ 21 "subscription_id": { 22 Type: schema.TypeString, 23 Required: true, 24 DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""), 25 }, 26 27 "client_id": { 28 Type: schema.TypeString, 29 Required: true, 30 DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""), 31 }, 32 33 "client_secret": { 34 Type: schema.TypeString, 35 Required: true, 36 DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""), 37 }, 38 39 "tenant_id": { 40 Type: schema.TypeString, 41 Required: true, 42 DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""), 43 }, 44 }, 45 46 ResourcesMap: map[string]*schema.Resource{ 47 // These resources use the Azure ARM SDK 48 "azurerm_availability_set": resourceArmAvailabilitySet(), 49 "azurerm_cdn_endpoint": resourceArmCdnEndpoint(), 50 "azurerm_cdn_profile": resourceArmCdnProfile(), 51 "azurerm_local_network_gateway": resourceArmLocalNetworkGateway(), 52 "azurerm_network_interface": resourceArmNetworkInterface(), 53 "azurerm_network_security_group": resourceArmNetworkSecurityGroup(), 54 "azurerm_network_security_rule": resourceArmNetworkSecurityRule(), 55 "azurerm_public_ip": resourceArmPublicIp(), 56 "azurerm_route": resourceArmRoute(), 57 "azurerm_route_table": resourceArmRouteTable(), 58 "azurerm_storage_account": resourceArmStorageAccount(), 59 "azurerm_storage_blob": resourceArmStorageBlob(), 60 "azurerm_storage_container": resourceArmStorageContainer(), 61 "azurerm_storage_queue": resourceArmStorageQueue(), 62 "azurerm_subnet": resourceArmSubnet(), 63 "azurerm_template_deployment": resourceArmTemplateDeployment(), 64 "azurerm_virtual_machine": resourceArmVirtualMachine(), 65 "azurerm_virtual_network": resourceArmVirtualNetwork(), 66 67 // These resources use the Riviera SDK 68 "azurerm_dns_a_record": resourceArmDnsARecord(), 69 "azurerm_dns_aaaa_record": resourceArmDnsAAAARecord(), 70 "azurerm_dns_cname_record": resourceArmDnsCNameRecord(), 71 "azurerm_dns_mx_record": resourceArmDnsMxRecord(), 72 "azurerm_dns_ns_record": resourceArmDnsNsRecord(), 73 "azurerm_dns_srv_record": resourceArmDnsSrvRecord(), 74 "azurerm_dns_txt_record": resourceArmDnsTxtRecord(), 75 "azurerm_dns_zone": resourceArmDnsZone(), 76 "azurerm_resource_group": resourceArmResourceGroup(), 77 "azurerm_search_service": resourceArmSearchService(), 78 "azurerm_sql_database": resourceArmSqlDatabase(), 79 "azurerm_sql_firewall_rule": resourceArmSqlFirewallRule(), 80 "azurerm_sql_server": resourceArmSqlServer(), 81 }, 82 ConfigureFunc: providerConfigure, 83 } 84 } 85 86 // Config is the configuration structure used to instantiate a 87 // new Azure management client. 88 type Config struct { 89 ManagementURL string 90 91 SubscriptionID string 92 ClientID string 93 ClientSecret string 94 TenantID string 95 96 validateCredentialsOnce sync.Once 97 } 98 99 func (c *Config) validate() error { 100 var err *multierror.Error 101 102 if c.SubscriptionID == "" { 103 err = multierror.Append(err, fmt.Errorf("Subscription ID must be configured for the AzureRM provider")) 104 } 105 if c.ClientID == "" { 106 err = multierror.Append(err, fmt.Errorf("Client ID must be configured for the AzureRM provider")) 107 } 108 if c.ClientSecret == "" { 109 err = multierror.Append(err, fmt.Errorf("Client Secret must be configured for the AzureRM provider")) 110 } 111 if c.TenantID == "" { 112 err = multierror.Append(err, fmt.Errorf("Tenant ID must be configured for the AzureRM provider")) 113 } 114 115 return err.ErrorOrNil() 116 } 117 118 func providerConfigure(d *schema.ResourceData) (interface{}, error) { 119 config := &Config{ 120 SubscriptionID: d.Get("subscription_id").(string), 121 ClientID: d.Get("client_id").(string), 122 ClientSecret: d.Get("client_secret").(string), 123 TenantID: d.Get("tenant_id").(string), 124 } 125 126 if err := config.validate(); err != nil { 127 return nil, err 128 } 129 130 client, err := config.getArmClient() 131 if err != nil { 132 return nil, err 133 } 134 135 err = registerAzureResourceProvidersWithSubscription(client.rivieraClient) 136 if err != nil { 137 return nil, err 138 } 139 140 return client, nil 141 } 142 143 func registerProviderWithSubscription(providerName string, client *riviera.Client) error { 144 request := client.NewRequest() 145 request.Command = riviera.RegisterResourceProvider{ 146 Namespace: providerName, 147 } 148 149 response, err := request.Execute() 150 if err != nil { 151 return fmt.Errorf("Cannot request provider registration for Azure Resource Manager: %s.", err) 152 } 153 154 if !response.IsSuccessful() { 155 return fmt.Errorf("Credentials for acessing the Azure Resource Manager API are likely " + 156 "to be incorrect, or\n the service principal does not have permission to use " + 157 "the Azure Service Management\n API.") 158 } 159 160 return nil 161 } 162 163 var providerRegistrationOnce sync.Once 164 165 // registerAzureResourceProvidersWithSubscription uses the providers client to register 166 // all Azure resource providers which the Terraform provider may require (regardless of 167 // whether they are actually used by the configuration or not). It was confirmed by Microsoft 168 // that this is the approach their own internal tools also take. 169 func registerAzureResourceProvidersWithSubscription(client *riviera.Client) error { 170 var err error 171 providerRegistrationOnce.Do(func() { 172 // We register Microsoft.Compute during client initialization 173 providers := []string{"Microsoft.Network", "Microsoft.Cdn", "Microsoft.Storage", "Microsoft.Sql", "Microsoft.Search", "Microsoft.Resources"} 174 175 var wg sync.WaitGroup 176 wg.Add(len(providers)) 177 for _, providerName := range providers { 178 go func(p string) { 179 defer wg.Done() 180 if innerErr := registerProviderWithSubscription(p, client); err != nil { 181 err = innerErr 182 } 183 }(providerName) 184 } 185 wg.Wait() 186 }) 187 188 return err 189 } 190 191 // azureRMNormalizeLocation is a function which normalises human-readable region/location 192 // names (e.g. "West US") to the values used and returned by the Azure API (e.g. "westus"). 193 // In state we track the API internal version as it is easier to go from the human form 194 // to the canonical form than the other way around. 195 func azureRMNormalizeLocation(location interface{}) string { 196 input := location.(string) 197 return strings.Replace(strings.ToLower(input), " ", "", -1) 198 } 199 200 // armMutexKV is the instance of MutexKV for ARM resources 201 var armMutexKV = mutexkv.NewMutexKV() 202 203 func azureStateRefreshFunc(resourceURI string, client *ArmClient, command riviera.APICall) resource.StateRefreshFunc { 204 return func() (interface{}, string, error) { 205 req := client.rivieraClient.NewRequestForURI(resourceURI) 206 req.Command = command 207 208 res, err := req.Execute() 209 if err != nil { 210 return nil, "", fmt.Errorf("Error executing %T command in azureStateRefreshFunc", req.Command) 211 } 212 213 var value reflect.Value 214 if reflect.ValueOf(res.Parsed).Kind() == reflect.Ptr { 215 value = reflect.ValueOf(res.Parsed).Elem() 216 } else { 217 value = reflect.ValueOf(res.Parsed) 218 } 219 220 for i := 0; i < value.NumField(); i++ { // iterates through every struct type field 221 tag := value.Type().Field(i).Tag // returns the tag string 222 tagValue := tag.Get("mapstructure") 223 if tagValue == "provisioningState" { 224 return res.Parsed, value.Field(i).Elem().String(), nil 225 } 226 } 227 228 panic(fmt.Errorf("azureStateRefreshFunc called on structure %T with no mapstructure:provisioningState tag. This is a bug", res.Parsed)) 229 } 230 }