Skip to content

Commit

Permalink
enable widget and dashboard creation
Browse files Browse the repository at this point in the history
  • Loading branch information
rajagopalans committed Oct 17, 2023
1 parent 75f64bc commit 570f704
Show file tree
Hide file tree
Showing 22 changed files with 1,012 additions and 8 deletions.
2 changes: 1 addition & 1 deletion apstra/iba/probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type IbaProbe struct {
func (o IbaProbe) ResourceAttributes() map[string]resourceSchema.Attribute {
return map[string]resourceSchema.Attribute{
"blueprint_id": resourceSchema.StringAttribute{
MarkdownDescription: "Apstra Blueprint ID. Used to identify the Blueprint that the IBA Widget belongs to.",
MarkdownDescription: "Apstra Blueprint ID. Used to identify the Blueprint that the IBA Probe belongs to.",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
Expand Down
66 changes: 66 additions & 0 deletions apstra/iba/widget.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (
dataSourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"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/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"
)
Expand All @@ -16,6 +19,8 @@ type IbaWidget struct {
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Description types.String `tfsdk:"description"`
Stage types.String `tfsdk:"stage"`
ProbeId types.String `tfsdk:"probe_id"`
}

func (o IbaWidget) DataSourceAttributes() map[string]dataSourceSchema.Attribute {
Expand Down Expand Up @@ -49,11 +54,72 @@ func (o IbaWidget) DataSourceAttributes() map[string]dataSourceSchema.Attribute
MarkdownDescription: "Description of the IBA Widget",
Computed: true,
},
"stage": dataSourceSchema.StringAttribute{
MarkdownDescription: "Stage of IBA Probe used by this widget",
Computed: true,
},
"probe_id": dataSourceSchema.StringAttribute{
MarkdownDescription: "Id of IBA Probe used by this widget",
Computed: true,
},
}
}

func (o IbaWidget) ResourceAttributes() map[string]resourceSchema.Attribute {
return map[string]resourceSchema.Attribute{
"blueprint_id": resourceSchema.StringAttribute{
MarkdownDescription: "ID of the Apstra Blueprint where the IBA Widget will be created",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"id": resourceSchema.StringAttribute{
MarkdownDescription: "IBA Widget ID",
Computed: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
},
"name": resourceSchema.StringAttribute{
MarkdownDescription: "IBA Widget Name",
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"description": resourceSchema.StringAttribute{
MarkdownDescription: "IBA Widget Description",
Required: true,
},
"probe_id": dataSourceSchema.StringAttribute{
MarkdownDescription: "Id of IBA Probe used by this widget",
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
"stage": resourceSchema.StringAttribute{
MarkdownDescription: "Stage of IBA Probe used by this widget",
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
},
}
}

func (o *IbaWidget) LoadApiData(_ context.Context, in *apstra.IbaWidget, _ *diag.Diagnostics) {
o.Id = types.StringValue(in.Id.String())
o.Name = types.StringValue(in.Data.Label)
o.Description = types.StringValue(in.Data.Description)
o.Stage = types.StringValue(in.Data.StageName)
o.ProbeId = types.StringValue(in.Data.ProbeId.String())
}

func (o *IbaWidget) Request(ctx context.Context, d *diag.Diagnostics) *apstra.IbaWidgetData {

return &apstra.IbaWidgetData{
StageName: o.Stage.ValueString(),
Description: o.Description.ValueString(),
ProbeId: apstra.ObjectId(o.ProbeId.ValueString()),
Label: o.Name.ValueString(),
Type: apstra.IbaWidgetTypeStage,
}
}
1 change: 1 addition & 0 deletions apstra/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
func() resource.Resource { return &resourceDeviceAllocation{} },
func() resource.Resource { return &resourceIbaDashboard{} },
func() resource.Resource { return &resourceIbaProbe{} },
func() resource.Resource { return &resourceIbaWidget{} },
func() resource.Resource { return &resourceIntegerPool{} },
func() resource.Resource { return &resourceInterfaceMap{} },
func() resource.Resource { return &resourceIpv4Pool{} },
Expand Down
4 changes: 2 additions & 2 deletions apstra/resource_iba_probe.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,13 +172,13 @@ func (o *resourceIbaProbe) Delete(ctx context.Context, req resource.DeleteReques
return
}

// Delete IBA Dashboard by calling API
// Delete IBA Probe by calling API
err = bpClient.DeleteIbaProbe(ctx, apstra.ObjectId(state.Id.ValueString()))
if err != nil {
if utils.IsApstra404(err) {
return // 404 is okay
}
// resp.Diagnostics.AddError("error deleting IBA Dashboard", err.Error())
resp.Diagnostics.AddError("error deleting IBA Probe", err.Error())
return
}
}
56 changes: 56 additions & 0 deletions apstra/resource_iba_probe_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package tfapstra

import (
"context"
"errors"
"fmt"
testutils "github.com/Juniper/terraform-provider-apstra/apstra/test_utils"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"testing"
)

const (
resourceIbaProbeHCL = `
resource "apstra_iba_probe" "p_device_health" {
blueprint_id = "%s"
predefined_probe_id = "device_health"
probe_config = jsonencode(
{
"max_cpu_utilization": 80,
"max_memory_utilization": 80,
"max_disk_utilization": 80,
"duration": 660,
"threshold_duration": 360,
"history_duration": 604800
}
)
}
`
)

func TestAccResourceProbe(t *testing.T) {
ctx := context.Background()
bpClient, bpDelete, err := testutils.MakeOrFindBlueprint(ctx, "1bb57", testutils.BlueprintA)
if err != nil {
t.Fatal(errors.Join(err, bpDelete(ctx)))
}
defer func() {
err = bpDelete(ctx)
if err != nil {
t.Error(err)
}
}()

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: testAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
// Create and Read testing
{
Config: insecureProviderConfigHCL + fmt.Sprintf(resourceIbaProbeHCL, bpClient.Id()),
Check: resource.ComposeAggregateTestCheckFunc(
// Verify ID has any value set
resource.TestCheckResourceAttrSet("apstra_iba_probe.p_device_health", "id"), resource.TestCheckResourceAttr("apstra_iba_probe.p_device_health", "name", "Device System Health")),
},
},
})
}
172 changes: 172 additions & 0 deletions apstra/resource_iba_widget.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package tfapstra

import (
"context"
"fmt"
"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/Juniper/terraform-provider-apstra/apstra/iba"
"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 = &resourceIbaWidget{}

type resourceIbaWidget struct {
client *apstra.Client
}

func (o *resourceIbaWidget) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_iba_widget"
}

func (o *resourceIbaWidget) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
o.client = ResourceGetClient(ctx, req, resp)
}

