Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

735 freeform resource datasource for resource groups #741

Merged
merged 8 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions apstra/blueprint/freeform_resource_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package blueprint

import (
"context"
"encoding/json"
"regexp"

"github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"

"github.com/Juniper/apstra-go-sdk/apstra"
"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 FreeformResourceGroup struct {
BlueprintId types.String `tfsdk:"blueprint_id"`
Id types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
ParentId types.String `tfsdk:"parent_id"`
// Tags types.Set `tfsdk:"tags"`
Data jsontypes.Normalized `tfsdk:"data"`
GeneratorId types.String `tfsdk:"generator_id"`
}

func (o FreeformResourceGroup) 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 Resource Allocation Group lives.",
Required: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"id": dataSourceSchema.StringAttribute{
MarkdownDescription: "Populate this field to look up the Freeform Allocation Group 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 the Freeform Allocation Group by Name. Required when `id` is omitted.",
Optional: true,
Computed: true,
Validators: []validator.String{stringvalidator.LengthAtLeast(1)},
},
"parent_id": dataSourceSchema.StringAttribute{
MarkdownDescription: "ID of the group node that is present as a parent of the current one in a " +
"parent/child relationship. If this is a top-level (root) node, then `parent_id` will be `null`.",
Computed: true,
},
//"tags": dataSourceSchema.SetAttribute{
// MarkdownDescription: "Set of Tag labels",
// ElementType: types.StringType,
// Computed: true,
//},
"data": dataSourceSchema.StringAttribute{
MarkdownDescription: "Arbitrary key-value mapping that is useful in a context of this group. " +
"For example, you can store some VRF-related data there or add properties that are useful " +
"only in context of resource allocation, but not systems or interfaces.",
Computed: true,
CustomType: jsontypes.NormalizedType{},
},
"generator_id": dataSourceSchema.StringAttribute{
MarkdownDescription: "ID of the group generator that created the group, if any.",
Computed: true,
},
}
}

func (o FreeformResourceGroup) 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 Freeform Resource Allocation Group.",
Computed: true,
PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
},
"name": resourceSchema.StringAttribute{
MarkdownDescription: "Freeform Resource Allocation Group name as shown in the Web UI.",
Required: true,
Validators: []validator.String{
stringvalidator.RegexMatches(
regexp.MustCompile("^[a-zA-Z0-9.-_]+$"),
"name may consist only of the following characters : a-zA-Z0-9.-_"),
},
},
"parent_id": resourceSchema.StringAttribute{
MarkdownDescription: "ID of the parent Freeform Resource Allocation Group, if this group is to be nested.",
Optional: 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)},
//},
"data": resourceSchema.StringAttribute{
MarkdownDescription: "Arbitrary JSON-encoded key-value mapping that is useful in a context of this " +
"group. For example, you can store some VRF-related data there or add properties that are useful " +
"only in context of resource allocation, but not systems or interfaces.",
Optional: true,
Computed: true,
Default: stringdefault.StaticString("{}"),
CustomType: jsontypes.NormalizedType{},
},
"generator_id": resourceSchema.StringAttribute{
MarkdownDescription: "ID of the Generator that created Resource Allocation Group. " +
"Always `null` because groups created via resource declaration were not generated.",
Computed: true,
},
}
}

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

return &apstra.FreeformRaGroupData{
ParentId: (*apstra.ObjectId)(o.ParentId.ValueStringPointer()),
Label: o.Name.ValueString(),
// Tags: tags,
Data: json.RawMessage(o.Data.ValueString()),
GeneratorId: (*apstra.ObjectId)(o.GeneratorId.ValueStringPointer()),
}
}

func (o *FreeformResourceGroup) LoadApiData(_ context.Context, in *apstra.FreeformRaGroupData, _ *diag.Diagnostics) {
o.Name = types.StringValue(in.Label)
if in.ParentId != nil {
o.ParentId = types.StringValue(string(*in.ParentId))
}

o.Data = jsontypes.NewNormalizedValue(string(in.Data))
// o.Tags = utils.SetValueOrNull(ctx, types.StringType, in.Tags, diags) // safe to ignore diagnostic here
o.GeneratorId = types.StringPointerValue((*string)(in.GeneratorId))
}
101 changes: 101 additions & 0 deletions apstra/datasource_freeform_resource_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
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/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types"
)

var (
_ datasource.DataSourceWithConfigure = &dataSourceFreeformResourceGroup{}
_ datasourceWithSetFfBpClientFunc = &dataSourceFreeformResourceGroup{}
)

type dataSourceFreeformResourceGroup struct {
getBpClientFunc func(context.Context, string) (*apstra.FreeformClient, error)
}

func (o *dataSourceFreeformResourceGroup) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_freeform_resource_group"
}

func (o *dataSourceFreeformResourceGroup) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
configureDataSource(ctx, o, req, resp)
}

func (o *dataSourceFreeformResourceGroup) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: docCategoryFreeform + "This data source provides details of a specific Freeform Resource Allocation Group.\n\n" +
"At least one optional attribute is required.",
Attributes: blueprint.FreeformResourceGroup{}.DataSourceAttributes(),
}
}

func (o *dataSourceFreeformResourceGroup) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var config blueprint.FreeformResourceGroup
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}

// get a client for the Freeform reference design
bp, err := o.getBpClientFunc(ctx, config.BlueprintId.ValueString())
if err != nil {
if utils.IsApstra404(err) {
resp.Diagnostics.AddError(fmt.Sprintf("blueprint %s not found", config.BlueprintId), err.Error())
return
}
resp.Diagnostics.AddError("failed to create blueprint client", err.Error())
return
}

var api *apstra.FreeformRaGroup
switch {
case !config.Id.IsNull():
api, err = bp.GetRaGroup(ctx, apstra.ObjectId(config.Id.ValueString()))
if utils.IsApstra404(err) {
resp.Diagnostics.AddAttributeError(
path.Root("id"),
"Freeform Resource Allocation Group not found",
fmt.Sprintf("Freeform Resource Allocation Group with ID %s not found", config.Id))
return
}
case !config.Name.IsNull():
api, err = bp.GetRaGroupByName(ctx, config.Name.ValueString())
if utils.IsApstra404(err) {
resp.Diagnostics.AddAttributeError(
path.Root("name"),
"Freeform Resource Allocation Group not found",
fmt.Sprintf("Freeform resource allocation group with Name %s not found", config.Name))
return
}
}
if err != nil {
resp.Diagnostics.AddError("failed reading Freeform Resource Allocation Group", err.Error())
return
}
if api.Data == nil {
resp.Diagnostics.AddError("failed reading Freeform Resource Allocation Group", "api response has no payload")
return
}

config.Id = types.StringValue(api.Id.String())
config.LoadApiData(ctx, api.Data, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

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

func (o *dataSourceFreeformResourceGroup) setBpClientFunc(f func(context.Context, string) (*apstra.FreeformClient, error)) {
o.getBpClientFunc = f
}
1 change: 1 addition & 0 deletions apstra/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var (
ResourceFreeformLink = resourceFreeformLink{}
ResourceFreeformSystem = resourceFreeformSystem{}
ResourceFreeformPropertySet = resourceFreeformPropertySet{}
ResourceFreeformRaGroup = resourceFreeformResourceGroup{}
ResourceIpv4Pool = resourceIpv4Pool{}
ResourceTemplatePodBased = resourceTemplatePodBased{}
ResourceTemplateCollapsed = resourceTemplateCollapsed{}
Expand Down
2 changes: 2 additions & 0 deletions apstra/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource
func() datasource.DataSource { return &dataSourceFreeformConfigTemplate{} },
func() datasource.DataSource { return &dataSourceFreeformLink{} },
func() datasource.DataSource { return &dataSourceFreeformPropertySet{} },
func() datasource.DataSource { return &dataSourceFreeformResourceGroup{} },
func() datasource.DataSource { return &dataSourceFreeformSystem{} },
func() datasource.DataSource { return &dataSourceIntegerPool{} },
func() datasource.DataSource { return &dataSourceInterfacesByLinkTag{} },
Expand Down Expand Up @@ -609,6 +610,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource {
func() resource.Resource { return &resourceFreeformConfigTemplate{} },
func() resource.Resource { return &resourceFreeformLink{} },
func() resource.Resource { return &resourceFreeformPropertySet{} },
func() resource.Resource { return &resourceFreeformResourceGroup{} },
func() resource.Resource { return &resourceFreeformSystem{} },
func() resource.Resource { return &resourceIntegerPool{} },
func() resource.Resource { return &resourceInterfaceMap{} },
Expand Down
Loading
Loading