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

WIP: Change eliminate maps from rack_type schema - step 2 leafs #338

Open
wants to merge 5 commits into
base: task/335-rack-type-links-as-sets
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
65 changes: 31 additions & 34 deletions apstra/design/rack_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
dataSourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
Expand All @@ -24,7 +25,7 @@ type RackType struct {
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
FabricConnectivityDesign types.String `tfsdk:"fabric_connectivity_design"`
LeafSwitches types.Map `tfsdk:"leaf_switches"`
LeafSwitches types.Set `tfsdk:"leaf_switches"`
AccessSwitches types.Map `tfsdk:"access_switches"`
GenericSystems types.Map `tfsdk:"generic_systems"`
}
Expand Down Expand Up @@ -57,8 +58,8 @@ func (o RackType) DataSourceAttributes() map[string]dataSourceSchema.Attribute {
MarkdownDescription: "Indicates designs for which this Rack Type is intended.",
Computed: true,
},
"leaf_switches": dataSourceSchema.MapNestedAttribute{
MarkdownDescription: "A map of Leaf Switches in this Rack Type, keyed by name.",
"leaf_switches": dataSourceSchema.SetNestedAttribute{
MarkdownDescription: "A set of Leaf Switches in this Rack Type.",
Computed: true,
NestedObject: dataSourceSchema.NestedAttributeObject{
Attributes: LeafSwitch{}.DataSourceAttributes(),
Expand Down Expand Up @@ -99,8 +100,8 @@ func (o RackType) DataSourceAttributesNested() map[string]dataSourceSchema.Attri
MarkdownDescription: "Indicates designs for which this Rack Type is intended.",
Computed: true,
},
"leaf_switches": dataSourceSchema.MapNestedAttribute{
MarkdownDescription: "A map of Leaf Switches in this Rack Type, keyed by name.",
"leaf_switches": dataSourceSchema.SetNestedAttribute{
MarkdownDescription: "A set of Leaf Switches in this Rack Type.",
Computed: true,
NestedObject: dataSourceSchema.NestedAttributeObject{
Attributes: LeafSwitch{}.DataSourceAttributes(),
Expand Down Expand Up @@ -146,10 +147,10 @@ func (o RackType) ResourceAttributes() map[string]resourceSchema.Attribute {
Validators: []validator.String{stringvalidator.OneOf(utils.FcdModes()...)},
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
},
"leaf_switches": resourceSchema.MapNestedAttribute{
"leaf_switches": resourceSchema.SetNestedAttribute{
MarkdownDescription: "Each Rack Type is required to have at least one Leaf Switch.",
Required: true,
Validators: []validator.Map{mapvalidator.SizeAtLeast(1)},
Validators: []validator.Set{setvalidator.SizeAtLeast(1)},
NestedObject: resourceSchema.NestedAttributeObject{
Attributes: LeafSwitch{}.ResourceAttributes(),
},
Expand Down Expand Up @@ -192,8 +193,8 @@ func (o RackType) ResourceAttributesNested() map[string]resourceSchema.Attribute
MarkdownDescription: fmt.Sprintf("Must be one of '%s'.", strings.Join(utils.FcdModes(), "', '")),
Computed: true,
},
"leaf_switches": resourceSchema.MapNestedAttribute{
MarkdownDescription: "Each Rack Type is required to have at least one Leaf Switch.",
"leaf_switches": resourceSchema.SetNestedAttribute{
MarkdownDescription: "A set of Leaf Switches in this Rack Type.",
Computed: true,
NestedObject: resourceSchema.NestedAttributeObject{
Attributes: LeafSwitch{}.ResourceAttributesNested(),
Expand Down Expand Up @@ -223,7 +224,7 @@ func (o RackType) AttrTypes() map[string]attr.Type {
"name": types.StringType,
"description": types.StringType,
"fabric_connectivity_design": types.StringType,
"leaf_switches": types.MapType{ElemType: types.ObjectType{AttrTypes: LeafSwitch{}.AttrTypes()}},
"leaf_switches": types.SetType{ElemType: types.ObjectType{AttrTypes: LeafSwitch{}.AttrTypes()}},
"access_switches": types.MapType{ElemType: types.ObjectType{AttrTypes: AccessSwitch{}.AttrTypes()}},
"generic_systems": types.MapType{ElemType: types.ObjectType{AttrTypes: GenericSystem{}.AttrTypes()}},
}
Expand All @@ -247,7 +248,7 @@ func (o *RackType) LoadApiData(ctx context.Context, in *apstra.RackTypeData, dia
o.Name = types.StringValue(in.DisplayName)
o.Description = utils.StringValueOrNull(ctx, in.Description, diags)
o.FabricConnectivityDesign = types.StringValue(in.FabricConnectivityDesign.String())
o.LeafSwitches = NewLeafSwitchMap(ctx, in.LeafSwitches, in.FabricConnectivityDesign, diags)
o.LeafSwitches = NewLeafSwitchSet(ctx, in.LeafSwitches, in.FabricConnectivityDesign, diags)
o.AccessSwitches = NewAccessSwitchMap(ctx, in.AccessSwitches, diags)
o.GenericSystems = NewGenericSystemMap(ctx, in.GenericSystems, diags)
}
Expand Down Expand Up @@ -284,20 +285,16 @@ func (o *RackType) Request(ctx context.Context, diags *diag.Diagnostics) *apstra
return nil
}

var i int

leafSwitchRequests := make([]apstra.RackElementLeafSwitchRequest, len(leafSwitches))
i = 0
for name, ls := range leafSwitches {
req := ls.Request(ctx, path.Root("leaf_switches").AtMapKey(name), fcd, diags)
for i, ls := range leafSwitches {
leafSwitchRequests[i] = *ls.Request(ctx, path.Root("leaf_switches").AtListIndex(i), fcd, diags)
if diags.HasError() {
return nil
}
req.Label = name
leafSwitchRequests[i] = *req
i++
}

var i int

accessSwitchRequests := make([]apstra.RackElementAccessSwitchRequest, len(accessSwitches))
i = 0
for name, as := range accessSwitches {
Expand Down Expand Up @@ -385,10 +382,9 @@ func ValidateRackType(ctx context.Context, in *apstra.RackType, diags *diag.Diag
}
}

func (o *RackType) GetLeafSwitches(ctx context.Context, diags *diag.Diagnostics) map[string]LeafSwitch {
leafSwitches := make(map[string]LeafSwitch, len(o.LeafSwitches.Elements()))
d := o.LeafSwitches.ElementsAs(ctx, &leafSwitches, false)
diags.Append(d...)
func (o *RackType) GetLeafSwitches(ctx context.Context, diags *diag.Diagnostics) []LeafSwitch {
var leafSwitches []LeafSwitch
diags.Append(o.LeafSwitches.ElementsAs(ctx, &leafSwitches, false)...)
if diags.HasError() {
return nil
}
Expand All @@ -402,8 +398,10 @@ func (o *RackType) GetLeafSwitchByName(ctx context.Context, requested string, di
return nil
}

if ls, ok := leafSwitches[requested]; ok {
return &ls
for _, leafSwitch := range leafSwitches {
if leafSwitch.Name.ValueString() == requested {
return &leafSwitch
}
}

return nil
Expand Down Expand Up @@ -467,20 +465,19 @@ func (o *RackType) CopyWriteOnlyElements(ctx context.Context, src *RackType, dia
dstGenericSystems := o.GetGenericSystems(ctx, diags)

// invoke the CopyWriteOnlyElements on every leaf switch object
for name, dstLeafSwitch := range dstLeafSwitches {
srcLeafSwitch, ok := src.GetLeafSwitches(ctx, diags)[name]
if !ok {
continue
}
for i := range dstLeafSwitches {
srcLeafSwitch := src.GetLeafSwitchByName(ctx, dstLeafSwitches[i].Name.ValueString(), diags)
if diags.HasError() {
return
}
if srcLeafSwitch == nil {
continue
}

dstLeafSwitch.CopyWriteOnlyElements(ctx, &srcLeafSwitch, diags)
dstLeafSwitches[i].CopyWriteOnlyElements(ctx, srcLeafSwitch, diags)
if diags.HasError() {
return
}
dstLeafSwitches[name] = dstLeafSwitch
}

// invoke the CopyWriteOnlyElements on every access switch object
Expand Down Expand Up @@ -518,15 +515,15 @@ func (o *RackType) CopyWriteOnlyElements(ctx context.Context, src *RackType, dia
}

// transform the native go objects (with copied object IDs) back to TF set
leafSwitchMap := utils.MapValueOrNull(ctx, types.ObjectType{AttrTypes: LeafSwitch{}.AttrTypes()}, dstLeafSwitches, diags)
leafSwitchSet := utils.SetValueOrNull(ctx, types.ObjectType{AttrTypes: LeafSwitch{}.AttrTypes()}, dstLeafSwitches, diags)
accessSwitchMap := utils.MapValueOrNull(ctx, types.ObjectType{AttrTypes: AccessSwitch{}.AttrTypes()}, dstAccessSwitches, diags)
genericSystemMap := utils.MapValueOrNull(ctx, types.ObjectType{AttrTypes: GenericSystem{}.AttrTypes()}, dstGenericSystems, diags)
if diags.HasError() {
return
}

// save the TF sets into RackType
o.LeafSwitches = leafSwitchMap
o.LeafSwitches = leafSwitchSet
o.AccessSwitches = accessSwitchMap
o.GenericSystems = genericSystemMap
}
Expand Down
40 changes: 30 additions & 10 deletions apstra/design/rack_type_leaf_switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import (
"github.com/hashicorp/terraform-plugin-framework/diag"
"github.com/hashicorp/terraform-plugin-framework/path"
resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
Expand All @@ -37,6 +40,7 @@ func ValidateLeafSwitch(rt *apstra.RackType, i int, diags *diag.Diagnostics) {
}

type LeafSwitch struct {
Name types.String `tfsdk:"name"`
LogicalDeviceId types.String `tfsdk:"logical_device_id"`
LogicalDevice types.Object `tfsdk:"logical_device"`
MlagInfo types.Object `tfsdk:"mlag_info"`
Expand All @@ -49,6 +53,10 @@ type LeafSwitch struct {

func (o LeafSwitch) DataSourceAttributes() map[string]dataSourceSchema.Attribute {
return map[string]dataSourceSchema.Attribute{
"name": dataSourceSchema.StringAttribute{
MarkdownDescription: "Leaf Switch name.",
Computed: true,
},
"logical_device_id": dataSourceSchema.StringAttribute{
MarkdownDescription: "ID will always be `<null>` in data source contexts.",
Computed: true,
Expand Down Expand Up @@ -92,6 +100,11 @@ func (o LeafSwitch) DataSourceAttributes() map[string]dataSourceSchema.Attribute

func (o LeafSwitch) ResourceAttributes() map[string]resourceSchema.Attribute {
return map[string]resourceSchema.Attribute{
"name": dataSourceSchema.StringAttribute{
MarkdownDescription: "Leaf Switch name.",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"logical_device_id": resourceSchema.StringAttribute{
MarkdownDescription: "Apstra Object ID of the Logical Device used to model this Leaf Switch.",
Required: true,
Expand All @@ -100,6 +113,7 @@ func (o LeafSwitch) ResourceAttributes() map[string]resourceSchema.Attribute {
"logical_device": resourceSchema.SingleNestedAttribute{
MarkdownDescription: "Logical Device attributes cloned from the Global Catalog at creation time.",
Computed: true,
PlanModifiers: []planmodifier.Object{objectplanmodifier.UseStateForUnknown()},
Attributes: LogicalDevice{}.ResourceAttributesNested(),
},
"mlag_info": resourceSchema.SingleNestedAttribute{
Expand Down Expand Up @@ -150,6 +164,7 @@ func (o LeafSwitch) ResourceAttributes() map[string]resourceSchema.Attribute {
"tags": resourceSchema.SetNestedAttribute{
MarkdownDescription: "Set of Tags (Name + Description) applied to this Leaf Switch",
Computed: true,
PlanModifiers: []planmodifier.Set{setplanmodifier.UseStateForUnknown()},
NestedObject: resourceSchema.NestedAttributeObject{
Attributes: Tag{}.ResourceAttributesNested(),
},
Expand All @@ -159,6 +174,10 @@ func (o LeafSwitch) ResourceAttributes() map[string]resourceSchema.Attribute {

func (o LeafSwitch) ResourceAttributesNested() map[string]resourceSchema.Attribute {
return map[string]resourceSchema.Attribute{
"name": resourceSchema.StringAttribute{
MarkdownDescription: "Leaf Switch name.",
Computed: true,
},
"logical_device_id": resourceSchema.StringAttribute{
MarkdownDescription: "ID will always be `<null>` in nested contexts.",
Computed: true,
Expand Down Expand Up @@ -205,6 +224,7 @@ func (o LeafSwitch) ResourceAttributesNested() map[string]resourceSchema.Attribu

func (o LeafSwitch) AttrTypes() map[string]attr.Type {
return map[string]attr.Type{
"name": types.StringType,
"logical_device_id": types.StringType,
"logical_device": types.ObjectType{AttrTypes: LogicalDevice{}.AttrTypes()},
"mlag_info": types.ObjectType{AttrTypes: MlagInfo{}.AttrTypes()},
Expand Down Expand Up @@ -258,6 +278,7 @@ func (o *LeafSwitch) Request(ctx context.Context, path path.Path, fcd apstra.Fab
o.TagIds.ElementsAs(ctx, &tagIds, false)

return &apstra.RackElementLeafSwitchRequest{
Label: o.Name.ValueString(),
MlagInfo: leafMlagInfo,
LinkPerSpineCount: linkPerSpineCount,
LinkPerSpineSpeed: linkPerSpineSpeed,
Expand All @@ -268,6 +289,7 @@ func (o *LeafSwitch) Request(ctx context.Context, path path.Path, fcd apstra.Fab
}

func (o *LeafSwitch) LoadApiData(ctx context.Context, in *apstra.RackElementLeafSwitch, fcd apstra.FabricConnectivityDesign, diags *diag.Diagnostics) {
o.Name = types.StringValue(in.Label)
o.LogicalDeviceId = types.StringNull()
o.LogicalDevice = NewLogicalDeviceObject(ctx, in.LogicalDevice, diags)

Expand Down Expand Up @@ -305,18 +327,16 @@ func (o *LeafSwitch) CopyWriteOnlyElements(ctx context.Context, src *LeafSwitch,
o.TagIds = utils.SetValueOrNull(ctx, types.StringType, src.TagIds.Elements(), diags)
}

func NewLeafSwitchMap(ctx context.Context, in []apstra.RackElementLeafSwitch, fcd apstra.FabricConnectivityDesign, diags *diag.Diagnostics) types.Map {
leafSwitches := make(map[string]LeafSwitch, len(in))
for _, leafIn := range in {
var ls LeafSwitch
ls.LoadApiData(ctx, &leafIn, fcd, diags)
leafSwitches[leafIn.Label] = ls
if diags.HasError() {
return types.MapNull(types.ObjectType{AttrTypes: LeafSwitch{}.AttrTypes()})
}
func NewLeafSwitchSet(ctx context.Context, in []apstra.RackElementLeafSwitch, fcd apstra.FabricConnectivityDesign, diags *diag.Diagnostics) types.Set {
leafSwitches := make([]LeafSwitch, len(in))
for i, leafIn := range in {
leafSwitches[i].LoadApiData(ctx, &leafIn, fcd, diags)
}
if diags.HasError() {
return types.SetNull(types.ObjectType{AttrTypes: LeafSwitch{}.AttrTypes()})
}

return utils.MapValueOrNull(ctx, types.ObjectType{AttrTypes: LeafSwitch{}.AttrTypes()}, leafSwitches, diags)
return utils.SetValueOrNull(ctx, types.ObjectType{AttrTypes: LeafSwitch{}.AttrTypes()}, leafSwitches, diags)
}

// LeafRedundancyModes returns permitted fabric_connectivity_design mode strings
Expand Down
30 changes: 29 additions & 1 deletion apstra/resource_rack_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
)

var _ resource.ResourceWithConfigure = &resourceRackType{}
Expand Down Expand Up @@ -42,6 +43,33 @@ func (o *resourceRackType) ValidateConfig(ctx context.Context, req resource.Vali
return
}

// leaf switches must have a value
if config.LeafSwitches.IsUnknown() {
return // cannot proceed
}

// check each leaf switch
leafSwitchNameMap := make(map[string]bool)
for _, leafSwitchVal := range config.LeafSwitches.Elements() {
if leafSwitchVal.IsUnknown() {
return // cannot proceed
}

var leafSwitch design.LeafSwitch
leafSwitchVal.(basetypes.ObjectValue).As(ctx, &leafSwitch, basetypes.ObjectAsOptions{})
if leafSwitch.Name.IsUnknown() {
return // cannot proceed
}

if leafSwitchNameMap[leafSwitch.Name.ValueString()] {
resp.Diagnostics.AddAttributeError(
path.Root("leaf_switches"), "Leaf Switch must be unique",
fmt.Sprintf("Rack has multiple Leaf Switches with name %q", leafSwitch.Name.ValueString()))
} else {
leafSwitchNameMap[leafSwitch.Name.ValueString()] = true
}
}

// access switches must have a value
if config.AccessSwitches.IsUnknown() {
return // cannot proceed
Expand Down Expand Up @@ -255,7 +283,7 @@ func (o *resourceRackType) Read(ctx context.Context, req resource.ReadRequest, r
newState.CopyWriteOnlyElements(ctx, &state, &resp.Diagnostics)

// set state
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}

func (o *resourceRackType) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
Expand Down
3 changes: 2 additions & 1 deletion docs/data-sources/rack_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ output "rack_types_with_40_or_more_generic_systems_by_ID" {
- `description` (String) Rack Type description displayed in the Apstra web UI.
- `fabric_connectivity_design` (String) Indicates designs for which this Rack Type is intended.
- `generic_systems` (Attributes Map) A map of Generic Systems in the Rack Type, keyed by name. (see [below for nested schema](#nestedatt--generic_systems))
- `leaf_switches` (Attributes Map) A map of Leaf Switches in this Rack Type, keyed by name. (see [below for nested schema](#nestedatt--leaf_switches))
- `leaf_switches` (Attributes Set) A set of Leaf Switches in this Rack Type. (see [below for nested schema](#nestedatt--leaf_switches))

<a id="nestedatt--access_switches"></a>
### Nested Schema for `access_switches`
Expand Down Expand Up @@ -235,6 +235,7 @@ Read-Only:
- `logical_device` (Attributes) Logical Device attributes as represented in the Global Catalog. (see [below for nested schema](#nestedatt--leaf_switches--logical_device))
- `logical_device_id` (String) ID will always be `<null>` in data source contexts.
- `mlag_info` (Attributes) Details settings when the Leaf Switch is an MLAG-capable pair. (see [below for nested schema](#nestedatt--leaf_switches--mlag_info))
- `name` (String) Leaf Switch name.
- `redundancy_protocol` (String) When set, 'the switch' is actually a LAG-capable redundant pair of the given type.
- `spine_link_count` (Number) Number of links to each Spine switch.
- `spine_link_speed` (String) Speed of links to Spine switches.
Expand Down
3 changes: 2 additions & 1 deletion docs/data-sources/template_rack_based.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Read-Only:
- `fabric_connectivity_design` (String) Indicates designs for which this Rack Type is intended.
- `generic_systems` (Attributes Map) A map of Generic Systems in the Rack Type, keyed by name. (see [below for nested schema](#nestedatt--rack_infos--rack_type--generic_systems))
- `id` (String) IDs will always be `<null>` in nested contexts.
- `leaf_switches` (Attributes Map) A map of Leaf Switches in this Rack Type, keyed by name. (see [below for nested schema](#nestedatt--rack_infos--rack_type--leaf_switches))
- `leaf_switches` (Attributes Set) A set of Leaf Switches in this Rack Type. (see [below for nested schema](#nestedatt--rack_infos--rack_type--leaf_switches))
- `name` (String) Rack Type name displayed in the Apstra web UI.

<a id="nestedatt--rack_infos--rack_type--access_switches"></a>
Expand Down Expand Up @@ -250,6 +250,7 @@ Read-Only:
- `logical_device` (Attributes) Logical Device attributes as represented in the Global Catalog. (see [below for nested schema](#nestedatt--rack_infos--rack_type--leaf_switches--logical_device))
- `logical_device_id` (String) ID will always be `<null>` in data source contexts.
- `mlag_info` (Attributes) Details settings when the Leaf Switch is an MLAG-capable pair. (see [below for nested schema](#nestedatt--rack_infos--rack_type--leaf_switches--mlag_info))
- `name` (String) Leaf Switch name.
- `redundancy_protocol` (String) When set, 'the switch' is actually a LAG-capable redundant pair of the given type.
- `spine_link_count` (Number) Number of links to each Spine switch.
- `spine_link_speed` (String) Speed of links to Spine switches.
Expand Down
3 changes: 2 additions & 1 deletion docs/resources/rack_type.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ resource "apstra_rack_type" "example" {
### Required

- `fabric_connectivity_design` (String) Must be one of 'l3clos', 'l3collapsed'.
- `leaf_switches` (Attributes Map) Each Rack Type is required to have at least one Leaf Switch. (see [below for nested schema](#nestedatt--leaf_switches))
- `leaf_switches` (Attributes Set) Each Rack Type is required to have at least one Leaf Switch. (see [below for nested schema](#nestedatt--leaf_switches))
- `name` (String) Rack Type name, displayed in the Apstra web UI.

### Optional
Expand All @@ -83,6 +83,7 @@ resource "apstra_rack_type" "example" {
Required:

- `logical_device_id` (String) Apstra Object ID of the Logical Device used to model this Leaf Switch.
- `name` (String) Leaf Switch name.

Optional:

Expand Down