Skip to content

Commit

Permalink
Merge pull request #101 from Juniper/data-source-routing-zones
Browse files Browse the repository at this point in the history
Introduce `data.apstra_datacenter_routing_zones`
  • Loading branch information
chrismarget-j authored May 25, 2023
2 parents 71e683e + 33669cf commit 7e84078
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 11 deletions.
106 changes: 106 additions & 0 deletions apstra/blueprint/datacenter_routing_zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,50 @@ func (o DatacenterRoutingZone) DataSourceAttributes() map[string]dataSourceSchem
}
}

func (o DatacenterRoutingZone) DataSourceFilterAttributes() map[string]dataSourceSchema.Attribute {
return map[string]dataSourceSchema.Attribute{
"id": dataSourceSchema.StringAttribute{
MarkdownDescription: "Not applicable in filter context. Ignore.",
Computed: true,
},
"blueprint_id": dataSourceSchema.StringAttribute{
MarkdownDescription: "Not applicable in filter context. Ignore.",
Computed: true,
},
"name": dataSourceSchema.StringAttribute{
MarkdownDescription: "VRF name displayed in thw Apstra web UI.",
Optional: true,
},
"vlan_id": dataSourceSchema.Int64Attribute{
MarkdownDescription: "Used for VLAN tagged Layer 3 links on external connections.",
Optional: true,
},
"had_prior_vlan_id_config": dataSourceSchema.BoolAttribute{
MarkdownDescription: "Not applicable in filter context. Ignore.",
Computed: true,
},
"vni": dataSourceSchema.Int64Attribute{
MarkdownDescription: "VxLAN VNI associated with the routing zone.",
Optional: true,
},
"had_prior_vni_config": dataSourceSchema.BoolAttribute{
MarkdownDescription: "Not applicable in filter context. Ignore.",
Computed: true,
},
"dhcp_servers": dataSourceSchema.SetAttribute{
MarkdownDescription: "Set of addresses of DHCP servers (IPv4 or IPv6) which must be configured " +
"in the Routing Zone. This is a list of *required* servers, not an exact-match list.",
ElementType: types.StringType,
Optional: true,
},
"routing_policy_id": dataSourceSchema.StringAttribute{
MarkdownDescription: "Non-EVPN blueprints must use the default policy, so this field must be null. " +
"Set this attribute in an EVPN blueprint to use a non-default policy.",
Optional: true,
},
}
}

