diff --git a/apstra/blueprint/connectivity_template_assignments.go b/apstra/blueprint/connectivity_template_assignments.go new file mode 100644 index 00000000..2ceadb45 --- /dev/null +++ b/apstra/blueprint/connectivity_template_assignments.go @@ -0,0 +1,90 @@ +package blueprint + +import ( + "context" + "github.com/Juniper/apstra-go-sdk/apstra" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type ConnectivityTemplateAssignments struct { + BlueprintId types.String `tfsdk:"blueprint_id"` + ConnectivityTemplateId types.String `tfsdk:"connectivity_template_id"` + ApplicationPointIds types.Set `tfsdk:"application_point_ids"` +} + +func (o ConnectivityTemplateAssignments) ResourceAttributes() map[string]resourceSchema.Attribute { + return map[string]resourceSchema.Attribute{ + "blueprint_id": resourceSchema.StringAttribute{ + MarkdownDescription: "Apstra Blueprint ID.", + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, + }, + "connectivity_template_id": resourceSchema.StringAttribute{ + MarkdownDescription: "Connectivity Template ID which should be applied to the Application Points.", + Required: true, + Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + }, + "application_point_ids": resourceSchema.SetAttribute{ + MarkdownDescription: "Set of Apstra node IDs of the Interfaces or Systems where the Connectivity " + + "Template should be applied.", + Required: true, + ElementType: types.StringType, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + setvalidator.ValueStringsAre(stringvalidator.LengthAtLeast(1)), + }, + }, + } +} + +func (o *ConnectivityTemplateAssignments) Request(ctx context.Context, state *ConnectivityTemplateAssignments, diags *diag.Diagnostics) map[apstra.ObjectId]map[apstra.ObjectId]bool { + var desired, current []apstra.ObjectId // Application Point IDs + + diags.Append(o.ApplicationPointIds.ElementsAs(ctx, &desired, false)...) + if diags.HasError() { + return nil + } + desiredMap := make(map[apstra.ObjectId]bool, len(desired)) + for _, apId := range desired { + desiredMap[apId] = true + } + + if state != nil { + diags.Append(state.ApplicationPointIds.ElementsAs(ctx, ¤t, false)...) + if diags.HasError() { + return nil + } + } + currentMap := make(map[apstra.ObjectId]bool, len(current)) + for _, apId := range current { + currentMap[apId] = true + } + + result := make(map[apstra.ObjectId]map[apstra.ObjectId]bool) + ctId := apstra.ObjectId(o.ConnectivityTemplateId.ValueString()) + + for _, ApplicationPointId := range desired { + if _, ok := currentMap[ApplicationPointId]; !ok { + // desired Application Point not found in currentMap -- need to add + result[ApplicationPointId] = map[apstra.ObjectId]bool{ctId: true} // causes CT to be added + } + } + + for _, ApplicationPointId := range current { + if _, ok := desiredMap[ApplicationPointId]; !ok { + // current Application Point not found in desiredMap -- need to remove + result[ApplicationPointId] = map[apstra.ObjectId]bool{ctId: false} // causes CT to be added + } + } + + return result +} diff --git a/apstra/blueprint/ct_assignment.go b/apstra/blueprint/connectivity_templates_assignment.go similarity index 87% rename from apstra/blueprint/ct_assignment.go rename to apstra/blueprint/connectivity_templates_assignment.go index ac030711..b0b13232 100644 --- a/apstra/blueprint/ct_assignment.go +++ b/apstra/blueprint/connectivity_templates_assignment.go @@ -14,13 +14,13 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types" ) -type ConnectivityTemplateAssignment struct { +type ConnectivityTemplatesAssignment struct { BlueprintId types.String `tfsdk:"blueprint_id"` ConnectivityTemplateIds types.Set `tfsdk:"connectivity_template_ids"` ApplicationPointId types.String `tfsdk:"application_point_id"` } -func (o ConnectivityTemplateAssignment) ResourceAttributes() map[string]resourceSchema.Attribute { +func (o ConnectivityTemplatesAssignment) ResourceAttributes() map[string]resourceSchema.Attribute { return map[string]resourceSchema.Attribute{ "blueprint_id": resourceSchema.StringAttribute{ MarkdownDescription: "Apstra Blueprint ID.", @@ -29,7 +29,7 @@ func (o ConnectivityTemplateAssignment) ResourceAttributes() map[string]resource Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, }, "application_point_id": resourceSchema.StringAttribute{ - MarkdownDescription: "Apstra node ID of the Interface or System where the Connectivity Template " + + MarkdownDescription: "Apstra node ID of the Interface or System where the Connectivity Templates " + "should be applied.", Required: true, PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, @@ -47,7 +47,7 @@ func (o ConnectivityTemplateAssignment) ResourceAttributes() map[string]resource } } -func (o *ConnectivityTemplateAssignment) AddDelRequest(ctx context.Context, state *ConnectivityTemplateAssignment, diags *diag.Diagnostics) ([]apstra.ObjectId, []apstra.ObjectId) { +func (o *ConnectivityTemplatesAssignment) AddDelRequest(ctx context.Context, state *ConnectivityTemplatesAssignment, diags *diag.Diagnostics) ([]apstra.ObjectId, []apstra.ObjectId) { var planIds, stateIds []apstra.ObjectId if o != nil { // o will be nil in Delete() diff --git a/apstra/provider.go b/apstra/provider.go index de0b90c5..c49397ba 100644 --- a/apstra/provider.go +++ b/apstra/provider.go @@ -517,6 +517,8 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { func() resource.Resource { return &resourceDatacenterConfiglet{} }, func() resource.Resource { return &resourceDatacenterConnectivityTemplate{} }, func() resource.Resource { return &resourceDatacenterConnectivityTemplateAssignment{} }, + func() resource.Resource { return &resourceDatacenterConnectivityTemplateAssignments{} }, + func() resource.Resource { return &resourceDatacenterConnectivityTemplatesAssignment{} }, func() resource.Resource { return &resourceDatacenterExternalGateway{} }, func() resource.Resource { return &resourceDatacenterGenericSystem{} }, func() resource.Resource { return &resourceDatacenterPropertySet{} }, diff --git a/apstra/resource_datacenter_connectivity_template_assignment.go b/apstra/resource_datacenter_connectivity_template_assignment.go index 8f9062d2..5f5b4a30 100644 --- a/apstra/resource_datacenter_connectivity_template_assignment.go +++ b/apstra/resource_datacenter_connectivity_template_assignment.go @@ -28,17 +28,21 @@ func (o *resourceDatacenterConnectivityTemplateAssignment) Configure(ctx context } func (o *resourceDatacenterConnectivityTemplateAssignment) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + deprecationMessage := "This resource has been deprecated and will be removed in a future release. Please migrate " + + "your configuration to use the `apstra_datacenter_connectivity_templates_assignment` resource." resp.Schema = schema.Schema{ - MarkdownDescription: docCategoryDatacenter + "This resource assigns one or more Connectivity Templates to an " + + DeprecationMessage: deprecationMessage, + MarkdownDescription: docCategoryDatacenter + "**Deprecation Warning**\n\n" + deprecationMessage + + "\n\nThis resource assigns one or more Connectivity Templates to an " + "Application Point. Application Points are graph nodes including interfaces at the " + "fabric edge, and switches within the fabric.", - Attributes: blueprint.ConnectivityTemplateAssignment{}.ResourceAttributes(), + Attributes: blueprint.ConnectivityTemplatesAssignment{}.ResourceAttributes(), } } func (o *resourceDatacenterConnectivityTemplateAssignment) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // Retrieve values from plan. - var plan blueprint.ConnectivityTemplateAssignment + var plan blueprint.ConnectivityTemplatesAssignment resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return @@ -76,7 +80,7 @@ func (o *resourceDatacenterConnectivityTemplateAssignment) Create(ctx context.Co func (o *resourceDatacenterConnectivityTemplateAssignment) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // Retrieve values from state - var state blueprint.ConnectivityTemplateAssignment + var state blueprint.ConnectivityTemplatesAssignment resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return @@ -124,14 +128,14 @@ func (o *resourceDatacenterConnectivityTemplateAssignment) Read(ctx context.Cont func (o *resourceDatacenterConnectivityTemplateAssignment) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // Retrieve values from plan - var plan blueprint.ConnectivityTemplateAssignment + var plan blueprint.ConnectivityTemplatesAssignment resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } // Retrieve values from state - var state blueprint.ConnectivityTemplateAssignment + var state blueprint.ConnectivityTemplatesAssignment resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return @@ -178,7 +182,7 @@ func (o *resourceDatacenterConnectivityTemplateAssignment) Update(ctx context.Co func (o *resourceDatacenterConnectivityTemplateAssignment) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // Retrieve values from state - var state blueprint.ConnectivityTemplateAssignment + var state blueprint.ConnectivityTemplatesAssignment resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return diff --git a/apstra/resource_datacenter_connectivity_template_assignments.go b/apstra/resource_datacenter_connectivity_template_assignments.go new file mode 100644 index 00000000..0731e774 --- /dev/null +++ b/apstra/resource_datacenter_connectivity_template_assignments.go @@ -0,0 +1,223 @@ +package tfapstra + +import ( + "context" + "errors" + "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/attr" + "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 = &resourceDatacenterConnectivityTemplateAssignments{} + +type resourceDatacenterConnectivityTemplateAssignments struct { + getBpClientFunc func(context.Context, string) (*apstra.TwoStageL3ClosClient, error) + lockFunc func(context.Context, string) error +} + +func (o *resourceDatacenterConnectivityTemplateAssignments) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_datacenter_connectivity_template_assignments" +} + +func (o *resourceDatacenterConnectivityTemplateAssignments) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + o.getBpClientFunc = ResourceGetTwoStageL3ClosClientFunc(ctx, req, resp) + o.lockFunc = ResourceGetBlueprintLockFunc(ctx, req, resp) +} + +func (o *resourceDatacenterConnectivityTemplateAssignments) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: docCategoryDatacenter + "This resource assigns a Connectivity Template to one or more " + + "Application Points. Application Points are graph nodes including interfaces at the " + + "fabric edge, and switches within the fabric.", + Attributes: blueprint.ConnectivityTemplateAssignments{}.ResourceAttributes(), + } +} + +func (o *resourceDatacenterConnectivityTemplateAssignments) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan. + var plan blueprint.ConnectivityTemplateAssignments + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + // get a client for the datacenter reference design + bp, err := o.getBpClientFunc(ctx, 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("failed to create blueprint client", err.Error()) + return + } + + // Lock the blueprint mutex. + err = o.lockFunc(ctx, plan.BlueprintId.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("failed locking blueprint %q mutex", plan.BlueprintId.ValueString()), + err.Error()) + return + } + + request := plan.Request(ctx, nil, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + err = bp.SetApplicationPointsConnectivityTemplates(ctx, request) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("failed while assigning Connectivity Template %s to Application Points", plan.ConnectivityTemplateId), + err.Error()) + } + + // set state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (o *resourceDatacenterConnectivityTemplateAssignments) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Retrieve values from state + var state blueprint.ConnectivityTemplateAssignments + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // get a client for the datacenter reference design + bp, err := o.getBpClientFunc(ctx, state.BlueprintId.ValueString()) + if err != nil { + if utils.IsApstra404(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("failed to create blueprint client", err.Error()) + return + } + + apToCtMap, err := bp.GetApplicationPointsConnectivityTemplatesByCt(ctx, apstra.ObjectId(state.ConnectivityTemplateId.ValueString())) + if err != nil { + var ace apstra.ClientErr + if errors.As(err, &ace) && ace.Type() == apstra.ErrNotfound { + resp.State.RemoveResource(ctx) + } + resp.Diagnostics.AddError( + fmt.Sprintf("failed while reading Application Point assignments for Connectivity Template %s", state.ConnectivityTemplateId), + err.Error()) + } + + var apIds []attr.Value + for apId, ctInfo := range apToCtMap { + if ctInfo[apstra.ObjectId(state.ConnectivityTemplateId.ValueString())] { + apIds = append(apIds, types.StringValue(apId.String())) + } + } + + // Set state + state.ApplicationPointIds = types.SetValueMust(types.StringType, apIds) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (o *resourceDatacenterConnectivityTemplateAssignments) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Retrieve values from plan + var plan blueprint.ConnectivityTemplateAssignments + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + // Retrieve values from state + var state blueprint.ConnectivityTemplateAssignments + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // get a client for the datacenter reference design + bp, err := o.getBpClientFunc(ctx, plan.BlueprintId.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed to create blueprint client", err.Error()) + return + } + + // Lock the blueprint mutex. + err = o.lockFunc(ctx, plan.BlueprintId.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("failed locking blueprint %q mutex", plan.BlueprintId.ValueString()), + err.Error()) + return + } + + request := plan.Request(ctx, &state, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + err = bp.SetApplicationPointsConnectivityTemplates(ctx, request) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("failed while assigning Connectivity Template %s to Application Points", plan.ConnectivityTemplateId), + err.Error()) + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (o *resourceDatacenterConnectivityTemplateAssignments) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // Retrieve values from state + var state blueprint.ConnectivityTemplateAssignments + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // get a client for the datacenter reference design + bp, err := o.getBpClientFunc(ctx, state.BlueprintId.ValueString()) + if err != nil { + if utils.IsApstra404(err) { + return // 404 is okay + } + resp.Diagnostics.AddError("failed to create blueprint client", err.Error()) + return + } + + // Lock the blueprint mutex. + err = o.lockFunc(ctx, state.BlueprintId.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("failed locking blueprint %q mutex", state.BlueprintId.ValueString()), + err.Error()) + return + } + + // extract the application point IDs from state - we use these to calculate the deletion request + var applicationPointIds []apstra.ObjectId + resp.Diagnostics.Append(state.ApplicationPointIds.ElementsAs(ctx, &applicationPointIds, false)...) + if resp.Diagnostics.HasError() { + return + } + + // use the application point IDs to generate a deletion request + request := make(map[apstra.ObjectId]map[apstra.ObjectId]bool, len(applicationPointIds)) + for _, applicationPointId := range applicationPointIds { + request[applicationPointId] = map[apstra.ObjectId]bool{apstra.ObjectId(state.ConnectivityTemplateId.ValueString()): false} + } + + // send the request + err = bp.SetApplicationPointsConnectivityTemplates(ctx, request) + if err != nil { + var ace apstra.ClientErr + if errors.As(err, &ace) && ace.Type() == apstra.ErrNotfound { + return // 404 is okay + } + resp.Diagnostics.AddError("failed clearing connectivity template from application points", err.Error()) + return + } +} diff --git a/apstra/resource_datacenter_connectivity_templates_assignment.go b/apstra/resource_datacenter_connectivity_templates_assignment.go new file mode 100644 index 00000000..71eeb5ea --- /dev/null +++ b/apstra/resource_datacenter_connectivity_templates_assignment.go @@ -0,0 +1,220 @@ +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/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ resource.ResourceWithConfigure = &resourceDatacenterConnectivityTemplatesAssignment{} + +type resourceDatacenterConnectivityTemplatesAssignment struct { + getBpClientFunc func(context.Context, string) (*apstra.TwoStageL3ClosClient, error) + lockFunc func(context.Context, string) error +} + +func (o *resourceDatacenterConnectivityTemplatesAssignment) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_datacenter_connectivity_templates_assignment" +} + +func (o *resourceDatacenterConnectivityTemplatesAssignment) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + o.getBpClientFunc = ResourceGetTwoStageL3ClosClientFunc(ctx, req, resp) + o.lockFunc = ResourceGetBlueprintLockFunc(ctx, req, resp) +} + +func (o *resourceDatacenterConnectivityTemplatesAssignment) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: docCategoryDatacenter + "This resource assigns one or more Connectivity Templates to an " + + "Application Point. Application Points are graph nodes including interfaces at the " + + "fabric edge, and switches within the fabric.", + Attributes: blueprint.ConnectivityTemplatesAssignment{}.ResourceAttributes(), + } +} + +func (o *resourceDatacenterConnectivityTemplatesAssignment) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Retrieve values from plan. + var plan blueprint.ConnectivityTemplatesAssignment + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + // get a client for the datacenter reference design + bp, err := o.getBpClientFunc(ctx, 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("failed to create blueprint client", err.Error()) + return + } + + // Lock the blueprint mutex. + err = o.lockFunc(ctx, plan.BlueprintId.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("failed locking blueprint %q mutex", plan.BlueprintId.ValueString()), + err.Error()) + return + } + + addIds, _ := plan.AddDelRequest(ctx, nil, &resp.Diagnostics) + err = bp.SetApplicationPointConnectivityTemplates(ctx, apstra.ObjectId(plan.ApplicationPointId.ValueString()), addIds) + if err != nil { + resp.Diagnostics.AddError("failed applying Connectivity Template", err.Error()) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (o *resourceDatacenterConnectivityTemplatesAssignment) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Retrieve values from state + var state blueprint.ConnectivityTemplatesAssignment + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // get a client for the datacenter reference design + bp, err := o.getBpClientFunc(ctx, state.BlueprintId.ValueString()) + if err != nil { + if utils.IsApstra404(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("failed to create blueprint client", err.Error()) + return + } + + // currentCtIds come from the API, may include CTs unrelated to this resource + currentCtIds, err := bp.GetInterfaceConnectivityTemplates(ctx, apstra.ObjectId(state.ApplicationPointId.ValueString())) + if err != nil { + if utils.IsApstra404(err) { + resp.State.RemoveResource(ctx) + return + } + resp.Diagnostics.AddError("failed reading Connectivity Template assignments", err.Error()) + return + } + + // stateCtIds come from the history of this resource. What CTs have been previously assigned? + var stateCtIds []apstra.ObjectId + resp.Diagnostics.Append(state.ConnectivityTemplateIds.ElementsAs(ctx, &stateCtIds, false)...) + if resp.Diagnostics.HasError() { + return + } + + // remainingCtIds are the previously assigned IDs (state) which are still assigned (current) + remainingCtIds := utils.SliceIntersectionOfAB(currentCtIds, stateCtIds) + state.ConnectivityTemplateIds = utils.SetValueOrNull(ctx, types.StringType, remainingCtIds, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + // Set state + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (o *resourceDatacenterConnectivityTemplatesAssignment) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Retrieve values from plan + var plan blueprint.ConnectivityTemplatesAssignment + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + // Retrieve values from state + var state blueprint.ConnectivityTemplatesAssignment + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // get a client for the datacenter reference design + bp, err := o.getBpClientFunc(ctx, plan.BlueprintId.ValueString()) + if err != nil { + resp.Diagnostics.AddError("failed to create blueprint client", err.Error()) + return + } + + // Lock the blueprint mutex. + err = o.lockFunc(ctx, plan.BlueprintId.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("failed locking blueprint %q mutex", plan.BlueprintId.ValueString()), + err.Error()) + return + } + + // calculate the add/del sets + addIds, delIds := plan.AddDelRequest(ctx, &state, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + // add any required CTs + err = bp.SetApplicationPointConnectivityTemplates(ctx, apstra.ObjectId(plan.ApplicationPointId.ValueString()), addIds) + if err != nil { + resp.Diagnostics.AddError("failed assigning connectivity templates", err.Error()) + return + } + + // clear any undesired CTs + err = bp.DelApplicationPointConnectivityTemplates(ctx, apstra.ObjectId(plan.ApplicationPointId.ValueString()), delIds) + if err != nil { + resp.Diagnostics.AddError("failed clearing connectivity template assignments", err.Error()) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (o *resourceDatacenterConnectivityTemplatesAssignment) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // Retrieve values from state + var state blueprint.ConnectivityTemplatesAssignment + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + // get a client for the datacenter reference design + bp, err := o.getBpClientFunc(ctx, state.BlueprintId.ValueString()) + if err != nil { + if utils.IsApstra404(err) { + return // 404 is okay + } + resp.Diagnostics.AddError("failed to create blueprint client", err.Error()) + return + } + + // Lock the blueprint mutex. + err = o.lockFunc(ctx, state.BlueprintId.ValueString()) + if err != nil { + resp.Diagnostics.AddError( + fmt.Sprintf("failed locking blueprint %q mutex", state.BlueprintId.ValueString()), + err.Error()) + return + } + + var delIds []apstra.ObjectId + resp.Diagnostics.Append(state.ConnectivityTemplateIds.ElementsAs(ctx, &delIds, false)...) + if resp.Diagnostics.HasError() { + return + } + + err = bp.DelApplicationPointConnectivityTemplates(ctx, apstra.ObjectId(state.ApplicationPointId.ValueString()), delIds) + if err != nil { + if utils.IsApstra404(err) { + return // 404 is okay + } + resp.Diagnostics.AddError("failed clearing connectivity template assignments", err.Error()) + return + } +} diff --git a/docs/resources/datacenter_connectivity_template_assignment.md b/docs/resources/datacenter_connectivity_template_assignment.md index 04c205dd..5a93f229 100644 --- a/docs/resources/datacenter_connectivity_template_assignment.md +++ b/docs/resources/datacenter_connectivity_template_assignment.md @@ -2,11 +2,17 @@ page_title: "apstra_datacenter_connectivity_template_assignment Resource - terraform-provider-apstra" subcategory: "Reference Design: Datacenter" description: |- + Deprecation Warning + This resource has been deprecated and will be removed in a future release. Please migrate your configuration to use the apstra_datacenter_connectivity_templates_assignment resource. This resource assigns one or more Connectivity Templates to an Application Point. Application Points are graph nodes including interfaces at the fabric edge, and switches within the fabric. --- # apstra_datacenter_connectivity_template_assignment (Resource) +**Deprecation Warning** + +This resource has been deprecated and will be removed in a future release. Please migrate your configuration to use the `apstra_datacenter_connectivity_templates_assignment` resource. + This resource assigns one or more Connectivity Templates to an Application Point. Application Points are graph nodes including interfaces at the fabric edge, and switches within the fabric. @@ -37,7 +43,7 @@ resource "apstra_datacenter_connectivity_template_assignment" "a" { ### Required -- `application_point_id` (String) Apstra node ID of the Interface or System where the Connectivity Template should be applied. +- `application_point_id` (String) Apstra node ID of the Interface or System where the Connectivity Templates should be applied. - `blueprint_id` (String) Apstra Blueprint ID. - `connectivity_template_ids` (Set of String) Set of Connectivity Template IDs which should be applied to the Application Point. diff --git a/docs/resources/datacenter_connectivity_template_assignments.md b/docs/resources/datacenter_connectivity_template_assignments.md new file mode 100644 index 00000000..bec3f85f --- /dev/null +++ b/docs/resources/datacenter_connectivity_template_assignments.md @@ -0,0 +1,49 @@ +--- +page_title: "apstra_datacenter_connectivity_template_assignments Resource - terraform-provider-apstra" +subcategory: "Reference Design: Datacenter" +description: |- + This resource assigns a Connectivity Template to one or more Application Points. Application Points are graph nodes including interfaces at the fabric edge, and switches within the fabric. +--- + +# apstra_datacenter_connectivity_template_assignments (Resource) + +This resource assigns a Connectivity Template to one or more Application Points. Application Points are graph nodes including interfaces at the fabric edge, and switches within the fabric. + + +## Example Usage + +```terraform +# This example assigns connectivity template ef77e286-be82-4855-baaf-269d2a0ed893 +# to four different switch ports. + +# identified by the ID "FkYtMBdeoJ5urBaIEi8" +# +# Data sources like these can be used to find node IDs to use in +# the `application_point_ids` attribute: +# - apstra_datacenter_svis_map +# - apstra_datacenter_interfaces_by_link_tag +# - apstra_datacenter_interfaces_by_system + +resource "apstra_datacenter_connectivity_template_assignments" "a" { + blueprint_id = "0b1d5276-37e0-46fb-8d35-b8932015e56c" + connectivity_template_id = "ef77e286-be82-4855-baaf-269d2a0ed893" + application_point_ids = [ + "nC7HblArEjHdVkzrGAo", + "nCPDQ_nYXJ5qvQI4MiE", + "nCuWV_8yMbtL2EIrbT0", + "nD4Ae9RkblAgvrOUjCQ", + ] +} +``` + + +## Schema + +### Required + +- `application_point_ids` (Set of String) Set of Apstra node IDs of the Interfaces or Systems where the Connectivity Template should be applied. +- `blueprint_id` (String) Apstra Blueprint ID. +- `connectivity_template_id` (String) Connectivity Template ID which should be applied to the Application Points. + + + diff --git a/docs/resources/datacenter_connectivity_templates_assignment.md b/docs/resources/datacenter_connectivity_templates_assignment.md new file mode 100644 index 00000000..49e1e002 --- /dev/null +++ b/docs/resources/datacenter_connectivity_templates_assignment.md @@ -0,0 +1,45 @@ +--- +page_title: "apstra_datacenter_connectivity_templates_assignment Resource - terraform-provider-apstra" +subcategory: "Reference Design: Datacenter" +description: |- + This resource assigns one or more Connectivity Templates to an Application Point. Application Points are graph nodes including interfaces at the fabric edge, and switches within the fabric. +--- + +# apstra_datacenter_connectivity_templates_assignment (Resource) + +This resource assigns one or more Connectivity Templates to an Application Point. Application Points are graph nodes including interfaces at the fabric edge, and switches within the fabric. + + +## Example Usage + +```terraform +# This example assigns two connectivity templates to the switch port +# identified by the ID "FkYtMBdeoJ5urBaIEi8" +# +# Data sources like these can be used to find node IDs to use in +# the `application_point_id` attribute: +# - apstra_datacenter_svis_map +# - apstra_datacenter_interfaces_by_link_tag +# - apstra_datacenter_interfaces_by_system + +resource "apstra_datacenter_connectivity_template_assignment" "a" { + blueprint_id = "b726704d-f80e-4733-9103-abd6ccd8752c" + application_point_id = "FkYtMBdeoJ5urBaIEi8" + connectivity_template_ids = [ + "bcbcb35f-8f23-4bfb-916e-1b21d07d6904", + "1f8ac61f-6996-42bb-a34f-4f4a50a7111a", + ] +} +``` + + +## Schema + +### Required + +- `application_point_id` (String) Apstra node ID of the Interface or System where the Connectivity Templates should be applied. +- `blueprint_id` (String) Apstra Blueprint ID. +- `connectivity_template_ids` (Set of String) Set of Connectivity Template IDs which should be applied to the Application Point. + + + diff --git a/examples/resources/apstra_datacenter_connectivity_template_assignments/example.tf b/examples/resources/apstra_datacenter_connectivity_template_assignments/example.tf new file mode 100644 index 00000000..bb0ff4f8 --- /dev/null +++ b/examples/resources/apstra_datacenter_connectivity_template_assignments/example.tf @@ -0,0 +1,21 @@ +# This example assigns connectivity template ef77e286-be82-4855-baaf-269d2a0ed893 +# to four different switch ports. + +# identified by the ID "FkYtMBdeoJ5urBaIEi8" +# +# Data sources like these can be used to find node IDs to use in +# the `application_point_ids` attribute: +# - apstra_datacenter_svis_map +# - apstra_datacenter_interfaces_by_link_tag +# - apstra_datacenter_interfaces_by_system + +resource "apstra_datacenter_connectivity_template_assignments" "a" { + blueprint_id = "0b1d5276-37e0-46fb-8d35-b8932015e56c" + connectivity_template_id = "ef77e286-be82-4855-baaf-269d2a0ed893" + application_point_ids = [ + "nC7HblArEjHdVkzrGAo", + "nCPDQ_nYXJ5qvQI4MiE", + "nCuWV_8yMbtL2EIrbT0", + "nD4Ae9RkblAgvrOUjCQ", + ] +} diff --git a/examples/resources/apstra_datacenter_connectivity_templates_assignment/example.tf b/examples/resources/apstra_datacenter_connectivity_templates_assignment/example.tf new file mode 100644 index 00000000..60e4fdc2 --- /dev/null +++ b/examples/resources/apstra_datacenter_connectivity_templates_assignment/example.tf @@ -0,0 +1,17 @@ +# This example assigns two connectivity templates to the switch port +# identified by the ID "FkYtMBdeoJ5urBaIEi8" +# +# Data sources like these can be used to find node IDs to use in +# the `application_point_id` attribute: +# - apstra_datacenter_svis_map +# - apstra_datacenter_interfaces_by_link_tag +# - apstra_datacenter_interfaces_by_system + +resource "apstra_datacenter_connectivity_template_assignment" "a" { + blueprint_id = "b726704d-f80e-4733-9103-abd6ccd8752c" + application_point_id = "FkYtMBdeoJ5urBaIEi8" + connectivity_template_ids = [ + "bcbcb35f-8f23-4bfb-916e-1b21d07d6904", + "1f8ac61f-6996-42bb-a34f-4f4a50a7111a", + ] +} diff --git a/go.mod b/go.mod index b9297d65..c145d32d 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/IBM/netaddr v1.5.0 - github.com/Juniper/apstra-go-sdk v0.0.0-20231129162500-680ef134805f + github.com/Juniper/apstra-go-sdk v0.0.0-20231221031459-2d405e054ecd github.com/chrismarget-j/go-licenses v0.0.0-20230424163011-d60082a506e0 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-version v1.6.0 diff --git a/go.sum b/go.sum index a506895e..d034fa97 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,8 @@ github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbi github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/IBM/netaddr v1.5.0 h1:IJlFZe1+nFs09TeMB/HOP4+xBnX2iM/xgiDOgZgTJq0= github.com/IBM/netaddr v1.5.0/go.mod h1:DDBPeYgbFzoXHjSz9Jwk7K8wmWV4+a/Kv0LqRnb8we4= -github.com/Juniper/apstra-go-sdk v0.0.0-20231129162500-680ef134805f h1:n3enAQoqHa84J3luHSu0LxpoA4/eUvxcmFrClGFw8Pg= -github.com/Juniper/apstra-go-sdk v0.0.0-20231129162500-680ef134805f/go.mod h1:BB8X+PSov7CoCIAQ5P1z8bHc7DwtDN51fWCLJ93oeHY= +github.com/Juniper/apstra-go-sdk v0.0.0-20231221031459-2d405e054ecd h1:l+FA1tvl9Plsv+F8u2Krz2rjPU0gshIt0kJJ7TKid/Y= +github.com/Juniper/apstra-go-sdk v0.0.0-20231221031459-2d405e054ecd/go.mod h1:BB8X+PSov7CoCIAQ5P1z8bHc7DwtDN51fWCLJ93oeHY= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=