From ae6ff709c8b428231ff7f33f648262e64f643eac Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Mon, 20 Nov 2023 11:32:10 -0500 Subject: [PATCH 1/3] centralize get/set deploy mode functions --- apstra/blueprint/device_allocation.go | 69 ----------------- .../resource_datacenter_device_allocation.go | 60 ++++++++++----- apstra/utils/node.go | 74 +++++++++++++++++++ apstra/utils/system_agents.go | 13 ---- 4 files changed, 115 insertions(+), 101 deletions(-) create mode 100644 apstra/utils/node.go delete mode 100644 apstra/utils/system_agents.go diff --git a/apstra/blueprint/device_allocation.go b/apstra/blueprint/device_allocation.go index 74b89de8..7fcb8cdd 100644 --- a/apstra/blueprint/device_allocation.go +++ b/apstra/blueprint/device_allocation.go @@ -642,72 +642,3 @@ func (o *DeviceAllocation) deviceProfileNodeIdFromDeviceKey(ctx context.Context, o.DeviceProfileNodeId = types.StringValue(result.Items[0].DeviceProfile.Id) } - -func (o *DeviceAllocation) SetNodeDeployMode(ctx context.Context, client *apstra.Client, diags *diag.Diagnostics) { - if o.DeployMode.IsNull() { - return - } - - if o.DeployMode.IsUnknown() { - o.GetNodeDeployMode(ctx, client, diags) - return - } - - var deployMode apstra.NodeDeployMode - err := utils.ApiStringerFromFriendlyString(&deployMode, o.DeployMode.ValueString()) - if err != nil { - diags.AddError(fmt.Sprintf("error parsing deploy mode %q", o.DeployMode.ValueString()), err.Error()) - return - } - - type patch struct { - Id string `json:"id"` - DeployMode *string `json:"deploy_mode"` - } - - if err != nil { - diags.AddError(fmt.Sprintf("error parsing deploy mode %q", o.DeployMode.ValueString()), err.Error()) - return - } - - var stringPtr *string - if deployMode == apstra.NodeDeployModeNone { - stringPtr = nil - } else { - s := deployMode.String() - stringPtr = &s - } - - setDeployMode := patch{ - Id: o.NodeId.ValueString(), - DeployMode: stringPtr, - } - deployModeResponse := patch{} - - err = client.PatchNode(ctx, apstra.ObjectId(o.BlueprintId.ValueString()), apstra.ObjectId(o.NodeId.ValueString()), &setDeployMode, &deployModeResponse) - if err != nil { - diags.AddError("error setting deploy mode", err.Error()) - return - } -} - -func (o *DeviceAllocation) GetNodeDeployMode(ctx context.Context, client *apstra.Client, diags *diag.Diagnostics) { - var node struct { - Id string `json:"id"` - Type string `json:"type"` - DeployMode string `json:"deploy_mode"` - } - err := client.GetNode(ctx, apstra.ObjectId(o.BlueprintId.ValueString()), apstra.ObjectId(o.NodeId.ValueString()), &node) - if err != nil { - diags.AddError("failed to fetch blueprint node", err.Error()) - } - - var deployMode apstra.NodeDeployMode - err = deployMode.FromString(node.DeployMode) - if err != nil { - diags.AddError(fmt.Sprintf("error parsing deploy mode %q", node.DeployMode), err.Error()) - return - } - - o.DeployMode = types.StringValue(utils.StringersToFriendlyString(deployMode)) -} diff --git a/apstra/resource_datacenter_device_allocation.go b/apstra/resource_datacenter_device_allocation.go index 156ee08f..cd8005e4 100644 --- a/apstra/resource_datacenter_device_allocation.go +++ b/apstra/resource_datacenter_device_allocation.go @@ -44,19 +44,19 @@ func (o *resourceDeviceAllocation) Create(ctx context.Context, req resource.Crea return } - // Ensure the blueprint exists. - if !utils.BlueprintExists(ctx, o.client, apstra.ObjectId(plan.BlueprintId.ValueString()), &resp.Diagnostics) { - if resp.Diagnostics.HasError() { + // create a client for the datacenter reference design + bp, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(plan.BlueprintId.ValueString())) + if err != nil { + if utils.IsApstra404(err) { + resp.Diagnostics.AddError(fmt.Sprintf("blueprint %s not found", plan.BlueprintId), err.Error()) return } - resp.Diagnostics.AddError("no such blueprint", fmt.Sprintf("blueprint %s not found", plan.BlueprintId)) - } - if resp.Diagnostics.HasError() { + resp.Diagnostics.AddError(fmt.Sprintf(blueprint.ErrDCBlueprintCreate, plan.BlueprintId), err.Error()) return } // Lock the blueprint mutex. - err := o.lockFunc(ctx, plan.BlueprintId.ValueString()) + err = o.lockFunc(ctx, plan.BlueprintId.ValueString()) if err != nil { resp.Diagnostics.AddError( fmt.Sprintf("error locking blueprint %q mutex", plan.BlueprintId.ValueString()), @@ -92,7 +92,13 @@ func (o *resourceDeviceAllocation) Create(ctx context.Context, req resource.Crea } } - plan.SetNodeDeployMode(ctx, o.client, &resp.Diagnostics) + switch { + case plan.DeployMode.IsNull(): // not expected with Optional+Computed, nothing to do here + case plan.DeployMode.IsUnknown(): // config is null, get the Computed value + plan.DeployMode = types.StringValue(utils.GetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), &resp.Diagnostics)) + default: // value provided via config + utils.SetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), plan.DeployMode.ValueString(), &resp.Diagnostics) + } if resp.Diagnostics.HasError() { return } @@ -112,15 +118,14 @@ func (o *resourceDeviceAllocation) Read(ctx context.Context, req resource.ReadRe previousInterfaceMapCatalogId := state.InitialInterfaceMapId previousInterfaceMapName := state.InterfaceMapName - // Ensure the blueprint still exists. - if !utils.BlueprintExists(ctx, o.client, apstra.ObjectId(state.BlueprintId.ValueString()), &resp.Diagnostics) { - if resp.Diagnostics.HasError() { + // create a client for the datacenter reference design + bp, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(state.BlueprintId.ValueString())) + if err != nil { + if utils.IsApstra404(err) { + resp.State.RemoveResource(ctx) return } - resp.State.RemoveResource(ctx) - return - } - if resp.Diagnostics.HasError() { + resp.Diagnostics.AddError(fmt.Sprintf(blueprint.ErrDCBlueprintCreate, state.BlueprintId), err.Error()) return } @@ -151,7 +156,7 @@ func (o *resourceDeviceAllocation) Read(ctx context.Context, req resource.ReadRe return } - state.GetNodeDeployMode(ctx, o.client, &resp.Diagnostics) + state.DeployMode = types.StringValue(utils.GetNodeDeployMode(ctx, bp, state.NodeId.ValueString(), &resp.Diagnostics)) if resp.Diagnostics.HasError() { return } @@ -200,8 +205,19 @@ func (o *resourceDeviceAllocation) Update(ctx context.Context, req resource.Upda return } + // create a client for the datacenter reference design + bp, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(plan.BlueprintId.ValueString())) + if err != nil { + if utils.IsApstra404(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError(fmt.Sprintf(blueprint.ErrDCBlueprintCreate, plan.BlueprintId), err.Error()) + return + } + // Lock the blueprint mutex. - err := o.lockFunc(ctx, plan.BlueprintId.ValueString()) + err = o.lockFunc(ctx, plan.BlueprintId.ValueString()) if err != nil { resp.Diagnostics.AddError( fmt.Sprintf("error locking blueprint %q mutex", plan.BlueprintId.ValueString()), @@ -209,11 +225,17 @@ func (o *resourceDeviceAllocation) Update(ctx context.Context, req resource.Upda return } - state.DeployMode = plan.DeployMode - state.SetNodeDeployMode(ctx, o.client, &resp.Diagnostics) + switch { + case plan.DeployMode.IsNull(): // not expected with Optional+Computed, nothing to do here + case plan.DeployMode.IsUnknown(): // config is null, get the Computed value + plan.DeployMode = types.StringValue(utils.GetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), &resp.Diagnostics)) + default: // value provided via config + utils.SetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), plan.DeployMode.ValueString(), &resp.Diagnostics) + } if resp.Diagnostics.HasError() { return } + state.DeployMode = plan.DeployMode if !plan.DeviceKey.Equal(state.DeviceKey) { // device key has changed diff --git a/apstra/utils/node.go b/apstra/utils/node.go new file mode 100644 index 00000000..59a3f053 --- /dev/null +++ b/apstra/utils/node.go @@ -0,0 +1,74 @@ +package utils + +import ( + "context" + "fmt" + "github.com/Juniper/apstra-go-sdk/apstra" + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +func AllNodeDeployModes() []string { + modes := apstra.AllNodeDeployModes() + result := make([]string, len(modes)) + for i := range modes { + result[i] = StringersToFriendlyString(modes[i]) + } + + return result +} + +func GetNodeDeployMode(ctx context.Context, client *apstra.TwoStageL3ClosClient, nodeId string, diags *diag.Diagnostics) string { + var node struct { + Id string `json:"id"` + Type string `json:"type"` + DeployMode string `json:"deploy_mode"` + } + err := client.Client().GetNode(ctx, client.Id(), apstra.ObjectId(nodeId), &node) + if err != nil { + diags.AddError("failed to fetch blueprint node", err.Error()) + return "" + } + + var deployMode apstra.NodeDeployMode + err = deployMode.FromString(node.DeployMode) + if err != nil { + diags.AddError(fmt.Sprintf("error parsing deploy mode %q", node.DeployMode), err.Error()) + return "" + } + + return StringersToFriendlyString(deployMode) +} + +func SetNodeDeployMode(ctx context.Context, client *apstra.TwoStageL3ClosClient, nodeId string, modeString string, diags *diag.Diagnostics) { + var modeIota apstra.NodeDeployMode + err := ApiStringerFromFriendlyString(&modeIota, modeString) + if err != nil { + diags.AddError(fmt.Sprintf("error parsing deploy mode %q", modeString), err.Error()) + return + } + + type patch struct { + Id string `json:"id"` + DeployMode *string `json:"deploy_mode"` + } + + var stringPtr *string + if modeIota == apstra.NodeDeployModeNone { + stringPtr = nil + } else { + s := modeIota.String() + stringPtr = &s + } + + setDeployMode := patch{ + Id: nodeId, + DeployMode: stringPtr, + } + + err = client.Client().PatchNode(ctx, client.Id(), apstra.ObjectId(nodeId), &setDeployMode, nil) + if err != nil { + diags.AddError("error setting deploy mode", err.Error()) + return + } + +} diff --git a/apstra/utils/system_agents.go b/apstra/utils/system_agents.go deleted file mode 100644 index 6d341960..00000000 --- a/apstra/utils/system_agents.go +++ /dev/null @@ -1,13 +0,0 @@ -package utils - -import "github.com/Juniper/apstra-go-sdk/apstra" - -func AllNodeDeployModes() []string { - modes := apstra.AllNodeDeployModes() - result := make([]string, len(modes)) - for i := range modes { - result[i] = StringersToFriendlyString(modes[i]) - } - - return result -} From 774a6baca379ac5e62b46908e2e95f9e2c301727 Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Mon, 20 Nov 2023 12:12:40 -0500 Subject: [PATCH 2/3] whitespace --- apstra/utils/node.go | 1 - 1 file changed, 1 deletion(-) diff --git a/apstra/utils/node.go b/apstra/utils/node.go index 59a3f053..aabe115e 100644 --- a/apstra/utils/node.go +++ b/apstra/utils/node.go @@ -70,5 +70,4 @@ func SetNodeDeployMode(ctx context.Context, client *apstra.TwoStageL3ClosClient, diags.AddError("error setting deploy mode", err.Error()) return } - } From fa16db5ea9a7532f9f5b3f462482aff9c46123ff Mon Sep 17 00:00:00 2001 From: Chris Marget Date: Mon, 20 Nov 2023 16:50:08 -0500 Subject: [PATCH 3/3] change get/set deploy mode to return error rather than add diags --- .../resource_datacenter_device_allocation.go | 39 +++++++++++++++---- apstra/utils/node.go | 22 +++++------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/apstra/resource_datacenter_device_allocation.go b/apstra/resource_datacenter_device_allocation.go index cd8005e4..972eb469 100644 --- a/apstra/resource_datacenter_device_allocation.go +++ b/apstra/resource_datacenter_device_allocation.go @@ -2,6 +2,7 @@ package tfapstra import ( "context" + "errors" "fmt" "github.com/Juniper/apstra-go-sdk/apstra" "github.com/Juniper/terraform-provider-apstra/apstra/blueprint" @@ -95,9 +96,18 @@ func (o *resourceDeviceAllocation) Create(ctx context.Context, req resource.Crea switch { case plan.DeployMode.IsNull(): // not expected with Optional+Computed, nothing to do here case plan.DeployMode.IsUnknown(): // config is null, get the Computed value - plan.DeployMode = types.StringValue(utils.GetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), &resp.Diagnostics)) + deployMode, err := utils.GetNodeDeployMode(ctx, bp, plan.NodeId.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed to fetch node deploy mode", err.Error()) + return + } + plan.DeployMode = types.StringValue(deployMode) default: // value provided via config - utils.SetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), plan.DeployMode.ValueString(), &resp.Diagnostics) + err = utils.SetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), plan.DeployMode.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed while setting node deploy mode", err.Error()) + return + } } if resp.Diagnostics.HasError() { return @@ -156,10 +166,15 @@ func (o *resourceDeviceAllocation) Read(ctx context.Context, req resource.ReadRe return } - state.DeployMode = types.StringValue(utils.GetNodeDeployMode(ctx, bp, state.NodeId.ValueString(), &resp.Diagnostics)) - if resp.Diagnostics.HasError() { - return + deployMode, err := utils.GetNodeDeployMode(ctx, bp, state.NodeId.ValueString()) + if err != nil { + var ace apstra.ClientErr + if errors.As(err, &ace) && ace.Type() == apstra.ErrNotfound { + resp.State.RemoveResource(ctx) + return + } } + state.DeployMode = types.StringValue(deployMode) state.GetInterfaceMapName(ctx, o.client, &resp.Diagnostics) if resp.Diagnostics.HasError() { @@ -228,9 +243,19 @@ func (o *resourceDeviceAllocation) Update(ctx context.Context, req resource.Upda switch { case plan.DeployMode.IsNull(): // not expected with Optional+Computed, nothing to do here case plan.DeployMode.IsUnknown(): // config is null, get the Computed value - plan.DeployMode = types.StringValue(utils.GetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), &resp.Diagnostics)) + deployMode, err := utils.GetNodeDeployMode(ctx, bp, plan.NodeId.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed reading node deploy mode", err.Error()) + return + } + + plan.DeployMode = types.StringValue(deployMode) default: // value provided via config - utils.SetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), plan.DeployMode.ValueString(), &resp.Diagnostics) + err := utils.SetNodeDeployMode(ctx, bp, plan.NodeId.ValueString(), plan.DeployMode.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed setting node deploy mode", err.Error()) + return + } } if resp.Diagnostics.HasError() { return diff --git a/apstra/utils/node.go b/apstra/utils/node.go index aabe115e..11ccab2c 100644 --- a/apstra/utils/node.go +++ b/apstra/utils/node.go @@ -2,9 +2,7 @@ package utils import ( "context" - "fmt" "github.com/Juniper/apstra-go-sdk/apstra" - "github.com/hashicorp/terraform-plugin-framework/diag" ) func AllNodeDeployModes() []string { @@ -17,7 +15,7 @@ func AllNodeDeployModes() []string { return result } -func GetNodeDeployMode(ctx context.Context, client *apstra.TwoStageL3ClosClient, nodeId string, diags *diag.Diagnostics) string { +func GetNodeDeployMode(ctx context.Context, client *apstra.TwoStageL3ClosClient, nodeId string) (string, error) { var node struct { Id string `json:"id"` Type string `json:"type"` @@ -25,26 +23,23 @@ func GetNodeDeployMode(ctx context.Context, client *apstra.TwoStageL3ClosClient, } err := client.Client().GetNode(ctx, client.Id(), apstra.ObjectId(nodeId), &node) if err != nil { - diags.AddError("failed to fetch blueprint node", err.Error()) - return "" + return "", err } var deployMode apstra.NodeDeployMode err = deployMode.FromString(node.DeployMode) if err != nil { - diags.AddError(fmt.Sprintf("error parsing deploy mode %q", node.DeployMode), err.Error()) - return "" + return "", err } - return StringersToFriendlyString(deployMode) + return StringersToFriendlyString(deployMode), nil } -func SetNodeDeployMode(ctx context.Context, client *apstra.TwoStageL3ClosClient, nodeId string, modeString string, diags *diag.Diagnostics) { +func SetNodeDeployMode(ctx context.Context, client *apstra.TwoStageL3ClosClient, nodeId string, modeString string) error { var modeIota apstra.NodeDeployMode err := ApiStringerFromFriendlyString(&modeIota, modeString) if err != nil { - diags.AddError(fmt.Sprintf("error parsing deploy mode %q", modeString), err.Error()) - return + return err } type patch struct { @@ -67,7 +62,8 @@ func SetNodeDeployMode(ctx context.Context, client *apstra.TwoStageL3ClosClient, err = client.Client().PatchNode(ctx, client.Id(), apstra.ObjectId(nodeId), &setDeployMode, nil) if err != nil { - diags.AddError("error setting deploy mode", err.Error()) - return + return err } + + return nil }