Merge pull request #2052 from aznashwan/azure

provider/azure: added a number of storage and networking resources.
This commit is contained in:
Paul Hinze 2015-06-12 16:49:36 -05:00
commit 1ebe117085
45 changed files with 3342 additions and 651 deletions

View File

@ -5,7 +5,7 @@ import (
"os" "os"
"sync" "sync"
"github.com/svanharmelen/azure-sdk-for-go/management" "github.com/Azure/azure-sdk-for-go/management"
) )
// Config is the configuration structure used to instantiate a // Config is the configuration structure used to instantiate a

View File

@ -0,0 +1,48 @@
package azure
const (
// terraformAzureLabel is used as the label for the hosted service created
// by Terraform on Azure.
terraformAzureLabel = "terraform-on-azure"
// terraformAzureDescription is the description used for the hosted service
// created by Terraform on Azure.
terraformAzureDescription = "Hosted service automatically created by terraform."
)
// parameterDescriptions holds a list of descriptions for all the available
// parameters of an Azure configuration.
var parameterDescriptions = map[string]string{
// provider descriptions:
"management_url": "The URL of the management API all requests should be sent to.\n" +
"Defaults to 'https://management.core.windows.net/', which is the default Azure API URL.\n" +
"This should be filled in only if you have your own datacenter with its own hosted management API.",
"management_certificate": "The certificate for connecting to the management API specified with 'management_url'",
"subscription_id": "The subscription ID to be used when connecting to the management API.",
"publish_settings_file": "The publish settings file, either created by you or downloaded from 'https://manage.windowsazure.com/publishsettings'",
// general resource descriptions:
"name": "Name of the resource to be created as it will appear in the Azure dashboard.",
"service_name": "Name of the hosted service within Azure. Will have a DNS entry as dns-name.cloudapp.net",
"location": "The Azure location where the resource will be located.\n" +
"A list of Azure locations can be found here: http://azure.microsoft.com/en-us/regions/",
"reverse_dns_fqdn": "The reverse of the fully qualified domain name. Optional.",
"label": "Label by which the resource will be identified by. Optional.",
"description": "Brief description of the resource. Optional.",
// hosted service descriptions:
"ephemeral_contents": "Sets whether the associated contents of this resource should also be\n" +
"deleted upon this resource's deletion. Default is false.",
// instance descriptions:
"image": "The image the new VM will be booted from. Mandatory.",
"size": "The size in GB of the disk to be created. Mandatory.",
"os_type": "The OS type of the VM. Either Windows or Linux. Mandatory.",
"storage_account": "The storage account (pool) name. Mandatory.",
"storage_container": "The storage container name from the storage pool given with 'storage_pool'.",
"user_name": "The user name to be configured on the new VM.",
"user_password": "The user password to be configured on the new VM.",
"default_certificate_thumbprint": "The thumbprint of the WinRM Certificate to be used as a default.",
// local network descriptions:
"vpn_gateway_address": "The IP address of the VPN gateway bridged through this virtual network.",
"address_space_prefixes": "List of address space prefixes in the format '<IP>/netmask'",
// dns descriptions:
"dns_address": "Address of the DNS server. Required.",
}

View File

@ -32,10 +32,18 @@ func Provider() terraform.ResourceProvider {
}, },
ResourcesMap: map[string]*schema.Resource{ ResourcesMap: map[string]*schema.Resource{
"azure_data_disk": resourceAzureDataDisk(), "azure_instance": resourceAzureInstance(),
"azure_instance": resourceAzureInstance(), "azure_data_disk": resourceAzureDataDisk(),
"azure_security_group": resourceAzureSecurityGroup(), "azure_hosted_service": resourceAzureHostedService(),
"azure_virtual_network": resourceAzureVirtualNetwork(), "azure_storage_service": resourceAzureStorageService(),
"azure_storage_container": resourceAzureStorageContainer(),
"azure_storage_blob": resourceAzureStorageBlob(),
"azure_storage_queue": resourceAzureStorageQueue(),
"azure_virtual_network": resourceAzureVirtualNetwork(),
"azure_dns_server": resourceAzureDnsServer(),
"azure_local_network_connection": resourceAzureLocalNetworkConnection(),
"azure_security_group": resourceAzureSecurityGroup(),
"azure_security_group_rule": resourceAzureSecurityGroupRule(),
}, },
ConfigureFunc: providerConfigure, ConfigureFunc: providerConfigure,

View File

@ -11,6 +11,18 @@ import (
var testAccProviders map[string]terraform.ResourceProvider var testAccProviders map[string]terraform.ResourceProvider
var testAccProvider *schema.Provider var testAccProvider *schema.Provider
const testAccSecurityGroupName = "terraform-security-group"
// 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() { func init() {
testAccProvider = Provider().(*schema.Provider) testAccProvider = Provider().(*schema.Provider)
testAccProviders = map[string]terraform.ResourceProvider{ testAccProviders = map[string]terraform.ResourceProvider{

View File

@ -5,9 +5,9 @@ import (
"log" "log"
"time" "time"
"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/virtualmachinedisk"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/svanharmelen/azure-sdk-for-go/management"
"github.com/svanharmelen/azure-sdk-for-go/management/virtualmachinedisk"
) )
const dataDiskBlobStorageURL = "http://%s.blob.core.windows.net/disks/%s.vhd" const dataDiskBlobStorageURL = "http://%s.blob.core.windows.net/disks/%s.vhd"
@ -50,7 +50,7 @@ func resourceAzureDataDisk() *schema.Resource {
Default: "None", Default: "None",
}, },
"storage": &schema.Schema{ "storage_service_name": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
@ -321,7 +321,7 @@ func mediaLink(d *schema.ResourceData) string {
name = fmt.Sprintf("%s-%d", d.Get("virtual_machine").(string), d.Get("lun").(int)) name = fmt.Sprintf("%s-%d", d.Get("virtual_machine").(string), d.Get("lun").(int))
} }
return fmt.Sprintf(dataDiskBlobStorageURL, d.Get("storage").(string), name.(string)) return fmt.Sprintf(dataDiskBlobStorageURL, d.Get("storage_service_name").(string), name.(string))
} }
func verifyDataDiskParameters(d *schema.ResourceData) error { func verifyDataDiskParameters(d *schema.ResourceData) error {
@ -332,7 +332,7 @@ func verifyDataDiskParameters(d *schema.ResourceData) error {
} }
if _, ok := d.GetOk("media_link"); !ok { if _, ok := d.GetOk("media_link"); !ok {
if _, ok := d.GetOk("storage"); !ok { if _, ok := d.GetOk("storage_service_name"); !ok {
return fmt.Errorf("If not supplying 'media_link', you must supply 'storage'.") return fmt.Errorf("If not supplying 'media_link', you must supply 'storage'.")
} }
} }

View File

@ -2,14 +2,13 @@ package azure
import ( import (
"fmt" "fmt"
"os"
"strconv" "strconv"
"testing" "testing"
"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/virtualmachinedisk"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/svanharmelen/azure-sdk-for-go/management"
"github.com/svanharmelen/azure-sdk-for-go/management/virtualmachinedisk"
) )
func TestAccAzureDataDisk_basic(t *testing.T) { func TestAccAzureDataDisk_basic(t *testing.T) {
@ -174,7 +173,7 @@ resource "azure_instance" "foo" {
name = "terraform-test" name = "terraform-test"
image = "Ubuntu Server 14.04 LTS" image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1" size = "Basic_A1"
storage = "%s" storage_service_name = "%s"
location = "West US" location = "West US"
username = "terraform" username = "terraform"
password = "Pass!admin123" password = "Pass!admin123"
@ -183,16 +182,16 @@ resource "azure_instance" "foo" {
resource "azure_data_disk" "foo" { resource "azure_data_disk" "foo" {
lun = 0 lun = 0
size = 10 size = 10
storage = "${azure_instance.foo.storage}" storage_service_name = "${azure_instance.foo.storage_service_name}"
virtual_machine = "${azure_instance.foo.id}" virtual_machine = "${azure_instance.foo.id}"
}`, os.Getenv("AZURE_STORAGE")) }`, testAccStorageServiceName)
var testAccAzureDataDisk_advanced = fmt.Sprintf(` var testAccAzureDataDisk_advanced = fmt.Sprintf(`
resource "azure_instance" "foo" { resource "azure_instance" "foo" {
name = "terraform-test1" name = "terraform-test1"
image = "Ubuntu Server 14.04 LTS" image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1" size = "Basic_A1"
storage = "%s" storage_service_name = "%s"
location = "West US" location = "West US"
username = "terraform" username = "terraform"
password = "Pass!admin123" password = "Pass!admin123"
@ -202,16 +201,16 @@ resource "azure_data_disk" "foo" {
lun = 1 lun = 1
size = 10 size = 10
caching = "ReadOnly" caching = "ReadOnly"
storage = "${azure_instance.foo.storage}" storage_service_name = "${azure_instance.foo.storage_service_name}"
virtual_machine = "${azure_instance.foo.id}" virtual_machine = "${azure_instance.foo.id}"
}`, os.Getenv("AZURE_STORAGE")) }`, testAccStorageServiceName)
var testAccAzureDataDisk_update = fmt.Sprintf(` var testAccAzureDataDisk_update = fmt.Sprintf(`
resource "azure_instance" "foo" { resource "azure_instance" "foo" {
name = "terraform-test1" name = "terraform-test1"
image = "Ubuntu Server 14.04 LTS" image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1" size = "Basic_A1"
storage = "%s" storage_service_name = "%s"
location = "West US" location = "West US"
username = "terraform" username = "terraform"
password = "Pass!admin123" password = "Pass!admin123"
@ -221,7 +220,7 @@ resource "azure_instance" "bar" {
name = "terraform-test2" name = "terraform-test2"
image = "Ubuntu Server 14.04 LTS" image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1" size = "Basic_A1"
storage = "${azure_instance.foo.storage}" storage_service_name = "${azure_instance.foo.storage_service_name}"
location = "West US" location = "West US"
username = "terraform" username = "terraform"
password = "Pass!admin123" password = "Pass!admin123"
@ -231,6 +230,6 @@ resource "azure_data_disk" "foo" {
lun = 2 lun = 2
size = 20 size = 20
caching = "ReadWrite" caching = "ReadWrite"
storage = "${azure_instance.bar.storage}" storage_service_name = "${azure_instance.bar.storage_service_name}"
virtual_machine = "${azure_instance.bar.id}" virtual_machine = "${azure_instance.bar.id}"
}`, os.Getenv("AZURE_STORAGE")) }`, testAccStorageServiceName)

View File

@ -0,0 +1,246 @@
package azure
import (
"fmt"
"log"
"github.com/Azure/azure-sdk-for-go/management/virtualnetwork"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureDnsServer returns the *schema.Resource associated
// to an Azure hosted service.
func resourceAzureDnsServer() *schema.Resource {
return &schema.Resource{
Create: resourceAzureDnsServerCreate,
Read: resourceAzureDnsServerRead,
Update: resourceAzureDnsServerUpdate,
Exists: resourceAzureDnsServerExists,
Delete: resourceAzureDnsServerDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Required: true,
Description: parameterDescriptions["name"],
},
"dns_address": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: parameterDescriptions["dns_address"],
},
},
}
}
// resourceAzureDnsServerCreate does all the necessary API calls
// to create a new DNS server definition on Azure.
func resourceAzureDnsServerCreate(d *schema.ResourceData, meta interface{}) error {
// first; check for the existence of the resource:
exists, err := resourceAzureDnsServerExists(d, meta)
if err != nil {
return err
}
if exists {
return fmt.Errorf("Azure DNS server definition already exists.")
}
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
log.Println("[INFO] Fetching current network configuration from Azure.")
azureClient.mutex.Lock()
defer azureClient.mutex.Unlock()
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
log.Println("[DEBUG] Adding new DNS server definition to Azure.")
name := d.Get("name").(string)
address := d.Get("dns_address").(string)
netConf.Configuration.DNS.DNSServers = append(
netConf.Configuration.DNS.DNSServers,
virtualnetwork.DNSServer{
Name: name,
IPAddress: address,
})
// send the configuration back to Azure:
log.Println("[INFO] Sending updated network configuration back to Azure.")
reqID, err := networkClient.SetVirtualNetworkConfiguration(netConf)
if err != nil {
return fmt.Errorf("Failed issuing update to network configuration: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error setting network configuration: %s", err)
}
d.SetId(name)
return nil
}
// resourceAzureDnsServerRead does all the necessary API calls to read
// the state of the DNS server off Azure.
func resourceAzureDnsServerRead(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
log.Println("[INFO] Fetching current network configuration from Azure.")
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
var found bool
name := d.Get("name").(string)
// search for our DNS and update it if the IP has been changed:
for _, dns := range netConf.Configuration.DNS.DNSServers {
if dns.Name == name {
found = true
d.Set("dns_address", dns.IPAddress)
break
}
}
// remove the resource from the state if it has been deleted in the meantime:
if !found {
d.SetId("")
}
return nil
}
// resourceAzureDnsServerUpdate does all the necessary API calls
// to update the DNS definition on Azure.
func resourceAzureDnsServerUpdate(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
var found bool
name := d.Get("name").(string)
if d.HasChange("dns_address") {
log.Println("[DEBUG] DNS server address has changes; updating it on Azure.")
log.Println("[INFO] Fetching current network configuration from Azure.")
azureClient.mutex.Lock()
defer azureClient.mutex.Unlock()
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
// search for our DNS and update its address value:
for i, dns := range netConf.Configuration.DNS.DNSServers {
if dns.Name == name {
found = true
netConf.Configuration.DNS.DNSServers[i].IPAddress = d.Get("dns_address").(string)
break
}
}
// if the config has changes, send the configuration back to Azure:
if found {
log.Println("[INFO] Sending updated network configuration back to Azure.")
reqID, err := networkClient.SetVirtualNetworkConfiguration(netConf)
if err != nil {
return fmt.Errorf("Failed issuing update to network configuration: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error setting network configuration: %s", err)
}
return nil
}
}
// remove the resource from the state if it has been deleted in the meantime:
if !found {
d.SetId("")
}
return nil
}
// resourceAzureDnsServerExists does all the necessary API calls to
// check if the DNS server definition alredy exists on Azure.
func resourceAzureDnsServerExists(d *schema.ResourceData, meta interface{}) (bool, error) {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
log.Println("[INFO] Fetching current network configuration from Azure.")
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return false, fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
name := d.Get("name").(string)
// search for the DNS server's definition:
for _, dns := range netConf.Configuration.DNS.DNSServers {
if dns.Name == name {
return true, nil
}
}
// if we reached this point; the resource must have been deleted; and we must untrack it:
d.SetId("")
return false, nil
}
// resourceAzureDnsServerDelete does all the necessary API calls
// to delete the DNS server definition from Azure.
func resourceAzureDnsServerDelete(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
log.Println("[INFO] Fetching current network configuration from Azure.")
azureClient.mutex.Lock()
defer azureClient.mutex.Unlock()
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
name := d.Get("name").(string)
// search for the DNS server's definition and remove it:
var found bool
for i, dns := range netConf.Configuration.DNS.DNSServers {
if dns.Name == name {
found = true
netConf.Configuration.DNS.DNSServers = append(
netConf.Configuration.DNS.DNSServers[:i],
netConf.Configuration.DNS.DNSServers[i+1:]...,
)
break
}
}
// if not found; don't bother re-sending the natwork config:
if !found {
return nil
}
// send the configuration back to Azure:
log.Println("[INFO] Sending updated network configuration back to Azure.")
reqID, err := networkClient.SetVirtualNetworkConfiguration(netConf)
if err != nil {
return fmt.Errorf("Failed issuing update to network configuration: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error setting network configuration: %s", err)
}
return nil
}

View File

@ -0,0 +1,128 @@
package azure
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/management/virtualnetwork"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureDnsServerBasic(t *testing.T) {
name := "azure_dns_server.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureDnsServerDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureDnsServerBasic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureDnsServerExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-dns-server"),
resource.TestCheckResourceAttr(name, "dns_address", "8.8.8.8"),
),
},
},
})
}
func TestAccAzureDnsServerUpdate(t *testing.T) {
name := "azure_dns_server.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureDnsServerDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureDnsServerBasic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureDnsServerExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-dns-server"),
resource.TestCheckResourceAttr(name, "dns_address", "8.8.8.8"),
),
},
resource.TestStep{
Config: testAccAzureDnsServerUpdate,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureDnsServerExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-dns-server"),
resource.TestCheckResourceAttr(name, "dns_address", "8.8.4.4"),
),
},
},
})
}
func testAccCheckAzureDnsServerExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Resource not found: %s", name)
}
if resource.Primary.ID == "" {
return fmt.Errorf("No DNS Server ID set.")
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
netConf, err := virtualnetwork.NewClient(mgmtClient).GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Failed fetching networking configuration: %s", err)
}
for _, dns := range netConf.Configuration.DNS.DNSServers {
if dns.Name == resource.Primary.ID {
return nil
}
}
return fmt.Errorf("Azure DNS Server not found.")
}
}
func testAccCheckAzureDnsServerDestroy(s *terraform.State) error {
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_dns_server" {
continue
}
if resource.Primary.ID == "" {
return fmt.Errorf("No DNS Server ID is set.")
}
networkClient := virtualnetwork.NewClient(mgmtClient)
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Error retrieving networking configuration from Azure: %s", err)
}
for _, dns := range netConf.Configuration.DNS.DNSServers {
if dns.Name == resource.Primary.ID {
return fmt.Errorf("Azure DNS Server still exists.")
}
}
}
return nil
}
const testAccAzureDnsServerBasic = `
resource "azure_dns_server" "foo" {
name = "terraform-dns-server"
dns_address = "8.8.8.8"
}
`
const testAccAzureDnsServerUpdate = `
resource "azure_dns_server" "foo" {
name = "terraform-dns-server"
dns_address = "8.8.4.4"
}
`

