provider/azurerm: Clean up work for base provider

- Add documentation for resources
- Rename files to match standard patterns
- Add acceptance tests for resource groups
- Add acceptance tests for vnets
- Remove ARM_CREDENTIALS file - as discussed this does not appear to be
  an Azure standard, and there is scope for confusion with the
  azureProfile.json file which the CLI generates. If a standard emerges
  we can reconsider this.
- Validate credentials in the schema
- Remove storage testing artefacts
- Use ARM IDs as Terraform IDs
- Use autorest hooks for logging
This commit is contained in:
James Nugent 2015-12-08 20:25:05 -05:00
parent 63bc8e9852
commit 805c4896bd
17 changed files with 696 additions and 195 deletions

View File

@ -1 +0,0 @@
package main

View File

@ -1,8 +1,8 @@
package azurerm
import (
"encoding/json"
"fmt"
"log"
"net/http"
"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"
@ -10,7 +10,7 @@ import (
"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"
"github.com/Azure/go-autorest/autorest"
)
// ArmClient contains the handles to all the specific Azure Resource Manager
@ -46,14 +46,20 @@ type ArmClient struct {
storageUsageClient storage.UsageOperationsClient
}
func withRequestLogging() autorest.SendDecorator {
return func(s autorest.Sender) autorest.Sender {
return autorest.SenderFunc(func(r *http.Request) (*http.Response, error) {
log.Printf("[DEBUG] Sending Azure RM Request %s to %s\n", r.Method, r.URL)
resp, err := s.Do(r)
log.Printf("[DEBUG] Received Azure RM Request status code %s for %s\n", resp.Status, r.URL)
return resp, err
})
}
}
// 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
@ -66,149 +72,118 @@ func (c *Config) getArmClient() (*ArmClient, error) {
// clients be wished to be configured with custom Responders/PollingModess etc...
asc := compute.NewAvailabilitySetsClient(c.SubscriptionID)
asc.Authorizer = spt
asc.Sender = autorest.CreateSender(withRequestLogging())
client.availSetClient = asc
uoc := compute.NewUsageOperationsClient(c.SubscriptionID)
uoc.Authorizer = spt
uoc.Sender = autorest.CreateSender(withRequestLogging())
client.usageOpsClient = uoc
vmeic := compute.NewVirtualMachineExtensionImagesClient(c.SubscriptionID)
vmeic.Authorizer = spt
vmeic.Sender = autorest.CreateSender(withRequestLogging())
client.vmExtensionImageClient = vmeic
vmec := compute.NewVirtualMachineExtensionsClient(c.SubscriptionID)
vmec.Authorizer = spt
vmec.Sender = autorest.CreateSender(withRequestLogging())
client.vmExtensionClient = vmec
vmic := compute.NewVirtualMachineImagesClient(c.SubscriptionID)
vmic.Authorizer = spt
vmic.Sender = autorest.CreateSender(withRequestLogging())
client.vmImageClient = vmic
vmc := compute.NewVirtualMachinesClient(c.SubscriptionID)
vmc.Authorizer = spt
vmc.Sender = autorest.CreateSender(withRequestLogging())
client.vmClient = vmc
agc := network.NewApplicationGatewaysClient(c.SubscriptionID)
agc.Authorizer = spt
agc.Sender = autorest.CreateSender(withRequestLogging())
client.appGatewayClient = agc
ifc := network.NewInterfacesClient(c.SubscriptionID)
ifc.Authorizer = spt
ifc.Sender = autorest.CreateSender(withRequestLogging())
client.ifaceClient = ifc
lbc := network.NewLoadBalancersClient(c.SubscriptionID)
lbc.Authorizer = spt
lbc.Sender = autorest.CreateSender(withRequestLogging())
client.loadBalancerClient = lbc
lgc := network.NewLocalNetworkGatewaysClient(c.SubscriptionID)
lgc.Authorizer = spt
lgc.Sender = autorest.CreateSender(withRequestLogging())
client.localNetConnClient = lgc
pipc := network.NewPublicIPAddressesClient(c.SubscriptionID)
pipc.Authorizer = spt
pipc.Sender = autorest.CreateSender(withRequestLogging())
client.publicIPClient = pipc
sgc := network.NewSecurityGroupsClient(c.SubscriptionID)
sgc.Authorizer = spt
sgc.Sender = autorest.CreateSender(withRequestLogging())
client.secGroupClient = sgc
src := network.NewSecurityRulesClient(c.SubscriptionID)
src.Authorizer = spt
src.Sender = autorest.CreateSender(withRequestLogging())
client.secRuleClient = src
snc := network.NewSubnetsClient(c.SubscriptionID)
snc.Authorizer = spt
snc.Sender = autorest.CreateSender(withRequestLogging())
client.subnetClient = snc
vgcc := network.NewVirtualNetworkGatewayConnectionsClient(c.SubscriptionID)
vgcc.Authorizer = spt
vgcc.Sender = autorest.CreateSender(withRequestLogging())
client.vnetGatewayConnectionsClient = vgcc
vgc := network.NewVirtualNetworkGatewaysClient(c.SubscriptionID)
vgc.Authorizer = spt
vgc.Sender = autorest.CreateSender(withRequestLogging())
client.vnetGatewayClient = vgc
vnc := network.NewVirtualNetworksClient(c.SubscriptionID)
vnc.Authorizer = spt
vnc.Sender = autorest.CreateSender(withRequestLogging())
client.vnetClient = vnc
rgc := resources.NewGroupsClient(c.SubscriptionID)
rgc.Authorizer = spt
rgc.Sender = autorest.CreateSender(withRequestLogging())
client.resourceGroupClient = rgc
tc := resources.NewTagsClient(c.SubscriptionID)
tc.Authorizer = spt
tc.Sender = autorest.CreateSender(withRequestLogging())
client.tagsClient = tc
jc := scheduler.NewJobsClient(c.SubscriptionID)
jc.Authorizer = spt
jc.Sender = autorest.CreateSender(withRequestLogging())
client.jobsClient = jc
jcc := scheduler.NewJobCollectionsClient(c.SubscriptionID)
jcc.Authorizer = spt
jcc.Sender = autorest.CreateSender(withRequestLogging())
client.jobsCollectionsClient = jcc
ssc := storage.NewAccountsClient(c.SubscriptionID)
ssc.Authorizer = spt
ssc.Sender = autorest.CreateSender(withRequestLogging())
client.storageServiceClient = ssc
suc := storage.NewUsageOperationsClient(c.SubscriptionID)
suc.Authorizer = spt
suc.Sender = autorest.CreateSender(withRequestLogging())
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
}
// 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, _, err := pathorcontents.Read(v.(string))
if err != nil {
es = append(es, fmt.Errorf("Error reading 'arm_config_file': %s", err))
}
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"`
}

