provider/azurerm: Initial commit.
This commit brings some of the work over from #3808, but rearchitects to use a separate provider for Azure Resource Manager. This is in line with the decisions made by the Azure Powershell Cmdlets, and is important for usability since the sets of required fields change between the ASM and ARM APIs. Currently `azurerm_resource_group` and `azurerm_virtual_network` are implemented, more resources will follow.
This commit is contained in:
parent
294d59cee4
commit
c279adfc55
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/terraform/builtin/providers/azurerm"
|
||||||
|
"github.com/hashicorp/terraform/plugin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
plugin.Serve(&plugin.ServeOpts{
|
||||||
|
ProviderFunc: azurerm.Provider,
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
package main
|
|
@ -0,0 +1,227 @@
|
||||||
|
package azurerm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/azure"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/arm/compute"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/arm/resources"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/arm/scheduler"
|
||||||
|
"github.com/Azure/azure-sdk-for-go/arm/storage"
|
||||||
|
"github.com/hashicorp/terraform/helper/pathorcontents"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ArmClient contains the handles to all the specific Azure Resource Manager
|
||||||
|
// resource classes' respective clients.
|
||||||
|
type ArmClient struct {
|
||||||
|
availSetClient compute.AvailabilitySetsClient
|
||||||
|
usageOpsClient compute.UsageOperationsClient
|
||||||
|
vmExtensionImageClient compute.VirtualMachineExtensionImagesClient
|
||||||
|
vmExtensionClient compute.VirtualMachineExtensionsClient
|
||||||
|
vmImageClient compute.VirtualMachineImagesClient
|
||||||
|
vmClient compute.VirtualMachinesClient
|
||||||
|
|
||||||
|
appGatewayClient network.ApplicationGatewaysClient
|
||||||
|
ifaceClient network.InterfacesClient
|
||||||
|
loadBalancerClient network.LoadBalancersClient
|
||||||
|
localNetConnClient network.LocalNetworkGatewaysClient
|
||||||
|
publicIPClient network.PublicIPAddressesClient
|
||||||
|
secGroupClient network.SecurityGroupsClient
|
||||||
|
secRuleClient network.SecurityRulesClient
|
||||||
|
subnetClient network.SubnetsClient
|
||||||
|
netUsageClient network.UsagesClient
|
||||||
|
vnetGatewayConnectionsClient network.VirtualNetworkGatewayConnectionsClient
|
||||||
|
vnetGatewayClient network.VirtualNetworkGatewaysClient
|
||||||
|
vnetClient network.VirtualNetworksClient
|
||||||
|
|
||||||
|
resourceGroupClient resources.GroupsClient
|
||||||
|
tagsClient resources.TagsClient
|
||||||
|
|
||||||
|
jobsClient scheduler.JobsClient
|
||||||
|
jobsCollectionsClient scheduler.JobCollectionsClient
|
||||||
|
|
||||||
|
storageServiceClient storage.AccountsClient
|
||||||
|
storageUsageClient storage.UsageOperationsClient
|
||||||
|
}
|
||||||
|
|
||||||
|
// getArmClient is a helper method which returns a fully instantiated
|
||||||
|
// *ArmClient based on the Config's current settings.
|
||||||
|
func (c *Config) getArmClient() (*ArmClient, error) {
|
||||||
|
// first; check that all the necessary credentials were provided:
|
||||||
|
if !c._armCredentialsProvided() {
|
||||||
|
return nil, fmt.Errorf("Not all ARM-required fields have been provided.")
|
||||||
|
}
|
||||||
|
|
||||||
|
spt, err := azure.NewServicePrincipalToken(c.ClientID, c.ClientSecret, c.TenantID, azure.AzureResourceManagerScope)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// client declarations:
|
||||||
|
client := ArmClient{}
|
||||||
|
|
||||||
|
// NOTE: these declarations should be left separate for clarity should the
|
||||||
|
// clients be wished to be configured with custom Responders/PollingModess etc...
|
||||||
|
asc := compute.NewAvailabilitySetsClient(c.SubscriptionID)
|
||||||
|
asc.Authorizer = spt
|
||||||
|
client.availSetClient = asc
|
||||||
|
|
||||||
|
uoc := compute.NewUsageOperationsClient(c.SubscriptionID)
|
||||||
|
uoc.Authorizer = spt
|
||||||
|
client.usageOpsClient = uoc
|
||||||
|
|
||||||
|
vmeic := compute.NewVirtualMachineExtensionImagesClient(c.SubscriptionID)
|
||||||
|
vmeic.Authorizer = spt
|
||||||
|
client.vmExtensionImageClient = vmeic
|
||||||
|
|
||||||
|
vmec := compute.NewVirtualMachineExtensionsClient(c.SubscriptionID)
|
||||||
|
vmec.Authorizer = spt
|
||||||
|
client.vmExtensionClient = vmec
|
||||||
|
|
||||||
|
vmic := compute.NewVirtualMachineImagesClient(c.SubscriptionID)
|
||||||
|
vmic.Authorizer = spt
|
||||||
|
client.vmImageClient = vmic
|
||||||
|
|
||||||
|
vmc := compute.NewVirtualMachinesClient(c.SubscriptionID)
|
||||||
|
vmc.Authorizer = spt
|
||||||
|
client.vmClient = vmc
|
||||||
|
|
||||||
|
agc := network.NewApplicationGatewaysClient(c.SubscriptionID)
|
||||||
|
agc.Authorizer = spt
|
||||||
|
client.appGatewayClient = agc
|
||||||
|
|
||||||
|
ifc := network.NewInterfacesClient(c.SubscriptionID)
|
||||||
|
ifc.Authorizer = spt
|
||||||
|
client.ifaceClient = ifc
|
||||||
|
|
||||||
|
lbc := network.NewLoadBalancersClient(c.SubscriptionID)
|
||||||
|
lbc.Authorizer = spt
|
||||||
|
client.loadBalancerClient = lbc
|
||||||
|
|
||||||
|
lgc := network.NewLocalNetworkGatewaysClient(c.SubscriptionID)
|
||||||
|
lgc.Authorizer = spt
|
||||||
|
client.localNetConnClient = lgc
|
||||||
|
|
||||||
|
pipc := network.NewPublicIPAddressesClient(c.SubscriptionID)
|
||||||
|
pipc.Authorizer = spt
|
||||||
|
client.publicIPClient = pipc
|
||||||
|
|
||||||
|
sgc := network.NewSecurityGroupsClient(c.SubscriptionID)
|
||||||
|
sgc.Authorizer = spt
|
||||||
|
client.secGroupClient = sgc
|
||||||
|
|
||||||
|
src := network.NewSecurityRulesClient(c.SubscriptionID)
|
||||||
|
src.Authorizer = spt
|
||||||
|
client.secRuleClient = src
|
||||||
|
|
||||||
|
snc := network.NewSubnetsClient(c.SubscriptionID)
|
||||||
|
snc.Authorizer = spt
|
||||||
|
client.subnetClient = snc
|
||||||
|
|
||||||
|
vgcc := network.NewVirtualNetworkGatewayConnectionsClient(c.SubscriptionID)
|
||||||
|
vgcc.Authorizer = spt
|
||||||
|
client.vnetGatewayConnectionsClient = vgcc
|
||||||
|
|
||||||
|
vgc := network.NewVirtualNetworkGatewaysClient(c.SubscriptionID)
|
||||||
|
vgc.Authorizer = spt
|
||||||
|
client.vnetGatewayClient = vgc
|
||||||
|
|
||||||
|
vnc := network.NewVirtualNetworksClient(c.SubscriptionID)
|
||||||
|
vnc.Authorizer = spt
|
||||||
|
client.vnetClient = vnc
|
||||||
|
|
||||||
|
rgc := resources.NewGroupsClient(c.SubscriptionID)
|
||||||
|
rgc.Authorizer = spt
|
||||||
|
client.resourceGroupClient = rgc
|
||||||
|
|
||||||
|
tc := resources.NewTagsClient(c.SubscriptionID)
|
||||||
|
tc.Authorizer = spt
|
||||||
|
client.tagsClient = tc
|
||||||
|
|
||||||
|
jc := scheduler.NewJobsClient(c.SubscriptionID)
|
||||||
|
jc.Authorizer = spt
|
||||||
|
client.jobsClient = jc
|
||||||
|
|
||||||
|
jcc := scheduler.NewJobCollectionsClient(c.SubscriptionID)
|
||||||
|
jcc.Authorizer = spt
|
||||||
|
client.jobsCollectionsClient = jcc
|
||||||
|
|
||||||
|
ssc := storage.NewAccountsClient(c.SubscriptionID)
|
||||||
|
ssc.Authorizer = spt
|
||||||
|
client.storageServiceClient = ssc
|
||||||
|
|
||||||
|
suc := storage.NewUsageOperationsClient(c.SubscriptionID)
|
||||||
|
suc.Authorizer = spt
|
||||||
|
client.storageUsageClient = suc
|
||||||
|
|
||||||
|
return &client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// armCredentialsProvided is a helper method which indicates whether or not the
|
||||||
|
// credentials required for authenticating against the ARM APIs were provided.
|
||||||
|
func (c *Config) armCredentialsProvided() bool {
|
||||||
|
return c.ArmConfig != "" || c._armCredentialsProvided()
|
||||||
|
}
|
||||||
|
func (c *Config) _armCredentialsProvided() bool {
|
||||||
|
return !(c.SubscriptionID == "" || c.ClientID == "" || c.ClientSecret == "" || c.TenantID == "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// readArmSettings is a helper method which; given the contents of the ARM
|
||||||
|
// credentials file, loads all the data into the Config.
|
||||||
|
func (c *Config) readArmSettings(contents string) error {
|
||||||
|
data := &armConfigData{}
|
||||||
|
err := json.Unmarshal([]byte(contents), data)
|
||||||
|
|
||||||
|
c.SubscriptionID = data.SubscriptionID
|
||||||
|
c.ClientID = data.ClientID
|
||||||
|
c.ClientSecret = data.ClientSecret
|
||||||
|
c.TenantID = data.TenantID
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// configFileContentsWarning represents the warning message returned when the
|
||||||
|
// path to the 'arm_config_file' is provided instead of its sourced contents.
|
||||||
|
var configFileContentsWarning = `
|
||||||
|
The path to the 'arm_config_file' was provided instead of its contents.
|
||||||
|
Support for accepting filepaths instead of their contents will be removed
|
||||||
|
in the near future. Do please consider switching over to using
|
||||||
|
'${file("/path/to/config.arm")}' instead.
|
||||||
|
`[1:]
|
||||||
|
|
||||||
|
// validateArmConfigFile is a helper function which verifies that
|
||||||
|
// the provided ARM configuration file is valid.
|
||||||
|
func validateArmConfigFile(v interface{}, _ string) (ws []string, es []error) {
|
||||||
|
value := v.(string)
|
||||||
|
if value == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pathOrContents, wasPath, err := pathorcontents.Read(v.(string))
|
||||||
|
if err != nil {
|
||||||
|
es = append(es, fmt.Errorf("Error reading 'arm_config_file': %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if wasPath {
|
||||||
|
ws = append(ws, configFileContentsWarning)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := armConfigData{}
|
||||||
|
err = json.Unmarshal([]byte(pathOrContents), &data)
|
||||||
|
if err != nil {
|
||||||
|
es = append(es, fmt.Errorf("Error unmarshalling the provided 'arm_config_file': %s", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// armConfigData is a private struct which represents the expected layout of
|
||||||
|
// an ARM configuration file. It is used for unmarshalling purposes.
|
||||||
|
type armConfigData struct {
|
||||||
|
ClientID string `json:"clientID"`
|
||||||
|
ClientSecret string `json:"clientSecret"`
|
||||||
|
SubscriptionID string `json:"subscriptionID"`
|
||||||
|
TenantID string `json:"tenantID"`
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
package azurerm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider returns a terraform.ResourceProvider.
|
||||||
|
func Provider() terraform.ResourceProvider {
|
||||||
|
return &schema.Provider{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"arm_config_file": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "",
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("ARM_CONFIG_FILE", nil),
|
||||||
|
ValidateFunc: validateArmConfigFile,
|
||||||
|
},
|
||||||
|
|
||||||
|
"subscription_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("AZURE_SUBSCRIPTION_ID", ""),
|
||||||
|
},
|
||||||
|
|
||||||
|
"client_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
|
||||||
|
},
|
||||||
|
|
||||||
|
"client_secret": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
|
||||||
|
},
|
||||||
|
|
||||||
|
"tenant_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
|
"azurerm_resource_group": resourceArmResourceGroup(),
|
||||||
|
"azurerm_virtual_network": resourceArmVirtualNetwork(),
|
||||||
|
},
|
||||||
|
|
||||||
|
ConfigureFunc: providerConfigure,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the configuration structure used to instantiate a
|
||||||
|
// new Azure management client.
|
||||||
|
type Config struct {
|
||||||
|
ManagementURL string
|
||||||
|
|
||||||
|
ArmConfig string
|
||||||
|
|
||||||
|
SubscriptionID string
|
||||||
|
ClientID string
|
||||||
|
ClientSecret string
|
||||||
|
TenantID string
|
||||||
|
}
|
||||||
|
|
||||||
|
const noConfigError = `Credentials must be provided either via arm_config_file, or via
|
||||||
|
subscription_id, client_id, client_secret and tenant_id. Please see
|
||||||
|
the provider documentation for more information on how to obtain these
|
||||||
|
credentials.`
|
||||||
|
|
||||||
|
func providerConfigure(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),
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if credentials file is provided:
|
||||||
|
armConfig := d.Get("arm_config_file").(string)
|
||||||
|
if armConfig != "" {
|
||||||
|
// then, load the settings from that:
|
||||||
|
if err := config.readArmSettings(armConfig); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then; check whether the ARM credentials were provided:
|
||||||
|
if !config.armCredentialsProvided() {
|
||||||
|
return nil, fmt.Errorf(noConfigError)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := config.getArmClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func azureRMNormalizeLocation(location interface{}) string {
|
||||||
|
input := location.(string)
|
||||||
|
return strings.Replace(strings.ToLower(input), " ", "", -1)
|
||||||
|
}
|
|
@ -0,0 +1,195 @@
|
||||||
|
package azurerm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/config"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/mitchellh/go-homedir"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testAccProviders map[string]terraform.ResourceProvider
|
||||||
|
var testAccProvider *schema.Provider
|
||||||
|
|
||||||
|
const (
|
||||||
|
testAccSecurityGroupName = "terraform-security-group"
|
||||||
|
testAccHostedServiceName = "terraform-testing-service"
|
||||||
|
)
|
||||||
|
|
||||||
|
// testAccStorageServiceName is used as the name for the Storage Service
|
||||||
|
// created in all storage-related tests.
|
||||||
|
// It is much more convenient to provide a Storage Service which
|
||||||
|
// has been created beforehand as the creation of one takes a lot
|
||||||
|
// and would greatly impede the multitude of tests which rely on one.
|
||||||
|
// NOTE: the storage container should be located in `West US`.
|
||||||
|
var testAccStorageServiceName = os.Getenv("AZURE_STORAGE")
|
||||||
|
|
||||||
|
const testAccStorageContainerName = "terraform-testing-container"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testAccProvider = Provider().(*schema.Provider)
|
||||||
|
testAccProviders = map[string]terraform.ResourceProvider{
|
||||||
|
"azure": testAccProvider,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider(t *testing.T) {
|
||||||
|
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProvider_impl(t *testing.T) {
|
||||||
|
var _ terraform.ResourceProvider = Provider()
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccPreCheck(t *testing.T) {
|
||||||
|
if v := os.Getenv("AZURE_PUBLISH_SETTINGS"); v == "" {
|
||||||
|
subscriptionID := os.Getenv("AZURE_SUBSCRIPTION_ID")
|
||||||
|
certificate := os.Getenv("AZURE_CERTIFICATE")
|
||||||
|
|
||||||
|
if subscriptionID == "" || certificate == "" {
|
||||||
|
t.Fatal("either AZURE_PUBLISH_SETTINGS, or AZURE_SUBSCRIPTION_ID " +
|
||||||
|
"and AZURE_CERTIFICATE must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := os.Getenv("AZURE_STORAGE"); v == "" {
|
||||||
|
t.Fatal("AZURE_STORAGE must be set for acceptance tests")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAzure_validateSettingsFile(t *testing.T) {
|
||||||
|
f, err := ioutil.TempFile("", "tf-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating temporary file in TestAzure_validateSettingsFile: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
fx, err := ioutil.TempFile("", "tf-test-xml")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating temporary file with XML in TestAzure_validateSettingsFile: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(fx.Name())
|
||||||
|
_, err = io.WriteString(fx, "<PublishData></PublishData>")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error writing XML File: %s", err)
|
||||||
|
}
|
||||||
|
fx.Close()
|
||||||
|
|
||||||
|
home, err := homedir.Dir()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error fetching homedir: %s", err)
|
||||||
|
}
|
||||||
|
fh, err := ioutil.TempFile(home, "tf-test-home")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating homedir-based temporary file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(fh.Name())
|
||||||
|
_, err = io.WriteString(fh, "<PublishData></PublishData>")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error writing XML File: %s", err)
|
||||||
|
}
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
r := strings.NewReplacer(home, "~")
|
||||||
|
homePath := r.Replace(fh.Name())
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
Input string // String of XML or a path to an XML file
|
||||||
|
W int // expected count of warnings
|
||||||
|
E int // expected count of errors
|
||||||
|
}{
|
||||||
|
{"test", 0, 1},
|
||||||
|
{f.Name(), 1, 1},
|
||||||
|
{fx.Name(), 1, 0},
|
||||||
|
{homePath, 1, 0},
|
||||||
|
{"<PublishData></PublishData>", 0, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
w, e := validateSettingsFile(tc.Input, "")
|
||||||
|
|
||||||
|
if len(w) != tc.W {
|
||||||
|
t.Errorf("Error in TestAzureValidateSettingsFile: input: %s , warnings: %v, errors: %v", tc.Input, w, e)
|
||||||
|
}
|
||||||
|
if len(e) != tc.E {
|
||||||
|
t.Errorf("Error in TestAzureValidateSettingsFile: input: %s , warnings: %v, errors: %v", tc.Input, w, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAzure_providerConfigure(t *testing.T) {
|
||||||
|
home, err := homedir.Dir()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error fetching homedir: %s", err)
|
||||||
|
}
|
||||||
|
fh, err := ioutil.TempFile(home, "tf-test-home")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error creating homedir-based temporary file: %s", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(fh.Name())
|
||||||
|
|
||||||
|
_, err = io.WriteString(fh, testAzurePublishSettingsStr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
fh.Close()
|
||||||
|
|
||||||
|
r := strings.NewReplacer(home, "~")
|
||||||
|
homePath := r.Replace(fh.Name())
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
SettingsFile string // String of XML or a path to an XML file
|
||||||
|
NilMeta bool // whether meta is expected to be nil
|
||||||
|
}{
|
||||||
|
{testAzurePublishSettingsStr, false},
|
||||||
|
{homePath, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
rp := Provider()
|
||||||
|
raw := map[string]interface{}{
|
||||||
|
"settings_file": tc.SettingsFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
rawConfig, err := config.NewRawConfig(raw)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rp.Configure(terraform.NewResourceConfig(rawConfig))
|
||||||
|
meta := rp.(*schema.Provider).Meta()
|
||||||
|
if (meta == nil) != tc.NilMeta {
|
||||||
|
t.Fatalf("expected NilMeta: %t, got meta: %#v, settings_file: %q",
|
||||||
|
tc.NilMeta, meta, tc.SettingsFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genRandInt() int {
|
||||||
|
return rand.New(rand.NewSource(time.Now().UnixNano())).Int() % 100000
|
||||||
|
}
|
||||||
|
|
||||||
|
// testAzurePublishSettingsStr is a revoked publishsettings file
|
||||||
|
const testAzurePublishSettingsStr = `
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<PublishData>
|
||||||
|
<PublishProfile
|
||||||
|
SchemaVersion="2.0"
|
||||||
|
PublishMethod="AzureServiceManagementAPI">
|
||||||
|
<Subscription
|
||||||
|
ServiceManagementUrl="https://management.core.windows.net"
|
||||||
|
Id="a65bf94f-26b3-4fb1-9d50-6e27c6096df1"
|
||||||
|
Name="terraform-testing"
|
||||||
|
ManagementCertificate="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" />
|
||||||
|
</PublishProfile>
|
||||||
|
</PublishData>
|
||||||
|
`
|
|
@ -0,0 +1,140 @@
|
||||||
|
package azurerm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/arm/resources"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
// resourceArmResourceGroup returns the *schema.Resource
|
||||||
|
// associated to resource group resources on ARM.
|
||||||
|
func resourceArmResourceGroup() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceArmResourceGroupCreate,
|
||||||
|
Read: resourceArmResourceGroupRead,
|
||||||
|
Exists: resourceArmResourceGroupExists,
|
||||||
|
Delete: resourceArmResourceGroupDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
//TODO(jen20) - implement validation func: {resource-group-name} must uniquely identify the resource group within the subscription. It must be no longer than 80 characters long. It can only contain alphanumeric characters, dash, underscore, opening parenthesis, closing parenthesis or period. The name cannot end with a period.
|
||||||
|
},
|
||||||
|
"location": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
StateFunc: azureRMNormalizeLocation,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceArmResourceGroupCreate goes ahead and creates the specified ARM resource group.
|
||||||
|
func resourceArmResourceGroupCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ArmClient)
|
||||||
|
resGroupClient := client.resourceGroupClient
|
||||||
|
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
location := d.Get("location").(string)
|
||||||
|
|
||||||
|
log.Printf("[INFO] Issuing Azure ARM creation request for resource group '%s'.", name)
|
||||||
|
|
||||||
|
rg := resources.ResourceGroup{
|
||||||
|
Name: &name,
|
||||||
|
Location: &location,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := resGroupClient.CreateOrUpdate(name, rg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error issuing Azure ARM create request for resource group '%s': %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.SetId(*rg.Name)
|
||||||
|
|
||||||
|
// Wait for the resource group to become available
|
||||||
|
// TODO(jen20): Is there any need for this?
|
||||||
|
log.Printf("[DEBUG] Waiting for Resource Group (%s) to become available", d.Id())
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Pending: []string{"Accepted"},
|
||||||
|
Target: "Succeeded",
|
||||||
|
Refresh: resourceGroupStateRefreshFunc(client, d.Id()),
|
||||||
|
Timeout: 10 * time.Minute,
|
||||||
|
}
|
||||||
|
if _, err := stateConf.WaitForState(); err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for Resource Group (%s) to become available: %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceArmResourceGroupRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceArmResourceGroupRead goes ahead and reads the state of the corresponding ARM resource group.
|
||||||
|
func resourceArmResourceGroupRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
resGroupClient := meta.(*ArmClient).resourceGroupClient
|
||||||
|
|
||||||
|
name := d.Id()
|
||||||
|
log.Printf("[INFO] Issuing read request to Azure ARM for resource group '%s'.", name)
|
||||||
|
|
||||||
|
res, err := resGroupClient.Get(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error issuing read request to Azure ARM for resource group '%s': %s", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
d.Set("name", *res.Name)
|
||||||
|
d.Set("location", *res.Location)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceArmResourceGroupExists goes ahead and checks for the existence of the correspoding ARM resource group.
|
||||||
|
func resourceArmResourceGroupExists(d *schema.ResourceData, meta interface{}) (bool, error) {
|
||||||
|
resGroupClient := meta.(*ArmClient).resourceGroupClient
|
||||||
|
|
||||||
|
name := d.Id()
|
||||||
|
|
||||||
|
resp, err := resGroupClient.CheckExistence(name)
|
||||||
|
if err != nil {
|
||||||
|
// TODO(aznashwan): implement some error switching helpers in the SDK
|
||||||
|
// to avoid HTTP error checks such as the below:
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceArmResourceGroupDelete deletes the specified ARM resource group.
|
||||||
|
func resourceArmResourceGroupDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
resGroupClient := meta.(*ArmClient).resourceGroupClient
|
||||||
|
|
||||||
|
name := d.Id()
|
||||||
|
|
||||||
|
_, err := resGroupClient.Delete(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceGroupStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
||||||
|
// a resource group.
|
||||||
|
func resourceGroupStateRefreshFunc(client *ArmClient, id string) resource.StateRefreshFunc {
|
||||||
|
return func() (interface{}, string, error) {
|
||||||
|
res, err := client.resourceGroupClient.Get(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("Error issuing read request in resourceGroupStateRefreshFunc to Azure ARM for resource group '%s': %s", id, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, *res.Properties.ProvisioningState, nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
package azurerm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Azure/azure-sdk-for-go/arm/network"
|
||||||
|
"github.com/hashicorp/terraform/helper/hashcode"
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceArmVirtualNetwork() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceArmVirtualNetworkCreate,
|
||||||
|
Read: resourceArmVirtualNetworkRead,
|
||||||
|
Update: resourceArmVirtualNetworkUpdate,
|
||||||
|
Delete: resourceArmVirtualNetworkDelete,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"address_space": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
},
|
||||||
|
|
||||||
|
"dns_servers_names": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"subnet": &schema.Schema{
|
||||||
|
Type: schema.TypeSet,
|
||||||
|
Required: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"address_prefix": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"security_group": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: resourceAzureSubnetHash,
|
||||||
|
},
|
||||||
|
|
||||||
|
"location": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
StateFunc: azureRMNormalizeLocation,
|
||||||
|
},
|
||||||
|
|
||||||
|
"resource_group_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceArmVirtualNetworkCreate creates the specified ARM virtual network.
|
||||||
|
func resourceArmVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ArmClient)
|
||||||
|
vnetClient := client.vnetClient
|
||||||
|
|
||||||
|
log.Printf("[INFO] preparing arguments for Azure ARM virtual network creation.")
|
||||||
|
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
location := d.Get("location").(string)
|
||||||
|
resGroup := d.Get("resource_group_name").(string)
|
||||||
|
|
||||||
|
vnet := network.VirtualNetwork{
|
||||||
|
Name: &name,
|
||||||
|
Location: &location,
|
||||||
|
Properties: getVirtualNetworkProperties(d),
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("[INFO] Sending virtual network create request to ARM.")
|
||||||
|
_, err := vnetClient.CreateOrUpdate(resGroup, name, vnet)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if res.Response.StatusCode != http.StatusAccepted {
|
||||||
|
// return fmt.Errorf("Creation request was denies: code: %d", res.Response.StatusCode)
|
||||||
|
// }
|
||||||
|
|
||||||
|
d.SetId(name)
|
||||||
|
d.Set("resGroup", resGroup)
|
||||||
|
|
||||||
|
// Wait for the resource group to become available
|
||||||
|
// TODO(jen20): Is there any need for this?
|
||||||
|
log.Printf("[DEBUG] Waiting for Virtual Network (%s) to become available", d.Id())
|
||||||
|
stateConf := &resource.StateChangeConf{
|
||||||
|
Pending: []string{"Accepted", "Updating"},
|
||||||
|
Target: "Succeeded",
|
||||||
|
Refresh: virtualNetworkStateRefreshFunc(client, resGroup, name),
|
||||||
|
Timeout: 10 * time.Minute,
|
||||||
|
}
|
||||||
|
if _, err := stateConf.WaitForState(); err != nil {
|
||||||
|
return fmt.Errorf("Error waiting for Virtual Network (%s) to become available: %s", d.Id(), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceArmVirtualNetworkRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceArmVirtualNetworkRead goes ahead and reads the state of the corresponding ARM virtual network.
|
||||||
|
func resourceArmVirtualNetworkRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
vnetClient := meta.(*ArmClient).vnetClient
|
||||||
|
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
resGroup := d.Get("resource_group_name").(string)
|
||||||
|
|
||||||
|
log.Printf("[INFO] Sending virtual network read request to ARM.")
|
||||||
|
|
||||||
|
resp, err := vnetClient.Get(resGroup, name)
|
||||||
|
if resp.StatusCode == http.StatusNotFound {
|
||||||
|
// it means the virtual network has been deleted in the meantime;
|
||||||
|
// so we must go ahead and remove it here:
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error making Read request on Azure virtual network %s: %s", name, err)
|
||||||
|
}
|
||||||
|
vnet := *resp.Properties
|
||||||
|
|
||||||
|
// update all the appropriate values:
|
||||||
|
d.Set("address_space", vnet.AddressSpace.AddressPrefixes)
|
||||||
|
|
||||||
|
// read state of subnets:
|
||||||
|
subnets := &schema.Set{
|
||||||
|
F: resourceAzureSubnetHash,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, subnet := range *vnet.Subnets {
|
||||||
|
s := map[string]interface{}{}
|
||||||
|
|
||||||
|
s["name"] = *subnet.Name
|
||||||
|
s["address_prefix"] = *subnet.Properties.AddressPrefix
|
||||||
|
// NOTE(aznashwan): ID's necessary?
|
||||||
|
if subnet.Properties.NetworkSecurityGroup != nil {
|
||||||
|
s["security_group"] = *subnet.Properties.NetworkSecurityGroup.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
subnets.Add(s)
|
||||||
|
}
|
||||||
|
d.Set("subnet", subnets)
|
||||||
|
|
||||||
|
// now; dns servers:
|
||||||
|
dnses := []string{}
|
||||||
|
for _, dns := range *vnet.DhcpOptions.DNSServers {
|
||||||
|
dnses = append(dnses, dns)
|
||||||
|
}
|
||||||
|
d.Set("dns_servers_names", dnses)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceArmVirtualNetworkUpdate goes ahead and updates the corresponding ARM virtual network.
|
||||||
|
func resourceArmVirtualNetworkUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
// considering Create's idempotency, Update is simply a proxy for it...
|
||||||
|
// Update has been left as a separate function here for utmost clarity:
|
||||||
|
return resourceArmVirtualNetworkCreate(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resourceArmVirtualNetworkDelete deletes the specified ARM virtual network.
|
||||||
|
func resourceArmVirtualNetworkDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
vnetClient := meta.(*ArmClient).vnetClient
|
||||||
|
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
resGroup := d.Get("resource_group_name").(string)
|
||||||
|
_, err := vnetClient.Delete(resGroup, name)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVirtualNetworkProperties is a helper function which returns the
|
||||||
|
// VirtualNetworkPropertiesFormat of the network resource.
|
||||||
|
func getVirtualNetworkProperties(d *schema.ResourceData) *network.VirtualNetworkPropertiesFormat {
|
||||||
|
// first; get address space prefixes:
|
||||||
|
prefixes := []string{}
|
||||||
|
for _, prefix := range d.Get("address_space").([]interface{}) {
|
||||||
|
prefixes = append(prefixes, prefix.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
// then; the dns servers:
|
||||||
|
dnses := []string{}
|
||||||
|
for _, dns := range d.Get("dns_servers_names").([]interface{}) {
|
||||||
|
dnses = append(dnses, dns.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
// then; the subnets:
|
||||||
|
subnets := []network.Subnet{}
|
||||||
|
if subs := d.Get("subnet").(*schema.Set); subs.Len() > 0 {
|
||||||
|
for _, subnet := range subs.List() {
|
||||||
|
subnet := subnet.(map[string]interface{})
|
||||||
|
|
||||||
|
name := subnet["name"].(string)
|
||||||
|
prefix := subnet["address_prefix"].(string)
|
||||||
|
secGroup := subnet["security_group"].(string)
|
||||||
|
|
||||||
|
var subnetObj network.Subnet
|
||||||
|
subnetObj.Name = &name
|
||||||
|
subnetObj.Properties = &network.SubnetPropertiesFormat{}
|
||||||
|
subnetObj.Properties.AddressPrefix = &prefix
|
||||||
|
|
||||||
|
if secGroup != "" {
|
||||||
|
subnetObj.Properties.NetworkSecurityGroup = &network.SubResource{
|
||||||
|
ID: &secGroup,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subnets = append(subnets, subnetObj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally; return the struct:
|
||||||
|
return &network.VirtualNetworkPropertiesFormat{
|
||||||
|
AddressSpace: &network.AddressSpace{
|
||||||
|
AddressPrefixes: &prefixes,
|
||||||
|
},
|
||||||
|
DhcpOptions: &network.DhcpOptions{
|
||||||
|
DNSServers: &dnses,
|
||||||
|
},
|
||||||
|
Subnets: &subnets,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceAzureSubnetHash(v interface{}) int {
|
||||||
|
m := v.(map[string]interface{})
|
||||||
|
subnet := m["name"].(string) + m["address_prefix"].(string)
|
||||||
|
if securityGroup, present := m["security_group"]; present {
|
||||||
|
subnet = subnet + securityGroup.(string)
|
||||||
|
}
|
||||||
|
return hashcode.String(subnet)
|
||||||
|
}
|
||||||
|
|
||||||
|
// virtualNetworkStateRefreshFunc returns a resource.StateRefreshFunc that is used to watch
|
||||||
|
// a virtual network.
|
||||||
|
func virtualNetworkStateRefreshFunc(client *ArmClient, resourceGroupName string, networkName string) resource.StateRefreshFunc {
|
||||||
|
return func() (interface{}, string, error) {
|
||||||
|
res, err := client.vnetClient.Get(resourceGroupName, networkName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("Error issuing read request in virtualNetworkStateRefreshFunc to Azure ARM for virtual network '%s' (RG: '%s'): %s", networkName, resourceGroupName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, *res.Properties.ProvisioningState, nil
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue