Skip to content

Commit

Permalink
Merge pull request #731 from Juniper/688-support-freeform-reference-d…
Browse files Browse the repository at this point in the history
…esign

688 support freeform reference design
  • Loading branch information
bwJuniper authored Jul 19, 2024
2 parents 5ae80da + 707bf53 commit 14af511
Show file tree
Hide file tree
Showing 87 changed files with 3,923 additions and 66 deletions.
120 changes: 120 additions & 0 deletions apstra/blueprint/freeform_config_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package blueprint

import (
"context"
"regexp"

"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
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"
)

type FreeformConfigTemplate struct {
Id types.String `tfsdk:"id"`
BlueprintId types.String `tfsdk:"blueprint_id"`
Name types.String `tfsdk:"name"`
Text types.String `tfsdk:"text"`
Tags types.Set `tfsdk:"tags"`
}

func (o FreeformConfigTemplate) DataSourceAttributes() map[string]dataSourceSchema.Attribute {
return map[string]dataSourceSchema.Attribute{
"blueprint_id": dataSourceSchema.StringAttribute{
MarkdownDescription: "Apstra Blueprint ID. Used to identify " +
"the Blueprint where the Config Template lives.",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"id": dataSourceSchema.StringAttribute{
MarkdownDescription: "Populate this field to look up the Config Template by ID. Required when `name` is omitted.",
Optional: true,
Computed: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
stringvalidator.ExactlyOneOf(path.Expressions{
path.MatchRelative(),
path.MatchRoot("name"),
}...),
},
},
"name": dataSourceSchema.StringAttribute{
MarkdownDescription: "Populate this field to look up an imported Config Template by Name. Required when `id` is omitted.",
Optional: true,
Computed: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"text": dataSourceSchema.StringAttribute{
MarkdownDescription: "Configuration Jinja2 template text",
Computed: true,
},
"tags": dataSourceSchema.SetAttribute{
MarkdownDescription: "Set of Tag labels",
ElementType: types.StringType,
Computed: true,
},
}
}

func (o FreeformConfigTemplate) ResourceAttributes() map[string]resourceSchema.Attribute {
return map[string]resourceSchema.Attribute{
"blueprint_id": resourceSchema.StringAttribute{
MarkdownDescription: "Apstra Blueprint ID.",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()},
},
"id": resourceSchema.StringAttribute{
MarkdownDescription: "ID of the Config Template.",
Computed: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
},
"name": resourceSchema.StringAttribute{
MarkdownDescription: "Config Template name as shown in the Web UI. Must end with `.jinja`.",
Required: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(7),
stringvalidator.RegexMatches(regexp.MustCompile(".jinja$"), "must end with '.jinja'"),
},
},
"text": resourceSchema.StringAttribute{
MarkdownDescription: "Configuration Jinja2 template text",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"tags": resourceSchema.SetAttribute{
MarkdownDescription: "Set of Tag labels",
ElementType: types.StringType,
Optional: true,
Validators: []validator.Set{setvalidator.SizeAtLeast(1)},
},
}
}

func (o *FreeformConfigTemplate) Request(ctx context.Context, diags *diag.Diagnostics) *apstra.ConfigTemplateData {
var tags []string
diags.Append(o.Tags.ElementsAs(ctx, &tags, false)...)
if diags.HasError() {
return nil
}

return &apstra.ConfigTemplateData{
Label: o.Name.ValueString(),
Text: o.Text.ValueString(),
Tags: tags,
}
}

func (o *FreeformConfigTemplate) LoadApiData(ctx context.Context, in *apstra.ConfigTemplateData, diags *diag.Diagnostics) {
o.Name = types.StringValue(in.Label)
o.Text = types.StringValue(in.Text)
o.Tags = utils.SetValueOrNull(ctx, types.StringType, in.Tags, diags) // safe to ignore diagnostic here
}
175 changes: 175 additions & 0 deletions apstra/blueprint/freeform_endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package blueprint

import (
"context"
"fmt"
"net"
"strings"

"github.com/Juniper/apstra-go-sdk/apstra"
"github.com/Juniper/terraform-provider-apstra/apstra/utils"
"github.com/hashicorp/terraform-plugin-framework-nettypes/cidrtypes"
"github.com/hashicorp/terraform-plugin-framework-validators/int64validator"
"github.com/hashicorp/terraform-plugin-framework-validators/setvalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
dataSourceSchema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/diag"
resourceSchema "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
)

type freeformEndpoint struct {
InterfaceName types.String `tfsdk:"interface_name"`
InterfaceId types.String `tfsdk:"interface_id"`
TransformationId types.Int64 `tfsdk:"transformation_id"`
Ipv4Address cidrtypes.IPv4Prefix `tfsdk:"ipv4_address"`
Ipv6Address cidrtypes.IPv6Prefix `tfsdk:"ipv6_address"`
Tags types.Set `tfsdk:"tags"`
}

func (o freeformEndpoint) attrTypes() map[string]attr.Type {
return map[string]attr.Type{
"interface_name": types.StringType,
"interface_id": types.StringType,
"transformation_id": types.Int64Type,
"ipv4_address": cidrtypes.IPv4PrefixType{},
"ipv6_address": cidrtypes.IPv6PrefixType{},
"tags": types.SetType{ElemType: types.StringType},
}
}

