Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Data Source: azurerm_express_route_circuit #3158

Merged
merged 8 commits into from
Apr 16, 2019
204 changes: 204 additions & 0 deletions azurerm/data_source_express_route_circuit.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package azurerm

import (
"fmt"

"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-08-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 dataSourceArmExpressRouteCircuit() *schema.Resource {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return &schema.Resource{
Read: dataSourceArmExpressRouteCircuitRead,
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validate.NoEmptyStrings,
},

"resource_group_name": resourceGroupNameForDataSourceSchema(),

"peerings": {
Type: schema.TypeList,
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"peering_type": {
Type: schema.TypeString,
Computed: true,
},
"azure_asn": {
Type: schema.TypeInt,
Computed: true,
},
"peer_asn": {
Type: schema.TypeInt,
Computed: true,
},
"vlan_id": {
Type: schema.TypeInt,
Computed: true,
},
"shared_key": {
Type: schema.TypeString,
Computed: true,
},
},
},
},

"service_key": {
Type: schema.TypeString,
Computed: true,
},

"service_provider_properties": {
Type: schema.TypeSet,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

due to a bug in Terraform Core this'll need to be a TypeList unfortunately - as such can we make this:

Suggested change
Type: schema.TypeSet,
Type: schema.TypeList,

Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"service_provider_name": {
Type: schema.TypeString,
Computed: true,
},
"peering_location": {
Type: schema.TypeString,
Computed: true,
},
"bandwidth_in_mbps": {
Type: schema.TypeInt,
Computed: true,
},
},
},
},

"service_provider_provisioning_state": {
Type: schema.TypeString,
Computed: true,
},

"sku": {
Type: schema.TypeSet,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(as above) since this is a Data Source there's a known bug with Sets, so can we update this to be a List:

Suggested change
Type: schema.TypeSet,
Type: schema.TypeList,

Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"tier": {
Type: schema.TypeString,
Computed: true,
},

"family": {
Type: schema.TypeString,
Computed: true,
},
},
},
},
},
}
}

func dataSourceArmExpressRouteCircuitRead(d *schema.ResourceData, meta interface{}) error {
ctx := meta.(*ArmClient).StopContext
client := meta.(*ArmClient).expressRouteCircuitClient

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: Express Route Circuit %q (Resource Group %q) was not found", name, resourceGroup)
}
return fmt.Errorf("Error making Read request on the Express Route Circuit %q (Resource Group %q): %+v", name, resourceGroup, err)
}

d.SetId(*resp.ID)

if properties := resp.ExpressRouteCircuitPropertiesFormat; properties != nil {
peerings := flattenExpressRouteCircuitPeerings(properties.Peerings)
if err := d.Set("peerings", peerings); err != nil {
return err
}

d.Set("service_key", properties.ServiceKey)
d.Set("service_provider_provisioning_state", properties.ServiceProviderProvisioningState)

if serviceProviderProperties := flattenExpressRouteCircuitServiceProviderProperties(properties.ServiceProviderProperties); serviceProviderProperties != nil {
if err := d.Set("service_provider_properties", serviceProviderProperties); err != nil {
return fmt.Errorf("Error setting `service_provider_properties`: %+v", err)
}
}

}