View File

@ -0,0 +1,171 @@
package azure
import (
"encoding/base64"
"fmt"
"log"
"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/hostedservice"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureHostedService returns the schema.Resource associated to an
// Azure hosted service.
func resourceAzureHostedService() *schema.Resource {
return &schema.Resource{
Create: resourceAzureHostedServiceCreate,
Read: resourceAzureHostedServiceRead,
Update: resourceAzureHostedServiceUpdate,
Delete: resourceAzureHostedServiceDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["name"],
},
"location": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["location"],
},
"ephemeral_contents": &schema.Schema{
Type: schema.TypeBool,
Required: true,
Description: parameterDescriptions["ephemeral_contents"],
},
"url": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"status": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"reverse_dns_fqdn": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: parameterDescriptions["reverse_dns_fqdn"],
},
"label": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Optional: true,
Default: "Made by Terraform.",
Description: parameterDescriptions["label"],
},
"description": &schema.Schema{
Type: schema.TypeString,
ForceNew: true,
Optional: true,
Description: parameterDescriptions["description"],
},
"default_certificate_thumbprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Optional: true,
Description: parameterDescriptions["default_certificate_thumbprint"],
},
},
}
}
// resourceAzureHostedServiceCreate does all the necessary API calls
// to create a hosted service on Azure.
func resourceAzureHostedServiceCreate(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
hostedServiceClient := hostedservice.NewClient(mgmtClient)
serviceName := d.Get("name").(string)
location := d.Get("location").(string)
reverseDNS := d.Get("reverse_dns_fqdn").(string)
description := d.Get("description").(string)
label := base64.StdEncoding.EncodeToString([]byte(d.Get("label").(string)))
err := hostedServiceClient.CreateHostedService(
hostedservice.CreateHostedServiceParameters{
ServiceName: serviceName,
Location: location,
Label: label,
Description: description,
ReverseDNSFqdn: reverseDNS,
},
)
if err != nil {
return fmt.Errorf("Failed defining new Azure hosted service: %s", err)
}
d.SetId(serviceName)
return nil
}
// resourceAzureHostedServiceRead does all the necessary API calls
// to read the state of a hosted service from Azure.
func resourceAzureHostedServiceRead(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
hostedServiceClient := hostedservice.NewClient(azureClient.mgmtClient)
log.Println("[INFO] Querying for hosted service info.")
serviceName := d.Get("name").(string)
hostedService, err := hostedServiceClient.GetHostedService(serviceName)
if err != nil {
if management.IsResourceNotFoundError(err) {
// it means the hosted service was deleted in the meantime,
// so we must remove it here:
d.SetId("")
return nil
} else {
return fmt.Errorf("Failed to get hosted service: %s", err)
}
}
log.Println("[DEBUG] Reading hosted service query result data.")
d.Set("name", hostedService.ServiceName)
d.Set("url", hostedService.URL)
d.Set("location", hostedService.Location)
d.Set("description", hostedService.Description)
d.Set("label", hostedService.Label)
d.Set("status", hostedService.Status)
d.Set("reverse_dns_fqdn", hostedService.ReverseDNSFqdn)
d.Set("default_certificate_thumbprint", hostedService.DefaultWinRmCertificateThumbprint)
return nil
}
// resourceAzureHostedServiceUpdate does all the necessary API calls to
// update some settings of a hosted service on Azure.
func resourceAzureHostedServiceUpdate(d *schema.ResourceData, meta interface{}) error {
// NOTE: although no-op; this is still required in order for updates to
// ephemeral_contents to be possible.
// check if the service still exists:
return resourceAzureHostedServiceRead(d, meta)
}
// resourceAzureHostedServiceDelete does all the necessary API calls to
// delete a hosted service from Azure.
func resourceAzureHostedServiceDelete(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
hostedServiceClient := hostedservice.NewClient(mgmtClient)
log.Println("[INFO] Issuing hosted service deletion.")
serviceName := d.Get("name").(string)
ephemeral := d.Get("ephemeral_contents").(bool)
reqID, err := hostedServiceClient.DeleteHostedService(serviceName, ephemeral)
if err != nil {
return fmt.Errorf("Failed issuing hosted service deletion request: %s", err)
}
log.Println("[DEBUG] Awaiting confirmation on hosted service deletion.")
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error on hosted service deletion: %s", err)
}
return nil
}

View File

@ -0,0 +1,124 @@
package azure
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/management/hostedservice"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureHostedServiceBasic(t *testing.T) {
name := "azure_hosted_service.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureHostedServiceDestroyed,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureHostedServiceBasic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureHostedServiceExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-testing-service"),
resource.TestCheckResourceAttr(name, "location", "North Europe"),
resource.TestCheckResourceAttr(name, "ephemeral_contents", "false"),
resource.TestCheckResourceAttr(name, "description", "very discriptive"),
resource.TestCheckResourceAttr(name, "label", "very identifiable"),
),
},
},
})
}
func TestAccAzureHostedServiceUpdate(t *testing.T) {
name := "azure_hosted_service.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureHostedServiceDestroyed,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureHostedServiceBasic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureHostedServiceExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-testing-service"),
resource.TestCheckResourceAttr(name, "location", "North Europe"),
resource.TestCheckResourceAttr(name, "ephemeral_contents", "false"),
resource.TestCheckResourceAttr(name, "description", "very discriptive"),
resource.TestCheckResourceAttr(name, "label", "very identifiable"),
),
},
resource.TestStep{
Config: testAccAzureHostedServiceUpdate,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureHostedServiceExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-testing-service"),
resource.TestCheckResourceAttr(name, "location", "North Europe"),
resource.TestCheckResourceAttr(name, "ephemeral_contents", "true"),
resource.TestCheckResourceAttr(name, "description", "very discriptive"),
resource.TestCheckResourceAttr(name, "label", "very identifiable"),
),
},
},
})
}
func testAccCheckAzureHostedServiceExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Hosted Service resource not found.")
}
if resource.Primary.ID == "" {
return fmt.Errorf("Resource's ID is not set.")
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
_, err := hostedservice.NewClient(mgmtClient).GetHostedService(resource.Primary.ID)
return err
}
}
func testAccCheckAzureHostedServiceDestroyed(s *terraform.State) error {
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_hosted_service" {
continue
}
if resource.Primary.ID == "" {
return fmt.Errorf("No Azure Hosted Service Resource found.")
}
_, err := hostedservice.NewClient(mgmtClient).GetHostedService(resource.Primary.ID)
return testAccResourceDestroyedErrorFilter("Hosted Service", err)
}
return nil
}
const testAccAzureHostedServiceBasic = `
resource "azure_hosted_service" "foo" {
name = "terraform-testing-service"
location = "North Europe"
ephemeral_contents = false
description = "very discriptive"
label = "very identifiable"
}
`
const testAccAzureHostedServiceUpdate = `
resource "azure_hosted_service" "foo" {
name = "terraform-testing-service"
location = "North Europe"
ephemeral_contents = true
description = "very discriptive"
label = "very identifiable"
}
`

View File