func (o DatacenterRoutingZone) ResourceAttributes() map[string]resourceSchema.Attribute {
nameRE := regexp.MustCompile("^[A-Za-z0-9_-]+$")
return map[string]resourceSchema.Attribute{
Expand Down Expand Up @@ -227,3 +271,65 @@ func (o *DatacenterRoutingZone) LoadApiDhcpServers(ctx context.Context, IPs []ne
}
o.DhcpServers = utils.SetValueOrNull(ctx, types.StringType, dhcpServers, diags)
}

func (o *DatacenterRoutingZone) Query(szResultName, policyResultName string) *apstra.PathQuery {
query := new(apstra.PathQuery)

if utils.Known(o.RoutingPolicyId) {
query.Node([]apstra.QEEAttribute{
{Key: "type", Value: apstra.QEStringVal(apstra.NodeTypeRoutingPolicy.String())},
{Key: "id", Value: apstra.QEStringVal(o.RoutingPolicyId.ValueString())},
})
query.In([]apstra.QEEAttribute{
{Key: "type", Value: apstra.QEStringVal("policy")},
})
}

query.Node(o.szNodeQueryAttributes(szResultName))

if utils.Known(o.DhcpServers) {
query.Out([]apstra.QEEAttribute{
{Key: "type", Value: apstra.QEStringVal("policy")},
})
query.Node([]apstra.QEEAttribute{
{Key: "type", Value: apstra.QEStringVal(apstra.NodeTypePolicy.String())},
{Key: "policy_type", Value: apstra.QEStringVal("dhcp_relay")},
{Key: "name", Value: apstra.QEStringVal(policyResultName)},
})
}

for _, dhcpServerVal := range o.DhcpServers.Elements() {
query.Where("lambda " +
policyResultName +
": '" +
dhcpServerVal.(types.String).ValueString() +
"' in " +
policyResultName +
".dhcp_servers")
}
return query
}

func (o *DatacenterRoutingZone) szNodeQueryAttributes(name string) []apstra.QEEAttribute {
result := []apstra.QEEAttribute{
{Key: "type", Value: apstra.QEStringVal(apstra.NodeTypeSecurityZone.String())},
}

if name != "" {
result = append(result, apstra.QEEAttribute{Key: "name", Value: apstra.QEStringVal(name)})
}

if utils.Known(o.Name) {
result = append(result, apstra.QEEAttribute{Key: "label", Value: apstra.QEStringVal(o.Name.ValueString())})
}

if utils.Known(o.Vni) {
result = append(result, apstra.QEEAttribute{Key: "vni_id", Value: apstra.QEIntVal(int(o.Vni.ValueInt64()))})
}

if utils.Known(o.VlanId) {
result = append(result, apstra.QEEAttribute{Key: "vlan_id", Value: apstra.QEIntVal(int(o.VlanId.ValueInt64()))})
}

return result
}
12 changes: 6 additions & 6 deletions apstra/data_source_datacenter_routing_zone.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,28 @@ import (
"terraform-provider-apstra/apstra/blueprint"
)

var _ datasource.DataSourceWithConfigure = &dataSourceRoutingZone{}
var _ datasource.DataSourceWithConfigure = &dataSourceDatacenterRoutingZone{}

type dataSourceRoutingZone struct {
type dataSourceDatacenterRoutingZone struct {
client *apstra.Client
}

func (o *dataSourceRoutingZone) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
func (o *dataSourceDatacenterRoutingZone) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_datacenter_routing_zone"
}

func (o *dataSourceRoutingZone) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
func (o *dataSourceDatacenterRoutingZone) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
o.client = DataSourceGetClient(ctx, req, resp)
}

func (o *dataSourceRoutingZone) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
func (o *dataSourceDatacenterRoutingZone) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "This resource returns details of a Routing Zone within a Datacenter Blueprint.",
Attributes: blueprint.DatacenterRoutingZone{}.DataSourceAttributes(),
}
}

func (o *dataSourceRoutingZone) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
func (o *dataSourceDatacenterRoutingZone) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
if o.client == nil {
resp.Diagnostics.AddError(errDataSourceUnconfiguredSummary, errDatasourceUnconfiguredDetail)
return
Expand Down
115 changes: 115 additions & 0 deletions apstra/data_source_datacenter_routing_zones.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package tfapstra

import (
"context"
"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"terraform-provider-apstra/apstra/blueprint"
)

var _ datasource.DataSourceWithConfigure = &dataSourceDatacenterRoutingZones{}

type dataSourceDatacenterRoutingZones struct {
client *apstra.Client
}

func (o *dataSourceDatacenterRoutingZones) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_datacenter_routing_zones"
}

func (o *dataSourceDatacenterRoutingZones) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
o.client = DataSourceGetClient(ctx, req, resp)
}

func (o *dataSourceDatacenterRoutingZones) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "This data source returns the IDs of Routing Zones within the specified Blueprint. " +
"All of the `filters` attributes are optional.",
Attributes: map[string]schema.Attribute{
"blueprint_id": schema.StringAttribute{
MarkdownDescription: "Apstra Blueprint ID.",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"ids": schema.SetAttribute{
MarkdownDescription: "Set of Routing Zone IDs",
Computed: true,
ElementType: types.StringType,
},
"filters": schema.SingleNestedAttribute{
MarkdownDescription: "Routing Zone attributes used as filters",
Optional: true,
Attributes: blueprint.DatacenterRoutingZone{}.DataSourceFilterAttributes(),
},
"graph_query": schema.StringAttribute{
MarkdownDescription: "The graph datastore query used to perform the lookup.",
Computed: true,
},
},
}
}

