From d7e1f6d151b8262b949592246d7ec5f3a46ea5d7 Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Sun, 20 Oct 2024 01:37:35 -0400 Subject: [PATCH] add switch name validation to rack_type resource --- apstra/design/rack_type.go | 37 ++++++++------- apstra/resource_rack_type.go | 92 ++++++++++++++++++++++++++++++++++-- 2 files changed, 108 insertions(+), 21 deletions(-) diff --git a/apstra/design/rack_type.go b/apstra/design/rack_type.go index 4f389fa7..ac45aefc 100644 --- a/apstra/design/rack_type.go +++ b/apstra/design/rack_type.go @@ -3,6 +3,9 @@ package design import ( "context" "fmt" + "sort" + "strings" + "github.com/Juniper/apstra-go-sdk/apstra" "github.com/Juniper/terraform-provider-apstra/apstra/utils" "github.com/hashicorp/terraform-plugin-framework-validators/mapvalidator" @@ -16,8 +19,6 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" - "sort" - "strings" ) type RackType struct { @@ -270,17 +271,17 @@ func (o *RackType) Request(ctx context.Context, diags *diag.Diagnostics) *apstra return nil } - leafSwitches := o.leafSwitches(ctx, diags) + leafSwitches := o.LeafSwitchMap(ctx, diags) if diags.HasError() { return nil } - accessSwitches := o.accessSwitches(ctx, diags) + accessSwitches := o.AccessSwitchMap(ctx, diags) if diags.HasError() { return nil } - genericSystems := o.genericSystems(ctx, diags) + genericSystems := o.GenericSystemMap(ctx, diags) if diags.HasError() { return nil } @@ -397,7 +398,7 @@ func ValidateRackType(ctx context.Context, in *apstra.RackType, diags *diag.Diag } } -func (o *RackType) leafSwitches(ctx context.Context, diags *diag.Diagnostics) map[string]LeafSwitch { +func (o *RackType) LeafSwitchMap(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...) @@ -409,7 +410,7 @@ func (o *RackType) leafSwitches(ctx context.Context, diags *diag.Diagnostics) ma } func (o *RackType) leafSwitchByName(ctx context.Context, requested string, diags *diag.Diagnostics) *LeafSwitch { - leafSwitches := o.leafSwitches(ctx, diags) + leafSwitches := o.LeafSwitchMap(ctx, diags) if diags.HasError() { return nil } @@ -421,7 +422,7 @@ func (o *RackType) leafSwitchByName(ctx context.Context, requested string, diags return nil } -func (o *RackType) accessSwitches(ctx context.Context, diags *diag.Diagnostics) map[string]AccessSwitch { +func (o *RackType) AccessSwitchMap(ctx context.Context, diags *diag.Diagnostics) map[string]AccessSwitch { accessSwitches := make(map[string]AccessSwitch, len(o.AccessSwitches.Elements())) d := o.AccessSwitches.ElementsAs(ctx, &accessSwitches, false) diags.Append(d...) @@ -433,7 +434,7 @@ func (o *RackType) accessSwitches(ctx context.Context, diags *diag.Diagnostics) } func (o *RackType) accessSwitchByName(ctx context.Context, requested string, diags *diag.Diagnostics) *AccessSwitch { - accessSwitches := o.accessSwitches(ctx, diags) + accessSwitches := o.AccessSwitchMap(ctx, diags) if diags.HasError() { return nil } @@ -445,7 +446,7 @@ func (o *RackType) accessSwitchByName(ctx context.Context, requested string, dia return nil } -func (o *RackType) genericSystems(ctx context.Context, diags *diag.Diagnostics) map[string]GenericSystem { +func (o *RackType) GenericSystemMap(ctx context.Context, diags *diag.Diagnostics) map[string]GenericSystem { genericSystems := make(map[string]GenericSystem, len(o.GenericSystems.Elements())) d := o.GenericSystems.ElementsAs(ctx, &genericSystems, true) diags.Append(d...) @@ -457,12 +458,12 @@ func (o *RackType) genericSystems(ctx context.Context, diags *diag.Diagnostics) } //func (o *RackType) genericSystemByName(ctx context.Context, requested string, diags *diag.Diagnostics) *GenericSystem { -// genericSystems := o.genericSystems(ctx, diags) +// GenericSystemMap := o.GenericSystemMap(ctx, diags) // if diags.HasError() { // return nil // } // -// if gs, ok := genericSystems[requested]; ok { +// if gs, ok := GenericSystemMap[requested]; ok { // return &gs // } // @@ -474,13 +475,13 @@ func (o *RackType) genericSystems(ctx context.Context, diags *diag.Diagnostics) // RackType to be used as state. func (o *RackType) CopyWriteOnlyElements(ctx context.Context, src *RackType, diags *diag.Diagnostics) { // first extract native go structs from the TF set of objects - dstLeafSwitches := o.leafSwitches(ctx, diags) - dstAccessSwitches := o.accessSwitches(ctx, diags) - dstGenericSystems := o.genericSystems(ctx, diags) + dstLeafSwitches := o.LeafSwitchMap(ctx, diags) + dstAccessSwitches := o.AccessSwitchMap(ctx, diags) + dstGenericSystems := o.GenericSystemMap(ctx, diags) // invoke the CopyWriteOnlyElements on every leaf switch object for name, dstLeafSwitch := range dstLeafSwitches { - srcLeafSwitch, ok := src.leafSwitches(ctx, diags)[name] + srcLeafSwitch, ok := src.LeafSwitchMap(ctx, diags)[name] if !ok { continue } @@ -497,7 +498,7 @@ func (o *RackType) CopyWriteOnlyElements(ctx context.Context, src *RackType, dia // invoke the CopyWriteOnlyElements on every access switch object for name, dstAccessSwitch := range dstAccessSwitches { - srcAccessSwitch, ok := src.accessSwitches(ctx, diags)[name] + srcAccessSwitch, ok := src.AccessSwitchMap(ctx, diags)[name] if !ok { continue } @@ -514,7 +515,7 @@ func (o *RackType) CopyWriteOnlyElements(ctx context.Context, src *RackType, dia // invoke the CopyWriteOnlyElements on every generic system object for name, dstGenericSystem := range dstGenericSystems { - srcGenericSystem, ok := src.genericSystems(ctx, diags)[name] + srcGenericSystem, ok := src.GenericSystemMap(ctx, diags)[name] if !ok { continue } diff --git a/apstra/resource_rack_type.go b/apstra/resource_rack_type.go index 7364cd2e..ca9386ca 100644 --- a/apstra/resource_rack_type.go +++ b/apstra/resource_rack_type.go @@ -2,16 +2,22 @@ package tfapstra import ( "context" + "fmt" + "github.com/Juniper/apstra-go-sdk/apstra" "github.com/Juniper/terraform-provider-apstra/apstra/design" "github.com/Juniper/terraform-provider-apstra/apstra/utils" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/types" ) -var _ resource.ResourceWithConfigure = &resourceRackType{} -var _ resourceWithSetClient = &resourceRackType{} +var ( + _ resource.ResourceWithConfigure = &resourceRackType{} + _ resource.ResourceWithValidateConfig = &resourceRackType{} + _ resourceWithSetClient = &resourceRackType{} +) type resourceRackType struct { client *apstra.Client @@ -32,6 +38,87 @@ func (o *resourceRackType) Schema(_ context.Context, _ resource.SchemaRequest, r } } +func (o *resourceRackType) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + // Retrieve values from config + var config design.RackType + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + if config.LeafSwitches.IsUnknown() || config.AccessSwitches.IsUnknown() || config.GenericSystems.IsUnknown() { + return // cannot proceed + } + + leafSwitches := config.LeafSwitchMap(ctx, &resp.Diagnostics) + accessSwitches := config.AccessSwitchMap(ctx, &resp.Diagnostics) + genericSystems := config.GenericSystemMap(ctx, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + for accessSwitchName, accessSwitch := range accessSwitches { + if _, ok := leafSwitches[accessSwitchName]; ok { + resp.Diagnostics.AddAttributeError( + path.Root("access_switches").AtMapKey(accessSwitchName), + errInvalidConfig, + fmt.Sprintf("switch names must be unique - cannot have leaf and switches named %q", accessSwitchName), + ) + } + + if accessSwitch.Links.IsUnknown() { + return // cannot proceed + } + + links := accessSwitch.GetLinks(ctx, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + for linkName, link := range links { + if link.TargetSwitchName.IsUnknown() { + return // cannot proceed + } + + _, leafSwitchExists := leafSwitches[link.TargetSwitchName.ValueString()] + if !leafSwitchExists { + resp.Diagnostics.AddAttributeError( + path.Root("access_switches").AtMapKey(accessSwitchName).AtName("links").AtMapKey(linkName), + errInvalidConfig, + fmt.Sprintf("switch named %q is not among the declared leaf switches", link.TargetSwitchName.ValueString()), + ) + } + } + } + + for genericSystemName, genericSystem := range genericSystems { + if genericSystem.Links.IsUnknown() { + return // cannot proceed + } + + links := genericSystem.GetLinks(ctx, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + for linkName, link := range links { + if link.TargetSwitchName.IsUnknown() { + return // cannot proceed + } + + _, leafSwitchExists := leafSwitches[link.TargetSwitchName.ValueString()] + _, accessSwitchExists := accessSwitches[link.TargetSwitchName.ValueString()] + if !leafSwitchExists && !accessSwitchExists { + resp.Diagnostics.AddAttributeError( + path.Root("generic_systems").AtMapKey(genericSystemName).AtName("links").AtMapKey(linkName), + errInvalidConfig, + fmt.Sprintf("switch named %q is not among the declared leaf or access switches", link.TargetSwitchName.ValueString()), + ) + } + } + } +} + func (o *resourceRackType) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // Retrieve values from plan var plan design.RackType @@ -186,7 +273,6 @@ func (o *resourceRackType) Delete(ctx context.Context, req resource.DeleteReques resp.Diagnostics.AddError("error deleting Rack Type", err.Error()) return } - } func (o *resourceRackType) setClient(client *apstra.Client) {