View File

@ -1,7 +1,6 @@
package azurerm
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/helper/schema"
@ -12,35 +11,27 @@ import (
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,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""),
},
"client_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
},
"client_secret": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
},
"tenant_id": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_TENANT_ID", ""),
},
},
@ -59,19 +50,12 @@ func Provider() terraform.ResourceProvider {
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),
@ -80,20 +64,6 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
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

View File

@ -11,21 +11,6 @@ import (
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{
@ -44,19 +29,12 @@ func TestProvider_impl(t *testing.T) {
}
func testAccPreCheck(t *testing.T) {
if v := os.Getenv("ARM_CREDENTIALS_FILE"); v == "" {
subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID")
clientID := os.Getenv("ARM_CLIENT_ID")
clientSecret := os.Getenv("ARM_CLIENT_SECRET")
tenantID := os.Getenv("ARM_TENANT_ID")
subscriptionID := os.Getenv("ARM_SUBSCRIPTION_ID")
clientID := os.Getenv("ARM_CLIENT_ID")
clientSecret := os.Getenv("ARM_CLIENT_SECRET")
tenantID := os.Getenv("ARM_TENANT_ID")
if subscriptionID == "" || clientID == "" || clientSecret == "" || tenantID == "" {
t.Fatal("Either ARM_CREDENTIALS_FILE or ARM_SUBSCRIPTION_ID, ARM_CLIENT_ID, " +
"ARM_CLIENT_SECRET and ARM_TENANT_ID must be set for acceptance tests")
}
}
if v := os.Getenv("AZURE_STORAGE"); v == "" {
t.Fatal("AZURE_STORAGE must be set for acceptance tests")
if subscriptionID == "" || clientID == "" || clientSecret == "" || tenantID == "" {
t.Fatal("ARM_SUBSCRIPTION_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET and ARM_TENANT_ID must be set for acceptance tests")
}
}

View File

@ -3,6 +3,7 @@ package azurerm
import (
"fmt"
"log"
"net/http"
"regexp"
"strings"
"time"
@ -12,8 +13,6 @@ import (
"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,
@ -38,8 +37,6 @@ func resourceArmResourceGroup() *schema.Resource {
}
}
// validateArmResourceGroupName validates inputs to the name argument against the requirements
// documented in the ARM REST API guide: http://bit.ly/1NEXclG
func validateArmResourceGroupName(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
@ -51,14 +48,13 @@ func validateArmResourceGroupName(v interface{}, k string) (ws []string, es []er
es = append(es, fmt.Errorf("%q may not end with a period", k))
}
if matched := regexp.MustCompile(`^[\(\)\.a-zA-Z0-9_-]$`).Match([]byte(value)); !matched {
if matched := regexp.MustCompile(`[\(\)\.a-zA-Z0-9_-]`).Match([]byte(value)); !matched {
es = append(es, fmt.Errorf("%q may only contain alphanumeric characters, dash, underscores, parentheses and periods", k))
}
return
}
// resourceArmResourceGroupCreate goes ahead and creates the specified ARM resource group.
func resourceArmResourceGroupCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient)
resGroupClient := client.resourceGroupClient
@ -66,64 +62,67 @@ func resourceArmResourceGroupCreate(d *schema.ResourceData, meta interface{}) er
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)
resp, 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)
d.SetId(*resp.ID)
// 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())
log.Printf("[DEBUG] Waiting for Resource Group (%s) to become available", name)
stateConf := &resource.StateChangeConf{
Pending: []string{"Accepted"},
Target: "Succeeded",
Refresh: resourceGroupStateRefreshFunc(client, d.Id()),
Refresh: resourceGroupStateRefreshFunc(client, name),
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 fmt.Errorf("Error waiting for Resource Group (%s) to become available: %s", name, 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)
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
name := id.ResourceGroup
res, err := resGroupClient.Get(name)
if err != nil {
if res.StatusCode == http.StatusNotFound {
d.SetId("")
return 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)
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()
id, err := parseAzureResourceID(d.Id())
if err != nil {
return false, err
}
name := id.ResourceGroup
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
}
@ -134,13 +133,16 @@ func resourceArmResourceGroupExists(d *schema.ResourceData, meta interface{}) (b
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()
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
name := id.ResourceGroup
_, err := resGroupClient.Delete(name)
_, err = resGroupClient.Delete(name)
if err != nil {
return err
}
@ -148,8 +150,6 @@ func resourceArmResourceGroupDelete(d *schema.ResourceData, meta interface{}) er
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)

View File

@ -0,0 +1,82 @@
package azurerm
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/core/http"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureRMResourceGroup_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMResourceGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureRMResourceGroup_basic,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMResourceGroupExists("azurerm_resource_group.test"),
),
},
},
})
}
func testCheckAzureRMResourceGroupExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
// Ensure we have enough information in state to look up in API
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
resourceGroup := rs.Primary.Attributes["name"]
// Ensure resource group exists in API
conn := testAccProvider.Meta().(*ArmClient).resourceGroupClient
resp, err := conn.Get(resourceGroup)
if err != nil {
return fmt.Errorf("Bad: Get on resourceGroupClient: %s", err)
}
if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("Bad: Virtual Network %q (resource group: %q) does not exist", name, resourceGroup)
}
return nil
}
}
func testCheckAzureRMResourceGroupDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*ArmClient).resourceGroupClient
for _, rs := range s.RootModule().Resources {
if rs.Type != "azurerm_resource_group" {
continue
}
resourceGroup := rs.Primary.ID
resp, err := conn.Get(resourceGroup)
if err != nil {
return nil
}
if resp.StatusCode != http.StatusNotFound {
return fmt.Errorf("Resource Group still exists:\n%#v", resp.Properties)
}
}
return nil
}
var testAccAzureRMResourceGroup_basic = `
resource "azurerm_resource_group" "test" {
name = "acceptanceTestResourceGroup1_basic"
location = "West US"
}
`

