Merge pull request #4226 from hashicorp/f-azure-arm

Preliminary support for Azure Resource Manager
This commit is contained in:
James Nugent 2015-12-15 18:48:39 -05:00
commit 06f50e0144
17 changed files with 1331 additions and 2 deletions

View File

@ -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,
})
}

View File

@ -0,0 +1,189 @@
package azurerm
import (
"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"
"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/Azure/go-autorest/autorest"
)
// 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
}
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) {
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
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
}

View File

@ -0,0 +1,78 @@
package azurerm
import (
"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{
"subscription_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_SUBSCRIPTION_ID", ""),
},
"client_id": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_ID", ""),
},
"client_secret": &schema.Schema{
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("ARM_CLIENT_SECRET", ""),
},
"tenant_id": &schema.Schema{
Type: schema.TypeString,
Required: 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
SubscriptionID string
ClientID string
ClientSecret string
TenantID string
}
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),
}
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)
}

View File

@ -0,0 +1,40 @@
package azurerm
import (
"os"
"testing"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform"
)
var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider
func init() {
testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{
"azurerm": 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) {
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("ARM_SUBSCRIPTION_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET and ARM_TENANT_ID must be set for acceptance tests")
}
}

View File

@ -0,0 +1,162 @@
package azurerm
import (
"fmt"
"log"
"net/http"
"regexp"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/arm/resources"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"
)
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,
ValidateFunc: validateArmResourceGroupName,
},
"location": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
StateFunc: azureRMNormalizeLocation,
},
},
}
}
func validateArmResourceGroupName(v interface{}, k string) (ws []string, es []error) {
value := v.(string)
if len(value) > 80 {
es = append(es, fmt.Errorf("%q may not exceed 80 characters in length", k))
}
if strings.HasSuffix(value, ".") {
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 {
es = append(es, fmt.Errorf("%q may only contain alphanumeric characters, dash, underscores, parentheses and periods", k))
}
return
}
func resourceArmResourceGroupCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ArmClient)
resGroupClient := client.resourceGroupClient
name := d.Get("name").(string)
location := d.Get("location").(string)
rg := resources.ResourceGroup{
Name: &name,
Location: &location,
}
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(*resp.ID)
log.Printf("[DEBUG] Waiting for Resource Group (%s) to become available", name)
stateConf := &resource.StateChangeConf{
Pending: []string{"Accepted"},
Target: "Succeeded",
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", name, err)
}
return resourceArmResourceGroupRead(d, meta)
}
func resourceArmResourceGroupRead(d *schema.ResourceData, meta interface{}) error {
resGroupClient := meta.(*ArmClient).resourceGroupClient
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)
return nil
}
func resourceArmResourceGroupExists(d *schema.ResourceData, meta interface{}) (bool, error) {
resGroupClient := meta.(*ArmClient).resourceGroupClient
id, err := parseAzureResourceID(d.Id())
if err != nil {
return false, err
}
name := id.ResourceGroup
resp, err := resGroupClient.CheckExistence(name)
if err != nil {
if resp.StatusCode != 200 {
return false, err
}
return true, nil
}
return true, nil
}
func resourceArmResourceGroupDelete(d *schema.ResourceData, meta interface{}) error {
resGroupClient := meta.(*ArmClient).resourceGroupClient
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
name := id.ResourceGroup
_, err = resGroupClient.Delete(name)
if err != nil {
return err
}
return nil
}
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
}
}

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

@ -0,0 +1,250 @@
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: resourceArmVirtualNetworkCreate,
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": &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,
},
},
}
}
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),
}
resp, err := vnetClient.CreateOrUpdate(resGroup, name, vnet)
if err != nil {
return err
}
d.SetId(*resp.ID)
log.Printf("[DEBUG] Waiting for Virtual Network (%s) to become available", name)
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", name, err)
}
return resourceArmVirtualNetworkRead(d, meta)
}
func resourceArmVirtualNetworkRead(d *schema.ResourceData, meta interface{}) error {
vnetClient := meta.(*ArmClient).vnetClient
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 {
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 appropriate values
d.Set("address_space", vnet.AddressSpace.AddressPrefixes)
subnets := &schema.Set{
F: resourceAzureSubnetHash,
}
for _, subnet := range *vnet.Subnets {
s := map[string]interface{}{}
s["name"] = *subnet.Name
s["address_prefix"] = *subnet.Properties.AddressPrefix
if subnet.Properties.NetworkSecurityGroup != nil {
s["security_group"] = *subnet.Properties.NetworkSecurityGroup.ID
}
subnets.Add(s)
}
d.Set("subnet", subnets)
dnses := []string{}
for _, dns := range *vnet.DhcpOptions.DNSServers {
dnses = append(dnses, dns)
}
d.Set("dns_servers", dnses)
return nil
}
func resourceArmVirtualNetworkDelete(d *schema.ResourceData, meta interface{}) error {
vnetClient := meta.(*ArmClient).vnetClient
id, err := parseAzureResourceID(d.Id())
if err != nil {
return err
}
resGroup := id.ResourceGroup
name := id.Path["virtualNetworks"]
_, err = vnetClient.Delete(resGroup, name)
return err
}
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").([]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)
}
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
}
}

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") %>>