func (o freeformEndpoint) DatasourceAttributes() map[string]dataSourceSchema.Attribute {
return map[string]dataSourceSchema.Attribute{
"interface_name": dataSourceSchema.StringAttribute{
Computed: true,
MarkdownDescription: "The interface name, as found in the associated Device Profile, e.g. `xe-0/0/0`",
},
"interface_id": dataSourceSchema.StringAttribute{
Computed: true,
MarkdownDescription: "Graph node ID of the associated interface",
},
"transformation_id": dataSourceSchema.Int64Attribute{
Computed: true,
MarkdownDescription: "ID # of the transformation in the Device Profile",
},
"ipv4_address": dataSourceSchema.StringAttribute{
Computed: true,
MarkdownDescription: "Ipv4 address of the interface in CIDR notation",
CustomType: cidrtypes.IPv4PrefixType{},
},
"ipv6_address": dataSourceSchema.StringAttribute{
Computed: true,
MarkdownDescription: "Ipv6 address of the interface in CIDR notation",
CustomType: cidrtypes.IPv6PrefixType{},
},
"tags": dataSourceSchema.SetAttribute{
MarkdownDescription: "Set of Tags applied to the interface",
Computed: true,
ElementType: types.StringType,
},
}
}

func (o freeformEndpoint) ResourceAttributes() map[string]resourceSchema.Attribute {
return map[string]resourceSchema.Attribute{
"interface_name": resourceSchema.StringAttribute{
Required: true,
MarkdownDescription: "The interface name, as found in the associated Device Profile, e.g. `xe-0/0/0`",
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"interface_id": resourceSchema.StringAttribute{
Computed: true,
MarkdownDescription: "Graph node ID of the associated interface",
},
"transformation_id": resourceSchema.Int64Attribute{
Required: true,
MarkdownDescription: "ID # of the transformation in the Device Profile",
Validators: []validator.Int64{int64validator.AtLeast(1)},
},
"ipv4_address": resourceSchema.StringAttribute{
Optional: true,
MarkdownDescription: "Ipv4 address of the interface in CIDR notation",
CustomType: cidrtypes.IPv4PrefixType{},
},
"ipv6_address": resourceSchema.StringAttribute{
Optional: true,
MarkdownDescription: "Ipv6 address of the interface in CIDR notation",
CustomType: cidrtypes.IPv6PrefixType{},
},
"tags": resourceSchema.SetAttribute{
MarkdownDescription: "Set of Tags applied to the interface",
Optional: true,
ElementType: types.StringType,
Validators: []validator.Set{setvalidator.SizeAtLeast(1)},
},
}
}

func (o *freeformEndpoint) request(ctx context.Context, systemId string, diags *diag.Diagnostics) *apstra.FreeformEndpoint {
var ipNet4, ipNet6 *net.IPNet
if !o.Ipv4Address.IsNull() {
var ip4 net.IP
ip4, ipNet4, _ = net.ParseCIDR(o.Ipv4Address.ValueString())
ipNet4.IP = ip4
}
if !o.Ipv6Address.IsNull() {
var ip6 net.IP
ip6, ipNet6, _ = net.ParseCIDR(o.Ipv6Address.ValueString())
ipNet6.IP = ip6
}

var tags []string
diags.Append(o.Tags.ElementsAs(ctx, &tags, false)...)

return &apstra.FreeformEndpoint{
SystemId: apstra.ObjectId(systemId),
Interface: apstra.FreeformInterface{
Data: &apstra.FreeformInterfaceData{
IfName: o.InterfaceName.ValueString(),
TransformationId: int(o.TransformationId.ValueInt64()),
Ipv4Address: ipNet4,
Ipv6Address: ipNet6,
Tags: tags,
},
},
}
}

func (o *freeformEndpoint) loadApiData(ctx context.Context, in apstra.FreeformEndpoint, diags *diag.Diagnostics) {
if in.Interface.Id == nil {
diags.AddError(
fmt.Sprintf("api returned nil interface Id for system %s", in.SystemId),
"interface IDs should always be populated",
)
return
}

o.InterfaceName = types.StringValue(in.Interface.Data.IfName)
o.InterfaceId = types.StringValue(in.Interface.Id.String())
o.TransformationId = types.Int64Value(int64(in.Interface.Data.TransformationId))
o.Ipv4Address = cidrtypes.NewIPv4PrefixValue(in.Interface.Data.Ipv4Address.String())
if strings.Contains(o.Ipv4Address.ValueString(), "nil") {
o.Ipv4Address = cidrtypes.NewIPv4PrefixNull()
}
o.Ipv6Address = cidrtypes.NewIPv6PrefixValue(in.Interface.Data.Ipv6Address.String())
if strings.Contains(o.Ipv6Address.ValueString(), "nil") {
o.Ipv6Address = cidrtypes.NewIPv6PrefixNull()
}
o.Tags = utils.SetValueOrNull(ctx, types.StringType, in.Interface.Data.Tags, diags)
}

func newFreeformEndpointMap(ctx context.Context, in [2]apstra.FreeformEndpoint, diags *diag.Diagnostics) types.Map {
endpoints := make(map[string]freeformEndpoint, len(in))
for i := range in {
var endpoint freeformEndpoint
endpoint.loadApiData(ctx, in[i], diags)
endpoints[in[i].SystemId.String()] = endpoint
}
if diags.HasError() {
return types.MapNull(types.ObjectType{AttrTypes: freeformEndpoint{}.attrTypes()})
}

return utils.MapValueOrNull(ctx, types.ObjectType{AttrTypes: freeformEndpoint{}.attrTypes()}, endpoints, diags)
}
Loading

0 comments on commit 14af511

Please sign in to comment.