diff --git a/apstra/design/rack_type.go b/apstra/design/rack_type.go index f5721c55..b31ab8e8 100644 --- a/apstra/design/rack_type.go +++ b/apstra/design/rack_type.go @@ -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" @@ -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"` } @@ -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(), @@ -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(), @@ -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(), }, @@ -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(), @@ -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()}}, } @@ -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) } @@ -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 { @@ -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 } @@ -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 @@ -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 @@ -518,7 +515,7 @@ 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() { @@ -526,7 +523,7 @@ func (o *RackType) CopyWriteOnlyElements(ctx context.Context, src *RackType, dia } // save the TF sets into RackType - o.LeafSwitches = leafSwitchMap + o.LeafSwitches = leafSwitchSet o.AccessSwitches = accessSwitchMap o.GenericSystems = genericSystemMap } diff --git a/apstra/design/rack_type_leaf_switch.go b/apstra/design/rack_type_leaf_switch.go index 7977804e..4f598a6f 100644 --- a/apstra/design/rack_type_leaf_switch.go +++ b/apstra/design/rack_type_leaf_switch.go @@ -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" @@ -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"` @@ -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 `` in data source contexts.", Computed: true, @@ -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, @@ -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{ @@ -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(), }, @@ -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 `` in nested contexts.", Computed: true, @@ -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()}, @@ -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, @@ -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) @@ -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 diff --git a/apstra/resource_rack_type.go b/apstra/resource_rack_type.go index 1c7eb6ee..9b3b1dc2 100644 --- a/apstra/resource_rack_type.go +++ b/apstra/resource_rack_type.go @@ -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{} @@ -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 @@ -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) { diff --git a/docs/data-sources/rack_type.md b/docs/data-sources/rack_type.md index e8455735..5c79f2f1 100644 --- a/docs/data-sources/rack_type.md +++ b/docs/data-sources/rack_type.md @@ -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)) ### Nested Schema for `access_switches` @@ -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 `` 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. diff --git a/docs/data-sources/template_rack_based.md b/docs/data-sources/template_rack_based.md index f30c7661..c2ef748e 100644 --- a/docs/data-sources/template_rack_based.md +++ b/docs/data-sources/template_rack_based.md @@ -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 `` 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. @@ -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 `` 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. diff --git a/docs/resources/rack_type.md b/docs/resources/rack_type.md index ffdafb3d..6bff2fa2 100644 --- a/docs/resources/rack_type.md +++ b/docs/resources/rack_type.md @@ -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 @@ -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: diff --git a/docs/resources/template_rack_based.md b/docs/resources/template_rack_based.md index 334e2043..d4b29618 100644 --- a/docs/resources/template_rack_based.md +++ b/docs/resources/template_rack_based.md @@ -81,7 +81,7 @@ Read-Only: - `fabric_connectivity_design` (String) Must be one of 'l3clos', 'l3collapsed'. - `generic_systems` (Attributes Map) Generic Systems are optional rack elements notmanaged by Apstra: Servers, routers, firewalls, etc... (see [below for nested schema](#nestedatt--rack_infos--rack_type--generic_systems)) - `id` (String) ID will always be `` in nested contexts. -- `leaf_switches` (Attributes Map) Each Rack Type is required to have at least one Leaf Switch. (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. @@ -279,6 +279,7 @@ Read-Only: - `logical_device` (Attributes) Logical Device attributes cloned from the Global Catalog at creation time. (see [below for nested schema](#nestedatt--rack_infos--rack_type--leaf_switches--logical_device)) - `logical_device_id` (String) ID will always be `` in nested contexts. - `mlag_info` (Attributes) Defines connectivity between MLAG peers when `redundancy_protocol` is set to `mlag`. (see [below for nested schema](#nestedatt--rack_infos--rack_type--leaf_switches--mlag_info)) +- `name` (String) Leaf Switch name. - `redundancy_protocol` (String) Enabling a redundancy protocol converts a single Leaf Switch into a LAG-capable switch pair. Must be one of 'esi', 'mlag'. - `spine_link_count` (Number) Links per Spine. - `spine_link_speed` (String) Speed of Spine-facing links, something like '10G'