diff --git a/builtin/providers/azurerm/config.go b/builtin/providers/azurerm/config.go index 20f6f073b..43655b6cf 100644 --- a/builtin/providers/azurerm/config.go +++ b/builtin/providers/azurerm/config.go @@ -38,6 +38,7 @@ type ArmClient struct { vnetGatewayClient network.VirtualNetworkGatewaysClient vnetClient network.VirtualNetworksClient routeTablesClient network.RouteTablesClient + routesClient network.RoutesClient providers resources.ProvidersClient resourceGroupClient resources.GroupsClient @@ -193,6 +194,12 @@ func (c *Config) getArmClient() (*ArmClient, error) { rtc.Sender = autorest.CreateSender(withRequestLogging()) client.routeTablesClient = rtc + rc := network.NewRoutesClient(c.SubscriptionID) + setUserAgent(&rc.Client) + rc.Authorizer = spt + rc.Sender = autorest.CreateSender(withRequestLogging()) + client.routesClient = rc + rgc := resources.NewGroupsClient(c.SubscriptionID) setUserAgent(&rgc.Client) rgc.Authorizer = spt diff --git a/builtin/providers/azurerm/provider.go b/builtin/providers/azurerm/provider.go index bcf93684c..44cecf773 100644 --- a/builtin/providers/azurerm/provider.go +++ b/builtin/providers/azurerm/provider.go @@ -50,6 +50,7 @@ func Provider() terraform.ResourceProvider { "azurerm_subnet": resourceArmSubnet(), "azurerm_network_interface": resourceArmNetworkInterface(), "azurerm_route_table": resourceArmRouteTable(), + "azurerm_route": resourceArmRoute(), }, ConfigureFunc: providerConfigure, } diff --git a/builtin/providers/azurerm/resource_arm_route.go b/builtin/providers/azurerm/resource_arm_route.go new file mode 100644 index 000000000..837093e2f --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_route.go @@ -0,0 +1,161 @@ +package azurerm + +import ( + "fmt" + "log" + "net/http" + "time" + + "github.com/Azure/azure-sdk-for-go/arm/network" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceArmRoute() *schema.Resource { + return &schema.Resource{ + Create: resourceArmRouteCreate, + Read: resourceArmRouteRead, + Update: resourceArmRouteCreate, + Delete: resourceArmRouteDelete, + + 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, + }, + + "route_table_name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "address_prefix": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + + "next_hop_type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: validateRouteTableNextHopType, + }, + + "next_hop_in_ip_address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceArmRouteCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + routesClient := client.routesClient + + name := d.Get("name").(string) + rtName := d.Get("route_table_name").(string) + resGroup := d.Get("resource_group_name").(string) + + addressPrefix := d.Get("address_prefix").(string) + nextHopType := d.Get("next_hop_type").(string) + + armMutexKV.Lock(rtName) + defer armMutexKV.Unlock(rtName) + + properties := network.RoutePropertiesFormat{ + AddressPrefix: &addressPrefix, + NextHopType: network.RouteNextHopType(nextHopType), + } + + if v, ok := d.GetOk("next_hop_in_ip_address"); ok { + nextHopInIpAddress := v.(string) + properties.NextHopIPAddress = &nextHopInIpAddress + } + + route := network.Route{ + Name: &name, + Properties: &properties, + } + + resp, err := routesClient.CreateOrUpdate(resGroup, rtName, name, route) + if err != nil { + return err + } + d.SetId(*resp.ID) + + log.Printf("[DEBUG] Waiting for Route (%s) to become available", name) + stateConf := &resource.StateChangeConf{ + Pending: []string{"Accepted", "Updating"}, + Target: "Succeeded", + Refresh: routeStateRefreshFunc(client, resGroup, rtName, name), + Timeout: 10 * time.Minute, + } + if _, err := stateConf.WaitForState(); err != nil { + return fmt.Errorf("Error waiting for Route (%s) to become available: %s", name, err) + } + + return resourceArmRouteRead(d, meta) +} + +func resourceArmRouteRead(d *schema.ResourceData, meta interface{}) error { + routesClient := meta.(*ArmClient).routesClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + rtName := id.Path["routeTables"] + routeName := id.Path["routes"] + + resp, err := routesClient.Get(resGroup, rtName, routeName) + if resp.StatusCode == http.StatusNotFound { + d.SetId("") + return nil + } + if err != nil { + return fmt.Errorf("Error making Read request on Azure Route %s: %s", routeName, err) + } + + return nil +} + +func resourceArmRouteDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient) + routesClient := client.routesClient + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + rtName := id.Path["routeTables"] + routeName := id.Path["routes"] + + armMutexKV.Lock(rtName) + defer armMutexKV.Unlock(rtName) + + _, err = routesClient.Delete(resGroup, rtName, routeName) + + return err +} + +func routeStateRefreshFunc(client *ArmClient, resourceGroupName string, routeTableName string, routeName string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + res, err := client.routesClient.Get(resourceGroupName, routeTableName, routeName) + if err != nil { + return nil, "", fmt.Errorf("Error issuing read request in routeStateRefreshFunc to Azure ARM for route '%s' (RG: '%s') (NSG: '%s'): %s", routeName, resourceGroupName, routeTableName, err) + } + + return res, *res.Properties.ProvisioningState, nil + } +} diff --git a/builtin/providers/azurerm/resource_arm_route_table.go b/builtin/providers/azurerm/resource_arm_route_table.go index daedbafff..615aaba72 100644 --- a/builtin/providers/azurerm/resource_arm_route_table.go +++ b/builtin/providers/azurerm/resource_arm_route_table.go @@ -242,7 +242,7 @@ func validateRouteTableNextHopType(v interface{}, k string) (ws []string, errors "vnetlocal": true, "internet": true, "virtualappliance": true, - "null": true, + "none": true, } if !hopTypes[value] { diff --git a/builtin/providers/azurerm/resource_arm_route_table_test.go b/builtin/providers/azurerm/resource_arm_route_table_test.go index 8df04e3b2..1ec93602e 100644 --- a/builtin/providers/azurerm/resource_arm_route_table_test.go +++ b/builtin/providers/azurerm/resource_arm_route_table_test.go @@ -35,7 +35,7 @@ func TestResourceAzureRMRouteTableNextHopType_validation(t *testing.T) { ErrCount: 0, }, { - Value: "Null", + Value: "None", ErrCount: 0, }, { diff --git a/builtin/providers/azurerm/resource_arm_route_test.go b/builtin/providers/azurerm/resource_arm_route_test.go new file mode 100644 index 000000000..3c8d6e8be --- /dev/null +++ b/builtin/providers/azurerm/resource_arm_route_test.go @@ -0,0 +1,151 @@ +package azurerm + +import ( + "fmt" + "net/http" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAzureRMRoute_basic(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRouteDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureRMRoute_basic, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRouteExists("azurerm_route.test"), + ), + }, + }, + }) +} + +func TestAccAzureRMRoute_multipleRoutes(t *testing.T) { + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMRouteDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAzureRMRoute_basic, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRouteExists("azurerm_route.test"), + ), + }, + + resource.TestStep{ + Config: testAccAzureRMRoute_multipleRoutes, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMRouteExists("azurerm_route.test1"), + ), + }, + }, + }) +} + +func testCheckAzureRMRouteExists(name string) resource.TestCheckFunc { + return func(s *terraform.State) error { + + rs, ok := s.RootModule().Resources[name] + if !ok { + return fmt.Errorf("Not found: %s", name) + } + + name := rs.Primary.Attributes["name"] + rtName := rs.Primary.Attributes["route_table_name"] + resourceGroup, hasResourceGroup := rs.Primary.Attributes["resource_group_name"] + if !hasResourceGroup { + return fmt.Errorf("Bad: no resource group found in state for route: %s", name) + } + + conn := testAccProvider.Meta().(*ArmClient).routesClient + + resp, err := conn.Get(resourceGroup, rtName, name) + if err != nil { + return fmt.Errorf("Bad: Get on routesClient: %s", err) + } + + if resp.StatusCode == http.StatusNotFound { + return fmt.Errorf("Bad: Route %q (resource group: %q) does not exist", name, resourceGroup) + } + + return nil + } +} + +func testCheckAzureRMRouteDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*ArmClient).routesClient + + for _, rs := range s.RootModule().Resources { + if rs.Type != "azurerm_route" { + continue + } + + name := rs.Primary.Attributes["name"] + rtName := rs.Primary.Attributes["route_table_name"] + resourceGroup := rs.Primary.Attributes["resource_group_name"] + + resp, err := conn.Get(resourceGroup, rtName, name) + + if err != nil { + return nil + } + + if resp.StatusCode != http.StatusNotFound { + return fmt.Errorf("Route still exists:\n%#v", resp.Properties) + } + } + + return nil +} + +var testAccAzureRMRoute_basic = ` +resource "azurerm_resource_group" "test" { + name = "acceptanceTestResourceGroup1" + location = "West US" +} + +resource "azurerm_route_table" "test" { + name = "acceptanceTestRouteTable1" + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_route" "test" { + name = "acceptanceTestRoute1" + resource_group_name = "${azurerm_resource_group.test.name}" + route_table_name = "${azurerm_route_table.test.name}" + + address_prefix = "10.1.0.0/16" + next_hop_type = "vnetlocal" +} +` + +var testAccAzureRMRoute_multipleRoutes = ` +resource "azurerm_resource_group" "test" { + name = "acceptanceTestResourceGroup1" + location = "West US" +} + +resource "azurerm_route_table" "test" { + name = "acceptanceTestRouteTable1" + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_route" "test1" { + name = "acceptanceTestRoute2" + resource_group_name = "${azurerm_resource_group.test.name}" + route_table_name = "${azurerm_route_table.test.name}" + + address_prefix = "10.2.0.0/16" + next_hop_type = "none" +} +` diff --git a/website/source/docs/providers/azurerm/r/route.html.markdown b/website/source/docs/providers/azurerm/r/route.html.markdown new file mode 100644 index 000000000..82f1ae378 --- /dev/null +++ b/website/source/docs/providers/azurerm/r/route.html.markdown @@ -0,0 +1,61 @@ +--- +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_route" +sidebar_current: "docs-azurerm-resource-network-route" +description: |- + Creates a new Route Resource +--- + +# azurerm\_route + +Creates a new Route Resource + +## Example Usage + +``` +resource "azurerm_resource_group" "test" { + name = "acceptanceTestResourceGroup1" + location = "West US" +} + +resource "azurerm_route_table" "test" { + name = "acceptanceTestRouteTable1" + location = "West US" + resource_group_name = "${azurerm_resource_group.test.name}" +} + +resource "azurerm_route" "test" { + name = "acceptanceTestRoute1" + resource_group_name = "${azurerm_resource_group.test.name}" + route_table_name = "${azurerm_route_table.test.name}" + + address_prefix = "10.1.0.0/16" + next_hop_type = "vnetlocal" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the route. Changing this forces a + new resource to be created. + +* `resource_group_name` - (Required) The name of the resource group in which to + create the route. + + +* `route_table_name` - (Required) The name of the route table to which to create the route + +* `address_prefix` - (Required) The destination CIDR to which the route applies, such as 10.1.0.0/16 + +* `next_hop_type` - (Required) The type of Azure hop the packet should be sent to. + Possible values are VirtualNetworkGateway, VnetLocal, Internet, VirtualAppliance and None + +* `next_hop_in_ip_address` - (Optional) Contains the IP address packets should be forwarded to. Next hop values are only allowed in routes where the next hop type is VirtualAppliance. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The Route ID. diff --git a/website/source/layouts/azurerm.erb b/website/source/layouts/azurerm.erb index 8542fe625..e6e041857 100644 --- a/website/source/layouts/azurerm.erb +++ b/website/source/layouts/azurerm.erb @@ -51,6 +51,10 @@ azurerm_route_table + > + azurerm_route + +