if resp.Sku != nil {
sku := flattenExpressRouteCircuitSku(resp.Sku)
if err := d.Set("sku", sku); err != nil {
return fmt.Errorf("Error setting `sku`: %+v", err)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(since this is the same in the other resource, it's fine to leave this as-is for now) in general we'd tend to ensure the sku block was always set, for example by making this:

sku := flattenExpressRouteCircuitSku(resp.Sku)
if err := d.Set("sku", sku); err != nil {
  return fmt.Errorf("Error setting `sku`: %+v", err)
}

(e.g. by removing the surrounding if)

and then updating the flattenExpressRouteCircuitSku method to be:

func flattenExpressRouteCircuitSku(sku *network.ExpressRouteCircuitSku) []interface{} {
	if sku == nil {
	  return []interface{}{}
	}

	return []interface{}{
		map[string]interface{}{
			"tier":   string(sku.Tier),
			"family": string(sku.Family),
		},
	}
}

(but as mentioned above, since this is the same in the resource this is fine for now, just for info 😄)


return nil
}

func flattenExpressRouteCircuitPeerings(input *[]network.ExpressRouteCircuitPeering) []interface{} {
peerings := make([]interface{}, 0)

if input != nil {
for _, peering := range *input {
props := peering.ExpressRouteCircuitPeeringPropertiesFormat
p := make(map[string]interface{})

p["peering_type"] = string(props.PeeringType)

if azureAsn := props.AzureASN; azureAsn != nil {
p["azure_asn"] = *azureAsn
}

if peerAsn := props.PeerASN; peerAsn != nil {
p["peer_asn"] = *peerAsn
}

if vlanID := props.VlanID; vlanID != nil {
p["vlan_id"] = *vlanID
}

if sharedKey := props.SharedKey; sharedKey != nil {
p["shared_key"] = *sharedKey
}

peerings = append(peerings, p)
}
}

return peerings
}

func flattenExpressRouteCircuitServiceProviderProperties(input *network.ExpressRouteCircuitServiceProviderProperties) []interface{} {
serviceProviderProperties := make([]interface{}, 0)

if input != nil {
p := make(map[string]interface{})

if serviceProviderName := input.ServiceProviderName; serviceProviderName != nil {
p["service_provider_name"] = *serviceProviderName
}

if peeringLocation := input.PeeringLocation; peeringLocation != nil {
p["peering_location"] = *peeringLocation
}

if bandwidthInMbps := input.BandwidthInMbps; bandwidthInMbps != nil {
p["bandwidth_in_mbps"] = *bandwidthInMbps
}

serviceProviderProperties = append(serviceProviderProperties, p)
}

return serviceProviderProperties
}
83 changes: 83 additions & 0 deletions azurerm/data_source_express_route_circuit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package azurerm

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
)

func TestAccDataSourceAzureRMExpressRoute_basic(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since there's a limit on the number of ExpressRoute's which can be provisioned at one time (and we run the tests in parallel) - we'll need to make this an internal method (by making it lower-case) and then ensure it's called from within this test function in the Express Route resource

(but to make this simpler to test, we probably want to do this when all the other changes are done 😄)

Copy link
Contributor Author

@romitgirdhar romitgirdhar Apr 9, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it only has the 1 test in there, do we need the function? Also, should we add this function to the test function (in the resource file) or should we create a similar function in this test file?

dataSourceName := "data.azurerm_express_route_circuit.test"
ri := tf.AccRandTimeInt()
location := testLocation()

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMExpressRouteCircuitDestroy,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAzureRMExpressRoute_basic(ri, location),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceName, "service_provider_properties.0.service_provider_name", "Equinix Test"),
resource.TestCheckResourceAttr(dataSourceName, "service_provider_properties.0.peering_location", "Silicon Valley Test"),
resource.TestCheckResourceAttr(dataSourceName, "service_provider_properties.0.bandwidth_in_mbps", "50"),
resource.TestCheckResourceAttr(dataSourceName, "sku.0.tier", "Standard"),
resource.TestCheckResourceAttr(dataSourceName, "sku.0.family", "MeteredData"),
resource.TestCheckResourceAttr(dataSourceName, "service_provider_provisioning_state", "NotProvisioned"),
),
},
},
})
}

/*func TestAccDataSourceAzureRMExpressRoute_complete(t *testing.T) {
dataSourceName := "data.azurerm_express_route_circuit.test"
ri := tf.AccRandTimeInt()

}*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since this is unused, can we remove this?


func testAccDataSourceAzureRMExpressRoute_basic(rInt int, location string) string {
config := testAccAzureRMExpressRouteCircuit_MeteredBasic(rInt, location)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I meant to do. But, the subscription I used to test did not support the Express Route configuration that is hard-coded in the test you referenced. But, I checked another subscription and it looks like it does support those configurations. I'll make the change. thanks!


return fmt.Sprintf(`
%s

data "azurerm_express_route_circuit" test {
resource_group_name = "${azurerm_resource_group.test.name}"
name = "${azurerm_express_route_circuit.test.name}"
}
`, config)
}

func testAccAzureRMExpressRouteCircuit_MeteredBasic(rInt int, location string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "test" {
name = "acctestRG-%d"
location = "%s"
}

resource "azurerm_express_route_circuit" "test" {
name = "acctest-erc-%d"
location = "${azurerm_resource_group.test.location}"
resource_group_name = "${azurerm_resource_group.test.name}"
service_provider_name = "Equinix Test"
peering_location = "Silicon Valley Test"
bandwidth_in_mbps = 50

sku {
tier = "Standard"
family = "MeteredData"
}

allow_classic_operations = false

tags = {
Environment = "production"
Purpose = "AcceptanceTests"
}
}
`, rInt, location, rInt)
}
1 change: 1 addition & 0 deletions azurerm/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ func Provider() terraform.ResourceProvider {
"azurerm_dev_test_lab": dataSourceArmDevTestLab(),
"azurerm_dns_zone": dataSourceArmDnsZone(),
"azurerm_eventhub_namespace": dataSourceEventHubNamespace(),
"azurerm_express_route_circuit": dataSourceArmExpressRouteCircuit(),
"azurerm_image": dataSourceArmImage(),
"azurerm_key_vault_access_policy": dataSourceArmKeyVaultAccessPolicy(),
"azurerm_key_vault_key": dataSourceArmKeyVaultKey(),
Expand Down