@ -7,14 +7,14 @@ import (
"log" "log"
"strings" "strings"
"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/hostedservice"
"github.com/Azure/azure-sdk-for-go/management/osimage"
"github.com/Azure/azure-sdk-for-go/management/virtualmachine"
"github.com/Azure/azure-sdk-for-go/management/virtualmachineimage"
"github.com/Azure/azure-sdk-for-go/management/vmutils"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/svanharmelen/azure-sdk-for-go/management"
"github.com/svanharmelen/azure-sdk-for-go/management/hostedservice"
"github.com/svanharmelen/azure-sdk-for-go/management/osimage"
"github.com/svanharmelen/azure-sdk-for-go/management/virtualmachine"
"github.com/svanharmelen/azure-sdk-for-go/management/virtualmachineimage"
"github.com/svanharmelen/azure-sdk-for-go/management/vmutils"
) )
const ( const (
@ -68,7 +68,7 @@ func resourceAzureInstance() *schema.Resource {
ForceNew: true, ForceNew: true,
}, },
"storage": &schema.Schema{ "storage_service_name": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Optional: true,
ForceNew: true, ForceNew: true,
@ -183,7 +183,7 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err
mc, mc,
d.Get("image").(string), d.Get("image").(string),
name, name,
d.Get("storage").(string), d.Get("storage_service_name").(string),
) )
if err != nil { if err != nil {
return err return err

View File

@ -2,14 +2,13 @@ package azure
import ( import (
"fmt" "fmt"
"os"
"testing" "testing"
"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/hostedservice"
"github.com/Azure/azure-sdk-for-go/management/virtualmachine"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/svanharmelen/azure-sdk-for-go/management"
"github.com/svanharmelen/azure-sdk-for-go/management/hostedservice"
"github.com/svanharmelen/azure-sdk-for-go/management/virtualmachine"
) )
func TestAccAzureInstance_basic(t *testing.T) { func TestAccAzureInstance_basic(t *testing.T) {
@ -313,7 +312,7 @@ resource "azure_instance" "foo" {
name = "terraform-test" name = "terraform-test"
image = "Ubuntu Server 14.04 LTS" image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1" size = "Basic_A1"
storage = "%s" storage_service_name = "%s"
location = "West US" location = "West US"
username = "terraform" username = "terraform"
password = "Pass!admin123" password = "Pass!admin123"
@ -324,7 +323,7 @@ resource "azure_instance" "foo" {
public_port = 22 public_port = 22
private_port = 22 private_port = 22
} }
}`, os.Getenv("AZURE_STORAGE")) }`, testAccStorageServiceName)
var testAccAzureInstance_advanced = fmt.Sprintf(` var testAccAzureInstance_advanced = fmt.Sprintf(`
resource "azure_virtual_network" "foo" { resource "azure_virtual_network" "foo" {
@ -346,23 +345,26 @@ resource "azure_virtual_network" "foo" {
resource "azure_security_group" "foo" { resource "azure_security_group" "foo" {
name = "terraform-security-group1" name = "terraform-security-group1"
location = "West US" location = "West US"
}
rule { resource "azure_security_group_rule" "foo" {
name = "rdp" name = "rdp"
priority = 101 security_group_name = "${azure_security_group.foo.name}"
source_cidr = "*" priority = 101
source_port = "*" source_address_prefix = "*"
destination_cidr = "*" source_port_range = "*"
destination_port = 3389 destination_address_prefix = "*"
protocol = "TCP" destination_port_range = "3389"
} action = "Deny"
type = "Inbound"
protocol = "TCP"
} }
resource "azure_instance" "foo" { resource "azure_instance" "foo" {
name = "terraform-test1" name = "terraform-test1"
image = "Windows Server 2012 R2 Datacenter, April 2015" image = "Windows Server 2012 R2 Datacenter, April 2015"
size = "Basic_A1" size = "Basic_A1"
storage = "%s" storage_service_name = "%s"
location = "West US" location = "West US"
time_zone = "America/Los_Angeles" time_zone = "America/Los_Angeles"
subnet = "subnet1" subnet = "subnet1"
@ -377,7 +379,7 @@ resource "azure_instance" "foo" {
public_port = 3389 public_port = 3389
private_port = 3389 private_port = 3389
} }
}`, os.Getenv("AZURE_STORAGE")) }`, testAccStorageServiceName)
var testAccAzureInstance_update = fmt.Sprintf(` var testAccAzureInstance_update = fmt.Sprintf(`
resource "azure_virtual_network" "foo" { resource "azure_virtual_network" "foo" {
@ -385,52 +387,58 @@ resource "azure_virtual_network" "foo" {
address_space = ["10.1.2.0/24"] address_space = ["10.1.2.0/24"]
location = "West US" location = "West US"
subnet { subnet {
name = "subnet1" name = "subnet1"
address_prefix = "10.1.2.0/25" address_prefix = "10.1.2.0/25"
} }
subnet { subnet {
name = "subnet2" name = "subnet2"
address_prefix = "10.1.2.128/25" address_prefix = "10.1.2.128/25"
} }
} }
resource "azure_security_group" "foo" { resource "azure_security_group" "foo" {
name = "terraform-security-group1" name = "terraform-security-group1"
location = "West US" location = "West US"
}
rule { resource "azure_security_group_rule" "foo" {
name = "rdp" name = "rdp"
priority = 101 security_group_name = "${azure_security_group.foo.name}"
source_cidr = "*" priority = 101
source_port = "*" source_address_prefix = "*"
destination_cidr = "*" source_port_range = "*"
destination_port = 3389 destination_address_prefix = "*"
protocol = "TCP" destination_port_range = "3389"
} type = "Inbound"
action = "Deny"
protocol = "TCP"
} }
resource "azure_security_group" "bar" { resource "azure_security_group" "bar" {
name = "terraform-security-group2" name = "terraform-security-group2"
location = "West US" location = "West US"
}
rule { resource "azure_security_group_rule" "bar" {
name = "rdp" name = "rdp"
priority = 101 security_group_name = "${azure_security_group.bar.name}"
source_cidr = "192.168.0.0/24" priority = 101
source_port = "*" source_address_prefix = "192.168.0.0/24"
destination_cidr = "*" source_port_range = "*"
destination_port = 3389 destination_address_prefix = "*"
protocol = "TCP" destination_port_range = "3389"
} type = "Inbound"
action = "Deny"
protocol = "TCP"
} }
resource "azure_instance" "foo" { resource "azure_instance" "foo" {
name = "terraform-test1" name = "terraform-test1"
image = "Windows Server 2012 R2 Datacenter, April 2015" image = "Windows Server 2012 R2 Datacenter, April 2015"
size = "Basic_A2" size = "Basic_A2"
storage = "%s" storage_service_name = "%s"
location = "West US" location = "West US"
time_zone = "America/Los_Angeles" time_zone = "America/Los_Angeles"
subnet = "subnet1" subnet = "subnet1"
@ -452,4 +460,4 @@ resource "azure_instance" "foo" {
public_port = 5985 public_port = 5985
private_port = 5985 private_port = 5985
} }
}`, os.Getenv("AZURE_STORAGE")) }`, testAccStorageServiceName)

View File

@ -0,0 +1,250 @@
package azure
import (
"fmt"
"log"
"github.com/Azure/azure-sdk-for-go/management/virtualnetwork"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureLocalNetworkConnetion returns the schema.Resource associated to an
// Azure hosted service.
func resourceAzureLocalNetworkConnection() *schema.Resource {
return &schema.Resource{
Create: resourceAzureLocalNetworkConnectionCreate,
Read: resourceAzureLocalNetworkConnectionRead,
Update: resourceAzureLocalNetworkConnectionUpdate,
Exists: resourceAzureLocalNetworkConnectionExists,
Delete: resourceAzureLocalNetworkConnectionDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["name"],
},
"vpn_gateway_address": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: parameterDescriptions["vpn_gateway_address"],
},
"address_space_prefixes": &schema.Schema{
Type: schema.TypeList,
Required: true,
Elem: &schema.Schema{
Type: schema.TypeString,
},
Description: parameterDescriptions["address_space_prefixes"],
},
},
}
}
// sourceAzureLocalNetworkConnectionCreate issues all the necessary API calls
// to create a virtual network on Azure.
func resourceAzureLocalNetworkConnectionCreate(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
log.Println("[INFO] Fetching current network configuration from Azure.")
azureClient.mutex.Lock()
defer azureClient.mutex.Unlock()
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
// get provided configuration:
name := d.Get("name").(string)
vpnGateway := d.Get("vpn_gateway_address").(string)
var prefixes []string
for _, prefix := range d.Get("address_space_prefixes").([]interface{}) {
prefixes = append(prefixes, prefix.(string))
}
// add configuration to network config:
netConf.Configuration.LocalNetworkSites = append(netConf.Configuration.LocalNetworkSites,
virtualnetwork.LocalNetworkSite{
Name: name,
VPNGatewayAddress: vpnGateway,
AddressSpace: virtualnetwork.AddressSpace{
AddressPrefix: prefixes,
},
})
// send the configuration back to Azure:
log.Println("[INFO] Sending updated network configuration back to Azure.")
reqID, err := networkClient.SetVirtualNetworkConfiguration(netConf)
if err != nil {
return fmt.Errorf("Failed setting updated network configuration: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Failed updating the network configuration: %s", err)
}
d.SetId(name)
return nil
}
// resourceAzureLocalNetworkConnectionRead does all the necessary API calls to
// read the state of our local natwork from Azure.
func resourceAzureLocalNetworkConnectionRead(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
log.Println("[INFO] Fetching current network configuration from Azure.")
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
var found bool
name := d.Get("name").(string)
// browsing for our network config:
for _, lnet := range netConf.Configuration.LocalNetworkSites {
if lnet.Name == name {
found = true
d.Set("vpn_gateway_address", lnet.VPNGatewayAddress)
d.Set("address_space_prefixes", lnet.AddressSpace.AddressPrefix)
break
}
}
// remove the resource from the state of it has been deleted in the meantime:
if !found {
log.Println(fmt.Printf("[INFO] Azure local network '%s' has been deleted remotely. Removimg from Terraform.", name))
d.SetId("")
}
return nil
}
// resourceAzureLocalNetworkConnectionUpdate does all the necessary API calls
// update the settings of our Local Network on Azure.
func resourceAzureLocalNetworkConnectionUpdate(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
log.Println("[INFO] Fetching current network configuration from Azure.")
azureClient.mutex.Lock()
defer azureClient.mutex.Unlock()
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
name := d.Get("name").(string)
cvpn := d.HasChange("vpn_gateway_address")
cprefixes := d.HasChange("address_space_prefixes")
var found bool
for i, lnet := range netConf.Configuration.LocalNetworkSites {
if lnet.Name == name {
found = true
if cvpn {
netConf.Configuration.LocalNetworkSites[i].VPNGatewayAddress = d.Get("vpn_gateway_address").(string)
}
if cprefixes {
var prefixes []string
for _, prefix := range d.Get("address_space_prefixes").([]interface{}) {
prefixes = append(prefixes, prefix.(string))
}
netConf.Configuration.LocalNetworkSites[i].AddressSpace.AddressPrefix = prefixes
}
break
}
}
// remove the resource from the state of it has been deleted in the meantime:
if !found {
log.Println(fmt.Printf("[INFO] Azure local network '%s' has been deleted remotely. Removimg from Terraform.", name))
d.SetId("")
} else if cvpn || cprefixes {
// else, send the configuration back to Azure:
log.Println("[INFO] Sending updated network configuration back to Azure.")
reqID, err := networkClient.SetVirtualNetworkConfiguration(netConf)
if err != nil {
return fmt.Errorf("Failed setting updated network configuration: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Failed updating the network configuration: %s", err)
}
}
return nil
}
// resourceAzureLocalNetworkConnectionExists does all the necessary API calls
// to check if the local network already exists on Azure.
func resourceAzureLocalNetworkConnectionExists(d *schema.ResourceData, meta interface{}) (bool, error) {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
log.Println("[INFO] Fetching current network configuration from Azure.")
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return false, fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
name := d.Get("name")
for _, lnet := range netConf.Configuration.LocalNetworkSites {
if lnet.Name == name {
return true, nil
}
}
return false, nil
}
// resourceAzureLocalNetworkConnectionDelete does all the necessary API calls
// to delete a local network off Azure.
func resourceAzureLocalNetworkConnectionDelete(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
networkClient := virtualnetwork.NewClient(mgmtClient)
log.Println("[INFO] Fetching current network configuration from Azure.")
azureClient.mutex.Lock()
defer azureClient.mutex.Unlock()
netConf, err := networkClient.GetVirtualNetworkConfiguration()
if err != nil {
return fmt.Errorf("Failed to get the current network configuration from Azure: %s", err)
}
name := d.Get("name").(string)
// search for our local network and remove it if found:
for i, lnet := range netConf.Configuration.LocalNetworkSites {
if lnet.Name == name {
netConf.Configuration.LocalNetworkSites = append(
netConf.Configuration.LocalNetworkSites[:i],
netConf.Configuration.LocalNetworkSites[i+1:]...,
)
break
}
}
// send the configuration back to Azure:
log.Println("[INFO] Sending updated network configuration back to Azure.")
reqID, err := networkClient.SetVirtualNetworkConfiguration(netConf)
if err != nil {
return fmt.Errorf("Failed setting updated network configuration: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Failed updating the network configuration: %s", err)
}
d.SetId("")
return nil
}

View File

@ -0,0 +1,140 @@
package azure
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/management/virtualnetwork"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureLocalNetworkConnectionBasic(t *testing.T) {
name := "azure_local_network_connection.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccAzureLocalNetworkConnectionDestroyed,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureLocalNetworkConnectionBasic,
Check: resource.ComposeTestCheckFunc(
testAccAzureLocalNetworkConnectionExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-local-network-connection"),
resource.TestCheckResourceAttr(name, "vpn_gateway_address", "10.11.12.13"),
resource.TestCheckResourceAttr(name, "address_space_prefixes.0", "10.10.10.0/31"),
resource.TestCheckResourceAttr(name, "address_space_prefixes.1", "10.10.10.1/31"),
),
},
},
})
}
func TestAccAzureLocalNetworkConnectionUpdate(t *testing.T) {
name := "azure_local_network_connection.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccAzureLocalNetworkConnectionDestroyed,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureLocalNetworkConnectionBasic,
Check: resource.ComposeTestCheckFunc(
testAccAzureLocalNetworkConnectionExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-local-network-connection"),
resource.TestCheckResourceAttr(name, "vpn_gateway_address", "10.11.12.13"),
resource.TestCheckResourceAttr(name, "address_space_prefixes.0", "10.10.10.0/31"),
resource.TestCheckResourceAttr(name, "address_space_prefixes.1", "10.10.10.1/31"),
),
},
resource.TestStep{
Config: testAccAzureLocalNetworkConnectionUpdate,
Check: resource.ComposeTestCheckFunc(
testAccAzureLocalNetworkConnectionExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-local-network-connection"),
resource.TestCheckResourceAttr(name, "vpn_gateway_address", "10.11.12.14"),
resource.TestCheckResourceAttr(name, "address_space_prefixes.0", "10.10.10.2/30"),
resource.TestCheckResourceAttr(name, "address_space_prefixes.1", "10.10.10.3/30"),
),
},
},
})
}
// testAccAzureLocalNetworkConnectionExists checks whether the given local network
// connection exists on Azure.
func testAccAzureLocalNetworkConnectionExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Azure Local Network Connection not found: %s", name)
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure Local Network Connection ID not set.")
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
netConf, err := virtualnetwork.NewClient(mgmtClient).GetVirtualNetworkConfiguration()
if err != nil {
return err
}
for _, lnet := range netConf.Configuration.LocalNetworkSites {
if lnet.Name == resource.Primary.ID {
return nil
}
break
}
return fmt.Errorf("Local Network Connection not found: %s", name)
}
}
// testAccAzureLocalNetworkConnectionDestroyed checks whether the local network
// connection has been destroyed on Azure or not.
func testAccAzureLocalNetworkConnectionDestroyed(s *terraform.State) error {
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_local_network_connection" {
continue
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure Local Network Connection ID not set.")
}
netConf, err := virtualnetwork.NewClient(mgmtClient).GetVirtualNetworkConfiguration()
if err != nil {
return err
}
for _, lnet := range netConf.Configuration.LocalNetworkSites {
if lnet.Name == resource.Primary.ID {
return fmt.Errorf("Azure Local Network Connection still exists.")
}
}
}
return nil
}
const testAccAzureLocalNetworkConnectionBasic = `
resource "azure_local_network_connection" "foo" {
name = "terraform-local-network-connection"
vpn_gateway_address = "10.11.12.13"
address_space_prefixes = ["10.10.10.0/31", "10.10.10.1/31"]
}
`
const testAccAzureLocalNetworkConnectionUpdate = `
resource "azure_local_network_connection" "foo" {
name = "terraform-local-network-connection"
vpn_gateway_address = "10.11.12.14"
address_space_prefixes = ["10.10.10.2/30", "10.10.10.3/30"]
}
`

View File

