diff --git a/builtin/providers/azure/config.go b/builtin/providers/azure/config.go index c21f6f7057db..5ff46084ec1a 100644 --- a/builtin/providers/azure/config.go +++ b/builtin/providers/azure/config.go @@ -5,7 +5,7 @@ import ( "os" "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 diff --git a/builtin/providers/azure/constants.go b/builtin/providers/azure/constants.go new file mode 100644 index 000000000000..55264f969681 --- /dev/null +++ b/builtin/providers/azure/constants.go @@ -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 '/netmask'", + // dns descriptions: + "dns_address": "Address of the DNS server. Required.", +} diff --git a/builtin/providers/azure/provider.go b/builtin/providers/azure/provider.go index eef0ca49c339..98a7b2a097ed 100644 --- a/builtin/providers/azure/provider.go +++ b/builtin/providers/azure/provider.go @@ -32,10 +32,18 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "azure_data_disk": resourceAzureDataDisk(), - "azure_instance": resourceAzureInstance(), - "azure_security_group": resourceAzureSecurityGroup(), - "azure_virtual_network": resourceAzureVirtualNetwork(), + "azure_instance": resourceAzureInstance(), + "azure_data_disk": resourceAzureDataDisk(), + "azure_hosted_service": resourceAzureHostedService(), + "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, diff --git a/builtin/providers/azure/provider_test.go b/builtin/providers/azure/provider_test.go index 5403df3ef849..8ab2ad0878f0 100644 --- a/builtin/providers/azure/provider_test.go +++ b/builtin/providers/azure/provider_test.go @@ -11,6 +11,18 @@ import ( var testAccProviders map[string]terraform.ResourceProvider 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() { testAccProvider = Provider().(*schema.Provider) testAccProviders = map[string]terraform.ResourceProvider{ diff --git a/builtin/providers/azure/resource_azure_data_disk.go b/builtin/providers/azure/resource_azure_data_disk.go index 39316a49c7b2..eb8a6af00dd7 100644 --- a/builtin/providers/azure/resource_azure_data_disk.go +++ b/builtin/providers/azure/resource_azure_data_disk.go @@ -5,9 +5,9 @@ import ( "log" "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/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" @@ -50,7 +50,7 @@ func resourceAzureDataDisk() *schema.Resource { Default: "None", }, - "storage": &schema.Schema{ + "storage_service_name": &schema.Schema{ Type: schema.TypeString, Optional: 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)) } - 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 { @@ -332,7 +332,7 @@ func verifyDataDiskParameters(d *schema.ResourceData) error { } 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'.") } } diff --git a/builtin/providers/azure/resource_azure_data_disk_test.go b/builtin/providers/azure/resource_azure_data_disk_test.go index 6eb30361048e..c2719aa9dcf2 100644 --- a/builtin/providers/azure/resource_azure_data_disk_test.go +++ b/builtin/providers/azure/resource_azure_data_disk_test.go @@ -2,14 +2,13 @@ package azure import ( "fmt" - "os" "strconv" "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/terraform" - "github.com/svanharmelen/azure-sdk-for-go/management" - "github.com/svanharmelen/azure-sdk-for-go/management/virtualmachinedisk" ) func TestAccAzureDataDisk_basic(t *testing.T) { @@ -174,7 +173,7 @@ resource "azure_instance" "foo" { name = "terraform-test" image = "Ubuntu Server 14.04 LTS" size = "Basic_A1" - storage = "%s" + storage_service_name = "%s" location = "West US" username = "terraform" password = "Pass!admin123" @@ -183,16 +182,16 @@ resource "azure_instance" "foo" { resource "azure_data_disk" "foo" { lun = 0 size = 10 - storage = "${azure_instance.foo.storage}" + storage_service_name = "${azure_instance.foo.storage_service_name}" virtual_machine = "${azure_instance.foo.id}" -}`, os.Getenv("AZURE_STORAGE")) +}`, testAccStorageServiceName) var testAccAzureDataDisk_advanced = fmt.Sprintf(` resource "azure_instance" "foo" { name = "terraform-test1" image = "Ubuntu Server 14.04 LTS" size = "Basic_A1" - storage = "%s" + storage_service_name = "%s" location = "West US" username = "terraform" password = "Pass!admin123" @@ -202,16 +201,16 @@ resource "azure_data_disk" "foo" { lun = 1 size = 10 caching = "ReadOnly" - storage = "${azure_instance.foo.storage}" + storage_service_name = "${azure_instance.foo.storage_service_name}" virtual_machine = "${azure_instance.foo.id}" -}`, os.Getenv("AZURE_STORAGE")) +}`, testAccStorageServiceName) var testAccAzureDataDisk_update = fmt.Sprintf(` resource "azure_instance" "foo" { name = "terraform-test1" image = "Ubuntu Server 14.04 LTS" size = "Basic_A1" - storage = "%s" + storage_service_name = "%s" location = "West US" username = "terraform" password = "Pass!admin123" @@ -221,7 +220,7 @@ resource "azure_instance" "bar" { name = "terraform-test2" image = "Ubuntu Server 14.04 LTS" size = "Basic_A1" - storage = "${azure_instance.foo.storage}" + storage_service_name = "${azure_instance.foo.storage_service_name}" location = "West US" username = "terraform" password = "Pass!admin123" @@ -231,6 +230,6 @@ resource "azure_data_disk" "foo" { lun = 2 size = 20 caching = "ReadWrite" - storage = "${azure_instance.bar.storage}" + storage_service_name = "${azure_instance.bar.storage_service_name}" virtual_machine = "${azure_instance.bar.id}" -}`, os.Getenv("AZURE_STORAGE")) +}`, testAccStorageServiceName) diff --git a/builtin/providers/azure/resource_azure_dns_server.go b/builtin/providers/azure/resource_azure_dns_server.go new file mode 100644 index 000000000000..30da0843a48b --- /dev/null +++ b/builtin/providers/azure/resource_azure_dns_server.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_dns_server_test.go b/builtin/providers/azure/resource_azure_dns_server_test.go new file mode 100644 index 000000000000..1654f7ee81a9 --- /dev/null +++ b/builtin/providers/azure/resource_azure_dns_server_test.go @@ -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" +} +` diff --git a/builtin/providers/azure/resource_azure_hosted_service.go b/builtin/providers/azure/resource_azure_hosted_service.go new file mode 100644 index 000000000000..6fb2d956daec --- /dev/null +++ b/builtin/providers/azure/resource_azure_hosted_service.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_hosted_service_test.go b/builtin/providers/azure/resource_azure_hosted_service_test.go new file mode 100644 index 000000000000..f0ef50e89e2d --- /dev/null +++ b/builtin/providers/azure/resource_azure_hosted_service_test.go @@ -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" +} +` diff --git a/builtin/providers/azure/resource_azure_instance.go b/builtin/providers/azure/resource_azure_instance.go index ad8a77a9ccdc..967e49dce8ef 100644 --- a/builtin/providers/azure/resource_azure_instance.go +++ b/builtin/providers/azure/resource_azure_instance.go @@ -7,14 +7,14 @@ import ( "log" "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/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 ( @@ -68,7 +68,7 @@ func resourceAzureInstance() *schema.Resource { ForceNew: true, }, - "storage": &schema.Schema{ + "storage_service_name": &schema.Schema{ Type: schema.TypeString, Optional: true, ForceNew: true, @@ -183,7 +183,7 @@ func resourceAzureInstanceCreate(d *schema.ResourceData, meta interface{}) (err mc, d.Get("image").(string), name, - d.Get("storage").(string), + d.Get("storage_service_name").(string), ) if err != nil { return err diff --git a/builtin/providers/azure/resource_azure_instance_test.go b/builtin/providers/azure/resource_azure_instance_test.go index 590399dfd1bd..938f306f4475 100644 --- a/builtin/providers/azure/resource_azure_instance_test.go +++ b/builtin/providers/azure/resource_azure_instance_test.go @@ -2,14 +2,13 @@ package azure import ( "fmt" - "os" "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/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) { @@ -313,7 +312,7 @@ resource "azure_instance" "foo" { name = "terraform-test" image = "Ubuntu Server 14.04 LTS" size = "Basic_A1" - storage = "%s" + storage_service_name = "%s" location = "West US" username = "terraform" password = "Pass!admin123" @@ -324,7 +323,7 @@ resource "azure_instance" "foo" { public_port = 22 private_port = 22 } -}`, os.Getenv("AZURE_STORAGE")) +}`, testAccStorageServiceName) var testAccAzureInstance_advanced = fmt.Sprintf(` resource "azure_virtual_network" "foo" { @@ -346,23 +345,26 @@ resource "azure_virtual_network" "foo" { resource "azure_security_group" "foo" { name = "terraform-security-group1" location = "West US" +} - rule { - name = "rdp" - priority = 101 - source_cidr = "*" - source_port = "*" - destination_cidr = "*" - destination_port = 3389 - protocol = "TCP" - } +resource "azure_security_group_rule" "foo" { + name = "rdp" + security_group_name = "${azure_security_group.foo.name}" + priority = 101 + source_address_prefix = "*" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "3389" + action = "Deny" + type = "Inbound" + protocol = "TCP" } resource "azure_instance" "foo" { name = "terraform-test1" image = "Windows Server 2012 R2 Datacenter, April 2015" size = "Basic_A1" - storage = "%s" + storage_service_name = "%s" location = "West US" time_zone = "America/Los_Angeles" subnet = "subnet1" @@ -377,7 +379,7 @@ resource "azure_instance" "foo" { public_port = 3389 private_port = 3389 } -}`, os.Getenv("AZURE_STORAGE")) +}`, testAccStorageServiceName) var testAccAzureInstance_update = fmt.Sprintf(` resource "azure_virtual_network" "foo" { @@ -385,52 +387,58 @@ resource "azure_virtual_network" "foo" { address_space = ["10.1.2.0/24"] location = "West US" - subnet { + subnet { name = "subnet1" - address_prefix = "10.1.2.0/25" - } + address_prefix = "10.1.2.0/25" + } - subnet { + subnet { name = "subnet2" - address_prefix = "10.1.2.128/25" + address_prefix = "10.1.2.128/25" } } resource "azure_security_group" "foo" { name = "terraform-security-group1" location = "West US" +} - rule { - name = "rdp" - priority = 101 - source_cidr = "*" - source_port = "*" - destination_cidr = "*" - destination_port = 3389 - protocol = "TCP" - } +resource "azure_security_group_rule" "foo" { + name = "rdp" + security_group_name = "${azure_security_group.foo.name}" + priority = 101 + source_address_prefix = "*" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "3389" + type = "Inbound" + action = "Deny" + protocol = "TCP" } resource "azure_security_group" "bar" { name = "terraform-security-group2" location = "West US" +} - rule { - name = "rdp" - priority = 101 - source_cidr = "192.168.0.0/24" - source_port = "*" - destination_cidr = "*" - destination_port = 3389 - protocol = "TCP" - } +resource "azure_security_group_rule" "bar" { + name = "rdp" + security_group_name = "${azure_security_group.bar.name}" + priority = 101 + source_address_prefix = "192.168.0.0/24" + source_port_range = "*" + destination_address_prefix = "*" + destination_port_range = "3389" + type = "Inbound" + action = "Deny" + protocol = "TCP" } resource "azure_instance" "foo" { name = "terraform-test1" image = "Windows Server 2012 R2 Datacenter, April 2015" size = "Basic_A2" - storage = "%s" + storage_service_name = "%s" location = "West US" time_zone = "America/Los_Angeles" subnet = "subnet1" @@ -452,4 +460,4 @@ resource "azure_instance" "foo" { public_port = 5985 private_port = 5985 } -}`, os.Getenv("AZURE_STORAGE")) +}`, testAccStorageServiceName) diff --git a/builtin/providers/azure/resource_azure_local_network.go b/builtin/providers/azure/resource_azure_local_network.go new file mode 100644 index 000000000000..ef07ddcb32cb --- /dev/null +++ b/builtin/providers/azure/resource_azure_local_network.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_local_network_test.go b/builtin/providers/azure/resource_azure_local_network_test.go new file mode 100644 index 000000000000..ae120569a0e4 --- /dev/null +++ b/builtin/providers/azure/resource_azure_local_network_test.go @@ -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"] +} +` diff --git a/builtin/providers/azure/resource_azure_security_group.go b/builtin/providers/azure/resource_azure_security_group.go index 842b124340f2..4b2b8b6de8e3 100644 --- a/builtin/providers/azure/resource_azure_security_group.go +++ b/builtin/providers/azure/resource_azure_security_group.go @@ -1,22 +1,18 @@ package azure import ( - "bytes" "fmt" "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/svanharmelen/azure-sdk-for-go/management" - "github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup" ) func resourceAzureSecurityGroup() *schema.Resource { return &schema.Resource{ Create: resourceAzureSecurityGroupCreate, Read: resourceAzureSecurityGroupRead, - Update: resourceAzureSecurityGroupUpdate, Delete: resourceAzureSecurityGroupDelete, Schema: map[string]*schema.Schema{ @@ -38,63 +34,6 @@ func resourceAzureSecurityGroup() *schema.Resource { Required: 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) - // 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) } -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 { mc := meta.(*Client).mgmtClient @@ -205,70 +83,9 @@ func resourceAzureSecurityGroupRead(d *schema.ResourceData, meta interface{}) er d.Set("label", sg.Label) 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 } -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 { mc := meta.(*Client).mgmtClient @@ -288,66 +105,3 @@ func resourceAzureSecurityGroupDelete(d *schema.ResourceData, meta interface{}) 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 -} diff --git a/builtin/providers/azure/resource_azure_security_group_rule.go b/builtin/providers/azure/resource_azure_security_group_rule.go new file mode 100644 index 000000000000..23c313729047 --- /dev/null +++ b/builtin/providers/azure/resource_azure_security_group_rule.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_security_group_rule_test.go b/builtin/providers/azure/resource_azure_security_group_rule_test.go new file mode 100644 index 000000000000..f16f29ced6b1 --- /dev/null +++ b/builtin/providers/azure/resource_azure_security_group_rule_test.go @@ -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" +} +` diff --git a/builtin/providers/azure/resource_azure_security_group_test.go b/builtin/providers/azure/resource_azure_security_group_test.go index 31c73daf085f..eb18d38e601b 100644 --- a/builtin/providers/azure/resource_azure_security_group_test.go +++ b/builtin/providers/azure/resource_azure_security_group_test.go @@ -4,10 +4,10 @@ import ( "fmt" "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/terraform" - "github.com/svanharmelen/azure-sdk-for-go/management" - "github.com/svanharmelen/azure-sdk-for-go/management/networksecuritygroup" ) func TestAccAzureSecurityGroup_basic(t *testing.T) { @@ -19,72 +19,16 @@ func TestAccAzureSecurityGroup_basic(t *testing.T) { CheckDestroy: testAccCheckAzureSecurityGroupDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: testAccAzureSecurityGroup_basic, + Config: testAccAzureSecurityGroupConfig, 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_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"), + "azure_security_group.foo", "label", "terraform testing security group"), ), }, }, @@ -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 { mc := testAccProvider.Meta().(*Client).mgmtClient @@ -228,44 +89,9 @@ func testAccCheckAzureSecurityGroupDestroy(s *terraform.State) error { return nil } -const testAccAzureSecurityGroup_basic = ` +var testAccAzureSecurityGroupConfig = fmt.Sprintf(` resource "azure_security_group" "foo" { - name = "terraform-security-group" + name = "%s" location = "West US" - - rule { - 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" - } -}` + label = "terraform testing security group" +}`, testAccSecurityGroupName) diff --git a/builtin/providers/azure/resource_azure_storage_blob.go b/builtin/providers/azure/resource_azure_storage_blob.go new file mode 100644 index 000000000000..8007af4f8799 --- /dev/null +++ b/builtin/providers/azure/resource_azure_storage_blob.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_storage_blob_test.go b/builtin/providers/azure/resource_azure_storage_blob_test.go new file mode 100644 index 000000000000..6f44389f0666 --- /dev/null +++ b/builtin/providers/azure/resource_azure_storage_blob_test.go @@ -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) diff --git a/builtin/providers/azure/resource_azure_storage_container.go b/builtin/providers/azure/resource_azure_storage_container.go new file mode 100644 index 000000000000..d97e6b9efbd1 --- /dev/null +++ b/builtin/providers/azure/resource_azure_storage_container.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_storage_container_test.go b/builtin/providers/azure/resource_azure_storage_container_test.go new file mode 100644 index 000000000000..80f044ccb851 --- /dev/null +++ b/builtin/providers/azure/resource_azure_storage_container_test.go @@ -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) diff --git a/builtin/providers/azure/resource_azure_storage_queue.go b/builtin/providers/azure/resource_azure_storage_queue.go new file mode 100644 index 000000000000..db4edbb60170 --- /dev/null +++ b/builtin/providers/azure/resource_azure_storage_queue.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_storage_queue_test.go b/builtin/providers/azure/resource_azure_storage_queue_test.go new file mode 100644 index 000000000000..de3702591d75 --- /dev/null +++ b/builtin/providers/azure/resource_azure_storage_queue_test.go @@ -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) diff --git a/builtin/providers/azure/resource_azure_storage_service.go b/builtin/providers/azure/resource_azure_storage_service.go new file mode 100644 index 000000000000..4292cc47e1ff --- /dev/null +++ b/builtin/providers/azure/resource_azure_storage_service.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_storage_service_helpers.go b/builtin/providers/azure/resource_azure_storage_service_helpers.go new file mode 100644 index 000000000000..68c7609202cf --- /dev/null +++ b/builtin/providers/azure/resource_azure_storage_service_helpers.go @@ -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 +} diff --git a/builtin/providers/azure/resource_azure_storage_service_test.go b/builtin/providers/azure/resource_azure_storage_service_test.go new file mode 100644 index 000000000000..4f1a54e2b49f --- /dev/null +++ b/builtin/providers/azure/resource_azure_storage_service_test.go @@ -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" +} +` diff --git a/builtin/providers/azure/resource_azure_virtual_network.go b/builtin/providers/azure/resource_azure_virtual_network.go index be66c1a42fd4..c5c250c55526 100644 --- a/builtin/providers/azure/resource_azure_virtual_network.go +++ b/builtin/providers/azure/resource_azure_virtual_network.go @@ -5,12 +5,11 @@ import ( "log" "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/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 ( @@ -37,6 +36,14 @@ func resourceAzureVirtualNetwork() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, + "dns_servers_names": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "subnet": &schema.Schema{ Type: schema.TypeSet, Required: true, @@ -94,11 +101,7 @@ func resourceAzureVirtualNetworkCreate(d *schema.ResourceData, meta interface{}) } } - network, err := createVirtualNetwork(d) - if err != nil { - return err - } - + network := createVirtualNetwork(d) nc.Configuration.VirtualNetworkSites = append(nc.Configuration.VirtualNetworkSites, network) req, err := virtualnetwork.NewClient(mc).SetVirtualNetworkConfiguration(nc) @@ -187,11 +190,7 @@ func resourceAzureVirtualNetworkUpdate(d *schema.ResourceData, meta interface{}) found := false for i, n := range nc.Configuration.VirtualNetworkSites { if n.Name == d.Id() { - network, err := createVirtualNetwork(d) - if err != nil { - return err - } - + network := createVirtualNetwork(d) nc.Configuration.VirtualNetworkSites[i] = network found = true @@ -263,15 +262,19 @@ func resourceAzureSubnetHash(v interface{}) int { return hashcode.String(subnet) } -func createVirtualNetwork(d *schema.ResourceData) (virtualnetwork.VirtualNetworkSite, error) { - var addressPrefix []string - err := mapstructure.WeakDecode(d.Get("address_space"), &addressPrefix) - if err != nil { - return virtualnetwork.VirtualNetworkSite{}, fmt.Errorf("Error decoding address_space: %s", err) +func createVirtualNetwork(d *schema.ResourceData) virtualnetwork.VirtualNetworkSite { + // fetch address spaces: + var prefixes []string + for _, prefix := range d.Get("address_space").([]interface{}) { + prefixes = append(prefixes, prefix.(string)) } - addressSpace := virtualnetwork.AddressSpace{ - AddressPrefix: addressPrefix, + // fetch DNS references: + 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 @@ -287,11 +290,14 @@ func createVirtualNetwork(d *schema.ResourceData) (virtualnetwork.VirtualNetwork } return virtualnetwork.VirtualNetworkSite{ - Name: d.Get("name").(string), - Location: d.Get("location").(string), - AddressSpace: addressSpace, - Subnets: subnets, - }, nil + Name: d.Get("name").(string), + Location: d.Get("location").(string), + AddressSpace: virtualnetwork.AddressSpace{ + AddressPrefix: prefixes, + }, + DNSServersRef: dnsRefs, + Subnets: subnets, + } } func associateSecurityGroups(d *schema.ResourceData, meta interface{}) error { diff --git a/builtin/providers/azure/resource_azure_virtual_network_test.go b/builtin/providers/azure/resource_azure_virtual_network_test.go index cc0817f03c91..70327cad050a 100644 --- a/builtin/providers/azure/resource_azure_virtual_network_test.go +++ b/builtin/providers/azure/resource_azure_virtual_network_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" + "github.com/Azure/azure-sdk-for-go/management/virtualnetwork" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/svanharmelen/azure-sdk-for-go/management/virtualnetwork" ) func TestAccAzureVirtualNetwork_basic(t *testing.T) { @@ -214,16 +214,19 @@ const testAccAzureVirtualNetwork_advanced = ` resource "azure_security_group" "foo" { name = "terraform-security-group1" location = "West US" +} - rule { - name = "RDP" - priority = 101 - source_cidr = "*" - source_port = "*" - destination_cidr = "*" - destination_port = "3389" - protocol = "TCP" - } +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" } resource "azure_virtual_network" "foo" { @@ -242,31 +245,24 @@ const testAccAzureVirtualNetwork_update = ` resource "azure_security_group" "foo" { name = "terraform-security-group1" location = "West US" +} - rule { - name = "RDP" - priority = 101 - source_cidr = "*" - source_port = "*" - destination_cidr = "*" - destination_port = "3389" - protocol = "TCP" - } +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" } resource "azure_security_group" "bar" { name = "terraform-security-group2" location = "West US" - - rule { - name = "SSH" - priority = 101 - source_cidr = "*" - source_port = "*" - destination_cidr = "*" - destination_port = "22" - protocol = "TCP" - } } resource "azure_virtual_network" "foo" { diff --git a/builtin/providers/azure/utils_test.go b/builtin/providers/azure/utils_test.go new file mode 100644 index 000000000000..3a321226332a --- /dev/null +++ b/builtin/providers/azure/utils_test.go @@ -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 + } +} diff --git a/website/source/docs/providers/azure/index.html.markdown b/website/source/docs/providers/azure/index.html.markdown index ea38e472e668..eec8494a1d90 100644 --- a/website/source/docs/providers/azure/index.html.markdown +++ b/website/source/docs/providers/azure/index.html.markdown @@ -46,3 +46,14 @@ The following arguments are supported: * `certificate` - (Optional) The certificate used to authenticate with the Azure API. If a `settings_file` is not provided `certificate` is required. 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. diff --git a/website/source/docs/providers/azure/r/data_disk.html.markdown b/website/source/docs/providers/azure/r/data_disk.html.markdown index 3e95c5736f37..5f0aad95b092 100644 --- a/website/source/docs/providers/azure/r/data_disk.html.markdown +++ b/website/source/docs/providers/azure/r/data_disk.html.markdown @@ -17,7 +17,7 @@ it will attach that disk. Otherwise it will create and attach a new empty disk. resource "azure_data_disk" "data" { lun = 0 size = 10 - storage = "yourstorage" + storage_service_name = "yourstorage" virtual_machine = "server1" } ``` @@ -43,10 +43,10 @@ The following arguments are supported: * `caching` - (Optional) The caching behavior of data disk. Valid options are: `None`, `ReadOnly` and `ReadWrite` (defaults `None`) -* `storage ` - (Optional) The name of an existing storage account within the - subscription which will be used to store the VHD of this disk. Required - if no value is supplied for `media_link`. Changing this forces a new - resource to be created. +* `storage_service_name` - (Optional) The name of an existing storage account + within the subscription which will be used to store the VHD of this disk. + Required if no value is supplied for `media_link`. Changing this forces + a new resource to be created. * `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 diff --git a/website/source/docs/providers/azure/r/dns_server.html.markdown b/website/source/docs/providers/azure/r/dns_server.html.markdown new file mode 100644 index 000000000000..d9f3567c2dd4 --- /dev/null +++ b/website/source/docs/providers/azure/r/dns_server.html.markdown @@ -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`. diff --git a/website/source/docs/providers/azure/r/hosted_service.html.markdown b/website/source/docs/providers/azure/r/hosted_service.html.markdown new file mode 100644 index 000000000000..9c76307ec6ef --- /dev/null +++ b/website/source/docs/providers/azure/r/hosted_service.html.markdown @@ -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`. diff --git a/website/source/docs/providers/azure/r/instance.html.markdown b/website/source/docs/providers/azure/r/instance.html.markdown index 0ece7668dee6..fb23df4ce0af 100644 --- a/website/source/docs/providers/azure/r/instance.html.markdown +++ b/website/source/docs/providers/azure/r/instance.html.markdown @@ -18,7 +18,7 @@ resource "azure_instance" "web" { name = "terraform-test" image = "Ubuntu Server 14.04 LTS" size = "Basic_A1" - storage = "yourstorage" + storage_service_name = "yourstorage" location = "West US" username = "terraform" password = "Pass!admin123" @@ -56,9 +56,9 @@ The following arguments are supported: belongs to. If a value is supplied `subnet` is required. Changing this forces a new resource to be created. -* `storage` - (Optional) The name of an existing storage account within the - subscription which will be used to store the VHDs of this instance. - Changing this forces a new resource to be created. +* `storage_service_name` - (Optional) The name of an existing storage account + within the subscription which will be used to store the VHDs of this + instance. Changing this forces a new resource to be created. * `reverse_dns` - (Optional) The DNS address to which the IP address of the hosted service resolves when queried using a reverse DNS query. Changing diff --git a/website/source/docs/providers/azure/r/local_network_connection.html.markdown b/website/source/docs/providers/azure/r/local_network_connection.html.markdown new file mode 100644 index 000000000000..53e758b3a229 --- /dev/null +++ b/website/source/docs/providers/azure/r/local_network_connection.html.markdown @@ -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. diff --git a/website/source/docs/providers/azure/r/security_group.html.markdown b/website/source/docs/providers/azure/r/security_group.html.markdown new file mode 100644 index 000000000000..771923e5bdbe --- /dev/null +++ b/website/source/docs/providers/azure/r/security_group.html.markdown @@ -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. diff --git a/website/source/docs/providers/azure/r/security_group.markdown b/website/source/docs/providers/azure/r/security_group.markdown deleted file mode 100644 index c4699f716a97..000000000000 --- a/website/source/docs/providers/azure/r/security_group.markdown +++ /dev/null @@ -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. diff --git a/website/source/docs/providers/azure/r/security_group_rule.html.markdown b/website/source/docs/providers/azure/r/security_group_rule.html.markdown new file mode 100644 index 000000000000..004d73eb5434 --- /dev/null +++ b/website/source/docs/providers/azure/r/security_group_rule.html.markdown @@ -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`. diff --git a/website/source/docs/providers/azure/r/storage_blob.html.markdown b/website/source/docs/providers/azure/r/storage_blob.html.markdown new file mode 100644 index 000000000000..0453f68a2016 --- /dev/null +++ b/website/source/docs/providers/azure/r/storage_blob.html.markdown @@ -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`. diff --git a/website/source/docs/providers/azure/r/storage_container.html.markdown b/website/source/docs/providers/azure/r/storage_container.html.markdown new file mode 100644 index 000000000000..45bf7d873083 --- /dev/null +++ b/website/source/docs/providers/azure/r/storage_container.html.markdown @@ -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`. diff --git a/website/source/docs/providers/azure/r/storage_queue.html.markdown b/website/source/docs/providers/azure/r/storage_queue.html.markdown new file mode 100644 index 000000000000..8b2abc5d87d1 --- /dev/null +++ b/website/source/docs/providers/azure/r/storage_queue.html.markdown @@ -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`. diff --git a/website/source/docs/providers/azure/r/storage_service.html.markdown b/website/source/docs/providers/azure/r/storage_service.html.markdown new file mode 100644 index 000000000000..a1e9fca0c562 --- /dev/null +++ b/website/source/docs/providers/azure/r/storage_service.html.markdown @@ -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`. diff --git a/website/source/docs/providers/azure/r/virtual_network.html.markdown b/website/source/docs/providers/azure/r/virtual_network.html.markdown index 66140c73dbf2..50f717714e20 100644 --- a/website/source/docs/providers/azure/r/virtual_network.html.markdown +++ b/website/source/docs/providers/azure/r/virtual_network.html.markdown @@ -40,6 +40,9 @@ The following arguments are supported: * `location` - (Required) The location/region where the virtual network is created. Changing this forces a new resource to be created. +* `dns_servers` - (Optional) List of names of DNS servers previously registered + on Azure. + * `subnet` - (Required) Can be specified multiple times to define multiple subnets. Each `subnet` block supports fields documented below. diff --git a/website/source/layouts/azure.erb b/website/source/layouts/azure.erb index 4699b354d4c5..f0104828502d 100644 --- a/website/source/layouts/azure.erb +++ b/website/source/layouts/azure.erb @@ -17,14 +17,46 @@ azure_data_disk + > + azure_dns_server + + + > + azure_hosted_service + + > azure_instance + > + azure_local_network_connection + + > azure_security_group + > + azure_security_group_rule + + + > + azure_storage_blob + + + > + azure_storage_container + + + > + azure_storage_queue + + + > + azure_storage_service + + > azure_virtual_network