diff --git a/builtin/providers/azurerm/provider.go b/builtin/providers/azurerm/provider.go index 5e1ff9dbb0ca..e2dea9974865 100644 --- a/builtin/providers/azurerm/provider.go +++ b/builtin/providers/azurerm/provider.go @@ -48,6 +48,7 @@ func Provider() terraform.ResourceProvider { "azurerm_network_security_rule": resourceArmNetworkSecurityRule(), "azurerm_public_ip": resourceArmPublicIp(), "azurerm_subnet": resourceArmSubnet(), + "azurerm_network_interface": resourceArmNetworkInterface(), }, ConfigureFunc: providerConfigure, } diff --git a/builtin/providers/azurerm/resource_arm_network_interface_card.go b/builtin/providers/azurerm/resource_arm_network_interface_card.go new file mode 100644 index 000000000000..ddcc7a454dc2 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_network_interface_card.go @@ -0,0 +1,393 @@ +package azurerm + +import ( + "bytes" + "fmt" + "log" + "net/http" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/arm/network" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceArmNetworkInterface() *schema.Resource { + return &schema.Resource{ + Create: resourceArmNetworkInterfaceCreate, + Read: resourceArmNetworkInterfaceRead, + Update: resourceArmNetworkInterfaceCreate, + Delete: resourceArmNetworkInterfaceDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + StateFunc: azureRMNormalizeLocation, + }, + + "resource_group_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "network_security_group_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "mac_address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "virtual_machine_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "ip_configuration": &schema.Schema{ + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "private_ip_address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "private_ip_address_allocation": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validateNetworkInterfacePrivateIpAddressAllocation, + }, + + "public_ip_address_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "load_balancer_backend_address_pools_ids": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "load_balancer_inbound_nat_rules_ids": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + }, + Set: resourceArmNetworkInterfaceIpConfigurationHash, + }, + + "dns_servers": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "internal_dns_name_label": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "applied_dns_servers": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "internal_fqdn": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceArmNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + ifaceClient := client.ifaceClient + + log.Printf("[INFO] preparing arguments for Azure ARM Network Interface creation.") + + name := d.Get("name").(string) + location := d.Get("location").(string) + resGroup := d.Get("resource_group_name").(string) + + properties := network.InterfacePropertiesFormat{} + + if v, ok := d.GetOk("network_security_group_id"); ok { + nsgId := v.(string) + properties.NetworkSecurityGroup = &network.SecurityGroup{ + ID: &nsgId, + } + } + + dns, hasDns := d.GetOk("dns_servers") + nameLabel, hasNameLabel := d.GetOk("internal_dns_name_label") + if hasDns || hasNameLabel { + ifaceDnsSettings := network.InterfaceDNSSettings{} + + if hasDns { + var dnsServers []string + dns := dns.(*schema.Set).List() + for _, v := range dns { + str := v.(string) + dnsServers = append(dnsServers, str) + } + ifaceDnsSettings.DNSServers = &dnsServers + } + + if hasNameLabel { + name_label := nameLabel.(string) + ifaceDnsSettings.InternalDNSNameLabel = &name_label + + } + + properties.DNSSettings = &ifaceDnsSettings + } + + ipConfigs, sgErr := expandAzureRmNetworkInterfaceIpConfigurations(d) + if sgErr != nil { + return fmt.Errorf("Error Building list of Network Interface IP Configurations: %s", sgErr) + } + if len(ipConfigs) > 0 { + properties.IPConfigurations = &ipConfigs + } + + iface := network.Interface{ + Name: &name, + Location: &location, + Properties: &properties, + } + + resp, err := ifaceClient.CreateOrUpdate(resGroup, name, iface) + if err != nil { + return err + } + + d.SetId(*resp.ID) + + log.Printf("[DEBUG] Waiting for Network Interface (%s) to become available", name) + stateConf := &resource.StateChangeConf{ + Pending: []string{"Accepted", "Updating"}, + Target: "Succeeded", + Refresh: networkInterfaceStateRefreshFunc(client, resGroup, name), + Timeout: 10 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for Network Interface (%s) to become available: %s", name, err) + } + + return resourceArmNetworkInterfaceRead(d, meta) +} + +func resourceArmNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) error { + ifaceClient := meta.(*ArmClient).ifaceClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + name := id.Path["networkInterfaces"] + + resp, err := ifaceClient.Get(resGroup, name, "") + if resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("Error making Read request on Azure Netowkr Interface %s: %s", name, err) + } + + iface := *resp.Properties + + if iface.MacAddress != nil { + if *iface.MacAddress != "" { + d.Set("mac_address", iface.MacAddress) + } + } + + if iface.VirtualMachine != nil { + if *iface.VirtualMachine.ID != "" { + d.Set("virtual_machine_id", *iface.VirtualMachine.ID) + } + } + + if iface.DNSSettings != nil { + if iface.DNSSettings.AppliedDNSServers != nil && len(*iface.DNSSettings.AppliedDNSServers) > 0 { + dnsServers := make([]string, 0, len(*iface.DNSSettings.AppliedDNSServers)) + for _, dns := range *iface.DNSSettings.AppliedDNSServers { + dnsServers = append(dnsServers, dns) + } + + if err := d.Set("applied_dns_servers", dnsServers); err != nil { + return err + } + } + + if iface.DNSSettings.InternalFqdn != nil && *iface.DNSSettings.InternalFqdn != "" { + d.Set("internal_fqdn", iface.DNSSettings.InternalFqdn) + } + } + + return nil +} + +func resourceArmNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) error { + ifaceClient := meta.(*ArmClient).ifaceClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + name := id.Path["networkInterfaces"] + + _, err = ifaceClient.Delete(resGroup, name) + + return err +} + +func networkInterfaceStateRefreshFunc(client *ArmClient, resourceGroupName string, ifaceName string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + res, err := client.ifaceClient.Get(resourceGroupName, ifaceName, "") + if err != nil { + return nil, "", fmt.Errorf("Error issuing read request in networkInterfaceStateRefreshFunc to Azure ARM for network interace '%s' (RG: '%s'): %s", ifaceName, resourceGroupName, err) + } + + return res, *res.Properties.ProvisioningState, nil + } +} + +func resourceArmNetworkInterfaceIpConfigurationHash(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["subnet_id"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["private_ip_address_allocation"].(string))) + + return hashcode.String(buf.String()) +} + +func validateNetworkInterfacePrivateIpAddressAllocation(v interface{}, k string) (ws []string, errors []error) { + value := strings.ToLower(v.(string)) + allocations := map[string]bool{ + "static": true, + "dynamic": true, + } + + if !allocations[value] { + errors = append(errors, fmt.Errorf("Network Interface Allocations can only be Static or Dynamic")) + } + return +} + +func expandAzureRmNetworkInterfaceIpConfigurations(d *schema.ResourceData) ([]network.InterfaceIPConfiguration, error) { + configs := d.Get("ip_configuration").(*schema.Set).List() + ipConfigs := make([]network.InterfaceIPConfiguration, 0, len(configs)) + + for _, configRaw := range configs { + data := configRaw.(map[string]interface{}) + + subnet_id := data["subnet_id"].(string) + private_ip_allocation_method := data["private_ip_address_allocation"].(string) + + properties := network.InterfaceIPConfigurationPropertiesFormat{ + Subnet: &network.Subnet{ + ID: &subnet_id, + }, + PrivateIPAllocationMethod: &private_ip_allocation_method, + } + + if v := data["private_ip_address"].(string); v != "" { + properties.PrivateIPAddress = &v + } + + if v := data["public_ip_address_id"].(string); v != "" { + properties.PublicIPAddress = &network.PublicIPAddress{ + ID: &v, + } + } + + if v, ok := data["load_balancer_backend_address_pools_ids"]; ok { + var ids []network.BackendAddressPool + pools := v.(*schema.Set).List() + for _, p := range pools { + pool_id := p.(string) + id := network.BackendAddressPool{ + ID: &pool_id, + } + + ids = append(ids, id) + } + + properties.LoadBalancerBackendAddressPools = &ids + } + + if v, ok := data["load_balancer_inbound_nat_rules_ids"]; ok { + var natRules []network.InboundNatRule + rules := v.(*schema.Set).List() + for _, r := range rules { + rule_id := r.(string) + rule := network.InboundNatRule{ + ID: &rule_id, + } + + natRules = append(natRules, rule) + } + + properties.LoadBalancerInboundNatRules = &natRules + } + + name := data["name"].(string) + ipConfig := network.InterfaceIPConfiguration{ + Name: &name, + Properties: &properties, + } + + ipConfigs = append(ipConfigs, ipConfig) + } + + return ipConfigs, nil +} diff --git a/builtin/providers/azurerm/resource_arm_network_interface_card_test.go b/builtin/providers/azurerm/resource_arm_network_interface_card_test.go new file mode 100644 index 000000000000..26db03ed5d30 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_network_interface_card_test.go @@ -0,0 +1,191 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAzureRMNetworkInterface_basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMNetworkInterfaceDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureRMNetworkInterface_basic, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMNetworkInterfaceExists("azurerm_network_interface.test"), + ), + }, + }, + }) +} + +///TODO: Re-enable this test when https://github.com/Azure/azure-sdk-for-go/issues/259 is fixed +//func TestAccAzureRMNetworkInterface_addingIpConfigurations(t *testing.T) { +// +// resource.Test(t, resource.TestCase{ +// PreCheck: func() { testAccPreCheck(t) }, +// Providers: testAccProviders, +// CheckDestroy: testCheckAzureRMNetworkInterfaceDestroy, +// Steps: []resource.TestStep{ +// resource.TestStep{ +// Config: testAccAzureRMNetworkInterface_basic, +// Check: resource.ComposeTestCheckFunc( +// testCheckAzureRMNetworkInterfaceExists("azurerm_network_interface.test"), +// resource.TestCheckResourceAttr( +// "azurerm_network_interface.test", "ip_configuration.#", "1"), +// ), +// }, +// +// resource.TestStep{ +// Config: testAccAzureRMNetworkInterface_extraIpConfiguration, +// Check: resource.ComposeTestCheckFunc( +// testCheckAzureRMNetworkInterfaceExists("azurerm_network_interface.test"), +// resource.TestCheckResourceAttr( +// "azurerm_network_interface.test", "ip_configuration.#", "2"), +// ), +// }, +// }, +// }) +//} + +func testCheckAzureRMNetworkInterfaceExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // Ensure we have enough information in state to look up in API + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + name := rs.Primary.Attributes["name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for availability set: %s", name) + } + + conn := testAccProvider.Meta().(*ArmClient).ifaceClient + + resp, err := conn.Get(resourceGroup, name, "") + if err != nil { + return fmt.Errorf("Bad: Get on ifaceClient: %s", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: Network Interface %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMNetworkInterfaceDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).ifaceClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_network_interface" { + continue + } + + name := rs.Primary.Attributes["name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(resourceGroup, name, "") + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Network Interface still exists:\n%#v", resp.Properties) + } + } + + return nil +} + +var testAccAzureRMNetworkInterface_basic = ` +resource "azurerm_resource_group" "test" { + name = "acceptanceTestResourceGroup1" + location = "West US" +} + +resource "azurerm_virtual_network" "test" { + name = "acceptanceTestVirtualNetwork1" + address_space = ["10.0.0.0/16"] + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_subnet" "test" { + name = "testsubnet" + resource_group_name = "${azurerm_resource_group.test.name}" + virtual_network_name = "${azurerm_virtual_network.test.name}" + address_prefix = "10.0.2.0/24" +} + +resource "azurerm_network_interface" "test" { + name = "acceptanceTestNetworkInterface1" + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" + + ip_configuration { + name = "testconfiguration1" + subnet_id = "${azurerm_subnet.test.id}" + private_ip_address_allocation = "dynamic" + } +} +` + +//TODO: Re-enable this test when https://github.com/Azure/azure-sdk-for-go/issues/259 is fixed +//var testAccAzureRMNetworkInterface_extraIpConfiguration = ` +//resource "azurerm_resource_group" "test" { +// name = "acceptanceTestResourceGroup1" +// location = "West US" +//} +// +//resource "azurerm_virtual_network" "test" { +// name = "acceptanceTestVirtualNetwork1" +// address_space = ["10.0.0.0/16"] +// location = "West US" +// resource_group_name = "${azurerm_resource_group.test.name}" +//} +// +//resource "azurerm_subnet" "test" { +// name = "testsubnet" +// resource_group_name = "${azurerm_resource_group.test.name}" +// virtual_network_name = "${azurerm_virtual_network.test.name}" +// address_prefix = "10.0.2.0/24" +//} +// +//resource "azurerm_subnet" "test1" { +// name = "testsubnet1" +// resource_group_name = "${azurerm_resource_group.test.name}" +// virtual_network_name = "${azurerm_virtual_network.test.name}" +// address_prefix = "10.0.1.0/24" +//} +// +//resource "azurerm_network_interface" "test" { +// name = "acceptanceTestNetworkInterface1" +// location = "West US" +// resource_group_name = "${azurerm_resource_group.test.name}" +// +// ip_configuration { +// name = "testconfiguration1" +// subnet_id = "${azurerm_subnet.test.id}" +// private_ip_address_allocation = "dynamic" +// } +// +// ip_configuration { +// name = "testconfiguration2" +// subnet_id = "${azurerm_subnet.test1.id}" +// private_ip_address_allocation = "dynamic" +// primary = true +// } +//} +//` diff --git a/website/source/docs/providers/azurerm/r/network_interface.html.markdown b/website/source/docs/providers/azurerm/r/network_interface.html.markdown new file mode 100644 index 000000000000..ef392a288a5c --- /dev/null +++ b/website/source/docs/providers/azurerm/r/network_interface.html.markdown @@ -0,0 +1,86 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azure_virtual_network" +sidebar_current: "docs-azurerm-resource-virtual-network" +description: |- + Creates a new virtual network including any configured subnets. Each subnet can optionally be configured with a security group to be associated with the subnet. +--- + +# azurerm\_virtual\_network + +Creates a new virtual network including any configured subnets. Each subnet can +optionally be configured with a security group to be associated with the subnet. + +## Example Usage + +``` +resource "azurerm_virtual_network" "test" { + name = "virtualNetwork1" + resource_group_name = "${azurerm_resource_group.test.name}" + address_space = ["10.0.0.0/16"] + location = "West US" + + subnet { + name = "subnet1" + address_prefix = "10.0.1.0/24" + } + + subnet { + name = "subnet2" + address_prefix = "10.0.2.0/24" + } + + subnet { + name = "subnet3" + address_prefix = "10.0.3.0/24" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the network interface. Changing this forces a + new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to + create the network interface. + +* `location` - (Required) The location/region where the network interface is + created. Changing this forces a new resource to be created. + +* `network_security_group_id` - (Optional) The ID of the Network Security Group to associate with + the network interface. + +* `internal_dns_name_label` - (Optional) Relative DNS name for this NIC used for internal communications between VMs in the same VNet + +* `dns_servers` - (Optional) List of DNS servers IP addresses to use for this NIC, overrides the VNet-level server list + +* `ip_configuration` - (Optional) Collection of ipConfigurations associated with this NIC. Each `ip_configuration` block supports fields documented below. + +The `ip_configuration` block supports: + +* `name` - (Required) User-defined name of the IP. + +* `subnet_id` - (Required) Reference to a subnet in which this NIC has been created. + +* `private_ip_address` - (Optional) Static IP Address. + +* `private_ip_address_allocation` - (Required) Defines how a private IP address is assigned. Options are Static or Dynamic. + +* `public_ip_address_id` - (Optional) Reference to a Public IP Address to associate with this NIC + +* `load_balancer_backend_address_pools_ids` - (Optional) List of Load Balancer Backend Address Pool IDs references to which this NIC belongs + +* `load_balancer_inbound_nat_rules_ids` - (Optional) List of Load Balancer Inbound Nat Rules IDs involving this NIC + +## Attributes Reference + +The following attributes are exported: + +* `id` - The virtual NetworkConfiguration ID. +* `mac_address` - +* `virtual_machine_id` - +* `applied_dns_servers` - +* `internal_fqdn` - diff --git a/website/source/layouts/azurerm.erb b/website/source/layouts/azurerm.erb index eb27154e0d28..155e8058ad8a 100644 --- a/website/source/layouts/azurerm.erb +++ b/website/source/layouts/azurerm.erb @@ -1,62 +1,67 @@ + <% wrap_layout :inner do %> <% content_for :sidebar do %>
<% end %>