@ -1,22 +1,18 @@
package azure package azure
import ( import (
"bytes"
"fmt" "fmt"
"log" "log"
"strconv"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/networksecuritygroup"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/svanharmelen/azure-sdk-for-go/management"
"github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup"
) )
func resourceAzureSecurityGroup() *schema.Resource { func resourceAzureSecurityGroup() *schema.Resource {
return &schema.Resource{ return &schema.Resource{
Create: resourceAzureSecurityGroupCreate, Create: resourceAzureSecurityGroupCreate,
Read: resourceAzureSecurityGroupRead, Read: resourceAzureSecurityGroupRead,
Update: resourceAzureSecurityGroupUpdate,
Delete: resourceAzureSecurityGroupDelete, Delete: resourceAzureSecurityGroupDelete,
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
@ -38,63 +34,6 @@ func resourceAzureSecurityGroup() *schema.Resource {
Required: true, Required: true,
ForceNew: true, ForceNew: true,
}, },
"rule": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"type": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "Inbound",
},
"priority": &schema.Schema{
Type: schema.TypeInt,
Required: true,
},
"action": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "Allow",
},
"source_cidr": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"source_port": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"destination_cidr": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"destination_port": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"protocol": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "TCP",
},
},
},
Set: resourceAzureSecurityGroupRuleHash,
},
}, },
} }
} }
@ -126,70 +65,9 @@ func resourceAzureSecurityGroupCreate(d *schema.ResourceData, meta interface{})
d.SetId(name) d.SetId(name)
// Create all rules that are configured
if rs := d.Get("rule").(*schema.Set); rs.Len() > 0 {
// Create an empty schema.Set to hold all rules
rules := &schema.Set{
F: resourceAzureSecurityGroupRuleHash,
}
for _, rule := range rs.List() {
// Create a single rule
err := resourceAzureSecurityGroupRuleCreate(d, meta, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
rules.Add(rule)
d.Set("rule", rules)
if err != nil {
return err
}
}
}
return resourceAzureSecurityGroupRead(d, meta) return resourceAzureSecurityGroupRead(d, meta)
} }
func resourceAzureSecurityGroupRuleCreate(
d *schema.ResourceData,
meta interface{},
rule map[string]interface{}) error {
mc := meta.(*Client).mgmtClient
// Make sure all required parameters are there
if err := verifySecurityGroupRuleParams(rule); err != nil {
return err
}
name := rule["name"].(string)
// Create the rule
req, err := networksecuritygroup.NewClient(mc).SetNetworkSecurityGroupRule(d.Id(),
networksecuritygroup.RuleRequest{
Name: name,
Type: networksecuritygroup.RuleType(rule["type"].(string)),
Priority: rule["priority"].(int),
Action: networksecuritygroup.RuleAction(rule["action"].(string)),
SourceAddressPrefix: rule["source_cidr"].(string),
SourcePortRange: rule["source_port"].(string),
DestinationAddressPrefix: rule["destination_cidr"].(string),
DestinationPortRange: rule["destination_port"].(string),
Protocol: networksecuritygroup.RuleProtocol(rule["protocol"].(string)),
},
)
if err != nil {
return fmt.Errorf("Error creating Network Security Group rule %s: %s", name, err)
}
if err := mc.WaitForOperation(req, nil); err != nil {
return fmt.Errorf(
"Error waiting for Network Security Group rule %s to be created: %s", name, err)
}
return nil
}
func resourceAzureSecurityGroupRead(d *schema.ResourceData, meta interface{}) error { func resourceAzureSecurityGroupRead(d *schema.ResourceData, meta interface{}) error {
mc := meta.(*Client).mgmtClient mc := meta.(*Client).mgmtClient
@ -205,70 +83,9 @@ func resourceAzureSecurityGroupRead(d *schema.ResourceData, meta interface{}) er
d.Set("label", sg.Label) d.Set("label", sg.Label)
d.Set("location", sg.Location) d.Set("location", sg.Location)
// Create an empty schema.Set to hold all rules
rules := &schema.Set{
F: resourceAzureSecurityGroupRuleHash,
}
for _, r := range sg.Rules {
if !r.IsDefault {
rule := map[string]interface{}{
"name": r.Name,
"type": string(r.Type),
"priority": r.Priority,
"action": string(r.Action),
"source_cidr": r.SourceAddressPrefix,
"source_port": r.SourcePortRange,
"destination_cidr": r.DestinationAddressPrefix,
"destination_port": r.DestinationPortRange,
"protocol": string(r.Protocol),
}
rules.Add(rule)
}
}
d.Set("rule", rules)
return nil return nil
} }
func resourceAzureSecurityGroupUpdate(d *schema.ResourceData, meta interface{}) error {
// Check if the rule set as a whole has changed
if d.HasChange("rule") {
o, n := d.GetChange("rule")
ors := o.(*schema.Set).Difference(n.(*schema.Set))
nrs := n.(*schema.Set).Difference(o.(*schema.Set))
// Now first loop through all the old rules and delete any obsolete ones
for _, rule := range ors.List() {
// Delete the rule as it no longer exists in the config
err := resourceAzureSecurityGroupRuleDelete(d, meta, rule.(map[string]interface{}))
if err != nil {
return err
}
}
// Make sure we save the state of the currently configured rules
rules := o.(*schema.Set).Intersection(n.(*schema.Set))
d.Set("rule", rules)
// Then loop through al the currently configured rules and create the new ones
for _, rule := range nrs.List() {
err := resourceAzureSecurityGroupRuleCreate(d, meta, rule.(map[string]interface{}))
// We need to update this first to preserve the correct state
rules.Add(rule)
d.Set("rule", rules)
if err != nil {
return err
}
}
}
return resourceAzureSecurityGroupRead(d, meta)
}
func resourceAzureSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error { func resourceAzureSecurityGroupDelete(d *schema.ResourceData, meta interface{}) error {
mc := meta.(*Client).mgmtClient mc := meta.(*Client).mgmtClient
@ -288,66 +105,3 @@ func resourceAzureSecurityGroupDelete(d *schema.ResourceData, meta interface{})
return nil return nil
} }
func resourceAzureSecurityGroupRuleDelete(
d *schema.ResourceData, meta interface{}, rule map[string]interface{}) error {
mc := meta.(*Client).mgmtClient
name := rule["name"].(string)
// Delete the rule
req, err := networksecuritygroup.NewClient(mc).DeleteNetworkSecurityGroupRule(d.Id(), name)
if err != nil {
if management.IsResourceNotFoundError(err) {
return nil
}
return fmt.Errorf("Error deleting Network Security Group rule %s: %s", name, err)
}
if err := mc.WaitForOperation(req, nil); err != nil {
return fmt.Errorf(
"Error waiting for Network Security Group rule %s to be deleted: %s", name, err)
}
return nil
}
func resourceAzureSecurityGroupRuleHash(v interface{}) int {
var buf bytes.Buffer
m := v.(map[string]interface{})
buf.WriteString(fmt.Sprintf(
"%s-%d-%s-%s-%s-%s-%s-%s",
m["type"].(string),
m["priority"].(int),
m["action"].(string),
m["source_cidr"].(string),
m["source_port"].(string),
m["destination_cidr"].(string),
m["destination_port"].(string),
m["protocol"].(string)))
return hashcode.String(buf.String())
}
func verifySecurityGroupRuleParams(rule map[string]interface{}) error {
typ := rule["type"].(string)
if typ != "Inbound" && typ != "Outbound" {
return fmt.Errorf("Parameter type only accepts 'Inbound' or 'Outbound' as values")
}
action := rule["action"].(string)
if action != "Allow" && action != "Deny" {
return fmt.Errorf("Parameter action only accepts 'Allow' or 'Deny' as values")
}
protocol := rule["protocol"].(string)
if protocol != "TCP" && protocol != "UDP" && protocol != "*" {
_, err := strconv.ParseInt(protocol, 0, 0)
if err != nil {
return fmt.Errorf(
"Parameter type only accepts 'TCP', 'UDP' or '*' as values")
}
}
return nil
}

View File

@ -0,0 +1,315 @@
package azure
import (
"fmt"
"log"
"github.com/Azure/azure-sdk-for-go/management"
netsecgroup "github.com/Azure/azure-sdk-for-go/management/networksecuritygroup"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureSecurityGroupRule returns the *schema.Resource for
// a network security group rule on Azure.
func resourceAzureSecurityGroupRule() *schema.Resource {
return &schema.Resource{
Create: resourceAzureSecurityGroupRuleCreate,
Read: resourceAzureSecurityGroupRuleRead,
Update: resourceAzureSecurityGroupRuleUpdate,
Exists: resourceAzureSecurityGroupRuleExists,
Delete: resourceAzureSecurityGroupRuleDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["name"],
},
"security_group_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["netsecgroup_secgroup_name"],
},
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: parameterDescriptions["netsecgroup_type"],
},
"priority": &schema.Schema{
Type: schema.TypeInt,
Required: true,
Description: parameterDescriptions["netsecgroup_priority"],
},
"action": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: parameterDescriptions["netsecgroup_action"],
},
"source_address_prefix": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: parameterDescriptions["netsecgroup_src_addr_prefix"],
},
"source_port_range": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: parameterDescriptions["netsecgroup_src_port_range"],
},
"destination_address_prefix": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: parameterDescriptions["netsecgroup_dest_addr_prefix"],
},
"destination_port_range": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: parameterDescriptions["netsecgroup_dest_port_range"],
},
"protocol": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: parameterDescriptions["netsecgroup_protocol"],
},
},
}
}
// resourceAzureSecurityGroupRuleCreate does all the necessary API calls to
// create a new network security group rule on Azure.
func resourceAzureSecurityGroupRuleCreate(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
netSecClient := netsecgroup.NewClient(mgmtClient)
// create and configure the RuleResponse:
name := d.Get("name").(string)
rule := netsecgroup.RuleRequest{
Name: name,
Type: netsecgroup.RuleType(d.Get("type").(string)),
Priority: d.Get("priority").(int),
Action: netsecgroup.RuleAction(d.Get("action").(string)),
SourceAddressPrefix: d.Get("source_address_prefix").(string),
SourcePortRange: d.Get("source_port_range").(string),
DestinationAddressPrefix: d.Get("destination_address_prefix").(string),
DestinationPortRange: d.Get("destination_port_range").(string),
Protocol: netsecgroup.RuleProtocol(d.Get("protocol").(string)),
}
// send the create request to Azure:
log.Println("[INFO] Sending network security group rule creation request to Azure.")
reqID, err := netSecClient.SetNetworkSecurityGroupRule(
d.Get("security_group_name").(string),
rule,
)
if err != nil {
return fmt.Errorf("Error sending network security group rule creation request to Azure: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error creating network security group rule on Azure: %s", err)
}
d.SetId(name)
return nil
}
// resourceAzureSecurityGroupRuleRead does all the necessary API calls to
// read the state of a network security group ruke off Azure.
func resourceAzureSecurityGroupRuleRead(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
netSecClient := netsecgroup.NewClient(mgmtClient)
secGroupName := d.Get("security_group_name").(string)
// get info on the network security group and check its rules for this one:
log.Println("[INFO] Sending network security group rule query to Azure.")
secgroup, err := netSecClient.GetNetworkSecurityGroup(secGroupName)
if err != nil {
if !management.IsResourceNotFoundError(err) {
return fmt.Errorf("Error issuing network security group rules query: %s", err)
} else {
// it meants that the network security group this rule belonged to has
// been deleted; so we must remove this resource from the schema:
d.SetId("")
return nil
}
}
// find our security rule:
var found bool
name := d.Get("name").(string)
for _, rule := range secgroup.Rules {
if rule.Name == name {
found = true
log.Println("[DEBUG] Reading state of Azure network security group rule.")
d.Set("type", rule.Type)
d.Set("priority", rule.Priority)
d.Set("action", rule.Action)
d.Set("source_address_prefix", rule.SourceAddressPrefix)
d.Set("source_port_range", rule.SourcePortRange)
d.Set("destination_address_prefix", rule.DestinationAddressPrefix)
d.Set("destination_port_range", rule.DestinationPortRange)
d.Set("protocol", rule.Protocol)
break
}
}
// check if the rule still exists, and is not, remove the resource:
if !found {
d.SetId("")
}
return nil
}
// resourceAzureSecurityGroupRuleUpdate does all the necessary API calls to
// update the state of a network security group ruke off Azure.
func resourceAzureSecurityGroupRuleUpdate(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
netSecClient := netsecgroup.NewClient(mgmtClient)
secGroupName := d.Get("security_group_name").(string)
// get info on the network security group and check its rules for this one:
log.Println("[INFO] Sending network security group rule query for update to Azure.")
secgroup, err := netSecClient.GetNetworkSecurityGroup(secGroupName)
if err != nil {
if !management.IsResourceNotFoundError(err) {
return fmt.Errorf("Error issuing network security group rules query: %s", err)
} else {
// it meants that the network security group this rule belonged to has
// been deleted; so we must remove this resource from the schema:
d.SetId("")
return nil
}
}
// try and find our security group rule:
var found bool
name := d.Get("name").(string)
for _, rule := range secgroup.Rules {
if rule.Name == name {
found = true
}
}
// check is the resource has not been deleted in the meantime:
if !found {
// if not; remove the resource:
d.SetId("")
return nil
}
// else, start building up the rule request struct:
newRule := netsecgroup.RuleRequest{
Name: d.Get("name").(string),
Type: netsecgroup.RuleType(d.Get("type").(string)),
Priority: d.Get("priority").(int),
Action: netsecgroup.RuleAction(d.Get("action").(string)),
SourceAddressPrefix: d.Get("source_address_prefix").(string),
SourcePortRange: d.Get("source_port_range").(string),
DestinationAddressPrefix: d.Get("destination_address_prefix").(string),
DestinationPortRange: d.Get("destination_port_range").(string),
Protocol: netsecgroup.RuleProtocol(d.Get("protocol").(string)),
}
// send the create request to Azure:
log.Println("[INFO] Sending network security group rule update request to Azure.")
reqID, err := netSecClient.SetNetworkSecurityGroupRule(
secGroupName,
newRule,
)
if err != nil {
return fmt.Errorf("Error sending network security group rule update request to Azure: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error updating network security group rule on Azure: %s", err)
}
return nil
}
// resourceAzureSecurityGroupRuleExists does all the necessary API calls to
// check for the existence of the network security group rule on Azure.
func resourceAzureSecurityGroupRuleExists(d *schema.ResourceData, meta interface{}) (bool, error) {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
netSecClient := netsecgroup.NewClient(mgmtClient)
secGroupName := d.Get("security_group_name").(string)
// get info on the network security group and search for our rule:
log.Println("[INFO] Sending network security group rule query for existence check to Azure.")
secgroup, err := netSecClient.GetNetworkSecurityGroup(secGroupName)
if err != nil {
if !management.IsResourceNotFoundError(err) {
return false, fmt.Errorf("Error issuing network security group rules query: %s", err)
} else {
// it meants that the network security group this rule belonged to has
// been deleted; so we must remove this resource from the schema:
d.SetId("")
return false, nil
}
}
// try and find our security group rule:
name := d.Get("name").(string)
for _, rule := range secgroup.Rules {
if rule.Name == name {
return true, nil
}
}
// if here; it means the resource has been deleted in the
// meantime and must be removed from the schema:
d.SetId("")
return false, nil
}
// resourceAzureSecurityGroupRuleDelete does all the necessary API calls to
// delete a network security group rule off Azure.
func resourceAzureSecurityGroupRuleDelete(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
netSecClient := netsecgroup.NewClient(mgmtClient)
secGroupName := d.Get("security_group_name").(string)
// get info on the network security group and search for our rule:
log.Println("[INFO] Sending network security group rule query for deletion to Azure.")
secgroup, err := netSecClient.GetNetworkSecurityGroup(secGroupName)
if err != nil {
if management.IsResourceNotFoundError(err) {
// it meants that the network security group this rule belonged to has
// been deleted; so we need do nothing more but stop tracking the resource:
d.SetId("")
return nil
} else {
return fmt.Errorf("Error issuing network security group rules query: %s", err)
}
}
// check is the resource has not been deleted in the meantime:
name := d.Get("name").(string)
for _, rule := range secgroup.Rules {
if rule.Name == name {
// if not; we shall issue the delete:
reqID, err := netSecClient.DeleteNetworkSecurityGroupRule(secGroupName, name)
if err != nil {
return fmt.Errorf("Error sending network security group rule delete request to Azure: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error deleting network security group rule off Azure: %s", err)
}
}
}
return nil
}

View File

@ -0,0 +1,109 @@
package azure
import (
"fmt"
"testing"
netsecgroup "github.com/Azure/azure-sdk-for-go/management/networksecuritygroup"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureSecurityGroupRule(t *testing.T) {
name := "azure_security_group_rule.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureSecurityGroupRuleDeleted,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureSecurityGroupRule,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureSecurityGroupRuleExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-secgroup-rule"),
resource.TestCheckResourceAttr(name, "security_group_name", testAccSecurityGroupName),
resource.TestCheckResourceAttr(name, "type", "Inbound"),
resource.TestCheckResourceAttr(name, "action", "Deny"),
resource.TestCheckResourceAttr(name, "priority", "200"),
resource.TestCheckResourceAttr(name, "source_address_prefix", "100.0.0.0/32"),
resource.TestCheckResourceAttr(name, "source_port_range", "1000"),
resource.TestCheckResourceAttr(name, "destination_address_prefix", "10.0.0.0/32"),
resource.TestCheckResourceAttr(name, "protocol", "TCP"),
),
},
},
})
}
func testAccCheckAzureSecurityGroupRuleExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Azure security group rule not found: %s", name)
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure network security group rule ID not set: %s", name)
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
secGroupClient := netsecgroup.NewClient(mgmtClient)
secGroup, err := secGroupClient.GetNetworkSecurityGroup(testAccSecurityGroupName)
if err != nil {
return fmt.Errorf("Failed getting network security group details: %s", err)
}
for _, rule := range secGroup.Rules {
if rule.Name == resource.Primary.ID {
return nil
}
}
return fmt.Errorf("Azure security group rule doesn't exist: %s", name)
}
}
func testAccCheckAzureSecurityGroupRuleDeleted(s *terraform.State) error {
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_security_group_rule" {
continue
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure network security group ID not set.")
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
secGroupClient := netsecgroup.NewClient(mgmtClient)
secGroup, err := secGroupClient.GetNetworkSecurityGroup(testAccSecurityGroupName)
if err != nil {
return fmt.Errorf("Failed getting network security group details: %s", err)
}
for _, rule := range secGroup.Rules {
if rule.Name == resource.Primary.ID {
return fmt.Errorf("Azure network security group rule still exists!")
}
}
}
return nil
}
var testAccAzureSecurityGroupRule = testAccAzureSecurityGroupConfig + `
resource "azure_security_group_rule" "foo" {
name = "terraform-secgroup-rule"
security_group_name = "${azure_security_group.foo.name}"
type = "Inbound"
action = "Deny"
priority = 200
source_address_prefix = "100.0.0.0/32"
source_port_range = "1000"
destination_address_prefix = "10.0.0.0/32"
destination_port_range = "1000"
protocol = "TCP"
}
`