func (o *dataSourceDatacenterRoutingZones) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
if o.client == nil {
resp.Diagnostics.AddError(errDataSourceUnconfiguredSummary, errDatasourceUnconfiguredDetail)
return
}

type routingZones struct {
BlueprintId types.String `tfsdk:"blueprint_id"`
IDs types.Set `tfsdk:"ids"`
Filters types.Object `tfsdk:"filters"`
Query types.String `tfsdk:"graph_query"`
}

var config routingZones
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}

filters := &blueprint.DatacenterRoutingZone{}
d := config.Filters.As(ctx, &filters, basetypes.ObjectAsOptions{})
resp.Diagnostics.Append(d...)
if resp.Diagnostics.HasError() {
return
}

query := filters.Query("n_security_zone", "n_policy")

queryResponse := new(struct {
Items []struct {
SecurityZone struct {
Id string `json:"id"`
} `json:"n_security_zone"`
} `json:"items"`
})

query.
SetClient(o.client).
SetBlueprintId(apstra.ObjectId(config.BlueprintId.ValueString())).
SetBlueprintType(apstra.BlueprintTypeStaging)

err := query.Do(ctx, queryResponse)
if err != nil {
resp.Diagnostics.AddError("error querying graph datastore", err.Error())
return
}

ids := make([]attr.Value, len(queryResponse.Items))
for i, item := range queryResponse.Items {
ids[i] = types.StringValue(item.SecurityZone.Id)
}

config.IDs = types.SetValueMust(types.StringType, ids)
config.Query = types.StringValue(query.String())

// set state
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
}
3 changes: 2 additions & 1 deletion apstra/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,8 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
func() datasource.DataSource { return &dataSourceRackTypes{} },
//func() datasource.DataSource { return &dataSourceTemplateL3Collapsed{} },
//func() datasource.DataSource { return &dataSourceTemplatePodBased{}},
func() datasource.DataSource { return &dataSourceRoutingZone{} },
func() datasource.DataSource { return &dataSourceDatacenterRoutingZone{} },
func() datasource.DataSource { return &dataSourceDatacenterRoutingZones{} },
func() datasource.DataSource { return &dataSourceTemplateRackBased{} },
func() datasource.DataSource { return &dataSourceTemplates{} },
func() datasource.DataSource { return &dataSourceDatacenterBlueprint{} },
Expand Down
67 changes: 67 additions & 0 deletions docs/data-sources/datacenter_routing_zones.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
page_title: "apstra_datacenter_routing_zones Data Source - terraform-provider-apstra"
subcategory: ""
description: |-
This data source returns the IDs of Routing Zones within the specified Blueprint. All of the filters attributes are optional.
---

# apstra_datacenter_routing_zones (Data Source)

This data source returns the IDs of Routing Zones within the specified Blueprint. All of the `filters` attributes are optional.

## Example Usage