View File

@ -16,7 +16,7 @@ func resourceArmVirtualNetwork() *schema.Resource {
return &schema.Resource{
Create: resourceArmVirtualNetworkCreate,
Read: resourceArmVirtualNetworkRead,
Update: resourceArmVirtualNetworkUpdate,
Update: resourceArmVirtualNetworkCreate,
Delete: resourceArmVirtualNetworkDelete,
Schema: map[string]*schema.Schema{
@ -78,7 +78,6 @@ func resourceArmVirtualNetwork() *schema.Resource {
}
}
// resourceArmVirtualNetworkCreate creates the specified ARM virtual network.
func resourceArmVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient)
vnetClient := client.vnetClient
@ -95,22 +94,14 @@ func resourceArmVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) e
Properties: getVirtualNetworkProperties(d),
}
log.Printf("[INFO] Sending virtual network create request to ARM.")
_, err := vnetClient.CreateOrUpdate(resGroup, name, vnet)
resp, 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(*resp.ID)
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())
log.Printf("[DEBUG] Waiting for Virtual Network (%s) to become available", name)
stateConf := &resource.StateChangeConf{
Pending: []string{"Accepted", "Updating"},
Target: "Succeeded",
@ -118,25 +109,24 @@ func resourceArmVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) e
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 fmt.Errorf("Error waiting for Virtual Network (%s) to become available: %s", name, 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.")
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
resGroup := id.ResourceGroup
name := id.Path["virtualNetworks"]
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
}
@ -145,10 +135,9 @@ func resourceArmVirtualNetworkRead(d *schema.ResourceData, meta interface{}) err
}
vnet := *resp.Properties
// update all the appropriate values:
// update appropriate values
d.Set("address_space", vnet.AddressSpace.AddressPrefixes)
// read state of subnets:
subnets := &schema.Set{
F: resourceAzureSubnetHash,
}
@ -166,7 +155,6 @@ func resourceArmVirtualNetworkRead(d *schema.ResourceData, meta interface{}) err
}
d.Set("subnet", subnets)
// now; dns servers:
dnses := []string{}
for _, dns := range *vnet.DhcpOptions.DNSServers {
dnses = append(dnses, dns)
@ -176,26 +164,21 @@ func resourceArmVirtualNetworkRead(d *schema.ResourceData, meta interface{}) err
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)
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
resGroup := id.ResourceGroup
name := id.Path["virtualNetworks"]
_, 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{}
@ -255,8 +238,6 @@ func resourceAzureSubnetHash(v interface{}) int {
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)