View File

@ -4,10 +4,10 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/networksecuritygroup"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/svanharmelen/azure-sdk-for-go/management"
"github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup"
) )
func TestAccAzureSecurityGroup_basic(t *testing.T) { func TestAccAzureSecurityGroup_basic(t *testing.T) {
@ -19,72 +19,16 @@ func TestAccAzureSecurityGroup_basic(t *testing.T) {
CheckDestroy: testAccCheckAzureSecurityGroupDestroy, CheckDestroy: testAccCheckAzureSecurityGroupDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: testAccAzureSecurityGroup_basic, Config: testAccAzureSecurityGroupConfig,
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckAzureSecurityGroupExists( testAccCheckAzureSecurityGroupExists(
"azure_security_group.foo", &group), "azure_security_group.foo", &group),
testAccCheckAzureSecurityGroupBasicAttributes(&group),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_security_group.foo", "name", "terraform-security-group"), "azure_security_group.foo", "name", "terraform-security-group"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_security_group.foo", "location", "West US"), "azure_security_group.foo", "location", "West US"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.936204579.name", "RDP"), "azure_security_group.foo", "label", "terraform testing security group"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.936204579.source_port", "*"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.936204579.destination_port", "3389"),
),
},
},
})
}
func TestAccAzureSecurityGroup_update(t *testing.T) {
var group networksecuritygroup.SecurityGroupResponse
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureSecurityGroupDestroy,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureSecurityGroup_basic,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureSecurityGroupExists(
"azure_security_group.foo", &group),
testAccCheckAzureSecurityGroupBasicAttributes(&group),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "name", "terraform-security-group"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "location", "West US"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.936204579.name", "RDP"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.936204579.source_cidr", "*"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.936204579.destination_port", "3389"),
),
},
resource.TestStep{
Config: testAccAzureSecurityGroup_update,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureSecurityGroupExists(
"azure_security_group.foo", &group),
testAccCheckAzureSecurityGroupUpdatedAttributes(&group),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.3322523298.name", "RDP"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.3322523298.source_cidr", "192.168.0.0/24"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.3322523298.destination_port", "3389"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.3929353075.name", "WINRM"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.3929353075.source_cidr", "192.168.0.0/24"),
resource.TestCheckResourceAttr(
"azure_security_group.foo", "rule.3929353075.destination_port", "5985"),
), ),
}, },
}, },
@ -120,89 +64,6 @@ func testAccCheckAzureSecurityGroupExists(
} }
} }
func testAccCheckAzureSecurityGroupBasicAttributes(
group *networksecuritygroup.SecurityGroupResponse) resource.TestCheckFunc {
return func(s *terraform.State) error {
if group.Name != "terraform-security-group" {
return fmt.Errorf("Bad name: %s", group.Name)
}
for _, r := range group.Rules {
if !r.IsDefault {
if r.Name != "RDP" {
return fmt.Errorf("Bad rule name: %s", r.Name)
}
if r.Priority != 101 {
return fmt.Errorf("Bad rule priority: %d", r.Priority)
}
if r.SourceAddressPrefix != "*" {
return fmt.Errorf("Bad source CIDR: %s", r.SourceAddressPrefix)
}
if r.DestinationAddressPrefix != "*" {
return fmt.Errorf("Bad destination CIDR: %s", r.DestinationAddressPrefix)
}
if r.DestinationPortRange != "3389" {
return fmt.Errorf("Bad destination port: %s", r.DestinationPortRange)
}
}
}
return nil
}
}
func testAccCheckAzureSecurityGroupUpdatedAttributes(
group *networksecuritygroup.SecurityGroupResponse) resource.TestCheckFunc {
return func(s *terraform.State) error {
if group.Name != "terraform-security-group" {
return fmt.Errorf("Bad name: %s", group.Name)
}
foundRDP := false
foundWINRM := false
for _, r := range group.Rules {
if !r.IsDefault {
if r.Name == "RDP" {
if r.SourceAddressPrefix != "192.168.0.0/24" {
return fmt.Errorf("Bad source CIDR: %s", r.SourceAddressPrefix)
}
foundRDP = true
}
if r.Name == "WINRM" {
if r.Priority != 102 {
return fmt.Errorf("Bad rule priority: %d", r.Priority)
}
if r.SourceAddressPrefix != "192.168.0.0/24" {
return fmt.Errorf("Bad source CIDR: %s", r.SourceAddressPrefix)
}
if r.DestinationAddressPrefix != "*" {
return fmt.Errorf("Bad destination CIDR: %s", r.DestinationAddressPrefix)
}
if r.DestinationPortRange != "5985" {
return fmt.Errorf("Bad destination port: %s", r.DestinationPortRange)
}
foundWINRM = true
}
}
}
if !foundRDP {
return fmt.Errorf("RDP rule not found")
}
if !foundWINRM {
return fmt.Errorf("WINRM rule not found")
}
return nil
}
}
func testAccCheckAzureSecurityGroupDestroy(s *terraform.State) error { func testAccCheckAzureSecurityGroupDestroy(s *terraform.State) error {
mc := testAccProvider.Meta().(*Client).mgmtClient mc := testAccProvider.Meta().(*Client).mgmtClient
@ -228,44 +89,9 @@ func testAccCheckAzureSecurityGroupDestroy(s *terraform.State) error {
return nil return nil
} }
const testAccAzureSecurityGroup_basic = ` var testAccAzureSecurityGroupConfig = fmt.Sprintf(`
resource "azure_security_group" "foo" { resource "azure_security_group" "foo" {
name = "terraform-security-group" name = "%s"
location = "West US" location = "West US"
label = "terraform testing security group"
rule { }`, testAccSecurityGroupName)
name = "RDP"
priority = 101
source_cidr = "*"
source_port = "*"
destination_cidr = "*"
destination_port = "3389"
protocol = "TCP"
}
}`
const testAccAzureSecurityGroup_update = `
resource "azure_security_group" "foo" {
name = "terraform-security-group"
location = "West US"
rule {
name = "RDP"
priority = 101
source_cidr = "192.168.0.0/24"
source_port = "*"
destination_cidr = "*"
destination_port = "3389"
protocol = "TCP"
}
rule {
name = "WINRM"
priority = 102
source_cidr = "192.168.0.0/24"
source_port = "*"
destination_cidr = "*"
destination_port = "5985"
protocol = "TCP"
}
}`

View File

@ -0,0 +1,183 @@
package azure
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureStorageBlob returns the *schema.Resource associated
// with a storage blob on Azure.
func resourceAzureStorageBlob() *schema.Resource {
return &schema.Resource{
Create: resourceAzureStorageBlobCreate,
Read: resourceAzureStorageBlobRead,
Update: resourceAzureStorageBlobUpdate,
Exists: resourceAzureStorageBlobExists,
Delete: resourceAzureStorageBlobDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["name"],
},
"type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["type"],
},
"size": &schema.Schema{
Type: schema.TypeInt,
Required: true,
ForceNew: true,
DefaultFunc: func() (interface{}, error) {
return int64(0), nil
},
},
"storage_container_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["storage_container_name"],
},
"storage_service_name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["storage_service_name"],
},
"url": &schema.Schema{
Type: schema.TypeString,
Computed: true,
Description: parameterDescriptions["url"],
},
},
}
}
// resourceAzureStorageBlobCreate does all the necessary API calls to
// create the storage blob on Azure.
func resourceAzureStorageBlobCreate(d *schema.ResourceData, meta interface{}) error {
mgmtClient := meta.(*Client).mgmtClient
storName := d.Get("storage_service_name").(string)
blobClient, err := getStorageServiceBlobClient(mgmtClient, storName)
if err != nil {
return err
}
log.Println("[INFO] Issuing create on Azure storage blob.")
name := d.Get("name").(string)
blobType := d.Get("type").(string)
cont := d.Get("storage_container_name").(string)
switch blobType {
case "BlockBlob":
err = blobClient.CreateBlockBlob(cont, name)
case "PageBlob":
size := int64(d.Get("size").(int))
err = blobClient.PutPageBlob(cont, name, size)
default:
err = fmt.Errorf("Invalid blob type specified; see parameter desciptions for more info.")
}
if err != nil {
return fmt.Errorf("Error creating storage blob on Azure: %s", err)
}
d.SetId(name)
return resourceAzureStorageBlobRead(d, meta)
}
// resourceAzureStorageBlobRead does all the necessary API calls to
// read the status of the storage blob off Azure.
func resourceAzureStorageBlobRead(d *schema.ResourceData, meta interface{}) error {
// check for it's existence:
exists, err := resourceAzureStorageBlobExists(d, meta)
if err != nil {
return err
}
// if it exists; read relevant information:
if exists {
mgmtClient := meta.(*Client).mgmtClient
storName := d.Get("storage_service_name").(string)
blobClient, err := getStorageServiceBlobClient(mgmtClient, storName)
if err != nil {
return err
}
name := d.Get("name").(string)
cont := d.Get("storage_container_name").(string)
url := blobClient.GetBlobURL(cont, name)
d.Set("url", url)
}
// NOTE: no need to unset the ID here, as resourceAzureStorageBlobExists
// already should have done so if it were required.
return nil
}
// resourceAzureStorageBlobUpdate does all the necessary API calls to
// update a blob on Azure.
func resourceAzureStorageBlobUpdate(d *schema.ResourceData, meta interface{}) error {
// NOTE: although empty as most paramters have ForceNew set; this is
// still required in case of changes to the storage_service_key
// run the ExistsFunc beforehand to ensure the resource's existence nonetheless:
_, err := resourceAzureStorageBlobExists(d, meta)
return err
}
// resourceAzureStorageBlobExists does all the necessary API calls to
// check for the existence of the blob on Azure.
func resourceAzureStorageBlobExists(d *schema.ResourceData, meta interface{}) (bool, error) {
mgmtClient := meta.(*Client).mgmtClient
storName := d.Get("storage_service_name").(string)
blobClient, err := getStorageServiceBlobClient(mgmtClient, storName)
if err != nil {
return false, err
}
log.Println("[INFO] Querying Azure for storage blob's existence.")
name := d.Get("name").(string)
cont := d.Get("storage_container_name").(string)
exists, err := blobClient.BlobExists(cont, name)
if err != nil {
return false, fmt.Errorf("Error whilst checking for Azure storage blob's existence: %s", err)
}
// if not found; it means it was deleted in the meantime and
// we must remove it from the schema.
if !exists {
d.SetId("")
}
return exists, nil
}
// resourceAzureStorageBlobDelete does all the necessary API calls to
// delete the blob off Azure.
func resourceAzureStorageBlobDelete(d *schema.ResourceData, meta interface{}) error {
mgmtClient := meta.(*Client).mgmtClient
storName := d.Get("storage_service_name").(string)
blobClient, err := getStorageServiceBlobClient(mgmtClient, storName)
if err != nil {
return err
}
log.Println("[INFO] Issuing storage blob delete command off Azure.")
name := d.Get("name").(string)
cont := d.Get("storage_container_name").(string)
if _, err = blobClient.DeleteBlobIfExists(cont, name); err != nil {
return fmt.Errorf("Error whilst deleting storage blob: %s", err)
}
d.SetId("")
return nil
}

View File

@ -0,0 +1,153 @@
package azure
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureStorageBlockBlob(t *testing.T) {
name := "azure_storage_blob.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureStorageBlobDeleted("block"),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureStorageBlockBlobConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureStorageBlobExists(name, "block"),
resource.TestCheckResourceAttr(name, "name", "tftesting-blob"),
resource.TestCheckResourceAttr(name, "type", "BlockBlob"),
resource.TestCheckResourceAttr(name, "storage_container_name",
fmt.Sprintf("%s-block", testAccStorageContainerName)),
resource.TestCheckResourceAttr(name, "storage_service_name", testAccStorageServiceName),
),
},
},
})
}
func TestAccAzureStoragePageBlob(t *testing.T) {
name := "azure_storage_blob.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureStorageBlobDeleted("page"),
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureStoragePageBlobConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureStorageBlobExists(name, "page"),
resource.TestCheckResourceAttr(name, "name", "tftesting-blob"),
resource.TestCheckResourceAttr(name, "type", "PageBlob"),
resource.TestCheckResourceAttr(name, "size", "512"),
resource.TestCheckResourceAttr(name, "storage_container_name",
fmt.Sprintf("%s-page", testAccStorageContainerName)),
resource.TestCheckResourceAttr(name, "storage_service_name", testAccStorageServiceName),
),
},
},
})
}
func testAccCheckAzureStorageBlobExists(name, typ string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Azure Storage Container resource not found: %s", name)
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure Storage Container ID not set: %s", name)
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
blobClient, err := getStorageServiceBlobClient(mgmtClient, testAccStorageServiceName)
if err != nil {
return err
}
exists, err := blobClient.BlobExists(fmt.Sprintf("%s-%s", testAccStorageContainerName, typ),
resource.Primary.ID)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("Azure Storage Blob %s doesn't exist.", name)
}
return nil
}
}
func testAccCheckAzureStorageBlobDeleted(typ string) resource.TestCheckFunc {
return func(s *terraform.State) error {
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_storage_blob" {
continue
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
blobClient, err := getStorageServiceBlobClient(mgmtClient, testAccStorageServiceName)
if err != nil {
return err
}
exists, err := blobClient.BlobExists(fmt.Sprintf("%s-%s", testAccStorageContainerName,
typ), resource.Primary.ID)
if err != nil {
return err
}
if exists {
return fmt.Errorf("Azure Storage Blob still exists.")
}
}
return nil
}
}
var testAccAzureStorageBlockBlobConfig = fmt.Sprintf(`
resource "azure_storage_container" "foo" {
name = "%s-block"
container_access_type = "blob"
# NOTE: A pre-existing Storage Service is used here so as to avoid
# the huge wait for creation of one.
storage_service_name = "%s"
}
resource "azure_storage_blob" "foo" {
name = "tftesting-blob"
type = "BlockBlob"
# NOTE: A pre-existing Storage Service is used here so as to avoid
# the huge wait for creation of one.
storage_service_name = "${azure_storage_container.foo.storage_service_name}"
storage_container_name = "${azure_storage_container.foo.name}"
}
`, testAccStorageContainerName, testAccStorageServiceName)
var testAccAzureStoragePageBlobConfig = fmt.Sprintf(`
resource "azure_storage_container" "foo" {
name = "%s-page"
container_access_type = "blob"
# NOTE: A pre-existing Storage Service is used here so as to avoid
# the huge wait for creation of one.
storage_service_name = "%s"
}
resource "azure_storage_blob" "foo" {
name = "tftesting-blob"
type = "PageBlob"
# NOTE: A pre-existing Storage Service is used here so as to avoid
# the huge wait for creation of one.
storage_service_name = "${azure_storage_container.foo.storage_service_name}"
storage_container_name = "${azure_storage_container.foo.name}"
# NOTE: must be a multiple of 512:
size = 512
}
`, testAccStorageContainerName, testAccStorageServiceName)

View File