```terraform
# By specifying no filters, a wide search is performed. All routing
# zones in the blueprint will match.
data "apstra_datacenter_routing_zones" "all" {
blueprint_id = "05f9d3fc-671a-4efc-8e91-5ef87b2937d3"
}
# This example performs a very narrow search. Only one (or zero!)
# routing zones can match the resulting query.
data "apstra_datacenter_routing_zones" "rzs" {
blueprint_id = "05f9d3fc-671a-4efc-8e91-5ef87b2937d3"
filters = { # all filters are optional
"name" = "customer_1"
"vlan_id" = 55
"vni" = 10055
dhcp_servers = ["192.168.5.100", "192.168.10.100"]
routing_policy_id = "vqsv3F93MBHUgg5e8ws"
}
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `blueprint_id` (String) Apstra Blueprint ID.

### Optional

- `filters` (Attributes) Routing Zone attributes used as filters (see [below for nested schema](#nestedatt--filters))

### Read-Only

- `graph_query` (String) The graph datastore query used to perform the lookup.
- `ids` (Set of String) Set of Routing Zone IDs

<a id="nestedatt--filters"></a>
### Nested Schema for `filters`

Optional:

- `dhcp_servers` (Set of String) Set of DHCP server IPv4 or IPv6 addresses of DHCP servers which must be configured in the Routing Zone. This is a list of *required* servers, not an exact-match list.
- `name` (String) VRF name displayed in thw Apstra web UI.
- `routing_policy_id` (String) Non-EVPN blueprints must use the default policy, so this field must be null. Set this attribute in an EVPN blueprint to use a non-default policy.
- `vlan_id` (Number) Used for VLAN tagged Layer 3 links on external connections.
- `vni` (Number) VxLAN VNI associated with the routing zone.

Read-Only:

- `blueprint_id` (String) Not applicable in filter context. Ignore.
- `had_prior_vlan_id_config` (Boolean) Not applicable in filter context. Ignore.
- `had_prior_vni_config` (Boolean) Not applicable in filter context. Ignore.
- `id` (String) Not applicable in filter context. Ignore.
18 changes: 18 additions & 0 deletions examples/data-sources/apstra_datacenter_routing_zones/example.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# By specifying no filters, a wide search is performed. All routing
# zones in the blueprint will match.
data "apstra_datacenter_routing_zones" "all" {
blueprint_id = "05f9d3fc-671a-4efc-8e91-5ef87b2937d3"
}

# This example performs a very narrow search. Only one (or zero!)
# routing zones can match the resulting query.
data "apstra_datacenter_routing_zones" "rzs" {
blueprint_id = "05f9d3fc-671a-4efc-8e91-5ef87b2937d3"
filters = { # all filters are optional
"name" = "customer_1"
"vlan_id" = 55
"vni" = 10055
dhcp_servers = ["192.168.5.100", "192.168.10.100"]
routing_policy_id = "vqsv3F93MBHUgg5e8ws"
}
}
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.20

require (
github.com/IBM/netaddr v1.5.0
github.com/Juniper/apstra-go-sdk v0.0.0-20230524142438-5959c7771c90
github.com/Juniper/apstra-go-sdk v0.0.0-20230525181310-bbaf3065d735
github.com/chrismarget-j/go-licenses v0.0.0-20230424163011-d60082a506e0
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/terraform-plugin-docs v0.13.0
Expand All @@ -16,7 +16,7 @@ require (
)

// HHMMSS
//replace github.com/Juniper/apstra-go-sdk => github.com/Juniper/apstra-go-sdk v0.0.0-20230524014058-55d8ff99df88
//replace github.com/Juniper/apstra-go-sdk => github.com/Juniper/apstra-go-sdk v0.0.0-20230524182305-eec4bb728c2b

require (
github.com/Masterminds/goutils v1.1.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/IBM/netaddr v1.5.0 h1:IJlFZe1+nFs09TeMB/HOP4+xBnX2iM/xgiDOgZgTJq0=
github.com/IBM/netaddr v1.5.0/go.mod h1:DDBPeYgbFzoXHjSz9Jwk7K8wmWV4+a/Kv0LqRnb8we4=
github.com/Juniper/apstra-go-sdk v0.0.0-20230524142438-5959c7771c90 h1:1WWSwbXF9USUkB42MpVJwFuZqsYJ6DNETPxJ6x5F2bw=
github.com/Juniper/apstra-go-sdk v0.0.0-20230524142438-5959c7771c90/go.mod h1:PKQO0TF/UB7vSaD3KtIlNT6VlYSuJRfPVE2loT9C/lM=
github.com/Juniper/apstra-go-sdk v0.0.0-20230525181310-bbaf3065d735 h1:Un2d5wIOmLqEt1wDR3Q4FkB052gW1rVmX+pZ/H7h3V4=
github.com/Juniper/apstra-go-sdk v0.0.0-20230525181310-bbaf3065d735/go.mod h1:PKQO0TF/UB7vSaD3KtIlNT6VlYSuJRfPVE2loT9C/lM=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
Expand Down

0 comments on commit 7e84078

Please sign in to comment.