diff --git a/docs/data-sources/fabric_metro.md b/docs/data-sources/fabric_metro.md new file mode 100644 index 000000000..47e45ca30 --- /dev/null +++ b/docs/data-sources/fabric_metro.md @@ -0,0 +1,95 @@ +--- +subcategory: "Fabric" +--- + +# equinix_fabric_metro (Data Source) + + + +## Example Usage + +```terraform +data "equinix_fabric_metro" "metro" { + metro_code = "" +} + +output "id" { + value = data.equinix_fabric_metro.metro.id +} + +output "type" { + value = data.equinix_fabric_metro.metro.type +} + +output "metro_code" { + value = data.equinix_fabric_metro.metro.metro_code +} + +output "region" { + value = data.equinix_fabric_metro.metro.region +} + +output "name" { + value = data.equinix_fabric_metro.metro.name +} + +output "equinix_asn" { + value = data.equinix_fabric_metro.metro.equinix_asn +} + +output "local_vc_bandwidth_max" { + value = data.equinix_fabric_metro.metro.local_vc_bandwidth_max +} + +output "geo_coordinates" { + value = data.equinix_fabric_metro.metro.geo_coordinates +} + +output "connected_metros" { + value = data.equinix_fabric_metro.metro.connected_metros +} + +output "geoScopes" { + value = data.equinix_fabric_metro.metro.geo_scopes +} +``` + + +## Schema + +### Required + +- `metro_code` (String) The metro code this data source should retrieve + +### Read-Only + +- `code` (String) Code assigned to an Equinix IBX data center in a specified metropolitan area +- `connected_metros` (List of Object) Arrays of objects containing latency data for the specified metro (see [below for nested schema](#nestedatt--connected_metros)) +- `equinix_asn` (Number) Autonomous system number (ASN) for a specified Fabric metro. The ASN is a unique identifier that carries the network routing protocol and exchanges that data with other internal systems via border gateway protocol. +- `geo_coordinates` (Attributes) Geographic location data of Fabric Metro (see [below for nested schema](#nestedatt--geo_coordinates)) +- `geo_scopes` (List of String) List of supported geographic boundaries of a Fabric Metro. Example values: CANADA, CONUS. +- `href` (String) The canonical URL at which the resource resides +- `id` (String) The unique identifier of the resource +- `local_vc_bandwidth_max` (Number) This field holds Max Connection speed within the metro. +- `name` (String) Name of the region in which the data center is located +- `region` (String) Board geographical area in which the data center is located +- `type` (String) Indicator of a fabric metro + + +### Nested Schema for `connected_metros` + +Read-Only: + +- `avg_latency` (Number) +- `code` (String) +- `href` (String) +- `remote_vc_bandwidth_max` (Number) + + + +### Nested Schema for `geo_coordinates` + +Read-Only: + +- `latitude` (Number) Latitude of the Metro +- `longitude` (Number) Longitude of the Metro diff --git a/docs/data-sources/fabric_metros.md b/docs/data-sources/fabric_metros.md new file mode 100644 index 000000000..578df7206 --- /dev/null +++ b/docs/data-sources/fabric_metros.md @@ -0,0 +1,128 @@ +--- +subcategory: "Fabric" +--- + +# equinix_fabric_metros (Data Source) + + + +## Example Usage + +```terraform +data "equinix_fabric_metros" "metros" { + pagination = { + limit = 12, + offset = 6 + } +} + +output "number_of_returned_metros" { + value = length(data.equinix_fabric_metros.metros.data) +} + +output "first_metro_id" { + value = data.equinix_fabric_metros.metros.data.0.id +} + +output "first_metro_type" { + value = data.equinix_fabric_metros.metros.data.0.type +} + +output "first_metro_code" { + value = data.equinix_fabric_metros.metros.data.0.code +} + +output "first_metro_region" { + value = data.equinix_fabric_metros.metros.data.0.region +} + +output "first_metro_name" { + value = data.equinix_fabric_metros.metros.data.0.name +} + +output "first_metro_equinix_asn" { + value = data.equinix_fabric_metros.metros.data.0.equinix_asn +} + +output "first_metro_local_vc_bandwidth_max" { + value = data.equinix_fabric_metros.metros.data.0.local_vc_bandwidth_max +} + +output "first_metro_geo_coordinates" { + value = data.equinix_fabric_metros.metros.data.0.geo_coordinates +} + +output "first_metro_connected_metros" { + value = data.equinix_fabric_metros.metros.data.0.connected_metros +} + +output "first_metro_geo_scopes" { + value = data.equinix_fabric_metros.metros.data.0.geo_scopes +} +``` + + +## Schema + +### Required + +- `pagination` (Attributes) Pagination details for the returned metro list (see [below for nested schema](#nestedatt--pagination)) + +### Optional + +- `presence` (String) User On Boarded Metros based on Fabric resource availability + +### Read-Only + +- `data` (Attributes List) Returned list of metro objects (see [below for nested schema](#nestedatt--data)) +- `id` (String) The unique identifier of the resource + + +### Nested Schema for `pagination` + +Optional: + +- `limit` (Number) Maximum number of search results returned per page. +- `offset` (Number) Index of the first item returned in the response. + +Read-Only: + +- `next` (String) URL relative to the next item in the response. +- `previous` (String) URL relative to the previous item in the response. +- `total` (Number) The total number of metro returned + + + +### Nested Schema for `data` + +Read-Only: + +- `code` (String) Code assigned to an Equinix IBX data center in a specified metropolitan area +- `connected_metros` (List of Object) Arrays of objects containing latency data for the specified metro (see [below for nested schema](#nestedatt--data--connected_metros)) +- `equinix_asn` (Number) Autonomous system number (ASN) for a specified Fabric metro. The ASN is a unique identifier that carries the network routing protocol and exchanges that data with other internal systems via border gateway protocol. +- `geo_coordinates` (Attributes) Geographic location data of Fabric Metro (see [below for nested schema](#nestedatt--data--geo_coordinates)) +- `geo_scopes` (List of String) List of supported geographic boundaries of a Fabric Metro. Example values: CANADA, CONUS. +- `href` (String) The canonical URL at which the resource resides +- `local_vc_bandwidth_max` (Number) This field holds Max Connection speed within the metro. +- `name` (String) Name of the region in which the data center is located +- `region` (String) Board geographical area in which the data center is located +- `type` (String) Indicator of a fabric metro + + +### Nested Schema for `data.connected_metros` + +Read-Only: + +- `avg_latency` (Number) +- `code` (String) +- `href` (String) +- `remote_vc_bandwidth_max` (Number) + + + +### Nested Schema for `data.geo_coordinates` + +Read-Only: + +- `latitude` (Number) Latitude of the Metro +- `longitude` (Number) Longitude of the Metro diff --git a/examples/data-sources/equinix_fabric_metro/data-source.tf b/examples/data-sources/equinix_fabric_metro/data-source.tf new file mode 100644 index 000000000..ff8e66573 --- /dev/null +++ b/examples/data-sources/equinix_fabric_metro/data-source.tf @@ -0,0 +1,44 @@ +data "equinix_fabric_metro" "metro" { + metro_code = "" +} + +output "id" { + value = data.equinix_fabric_metro.metro.id +} + +output "type" { + value = data.equinix_fabric_metro.metro.type +} + +output "metro_code" { + value = data.equinix_fabric_metro.metro.metro_code +} + +output "region" { + value = data.equinix_fabric_metro.metro.region +} + +output "name" { + value = data.equinix_fabric_metro.metro.name +} + +output "equinix_asn" { + value = data.equinix_fabric_metro.metro.equinix_asn +} + +output "local_vc_bandwidth_max" { + value = data.equinix_fabric_metro.metro.local_vc_bandwidth_max +} + +output "geo_coordinates" { + value = data.equinix_fabric_metro.metro.geo_coordinates +} + +output "connected_metros" { + value = data.equinix_fabric_metro.metro.connected_metros +} + +output "geoScopes" { + value = data.equinix_fabric_metro.metro.geo_scopes +} + diff --git a/examples/data-sources/equinix_fabric_metros/data-source.tf b/examples/data-sources/equinix_fabric_metros/data-source.tf new file mode 100644 index 000000000..c83bb35c2 --- /dev/null +++ b/examples/data-sources/equinix_fabric_metros/data-source.tf @@ -0,0 +1,51 @@ +data "equinix_fabric_metros" "metros" { + pagination = { + limit = 12, + offset = 6 + } +} + +output "number_of_returned_metros" { + value = length(data.equinix_fabric_metros.metros.data) +} + +output "first_metro_id" { + value = data.equinix_fabric_metros.metros.data.0.id +} + +output "first_metro_type" { + value = data.equinix_fabric_metros.metros.data.0.type +} + +output "first_metro_code" { + value = data.equinix_fabric_metros.metros.data.0.code +} + +output "first_metro_region" { + value = data.equinix_fabric_metros.metros.data.0.region +} + +output "first_metro_name" { + value = data.equinix_fabric_metros.metros.data.0.name +} + +output "first_metro_equinix_asn" { + value = data.equinix_fabric_metros.metros.data.0.equinix_asn +} + +output "first_metro_local_vc_bandwidth_max" { + value = data.equinix_fabric_metros.metros.data.0.local_vc_bandwidth_max +} + +output "first_metro_geo_coordinates" { + value = data.equinix_fabric_metros.metros.data.0.geo_coordinates +} + +output "first_metro_connected_metros" { + value = data.equinix_fabric_metros.metros.data.0.connected_metros +} + +output "first_metro_geo_scopes" { + value = data.equinix_fabric_metros.metros.data.0.geo_scopes +} + diff --git a/internal/provider/services/fabric.go b/internal/provider/services/fabric.go index d89eadf6c..9df1dc4b6 100644 --- a/internal/provider/services/fabric.go +++ b/internal/provider/services/fabric.go @@ -1,6 +1,7 @@ package services import ( + "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/metro" "github.com/equinix/terraform-provider-equinix/internal/resources/fabric/stream" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -14,6 +15,8 @@ func FabricResources() []func() resource.Resource { func FabricDatasources() []func() datasource.DataSource { return []func() datasource.DataSource{ + metro.NewDataSourceMetroCode, + metro.NewDataSourceMetros, stream.NewDataSourceByStreamID, stream.NewDataSourceAllStreams, } diff --git a/internal/resources/fabric/metro/datasources_metro_code.go b/internal/resources/fabric/metro/datasources_metro_code.go new file mode 100644 index 000000000..394539c39 --- /dev/null +++ b/internal/resources/fabric/metro/datasources_metro_code.go @@ -0,0 +1,56 @@ +package metro + +import ( + "context" + + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +func NewDataSourceMetroCode() datasource.DataSource { + return &DataSourceMetroCode{ + BaseDataSource: framework.NewBaseDataSource( + framework.BaseDataSourceConfig{ + Name: "equinix_fabric_metro", + }, + ), + } +} + +type DataSourceMetroCode struct { + framework.BaseDataSource +} + +func (r *DataSourceMetroCode) Schema( + ctx context.Context, + req datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = dataSourceSingleMetroSchema(ctx) +} + +func (r *DataSourceMetroCode) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) + + var data DataSourceByCodeModel + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + metroCode := data.MetroCode.ValueString() + metroByCode, _, err := client.MetrosApi.GetMetroByCode(ctx, metroCode).Execute() + if err != nil { + response.State.RemoveResource(ctx) + response.Diagnostics.AddError("Get By Metro Code API Error", equinix_errors.FormatFabricError(err).Error()) + return + } + + response.Diagnostics.Append(data.parse(ctx, metroByCode)...) + if response.Diagnostics.HasError() { + return + } + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} diff --git a/internal/resources/fabric/metro/datasources_metros.go b/internal/resources/fabric/metro/datasources_metros.go new file mode 100644 index 000000000..1cb1c6b43 --- /dev/null +++ b/internal/resources/fabric/metro/datasources_metros.go @@ -0,0 +1,75 @@ +package metro + +import ( + "context" + + "github.com/equinix/equinix-sdk-go/services/fabricv4" + equinix_errors "github.com/equinix/terraform-provider-equinix/internal/errors" + "github.com/equinix/terraform-provider-equinix/internal/framework" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +func NewDataSourceMetros() datasource.DataSource { + return &DataSourceMetros{ + BaseDataSource: framework.NewBaseDataSource( + framework.BaseDataSourceConfig{ + Name: "equinix_fabric_metros", + }, + ), + } +} + +type DataSourceMetros struct { + framework.BaseDataSource +} + +func (r *DataSourceMetros) Schema( + ctx context.Context, + req datasource.SchemaRequest, + resp *datasource.SchemaResponse, +) { + resp.Schema = dataSourceAllMetroSchema(ctx) +} + +func (r *DataSourceMetros) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + client := r.Meta.NewFabricClientForFramework(ctx, request.ProviderMeta) + + var allMetrosData DataSourceAllMetrosModel + var pagination PaginationModel + response.Diagnostics.Append(request.Config.Get(ctx, &allMetrosData)...) + if response.Diagnostics.HasError() { + return + } + + diags := allMetrosData.Pagination.As(ctx, &pagination, basetypes.ObjectAsOptions{}) + if diags.HasError() { + return + } + offset := pagination.Offset.ValueInt32() + limit := pagination.Limit.ValueInt32() + presence := allMetrosData.Presence.ValueString() + if limit == 0 { + limit = 20 + } + + metroRequest := client.MetrosApi.GetMetros(ctx). + Limit(limit). + Offset(offset) + if presence != "" { + metroRequest.Presence(fabricv4.Presence(presence)) + } + metros, _, err := metroRequest.Execute() + + if err != nil { + response.State.RemoveResource(ctx) + response.Diagnostics.AddError("api error retrieving metros data", equinix_errors.FormatFabricError(err).Error()) + return + } + + response.Diagnostics.Append(allMetrosData.parse(ctx, metros)...) + if response.Diagnostics.HasError() { + return + } + response.Diagnostics.Append(response.State.Set(ctx, &allMetrosData)...) +} diff --git a/internal/resources/fabric/metro/datasources_schema.go b/internal/resources/fabric/metro/datasources_schema.go new file mode 100644 index 000000000..4732efc62 --- /dev/null +++ b/internal/resources/fabric/metro/datasources_schema.go @@ -0,0 +1,140 @@ +package metro + +import ( + "context" + + "github.com/equinix/terraform-provider-equinix/internal/framework" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework-validators/int32validator" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func dataSourceAllMetroSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": framework.IDAttributeDefaultDescription(), + "presence": schema.StringAttribute{ + Description: "User On Boarded Metros based on Fabric resource availability", + Optional: true, + }, + "pagination": schema.SingleNestedAttribute{ + Description: "Pagination details for the returned metro list", + Required: true, + CustomType: fwtypes.NewObjectTypeOf[PaginationModel](ctx), + Attributes: map[string]schema.Attribute{ + "offset": schema.Int32Attribute{ + Description: "Index of the first item returned in the response.", + Optional: true, + Validators: []validator.Int32{ + int32validator.AtLeast(0), + }, + }, + "limit": schema.Int32Attribute{ + Description: "Maximum number of search results returned per page.", + Optional: true, + Validators: []validator.Int32{ + int32validator.Between(1, 100), + }, + }, + "total": schema.Int32Attribute{ + Description: "The total number of metro returned", + Computed: true, + Validators: []validator.Int32{ + int32validator.AtLeast(0), + }, + }, + "next": schema.StringAttribute{ + Description: "URL relative to the next item in the response.", + Computed: true, + }, + "previous": schema.StringAttribute{ + Description: "URL relative to the previous item in the response.", + Computed: true, + }, + }, + }, + "data": schema.ListNestedAttribute{ + Description: "Returned list of metro objects", + Computed: true, + CustomType: fwtypes.NewListNestedObjectTypeOf[MetroModel](ctx), + NestedObject: schema.NestedAttributeObject{ + Attributes: getMetroSchema(ctx), + }, + }, + }, + } +} + +func dataSourceSingleMetroSchema(ctx context.Context) schema.Schema { + baseMetroSchema := getMetroSchema(ctx) + baseMetroSchema["id"] = framework.IDAttributeDefaultDescription() + baseMetroSchema["metro_code"] = schema.StringAttribute{ + Description: "The metro code this data source should retrieve", + Required: true, + } + return schema.Schema{ + Attributes: baseMetroSchema, + } +} + +func getMetroSchema(ctx context.Context) map[string]schema.Attribute { + return map[string]schema.Attribute{ + "href": schema.StringAttribute{ + Description: "The canonical URL at which the resource resides", + Computed: true, + }, + "type": schema.StringAttribute{ + Description: "Indicator of a fabric metro", + Computed: true, + }, + "code": schema.StringAttribute{ + Description: "Code assigned to an Equinix IBX data center in a specified metropolitan area", + Computed: true, + }, + "region": schema.StringAttribute{ + Description: "Board geographical area in which the data center is located", + Computed: true, + }, + "name": schema.StringAttribute{ + Description: "Name of the region in which the data center is located", + Computed: true, + }, + "equinix_asn": schema.Int64Attribute{ + Description: "Autonomous system number (ASN) for a specified Fabric metro. The ASN is a unique identifier that carries the network routing protocol and exchanges that data with other internal systems via border gateway protocol.", + Computed: true, + }, + "local_vc_bandwidth_max": schema.Int64Attribute{ + Description: "This field holds Max Connection speed within the metro.", + Computed: true, + }, + "geo_coordinates": schema.SingleNestedAttribute{ + Description: "Geographic location data of Fabric Metro", + CustomType: fwtypes.NewObjectTypeOf[GeoCoordinatesModel](ctx), + Attributes: map[string]schema.Attribute{ + "latitude": schema.Float64Attribute{ + Description: "Latitude of the Metro", + Computed: true, + }, + "longitude": schema.Float64Attribute{ + Description: "Longitude of the Metro", + Computed: true, + }, + }, + Computed: true, + }, + "connected_metros": schema.ListAttribute{ + Description: "Arrays of objects containing latency data for the specified metro", + CustomType: fwtypes.NewListNestedObjectTypeOf[ConnectedMetroModel](ctx), + ElementType: fwtypes.NewObjectTypeOf[ConnectedMetroModel](ctx), + Computed: true, + }, + "geo_scopes": schema.ListAttribute{ + Description: "List of supported geographic boundaries of a Fabric Metro. Example values: CANADA, CONUS.", + CustomType: fwtypes.ListOfStringType, + ElementType: types.StringType, + Computed: true, + }, + } +} diff --git a/internal/resources/fabric/metro/datasources_test.go b/internal/resources/fabric/metro/datasources_test.go new file mode 100644 index 000000000..48bfcbbbf --- /dev/null +++ b/internal/resources/fabric/metro/datasources_test.go @@ -0,0 +1,80 @@ +package metro_test + +import ( + "fmt" + "strconv" + "testing" + + "github.com/equinix/terraform-provider-equinix/internal/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccFabricMetroDataSource_PFCR(t *testing.T) { + metroName := "Melbourne" + metroCode := "ME" + limit := 8 + offset := 6 + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.TestAccPreCheck(t); acceptance.TestAccPreCheckProviderConfigured(t) }, + ExternalProviders: acceptance.TestExternalProviders, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: testAccFabricMetroDataSourcesConfig(metroCode, limit, offset), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.equinix_fabric_metro.metro", "code", metroCode), + resource.TestCheckResourceAttr("data.equinix_fabric_metro.metro", "name", metroName), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "type"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "id"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "href"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "region"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "equinix_asn"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "local_vc_bandwidth_max"), + resource.TestCheckResourceAttr("data.equinix_fabric_metro.metro", "geo_coordinates.%", "2"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "geo_coordinates.latitude"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "geo_coordinates.longitude"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "connected_metros.0.href"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "connected_metros.0.code"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "connected_metros.0.avg_latency"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metro.metro", "connected_metros.0.remote_vc_bandwidth_max"), + + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "id"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.name"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.type"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.code"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.region"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.name"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.equinix_asn"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.local_vc_bandwidth_max"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.geo_coordinates.latitude"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.geo_coordinates.longitude"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.connected_metros.0.href"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.connected_metros.0.code"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.connected_metros.0.avg_latency"), + resource.TestCheckResourceAttrSet("data.equinix_fabric_metros.metros", "data.0.connected_metros.0.remote_vc_bandwidth_max"), + resource.TestCheckResourceAttr("data.equinix_fabric_metros.metros", "pagination.%", "5"), + resource.TestCheckResourceAttr("data.equinix_fabric_metros.metros", "pagination.limit", strconv.Itoa(limit)), + resource.TestCheckResourceAttr("data.equinix_fabric_metros.metros", "pagination.offset", strconv.Itoa(offset)), + ), + ExpectNonEmptyPlan: false, + }, + }, + }) +} + +func testAccFabricMetroDataSourcesConfig(code string, limit, offset int) string { + return fmt.Sprintf(` + + data "equinix_fabric_metro" "metro" { + metro_code = "%[1]s" + } + + data "equinix_fabric_metros" "metros" { + pagination = { + limit = "%[2]d", + offset = "%[3]d" + } + } + `, code, limit, offset) +} diff --git a/internal/resources/fabric/metro/models.go b/internal/resources/fabric/metro/models.go new file mode 100644 index 000000000..e54517d67 --- /dev/null +++ b/internal/resources/fabric/metro/models.go @@ -0,0 +1,205 @@ +package metro + +import ( + "context" + + "github.com/equinix/equinix-sdk-go/services/fabricv4" + fwtypes "github.com/equinix/terraform-provider-equinix/internal/framework/types" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +type MetroModel struct { + Href types.String `tfsdk:"href"` + Type types.String `tfsdk:"type"` + Code types.String `tfsdk:"code"` + Region types.String `tfsdk:"region"` + Name types.String `tfsdk:"name"` + EquinixASN types.Int64 `tfsdk:"equinix_asn"` + LocalVCBandwidthMax types.Int64 `tfsdk:"local_vc_bandwidth_max"` + GeoCoordinates fwtypes.ObjectValueOf[GeoCoordinatesModel] `tfsdk:"geo_coordinates"` + ConnectedMetros fwtypes.ListNestedObjectValueOf[ConnectedMetroModel] `tfsdk:"connected_metros"` + GeoScopes fwtypes.ListValueOf[types.String] `tfsdk:"geo_scopes"` +} + +type ConnectedMetroModel struct { + Href types.String `tfsdk:"href"` + Code types.String `tfsdk:"code"` + AvgLatency types.Float32 `tfsdk:"avg_latency"` + RemoteVCBandwidthMax types.Int64 `tfsdk:"remote_vc_bandwidth_max"` +} + +type GeoCoordinatesModel struct { + Latitude types.Float64 `tfsdk:"latitude"` + Longitude types.Float64 `tfsdk:"longitude"` +} + +type PaginationModel struct { + Offset types.Int32 `tfsdk:"offset"` + Limit types.Int32 `tfsdk:"limit"` + Total types.Int32 `tfsdk:"total"` + Next types.String `tfsdk:"next"` + Previous types.String `tfsdk:"previous"` +} + +type DataSourceByCodeModel struct { + ID types.String `tfsdk:"id"` + MetroCode types.String `tfsdk:"metro_code"` + MetroModel +} + +type DataSourceAllMetrosModel struct { + ID types.String `tfsdk:"id"` + Presence types.String `tfsdk:"presence"` + Data fwtypes.ListNestedObjectValueOf[MetroModel] `tfsdk:"data"` + Pagination fwtypes.ObjectValueOf[PaginationModel] `tfsdk:"pagination"` +} + +func (a *DataSourceAllMetrosModel) parse(ctx context.Context, metroResponse *fabricv4.MetroResponse) diag.Diagnostics { + var diags diag.Diagnostics + + if len(metroResponse.GetData()) < 1 { + diags.AddError("no data retrieved by metros data source", + "either the account does not have any metros data to pull or the combination of limit and offset needs to be updated") + return diags + } + + data := make([]MetroModel, len(metroResponse.GetData())) + metros := metroResponse.GetData() + for i, metro := range metros { + var metroModel MetroModel + diags := metroModel.parse(ctx, &metro) + if diags.HasError() { + return diags + } + data[i] = metroModel + } + responsePagination := metroResponse.GetPagination() + pagination := PaginationModel{ + Offset: types.Int32Value(responsePagination.GetOffset()), + Limit: types.Int32Value(responsePagination.GetLimit()), + Total: types.Int32Value(responsePagination.GetTotal()), + Next: types.StringValue(responsePagination.GetNext()), + Previous: types.StringValue(responsePagination.GetPrevious()), + } + + a.ID = types.StringValue(data[0].Code.ValueString()) + a.Pagination = fwtypes.NewObjectValueOf[PaginationModel](ctx, &pagination) + a.Data = fwtypes.NewListNestedObjectValueOfValueSlice[MetroModel](ctx, data) + + return diags +} + +func (m *DataSourceByCodeModel) parse(ctx context.Context, metro *fabricv4.Metro) diag.Diagnostics { + var diags diag.Diagnostics + + m.ID = types.StringValue(metro.GetCode()) + m.MetroCode = types.StringValue(metro.GetCode()) + + var metroModel MetroModel + + diags = metroModel.parse(ctx, metro) + if diags.HasError() { + return diags + } + + m.MetroModel = metroModel + + return diags +} + +func (m *MetroModel) parse(ctx context.Context, metro *fabricv4.Metro) diag.Diagnostics { + diags := parseMetro(ctx, metro, &m.Type, &m.Href, &m.Code, &m.Region, &m.Name, &m.EquinixASN, &m.LocalVCBandwidthMax, &m.GeoCoordinates, &m.ConnectedMetros, &m.GeoScopes) + return diags +} + +func parseMetro(ctx context.Context, metro *fabricv4.Metro, tp, href, code, region, name *basetypes.StringValue, equinixAsn, localBandwidthMax *basetypes.Int64Value, geoCoordinates *fwtypes.ObjectValueOf[GeoCoordinatesModel], connectedMetros *fwtypes.ListNestedObjectValueOf[ConnectedMetroModel], gScopes *fwtypes.ListValueOf[types.String]) diag.Diagnostics { + + var diags diag.Diagnostics + *href = types.StringValue(metro.GetHref()) + *tp = types.StringValue(metro.GetType()) + *code = types.StringValue(metro.GetCode()) + *region = types.StringValue(metro.GetRegion()) + if metro.GetName() != "" { + *name = types.StringValue(metro.GetName()) + } + + if equinixAsn != nil { + *equinixAsn = types.Int64Value(metro.GetEquinixAsn()) + } + + if localBandwidthMax != nil { + *localBandwidthMax = types.Int64Value(metro.GetLocalVCBandwidthMax()) + } + + geoCoord, diags := parseGeoCoordinates(ctx, metro.GetGeoCoordinates()) + if diags.HasError() { + return diags + } + *geoCoordinates = geoCoord + + connMetros, diags := parseConnectedMetros(ctx, metro.GetConnectedMetros()) + if diags.HasError() { + return diags + } + *connectedMetros = connMetros + + geoScopes, diags := parseGeoScopes(ctx, metro.GetGeoScopes()) + if diags.HasError() { + return diags + } + *gScopes = geoScopes + + return diags +} + +func parseGeoScopes(ctx context.Context, scopes []fabricv4.GeoScopeType) (fwtypes.ListValueOf[types.String], diag.Diagnostics) { + var diags diag.Diagnostics + geoScopeTypeList := make([]attr.Value, len(scopes)) + + for i, scope := range scopes { + geoScopeTypeList[i] = types.StringValue(string(scope)) + } + geoScopeValue, diags := fwtypes.NewListValueOf[types.String](ctx, geoScopeTypeList) + + if diags.HasError() { + return fwtypes.NewListValueOfNull[types.String](ctx), diags + } + return geoScopeValue, diags +} + +func parseGeoCoordinates(ctx context.Context, coordinates fabricv4.GeoCoordinates) (fwtypes.ObjectValueOf[GeoCoordinatesModel], diag.Diagnostics) { + diags := diag.Diagnostics{} + + result := GeoCoordinatesModel{} + if coordinates.Latitude != nil { + result.Latitude = types.Float64Value(coordinates.GetLatitude()) + } + + if coordinates.Longitude != nil { + result.Longitude = types.Float64Value(coordinates.GetLongitude()) + } + return fwtypes.NewObjectValueOf[GeoCoordinatesModel](ctx, &result), diags +} + +func parseConnectedMetros(ctx context.Context, connectedMetros []fabricv4.ConnectedMetro) (fwtypes.ListNestedObjectValueOf[ConnectedMetroModel], diag.Diagnostics) { + connMetros := make([]ConnectedMetroModel, len(connectedMetros)) + for i, metro := range connectedMetros { + connMetros[i] = ConnectedMetroModel{} + if metro.Href != nil { + connMetros[i].Href = types.StringValue(metro.GetHref()) + } + if metro.Code != nil { + connMetros[i].Code = types.StringValue(metro.GetCode()) + } + if metro.AvgLatency != nil { + connMetros[i].AvgLatency = types.Float32Value(metro.GetAvgLatency()) + } + if metro.RemoteVCBandwidthMax != nil { + connMetros[i].RemoteVCBandwidthMax = types.Int64Value(metro.GetRemoteVCBandwidthMax()) + } + } + return fwtypes.NewListNestedObjectValueOfValueSlice(ctx, connMetros), nil +}