@ -0,0 +1,163 @@
package azure
import (
"fmt"
"log"
"github.com/Azure/azure-sdk-for-go/storage"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureStorageContainer returns the *schema.Resource associated
// to a storage container on Azure.
func resourceAzureStorageContainer() *schema.Resource {
return &schema.Resource{
Create: resourceAzureStorageContainerCreate,
Read: resourceAzureStorageContainerRead,
Exists: resourceAzureStorageContainerExists,
Delete: resourceAzureStorageContainerDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["name"],
},
"storage_service_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["storage_service_name"],
},
"container_access_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["container_access_type"],
},
"properties": &schema.Schema{
Type: schema.TypeMap,
Computed: true,
Elem: schema.TypeString,
Description: parameterDescriptions["properties"],
},
},
}
}
// resourceAzureStorageContainerCreate does all the necessary API calls to
// create the storage container on Azure.
func resourceAzureStorageContainerCreate(d *schema.ResourceData, meta interface{}) error {
mgmtClient := meta.(*Client).mgmtClient
storName := d.Get("storage_service_name").(string)
blobClient, err := getStorageServiceBlobClient(mgmtClient, storName)
if err != nil {
return err
}
log.Println("[INFO] Creating storage container on Azure.")
name := d.Get("name").(string)
accessType := storage.ContainerAccessType(d.Get("container_access_type").(string))
err = blobClient.CreateContainer(name, accessType)
if err != nil {
return fmt.Errorf("Failed to create storage container on Azure: %s", err)
}
d.SetId(name)
return resourceAzureStorageContainerRead(d, meta)
}
// resourceAzureStorageContainerRead does all the necessary API calls to
// read the status of the storage container off Azure.
func resourceAzureStorageContainerRead(d *schema.ResourceData, meta interface{}) error {
mgmtClient := meta.(*Client).mgmtClient
storName := d.Get("storage_service_name").(string)
blobClient, err := getStorageServiceBlobClient(mgmtClient, storName)
if err != nil {
return err
}
log.Println("[INFO] Querying Azure for storage containers.")
name := d.Get("name").(string)
containers, err := blobClient.ListContainers(storage.ListContainersParameters{
Prefix: name,
Timeout: 90,
})
if err != nil {
return fmt.Errorf("Failed to query Azure for its storage containers: %s", err)
}
// search for our storage container and update its stats:
var found bool
// loop just to make sure we got the right container:
for _, cont := range containers.Containers {
if cont.Name == name {
found = true
props := make(map[string]interface{})
props["last_modified"] = cont.Properties.LastModified
props["lease_status"] = cont.Properties.LeaseStatus
props["lease_state"] = cont.Properties.LeaseState
props["lease_duration"] = cont.Properties.LeaseDuration
d.Set("properties", props)
}
}
// if not found; it means the resource has been deleted
// in the meantime; so we must untrack it:
if !found {
d.SetId("")
}
return nil
}
// resourceAzureStorageContainerExists does all the necessary API calls to
// check if the storage container already exists on Azure.
func resourceAzureStorageContainerExists(d *schema.ResourceData, meta interface{}) (bool, error) {
mgmtClient := meta.(*Client).mgmtClient
storName := d.Get("storage_service_name").(string)
blobClient, err := getStorageServiceBlobClient(mgmtClient, storName)
if err != nil {
return false, err
}
log.Println("[INFO] Checking existence of storage container on Azure.")
name := d.Get("name").(string)
exists, err := blobClient.ContainerExists(name)
if err != nil {
return false, fmt.Errorf("Failed to query for Azure storage container existence: %s", err)
}
// if it does not exist; untrack the resource:
if !exists {
d.SetId("")
}
return exists, nil
}
// resourceAzureStorageContainerDelete does all the necessary API calls to
// delete a storage container off Azure.
func resourceAzureStorageContainerDelete(d *schema.ResourceData, meta interface{}) error {
mgmtClient := meta.(*Client).mgmtClient
storName := d.Get("storage_service_name").(string)
blobClient, err := getStorageServiceBlobClient(mgmtClient, storName)
if err != nil {
return err
}
log.Println("[INFO] Issuing Azure storage container deletion call.")
name := d.Get("name").(string)
if _, err := blobClient.DeleteContainerIfExists(name); err != nil {
return fmt.Errorf("Failed deleting storage container off Azure: %s", err)
}
d.SetId("")
return nil
}

View File

@ -0,0 +1,97 @@
package azure
import (
"fmt"
"testing"
"time"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureStorageContainer(t *testing.T) {
name := "azure_storage_container.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureStorageContainerDestroyed,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureStorageContainerConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureStorageContainerExists(name),
resource.TestCheckResourceAttr(name, "name", testAccStorageContainerName),
resource.TestCheckResourceAttr(name, "storage_service_name", testAccStorageServiceName),
resource.TestCheckResourceAttr(name, "container_access_type", "blob"),
),
},
},
})
// because containers take a while to get deleted, sleep for one minute:
time.Sleep(3 * time.Minute)
}
func testAccCheckAzureStorageContainerExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Azure Storage Container resource not found: %s", name)
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure Storage Container ID not set: %s", name)
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
blobClient, err := getStorageServiceBlobClient(mgmtClient, testAccStorageServiceName)
if err != nil {
return err
}
exists, err := blobClient.ContainerExists(resource.Primary.ID)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("Azure Storage Container %s doesn't exist.", name)
}
return nil
}
}
func testAccCheckAzureStorageContainerDestroyed(s *terraform.State) error {
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_storage_container" {
continue
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
blobClient, err := getStorageServiceBlobClient(mgmtClient, testAccStorageServiceName)
if err != nil {
return err
}
exists, err := blobClient.ContainerExists(resource.Primary.ID)
if err != nil {
return err
}
if exists {
return fmt.Errorf("Azure Storage Container still exists.")
}
}
return nil
}
var testAccAzureStorageContainerConfig = fmt.Sprintf(`
resource "azure_storage_container" "foo" {
name = "%s"
container_access_type = "blob"
# NOTE: A pre-existing Storage Service is used here so as to avoid
# the huge wait for creation of one.
storage_service_name = "%s"
}
`, testAccStorageContainerName, testAccStorageServiceName)

View File

@ -0,0 +1,103 @@
package azure
import (
"fmt"
"log"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureStorageQueue returns the *schema.Resource associated
// to a storage queue on Azure.
func resourceAzureStorageQueue() *schema.Resource {
return &schema.Resource{
Create: resourceAzureStorageQueueCreate,
Read: resourceAzureStorageQueueRead,
Delete: resourceAzureStorageQueueDelete,
Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["name"],
},
"storage_service_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["storage_service_name"],
},
},
}
}
// resourceAzureStorageQueueCreate does all the necessary API calls to
// create a storage queue on Azure.
func resourceAzureStorageQueueCreate(d *schema.ResourceData, meta interface{}) error {
mgmtClient := meta.(*Client).mgmtClient
storServName := d.Get("storage_service_name").(string)
queueClient, err := getStorageServiceQueueClient(mgmtClient, storServName)
if err != nil {
return err
}
// create the queue:
log.Println("Sending Storage Queue creation request to Azure.")
name := d.Get("name").(string)
err = queueClient.CreateQueue(name)
if err != nil {
return fmt.Errorf("Error creation Storage Queue on Azure: %s", err)
}
d.SetId(name)
return nil
}
// resourceAzureStorageQueueRead does all the necessary API calls to
// read the state of the storage queue off Azure.
func resourceAzureStorageQueueRead(d *schema.ResourceData, meta interface{}) error {
mgmtClient := meta.(*Client).mgmtClient
storServName := d.Get("storage_service_name").(string)
queueClient, err := getStorageServiceQueueClient(mgmtClient, storServName)
if err != nil {
return err
}
// check for queue's existence:
log.Println("[INFO] Sending Storage Queue existence query to Azure.")
name := d.Get("name").(string)
exists, err := queueClient.QueueExists(name)
if err != nil {
return fmt.Errorf("Error checking for Storage Queue existence: %s", err)
}
// If the queue has been deleted in the meantime;
// untrack the resource from the schema.
if !exists {
d.SetId("")
}
return nil
}
// resourceAzureStorageQueueDelete does all the necessary API calls to
// delete the storage queue off Azure.
func resourceAzureStorageQueueDelete(d *schema.ResourceData, meta interface{}) error {
mgmtClient := meta.(*Client).mgmtClient
storServName := d.Get("storage_service_name").(string)
queueClient, err := getStorageServiceQueueClient(mgmtClient, storServName)
if err != nil {
return err
}
// issue the deletion of the storage queue:
log.Println("[INFO] Sending Storage Queue deletion request to Azure.")
name := d.Get("name").(string)
err = queueClient.DeleteQueue(name)
if err != nil {
return fmt.Errorf("Error deleting Storage queue off Azure: %s", err)
}
return nil
}

View File

@ -0,0 +1,93 @@
package azure
import (
"fmt"
"testing"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureStorageQueue(t *testing.T) {
name := "azure_storage_queue.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAzureStorageQueueDeleted,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureStorageQueueConfig,
Check: resource.ComposeTestCheckFunc(
testAccCheckAzureStorageQueueExists(name),
resource.TestCheckResourceAttr(name, "name", "terraform-queue"),
resource.TestCheckResourceAttr(name, "storage_service_name", testAccStorageServiceName),
),
},
},
})
}
func testAccCheckAzureStorageQueueExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Azure Storage Queue resource '%s' is missing.", name)
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure Storage Service Queue ID %s is missing.", name)
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
queueClient, err := getStorageServiceQueueClient(mgmtClient, testAccStorageServiceName)
if err != nil {
return err
}
exists, err := queueClient.QueueExists(resource.Primary.ID)
if err != nil {
return fmt.Errorf("Error querying Azure for Storage Queue existence: %s", err)
}
if !exists {
return fmt.Errorf("Azure Storage Queue %s doesn't exist!", resource.Primary.ID)
}
return nil
}
}
func testAccCheckAzureStorageQueueDeleted(s *terraform.State) error {
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_storage_queue" {
continue
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure Storage Service Queue ID %s is missing.", resource.Primary.ID)
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
queueClient, err := getStorageServiceQueueClient(mgmtClient, testAccStorageServiceName)
if err != nil {
return err
}
exists, err := queueClient.QueueExists(resource.Primary.ID)
if err != nil {
return fmt.Errorf("Error querying Azure for Storage Queue existence: %s", err)
}
if exists {
return fmt.Errorf("Azure Storage Queue %s still exists!", resource.Primary.ID)
}
}
return nil
}
var testAccAzureStorageQueueConfig = fmt.Sprintf(`
resource "azure_storage_queue" "foo" {
name = "terraform-queue"
storage_service_name = "%s"
}
`, testAccStorageServiceName)

View File

@ -0,0 +1,227 @@
package azure
import (
"encoding/base64"
"fmt"
"log"
"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/storageservice"
"github.com/hashicorp/terraform/helper/schema"
)
// resourceAzureStorageService returns the *schema.Resource associated
// to an Azure hosted service.
func resourceAzureStorageService() *schema.Resource {
return &schema.Resource{
Create: resourceAzureStorageServiceCreate,
Read: resourceAzureStorageServiceRead,
Exists: resourceAzureStorageServiceExists,
Delete: resourceAzureStorageServiceDelete,
Schema: map[string]*schema.Schema{
// General attributes:
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
// TODO(aznashwan): constrain name in description
Description: parameterDescriptions["name"],
},
"location": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["location"],
},
"label": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Default: "Made by Terraform.",
Description: parameterDescriptions["label"],
},
"description": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: parameterDescriptions["description"],
},
// Functional attributes:
"account_type": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: parameterDescriptions["account_type"],
},
"affinity_group": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: parameterDescriptions["affinity_group"],
},
"properties": &schema.Schema{
Type: schema.TypeMap,
Optional: true,
ForceNew: true,
Elem: schema.TypeString,
},
// Computed attributes:
"url": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"primary_key": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"secondary_key": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}
// resourceAzureStorageServiceCreate does all the necessary API calls to
// create a new Azure storage service.
func resourceAzureStorageServiceCreate(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
storageServiceClient := storageservice.NewClient(mgmtClient)
// get all the values:
log.Println("[INFO] Creating Azure Storage Service creation parameters.")
name := d.Get("name").(string)
location := d.Get("location").(string)
accountType := storageservice.AccountType(d.Get("account_type").(string))
affinityGroup := d.Get("affinity_group").(string)
description := d.Get("description").(string)
label := base64.StdEncoding.EncodeToString([]byte(d.Get("label").(string)))
var props []storageservice.ExtendedProperty
if given := d.Get("properties").(map[string]interface{}); len(given) > 0 {
props = []storageservice.ExtendedProperty{}
for k, v := range given {
props = append(props, storageservice.ExtendedProperty{
Name: k,
Value: v.(string),
})
}
}
// create parameters and send request:
log.Println("[INFO] Sending Storage Service creation request to Azure.")
reqID, err := storageServiceClient.CreateStorageService(
storageservice.StorageAccountCreateParameters{
ServiceName: name,
Location: location,
Description: description,
Label: label,
AffinityGroup: affinityGroup,
AccountType: accountType,
ExtendedProperties: storageservice.ExtendedPropertyList{
ExtendedProperty: props,
},
})
if err != nil {
return fmt.Errorf("Failed to create Azure storage service %s: %s", name, err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Failed creating storage service %s: %s", name, err)
}
d.SetId(name)
return resourceAzureStorageServiceRead(d, meta)
}
// resourceAzureStorageServiceRead does all the necessary API calls to
// read the state of the storage service off Azure.
func resourceAzureStorageServiceRead(d *schema.ResourceData, meta interface{}) error {
azureClient := meta.(*Client)
mgmtClient := azureClient.mgmtClient
storageServiceClient := storageservice.NewClient(mgmtClient)
// get our storage service:
log.Println("[INFO] Sending query about storage service to Azure.")
name := d.Get("name").(string)
storsvc, err := storageServiceClient.GetStorageService(name)
if err != nil {
if !management.IsResourceNotFoundError(err) {
return fmt.Errorf("Failed to query about Azure about storage service: %s", err)
} else {
// it means that the resource has been deleted from Azure
// in the meantime and we must remove its associated Resource.
d.SetId("")
return nil
}
}
// read values:
d.Set("url", storsvc.URL)
log.Println("[INFO] Querying keys of Azure storage service.")
keys, err := storageServiceClient.GetStorageServiceKeys(name)
if err != nil {
return fmt.Errorf("Failed querying keys for Azure storage service: %s", err)
}
d.Set("primary_key", keys.PrimaryKey)
d.Set("secondary_key", keys.SecondaryKey)
return nil
}
// resourceAzureStorageServiceExists does all the necessary API calls to
// check if the storage service exists on Azure.
func resourceAzureStorageServiceExists(d *schema.ResourceData, meta interface{}) (bool, error) {
azureClient, ok := meta.(*Client)
if !ok {
return false, fmt.Errorf("Failed to convert to *Client, got: %T", meta)
}
mgmtClient := azureClient.mgmtClient
storageServiceClient := storageservice.NewClient(mgmtClient)
// get our storage service:
log.Println("[INFO] Sending query about storage service to Azure.")
name := d.Get("name").(string)
_, err := storageServiceClient.GetStorageService(name)
if err != nil {
if !management.IsResourceNotFoundError(err) {
return false, fmt.Errorf("Failed to query about Azure about storage service: %s", err)
} else {
// it means that the resource has been deleted from Azure
// in the meantime and we must remove its associated Resource.
d.SetId("")
return false, nil
}
}
return true, nil
}
// resourceAzureStorageServiceDelete does all the necessary API calls to
// delete the storage service off Azure.
func resourceAzureStorageServiceDelete(d *schema.ResourceData, meta interface{}) error {
azureClient, ok := meta.(*Client)
if !ok {
return fmt.Errorf("Failed to convert to *Client, got: %T", meta)
}
mgmtClient := azureClient.mgmtClient
storageClient := storageservice.NewClient(mgmtClient)
// issue the deletion:
name := d.Get("name").(string)
log.Println("[INFO] Issuing delete of storage service off Azure.")
reqID, err := storageClient.DeleteStorageService(name)
if err != nil {
return fmt.Errorf("Error whilst issuing deletion of storage service off Azure: %s", err)
}
err = mgmtClient.WaitForOperation(reqID, nil)
if err != nil {
return fmt.Errorf("Error whilst deleting storage service off Azure: %s", err)
}
d.SetId("")
return nil
}

View File

@ -0,0 +1,50 @@
package azure
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/storageservice"
"github.com/Azure/azure-sdk-for-go/storage"
)
// getStorageClientForStorageService is helper function which returns the
// storage.Client associated to the given storage service name.
func getStorageClientForStorageService(mgmtClient management.Client, serviceName string) (storage.Client, error) {
var storageClient storage.Client
storageServiceClient := storageservice.NewClient(mgmtClient)
keys, err := storageServiceClient.GetStorageServiceKeys(serviceName)
if err != nil {
return storageClient, fmt.Errorf("Failed getting Storage Service keys for %s: %s", serviceName, err)
}
storageClient, err = storage.NewBasicClient(serviceName, keys.PrimaryKey)
if err != nil {
return storageClient, fmt.Errorf("Failed creating Storage Service client for %s: %s", serviceName, err)
}
return storageClient, err
}
// getStorageServiceBlobClient is a helper function which returns the
// storage.BlobStorageClient associated to the given storage service name.
func getStorageServiceBlobClient(mgmtClient management.Client, serviceName string) (storage.BlobStorageClient, error) {
storageClient, err := getStorageClientForStorageService(mgmtClient, serviceName)
if err != nil {
return storage.BlobStorageClient{}, err
}
return storageClient.GetBlobService(), nil
}
// getStorageServiceQueueClient is a helper function which returns the
// storage.QueueServiceClient associated to the given storage service name.
func getStorageServiceQueueClient(mgmtClient management.Client, serviceName string) (storage.QueueServiceClient, error) {
storageClient, err := getStorageClientForStorageService(mgmtClient, serviceName)
if err != nil {
return storage.QueueServiceClient{}, err
}
return storageClient.GetQueueService(), err
}

