diff --git a/builtin/providers/azurerm/provider.go b/builtin/providers/azurerm/provider.go index 9f6476c53aa1..b65e2d4850d7 100644 --- a/builtin/providers/azurerm/provider.go +++ b/builtin/providers/azurerm/provider.go @@ -40,6 +40,8 @@ func Provider() terraform.ResourceProvider { "azurerm_resource_group": resourceArmResourceGroup(), "azurerm_virtual_network": resourceArmVirtualNetwork(), "azurerm_local_network_gateway": resourceArmLocalNetworkGateway(), + "azurerm_network_interface": resourceArmNetworkInterface(), + "azurerm_public_ip": resourceArmPublicIP(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/azurerm/resource_arm_network_interface.go b/builtin/providers/azurerm/resource_arm_network_interface.go new file mode 100644 index 000000000000..cbfed1da3448 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_network_interface.go @@ -0,0 +1,374 @@ +package azurerm + +import ( + "fmt" + "net/http" + "time" + + "github.com/Azure/azure-sdk-for-go/arm/network" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +// resourceArmNetworkInterface returns the *schema.Resource +// associated to network interface resources on ARM. +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, + }, + + "resource_group_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "vm_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "mac_address": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "network_security_group_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + + "ip_config": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "private_ip_address": &schema.Schema{ + Type: schema.TypeString, + // required only when 'dynamic_provate_ip' is NOT set. + Optional: true, + ConflictsWith: []string{"dynamic_private_ip"}, + }, + "dynamic_private_ip": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + ConflictsWith: []string{"private_ip_address"}, + }, + "subnet_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "public_ip_id": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "load_balancer_backend_pool_ids": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: schema.TypeString, + }, + "load_balancer_inbound_nat_rule_ids": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: schema.TypeString, + }, + }, + }, + }, + + "dns_servers": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "applied_dns_servers": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + + "internal_name": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "internal_fqdn": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// resourceArmNetworkInterfaceCreate goes ahead and creates the specified ARM network interface. +func resourceArmNetworkInterfaceCreate(d *schema.ResourceData, meta interface{}) error { + ifaceClient := meta.(*ArmClient).ifaceClient + + name := d.Get("name").(string) + location := d.Get("location").(string) + resGrp := d.Get("resource_group_name").(string) + vmId := d.Get("vm_id").(string) + // TODO: netSecGrp := d.Get("network_security_group_id").(string) + + // get dns servers: + var dnses []string + if v, ok := d.GetOk("dns_servers"); ok { + for _, dns := range v.([]interface{}) { + dnses = append(dnses, dns.(string)) + } + } + + // get applied dns servers: + var usedDnses []string + if v, ok := d.GetOk("applied_dns_servers"); ok { + for _, adns := range v.([]interface{}) { + usedDnses = append(usedDnses, adns.(string)) + } + } + + // get ip configurations: + var ipconfigs []network.InterfaceIPConfiguration + if configs := d.Get("ip_config").([]interface{}); len(configs) > 0 { + for _, ipconfig := range configs { + conf := ipconfig.(map[string]interface{}) + + name := conf["name"].(string) + sub := conf["subnet_id"].(string) + + // set the allocation method and respective address: + var addr string + var allocMeth network.IPAllocationMethod + if b, ok := conf["dynamic_private_ip"]; ok && b.(bool) { + allocMeth = network.Dynamic + addr = "" + } else { + allocMeth = network.Static + addr = conf["private_ip_address"].(string) + } + + // get the optional public IP to bind to: + var pubip string + if v, ok := conf["public_ip_id"]; ok { + pubip = v.(string) + } + + // check and get the ids of the associated load balancer + // backend address pools: + var backpools []network.SubResource + if bps, ok := d.GetOk("load_balancer_backend_pool_ids"); ok { + for _, bp := range bps.([]interface{}) { + b := bp.(string) + backpools = append(backpools, network.SubResource{&b}) + } + } + + // now; check for any load balancer inbound nat rules: + var natrules []network.SubResource + if nrs, ok := d.GetOk("load_balancer_inbound_nat_rule_ids"); ok { + for _, nr := range nrs.([]interface{}) { + n := nr.(string) + natrules = append(natrules, network.SubResource{&n}) + } + } + + // finally, make and append the ipconfigs: + ipconfigs = append(ipconfigs, network.InterfaceIPConfiguration{ + Name: &name, + Properties: &network.InterfaceIPConfigurationPropertiesFormat{ + PrivateIPAddress: &addr, + PrivateIPAllocationMethod: allocMeth, + Subnet: &network.SubResource{&sub}, + PublicIPAddress: &network.SubResource{&pubip}, + LoadBalancerBackendAddressPools: &backpools, + LoadBalancerInboundNatRules: &natrules, + }, + }) + } + } + + resp, err := ifaceClient.CreateOrUpdate(resGrp, name, network.Interface{ + Name: &name, + Location: &location, + Properties: &network.InterfacePropertiesFormat{ + VirtualMachine: &network.SubResource{&vmId}, + IPConfigurations: &ipconfigs, + // TODO: NetworkSecurityGroup: &network.SubResource{&netSecGrp}, + DNSSettings: &network.InterfaceDNSSettings{ + DNSServers: &dnses, + AppliedDNSServers: &usedDnses, + }, + }, + }) + if err != nil { + return fmt.Errorf("Error encountered while issuing ARM interface %q creation: %s", name, err) + } + + d.SetId(*resp.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"Accepted", "Updating"}, + Target: "Succeded", + Refresh: interfaceStateRefreshFunc(meta, name, resGrp), + Timeout: 10 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for ARM interface %q creation: %s", name, err) + } + + return resourceArmNetworkInterfaceRead(d, meta) +} + +// resourceArmNetworkInterfaceRead goes ahead and reads the state of the corresponding ARM network interface. +func resourceArmNetworkInterfaceRead(d *schema.ResourceData, meta interface{}) error { + ifaceClient := meta.(*ArmClient).ifaceClient + + // parse the id to get the name of the interface + // and containing resource group: + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return fmt.Errorf("Error parsing id of ARM interface: %s", err) + } + + resGrp := id.ResourceGroup + name := id.Path["networkInterfaces"] + + // fetch the interface off Azure: + iface, err := ifaceClient.Get(resGrp, name) + if iface.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("Error reading the state of network interface %q off Azure: %s", name, err) + } + + // now update all the variable fields: + props := *iface.Properties + + d.Set("vm_id", *props.VirtualMachine.ID) + // TODO: d.Set("network_security_group_id", *props.NetworkSecurityGroup.ID) + d.Set("mac_address", *props.MacAddress) + + // get the ip configs: + var ipConfigs []map[string]interface{} + for _, ipconf := range *props.IPConfigurations { + v := map[string]interface{}{} + + v["id"] = *ipconf.ID + v["name"] = *ipconf.Name + + // check for the allocation method and the address it could imply: + if ipconf.Properties.PrivateIPAllocationMethod == network.Static { + v["private_ip_address"] = *ipconf.Properties.PrivateIPAddress + v["dynamic_private_ip"] = false + } else { // guaranteed to be network.Dynamic + v["private_ip_address"] = "" + v["dynamic_private_ip"] = true + } + + v["subnet_id"] = *ipconf.Properties.Subnet.ID + v["public_ip_id"] = *ipconf.Properties.PublicIPAddress.ID + + // get the load balancer backpools and inbound nat rules: + var backpools []string + if bps := ipconf.Properties.LoadBalancerBackendAddressPools; bps != nil { + for _, bp := range *bps { + backpools = append(backpools, *bp.ID) + } + + v["load_balancer_backend_pool_ids"] = backpools + } else { + + } + + var natrules []string + if nrs := ipconf.Properties.LoadBalancerInboundNatRules; nrs != nil { + for _, nr := range *nrs { + natrules = append(natrules, *nr.ID) + } + + v["load_balancer_inbound_nat_rule_ids"] = natrules + } + + // and finally, append the new list element: + ipConfigs = append(ipConfigs, v) + } + d.Set("ipConfig", ipConfigs) + + // and finally; read the DNS settings: + d.Set("dns_servers", *iface.Properties.DNSSettings.DNSServers) + d.Set("applied_dns_servers", *iface.Properties.DNSSettings.AppliedDNSServers) + d.Set("internal_name", *iface.Properties.DNSSettings.InternalDNSNameLabel) + d.Set("internal_fqdn", *iface.Properties.DNSSettings.InternalFqdn) + + return nil +} + +// resourceArmNetworkInterfaceDelete deletes the specified ARM network interface. +func resourceArmNetworkInterfaceDelete(d *schema.ResourceData, meta interface{}) error { + ifaceClient := meta.(*ArmClient).ifaceClient + + // fetch the name of the network interface and the containing + // resource group from the given parser. + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return fmt.Errorf("Error parsing ID of ARM network interface: %s") + } + resGrp := id.ResourceGroup + name := id.Path["networkInterfaces"] + + // then, issue the actual deletion: + resp, err := ifaceClient.Delete(resGrp, name) + if resp.StatusCode == http.StatusNotFound { + return nil + } + if err != nil { + return fmt.Errorf("Error issuing deletion fo ARM network interface %q: %s", name, err) + } + + return nil +} + +// interfaceStateRefreshFunc returns the resource.StateRefreshFunc for the +// given interface under the given resource group. +func interfaceStateRefreshFunc(meta interface{}, name, resGrp string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := meta.(*ArmClient).ifaceClient.Get(resGrp, name) + if err != nil { + return nil, "", err + } + + return resp, *resp.Properties.ProvisioningState, nil + } +} diff --git a/builtin/providers/azurerm/resource_arm_network_interface_test.go b/builtin/providers/azurerm/resource_arm_network_interface_test.go new file mode 100644 index 000000000000..c89bb37afe7f --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_network_interface_test.go @@ -0,0 +1,138 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccArmNetworkInterface(t *testing.T) { + name := "azurerm_virtual_network.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAccArmInterfaceDeleted, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureRMNetworkInterfaceConfig, + Check: resource.ComposeTestCheckFunc( + testCheckAccArmInterfaceExists(name), + resource.TestCheckResourceAttr(name, "name", "acceptanceTestNetworkInterface1"), + resource.TestCheckResourceAttr(name, "location", "West US"), + resource.TestCheckResourceAttr(name, "ip_config.0.name", "acceptanceTestIpConfiguration1"), + resource.TestCheckResourceAttr(name, "ip_config.0.dynamic_private_ip", "true"), + resource.TestCheckResourceAttr(name, "dns_servers.0", "8.8.8.8"), + resource.TestCheckResourceAttr(name, "dns_servers.1", "8.8.4.4"), + resource.TestCheckResourceAttr(name, "applied_servers.0", "8.8.8.8"), + resource.TestCheckResourceAttr(name, "internal_name", "iface1"), + ), + }, + }, + }) +} + +// testCheckAccArmInterfaceExists returns the resource.TestCheckFunc which +// verifies that the virtual network with the provided internal name exists and +// is well defined both within the schema, and on Azure. +func testCheckAccArmInterfaceExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // check forexistence in internal state: + res, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Could not find Network Interface %q.", name) + } + + resName := res.Primary.Attributes["name"] + resGrp := res.Primary.Attributes["resource_group_name"] + + ifaceClient := testAccProvider.Meta().(*ArmClient).vnetClient + + resp, err := ifaceClient.Get(resGrp, resName) + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Network interface %q does not exist on Azure!", resName) + } + if err != nil { + return fmt.Errorf("Error reading the state of network interface %q: %s", resName, err) + } + + return nil + } +} + +// testCheckAccArmInterfaceDeleted is a resource.TestCheckFunc which checks +// that out network interface has been deleted off Azure. +func testCheckAccArmInterfaceDeleted(s *terraform.State) error { + for _, res := range s.RootModule().Resources { + if res.Type != "azurerm_network_interface" { + continue + } + + name := res.Primary.Attributes["name"] + resGrp := res.Primary.Attributes["resource_group_name"] + + ifaceClient := testAccProvider.Meta().(ArmClient).ifaceClient + resp, err := ifaceClient.Get(resGrp, name) + + if resp.StatusCode == http.StatusNotFound { + return nil + } + + if err != nil { + return fmt.Errorf("Error checking if ARM network interface %q got deleted: %s", name, err) + } + } + + return nil +} + +// testAccAzureRMNetworkInterfaceConfig is the config tests will be conducted upon: +var testAccAzureRMNetworkInterfaceConfig = ` +resource "azurerm_resource_group" "test" { + name = "acceptanceTestResourceGroup1" + location = "West US" +} + +resource "azurerm_virtual_network" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + name = "acceptanceTestVirtualNetwork1" + address_space = ["10.0.0.0/16"] + location = "West US" + + subnet { + name = "subnet1" + address_prefix = "10.0.1.0/24" + } +} + +# TODO: resource "azurerm_instance" "test" ... + +resource "azurerm_public_ip" "test" { + name = "testAccPublicIPAddress1" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.name}" + dns_name = "testAccDnsName1" + ip_config_id = "${azurerm_network_interface.test.ip_config.0.id}" +} + +resource "azurerm_network_interface" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + name = "acceptanceTestPublicIPAddress1" + location = "West US" + vm_id = "${azurerm_instance.test.id}" + # TODO: network_security_group_id = ... + + ip_config = { + name = "acceptanceTestIpConfiguration1" + dynamic_private_ip = true + # TODO: subnet_id = "${azurerm_virtual_network.test.subnet.HASH.id}" + public_ip_id = "${azurerm_public_ip.test.id}" + } + + dns_servers = ["8.8.8.8", "8.8.4.4"] + applied_dns_servers: ["8.8.8.8"] + internal_name = "iface1" +} +` diff --git a/builtin/providers/azurerm/resource_arm_public_ip.go b/builtin/providers/azurerm/resource_arm_public_ip.go new file mode 100644 index 000000000000..be335cb9167b --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_public_ip.go @@ -0,0 +1,222 @@ +package azurerm + +import ( + "fmt" + "net/http" + "time" + + "github.com/Azure/azure-sdk-for-go/arm/network" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +// resourceArmPublicIP returns the *schema.Resource +// associated to a public i p resources on ARM. +func resourceArmPublicIP() *schema.Resource { + return &schema.Resource{ + Create: resourceArmPublicIPCreate, + Read: resourceArmPublicIPRead, + Update: resourceArmPublicIPCreate, + Delete: resourceArmPublicIPDelete, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "resource_group_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "location": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + // the default allocation method: + "dynamic_ip": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + ConflictsWith: []string{"ip_address"}, + }, + + "ip_address": &schema.Schema{ + Type: schema.TypeString, + // required only when 'dynamic_provate_ip' is NOT set. + Optional: true, + ConflictsWith: []string{"dynamic_ip"}, + }, + + "ip_config_id": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "timeout": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + + "dns_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "fqdn": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + + "reverse_fqdn": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// resourceArmPublicIPCreate goes ahead and creates the specified ARM public i p. +func resourceArmPublicIPCreate(d *schema.ResourceData, meta interface{}) error { + publicIPClient := meta.(*ArmClient).publicIPClient + + // get the standard params: + name := d.Get("name").(string) + resGrp := d.Get("resGrp").(string) + dnsName := d.Get("dns_name").(string) + location := d.Get("location").(string) + ipConfId := d.Get("ip_config_id").(string) + + // get the IP allocation method or the fixed IP address: + var addr string + var allocMeth network.IPAllocationMethod + if dyn, ok := d.GetOk("dynamic_ip"); ok && dyn.(bool) { + addr = "" + allocMeth = network.Dynamic + } else { + if add, ok := d.GetOk("ip_address"); ok { + addr = add.(string) + allocMeth = network.Static + } else { + return fmt.Errorf("Error in public IP definition: 'ip_address' must be provided if 'dynamic_ip' is not set.") + } + } + + // now get the timeout: + var timeout int + if t, ok := d.GetOk("timeout"); ok { + timeout = t.(int) + } + + resp, err := publicIPClient.CreateOrUpdate(resGrp, name, network.PublicIPAddress{ + Name: &name, + Location: &location, + Properties: &network.PublicIPAddressPropertiesFormat{ + PublicIPAllocationMethod: allocMeth, + IPConfiguration: &network.SubResource{&ipConfId}, + IPAddress: &addr, + IdleTimeoutInMinutes: &timeout, + DNSSettings: &network.PublicIPAddressDNSSettings{ + DomainNameLabel: &dnsName, + }, + }, + }) + + if err != nil { + return fmt.Errorf("Error creating ARM public IP address %q: %s", name, err) + } + + d.SetId(*resp.ID) + + stateConf := &resource.StateChangeConf{ + Pending: []string{"Accepted", "Updating"}, + Target: "Succeded", + Refresh: publicIPStateRefreshFunc(meta, name, resGrp), + Timeout: 10 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for ARM public IP %q creation: %s", name, err) + } + + return resourceArmPublicIPRead(d, meta) +} + +// resourceArmPublicIPRead goes ahead and reads the state of the corresponding ARM public i p. +func resourceArmPublicIPRead(d *schema.ResourceData, meta interface{}) error { + publicIPClient := meta.(*ArmClient).publicIPClient + + // parse the ID for the name of the resource + // and that of the resource group: + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return fmt.Errorf("Error parsing the id of ARM public IP: %s") + } + name := id.Path["publicIPAddresses"] + resGrp := id.ResourceGroup + + // make a query to Azure: + pip, err := publicIPClient.Get(resGrp, name) + if pip.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("Error reading the state off Azure for public IP %q: %s", name, err) + } + + props := pip.Properties + + // start reading fields: + d.Set("ip_address", *props.IPAddress) + d.Set("dynamic_ip", props.PublicIPAllocationMethod == network.Dynamic) + d.Set("ip_config_id", *props.IPConfiguration.ID) + d.Set("timeout", *props.IdleTimeoutInMinutes) + d.Set("dns_name", *props.DNSSettings.DomainNameLabel) + d.Set("fqdn", *props.DNSSettings.ReverseFqdn) + d.Set("reverse_fqdn", *props.DNSSettings.ReverseFqdn) + + return nil +} + +// resourceArmPublicIPDelete deletes the specified ARM public i p. +func resourceArmPublicIPDelete(d *schema.ResourceData, meta interface{}) error { + publicIPClient := meta.(*ArmClient).publicIPClient + + // first; parse the resource ID for the name of the public IP resource as + // well as that of its containing resurce group: + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return fmt.Errorf("Error parsing public IP resource ID: %s", err) + } + name := id.Path["publicIPAddresses"] + resGrp := id.ResourceGroup + + // issue the actual deletion: + resp, err := publicIPClient.Delete(resGrp, name) + if resp.StatusCode == http.StatusNotFound { + return nil + } + if err != nil { + return fmt.Errorf("Error issuing deletion of Azure public IP address %q: %s", name, err) + } + + return nil +} + +// publicIPStateRefreshFunc returns the resource.StateRefreshFunc for the +// given public IP resource under the given resource group. +func publicIPStateRefreshFunc(meta interface{}, name, resGrp string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := meta.(*ArmClient).publicIPClient.Get(resGrp, name) + if err != nil { + return nil, "", err + } + + return resp, *resp.Properties.ProvisioningState, nil + } +} diff --git a/builtin/providers/azurerm/resource_arm_public_ip_test.go b/builtin/providers/azurerm/resource_arm_public_ip_test.go new file mode 100644 index 000000000000..891eca8e4334 --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_public_ip_test.go @@ -0,0 +1,89 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccArmPublicIPAddress(t *testing.T) { + name := "azurerm_public_ip.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAccArmPublicIPDeleted, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureRMPublicIPAddress, + Check: resource.ComposeTestCheckFunc( + testCheckAccArmPublicIPExists(name), + resource.TestCheckResourceAttr(name, "name", "acceptanceTestPublicIPAddress1"), + resource.TestCheckResourceAttr(name, "location", "West US"), + resource.TestCheckResourceAttr(name, "dns_name", "testAccDnsName1"), + ), + }, + }, + }) +} + +// testCheckAccArmPublicIPExists returns the resource.TestCheckFunc which +// verifies that the public IP with the provided internal name exists and +// is well defined both within the schema, and on Azure. +func testCheckAccArmPublicIPExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + // check forexistence in internal state: + res, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Could not find public IP %q.", name) + } + + resName := res.Primary.Attributes["name"] + resGrp := res.Primary.Attributes["resource_group_name"] + + publicIPClient := testAccProvider.Meta().(*ArmClient).vnetClient + + resp, err := publicIPClient.Get(resGrp, resName) + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Public IP %q does not exist on Azure!", resName) + } + if err != nil { + return fmt.Errorf("Error reading the state of public IP %q: %s", resName, err) + } + + return nil + } +} + +// testCheckAccArmPublicIPDeleted is a resource.TestCheckFunc which checks +// that out public IP has been deleted off Azure. +func testCheckAccArmPublicIPDeleted(s *terraform.State) error { + for _, res := range s.RootModule().Resources { + if res.Type != "azurerm_public_ip" { + continue + } + + name := res.Primary.Attributes["name"] + resGrp := res.Primary.Attributes["resource_group_name"] + + publicIPClient := testAccProvider.Meta().(ArmClient).publicIPClient + resp, err := publicIPClient.Get(resGrp, name) + + if resp.StatusCode == http.StatusNotFound { + return nil + } + + if err != nil { + return fmt.Errorf("Error checking if ARM public IP %q got deleted: %s", name, err) + } + } + + return nil +} + +// testAccAzureRMPublicIPAddress is the config tests will be conducted upon. +// It is the same as the config for network public IPs as the two resource are +// so co-dependendt. +var testAccAzureRMPublicIPAddress = testAccAzureRMNetworkInterfaceConfig diff --git a/builtin/providers/azurerm/resource_arm_virtual_network.go b/builtin/providers/azurerm/resource_arm_virtual_network.go index 305af5a76668..e795aecf3534 100644 --- a/builtin/providers/azurerm/resource_arm_virtual_network.go +++ b/builtin/providers/azurerm/resource_arm_virtual_network.go @@ -45,6 +45,10 @@ func resourceArmVirtualNetwork() *schema.Resource { Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, "name": &schema.Schema{ Type: schema.TypeString, Required: true, @@ -145,6 +149,7 @@ func resourceArmVirtualNetworkRead(d *schema.ResourceData, meta interface{}) err for _, subnet := range *vnet.Subnets { s := map[string]interface{}{} + s["id"] = *subnet.ID s["name"] = *subnet.Name s["address_prefix"] = *subnet.Properties.AddressPrefix if subnet.Properties.NetworkSecurityGroup != nil { diff --git a/builtin/providers/azurerm/set_utils.go b/builtin/providers/azurerm/set_utils.go new file mode 100644 index 000000000000..2325b294c8ba --- /dev/null +++ b/builtin/providers/azurerm/set_utils.go @@ -0,0 +1,36 @@ +package azurerm + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +// makeHashFunction is a helper function which; given a slice of field names +// and a list of the names returns a schema.SchemaSetFunc +func makeHashFunction(simpleFields []string, listFields []string) schema.SchemaSetFunc { + return func(v interface{}) int { + m := v.(map[string]interface{}) + s := "" + + // first; fetch the simple fields: + for _, field := range simpleFields { + if val, ok := m[field]; ok { + // NOTE: some fields may hold integers or booleans: + s = s + fmt.Sprintf("%v", val) + } + } + + // then; fetch the list fields: + for _, field := range listFields { + if val, ok := m[field]; ok { + for _, item := range val.([]interface{}) { + s = s + fmt.Sprintf("%v", item) + } + } + } + + return hashcode.String(s) + } +} 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..ef8abdc9b81e --- /dev/null +++ b/website/source/docs/providers/azurerm/r/network_interface.html.markdown @@ -0,0 +1,130 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_network_interface" +sidebar_current: "docs-azurerm-resource-network-interface" +description: |- + Creates a new VM network interface on Azure. +--- + +# azurerm\_network\_interface + +Creates a new VM network interface on Azure. + +## Example Usage + +``` +resource "azurerm_resource_group" "test" { + name = "acceptanceTestResourceGroup1" + location = "West US" +} + +resource "azurerm_virtual_network" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + name = "acceptanceTestVirtualNetwork1" + address_space = ["10.0.0.0/16"] + location = "West US" + + subnet { + name = "subnet1" + address_prefix = "10.0.1.0/24" + } +} + +# TODO: resource "azurerm_instance" "test" ... + +resource "azurerm_public_ip" "test" { + name = "testAccPublicIPAddress1" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.name}" + dns_name = "testAccDnsName1" + ip_config_id = "${azurerm_network_interface.test.ip_config.0.id}" +} + +resource "azurerm_network_interface" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + name = "acceptanceTestPublicIPAddress1" + location = "West US" + vm_id = "${azurerm_instance.test.id}" + # TODO: network_security_group_id = ... + + ip_config = { + name = "acceptanceTestIpConfiguration1" + dynamic_private_ip = true + # TODO: subnet_id = "${azurerm_virtual_network.test.subnet.HASH.id}" + public_ip_id = "${azurerm_public_ip.test.id}" + } + + dns_servers = ["8.8.8.8", "8.8.4.4"] + applied_dns_servers: ["8.8.8.8"] + internal_name = "iface1" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the network interface. Changes force + redeployment. + +* `resource_group_name` - (Required) The name of the resource group in which + the network interface should be created. Changes force redeployment. + +* `location` - (Required) The location where the network interface should be created. Must + be the same as the location of the resource group the interface will belong to. + For a list of all Azure locations, please consult [this link](http://azure.microsoft.com/en-us/regions/). Changes force redeployment. + +* `vm_id` - (Required) The ID of the VM the interface will be attached to. + Changes force redeployment. + +* `mac_address` - (Computed) The MAC address of the network interface. + +* `network_security_group_id` - (Optional) The ID of the network security group + by which to govern trafic through the interface. + +* `dns_servers` - (Optional) A list of addresses of DNS servers to be used. + +* `applied_dns_servers` - (Optional) A list consisting of the addresses of DNS + servers which get used for resolving addresses. + +* `internal_name` - (Optional) The domain name the interface should use internally + to refer to itself. + +* `internal_fqdn` - (Optional) The fully-qualified domain name the interface + should use internally to refer to itself. + +* `ip_config` - (Required) A list of fields denoting an IP configuration for the + interface. Can be declared multiple times for multiple configurations. + +An `ip_config` definition contains the following: + +* `id` - (Computed) The unique ID of the IP configuration. + +* `name` - (Required) Name of the network interface IP configuration. + +* `dynamic_private_ip` - (Optional) Boolean flag to indicate whether a not a + private IP address should be given to the interface dynamically. Conflicts + with `private_ip_address`. Is the default behavior. + +* `private_ip_address` - (Optional) Address to be used as the private IP for + the network interface. Conflicts with `dynamic_private_ip`. + +* `subnet_id` - (Required) ID of the subnet which the network interface should + be connected to. + +* `public_ip_id` - (Optional) ID of the public IP which points to the network + interface. + +* `load_balancer_bakend_bool_ids` - (Optional) List of the string IDs of load + balancer backend pools which rely on this public IP configuration. + +* `load_balancer_inbound_nat_rule_ids` - (Optional) List of the string IDs of load + balancer inbound NAT rules, which are enforced through this IP config. + + + +## Attributes Reference + +The following attributes are exported: + +* `id` - The network interface ID. diff --git a/website/source/docs/providers/azurerm/r/public_ip.html.markdown b/website/source/docs/providers/azurerm/r/public_ip.html.markdown new file mode 100644 index 000000000000..4e76a51d41b9 --- /dev/null +++ b/website/source/docs/providers/azurerm/r/public_ip.html.markdown @@ -0,0 +1,105 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_public_ip" +sidebar_current: "docs-azurerm-resource-public-ip" +description: |- + Creates a new Azure Public IP resource. +--- + +# azurerm\_public\_ip + +Creates a new Azure Public IP resource. + +## Example Usage + +``` +resource "azurerm_resource_group" "test" { + name = "acceptanceTestResourceGroup1" + location = "West US" +} + +resource "azurerm_virtual_network" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + name = "acceptanceTestVirtualNetwork1" + address_space = ["10.0.0.0/16"] + location = "West US" + + subnet { + name = "subnet1" + address_prefix = "10.0.1.0/24" + } +} + +# TODO: resource "azurerm_instance" "test" ... + +resource "azurerm_public_ip" "test" { + name = "testAccPublicIPAddress1" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.name}" + dns_name = "testAccDnsName1" + ip_config_id = "${azurerm_network_interface.test.ip_config.0.id}" +} + +resource "azurerm_network_interface" "test" { + resource_group_name = "${azurerm_resource_group.test.name}" + name = "acceptanceTestPublicIPAddress1" + location = "West US" + vm_id = "${azurerm_instance.test.id}" + # TODO: network_security_group_id = ... + + ip_config = { + name = "acceptanceTestIpConfiguration1" + dynamic_private_ip = true + # TODO: subnet_id = "${azurerm_virtual_network.test.subnet.HASH.id}" + public_ip_id = "${azurerm_public_ip.test.id}" + } + + dns_servers = ["8.8.8.8", "8.8.4.4"] + applied_dns_servers: ["8.8.8.8"] + internal_name = "iface1" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the public IP. Changes force + redeployment. + +* `resource_group_name` - (Required) The name of the resource group in which + the public IP should be created. Changes force redeployment. + +* `location` - (Required) The location where the public IP should be created. Must + be the same as the location of the resource group the public IP will belong to. + For a list of all Azure locations, please consult [this link](http://azure.microsoft.com/en-us/regions/). Changes force redeployment. + +* `dynamic_ip` - (Optional) Boolean flag to indicate whether a not an + IP address should be given to the public IP through dhcp. Conflicts + with `ip_address`. Using dynamic IP is the default behavior. + +* `ip_address` - (Optional) The IP address to be used for + the public IP. Conflicts with `dynamic_private_ip`. + +* `dns_name` - (Required) Unique public DNS prefix for the deployment. The fqdn + will look something like '.westus.cloudapp.azure.com'. Up to 62 + chars, digits or dashes, lowercase, should start with a letter: must conform + to '^[a-z][a-z0-9-]{1,61}[a-z0-9]$'. + +* `ip_config_id` - (Required) The ID of the IP Configuration to be used for + the public IP. + +* `fqdn` - (Computed) The fully-qualified domain name the public IP + should use internally to refer to itself. + +* `reverse_fqdn` - (Computed) The reverse fully qualified domain name + for the public IP. + +* `timeout` - (Optional) The positive number of minutes of laying idle + that will trigger a timeout. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The public IP ID. diff --git a/website/source/layouts/azurerm.erb b/website/source/layouts/azurerm.erb index fe00f7939d85..7a16017ac035 100644 --- a/website/source/layouts/azurerm.erb +++ b/website/source/layouts/azurerm.erb @@ -25,6 +25,13 @@ azurerm_local_network_gateway + > + azurerm_network_interface + + + > + azurerm_public_ip +