View File

@ -0,0 +1,100 @@
package azurerm
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/core/http"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureRMVirtualNetwork_basic(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMVirtualNetworkDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureRMVirtualNetwork_basic,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMVirtualNetworkExists("azurerm_virtual_network.test"),
),
},
},
})
}
func testCheckAzureRMVirtualNetworkExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
// Ensure we have enough information in state to look up in API
rs, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Not found: %s", name)
}
virtualNetworkName := rs.Primary.Attributes["name"]
resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"]
if !hasResourceGroup {
return fmt.Errorf("Bad: no resource group found in state for virtual network: %s", virtualNetworkName)
}
// Ensure resource group/virtual network combination exists in API
conn := testAccProvider.Meta().(*ArmClient).vnetClient
resp, err := conn.Get(resourceGroup, virtualNetworkName)
if err != nil {
return fmt.Errorf("Bad: Get on vnetClient: %s", err)
}
if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("Bad: Virtual Network %q (resource group: %q) does not exist", name, resourceGroup)
}
return nil
}
}
func testCheckAzureRMVirtualNetworkDestroy(s *terraform.State) error {
conn := testAccProvider.Meta().(*ArmClient).vnetClient
for _, rs := range s.RootModule().Resources {
if rs.Type != "azurerm_virtual_network" {
continue
}
name := rs.Primary.Attributes["name"]
resourceGroup := rs.Primary.Attributes["resource_group_name"]
resp, err := conn.Get(resourceGroup, name)
if err != nil {
return nil
}
if resp.StatusCode != http.StatusNotFound {
return fmt.Errorf("Virtual Network sitll exists:\n%#v", resp.Properties)
}
}
return nil
}
var testAccAzureRMVirtualNetwork_basic = `
resource "azurerm_resource_group" "test" {
name = "acceptanceTestResourceGroup1"
location = "West US"
}
resource "azurerm_virtual_network" "test" {
name = "acceptanceTestVirtualNetwork1"
address_space = ["10.0.0.0/16"]
location = "West US"
resource_group_name = "${azurerm_resource_group.test.name}"
subnet {
name = "subnet1"
address_prefix = "10.0.1.0/24"
}
}
`