View File

@ -0,0 +1,79 @@
package azure
import (
"fmt"
"testing"
"github.com/Azure/azure-sdk-for-go/management/storageservice"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
func TestAccAzureStorageService(t *testing.T) {
name := "azure_storage_service.foo"
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccAzureStorageServiceDestroyed,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccAzureStorageServiceConfig,
Check: resource.ComposeTestCheckFunc(
testAccAzureStorageServiceExists(name),
resource.TestCheckResourceAttr(name, "name", "tftesting"),
resource.TestCheckResourceAttr(name, "location", "North Europe"),
resource.TestCheckResourceAttr(name, "description", "very descriptive"),
resource.TestCheckResourceAttr(name, "account_type", "Standard_LRS"),
),
},
},
})
}
func testAccAzureStorageServiceExists(name string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resource, ok := s.RootModule().Resources[name]
if !ok {
return fmt.Errorf("Azure Storage Service Resource not found: %s", name)
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure Storage Service ID not set.")
}
mgmtClient := testAccProvider.Meta().(*Client).mgmtClient
_, err := storageservice.NewClient(mgmtClient).GetStorageService(resource.Primary.ID)
return err
}
}
func testAccAzureStorageServiceDestroyed(s *terraform.State) error {
mgmgClient := testAccProvider.Meta().(*Client).mgmtClient
for _, resource := range s.RootModule().Resources {
if resource.Type != "azure_storage_service" {
continue
}
if resource.Primary.ID == "" {
return fmt.Errorf("Azure Storage Service ID not set.")
}
_, err := storageservice.NewClient(mgmgClient).GetStorageService(resource.Primary.ID)
return testAccResourceDestroyedErrorFilter("Storage Service", err)
}
return nil
}
var testAccAzureStorageServiceConfig = `
resource "azure_storage_service" "foo" {
# NOTE: storage service names constrained to lowercase letters only.
name = "tftesting"
location = "West US"
description = "very descriptive"
account_type = "Standard_LRS"
}
`

View File