func (o *resourceIbaWidget) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "This resource creates a IBA Widget.",
Attributes: iba.IbaWidget{}.ResourceAttributes(),
}
}

func (o *resourceIbaWidget) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// Retrieve values from plan
var plan iba.IbaWidget
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

// create a blueprint client
bpClient, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(plan.BlueprintId.ValueString()))
if err != nil {
resp.Diagnostics.AddError("failed to create blueprint client", err.Error())
return
}

// Convert the plan into an API Request
probeReq := plan.Request(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

id, err := bpClient.CreateIbaWidget(ctx, probeReq)
if err != nil {
resp.Diagnostics.AddError("failed to create Iba Probe", err.Error())
return
}
plan.Id = types.StringValue(id.String())

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

func (o *resourceIbaWidget) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state iba.IbaWidget
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

bpClient, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(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
}

api, err := bpClient.GetIbaWidget(ctx, apstra.ObjectId(state.Id.ValueString()))
if err != nil {
if utils.IsApstra404(err) {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError("Failed to Read IBA Dashboard", err.Error())
return
}

// create new state object
state.LoadApiData(ctx, api, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

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

// Update resource
func (o *resourceIbaWidget) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// Get plan values
var plan iba.IbaWidget
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

widgetReq := plan.Request(ctx, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

bpClient, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(plan.BlueprintId.ValueString()))
if err != nil {
resp.Diagnostics.AddError("failed to create blueprint client", err.Error())
return
}

// Update IBA Widget
err = bpClient.UpdateIbaWidget(ctx, apstra.ObjectId(plan.Id.ValueString()), widgetReq)
if err != nil {
resp.Diagnostics.AddError("plan", fmt.Sprintf("%q", plan))
resp.Diagnostics.AddError("error updating IBA Dashboard plan", err.Error())
return
}

api, err := bpClient.GetIbaWidget(ctx, apstra.ObjectId(plan.Id.ValueString()))
if err != nil {
resp.Diagnostics.AddError("Failed to Read IBA Dashboard", err.Error())
return
}
plan.LoadApiData(ctx, api, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

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

// Delete resource
func (o *resourceIbaWidget) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state iba.IbaWidget
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

bpClient, err := o.client.NewTwoStageL3ClosClient(ctx, apstra.ObjectId(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
}

// Delete IBA Probe by calling API
err = bpClient.DeleteIbaWidget(ctx, apstra.ObjectId(state.Id.ValueString()))
if err != nil {
if utils.IsApstra404(err) {
return // 404 is okay
}
resp.Diagnostics.AddError("error deleting IBA Probe", err.Error())
return
}
}
Loading

0 comments on commit 570f704

Please sign in to comment.