diff --git a/apstra/apstra_validator/at_most_n_of.go b/apstra/apstra_validator/at_most_n_of.go
index c607a730..7274bd4d 100644
--- a/apstra/apstra_validator/at_most_n_of.go
+++ b/apstra/apstra_validator/at_most_n_of.go
@@ -96,7 +96,7 @@ func (o AtMostNOfValidator) Validate(ctx context.Context, req AtMostNOfValidator
resp.Diagnostics.Append(validatordiag.InvalidAttributeCombinationDiagnostic(
req.Path,
- fmt.Sprintf("At most %d attributes out of %s must be specified, but %d matches were found",
+ fmt.Sprintf("At most %d attributes out of %s may be specified, but %d non-null attributes were found",
req.N, expressions, len(notNullPaths)),
))
}
diff --git a/apstra/blueprint/datacenter_virtual_network.go b/apstra/blueprint/datacenter_virtual_network.go
index cbe3f534..a02f8251 100644
--- a/apstra/blueprint/datacenter_virtual_network.go
+++ b/apstra/blueprint/datacenter_virtual_network.go
@@ -162,22 +162,6 @@ func (o DatacenterVirtualNetwork) DataSourceFilterAttributes() map[string]dataSo
"name": dataSourceSchema.StringAttribute{
MarkdownDescription: "Virtual Network Name",
Optional: true,
- Validators: []validator.String{stringvalidator.AtLeastOneOf(
- path.MatchRelative(),
- path.MatchRoot("filter").AtName("type"),
- path.MatchRoot("filter").AtName("routing_zone_id"),
- path.MatchRoot("filter").AtName("vni"),
- path.MatchRoot("filter").AtName("reserve_vlan"),
- path.MatchRoot("filter").AtName("dhcp_service_enabled"),
- path.MatchRoot("filter").AtName("ipv4_connectivity_enabled"),
- path.MatchRoot("filter").AtName("ipv6_connectivity_enabled"),
- path.MatchRoot("filter").AtName("ipv4_subnet"),
- path.MatchRoot("filter").AtName("ipv6_subnet"),
- path.MatchRoot("filter").AtName("ipv4_virtual_gateway_enabled"),
- path.MatchRoot("filter").AtName("ipv6_virtual_gateway_enabled"),
- path.MatchRoot("filter").AtName("ipv4_virtual_gateway"),
- path.MatchRoot("filter").AtName("ipv6_virtual_gateway"),
- )},
},
"type": dataSourceSchema.StringAttribute{
MarkdownDescription: "Virtual Network Type",
diff --git a/apstra/data_source_datacenter_virtual_networks.go b/apstra/data_source_datacenter_virtual_networks.go
index 5e16dbcf..781a4676 100644
--- a/apstra/data_source_datacenter_virtual_networks.go
+++ b/apstra/data_source_datacenter_virtual_networks.go
@@ -4,8 +4,10 @@ import (
"context"
"fmt"
"github.com/Juniper/apstra-go-sdk/apstra"
+ apstravalidator "github.com/Juniper/terraform-provider-apstra/apstra/apstra_validator"
"github.com/Juniper/terraform-provider-apstra/apstra/blueprint"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
+ "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/datasource"
@@ -52,13 +54,49 @@ func (o *dataSourceDatacenterVirtualNetworks) Schema(_ context.Context, _ dataso
"one filter attribute must be included when this attribute is used.",
Optional: true,
Attributes: blueprint.DatacenterVirtualNetwork{}.DataSourceFilterAttributes(),
+ Validators: []validator.Object{
+ apstravalidator.AtMostNOf(1,
+ path.MatchRelative(),
+ path.MatchRoot("filters"),
+ ),
+ apstravalidator.AtLeastNAttributes(
+ 1,
+ "name", "type", "routing_zone_id", "vni", "reserve_vlan", "dhcp_service_enabled",
+ "ipv4_connectivity_enabled", "ipv6_connectivity_enabled", "ipv4_subnet", "ipv6_subnet",
+ "ipv4_virtual_gateway_enabled", "ipv6_virtual_gateway_enabled", "ipv4_virtual_gateway",
+ "ipv6_virtual_gateway",
+ ),
+ },
+ DeprecationMessage: "The `filter` attribute is deprecated and will be removed in a future " +
+ "release. Please migrate your configuration to use `filters` instead.",
},
- "graph_query": schema.StringAttribute{
+ "filters": schema.ListNestedAttribute{
+ MarkdownDescription: "List of filters used to select only desired node IDs. For a node " +
+ "to match a filter, all specified attributes must match (each attribute within a " +
+ "filter is AND-ed together). The returned node IDs represent the nodes matched by " +
+ "all of the filters together (filters are OR-ed together).",
+ Optional: true,
+ Validators: []validator.List{listvalidator.SizeAtLeast(1)},
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: blueprint.DatacenterVirtualNetwork{}.DataSourceFilterAttributes(),
+ Validators: []validator.Object{
+ apstravalidator.AtLeastNAttributes(
+ 1,
+ "name", "type", "routing_zone_id", "vni", "reserve_vlan", "dhcp_service_enabled",
+ "ipv4_connectivity_enabled", "ipv6_connectivity_enabled", "ipv4_subnet", "ipv6_subnet",
+ "ipv4_virtual_gateway_enabled", "ipv6_virtual_gateway_enabled", "ipv4_virtual_gateway",
+ "ipv6_virtual_gateway",
+ ),
+ },
+ },
+ },
+ "graph_queries": schema.ListAttribute{
MarkdownDescription: "The graph datastore query based on `filter` used to " +
"perform the lookup. Note that the `ipv6_subnet` and `ipv6_gateway` " +
"attributes are never part of the graph query because IPv6 zero " +
"compression rules make string matches unreliable.",
- Computed: true,
+ ElementType: types.StringType,
+ Computed: true,
},
},
}
@@ -69,7 +107,8 @@ func (o *dataSourceDatacenterVirtualNetworks) Read(ctx context.Context, req data
BlueprintId types.String `tfsdk:"blueprint_id"`
IDs types.Set `tfsdk:"ids"`
Filter types.Object `tfsdk:"filter"`
- Query types.String `tfsdk:"graph_query"`
+ Filters types.List `tfsdk:"filters"`
+ Queries types.List `tfsdk:"graph_queries"`
}
var config virtualNetworks
@@ -78,29 +117,43 @@ func (o *dataSourceDatacenterVirtualNetworks) Read(ctx context.Context, req data
return
}
- var ids []attr.Value
- var query *apstra.MatchQuery // todo change to interface after SDK update
bpId := apstra.ObjectId(config.BlueprintId.ValueString())
- if config.Filter.IsNull() {
+ if config.Filter.IsNull() && config.Filters.IsNull() {
// just pull the VN IDs via API when no filter is specified
- ids = o.getAllVnIds(ctx, bpId, &resp.Diagnostics)
+ ids := o.getAllVnIds(ctx, bpId, &resp.Diagnostics)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // set the state
config.IDs = types.SetValueMust(types.StringType, ids)
- } else {
- // use a graph query (and some IPv6 value matching)
- filter := blueprint.DatacenterVirtualNetwork{}
+ config.Queries = types.ListNull(types.StringType)
+ resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
+ return
+ }
+
+ var filters []blueprint.DatacenterVirtualNetwork
+ if !config.Filter.IsNull() {
+ var filter blueprint.DatacenterVirtualNetwork
resp.Diagnostics.Append(config.Filter.As(ctx, &filter, basetypes.ObjectAsOptions{})...)
if resp.Diagnostics.HasError() {
return
}
- ids, query = o.getFilteredVnIds(ctx, bpId, filter, &resp.Diagnostics)
- config.IDs = types.SetValueMust(types.StringType, ids)
- config.Query = types.StringValue(query.String())
+ filters = append(filters, filter)
}
- if resp.Diagnostics.HasError() {
- return
+
+ if !config.Filters.IsNull() {
+ resp.Diagnostics.Append(config.Filters.ElementsAs(ctx, &filters, false)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
}
+ ids, queries := o.getVnIdsWithFilters(ctx, bpId, filters, &resp.Diagnostics)
+ config.IDs = types.SetValueMust(types.StringType, ids)
+ config.Queries = types.ListValueMust(types.StringType, queries)
+
// set the state
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
}
@@ -131,8 +184,32 @@ func (o *dataSourceDatacenterVirtualNetworks) getAllVnIds(ctx context.Context, b
return result
}
-// todo change returned query to interface after SDK update
-func (o *dataSourceDatacenterVirtualNetworks) getFilteredVnIds(ctx context.Context, bpId apstra.ObjectId, filter blueprint.DatacenterVirtualNetwork, diags *diag.Diagnostics) ([]attr.Value, *apstra.MatchQuery) {
+func (o *dataSourceDatacenterVirtualNetworks) getVnIdsWithFilters(ctx context.Context, bpId apstra.ObjectId, filters []blueprint.DatacenterVirtualNetwork, diags *diag.Diagnostics) ([]attr.Value, []attr.Value) {
+ queries := make([]attr.Value, len(filters))
+ resultMap := make(map[string]bool)
+ for i, filter := range filters {
+ ids, query := o.getVnIdsWithFilter(ctx, bpId, filter, diags)
+ if diags.HasError() {
+ return nil, nil
+ }
+
+ queries[i] = types.StringValue(query.String())
+ for _, id := range ids {
+ resultMap[id] = true
+ }
+ }
+
+ ids := make([]attr.Value, len(resultMap))
+ var i int
+ for id := range resultMap {
+ ids[i] = types.StringValue(id)
+ i++
+ }
+
+ return ids, queries
+}
+
+func (o *dataSourceDatacenterVirtualNetworks) getVnIdsWithFilter(ctx context.Context, bpId apstra.ObjectId, filter blueprint.DatacenterVirtualNetwork, diags *diag.Diagnostics) ([]string, apstra.QEQuery) {
query := filter.Query("n_virtual_network")
queryResponse := new(struct {
Items []struct {
@@ -145,13 +222,10 @@ func (o *dataSourceDatacenterVirtualNetworks) getFilteredVnIds(ctx context.Conte
})
// todo remove this type assertion when QEQuery is extended with new methods used below
- query2 := query.(*apstra.MatchQuery)
- query2.
- SetClient(o.client).
- SetBlueprintId(bpId).
- SetBlueprintType(apstra.BlueprintTypeStaging)
-
- err := query2.Do(ctx, queryResponse)
+ query.(*apstra.MatchQuery).SetClient(o.client)
+ query.(*apstra.MatchQuery).SetBlueprintId(bpId)
+ query.(*apstra.MatchQuery).SetBlueprintType(apstra.BlueprintTypeStaging)
+ err := query.Do(ctx, queryResponse)
if err != nil {
diags.AddError("error querying graph datastore", err.Error())
return nil, nil
@@ -218,10 +292,10 @@ func (o *dataSourceDatacenterVirtualNetworks) getFilteredVnIds(ctx context.Conte
}
}
- result := make([]attr.Value, len(queryResponse.Items))
+ result := make([]string, len(queryResponse.Items))
for i, item := range queryResponse.Items {
- result[i] = types.StringValue(item.VirtualNetwork.Id)
+ result[i] = item.VirtualNetwork.Id
}
- return result, query2
+ return result, query
}
diff --git a/docs/data-sources/datacenter_virtual_networks.md b/docs/data-sources/datacenter_virtual_networks.md
index 36438fe7..f8ee7f50 100644
--- a/docs/data-sources/datacenter_virtual_networks.md
+++ b/docs/data-sources/datacenter_virtual_networks.md
@@ -32,11 +32,13 @@ data "apstra_datacenter_virtual_networks" "all" {
# )
data "apstra_datacenter_virtual_networks" "prod_unreserved_with_dhcp" {
blueprint_id = "b726704d-f80e-4733-9103-abd6ccd8752c"
- filter = {
- reserve_vlan = false
- dhcp_service_enabled = true
- routing_zone_id = apstra_datacenter_routing_zone.prod.id
- }
+ filters = [
+ {
+ reserve_vlan = false
+ dhcp_service_enabled = true
+ routing_zone_id = "Zplm0niOFCCCfjaXkXo"
+ }
+ ]
}
```
@@ -49,11 +51,12 @@ data "apstra_datacenter_virtual_networks" "prod_unreserved_with_dhcp" {
### Optional
-- `filter` (Attributes) Virtual Network attributes used as filter. At least one filter attribute must be included when this attribute is used. (see [below for nested schema](#nestedatt--filter))
+- `filter` (Attributes, Deprecated) Virtual Network attributes used as filter. At least one filter attribute must be included when this attribute is used. (see [below for nested schema](#nestedatt--filter))
+- `filters` (Attributes List) List of filters used to select only desired node IDs. For a node to match a filter, all specified attributes must match (each attribute within a filter is AND-ed together). The returned node IDs represent the nodes matched by all of the filters together (filters are OR-ed together). (see [below for nested schema](#nestedatt--filters))
### Read-Only
-- `graph_query` (String) The graph datastore query based on `filter` used to perform the lookup. Note that the `ipv6_subnet` and `ipv6_gateway` attributes are never part of the graph query because IPv6 zero compression rules make string matches unreliable.
+- `graph_queries` (List of String) The graph datastore query based on `filter` used to perform the lookup. Note that the `ipv6_subnet` and `ipv6_gateway` attributes are never part of the graph query because IPv6 zero compression rules make string matches unreliable.
- `ids` (Set of String) Set of Virtual Network IDs
@@ -85,3 +88,35 @@ Read-Only:
### Nested Schema for `filter.bindings`
+
+
+
+
+### Nested Schema for `filters`
+
+Optional:
+
+- `dhcp_service_enabled` (Boolean) Enables a DHCP relay agent.
+- `ipv4_connectivity_enabled` (Boolean) Enables IPv4 within the Virtual Network.
+- `ipv4_subnet` (String) IPv4 subnet associated with the Virtual Network.
+- `ipv4_virtual_gateway` (String) Specifies the IPv4 virtual gateway address within the Virtual Network.
+- `ipv4_virtual_gateway_enabled` (Boolean) Controls and indicates whether the IPv4 gateway within the Virtual Network is enabled.
+- `ipv6_connectivity_enabled` (Boolean) Enables IPv6 within the Virtual Network.
+- `ipv6_subnet` (String) IPv6 subnet associated with the Virtual Network. Note that this attribute will not appear in the `graph_query` output because IPv6 zero compression rules are problematic for mechanisms which rely on string matching.
+- `ipv6_virtual_gateway` (String) Specifies the IPv6 virtual gateway address within the Virtual Network. Note that this attribute will not appear in the `graph_query` output because IPv6 zero compression rules are problematic for mechanisms which rely on string matching.
+- `ipv6_virtual_gateway_enabled` (Boolean) Controls and indicates whether the IPv6 gateway within the Virtual Network is enabled.
+- `name` (String) Virtual Network Name
+- `reserve_vlan` (Boolean) For use only with `vxlan` type Virtual networks when all `bindings` use the same VLAN ID. This option reserves the VLAN fabric-wide, even on switches to which the Virtual Network has not yet been deployed.
+- `routing_zone_id` (String) Routing Zone ID (required when `type == vxlan`
+- `type` (String) Virtual Network Type
+- `vni` (Number) EVPN Virtual Network ID to be associated with this Virtual Network.
+
+Read-Only:
+
+- `bindings` (Attributes Map) Not applicable in filter context. Ignore. (see [below for nested schema](#nestedatt--filters--bindings))
+- `blueprint_id` (String) 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.
+
+
+### Nested Schema for `filters.bindings`
diff --git a/examples/data-sources/apstra_datacenter_virtual_networks/example.tf b/examples/data-sources/apstra_datacenter_virtual_networks/example.tf
index 316c440a..3a54b099 100644
--- a/examples/data-sources/apstra_datacenter_virtual_networks/example.tf
+++ b/examples/data-sources/apstra_datacenter_virtual_networks/example.tf
@@ -17,9 +17,11 @@ data "apstra_datacenter_virtual_networks" "all" {
# )
data "apstra_datacenter_virtual_networks" "prod_unreserved_with_dhcp" {
blueprint_id = "b726704d-f80e-4733-9103-abd6ccd8752c"
- filter = {
- reserve_vlan = false
- dhcp_service_enabled = true
- routing_zone_id = apstra_datacenter_routing_zone.prod.id
- }
+ filters = [
+ {
+ reserve_vlan = false
+ dhcp_service_enabled = true
+ routing_zone_id = "Zplm0niOFCCCfjaXkXo"
+ }
+ ]
}