@ -5,12 +5,11 @@ import (
"log" "log"
"strings" "strings"
"github.com/Azure/azure-sdk-for-go/management"
"github.com/Azure/azure-sdk-for-go/management/networksecuritygroup"
"github.com/Azure/azure-sdk-for-go/management/virtualnetwork"
"github.com/hashicorp/terraform/helper/hashcode" "github.com/hashicorp/terraform/helper/hashcode"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/mitchellh/mapstructure"
"github.com/svanharmelen/azure-sdk-for-go/management"
"github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup"
"github.com/svanharmelen/azure-sdk-for-go/management/virtualnetwork"
) )
const ( const (
@ -37,6 +36,14 @@ func resourceAzureVirtualNetwork() *schema.Resource {
Elem: &schema.Schema{Type: schema.TypeString}, 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{ "subnet": &schema.Schema{
Type: schema.TypeSet, Type: schema.TypeSet,
Required: true, Required: true,
@ -94,11 +101,7 @@ func resourceAzureVirtualNetworkCreate(d *schema.ResourceData, meta interface{})
} }
} }
network, err := createVirtualNetwork(d) network := createVirtualNetwork(d)
if err != nil {
return err
}
nc.Configuration.VirtualNetworkSites = append(nc.Configuration.VirtualNetworkSites, network) nc.Configuration.VirtualNetworkSites = append(nc.Configuration.VirtualNetworkSites, network)
req, err := virtualnetwork.NewClient(mc).SetVirtualNetworkConfiguration(nc) req, err := virtualnetwork.NewClient(mc).SetVirtualNetworkConfiguration(nc)
@ -187,11 +190,7 @@ func resourceAzureVirtualNetworkUpdate(d *schema.ResourceData, meta interface{})
found := false found := false
for i, n := range nc.Configuration.VirtualNetworkSites { for i, n := range nc.Configuration.VirtualNetworkSites {
if n.Name == d.Id() { if n.Name == d.Id() {
network, err := createVirtualNetwork(d) network := createVirtualNetwork(d)
if err != nil {
return err
}
nc.Configuration.VirtualNetworkSites[i] = network nc.Configuration.VirtualNetworkSites[i] = network
found = true found = true
@ -263,15 +262,19 @@ func resourceAzureSubnetHash(v interface{}) int {
return hashcode.String(subnet) return hashcode.String(subnet)
} }
func createVirtualNetwork(d *schema.ResourceData) (virtualnetwork.VirtualNetworkSite, error) { func createVirtualNetwork(d *schema.ResourceData) virtualnetwork.VirtualNetworkSite {
var addressPrefix []string // fetch address spaces:
err := mapstructure.WeakDecode(d.Get("address_space"), &addressPrefix) var prefixes []string
if err != nil { for _, prefix := range d.Get("address_space").([]interface{}) {
return virtualnetwork.VirtualNetworkSite{}, fmt.Errorf("Error decoding address_space: %s", err) prefixes = append(prefixes, prefix.(string))
} }
addressSpace := virtualnetwork.AddressSpace{ // fetch DNS references:
AddressPrefix: addressPrefix, var dnsRefs []virtualnetwork.DNSServerRef
for _, dns := range d.Get("dns_servers_names").([]interface{}) {
dnsRefs = append(dnsRefs, virtualnetwork.DNSServerRef{
Name: dns.(string),
})
} }
// Add all subnets that are configured // Add all subnets that are configured
@ -287,11 +290,14 @@ func createVirtualNetwork(d *schema.ResourceData) (virtualnetwork.VirtualNetwork
} }
return virtualnetwork.VirtualNetworkSite{ return virtualnetwork.VirtualNetworkSite{
Name: d.Get("name").(string), Name: d.Get("name").(string),
Location: d.Get("location").(string), Location: d.Get("location").(string),
AddressSpace: addressSpace, AddressSpace: virtualnetwork.AddressSpace{
Subnets: subnets, AddressPrefix: prefixes,
}, nil },
DNSServersRef: dnsRefs,
Subnets: subnets,
}
} }
func associateSecurityGroups(d *schema.ResourceData, meta interface{}) error { func associateSecurityGroups(d *schema.ResourceData, meta interface{}) error {

View File

@ -4,9 +4,9 @@ import (
"fmt" "fmt"
"testing" "testing"
"github.com/Azure/azure-sdk-for-go/management/virtualnetwork"
"github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/svanharmelen/azure-sdk-for-go/management/virtualnetwork"
) )
func TestAccAzureVirtualNetwork_basic(t *testing.T) { func TestAccAzureVirtualNetwork_basic(t *testing.T) {
@ -214,16 +214,19 @@ const testAccAzureVirtualNetwork_advanced = `
resource "azure_security_group" "foo" { resource "azure_security_group" "foo" {
name = "terraform-security-group1" name = "terraform-security-group1"
location = "West US" location = "West US"
}
rule { resource "azure_security_group_rule" "foo" {
name = "RDP" name = "terraform-secgroup-rule"
priority = 101 security_group_name = "${azure_security_group.foo.name}"
source_cidr = "*" type = "Inbound"
source_port = "*" action = "Deny"
destination_cidr = "*" priority = 200
destination_port = "3389" source_address_prefix = "100.0.0.0/32"
protocol = "TCP" source_port_range = "1000"
} destination_address_prefix = "10.0.0.0/32"
destination_port_range = "1000"
protocol = "TCP"
} }
resource "azure_virtual_network" "foo" { resource "azure_virtual_network" "foo" {
@ -242,31 +245,24 @@ const testAccAzureVirtualNetwork_update = `
resource "azure_security_group" "foo" { resource "azure_security_group" "foo" {
name = "terraform-security-group1" name = "terraform-security-group1"
location = "West US" location = "West US"
}
rule { resource "azure_security_group_rule" "foo" {
name = "RDP" name = "terraform-secgroup-rule"
priority = 101 security_group_name = "${azure_security_group.foo.name}"
source_cidr = "*" type = "Inbound"
source_port = "*" action = "Deny"
destination_cidr = "*" priority = 200
destination_port = "3389" source_address_prefix = "100.0.0.0/32"
protocol = "TCP" source_port_range = "1000"
} destination_address_prefix = "10.0.0.0/32"
destination_port_range = "1000"
protocol = "TCP"
} }
resource "azure_security_group" "bar" { resource "azure_security_group" "bar" {
name = "terraform-security-group2" name = "terraform-security-group2"
location = "West US" location = "West US"
rule {
name = "SSH"
priority = 101
source_cidr = "*"
source_port = "*"
destination_cidr = "*"
destination_port = "22"
protocol = "TCP"
}
} }
resource "azure_virtual_network" "foo" { resource "azure_virtual_network" "foo" {

View File

@ -0,0 +1,20 @@
package azure
import (
"fmt"
"github.com/Azure/azure-sdk-for-go/management"
)
// testAccResourceDestroyedErrorFilter tests whether the given error is an azure ResourceNotFound
// error and properly annotates it if otherwise:
func testAccResourceDestroyedErrorFilter(resource string, err error) error {
switch {
case err == nil:
return fmt.Errorf("Azure %s still exists.", resource)
case err != nil && management.IsResourceNotFoundError(err):
return nil
default:
return err
}
}

View File

@ -46,3 +46,14 @@ The following arguments are supported:
* `certificate` - (Optional) The certificate used to authenticate with the * `certificate` - (Optional) The certificate used to authenticate with the
Azure API. If a `settings_file` is not provided `certificate` is required. Azure API. If a `settings_file` is not provided `certificate` is required.
It can also be sourced from the `AZURE_CERTIFICATE` environment variable. It can also be sourced from the `AZURE_CERTIFICATE` environment variable.
## Testing:
The following environment variables must be set for the running of the
acceptance test suite:
* A valid combination of the above which are required for authentification.
* `AZURE_STORAGE` - The name of a storage account to be used in tests which
require a storage backend. The storage account needs to be located in
the Western US Azure region.

View File

@ -17,7 +17,7 @@ it will attach that disk. Otherwise it will create and attach a new empty disk.
resource "azure_data_disk" "data" { resource "azure_data_disk" "data" {
lun = 0 lun = 0
size = 10 size = 10
storage = "yourstorage" storage_service_name = "yourstorage"
virtual_machine = "server1" virtual_machine = "server1"
} }
``` ```
@ -43,10 +43,10 @@ The following arguments are supported:
* `caching` - (Optional) The caching behavior of data disk. Valid options are: * `caching` - (Optional) The caching behavior of data disk. Valid options are:
`None`, `ReadOnly` and `ReadWrite` (defaults `None`) `None`, `ReadOnly` and `ReadWrite` (defaults `None`)
* `storage ` - (Optional) The name of an existing storage account within the * `storage_service_name` - (Optional) The name of an existing storage account
subscription which will be used to store the VHD of this disk. Required within the subscription which will be used to store the VHD of this disk.
if no value is supplied for `media_link`. Changing this forces a new Required if no value is supplied for `media_link`. Changing this forces
resource to be created. a new resource to be created.
* `media_link` - (Optional) The location of the blob in storage where the VHD * `media_link` - (Optional) The location of the blob in storage where the VHD
of this disk will be created. The storage account where must be associated of this disk will be created. The storage account where must be associated

View File

@ -0,0 +1,36 @@
---
layout: "azure"
page_title: "Azure: azure_dns_server"
sidebar_current: "docs-azure-resource-dns-server"
description: |-
Creates a new DNS server definition to be used internally in Azure.
---
# azure\_dns\_server
Creates a new DNS server definition to be used internally in Azure.
## Example Usage
```
resource "azure_dns_server" "google-dns" {
name = "google"
dns_address = "8.8.8.8"
}
`
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the DNS server reference. Changing this
forces a new resource to be created.
* `dns_address` - (Required) The IP address of the DNS server.
## Attributes Reference
The following attributes are exported:
* `id` - The DNS server definition ID. Coincides with the given `name`.

View File

@ -0,0 +1,50 @@
---
layout: "azure"
page_title: "Azure: azure_hosted_service"
sidebar_current: "docs-azure-hosted-service"
description: |-
Creates a new hosted service on Azure with its own .cloudapp.net domain.
---
# azure\_hosted\_service
Creates a new hosted service on Azure with its own .cloudapp.net domain.
## Example Usage
```
resource "azure_hosted_service" "terraform-service" {
name = "terraform-service"
location = "North Europe"
ephemeral_contents = false
description = "Hosted service created by Terraform."
label = "tf-hs-01"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the hosted service. Must be unique on Azure.
* `location` - (Required) The location where the hosted service should be created.
For a list of all Azure locations, please consult [this link](http://azure.microsoft.com/en-us/regions/).
* `ephemeral_contents` - (Required) A boolean value (true|false), specifying
whether all the resources present in the hosted hosted service should be
destroyed following the hosted service's destruction.
* `reverse_dns_fqdn` - (Optional) The reverse of the fully qualified domain name
for the hosted service.
* `label` - (Optional) A label to be used for tracking purposes. Must be
non-void. Defaults to `Made by Terraform.`.
* `description` - (Optional) A description for the hosted service.
## Attributes Reference
The following attributes are exported:
* `id` - The hosted service ID. Coincides with the given `name`.

View File

@ -18,7 +18,7 @@ resource "azure_instance" "web" {
name = "terraform-test" name = "terraform-test"
image = "Ubuntu Server 14.04 LTS" image = "Ubuntu Server 14.04 LTS"
size = "Basic_A1" size = "Basic_A1"
storage = "yourstorage" storage_service_name = "yourstorage"
location = "West US" location = "West US"
username = "terraform" username = "terraform"
password = "Pass!admin123" password = "Pass!admin123"
@ -56,9 +56,9 @@ The following arguments are supported:
belongs to. If a value is supplied `subnet` is required. Changing this belongs to. If a value is supplied `subnet` is required. Changing this
forces a new resource to be created. forces a new resource to be created.
* `storage` - (Optional) The name of an existing storage account within the * `storage_service_name` - (Optional) The name of an existing storage account
subscription which will be used to store the VHDs of this instance. within the subscription which will be used to store the VHDs of this
Changing this forces a new resource to be created. instance. Changing this forces a new resource to be created.
* `reverse_dns` - (Optional) The DNS address to which the IP address of the * `reverse_dns` - (Optional) The DNS address to which the IP address of the
hosted service resolves when queried using a reverse DNS query. Changing hosted service resolves when queried using a reverse DNS query. Changing

View File

@ -0,0 +1,39 @@
---
layout: "azure"
page_title: "Azure: azure_local_network_connection"
sidebar_current: "docs-azure-resource-local-network-connection"
description: |-
Defines a new connection to a remote network throguh a VPN tunnel.
---
# azure\_local\_network\_connection
Defines a new connection to a remote network throguh a VPN tunnel.
## Example Usage
```
resource "azure_local_network_connection" "localnet" {
name = "terraform-local-network-connection"
vpn_gateway_address = "45.12.189.2"
address_space_prefixes = ["10.10.10.0/24", "10.10.11.0/24"]
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name by which this local network connection will
be referenced by. Changing this forces a new resource to be created.
* `vpn_gateway_address` - (Required) The public IPv4 of the VPN endpoint.
* `address_space_prefixes` - (Required) List of address spaces accessible
through the VPN connection. The elements are in the CIDR format.
## Attributes Reference
The following attributes are exported:
* `id` - The local network connection ID.

View File

@ -0,0 +1,42 @@
---
layout: "azure"
page_title: "Azure: azure_security_group"
sidebar_current: "docs-azure-resource-security-group"
description: |-
Creates a new network security group within the context of the specified subscription.
---
# azure\_security\_group
Creates a new network security group within the context of the specified
subscription.
## Example Usage
```
resource "azure_security_group" "web" {
name = "webservers"
location = "West US"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the security group. Changing this forces a
new resource to be created.
* `label` - (Optional) The identifier for the security group. The label can be
up to 1024 characters long. Changing this forces a new resource to be
created (defaults to the security group name)
* `location` - (Required) The location/region where the security group is
created. Changing this forces a new resource to be created.
## Attributes Reference
The following attributes are exported:
* `id` - The security group ID.
* `label` - The identifier for the security group.

View File

@ -1,84 +0,0 @@
---
layout: "azure"
page_title: "Azure: azure_security_group"
sidebar_current: "docs-azure-resource-security-group"
description: |-
Creates a new network security group within the context of the specified subscription.
---
# azure\_security\_group
Creates a new network security group within the context of the specified
subscription.
## Example Usage
```
resource "azure_security_group" "web" {
name = "webservers"
location = "West US"
rule {
name = "HTTPS"
priority = 101
source_cidr = "*"
source_port = "*"
destination_cidr = "*"
destination_port = "443"
protocol = "TCP"
}
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the security group. Changing this forces a
new resource to be created.
* `label` - (Optional) The identifier for the security group. The label can be
up to 1024 characters long. Changing this forces a new resource to be
created (defaults to the security group name)
* `location` - (Required) The location/region where the security group is
created. Changing this forces a new resource to be created.
* `rule` - (Required) Can be specified multiple times to define multiple
rules. Each `rule` block supports fields documented below.
The `rule` block supports:
* `name` - (Required) The name of the security rule.
* `type ` - (Optional) The type of the security rule. Valid options are:
`Inbound` and `Outbound` (defaults `Inbound`)
* `priority` - (Required) The priority of the network security rule. Rules with
lower priority are evaluated first. This value can be between 100 and 4096.
* `action` - (Optional) The action that is performed when the security rule is
matched. Valid options are: `Allow` and `Deny` (defaults `Allow`)
* `source_cidr` - (Required) The CIDR or source IP range. An asterisk (\*) can
also be used to match all source IPs.
* `source_port` - (Required) The source port or range. This value can be
between 0 and 65535. An asterisk (\*) can also be used to match all ports.
* `destination_cidr` - (Required) The CIDR or destination IP range. An asterisk
(\*) can also be used to match all destination IPs.
* `destination_port` - (Required) The destination port or range. This value can
be between 0 and 65535. An asterisk (\*) can also be used to match all
ports.
* `protocol` - (Optional) The protocol of the security rule. Valid options are:
`TCP`, `UDP` and `*` (defaults `TCP`)
## Attributes Reference
The following attributes are exported:
* `id` - The security group ID.
* `label` - The identifier for the security group.

View File

@ -0,0 +1,71 @@
---
layout: "azure"
page_title: "Azure: azure_security_group_rule"
sidebar_current: "docs-azure-resource-security-group-rule"
description: |-
Creates a new network security rule to be associated with a given security group.
---
# azure\_security\_group\_rule
Creates a new network security rule to be associated with a given security group.
## Example Usage
```
resource "azure_security_group" "web" {
...
}
resource "azure_security_group_rule" "ssh_access" {
name = "ssh-access-rule"
security_group_name = "${azure_security_group.web.name}"
type = "Inbound"
action = "Allow"
priority = 200
source_address_prefix = "100.0.0.0/32"
source_port_range = "*"
destination_address_prefix = "10.0.0.0/32"
destination_port_range = "22"
protocol = "TCP"
}
```
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the security group the rule should be
applied to.
* `security_group_name` - (Required) The name of the security group m
* `type` - (Required) The type of the security rule. Valid options are:
`Inbound` and `Outbound`.
* `priority` - (Required) The priority of the network security rule. Rules with
lower priority are evaluated first. This value can be between 100 and 4096.
* `action` - (Optional) The action that is performed when the security rule is
matched. Valid options are: `Allow` and `Deny`.
* `source_address_prefix` - (Required) The address prefix of packet sources that
that should be subjected to the rule. An asterisk (\*) can also be used to
match all source IPs.
* `source_port_range` - (Required) The source port or range. This value can be
between 0 and 65535. An asterisk (\*) can also be used to match all ports.
* `destination_address_prefix` - (Required) The address prefix of packet
destinations that should be subjected to the rule. An asterisk
(\*) can also be used to match all destination IPs.
* `destination_port_range` - (Required) The destination port or range. This value
can be between 0 and 65535. An asterisk (\*) can also be used to match all
ports.
* `protocol` - (Optional) The protocol of the security rule. Valid options are:
`TCP`, `UDP` and `*`.
The following attributes are exported:
* `id` - The security group rule ID. Coincides with its given `name`.

View File

@ -0,0 +1,49 @@
---
layout: "azure"
page_title: "Azure: azure_storage_blob"
sidebar_current: "docs-azure-storage-blob"
description: |-
Creates a new storage blob within a given storage container on Azure.
---
# azure\_storage\_blob
Creates a new storage blob within a given storage container on Azure.
## Example Usage
```
resource "azure_storage_blob" "foo" {
name = "tftesting-blob"
storage_service_name = "tfstorserv"
storage_container_name = "terraform-storage-container"
type = "PageBlob"
size = 1024
}
````
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the storage blob. Must be unique within
the storage service the blob is located.
* `storage_service_name` - (Required) The name of the storage service within
which the storage container in which the blob will be created resides.
* `storage_container_name` - (Required) The name of the storage container
in which this blob should be created. Must be located on the storage
service given with `storage_service_name`.
* `type` - (Required) The type of the storage blob to be created. One of either
`BlockBlob` or `PageBlob`.
* `size` - (Optional) Used only for `PageBlob`'s to specify the size in bytes
of the blob to be created. Must be a multiple of 512. Defaults to 0.
## Attributes Reference
The following attributes are exported:
* `id` - The storage blob ID. Coincides with the given `name`.

View File

@ -0,0 +1,43 @@
---
layout: "azure"
page_title: "Azure: azure_storage_container"
sidebar_current: "docs-azure-storage-container"
description: |-
Creates a new storage container within a given storage service on Azure.
---
# azure\_storage\_container
Creates a new storage container within a given storage service on Azure.
## Example Usage
```
resource "azure_storage_container" "stor-cont" {
name = "terraform-storage-container"
container_access_type = "blob"
storage_service_name = "tfstorserv"
}
````
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the storage container. Must be unique within
the storage service the container is located.
* `storage_service_name` - (Required) The name of the storage service within
which the storage container should be created.
* `container_access_type` - (Required) The 'interface' for access the container
provides. Can be either `blob`, `container` or ``.
* `properties` - (Optional) Key-value definition of additional properties
associated to the storage service.
## Attributes Reference
The following attributes are exported:
* `id` - The storage container ID. Coincides with the given `name`.

View File

@ -0,0 +1,36 @@
---
layout: "azure"
page_title: "Azure: azure_storage_queue"
sidebar_current: "docs-azure-storage-queue"
description: |-
Creates a new storage queue within a given storage service on Azure.
---
# azure\_storage\_queue
Creates a new storage queue within a given storage service on Azure.
## Example Usage
```
resource "azure_storage_queue" "stor-queue" {
name = "terraform-storage-queue"
storage_service_name = "tfstorserv"
}
````
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the storage queue. Must be unique within
the storage service the queue is located.
* `storage_service_name` - (Required) The name of the storage service within
which the storage queue should be created.
## Attributes Reference
The following attributes are exported:
* `id` - The storage queue ID. Coincides with the given `name`.

View File

@ -0,0 +1,55 @@
---
layout: "azure"
page_title: "Azure: azure_storage_service"
sidebar_current: "docs-azure-storage-service"
description: |-
Creates a new storage service on Azure in which storage containers may be created.
---
# azure\_storage\_service
Creates a new storage service on Azure in which storage containers may be created.
## Example Usage
```
resource "azure_storage_service" "tfstor" {
name = "tfstor"
location = "West US"
description = "Made by Terraform."
account_type = "Standard_LRS"
}
````
## Argument Reference
The following arguments are supported:
* `name` - (Required) The name of the storage service. Must be between 4 and 24
lowercase-only characters or digits Must be unique on Azure.
* `location` - (Required) The location where the storage service should be created.
For a list of all Azure locations, please consult [this link](http://azure.microsoft.com/en-us/regions/).
* `account_type` - (Required) The type of storage account to be created.
Available options include `Standard_LRS`, `Standard_ZRS`, `Standard_GRS`,
`Standard_RAGRS` and `Premium_LRS`. To learn more about the differences
of each storage account type, please consult [this link](http://blogs.msdn.com/b/windowsazurestorage/archive/2013/12/11/introducing-read-access-geo-replicated-storage-ra-grs-for-windows-azure-storage.aspx).
* `affinity_group` - (Optional) The affinity group the storage service should
belong to.
* `properties` - (Optional) Key-value definition of additional properties
associated to the storage service. For additional information on what
these properties do, please consult [this link](https://msdn.microsoft.com/en-us/library/azure/hh452235.aspx).
* `label` - (Optional) A label to be used for tracking purposes. Must be
non-void. Defaults to `Made by Terraform.`.
* `description` - (Optional) A description for the storage service.
## Attributes Reference
The following attributes are exported:
* `id` - The storage service ID. Coincides with the given `name`.

View File

@ -40,6 +40,9 @@ The following arguments are supported:
* `location` - (Required) The location/region where the virtual network is * `location` - (Required) The location/region where the virtual network is
created. Changing this forces a new resource to be created. 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 * `subnet` - (Required) Can be specified multiple times to define multiple
subnets. Each `subnet` block supports fields documented below. subnets. Each `subnet` block supports fields documented below.

View File

@ -17,14 +17,46 @@
<a href="/docs/providers/azure/r/data_disk.html">azure_data_disk</a> <a href="/docs/providers/azure/r/data_disk.html">azure_data_disk</a>
</li> </li>
<li<%= sidebar_current("docs-azure-resource-dns-server") %>>
<a href="/docs/providers/azure/r/dns_server.html">azure_dns_server</a>
</li>
<li<%= sidebar_current("docs-azure-resource-hosted-service") %>>
<a href="/docs/providers/azure/r/hosted_service.html">azure_hosted_service</a>
</li>
<li<%= sidebar_current("docs-azure-resource-instance") %>> <li<%= sidebar_current("docs-azure-resource-instance") %>>
<a href="/docs/providers/azure/r/instance.html">azure_instance</a> <a href="/docs/providers/azure/r/instance.html">azure_instance</a>
</li> </li>
<li<%= sidebar_current("docs-azure-resource-local-network") %>>
<a href="/docs/providers/azure/r/local_network_connection.html">azure_local_network_connection</a>
</li>
<li<%= sidebar_current("docs-azure-resource-security-group") %>> <li<%= sidebar_current("docs-azure-resource-security-group") %>>
<a href="/docs/providers/azure/r/security_group.html">azure_security_group</a> <a href="/docs/providers/azure/r/security_group.html">azure_security_group</a>
</li> </li>
<li<%= sidebar_current("docs-azure-resource-security-group-rule") %>>
<a href="/docs/providers/azure/r/security_group_rule.html">azure_security_group_rule</a>
</li>
<li<%= sidebar_current("docs-azure-resource-storage-blob") %>>
<a href="/docs/providers/azure/r/storage_blob.html">azure_storage_blob</a>
</li>
<li<%= sidebar_current("docs-azure-resource-storage-container") %>>
<a href="/docs/providers/azure/r/storage_container.html">azure_storage_container</a>
</li>
<li<%= sidebar_current("docs-azure-resource-storage-queue") %>>
<a href="/docs/providers/azure/r/storage_queue.html">azure_storage_queue</a>
</li>
<li<%= sidebar_current("docs-azure-resource-storage-service") %>>
<a href="/docs/providers/azure/r/storage_service.html">azure_storage_service</a>
</li>
<li<%= sidebar_current("docs-azure-resource-virtual-network") %>> <li<%= sidebar_current("docs-azure-resource-virtual-network") %>>
<a href="/docs/providers/azure/r/virtual_network.html">azure_virtual_network</a> <a href="/docs/providers/azure/r/virtual_network.html">azure_virtual_network</a>
</li> </li>