View File

@ -0,0 +1,82 @@
package azurerm
import (
"fmt"
"net/url"
"strings"
)
// ResourceID represents a parsed long-form Azure Resource Manager ID
// with the Subscription ID, Resource Group and the Provider as top-
// level fields, and other key-value pairs available via a map in the
// Path field.
type ResourceID struct {
SubscriptionID string
ResourceGroup string
Provider string
Path map[string]string
}
// parseAzureResourceID converts a long-form Azure Resource Manager ID
// into a ResourceID. We make assumptions about the structure of URLs,
// which is obviously not good, but the best thing available given the
// SDK.
func parseAzureResourceID(id string) (*ResourceID, error) {
idURL, err := url.ParseRequestURI(id)
if err != nil {
return nil, fmt.Errorf("Cannot parse Azure Id: %s", err)
}
path := idURL.Path
path = strings.TrimSpace(path)
if strings.HasPrefix(path, "/") {
path = path[1:]
}
if strings.HasSuffix(path, "/") {
path = path[:len(path)-1]
}
components := strings.Split(path, "/")
// We should have an even number of key-value pairs.
if len(components)%2 != 0 {
return nil, fmt.Errorf("The number of path segments is not divisible by 2 in %q", path)
}
// Put the constituent key-value pairs into a map
componentMap := make(map[string]string, len(components)/2)
for current := 0; current < len(components); current += 2 {
key := components[current]
value := components[current+1]
componentMap[key] = value
}
// Build up a ResourceID from the map
idObj := &ResourceID{}
idObj.Path = componentMap
if subscription, ok := componentMap["subscriptions"]; ok {
idObj.SubscriptionID = subscription
delete(componentMap, "subscriptions")
} else {
return nil, fmt.Errorf("No subscription ID found in: %q", path)
}
if resourceGroup, ok := componentMap["resourceGroups"]; ok {
idObj.ResourceGroup = resourceGroup
delete(componentMap, "resourceGroups")
} else {
return nil, fmt.Errorf("No resource group name found in: %q", path)
}
// It is OK not to have a provider in the case of a resource group
if provider, ok := componentMap["providers"]; ok {
idObj.Provider = provider
delete(componentMap, "providers")
}
return idObj, nil
}

View File

@ -0,0 +1,107 @@
package azurerm
import (
"reflect"
"testing"
)
func TestParseAzureResourceID(t *testing.T) {
testCases := []struct {
id string
expectedResourceID *ResourceID
expectError bool
}{
{
"random",
nil,
true,
},
{
"/subscriptions/6d74bdd2-9f84-11e5-9bd9-7831c1c4c038",
nil,
true,
},
{
"subscriptions/6d74bdd2-9f84-11e5-9bd9-7831c1c4c038",
nil,
true,
},
{
"/subscriptions/6d74bdd2-9f84-11e5-9bd9-7831c1c4c038/resourceGroups/testGroup1",
&ResourceID{
SubscriptionID: "6d74bdd2-9f84-11e5-9bd9-7831c1c4c038",
ResourceGroup: "testGroup1",
Provider: "",
Path: map[string]string{},
},
false,
},
{
"/subscriptions/6d74bdd2-9f84-11e5-9bd9-7831c1c4c038/resourceGroups/testGroup1/providers/Microsoft.Network",
&ResourceID{
SubscriptionID: "6d74bdd2-9f84-11e5-9bd9-7831c1c4c038",
ResourceGroup: "testGroup1",
Provider: "Microsoft.Network",
Path: map[string]string{},
},
false,
},
{
// Missing leading /
"subscriptions/6d74bdd2-9f84-11e5-9bd9-7831c1c4c038/resourceGroups/testGroup1/providers/Microsoft.Network/virtualNetworks/virtualNetwork1/",
nil,
true,
},
{
"/subscriptions/6d74bdd2-9f84-11e5-9bd9-7831c1c4c038/resourceGroups/testGroup1/providers/Microsoft.Network/virtualNetworks/virtualNetwork1",
&ResourceID{
SubscriptionID: "6d74bdd2-9f84-11e5-9bd9-7831c1c4c038",
ResourceGroup: "testGroup1",
Provider: "Microsoft.Network",
Path: map[string]string{
"virtualNetworks": "virtualNetwork1",
},
},
false,
},
{
"/subscriptions/6d74bdd2-9f84-11e5-9bd9-7831c1c4c038/resourceGroups/testGroup1/providers/Microsoft.Network/virtualNetworks/virtualNetwork1?api-version=2006-01-02-preview",
&ResourceID{
SubscriptionID: "6d74bdd2-9f84-11e5-9bd9-7831c1c4c038",
ResourceGroup: "testGroup1",
Provider: "Microsoft.Network",
Path: map[string]string{
"virtualNetworks": "virtualNetwork1",
},
},
false,
},
{
"/subscriptions/6d74bdd2-9f84-11e5-9bd9-7831c1c4c038/resourceGroups/testGroup1/providers/Microsoft.Network/virtualNetworks/virtualNetwork1/subnets/publicInstances1?api-version=2006-01-02-preview",
&ResourceID{
SubscriptionID: "6d74bdd2-9f84-11e5-9bd9-7831c1c4c038",
ResourceGroup: "testGroup1",
Provider: "Microsoft.Network",
Path: map[string]string{
"virtualNetworks": "virtualNetwork1",
"subnets": "publicInstances1",
},
},
false,
},
}
for _, test := range testCases {
parsed, err := parseAzureResourceID(test.id)
if test.expectError && err != nil {
continue
}
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
if !reflect.DeepEqual(test.expectedResourceID, parsed) {
t.Fatalf("Unexpected resource ID:\nExpected: %+v\nGot: %+v\n", test.expectedResourceID, parsed)
}
}
}

