From 7a9c3714b8b62432b4c3493065c8040ef0d98520 Mon Sep 17 00:00:00 2001 From: CorentinPtrl Date: Thu, 23 Jan 2025 22:56:48 +0100 Subject: [PATCH] feat(style): support pro style --- docs/resources/node_link.md | 25 +- .../resources/eveng_node_link/resource.tf | 5 - go.mod | 2 +- go.sum | 4 +- internal/provider/node_link_resource.go | 216 +++++++++++++++++- internal/provider/node_resource.go | 7 + internal/provider/start_nodes_resource.go | 3 +- 7 files changed, 241 insertions(+), 21 deletions(-) diff --git a/docs/resources/node_link.md b/docs/resources/node_link.md index 5dfaab3..743e9f5 100644 --- a/docs/resources/node_link.md +++ b/docs/resources/node_link.md @@ -63,11 +63,6 @@ resource "eveng_node_link" "node" { target_node_id = eveng_node.test.id target_port = "Gi0/1" } - -resource "eveng_start_nodes" "start" { - lab_path = eveng_lab.example.path - depends_on = [eveng_node_link.node] -} ``` @@ -82,5 +77,25 @@ resource "eveng_start_nodes" "start" { ### Optional - `network_id` (Number) ID of the network. +- `style` (Attributes) Style of the link(Only for the Pro version of EVE-NG). (see [below for nested schema](#nestedatt--style)) - `target_node_id` (Number) ID of the target node. - `target_port` (String) Target port. + + +### Nested Schema for `style` + +Optional: + +- `beziercurviness` (Number) Bezier curviness of the link. +- `color` (String) Color of the link in hexadecimal format. +- `curviness` (Number) Curviness of the link. +- `dstpos` (Number) Position of the destination. +- `label` (String) Label of the link. +- `labelpos` (Number) Position of the label. +- `linkstyle` (String) Style of the link. +- `midpoint` (Number) +- `round` (Number) Roundness of the link. +- `srcpos` (Number) Position of the source. +- `stub` (Number) Stub of the link. +- `style` (String) Style of the link. +- `width` (Number) Width of the link. diff --git a/examples/resources/eveng_node_link/resource.tf b/examples/resources/eveng_node_link/resource.tf index 9fe706b..d54f2ee 100755 --- a/examples/resources/eveng_node_link/resource.tf +++ b/examples/resources/eveng_node_link/resource.tf @@ -47,9 +47,4 @@ resource "eveng_node_link" "node" { source_port = "Gi0/1" target_node_id = eveng_node.test.id target_port = "Gi0/1" -} - -resource "eveng_start_nodes" "start" { - lab_path = eveng_lab.example.path - depends_on = [eveng_node_link.node] } \ No newline at end of file diff --git a/go.mod b/go.mod index 76f5f3f..d49ee49 100755 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module terraform-provider-eveng go 1.22.7 require ( - github.com/CorentinPtrl/evengsdk v0.0.8 + github.com/CorentinPtrl/evengsdk v0.0.9 github.com/hashicorp/terraform-plugin-framework v1.13.0 github.com/hashicorp/terraform-plugin-framework-validators v0.16.0 github.com/hashicorp/terraform-plugin-go v0.25.0 diff --git a/go.sum b/go.sum index 7a4cfc9..e166c09 100755 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/CorentinPtrl/evengsdk v0.0.8 h1:hOrJ/bKgYTguq8NX60rvbjpUSYw/nUIXmU0loDwfwNI= -github.com/CorentinPtrl/evengsdk v0.0.8/go.mod h1:3OEo/lyy+si+UmAOKBMnasIjFWP2RlPaHi3+13Z/KMM= +github.com/CorentinPtrl/evengsdk v0.0.9 h1:vcIYV8Qg8rxcOPhkOSPmpb6QMPd0+mj2OyUh2qAy28Q= +github.com/CorentinPtrl/evengsdk v0.0.9/go.mod h1:3OEo/lyy+si+UmAOKBMnasIjFWP2RlPaHi3+13Z/KMM= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= diff --git a/internal/provider/node_link_resource.go b/internal/provider/node_link_resource.go index 86b20be..6f10c7d 100755 --- a/internal/provider/node_link_resource.go +++ b/internal/provider/node_link_resource.go @@ -5,12 +5,17 @@ package provider import ( "context" + "encoding/json" "fmt" "github.com/CorentinPtrl/evengsdk" "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "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/resource/schema/float32default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types/basetypes" @@ -34,14 +39,31 @@ type nodeLinkResource struct { client *evengsdk.Client } +type StyleResourceModel struct { + Style types.String `tfsdk:"style"` + Color types.String `tfsdk:"color"` + SrcPos types.Float32 `tfsdk:"srcpos"` + DstPos types.Float32 `tfsdk:"dstpos"` + LinkStyle types.String `tfsdk:"linkstyle"` + Width types.Int32 `tfsdk:"width"` + Label types.String `tfsdk:"label"` + LabelPos types.Float32 `tfsdk:"labelpos"` + Stub types.Int32 `tfsdk:"stub"` + Curviness types.Int32 `tfsdk:"curviness"` + BezierCurviness types.Int32 `tfsdk:"beziercurviness"` + Round types.Int32 `tfsdk:"round"` + Midpoint types.Float32 `tfsdk:"midpoint"` +} + // NodeLinkResourceModel describes the resource data model. type NodeLinkResourceModel struct { - LabPath types.String `tfsdk:"lab_path"` - NetworkId types.Int64 `tfsdk:"network_id"` - SourceNodeId types.Int64 `tfsdk:"source_node_id"` - SourcePort types.String `tfsdk:"source_port"` - TargetNodeId types.Int64 `tfsdk:"target_node_id"` - TargetPort types.String `tfsdk:"target_port"` + LabPath types.String `tfsdk:"lab_path"` + NetworkId types.Int64 `tfsdk:"network_id"` + SourceNodeId types.Int64 `tfsdk:"source_node_id"` + SourcePort types.String `tfsdk:"source_port"` + TargetNodeId types.Int64 `tfsdk:"target_node_id"` + TargetPort types.String `tfsdk:"target_port"` + Style *StyleResourceModel `tfsdk:"style"` } // Metadata returns the resource type name. @@ -112,6 +134,95 @@ func (r *nodeLinkResource) Schema(_ context.Context, _ resource.SchemaRequest, r Optional: true, Description: "Target port.", }, + "style": schema.SingleNestedAttribute{ + Optional: true, + Description: "Style of the link(Only for the Pro version of EVE-NG).", + Attributes: map[string]schema.Attribute{ + "style": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("Solid"), + Validators: []validator.String{ + stringvalidator.OneOf("Solid", "Dashed"), + }, + Description: "Style of the link.", + }, + "color": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("#3e7089"), + Description: "Color of the link in hexadecimal format.", + }, + "srcpos": schema.Float32Attribute{ + Optional: true, + Computed: true, + Default: float32default.StaticFloat32(0.15), + Description: "Position of the source.", + }, + "dstpos": schema.Float32Attribute{ + Optional: true, + Computed: true, + Default: float32default.StaticFloat32(0.85), + Description: "Position of the destination.", + }, + "linkstyle": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString("Straight"), + Validators: []validator.String{ + stringvalidator.OneOf("Straight", "Bezier", "Flowchart", "StateMachine"), + }, + Description: "Style of the link.", + }, + "width": schema.Int32Attribute{ + Optional: true, + Computed: true, + Default: int32default.StaticInt32(2), + Description: "Width of the link.", + }, + "label": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + Description: "Label of the link.", + }, + "labelpos": schema.Float32Attribute{ + Optional: true, + Computed: true, + Default: float32default.StaticFloat32(0.5), + Description: "Position of the label.", + }, + "stub": schema.Int32Attribute{ + Optional: true, + Computed: true, + Default: int32default.StaticInt32(0), + Description: "Stub of the link.", + }, + "curviness": schema.Int32Attribute{ + Optional: true, + Computed: true, + Default: int32default.StaticInt32(10), + Description: "Curviness of the link.", + }, + "beziercurviness": schema.Int32Attribute{ + Optional: true, + Computed: true, + Default: int32default.StaticInt32(150), + Description: "Bezier curviness of the link.", + }, + "round": schema.Int32Attribute{ + Optional: true, + Computed: true, + Default: int32default.StaticInt32(0), + Description: "Roundness of the link.", + }, + "midpoint": schema.Float32Attribute{ + Optional: true, + Computed: true, + Default: float32default.StaticFloat32(0.5), + }, + }, + }, }, } } @@ -151,6 +262,11 @@ func (r *nodeLinkResource) Create(ctx context.Context, req resource.CreateReques return } + if r.client.IsPro() { + r.MakeNodeStyle(ctx, plan) + rstyle := r.NewStyleModel(ctx, plan) + plan.Style = &rstyle + } state := NodeLinkResourceModel{ LabPath: plan.LabPath, NetworkId: basetypes.NewInt64Value(id), @@ -158,6 +274,7 @@ func (r *nodeLinkResource) Create(ctx context.Context, req resource.CreateReques SourcePort: plan.SourcePort, TargetNodeId: plan.TargetNodeId, TargetPort: plan.TargetPort, + Style: plan.Style, } diags = resp.State.Set(ctx, state) resp.Diagnostics.Append(diags...) @@ -190,6 +307,10 @@ func (r *nodeLinkResource) Read(ctx context.Context, req resource.ReadRequest, r return } + if r.client.IsPro() && state.Style != nil { + style := r.NewStyleModel(ctx, state) + state.Style = &style + } diags = resp.State.Set(ctx, state) resp.Diagnostics.Append(diags...) if resp.Diagnostics.HasError() { @@ -233,6 +354,11 @@ func (r *nodeLinkResource) Update(ctx context.Context, req resource.UpdateReques resp.Diagnostics.AddError("Failed to update node link", err.Error()) return } + if r.client.IsPro() { + r.MakeNodeStyle(ctx, plan) + rstyle := r.NewStyleModel(ctx, plan) + plan.Style = &rstyle + } plan.NetworkId = basetypes.NewInt64Value(id) diags = resp.State.Set(ctx, plan) resp.Diagnostics.Append(diags...) @@ -416,3 +542,81 @@ func (r *nodeLinkResource) createOrUpdateNetwork(labPath string, networkId int, return *network, err } } + +func (r *nodeLinkResource) NewStyleModel(ctx context.Context, plan NodeLinkResourceModel) StyleResourceModel { + return r.GetTopologyForTargetNode(ctx, plan) +} + +func (r *nodeLinkResource) GetTopologyForTargetNode(ctx context.Context, plan NodeLinkResourceModel) StyleResourceModel { + topology, err := r.client.Lab.GetTopology(plan.LabPath.ValueString()) + if err != nil { + tflog.Error(ctx, fmt.Sprintf("Failed to get topology %s", err)) + } + for _, node := range topology { + if node["source"].(string) == fmt.Sprintf("node%d", plan.TargetNodeId.ValueInt64()) && node["source_label"].(string) == plan.TargetPort.ValueString() { + style := node["style"].(string) + if style == "" { + style = "Solid" + } + color := node["color"].(string) + if color == "" { + color = "#3e7089" + } + srcpos, _ := strconv.ParseFloat(node["srcpos"].(string), 32) + dstpos, _ := strconv.ParseFloat(node["dstpos"].(string), 32) + linkstyle := node["linkstyle"].(string) + if linkstyle == "" { + linkstyle = "Straight" + } + width, _ := strconv.Atoi(node["width"].(string)) + label := node["label"].(string) + labelpos, _ := strconv.ParseFloat(node["labelpos"].(string), 32) + stub, _ := strconv.Atoi(node["stub"].(string)) + curviness, _ := strconv.Atoi(node["curviness"].(string)) + beziercurviness, _ := strconv.Atoi(node["beziercurviness"].(string)) + round, _ := strconv.Atoi(node["round"].(string)) + midpoint, _ := strconv.ParseFloat(node["midpoint"].(string), 32) + return StyleResourceModel{ + Style: basetypes.NewStringValue(style), + Color: basetypes.NewStringValue(color), + SrcPos: basetypes.NewFloat32Value(float32(srcpos)), + DstPos: basetypes.NewFloat32Value(float32(dstpos)), + LinkStyle: basetypes.NewStringValue(linkstyle), + Width: basetypes.NewInt32Value(int32(width)), + Label: basetypes.NewStringValue(label), + LabelPos: basetypes.NewFloat32Value(float32(labelpos)), + Stub: basetypes.NewInt32Value(int32(stub)), + Curviness: basetypes.NewInt32Value(int32(curviness)), + BezierCurviness: basetypes.NewInt32Value(int32(beziercurviness)), + Round: basetypes.NewInt32Value(int32(round)), + Midpoint: basetypes.NewFloat32Value(float32(midpoint)), + } + } + } + return StyleResourceModel{} +} + +func (r *nodeLinkResource) MakeNodeStyle(ctx context.Context, plan NodeLinkResourceModel) { + if plan.Style == nil { + return + } + style := evengsdk.Style{ + Style: plan.Style.Style.ValueString(), + Color: plan.Style.Color.ValueString(), + Srcpos: plan.Style.SrcPos.ValueFloat32(), + Dstpos: plan.Style.DstPos.ValueFloat32(), + Linkstyle: plan.Style.LinkStyle.ValueString(), + Width: json.Number(strconv.Itoa(int(plan.Style.Width.ValueInt32()))), + Label: plan.Style.Label.ValueString(), + Labelpos: plan.Style.LabelPos.ValueFloat32(), + Stub: json.Number(strconv.Itoa(int(plan.Style.Stub.ValueInt32()))), + Curviness: json.Number(strconv.Itoa(int(plan.Style.Curviness.ValueInt32()))), + Beziercurviness: json.Number(strconv.Itoa(int(plan.Style.BezierCurviness.ValueInt32()))), + Round: json.Number(strconv.Itoa(int(plan.Style.Round.ValueInt32()))), + Midpoint: plan.Style.Midpoint.ValueFloat32(), + } + err := r.client.Node.UpdateNodeInterfaceStyleByName(plan.LabPath.ValueString(), int(plan.TargetNodeId.ValueInt64()), plan.TargetPort.ValueString(), style) + if err != nil { + tflog.Error(context.Background(), fmt.Sprintf("Failed to update node interface style %s", err)) + } +} diff --git a/internal/provider/node_resource.go b/internal/provider/node_resource.go index cab934e..e9aff16 100755 --- a/internal/provider/node_resource.go +++ b/internal/provider/node_resource.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-log/tflog" ) // Ensure the implementation satisfies the expected interfaces. @@ -178,6 +179,12 @@ func (r *nodeResource) Create(ctx context.Context, req resource.CreateRequest, r resp.Diagnostics.AddError("Failed to create node", err.Error()) return } + tflog.Info(ctx, fmt.Sprintf("Created node %d", node.Id)) + _, err = r.client.Node.GetNodeConfig(plan.LabPath.ValueString(), node.Id) + if err != nil { + resp.Diagnostics.AddError("Failed to get node config", err.Error()) + return + } err = r.client.Node.UpdateNodeConfig(plan.LabPath.ValueString(), node.Id, plan.Config.ValueString()) if err != nil { resp.Diagnostics.AddError("Failed to update node config", err.Error()) diff --git a/internal/provider/start_nodes_resource.go b/internal/provider/start_nodes_resource.go index aab21b6..432ee41 100755 --- a/internal/provider/start_nodes_resource.go +++ b/internal/provider/start_nodes_resource.go @@ -112,8 +112,7 @@ func (r *startNodesResource) Read(ctx context.Context, req resource.ReadRequest, if resp.Diagnostics.HasError() { return } - state.StartTime = basetypes.NewInt64Null() - resp.State.Set(ctx, state) + resp.State.RemoveResource(ctx) } // Update updates the resource and sets the updated Terraform state on success.