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

Add apstra_datacenter_virtual_network data source #452

Merged
merged 6 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 104 additions & 3 deletions apstra/blueprint/datacenter_virtual_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,107 @@ type DatacenterVirtualNetwork struct {
IPv6Gateway types.String `tfsdk:"ipv6_virtual_gateway"`
}

func (o DatacenterVirtualNetwork) DataSourceAttributes() map[string]dataSourceSchema.Attribute {
return map[string]dataSourceSchema.Attribute{
"id": dataSourceSchema.StringAttribute{
MarkdownDescription: "The id of the Virtual Network",
Computed: true,
Optional: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.ExactlyOneOf(path.Expressions{
path.MatchRelative(),
path.MatchRoot("name"),
}...),
},
},
"blueprint_id": dataSourceSchema.StringAttribute{
MarkdownDescription: "The blueprint ID where the Virtual Network is present.",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"name": dataSourceSchema.StringAttribute{
MarkdownDescription: "Virtual Network Name",
Optional: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"type": dataSourceSchema.StringAttribute{
MarkdownDescription: "Virtual Network Type",
Computed: true,
},
"routing_zone_id": dataSourceSchema.StringAttribute{
MarkdownDescription: fmt.Sprintf("Routing Zone ID (only applies when `type == %s`", apstra.VnTypeVxlan),
Computed: true,
},
"vni": dataSourceSchema.Int64Attribute{
MarkdownDescription: "EVPN Virtual Network ID to be associated with this Virtual Network.",
Computed: true,
},
"had_prior_vni_config": dataSourceSchema.BoolAttribute{
MarkdownDescription: "Not applicable in data source context. Ignore.",
Computed: true,
},
"reserve_vlan": dataSourceSchema.BoolAttribute{
MarkdownDescription: fmt.Sprintf("For use only with `%s` 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.", apstra.VnTypeVxlan),
Computed: true,
},
"bindings": dataSourceSchema.MapNestedAttribute{
MarkdownDescription: "Details availability of the virtual network on leaf and access switches",
Computed: true,
NestedObject: dataSourceSchema.NestedAttributeObject{
Attributes: VnBinding{}.DataSourceAttributes(),
},
},
"dhcp_service_enabled": dataSourceSchema.BoolAttribute{
MarkdownDescription: "Enables a DHCP relay agent.",
Computed: true,
},
"ipv4_connectivity_enabled": dataSourceSchema.BoolAttribute{
MarkdownDescription: "Enables IPv4 within the Virtual Network.",
Computed: true,
},
"ipv6_connectivity_enabled": dataSourceSchema.BoolAttribute{
MarkdownDescription: "Enables IPv6 within the Virtual Network.",
Computed: true,
},
"ipv4_subnet": dataSourceSchema.StringAttribute{
MarkdownDescription: "IPv4 subnet associated with the Virtual Network.",
Computed: true,
},
"ipv6_subnet": dataSourceSchema.StringAttribute{
MarkdownDescription: "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.",
Computed: true,
},
"ipv4_virtual_gateway_enabled": dataSourceSchema.BoolAttribute{
MarkdownDescription: "Controls and indicates whether the IPv4 gateway within the " +
"Virtual Network is enabled.",
Computed: true,
},
"ipv6_virtual_gateway_enabled": dataSourceSchema.BoolAttribute{
MarkdownDescription: "Controls and indicates whether the IPv6 gateway within the " +
"Virtual Network is enabled.",
Computed: true,
},
"ipv4_virtual_gateway": dataSourceSchema.StringAttribute{
MarkdownDescription: "Specifies the IPv4 virtual gateway address within the " +
"Virtual Network.",
Computed: true,
},
"ipv6_virtual_gateway": dataSourceSchema.StringAttribute{
MarkdownDescription: "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.",
Computed: true,
},
}
}

func (o DatacenterVirtualNetwork) DataSourceFilterAttributes() map[string]dataSourceSchema.Attribute {
return map[string]dataSourceSchema.Attribute{
"id": dataSourceSchema.StringAttribute{
Expand Down Expand Up @@ -95,9 +196,9 @@ func (o DatacenterVirtualNetwork) DataSourceFilterAttributes() map[string]dataSo
Computed: true,
},
"reserve_vlan": dataSourceSchema.BoolAttribute{
MarkdownDescription: "For use only with `%s` 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.",
MarkdownDescription: fmt.Sprintf("For use only with `%s` 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.", apstra.VnTypeVxlan),
Optional: true,
},
"bindings": dataSourceSchema.MapNestedAttribute{
Expand Down
17 changes: 16 additions & 1 deletion apstra/blueprint/vn_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@ func (o VnBinding) attrTypes() map[string]attr.Type {

}

func (o VnBinding) DataSourceAttributes() map[string]dataSourceSchema.Attribute {
return map[string]dataSourceSchema.Attribute{
"vlan_id": dataSourceSchema.Int64Attribute{
MarkdownDescription: "VLAN id on the specified switch, switch pair and access switches.",
Computed: true,
},
"access_ids": dataSourceSchema.SetAttribute{
MarkdownDescription: "A set of zero or more graph db node IDs representing Access " +
"Switch `system` nodes or a `redundancy_group` nodes.",
Computed: true,
ElementType: types.StringType,
},
}
}

func (o VnBinding) DataSourceAttributesConstructorOutput() map[string]dataSourceSchema.Attribute {
return map[string]dataSourceSchema.Attribute{
"vlan_id": dataSourceSchema.Int64Attribute{
Expand All @@ -37,7 +52,7 @@ func (o VnBinding) DataSourceAttributesConstructorOutput() map[string]dataSource
},
"access_ids": dataSourceSchema.SetAttribute{
MarkdownDescription: "A set of zero or more graph db node IDs representing Access " +
"Lwitch `system` nodes or a `redundancy_group` nodes.",
"Switch `system` nodes or a `redundancy_group` nodes.",
Computed: true,
ElementType: types.StringType,
},
Expand Down
4 changes: 2 additions & 2 deletions apstra/data_source_datacenter_external_gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func TestDatacenterExternalGateway(t *testing.T) {
RouteTypes: apstra.RemoteGatewayRouteTypesAll,
LocalGwNodes: leafIds,
GwAsn: rand.Uint32(),
GwIp: randIpAddressMust(t, "10.0.0.0/8"),
GwIp: randIpv4AddressMust(t, "10.0.0.0/8"),
GwName: acctest.RandString(5),
Ttl: &ttl,
KeepaliveTimer: &keepalive,
Expand All @@ -69,7 +69,7 @@ func TestDatacenterExternalGateway(t *testing.T) {
RouteTypes: apstra.RemoteGatewayRouteTypesFiveOnly,
LocalGwNodes: leafIds,
GwAsn: rand.Uint32(),
GwIp: randIpAddressMust(t, "10.0.0.0/8"),
GwIp: randIpv4AddressMust(t, "10.0.0.0/8"),
GwName: acctest.RandString(5),
Ttl: &ttl,
KeepaliveTimer: &keepalive,
Expand Down
91 changes: 91 additions & 0 deletions apstra/data_source_datacenter_virtual_network.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package tfapstra

import (
"context"
"fmt"
"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/Juniper/terraform-provider-apstra/apstra/blueprint"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var _ datasource.DataSourceWithConfigure = &dataSourceDatacenterVirtualNetwork{}

type dataSourceDatacenterVirtualNetwork struct {
client *apstra.Client
}

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

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

func (o *dataSourceDatacenterVirtualNetwork) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: docCategoryDatacenter + "This resource returns details of a Virtual Network within a Datacenter Blueprint.\n\n" +
"At least one optional attribute is required.",
Attributes: blueprint.DatacenterVirtualNetwork{}.DataSourceAttributes(),
}
}

func (o *dataSourceDatacenterVirtualNetwork) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
// Retrieve values from config.
var config blueprint.DatacenterVirtualNetwork
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}

bp, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(config.BlueprintId.ValueString()))
if err != nil {
if utils.IsApstra404(err) {
resp.Diagnostics.AddError(fmt.Sprintf("blueprint %s not found",
config.BlueprintId), err.Error())
return
}
resp.Diagnostics.AddError(fmt.Sprintf(blueprint.ErrDCBlueprintCreate, config.BlueprintId), err.Error())
return
}

var api *apstra.VirtualNetwork
switch {
case !config.Name.IsNull():
api, err = bp.GetVirtualNetworkByName(ctx, config.Name.ValueString())
if utils.IsApstra404(err) {
resp.Diagnostics.AddAttributeError(
path.Root("name"),
"VirtualNetwork not found",
fmt.Sprintf("VirtualNetwork with label %s not found", config.Name))
return
}
case !config.Id.IsNull():
api, err = bp.GetVirtualNetwork(ctx, apstra.ObjectId(config.Id.ValueString()))
if utils.IsApstra404(err) {
resp.Diagnostics.AddAttributeError(
path.Root("id"),
"VirtualNetwork not found",
fmt.Sprintf("VirtualNetwork with ID %s not found", config.Id))
return
}
}
if err != nil {
resp.Diagnostics.AddError("Failed reading VirtualNetwork", err.Error())
return
}

// load the API response and set the state
config.Id = types.StringValue(api.Id.String())
config.LoadApiData(ctx, api.Data, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

// set state
resp.Diagnostics.Append(resp.State.Set(ctx, &config)...)
}
135 changes: 135 additions & 0 deletions apstra/data_source_datacenter_virtual_network_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package tfapstra_test

import (
"context"
"errors"
"fmt"
"github.com/Juniper/apstra-go-sdk/apstra"
testutils "github.com/Juniper/terraform-provider-apstra/apstra/test_utils"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"testing"
)

const (
dataSourceDataCenterVirtualNetworkByIdHcl = `
data "apstra_datacenter_virtual_network" "test" {
blueprint_id = "%s"
id = "%s"
}
`

dataSourceDataCenterVirtualNetworkByNameHcl = `
data "apstra_datacenter_virtual_network" "test" {
blueprint_id = "%s"
name = "%s"
}
`
)

func TestDatacenterVirtualNetwork(t *testing.T) {
ctx := context.Background()

// create a test blueprint
bp, bpDelete, err := testutils.BlueprintA(ctx)
if err != nil {
t.Fatal(errors.Join(err, bpDelete(ctx)))
}
defer func() {
err = bpDelete(ctx)
if err != nil {
t.Error(err)
}
}()

// create a security zone within the blueprint
name := acctest.RandString(5)
zoneId, err := bp.CreateSecurityZone(ctx, &apstra.SecurityZoneData{
SzType: apstra.SecurityZoneTypeEVPN,
VrfName: name,
Label: name,
})
if err != nil {
t.Fatal(err)
}

// grab some data we'll need when creating virtual networks
leafIdStrings := systemIds(ctx, t, bp, "leaf")
vnBindings := make([]apstra.VnBinding, len(leafIdStrings))
for i, id := range leafIdStrings {
vnBindings[i] = apstra.VnBinding{SystemId: apstra.ObjectId(id)}
}

// specify virtual networks we want to create (and ultimately test the data source against)
virtualNetworks := []apstra.VirtualNetwork{
{
Data: &apstra.VirtualNetworkData{
Ipv4Enabled: true,
Ipv4Subnet: randIpv4NetMust(t, "10.0.0.0/16"),
Label: acctest.RandString(5),
SecurityZoneId: zoneId,
VnType: apstra.VnTypeVxlan,
VnBindings: vnBindings,
},
},
{
Data: &apstra.VirtualNetworkData{
Ipv4Enabled: true,
Ipv4Subnet: randIpv4NetMust(t, "10.1.0.0/16"),
Label: acctest.RandString(5),
SecurityZoneId: zoneId,
VnType: apstra.VnTypeVlan,
VnBindings: []apstra.VnBinding{{SystemId: apstra.ObjectId(leafIdStrings[0])}},
},
},
}

// create the test virtual networks
for i := range virtualNetworks {
virtualNetworks[i].Id, err = bp.CreateVirtualNetwork(ctx, virtualNetworks[i].Data)
if err != nil {
t.Fatal(err)
}
}

genTestCheckFuncs := func(vn apstra.VirtualNetwork) []resource.TestCheckFunc {
result := []resource.TestCheckFunc{
resource.TestCheckResourceAttr("data.apstra_datacenter_virtual_network.test", "id", vn.Id.String()),
resource.TestCheckResourceAttr("data.apstra_datacenter_virtual_network.test", "blueprint_id", bp.Id().String()),
resource.TestCheckResourceAttr("data.apstra_datacenter_virtual_network.test", "name", vn.Data.Label),
resource.TestCheckResourceAttr("data.apstra_datacenter_virtual_network.test", "type", vn.Data.VnType.String()),
resource.TestCheckResourceAttr("data.apstra_datacenter_virtual_network.test", "ipv4_connectivity_enabled", fmt.Sprintf("%t", vn.Data.Ipv4Enabled)),
resource.TestCheckResourceAttr("data.apstra_datacenter_virtual_network.test", "ipv4_virtual_gateway_enabled", fmt.Sprintf("%t", vn.Data.VirtualGatewayIpv4Enabled)),
resource.TestCheckResourceAttr("data.apstra_datacenter_virtual_network.test", "ipv6_connectivity_enabled", fmt.Sprintf("%t", vn.Data.Ipv6Enabled)),
resource.TestCheckResourceAttr("data.apstra_datacenter_virtual_network.test", "ipv6_virtual_gateway_enabled", fmt.Sprintf("%t", vn.Data.VirtualGatewayIpv6Enabled)),
resource.TestCheckResourceAttr("data.apstra_datacenter_virtual_network.test", "bindings.%", fmt.Sprintf("%d", len(vn.Data.VnBindings))),
}
return result
}

testCheckFuncsByVnId := make(map[apstra.ObjectId][]resource.TestCheckFunc, len(virtualNetworks))
for _, virtualNetwork := range virtualNetworks {
testCheckFuncsByVnId[virtualNetwork.Id] = genTestCheckFuncs(virtualNetwork)
}

stepsById := make([]resource.TestStep, len(virtualNetworks))
for i, virtualNetwork := range virtualNetworks {
stepsById[i] = resource.TestStep{
Config: insecureProviderConfigHCL + fmt.Sprintf(dataSourceDataCenterVirtualNetworkByIdHcl, bp.Id(), virtualNetwork.Id),
Check: resource.ComposeAggregateTestCheckFunc(testCheckFuncsByVnId[virtualNetwork.Id]...),
}
}

stepsByName := make([]resource.TestStep, len(virtualNetworks))
for i, virtualNetwork := range virtualNetworks {
stepsByName[i] = resource.TestStep{
Config: insecureProviderConfigHCL + fmt.Sprintf(dataSourceDataCenterVirtualNetworkByNameHcl, bp.Id(), virtualNetwork.Data.Label),
Check: resource.ComposeAggregateTestCheckFunc(genTestCheckFuncs(virtualNetwork)...),
}
}

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: append(stepsById, stepsByName...),
})
}
Loading