2
website/Vagrantfile vendored
View File

@ -28,7 +28,7 @@ bundle exec middleman server &
SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "chef/ubuntu-12.04"
config.vm.box = "bento/ubuntu-12.04"
config.vm.network "private_network", ip: "33.33.30.10"
config.vm.provision "shell", inline: $script, privileged: false
config.vm.synced_folder ".", "/vagrant", type: "rsync"

View File

@ -10,6 +10,7 @@ body.layout-atlas,
body.layout-aws,
body.layout-azure,
body.layout-chef,
body.layout-azurerm,
body.layout-cloudflare,
body.layout-cloudstack,
body.layout-consul,

View File

@ -0,0 +1,80 @@
---
layout: "azurerm"
page_title: "Provider: Azure Resource Manager"
sidebar_current: "docs-azurerm-index"
description: |-
The Azure Resource Manager provider is used to interact with the many resources supported by Azure, via the ARM API. This supercedes the Azure provider, which interacts with Azure using the Service Management API. The provider needs to be configured with a credentials file, or credentials needed to generate OAuth tokens for the ARM API.
---
# Azure Resource Manager Provider
The Azure Resource Manager provider is used to interact with the many resources
supported by Azure, via the ARM API. This supercedes the Azure provider, which
interacts with Azure using the Service Management API. The provider needs to be
configured with the credentials needed to generate OAuth tokens for the ARM API.
Use the navigation to the left to read about the available resources.
## Example Usage
```
# Configure the Azure Resource Manager Provider
provider "azurerm" {
subscription_id = "..."
client_id = "..."
client_secret = "..."
tenant_id = "..."
}
# Create a resource group
resource "azurerm_resource_group" "production" {
name = "production"
location = "West US"
}
# Create a virtual network in the web_servers resource group
resource "azurerm_virtual_network" "network" {
name = "productionNetwork"
address_space = ["10.0.0.0/16"]
location = "West US"
resource_group_name = "${azurerm_resource_group.production.name}"
subnet {
name = "subnet1"
address_prefix = "10.0.1.0/24"
}
subnet {
name = "subnet2"
address_prefix = "10.0.2.0/24"
}
subnet {
name = "subnet3"
address_prefix = "10.0.3.0/24"
}
}
```
## Argument Reference
The following arguments are supported:
* `subscription_id` - (Optional) The subscription ID to use. It can also
be sourced from the `ARM_SUBSCRIPTION_ID` environment variable.
* `client_id` - (Optional) The client ID to use. It can also be sourced from
the `ARM_CLIENT_ID` environment variable.
* `client_secret` - (Optional) The client secret to use. It can also be sourced from
the `ARM_CLIENT_SECRET` environment variable.
* `tenant_id` - (Optional) The tenant ID to use. It can also be sourced from the
`ARM_TENANT_ID` environment variable.
## Testing:
Credentials must be provided via the `ARM_SUBSCRIPTION_ID`, `ARM_CLIENT_ID`,
`ARM_CLIENT_SECRET` and `ARM_TENANT_ID` environment variables in order to run
acceptance tests.

