From a46c8221517f810db25c2ac1f9baff415e55fadd Mon Sep 17 00:00:00 2001 From: Jeffrey Cline Date: Thu, 6 Dec 2018 17:31:02 -0800 Subject: [PATCH 01/13] Fix for name with only spaces --- azurerm/data_source_route_table.go | 278 ++++++------ azurerm/helpers/validate/strings.go | 23 + azurerm/helpers/validate/strings_test.go | 67 +++ azurerm/resource_arm_route_table.go | 535 ++++++++++++----------- 4 files changed, 497 insertions(+), 406 deletions(-) create mode 100644 azurerm/helpers/validate/strings.go create mode 100644 azurerm/helpers/validate/strings_test.go diff --git a/azurerm/data_source_route_table.go b/azurerm/data_source_route_table.go index 3dc249079d51..da8348ec8178 100644 --- a/azurerm/data_source_route_table.go +++ b/azurerm/data_source_route_table.go @@ -1,139 +1,139 @@ -package azurerm - -import ( - "fmt" - - "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/helper/validation" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" -) - -func dataSourceArmRouteTable() *schema.Resource { - return &schema.Resource{ - Read: dataSourceArmRouteTableRead, - - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.NoZeroValues, - }, - - "resource_group_name": resourceGroupNameForDataSourceSchema(), - - "location": locationForDataSourceSchema(), - - "route": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Computed: true, - }, - - "address_prefix": { - Type: schema.TypeString, - Computed: true, - }, - - "next_hop_type": { - Type: schema.TypeString, - Computed: true, - }, - - "next_hop_in_ip_address": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - - "subnets": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "tags": tagsForDataSourceSchema(), - }, - } -} - -func dataSourceArmRouteTableRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient).routeTablesClient - ctx := meta.(*ArmClient).StopContext - - name := d.Get("name").(string) - resourceGroup := d.Get("resource_group_name").(string) - - resp, err := client.Get(ctx, resourceGroup, name, "") - if err != nil { - if utils.ResponseWasNotFound(resp.Response) { - return fmt.Errorf("Error: Route Table %q (Resource Group %q) was not found", name, resourceGroup) - } - return fmt.Errorf("Error making Read request on Azure Route Table %q: %+v", name, err) - } - - d.SetId(*resp.ID) - - d.Set("name", name) - d.Set("resource_group_name", resourceGroup) - if location := resp.Location; location != nil { - d.Set("location", azureRMNormalizeLocation(*location)) - } - - if props := resp.RouteTablePropertiesFormat; props != nil { - if err := d.Set("route", flattenRouteTableDataSourceRoutes(props.Routes)); err != nil { - return err - } - - if err := d.Set("subnets", flattenRouteTableDataSourceSubnets(props.Subnets)); err != nil { - return err - } - } - - flattenAndSetTags(d, resp.Tags) - - return nil -} - -func flattenRouteTableDataSourceRoutes(input *[]network.Route) []interface{} { - results := make([]interface{}, 0) - - if routes := input; routes != nil { - for _, route := range *routes { - r := make(map[string]interface{}) - - r["name"] = *route.Name - - if props := route.RoutePropertiesFormat; props != nil { - r["address_prefix"] = *props.AddressPrefix - r["next_hop_type"] = string(props.NextHopType) - if ip := props.NextHopIPAddress; ip != nil { - r["next_hop_in_ip_address"] = *ip - } - } - - results = append(results, r) - } - } - - return results -} - -func flattenRouteTableDataSourceSubnets(subnets *[]network.Subnet) []string { - output := make([]string, 0) - - if subnets != nil { - for _, subnet := range *subnets { - output = append(output, *subnet.ID) - } - } - - return output -} +package azurerm + +import ( + "fmt" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceArmRouteTable() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmRouteTableRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + + "resource_group_name": resourceGroupNameForDataSourceSchema(), + + "location": locationForDataSourceSchema(), + + "route": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + + "address_prefix": { + Type: schema.TypeString, + Computed: true, + }, + + "next_hop_type": { + Type: schema.TypeString, + Computed: true, + }, + + "next_hop_in_ip_address": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "subnets": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "tags": tagsForDataSourceSchema(), + }, + } +} + +func dataSourceArmRouteTableRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).routeTablesClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + resp, err := client.Get(ctx, resourceGroup, name, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error: Route Table %q (Resource Group %q) was not found", name, resourceGroup) + } + return fmt.Errorf("Error making Read request on Azure Route Table %q: %+v", name, err) + } + + d.SetId(*resp.ID) + + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + if location := resp.Location; location != nil { + d.Set("location", azureRMNormalizeLocation(*location)) + } + + if props := resp.RouteTablePropertiesFormat; props != nil { + if err := d.Set("route", flattenRouteTableDataSourceRoutes(props.Routes)); err != nil { + return err + } + + if err := d.Set("subnets", flattenRouteTableDataSourceSubnets(props.Subnets)); err != nil { + return err + } + } + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func flattenRouteTableDataSourceRoutes(input *[]network.Route) []interface{} { + results := make([]interface{}, 0) + + if routes := input; routes != nil { + for _, route := range *routes { + r := make(map[string]interface{}) + + r["name"] = *route.Name + + if props := route.RoutePropertiesFormat; props != nil { + r["address_prefix"] = *props.AddressPrefix + r["next_hop_type"] = string(props.NextHopType) + if ip := props.NextHopIPAddress; ip != nil { + r["next_hop_in_ip_address"] = *ip + } + } + + results = append(results, r) + } + } + + return results +} + +func flattenRouteTableDataSourceSubnets(subnets *[]network.Subnet) []string { + output := make([]string, 0) + + if subnets != nil { + for _, subnet := range *subnets { + output = append(output, *subnet.ID) + } + } + + return output +} diff --git a/azurerm/helpers/validate/strings.go b/azurerm/helpers/validate/strings.go new file mode 100644 index 000000000000..ddf04566ab86 --- /dev/null +++ b/azurerm/helpers/validate/strings.go @@ -0,0 +1,23 @@ +package validate + +import ( + "fmt" + "regexp" + + "github.com/hashicorp/terraform/helper/schema" +) + +// NoEmptyStrings validates that the string is not just whitespace characters (equal to [\r\n\t\f\v ]) +func NoEmptyStrings() schema.SchemaValidateFunc { + return func(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + if !ok { + return nil, []error{fmt.Errorf("expected type of %q to be string", k)} + } + + if invalid := regexp.MustCompile(`^$|\s+`).MatchString(v); invalid { + return nil, []error{fmt.Errorf("%q must not begin or end with whitespace and can not be empty", k)} + } + return nil, nil + } +} diff --git a/azurerm/helpers/validate/strings_test.go b/azurerm/helpers/validate/strings_test.go new file mode 100644 index 000000000000..753540b5fdd9 --- /dev/null +++ b/azurerm/helpers/validate/strings_test.go @@ -0,0 +1,67 @@ +package validate + +import "testing" + +func TestNoEmptyStrings(t *testing.T) { + cases := []struct { + Value string + TestName string + ErrCount int + }{ + { + Value: "!", + TestName: "Exclamation", + ErrCount: 0, + }, + { + Value: ".", + TestName: "Period", + ErrCount: 0, + }, + { + Value: "-", + TestName: "Hyphen", + ErrCount: 0, + }, + { + Value: "_", + TestName: "Underscore", + ErrCount: 0, + }, + { + Value: "10.1.0.0/16", + TestName: "IP", + ErrCount: 0, + }, + { + Value: "", + TestName: "Empty", + ErrCount: 1, + }, + { + Value: " ", + TestName: "Space", + ErrCount: 1, + }, + { + Value: " 1", + TestName: "DoubleSpaceOne", + ErrCount: 1, + }, + { + Value: "1 ", + TestName: "OneSpace", + ErrCount: 1, + }, + } + + for _, tc := range cases { + t.Run(tc.TestName, func(t *testing.T) { + _, errors := NoEmptyStrings()(tc.Value, tc.TestName) + + if len(errors) < tc.ErrCount { + t.Fatalf("Expected NoEmptyStrings to have %d not %d errors for %q", tc.ErrCount, len(errors), tc.TestName) + } + }) + } +} diff --git a/azurerm/resource_arm_route_table.go b/azurerm/resource_arm_route_table.go index aba9faad16a5..4c8ae4b8034c 100644 --- a/azurerm/resource_arm_route_table.go +++ b/azurerm/resource_arm_route_table.go @@ -1,267 +1,268 @@ -package azurerm - -import ( - "fmt" - "log" - - "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/helper/validation" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" -) - -var routeTableResourceName = "azurerm_route_table" - -func resourceArmRouteTable() *schema.Resource { - return &schema.Resource{ - Create: resourceArmRouteTableCreateUpdate, - Read: resourceArmRouteTableRead, - Update: resourceArmRouteTableCreateUpdate, - Delete: resourceArmRouteTableDelete, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.NoZeroValues, - }, - - "location": locationSchema(), - - "resource_group_name": resourceGroupNameSchema(), - - "route": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.NoZeroValues, - }, - - "address_prefix": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.NoZeroValues, - }, - - "next_hop_type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(network.RouteNextHopTypeVirtualNetworkGateway), - string(network.RouteNextHopTypeVnetLocal), - string(network.RouteNextHopTypeInternet), - string(network.RouteNextHopTypeVirtualAppliance), - string(network.RouteNextHopTypeNone), - }, true), - DiffSuppressFunc: suppress.CaseDifference, - }, - - "next_hop_in_ip_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.NoZeroValues, - }, - }, - }, - }, - - "disable_bgp_route_propagation": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "subnets": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "tags": tagsSchema(), - }, - } -} - -func resourceArmRouteTableCreateUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient).routeTablesClient - ctx := meta.(*ArmClient).StopContext - - log.Printf("[INFO] preparing arguments for AzureRM Route Table creation.") - - name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) - resGroup := d.Get("resource_group_name").(string) - tags := d.Get("tags").(map[string]interface{}) - - routeSet := network.RouteTable{ - Name: &name, - Location: &location, - RouteTablePropertiesFormat: &network.RouteTablePropertiesFormat{ - Routes: expandRouteTableRoutes(d), - DisableBgpRoutePropagation: utils.Bool(d.Get("disable_bgp_route_propagation").(bool)), - }, - Tags: expandTags(tags), - } - - future, err := client.CreateOrUpdate(ctx, resGroup, name, routeSet) - if err != nil { - return fmt.Errorf("Error Creating/Updating Route Table %q (Resource Group %q): %+v", name, resGroup, err) - } - - if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Error waiting for completion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) - } - - read, err := client.Get(ctx, resGroup, name, "") - if err != nil { - return err - } - if read.ID == nil { - return fmt.Errorf("Cannot read Route Table %q (resource group %q) ID", name, resGroup) - } - - d.SetId(*read.ID) - - return resourceArmRouteTableRead(d, meta) -} - -func resourceArmRouteTableRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient).routeTablesClient - ctx := meta.(*ArmClient).StopContext - - id, err := parseAzureResourceID(d.Id()) - if err != nil { - return err - } - resGroup := id.ResourceGroup - name := id.Path["routeTables"] - - resp, err := client.Get(ctx, resGroup, name, "") - if err != nil { - if utils.ResponseWasNotFound(resp.Response) { - d.SetId("") - return nil - } - return fmt.Errorf("Error making Read request on Azure Route Table %q: %+v", name, err) - } - - d.Set("name", name) - d.Set("resource_group_name", resGroup) - if location := resp.Location; location != nil { - d.Set("location", azureRMNormalizeLocation(*location)) - } - - if props := resp.RouteTablePropertiesFormat; props != nil { - d.Set("disable_bgp_route_propagation", props.DisableBgpRoutePropagation) - if err := d.Set("route", flattenRouteTableRoutes(props.Routes)); err != nil { - return err - } - - if err := d.Set("subnets", flattenRouteTableSubnets(props.Subnets)); err != nil { - return err - } - } - - flattenAndSetTags(d, resp.Tags) - - return nil -} - -func resourceArmRouteTableDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient).routeTablesClient - ctx := meta.(*ArmClient).StopContext - - id, err := parseAzureResourceID(d.Id()) - if err != nil { - return err - } - resGroup := id.ResourceGroup - name := id.Path["routeTables"] - - future, err := client.Delete(ctx, resGroup, name) - if err != nil { - if !response.WasNotFound(future.Response()) { - return fmt.Errorf("Error deleting Route Table %q (Resource Group %q): %+v", name, resGroup, err) - } - } - - if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Error waiting for deletion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) - } - - return nil -} - -func expandRouteTableRoutes(d *schema.ResourceData) *[]network.Route { - configs := d.Get("route").([]interface{}) - routes := make([]network.Route, 0, len(configs)) - - for _, configRaw := range configs { - data := configRaw.(map[string]interface{}) - - route := network.Route{ - Name: utils.String(data["name"].(string)), - RoutePropertiesFormat: &network.RoutePropertiesFormat{ - AddressPrefix: utils.String(data["address_prefix"].(string)), - NextHopType: network.RouteNextHopType(data["next_hop_type"].(string)), - }, - } - - if v := data["next_hop_in_ip_address"].(string); v != "" { - route.RoutePropertiesFormat.NextHopIPAddress = &v - } - - routes = append(routes, route) - } - - return &routes -} - -func flattenRouteTableRoutes(input *[]network.Route) []interface{} { - results := make([]interface{}, 0) - - if routes := input; routes != nil { - for _, route := range *routes { - r := make(map[string]interface{}) - - r["name"] = *route.Name - - if props := route.RoutePropertiesFormat; props != nil { - r["address_prefix"] = *props.AddressPrefix - r["next_hop_type"] = string(props.NextHopType) - if ip := props.NextHopIPAddress; ip != nil { - r["next_hop_in_ip_address"] = *ip - } - } - - results = append(results, r) - } - } - - return results -} - -func flattenRouteTableSubnets(subnets *[]network.Subnet) []string { - output := make([]string, 0) - - if subnets != nil { - for _, subnet := range *subnets { - output = append(output, *subnet.ID) - } - } - - return output -} +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +var routeTableResourceName = "azurerm_route_table" + +func resourceArmRouteTable() *schema.Resource { + return &schema.Resource{ + Create: resourceArmRouteTableCreateUpdate, + Read: resourceArmRouteTableRead, + Update: resourceArmRouteTableCreateUpdate, + Delete: resourceArmRouteTableDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + + "location": locationSchema(), + + "resource_group_name": resourceGroupNameSchema(), + + "route": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + + "address_prefix": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + + "next_hop_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.RouteNextHopTypeVirtualNetworkGateway), + string(network.RouteNextHopTypeVnetLocal), + string(network.RouteNextHopTypeInternet), + string(network.RouteNextHopTypeVirtualAppliance), + string(network.RouteNextHopTypeNone), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "next_hop_in_ip_address": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + }, + }, + }, + + "disable_bgp_route_propagation": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "subnets": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceArmRouteTableCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).routeTablesClient + ctx := meta.(*ArmClient).StopContext + + log.Printf("[INFO] preparing arguments for AzureRM Route Table creation.") + + name := d.Get("name").(string) + location := azureRMNormalizeLocation(d.Get("location").(string)) + resGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + routeSet := network.RouteTable{ + Name: &name, + Location: &location, + RouteTablePropertiesFormat: &network.RouteTablePropertiesFormat{ + Routes: expandRouteTableRoutes(d), + DisableBgpRoutePropagation: utils.Bool(d.Get("disable_bgp_route_propagation").(bool)), + }, + Tags: expandTags(tags), + } + + future, err := client.CreateOrUpdate(ctx, resGroup, name, routeSet) + if err != nil { + return fmt.Errorf("Error Creating/Updating Route Table %q (Resource Group %q): %+v", name, resGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for completion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) + } + + read, err := client.Get(ctx, resGroup, name, "") + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read Route Table %q (resource group %q) ID", name, resGroup) + } + + d.SetId(*read.ID) + + return resourceArmRouteTableRead(d, meta) +} + +func resourceArmRouteTableRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).routeTablesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + name := id.Path["routeTables"] + + resp, err := client.Get(ctx, resGroup, name, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on Azure Route Table %q: %+v", name, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resGroup) + if location := resp.Location; location != nil { + d.Set("location", azureRMNormalizeLocation(*location)) + } + + if props := resp.RouteTablePropertiesFormat; props != nil { + d.Set("disable_bgp_route_propagation", props.DisableBgpRoutePropagation) + if err := d.Set("route", flattenRouteTableRoutes(props.Routes)); err != nil { + return err + } + + if err := d.Set("subnets", flattenRouteTableSubnets(props.Subnets)); err != nil { + return err + } + } + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func resourceArmRouteTableDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).routeTablesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + name := id.Path["routeTables"] + + future, err := client.Delete(ctx, resGroup, name) + if err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error deleting Route Table %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) + } + + return nil +} + +func expandRouteTableRoutes(d *schema.ResourceData) *[]network.Route { + configs := d.Get("route").([]interface{}) + routes := make([]network.Route, 0, len(configs)) + + for _, configRaw := range configs { + data := configRaw.(map[string]interface{}) + + route := network.Route{ + Name: utils.String(data["name"].(string)), + RoutePropertiesFormat: &network.RoutePropertiesFormat{ + AddressPrefix: utils.String(data["address_prefix"].(string)), + NextHopType: network.RouteNextHopType(data["next_hop_type"].(string)), + }, + } + + if v := data["next_hop_in_ip_address"].(string); v != "" { + route.RoutePropertiesFormat.NextHopIPAddress = &v + } + + routes = append(routes, route) + } + + return &routes +} + +func flattenRouteTableRoutes(input *[]network.Route) []interface{} { + results := make([]interface{}, 0) + + if routes := input; routes != nil { + for _, route := range *routes { + r := make(map[string]interface{}) + + r["name"] = *route.Name + + if props := route.RoutePropertiesFormat; props != nil { + r["address_prefix"] = *props.AddressPrefix + r["next_hop_type"] = string(props.NextHopType) + if ip := props.NextHopIPAddress; ip != nil { + r["next_hop_in_ip_address"] = *ip + } + } + + results = append(results, r) + } + } + + return results +} + +func flattenRouteTableSubnets(subnets *[]network.Subnet) []string { + output := make([]string, 0) + + if subnets != nil { + for _, subnet := range *subnets { + output = append(output, *subnet.ID) + } + } + + return output +} From e983d7084e9d1eecd4230ca01424623f597e8b1b Mon Sep 17 00:00:00 2001 From: Jeffrey Cline Date: Thu, 6 Dec 2018 18:24:33 -0800 Subject: [PATCH 02/13] Removed regex and replaced with TrimSpace --- azurerm/helpers/validate/strings.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/azurerm/helpers/validate/strings.go b/azurerm/helpers/validate/strings.go index ddf04566ab86..ff8a947a383d 100644 --- a/azurerm/helpers/validate/strings.go +++ b/azurerm/helpers/validate/strings.go @@ -2,7 +2,7 @@ package validate import ( "fmt" - "regexp" + "strings" "github.com/hashicorp/terraform/helper/schema" ) @@ -15,9 +15,12 @@ func NoEmptyStrings() schema.SchemaValidateFunc { return nil, []error{fmt.Errorf("expected type of %q to be string", k)} } - if invalid := regexp.MustCompile(`^$|\s+`).MatchString(v); invalid { - return nil, []error{fmt.Errorf("%q must not begin or end with whitespace and can not be empty", k)} + newv := strings.TrimSpace(v) + + if len(newv) == 0 { + return nil, []error{fmt.Errorf("%q must not be empty", k)} } + return nil, nil } } From 05bd771ec010434d6d912c10838815527538b05a Mon Sep 17 00:00:00 2001 From: Jeffrey Cline Date: Thu, 6 Dec 2018 18:28:46 -0800 Subject: [PATCH 03/13] Updated test for new behavior --- azurerm/helpers/validate/strings_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azurerm/helpers/validate/strings_test.go b/azurerm/helpers/validate/strings_test.go index 753540b5fdd9..a65737c96eaa 100644 --- a/azurerm/helpers/validate/strings_test.go +++ b/azurerm/helpers/validate/strings_test.go @@ -46,12 +46,12 @@ func TestNoEmptyStrings(t *testing.T) { { Value: " 1", TestName: "DoubleSpaceOne", - ErrCount: 1, + ErrCount: 0, }, { Value: "1 ", TestName: "OneSpace", - ErrCount: 1, + ErrCount: 0, }, } From ea89ddfc8881cbb9c4531f5b4f93cd1acf7b9b84 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline Date: Thu, 6 Dec 2018 19:04:00 -0800 Subject: [PATCH 04/13] Saved as LF only --- azurerm/data_source_route_table.go | 278 ++++++------ azurerm/helpers/validate/strings.go | 52 +-- azurerm/helpers/validate/strings_test.go | 134 +++--- azurerm/resource_arm_route_table.go | 536 +++++++++++------------ 4 files changed, 500 insertions(+), 500 deletions(-) diff --git a/azurerm/data_source_route_table.go b/azurerm/data_source_route_table.go index da8348ec8178..467f0eaa587f 100644 --- a/azurerm/data_source_route_table.go +++ b/azurerm/data_source_route_table.go @@ -1,139 +1,139 @@ -package azurerm - -import ( - "fmt" - - "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/schema" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" -) - -func dataSourceArmRouteTable() *schema.Resource { - return &schema.Resource{ - Read: dataSourceArmRouteTableRead, - - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validate.NoEmptyStrings(), - }, - - "resource_group_name": resourceGroupNameForDataSourceSchema(), - - "location": locationForDataSourceSchema(), - - "route": { - Type: schema.TypeList, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Computed: true, - }, - - "address_prefix": { - Type: schema.TypeString, - Computed: true, - }, - - "next_hop_type": { - Type: schema.TypeString, - Computed: true, - }, - - "next_hop_in_ip_address": { - Type: schema.TypeString, - Computed: true, - }, - }, - }, - }, - - "subnets": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "tags": tagsForDataSourceSchema(), - }, - } -} - -func dataSourceArmRouteTableRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient).routeTablesClient - ctx := meta.(*ArmClient).StopContext - - name := d.Get("name").(string) - resourceGroup := d.Get("resource_group_name").(string) - - resp, err := client.Get(ctx, resourceGroup, name, "") - if err != nil { - if utils.ResponseWasNotFound(resp.Response) { - return fmt.Errorf("Error: Route Table %q (Resource Group %q) was not found", name, resourceGroup) - } - return fmt.Errorf("Error making Read request on Azure Route Table %q: %+v", name, err) - } - - d.SetId(*resp.ID) - - d.Set("name", name) - d.Set("resource_group_name", resourceGroup) - if location := resp.Location; location != nil { - d.Set("location", azureRMNormalizeLocation(*location)) - } - - if props := resp.RouteTablePropertiesFormat; props != nil { - if err := d.Set("route", flattenRouteTableDataSourceRoutes(props.Routes)); err != nil { - return err - } - - if err := d.Set("subnets", flattenRouteTableDataSourceSubnets(props.Subnets)); err != nil { - return err - } - } - - flattenAndSetTags(d, resp.Tags) - - return nil -} - -func flattenRouteTableDataSourceRoutes(input *[]network.Route) []interface{} { - results := make([]interface{}, 0) - - if routes := input; routes != nil { - for _, route := range *routes { - r := make(map[string]interface{}) - - r["name"] = *route.Name - - if props := route.RoutePropertiesFormat; props != nil { - r["address_prefix"] = *props.AddressPrefix - r["next_hop_type"] = string(props.NextHopType) - if ip := props.NextHopIPAddress; ip != nil { - r["next_hop_in_ip_address"] = *ip - } - } - - results = append(results, r) - } - } - - return results -} - -func flattenRouteTableDataSourceSubnets(subnets *[]network.Subnet) []string { - output := make([]string, 0) - - if subnets != nil { - for _, subnet := range *subnets { - output = append(output, *subnet.ID) - } - } - - return output -} +package azurerm + +import ( + "fmt" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" + "github.com/hashicorp/terraform/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceArmRouteTable() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmRouteTableRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + + "resource_group_name": resourceGroupNameForDataSourceSchema(), + + "location": locationForDataSourceSchema(), + + "route": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + + "address_prefix": { + Type: schema.TypeString, + Computed: true, + }, + + "next_hop_type": { + Type: schema.TypeString, + Computed: true, + }, + + "next_hop_in_ip_address": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "subnets": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "tags": tagsForDataSourceSchema(), + }, + } +} + +func dataSourceArmRouteTableRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).routeTablesClient + ctx := meta.(*ArmClient).StopContext + + name := d.Get("name").(string) + resourceGroup := d.Get("resource_group_name").(string) + + resp, err := client.Get(ctx, resourceGroup, name, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return fmt.Errorf("Error: Route Table %q (Resource Group %q) was not found", name, resourceGroup) + } + return fmt.Errorf("Error making Read request on Azure Route Table %q: %+v", name, err) + } + + d.SetId(*resp.ID) + + d.Set("name", name) + d.Set("resource_group_name", resourceGroup) + if location := resp.Location; location != nil { + d.Set("location", azureRMNormalizeLocation(*location)) + } + + if props := resp.RouteTablePropertiesFormat; props != nil { + if err := d.Set("route", flattenRouteTableDataSourceRoutes(props.Routes)); err != nil { + return err + } + + if err := d.Set("subnets", flattenRouteTableDataSourceSubnets(props.Subnets)); err != nil { + return err + } + } + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func flattenRouteTableDataSourceRoutes(input *[]network.Route) []interface{} { + results := make([]interface{}, 0) + + if routes := input; routes != nil { + for _, route := range *routes { + r := make(map[string]interface{}) + + r["name"] = *route.Name + + if props := route.RoutePropertiesFormat; props != nil { + r["address_prefix"] = *props.AddressPrefix + r["next_hop_type"] = string(props.NextHopType) + if ip := props.NextHopIPAddress; ip != nil { + r["next_hop_in_ip_address"] = *ip + } + } + + results = append(results, r) + } + } + + return results +} + +func flattenRouteTableDataSourceSubnets(subnets *[]network.Subnet) []string { + output := make([]string, 0) + + if subnets != nil { + for _, subnet := range *subnets { + output = append(output, *subnet.ID) + } + } + + return output +} diff --git a/azurerm/helpers/validate/strings.go b/azurerm/helpers/validate/strings.go index ff8a947a383d..2ba99ff14fd0 100644 --- a/azurerm/helpers/validate/strings.go +++ b/azurerm/helpers/validate/strings.go @@ -1,26 +1,26 @@ -package validate - -import ( - "fmt" - "strings" - - "github.com/hashicorp/terraform/helper/schema" -) - -// NoEmptyStrings validates that the string is not just whitespace characters (equal to [\r\n\t\f\v ]) -func NoEmptyStrings() schema.SchemaValidateFunc { - return func(i interface{}, k string) ([]string, []error) { - v, ok := i.(string) - if !ok { - return nil, []error{fmt.Errorf("expected type of %q to be string", k)} - } - - newv := strings.TrimSpace(v) - - if len(newv) == 0 { - return nil, []error{fmt.Errorf("%q must not be empty", k)} - } - - return nil, nil - } -} +package validate + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform/helper/schema" +) + +// NoEmptyStrings validates that the string is not just whitespace characters (equal to [\r\n\t\f\v ]) +func NoEmptyStrings() schema.SchemaValidateFunc { + return func(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + if !ok { + return nil, []error{fmt.Errorf("expected type of %q to be string", k)} + } + + newv := strings.TrimSpace(v) + + if len(newv) == 0 { + return nil, []error{fmt.Errorf("%q must not be empty", k)} + } + + return nil, nil + } +} diff --git a/azurerm/helpers/validate/strings_test.go b/azurerm/helpers/validate/strings_test.go index a65737c96eaa..23b50d8741fa 100644 --- a/azurerm/helpers/validate/strings_test.go +++ b/azurerm/helpers/validate/strings_test.go @@ -1,67 +1,67 @@ -package validate - -import "testing" - -func TestNoEmptyStrings(t *testing.T) { - cases := []struct { - Value string - TestName string - ErrCount int - }{ - { - Value: "!", - TestName: "Exclamation", - ErrCount: 0, - }, - { - Value: ".", - TestName: "Period", - ErrCount: 0, - }, - { - Value: "-", - TestName: "Hyphen", - ErrCount: 0, - }, - { - Value: "_", - TestName: "Underscore", - ErrCount: 0, - }, - { - Value: "10.1.0.0/16", - TestName: "IP", - ErrCount: 0, - }, - { - Value: "", - TestName: "Empty", - ErrCount: 1, - }, - { - Value: " ", - TestName: "Space", - ErrCount: 1, - }, - { - Value: " 1", - TestName: "DoubleSpaceOne", - ErrCount: 0, - }, - { - Value: "1 ", - TestName: "OneSpace", - ErrCount: 0, - }, - } - - for _, tc := range cases { - t.Run(tc.TestName, func(t *testing.T) { - _, errors := NoEmptyStrings()(tc.Value, tc.TestName) - - if len(errors) < tc.ErrCount { - t.Fatalf("Expected NoEmptyStrings to have %d not %d errors for %q", tc.ErrCount, len(errors), tc.TestName) - } - }) - } -} +package validate + +import "testing" + +func TestNoEmptyStrings(t *testing.T) { + cases := []struct { + Value string + TestName string + ErrCount int + }{ + { + Value: "!", + TestName: "Exclamation", + ErrCount: 0, + }, + { + Value: ".", + TestName: "Period", + ErrCount: 0, + }, + { + Value: "-", + TestName: "Hyphen", + ErrCount: 0, + }, + { + Value: "_", + TestName: "Underscore", + ErrCount: 0, + }, + { + Value: "10.1.0.0/16", + TestName: "IP", + ErrCount: 0, + }, + { + Value: "", + TestName: "Empty", + ErrCount: 1, + }, + { + Value: " ", + TestName: "Space", + ErrCount: 1, + }, + { + Value: " 1", + TestName: "DoubleSpaceOne", + ErrCount: 0, + }, + { + Value: "1 ", + TestName: "OneSpace", + ErrCount: 0, + }, + } + + for _, tc := range cases { + t.Run(tc.TestName, func(t *testing.T) { + _, errors := NoEmptyStrings()(tc.Value, tc.TestName) + + if len(errors) < tc.ErrCount { + t.Fatalf("Expected NoEmptyStrings to have %d not %d errors for %q", tc.ErrCount, len(errors), tc.TestName) + } + }) + } +} diff --git a/azurerm/resource_arm_route_table.go b/azurerm/resource_arm_route_table.go index 4c8ae4b8034c..98fa535ff3e3 100644 --- a/azurerm/resource_arm_route_table.go +++ b/azurerm/resource_arm_route_table.go @@ -1,268 +1,268 @@ -package azurerm - -import ( - "fmt" - "log" - - "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/helper/validation" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" - "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" -) - -var routeTableResourceName = "azurerm_route_table" - -func resourceArmRouteTable() *schema.Resource { - return &schema.Resource{ - Create: resourceArmRouteTableCreateUpdate, - Read: resourceArmRouteTableRead, - Update: resourceArmRouteTableCreateUpdate, - Delete: resourceArmRouteTableDelete, - - Importer: &schema.ResourceImporter{ - State: schema.ImportStatePassthrough, - }, - - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validate.NoEmptyStrings(), - }, - - "location": locationSchema(), - - "resource_group_name": resourceGroupNameSchema(), - - "route": { - Type: schema.TypeList, - Optional: true, - Computed: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validate.NoEmptyStrings(), - }, - - "address_prefix": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validate.NoEmptyStrings(), - }, - - "next_hop_type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - string(network.RouteNextHopTypeVirtualNetworkGateway), - string(network.RouteNextHopTypeVnetLocal), - string(network.RouteNextHopTypeInternet), - string(network.RouteNextHopTypeVirtualAppliance), - string(network.RouteNextHopTypeNone), - }, true), - DiffSuppressFunc: suppress.CaseDifference, - }, - - "next_hop_in_ip_address": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validate.NoEmptyStrings(), - }, - }, - }, - }, - - "disable_bgp_route_propagation": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "subnets": { - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - }, - - "tags": tagsSchema(), - }, - } -} - -func resourceArmRouteTableCreateUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient).routeTablesClient - ctx := meta.(*ArmClient).StopContext - - log.Printf("[INFO] preparing arguments for AzureRM Route Table creation.") - - name := d.Get("name").(string) - location := azureRMNormalizeLocation(d.Get("location").(string)) - resGroup := d.Get("resource_group_name").(string) - tags := d.Get("tags").(map[string]interface{}) - - routeSet := network.RouteTable{ - Name: &name, - Location: &location, - RouteTablePropertiesFormat: &network.RouteTablePropertiesFormat{ - Routes: expandRouteTableRoutes(d), - DisableBgpRoutePropagation: utils.Bool(d.Get("disable_bgp_route_propagation").(bool)), - }, - Tags: expandTags(tags), - } - - future, err := client.CreateOrUpdate(ctx, resGroup, name, routeSet) - if err != nil { - return fmt.Errorf("Error Creating/Updating Route Table %q (Resource Group %q): %+v", name, resGroup, err) - } - - if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Error waiting for completion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) - } - - read, err := client.Get(ctx, resGroup, name, "") - if err != nil { - return err - } - if read.ID == nil { - return fmt.Errorf("Cannot read Route Table %q (resource group %q) ID", name, resGroup) - } - - d.SetId(*read.ID) - - return resourceArmRouteTableRead(d, meta) -} - -func resourceArmRouteTableRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient).routeTablesClient - ctx := meta.(*ArmClient).StopContext - - id, err := parseAzureResourceID(d.Id()) - if err != nil { - return err - } - resGroup := id.ResourceGroup - name := id.Path["routeTables"] - - resp, err := client.Get(ctx, resGroup, name, "") - if err != nil { - if utils.ResponseWasNotFound(resp.Response) { - d.SetId("") - return nil - } - return fmt.Errorf("Error making Read request on Azure Route Table %q: %+v", name, err) - } - - d.Set("name", name) - d.Set("resource_group_name", resGroup) - if location := resp.Location; location != nil { - d.Set("location", azureRMNormalizeLocation(*location)) - } - - if props := resp.RouteTablePropertiesFormat; props != nil { - d.Set("disable_bgp_route_propagation", props.DisableBgpRoutePropagation) - if err := d.Set("route", flattenRouteTableRoutes(props.Routes)); err != nil { - return err - } - - if err := d.Set("subnets", flattenRouteTableSubnets(props.Subnets)); err != nil { - return err - } - } - - flattenAndSetTags(d, resp.Tags) - - return nil -} - -func resourceArmRouteTableDelete(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ArmClient).routeTablesClient - ctx := meta.(*ArmClient).StopContext - - id, err := parseAzureResourceID(d.Id()) - if err != nil { - return err - } - resGroup := id.ResourceGroup - name := id.Path["routeTables"] - - future, err := client.Delete(ctx, resGroup, name) - if err != nil { - if !response.WasNotFound(future.Response()) { - return fmt.Errorf("Error deleting Route Table %q (Resource Group %q): %+v", name, resGroup, err) - } - } - - if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { - return fmt.Errorf("Error waiting for deletion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) - } - - return nil -} - -func expandRouteTableRoutes(d *schema.ResourceData) *[]network.Route { - configs := d.Get("route").([]interface{}) - routes := make([]network.Route, 0, len(configs)) - - for _, configRaw := range configs { - data := configRaw.(map[string]interface{}) - - route := network.Route{ - Name: utils.String(data["name"].(string)), - RoutePropertiesFormat: &network.RoutePropertiesFormat{ - AddressPrefix: utils.String(data["address_prefix"].(string)), - NextHopType: network.RouteNextHopType(data["next_hop_type"].(string)), - }, - } - - if v := data["next_hop_in_ip_address"].(string); v != "" { - route.RoutePropertiesFormat.NextHopIPAddress = &v - } - - routes = append(routes, route) - } - - return &routes -} - -func flattenRouteTableRoutes(input *[]network.Route) []interface{} { - results := make([]interface{}, 0) - - if routes := input; routes != nil { - for _, route := range *routes { - r := make(map[string]interface{}) - - r["name"] = *route.Name - - if props := route.RoutePropertiesFormat; props != nil { - r["address_prefix"] = *props.AddressPrefix - r["next_hop_type"] = string(props.NextHopType) - if ip := props.NextHopIPAddress; ip != nil { - r["next_hop_in_ip_address"] = *ip - } - } - - results = append(results, r) - } - } - - return results -} - -func flattenRouteTableSubnets(subnets *[]network.Subnet) []string { - output := make([]string, 0) - - if subnets != nil { - for _, subnet := range *subnets { - output = append(output, *subnet.ID) - } - } - - return output -} +package azurerm + +import ( + "fmt" + "log" + + "github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-04-01/network" + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/response" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/suppress" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +var routeTableResourceName = "azurerm_route_table" + +func resourceArmRouteTable() *schema.Resource { + return &schema.Resource{ + Create: resourceArmRouteTableCreateUpdate, + Read: resourceArmRouteTableRead, + Update: resourceArmRouteTableCreateUpdate, + Delete: resourceArmRouteTableDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + + "location": locationSchema(), + + "resource_group_name": resourceGroupNameSchema(), + + "route": { + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + + "address_prefix": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + + "next_hop_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(network.RouteNextHopTypeVirtualNetworkGateway), + string(network.RouteNextHopTypeVnetLocal), + string(network.RouteNextHopTypeInternet), + string(network.RouteNextHopTypeVirtualAppliance), + string(network.RouteNextHopTypeNone), + }, true), + DiffSuppressFunc: suppress.CaseDifference, + }, + + "next_hop_in_ip_address": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.NoEmptyStrings(), + }, + }, + }, + }, + + "disable_bgp_route_propagation": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + + "subnets": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + + "tags": tagsSchema(), + }, + } +} + +func resourceArmRouteTableCreateUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).routeTablesClient + ctx := meta.(*ArmClient).StopContext + + log.Printf("[INFO] preparing arguments for AzureRM Route Table creation.") + + name := d.Get("name").(string) + location := azureRMNormalizeLocation(d.Get("location").(string)) + resGroup := d.Get("resource_group_name").(string) + tags := d.Get("tags").(map[string]interface{}) + + routeSet := network.RouteTable{ + Name: &name, + Location: &location, + RouteTablePropertiesFormat: &network.RouteTablePropertiesFormat{ + Routes: expandRouteTableRoutes(d), + DisableBgpRoutePropagation: utils.Bool(d.Get("disable_bgp_route_propagation").(bool)), + }, + Tags: expandTags(tags), + } + + future, err := client.CreateOrUpdate(ctx, resGroup, name, routeSet) + if err != nil { + return fmt.Errorf("Error Creating/Updating Route Table %q (Resource Group %q): %+v", name, resGroup, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for completion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) + } + + read, err := client.Get(ctx, resGroup, name, "") + if err != nil { + return err + } + if read.ID == nil { + return fmt.Errorf("Cannot read Route Table %q (resource group %q) ID", name, resGroup) + } + + d.SetId(*read.ID) + + return resourceArmRouteTableRead(d, meta) +} + +func resourceArmRouteTableRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).routeTablesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + name := id.Path["routeTables"] + + resp, err := client.Get(ctx, resGroup, name, "") + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("Error making Read request on Azure Route Table %q: %+v", name, err) + } + + d.Set("name", name) + d.Set("resource_group_name", resGroup) + if location := resp.Location; location != nil { + d.Set("location", azureRMNormalizeLocation(*location)) + } + + if props := resp.RouteTablePropertiesFormat; props != nil { + d.Set("disable_bgp_route_propagation", props.DisableBgpRoutePropagation) + if err := d.Set("route", flattenRouteTableRoutes(props.Routes)); err != nil { + return err + } + + if err := d.Set("subnets", flattenRouteTableSubnets(props.Subnets)); err != nil { + return err + } + } + + flattenAndSetTags(d, resp.Tags) + + return nil +} + +func resourceArmRouteTableDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ArmClient).routeTablesClient + ctx := meta.(*ArmClient).StopContext + + id, err := parseAzureResourceID(d.Id()) + if err != nil { + return err + } + resGroup := id.ResourceGroup + name := id.Path["routeTables"] + + future, err := client.Delete(ctx, resGroup, name) + if err != nil { + if !response.WasNotFound(future.Response()) { + return fmt.Errorf("Error deleting Route Table %q (Resource Group %q): %+v", name, resGroup, err) + } + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("Error waiting for deletion of Route Table %q (Resource Group %q): %+v", name, resGroup, err) + } + + return nil +} + +func expandRouteTableRoutes(d *schema.ResourceData) *[]network.Route { + configs := d.Get("route").([]interface{}) + routes := make([]network.Route, 0, len(configs)) + + for _, configRaw := range configs { + data := configRaw.(map[string]interface{}) + + route := network.Route{ + Name: utils.String(data["name"].(string)), + RoutePropertiesFormat: &network.RoutePropertiesFormat{ + AddressPrefix: utils.String(data["address_prefix"].(string)), + NextHopType: network.RouteNextHopType(data["next_hop_type"].(string)), + }, + } + + if v := data["next_hop_in_ip_address"].(string); v != "" { + route.RoutePropertiesFormat.NextHopIPAddress = &v + } + + routes = append(routes, route) + } + + return &routes +} + +func flattenRouteTableRoutes(input *[]network.Route) []interface{} { + results := make([]interface{}, 0) + + if routes := input; routes != nil { + for _, route := range *routes { + r := make(map[string]interface{}) + + r["name"] = *route.Name + + if props := route.RoutePropertiesFormat; props != nil { + r["address_prefix"] = *props.AddressPrefix + r["next_hop_type"] = string(props.NextHopType) + if ip := props.NextHopIPAddress; ip != nil { + r["next_hop_in_ip_address"] = *ip + } + } + + results = append(results, r) + } + } + + return results +} + +func flattenRouteTableSubnets(subnets *[]network.Subnet) []string { + output := make([]string, 0) + + if subnets != nil { + for _, subnet := range *subnets { + output = append(output, *subnet.ID) + } + } + + return output +} From a61728aa475ce359b4c69f4aff95306f3b7ed55c Mon Sep 17 00:00:00 2001 From: Jeffrey Cline Date: Thu, 6 Dec 2018 19:18:58 -0800 Subject: [PATCH 05/13] Fix lint error --- azurerm/resource_arm_route_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_route_table.go b/azurerm/resource_arm_route_table.go index 98fa535ff3e3..ed9e624f58e8 100644 --- a/azurerm/resource_arm_route_table.go +++ b/azurerm/resource_arm_route_table.go @@ -111,7 +111,7 @@ func resourceArmRouteTableCreateUpdate(d *schema.ResourceData, meta interface{}) Name: &name, Location: &location, RouteTablePropertiesFormat: &network.RouteTablePropertiesFormat{ - Routes: expandRouteTableRoutes(d), + Routes: expandRouteTableRoutes(d), DisableBgpRoutePropagation: utils.Bool(d.Get("disable_bgp_route_propagation").(bool)), }, Tags: expandTags(tags), From 1b2ebcbf3d25ce89bed3a67678c7c6e0c1247fe9 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 6 Dec 2018 20:28:55 -0800 Subject: [PATCH 06/13] Update azurerm/data_source_route_table.go Co-Authored-By: jeffreyCline --- azurerm/data_source_route_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/data_source_route_table.go b/azurerm/data_source_route_table.go index 467f0eaa587f..bd649cbead78 100644 --- a/azurerm/data_source_route_table.go +++ b/azurerm/data_source_route_table.go @@ -17,7 +17,7 @@ func dataSourceArmRouteTable() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - ValidateFunc: validate.NoEmptyStrings(), + ValidateFunc: validate.NoEmptyStrings, }, "resource_group_name": resourceGroupNameForDataSourceSchema(), From 42384069dbadc681ddbc618b2dd88b263d216e41 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 6 Dec 2018 20:29:03 -0800 Subject: [PATCH 07/13] Update azurerm/resource_arm_route_table.go Co-Authored-By: jeffreyCline --- azurerm/resource_arm_route_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_route_table.go b/azurerm/resource_arm_route_table.go index ed9e624f58e8..e5421e6b74bc 100644 --- a/azurerm/resource_arm_route_table.go +++ b/azurerm/resource_arm_route_table.go @@ -31,7 +31,7 @@ func resourceArmRouteTable() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: validate.NoEmptyStrings(), + ValidateFunc: validate.NoEmptyStrings, }, "location": locationSchema(), From a87333a507139fc34e89ad6a1860e12a72065d98 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 6 Dec 2018 20:29:10 -0800 Subject: [PATCH 08/13] Update azurerm/resource_arm_route_table.go Co-Authored-By: jeffreyCline --- azurerm/resource_arm_route_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_route_table.go b/azurerm/resource_arm_route_table.go index e5421e6b74bc..ccf2d0859461 100644 --- a/azurerm/resource_arm_route_table.go +++ b/azurerm/resource_arm_route_table.go @@ -53,7 +53,7 @@ func resourceArmRouteTable() *schema.Resource { "address_prefix": { Type: schema.TypeString, Required: true, - ValidateFunc: validate.NoEmptyStrings(), + ValidateFunc: validate.NoEmptyStrings, }, "next_hop_type": { From b0ffcfb27fd8a2c8d57f1a9d916626bb0e70ddc9 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 6 Dec 2018 20:29:16 -0800 Subject: [PATCH 09/13] Update azurerm/resource_arm_route_table.go Co-Authored-By: jeffreyCline --- azurerm/resource_arm_route_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_route_table.go b/azurerm/resource_arm_route_table.go index ccf2d0859461..e94f4111dd10 100644 --- a/azurerm/resource_arm_route_table.go +++ b/azurerm/resource_arm_route_table.go @@ -72,7 +72,7 @@ func resourceArmRouteTable() *schema.Resource { "next_hop_in_ip_address": { Type: schema.TypeString, Optional: true, - ValidateFunc: validate.NoEmptyStrings(), + ValidateFunc: validate.NoEmptyStrings, }, }, }, From 811418a9f53ed84b406ac06c037bc5583680bcb8 Mon Sep 17 00:00:00 2001 From: kt Date: Thu, 6 Dec 2018 20:29:35 -0800 Subject: [PATCH 10/13] Update azurerm/resource_arm_route_table.go Co-Authored-By: jeffreyCline --- azurerm/resource_arm_route_table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azurerm/resource_arm_route_table.go b/azurerm/resource_arm_route_table.go index e94f4111dd10..6a3455dcf593 100644 --- a/azurerm/resource_arm_route_table.go +++ b/azurerm/resource_arm_route_table.go @@ -47,7 +47,7 @@ func resourceArmRouteTable() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - ValidateFunc: validate.NoEmptyStrings(), + ValidateFunc: validate.NoEmptyStrings, }, "address_prefix": { From 9094debf2b7904fbf5e0140aef5c0afbd3205498 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline Date: Fri, 7 Dec 2018 11:48:22 -0800 Subject: [PATCH 11/13] Update signature --- azurerm/helpers/validate/strings.go | 24 +++++++++--------------- azurerm/helpers/validate/strings_test.go | 2 +- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/azurerm/helpers/validate/strings.go b/azurerm/helpers/validate/strings.go index 2ba99ff14fd0..1dec1bd2e07f 100644 --- a/azurerm/helpers/validate/strings.go +++ b/azurerm/helpers/validate/strings.go @@ -3,24 +3,18 @@ package validate import ( "fmt" "strings" - - "github.com/hashicorp/terraform/helper/schema" ) // NoEmptyStrings validates that the string is not just whitespace characters (equal to [\r\n\t\f\v ]) -func NoEmptyStrings() schema.SchemaValidateFunc { - return func(i interface{}, k string) ([]string, []error) { - v, ok := i.(string) - if !ok { - return nil, []error{fmt.Errorf("expected type of %q to be string", k)} - } - - newv := strings.TrimSpace(v) - - if len(newv) == 0 { - return nil, []error{fmt.Errorf("%q must not be empty", k)} - } +func NoEmptyStrings(i interface{}, k string) ([]string, []error) { + v, ok := i.(string) + if !ok { + return nil, []error{fmt.Errorf("expected type of %q to be string", k)} + } - return nil, nil + if strings.TrimSpace(v) == "" { + return nil, []error{fmt.Errorf("%q must not be empty", k)} } + + return nil, nil } diff --git a/azurerm/helpers/validate/strings_test.go b/azurerm/helpers/validate/strings_test.go index 23b50d8741fa..039c94dd02d5 100644 --- a/azurerm/helpers/validate/strings_test.go +++ b/azurerm/helpers/validate/strings_test.go @@ -57,7 +57,7 @@ func TestNoEmptyStrings(t *testing.T) { for _, tc := range cases { t.Run(tc.TestName, func(t *testing.T) { - _, errors := NoEmptyStrings()(tc.Value, tc.TestName) + _, errors := NoEmptyStrings(tc.Value, tc.TestName) if len(errors) < tc.ErrCount { t.Fatalf("Expected NoEmptyStrings to have %d not %d errors for %q", tc.ErrCount, len(errors), tc.TestName) From f6bca951f5c38e8c4d1a9e23176c5d2ca3bd1e23 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline Date: Fri, 7 Dec 2018 11:50:31 -0800 Subject: [PATCH 12/13] Added test case --- azurerm/helpers/validate/strings_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/azurerm/helpers/validate/strings_test.go b/azurerm/helpers/validate/strings_test.go index 039c94dd02d5..8ff6b26b821c 100644 --- a/azurerm/helpers/validate/strings_test.go +++ b/azurerm/helpers/validate/strings_test.go @@ -43,6 +43,11 @@ func TestNoEmptyStrings(t *testing.T) { TestName: "Space", ErrCount: 1, }, + { + Value: " ", + TestName: "FiveSpaces", + ErrCount: 1, + }, { Value: " 1", TestName: "DoubleSpaceOne", From 6fda6af41a91fac05337d57f9cca34688aec3df4 Mon Sep 17 00:00:00 2001 From: Jeffrey Cline Date: Fri, 7 Dec 2018 13:00:45 -0800 Subject: [PATCH 13/13] Added special character test cases --- azurerm/helpers/validate/strings_test.go | 31 ++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/azurerm/helpers/validate/strings_test.go b/azurerm/helpers/validate/strings_test.go index 8ff6b26b821c..8d00a222bc60 100644 --- a/azurerm/helpers/validate/strings_test.go +++ b/azurerm/helpers/validate/strings_test.go @@ -1,6 +1,8 @@ package validate -import "testing" +import ( + "testing" +) func TestNoEmptyStrings(t *testing.T) { cases := []struct { @@ -58,13 +60,38 @@ func TestNoEmptyStrings(t *testing.T) { TestName: "OneSpace", ErrCount: 0, }, + { + Value: "\r", + TestName: "CarriageReturn", + ErrCount: 1, + }, + { + Value: "\n", + TestName: "NewLine", + ErrCount: 1, + }, + { + Value: "\t", + TestName: "HorizontalTab", + ErrCount: 1, + }, + { + Value: "\f", + TestName: "FormFeed", + ErrCount: 1, + }, + { + Value: "\v", + TestName: "VerticalTab", + ErrCount: 1, + }, } for _, tc := range cases { t.Run(tc.TestName, func(t *testing.T) { _, errors := NoEmptyStrings(tc.Value, tc.TestName) - if len(errors) < tc.ErrCount { + if len(errors) != tc.ErrCount { t.Fatalf("Expected NoEmptyStrings to have %d not %d errors for %q", tc.ErrCount, len(errors), tc.TestName) } })