View File

@ -0,0 +1,36 @@
---
layout: "azurerm"
page_title: "Azure Resource Manager: azurerm_resource_group"
sidebar_current: "docs-azurerm-resource-resource-group"
description: |-
Creates a new resource group on Azure.
---
# azurerm\_resource\_group
Creates a new resource group on Azure.
## Example Usage
```
resource "azurerm_resource_group" "test" {
name = "testResourceGroup1"
location = "West US"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the resource group. Must be unique on your
Azure subscription.
* `location` - (Required) The location where the resource group should be created.
For a list of all Azure locations, please consult [this link](http://azure.microsoft.com/en-us/regions/).
## Attributes Reference
The following attributes are exported:
* `id` - The resource group ID.

View File

@ -0,0 +1,76 @@
---
layout: "azurerm"
page_title: "Azure Resource Manager: azure_virtual_network"
sidebar_current: "docs-azurerm-resource-virtual-network"
description: |-
Creates a new virtual network including any configured subnets. Each subnet can optionally be configured with a security group to be associated with the subnet.
---
# azurerm\_virtual\_network
Creates a new virtual network including any configured subnets. Each subnet can
optionally be configured with a security group to be associated with the subnet.
## Example Usage
```
resource "azurerm_virtual_network" "test" {
name = "virtualNetwork1"
resource_group_name = "${azurerm_resource_group.test.name}"
address_space = ["10.0.0.0/16"]
location = "West US"
subnet {
name = "subnet1"
address_prefix = "10.0.1.0/24"
}
subnet {
name = "subnet2"
address_prefix = "10.0.2.0/24"
}
subnet {
name = "subnet3"
address_prefix = "10.0.3.0/24"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the virtual network. Changing this forces a
new resource to be created.
* `resource_group_name` - (Required) The name of the resource group in which to
create the virtual network.
* `address_space` - (Required) The address space that is used the virtual
network. You can supply more than one address space. Changing this forces
a new resource to be created.
* `location` - (Required) The location/region where the virtual network is
created. Changing this forces a new resource to be created.
* `dns_servers` - (Optional) List of names of DNS servers previously registered
on Azure.
* `subnet` - (Required) Can be specified multiple times to define multiple
subnets. Each `subnet` block supports fields documented below.
The `subnet` block supports:
* `name` - (Required) The name of the subnet.
* `address_prefix` - (Required) The address prefix to use for the subnet.
* `security_group` - (Optional) The Network Security Group to associate with
the subnet.
## Attributes Reference
The following attributes are exported:
* `id` - The virtual NetworkConfiguration ID.

View File

@ -0,0 +1,30 @@
<% wrap_layout :inner do %>
<% content_for :sidebar do %>
<div class="docs-sidebar hidden-print affix-top" role="complementary">
<ul class="nav docs-sidenav">
<li<%= sidebar_current("docs-home") %>>
<a href="/docs/providers/index.html">&laquo; Documentation Home</a>
</li>
<li<%= sidebar_current("docs-azurerm-index") %>>
<a href="/docs/providers/azurerm/index.html">Azure Resource Manager Provider</a>
</li>
<li<%= sidebar_current(/^docs-azurerm-resource/) %>>
<a href="#">Resources</a>
<ul class="nav nav-visible">
<li<%= sidebar_current("docs-azure-resource-resource-group") %>>
<a href="/docs/providers/azurerm/r/resource_group.html">azurerm_resource_group</a>
</li>
<li<%= sidebar_current("docs-azure-resource-virtual-network") %>>
<a href="/docs/providers/azurerm/r/virtual_network.html">azurerm_virtual_network</a>
</li>
</ul>
</li>
</ul>
</div>
<% end %>
<%= yield %>
<% end %>

View File

@ -130,7 +130,11 @@
</li>
<li<%= sidebar_current("docs-providers-azure") %>>
<a href="/docs/providers/azure/index.html">Azure</a>
<a href="/docs/providers/azure/index.html">Azure (Service Management)</a>
</li>
<li<%= sidebar_current("docs-providers-azurerm") %>>
<a href="/docs/providers/azurerm/index.html">Azure (Resource Manager)</a>
</li>
<li<%= sidebar_current("docs-providers-chef") %>>