From 2510db7ece28d995622c309a3600a466bfa7a797 Mon Sep 17 00:00:00 2001 From: bwJuniper <98770420+bwJuniper@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:30:01 +0200 Subject: [PATCH] finish alloc_group integration tests --- apstra/blueprint/freeform_alloc_group.go | 68 ++-- apstra/blueprint/freeform_resource.go | 4 +- apstra/data_source_freeform_alloc_group.go | 4 +- apstra/export_test.go | 1 + apstra/provider.go | 2 + apstra/resource_freeform_alloc_group.go | 20 +- ...e_freeform_alloc_group_integration_test.go | 316 ++++++++++++++++++ apstra/utils/freeform_resource.go | 2 +- apstra/utils/resource_pool.go | 17 + apstra/utils/rosetta.go | 30 ++ apstra/utils/rosetta_test.go | 5 + docs/data-sources/freeform_alloc_group.md | 85 +++++ docs/resources/freeform_alloc_group.md | 82 +++++ .../apstra_freeform_alloc_group/example.tf | 43 +++ .../apstra_freeform_alloc_group/example.tf | 43 +++ go.mod | 4 +- go.sum | 4 +- 17 files changed, 689 insertions(+), 41 deletions(-) create mode 100644 apstra/resource_freeform_alloc_group_integration_test.go create mode 100644 apstra/utils/resource_pool.go create mode 100644 docs/data-sources/freeform_alloc_group.md create mode 100644 docs/resources/freeform_alloc_group.md create mode 100644 examples/data-sources/apstra_freeform_alloc_group/example.tf create mode 100644 examples/resources/apstra_freeform_alloc_group/example.tf diff --git a/apstra/blueprint/freeform_alloc_group.go b/apstra/blueprint/freeform_alloc_group.go index f4a9af2b..2cc32531 100644 --- a/apstra/blueprint/freeform_alloc_group.go +++ b/apstra/blueprint/freeform_alloc_group.go @@ -3,7 +3,10 @@ package blueprint import ( "context" "fmt" + "github.com/Juniper/terraform-provider-apstra/apstra/utils" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" "regexp" + "strings" "github.com/Juniper/apstra-go-sdk/apstra" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" @@ -22,19 +25,19 @@ type FreeformAllocGroup struct { Id types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Type types.String `tfsdk:"type"` - PoolId types.String `tfsdk:"pool_id"` + PoolIds types.Set `tfsdk:"pool_ids"` } func (o FreeformAllocGroup) 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 System lives.", + "the Blueprint where the Allocation Group lives.", Required: true, Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, }, "id": dataSourceSchema.StringAttribute{ - MarkdownDescription: "Populate this field to look up the Freeform System by ID. Required when `name` is omitted.", + MarkdownDescription: "Populate this field to look up the Allocation Group by ID. Required when `name` is omitted.", Optional: true, Computed: true, Validators: []validator.String{ @@ -46,18 +49,20 @@ func (o FreeformAllocGroup) DataSourceAttributes() map[string]dataSourceSchema.A }, }, "name": dataSourceSchema.StringAttribute{ - MarkdownDescription: "Populate this field to look up System by Name. Required when `id` is omitted.", + MarkdownDescription: "Populate this field to look up the Allocation Group by Name. Required when `id` is omitted.", Optional: true, Computed: true, Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, }, "type": dataSourceSchema.StringAttribute{ - MarkdownDescription: "type of the Resource Pool, either Internal or External", - Computed: true, + MarkdownDescription: fmt.Sprintf("type of the Allocation Group, must be one of :\n - `" + + strings.Join(utils.AllResourcePoolTypes(), "`\n - `") + "`\n"), + Computed: true, }, - "pool_id": dataSourceSchema.StringAttribute{ + "pool_ids": dataSourceSchema.SetAttribute{ MarkdownDescription: "Pool ID assigned to the allocation group", Computed: true, + ElementType: types.StringType, }, } } @@ -71,40 +76,59 @@ func (o FreeformAllocGroup) ResourceAttributes() map[string]resourceSchema.Attri PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, }, "id": resourceSchema.StringAttribute{ - MarkdownDescription: "ID of the Freeform System.", + MarkdownDescription: "ID of the Freeform Allocation Group.", Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}, }, "name": resourceSchema.StringAttribute{ - MarkdownDescription: "Freeform System name as shown in the Web UI.", + MarkdownDescription: "Freeform Allocation Group name as shown in the Web UI.", Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, Validators: []validator.String{ - stringvalidator.RegexMatches(regexp.MustCompile("^[a-zA-Z0-9.-_]+$"), "name may consist only of the following characters : a-zA-Z0-9.-_")}, + stringvalidator.RegexMatches(regexp.MustCompile("^[a-zA-Z0-9.-_]+$"), + "name may consist only of the following characters : a-zA-Z0-9.-_"), + }, }, "type": resourceSchema.StringAttribute{ - MarkdownDescription: fmt.Sprintf("Type of the System. Must be one of `%s` or `%s`", apstra.SystemTypeInternal, apstra.SystemTypeExternal), - Required: true, - PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, - Validators: []validator.String{stringvalidator.OneOf(apstra.SystemTypeInternal.String(), apstra.SystemTypeExternal.String())}, + MarkdownDescription: fmt.Sprintf("type of the Allocation Group, must be one of :\n - `" + + strings.Join(utils.AllResourcePoolTypes(), "`\n - `") + "`\n"), + Required: true, + PlanModifiers: []planmodifier.String{stringplanmodifier.RequiresReplace()}, + Validators: []validator.String{stringvalidator.OneOf(utils.AllFFResourceTypes()...)}, }, - "pool_id": resourceSchema.StringAttribute{ - MarkdownDescription: "ID (usually serial number) of the Managed Device to associate with this System", - Optional: true, - Validators: []validator.String{stringvalidator.LengthAtLeast(1)}, + "pool_ids": resourceSchema.SetAttribute{ + MarkdownDescription: "ID of the Pool to associate with this Allocation Group", + ElementType: types.StringType, + Required: true, + Validators: []validator.Set{setvalidator.SizeAtLeast(1)}, }, } } func (o *FreeformAllocGroup) Request(ctx context.Context, diags *diag.Diagnostics) *apstra.FreeformAllocGroupData { + // unpack + var allocGroupType apstra.ResourcePoolType + err := utils.ApiStringerFromFriendlyString(&allocGroupType, o.Type.ValueString()) + if err != nil { + diags.AddError(fmt.Sprintf("error parsing type %q", o.Type.ValueString()), err.Error()) + } + + var poolIds []apstra.ObjectId + diags.Append(o.PoolIds.ElementsAs(ctx, &poolIds, false)...) + if diags.HasError() { + return nil + } return &apstra.FreeformAllocGroupData{ - Name: "", - Type: apstra.ResourcePoolType{}, - PoolIds: nil, + Name: o.Name.ValueString(), + Type: allocGroupType, + PoolIds: poolIds, } } func (o *FreeformAllocGroup) LoadApiData(ctx context.Context, in *apstra.FreeformAllocGroupData, diags *diag.Diagnostics) { + // pack o.Name = types.StringValue(in.Name) - o.Type = types.StringValue(in.Type.String()) + o.Type = types.StringValue(utils.StringersToFriendlyString(in.Type)) + o.PoolIds = utils.SetValueOrNull(ctx, types.StringType, in.PoolIds, diags) } diff --git a/apstra/blueprint/freeform_resource.go b/apstra/blueprint/freeform_resource.go index e02aac5f..cf886bb9 100644 --- a/apstra/blueprint/freeform_resource.go +++ b/apstra/blueprint/freeform_resource.go @@ -138,9 +138,9 @@ func (o FreeformResource) ResourceAttributes() map[string]resourceSchema.Attribu }, "type": resourceSchema.StringAttribute{ MarkdownDescription: "type of the Resource, must be one of :\n - `" + - strings.Join(utils.AllResourceTypes(), "`\n - `") + "`\n", + strings.Join(utils.AllFFResourceTypes(), "`\n - `") + "`\n", Optional: true, - Validators: []validator.String{stringvalidator.OneOf(utils.AllResourceTypes()...)}, + Validators: []validator.String{stringvalidator.OneOf(utils.AllFFResourceTypes()...)}, }, "integer_value": resourceSchema.Int64Attribute{ MarkdownDescription: fmt.Sprintf("Value used by integer type resources (`%s`, `%s`, `%s`, `%s`). "+ diff --git a/apstra/data_source_freeform_alloc_group.go b/apstra/data_source_freeform_alloc_group.go index e41254b5..3ea3f9d4 100644 --- a/apstra/data_source_freeform_alloc_group.go +++ b/apstra/data_source_freeform_alloc_group.go @@ -23,7 +23,7 @@ type dataSourceFreeformAllocGroup struct { } func (o *dataSourceFreeformAllocGroup) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_freeform_system" + resp.TypeName = req.ProviderTypeName + "_freeform_alloc_group" } func (o *dataSourceFreeformAllocGroup) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { @@ -32,7 +32,7 @@ func (o *dataSourceFreeformAllocGroup) Configure(ctx context.Context, req dataso func (o *dataSourceFreeformAllocGroup) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: docCategoryFreeform + "This data source provides details of a specific Freeform System.\n\n" + + MarkdownDescription: docCategoryFreeform + "This data source provides details of a Freeform Allocation Group.\n\n" + "At least one optional attribute is required.", Attributes: blueprint.FreeformAllocGroup{}.DataSourceAttributes(), } diff --git a/apstra/export_test.go b/apstra/export_test.go index 867ada4b..6862f898 100644 --- a/apstra/export_test.go +++ b/apstra/export_test.go @@ -18,6 +18,7 @@ var ( ResourceFreeformPropertySet = resourceFreeformPropertySet{} ResourceFreeformResourceGroup = resourceFreeformResourceGroup{} ResourceFreeformResource = resourceFreeformResource{} + ResourceFreeformAllocGroup = resourceFreeformAllocGroup{} ResourceFreeformSystem = resourceFreeformSystem{} ResourceIntegerPool = resourceIntegerPool{} ResourceIpv4Pool = resourceIpv4Pool{} diff --git a/apstra/provider.go b/apstra/provider.go index 05bdfee2..278a6da2 100644 --- a/apstra/provider.go +++ b/apstra/provider.go @@ -550,6 +550,7 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource func() datasource.DataSource { return &dataSourceDatacenterVirtualNetwork{} }, func() datasource.DataSource { return &dataSourceDatacenterVirtualNetworks{} }, func() datasource.DataSource { return &dataSourceDeviceConfig{} }, + func() datasource.DataSource { return &dataSourceFreeformAllocGroup{} }, func() datasource.DataSource { return &dataSourceFreeformConfigTemplate{} }, func() datasource.DataSource { return &dataSourceFreeformLink{} }, func() datasource.DataSource { return &dataSourceFreeformPropertySet{} }, @@ -610,6 +611,7 @@ func (p *Provider) Resources(_ context.Context) []func() resource.Resource { func() resource.Resource { return &resourceDatacenterIpLinkAddressing{} }, func() resource.Resource { return &resourceDatacenterVirtualNetwork{} }, func() resource.Resource { return &resourceDeviceAllocation{} }, + func() resource.Resource { return &resourceFreeformAllocGroup{} }, func() resource.Resource { return &resourceFreeformConfigTemplate{} }, func() resource.Resource { return &resourceFreeformLink{} }, func() resource.Resource { return &resourceFreeformPropertySet{} }, diff --git a/apstra/resource_freeform_alloc_group.go b/apstra/resource_freeform_alloc_group.go index 723f4303..9d14600c 100644 --- a/apstra/resource_freeform_alloc_group.go +++ b/apstra/resource_freeform_alloc_group.go @@ -23,7 +23,7 @@ type resourceFreeformAllocGroup struct { } func (o *resourceFreeformAllocGroup) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_freeform_system" + resp.TypeName = req.ProviderTypeName + "_freeform_alloc_group" } func (o *resourceFreeformAllocGroup) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { @@ -32,7 +32,7 @@ func (o *resourceFreeformAllocGroup) Configure(ctx context.Context, req resource func (o *resourceFreeformAllocGroup) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: docCategoryFreeform + "This resource creates a System in a Freeform Blueprint.", + MarkdownDescription: docCategoryFreeform + "This resource creates an Allocation Group in a Freeform Blueprint.", Attributes: blueprint.FreeformAllocGroup{}.ResourceAttributes(), } } @@ -71,9 +71,9 @@ func (o *resourceFreeformAllocGroup) Create(ctx context.Context, req resource.Cr return } - id, err := bp.CreateSystem(ctx, request) + id, err := bp.CreateAllocGroup(ctx, request) if err != nil { - resp.Diagnostics.AddError("error creating new System", err.Error()) + resp.Diagnostics.AddError("error creating new Allocation Group", err.Error()) return } @@ -101,13 +101,13 @@ func (o *resourceFreeformAllocGroup) Read(ctx context.Context, req resource.Read return } - api, err := bp.GetSystem(ctx, apstra.ObjectId(state.Id.ValueString())) + api, err := bp.GetAllocGroup(ctx, apstra.ObjectId(state.Id.ValueString())) if err != nil { if utils.IsApstra404(err) { resp.State.RemoveResource(ctx) return } - resp.Diagnostics.AddError("Error retrieving Freeform System", err.Error()) + resp.Diagnostics.AddError(fmt.Sprintf("Error retrieving Freeform Allocation Group %s", state.Id), err.Error()) return } @@ -154,9 +154,9 @@ func (o *resourceFreeformAllocGroup) Update(ctx context.Context, req resource.Up } // Update Config Template - err = bp.UpdateSystem(ctx, apstra.ObjectId(plan.Id.ValueString()), request) + err = bp.UpdateAllocGroup(ctx, apstra.ObjectId(plan.Id.ValueString()), request) if err != nil { - resp.Diagnostics.AddError("error updating Freeform System", err.Error()) + resp.Diagnostics.AddError("error updating Freeform Allocation Group", err.Error()) return } @@ -191,12 +191,12 @@ func (o *resourceFreeformAllocGroup) Delete(ctx context.Context, req resource.De } // Delete Config Template by calling API - err = bp.DeleteSystem(ctx, apstra.ObjectId(state.Id.ValueString())) + err = bp.DeleteAllocGroup(ctx, apstra.ObjectId(state.Id.ValueString())) if err != nil { if utils.IsApstra404(err) { return // 404 is okay } - resp.Diagnostics.AddError("error deleting Freeform System", err.Error()) + resp.Diagnostics.AddError("error deleting Freeform Allocation Group", err.Error()) return } } diff --git a/apstra/resource_freeform_alloc_group_integration_test.go b/apstra/resource_freeform_alloc_group_integration_test.go new file mode 100644 index 00000000..81bd4939 --- /dev/null +++ b/apstra/resource_freeform_alloc_group_integration_test.go @@ -0,0 +1,316 @@ +//go:build integration + +package tfapstra_test + +import ( + "context" + "fmt" + "github.com/Juniper/terraform-provider-apstra/apstra/utils" + "github.com/stretchr/testify/require" + "net" + "testing" + + "github.com/Juniper/apstra-go-sdk/apstra" + tfapstra "github.com/Juniper/terraform-provider-apstra/apstra" + testutils "github.com/Juniper/terraform-provider-apstra/apstra/test_utils" + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const ( + resourceFreeformAllocGroupHcl = ` +resource %q %q { + blueprint_id = %q + name = %q + type = %q + pool_ids = %s + } +` +) + +type resourceAllocGroup struct { + blueprintId string + name string + groupType apstra.ResourcePoolType + poolIds []string +} + +func (o resourceAllocGroup) render(rType, rName string) string { + return fmt.Sprintf(resourceFreeformAllocGroupHcl, + rType, rName, + o.blueprintId, + o.name, + utils.StringersToFriendlyString(o.groupType), + stringSetOrNull(o.poolIds), + ) +} + +func (o resourceAllocGroup) testChecks(t testing.TB, rType, rName string) testChecks { + result := newTestChecks(rType + "." + rName) + + // required and computed attributes can always be checked + result.append(t, "TestCheckResourceAttrSet", "id") + result.append(t, "TestCheckResourceAttr", "blueprint_id", o.blueprintId) + result.append(t, "TestCheckResourceAttr", "name", o.name) + result.append(t, "testCheckResourceAttr", "type", utils.StringersToFriendlyString(o.groupType)) + return result +} + +func TestResourceAllocGroup(t *testing.T) { + ctx := context.Background() + client := testutils.GetTestClient(t, ctx) + apiVersion := version.Must(version.NewVersion(client.ApiVersion())) + + // create a blueprint and a group... + bp := testutils.FfBlueprintA(t, ctx) + + newAsnPool := func(t testing.TB) string { + t.Helper() + + asnRange := []apstra.IntfIntRange{ + apstra.IntRangeRequest{First: 65535, Last: uint32(acctest.RandIntRange(65536, 67000))}, + } + + asnPoolId, err := bp.Client().CreateAsnPool(ctx, &apstra.AsnPoolRequest{ + DisplayName: acctest.RandString(6), + Ranges: asnRange, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, bp.Client().DeleteAsnPool(ctx, asnPoolId)) }) + return asnPoolId.String() + } + + newIntPool := func(t testing.TB) string { + t.Helper() + + intRange := []apstra.IntfIntRange{ + apstra.IntRangeRequest{First: 1, Last: uint32(acctest.RandIntRange(2, 3000))}, + } + + intPoolId, err := bp.Client().CreateIntegerPool(ctx, &apstra.IntPoolRequest{ + DisplayName: acctest.RandString(6), + Ranges: intRange, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, bp.Client().DeleteIntegerPool(ctx, intPoolId)) }) + return intPoolId.String() + } + + newVniPool := func(t testing.TB) string { + t.Helper() + + vniRange := []apstra.IntfIntRange{ + apstra.IntRangeRequest{First: 4100, Last: uint32(acctest.RandIntRange(4100, 10000))}, + } + + intPoolId, err := bp.Client().CreateVniPool(ctx, &apstra.VniPoolRequest{ + DisplayName: acctest.RandString(6), + Ranges: vniRange, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, bp.Client().DeleteVniPool(ctx, intPoolId)) }) + return intPoolId.String() + } + + newIpv4Pool := func(t testing.TB) string { + t.Helper() + + ip4subnets := []net.IPNet{ + randomPrefix(t, "10.0.0.0/16", 24), + randomPrefix(t, "10.1.0.0/16", 25), + randomPrefix(t, "10.2.0.0/16", 26), + randomPrefix(t, "10.3.0.0/16", 27), + } + + netStrings := make([]apstra.NewIpSubnet, len(ip4subnets)) + for i := range netStrings { + netStrings[i].Network = ip4subnets[i].String() + } + + ipv4PoolId, err := bp.Client().CreateIp4Pool(ctx, &apstra.NewIpPoolRequest{ + DisplayName: acctest.RandString(6), + Subnets: netStrings, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, bp.Client().DeleteIp4Pool(ctx, ipv4PoolId)) }) + return ipv4PoolId.String() + } + + newIpv6Pool := func(t testing.TB) string { + t.Helper() + + ip6subnets := []net.IPNet{ + randomPrefix(t, "2001:db8:0::/48", 112), + randomPrefix(t, "2001:db8:1::/48", 112), + randomPrefix(t, "2001:db8:2::/48", 112), + } + + netStrings := make([]apstra.NewIpSubnet, len(ip6subnets)) + for i := range netStrings { + netStrings[i].Network = ip6subnets[i].String() + } + + ipv6PoolId, err := bp.Client().CreateIp6Pool(ctx, &apstra.NewIpPoolRequest{ + DisplayName: acctest.RandString(6), + Subnets: netStrings, + }) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, bp.Client().DeleteIp6Pool(ctx, ipv6PoolId)) }) + return ipv6PoolId.String() + } + + namesByKey := make(map[string]string) + nameByKey := func(key string) string { + if name, ok := namesByKey[key]; ok { + return name + } + namesByKey[key] = acctest.RandString(6) + return namesByKey[key] + } + + type testStep struct { + config resourceAllocGroup + } + type testCase struct { + apiVersionConstraints version.Constraints + steps []testStep + } + + testCases := map[string]testCase{ + "start_asn": { + steps: []testStep{ + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_asn"), + groupType: apstra.ResourcePoolTypeAsn, + poolIds: []string{newAsnPool(t)}, + }, + }, + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_asn"), + groupType: apstra.ResourcePoolTypeAsn, + poolIds: []string{newAsnPool(t), newAsnPool(t)}, + }, + }, + }, + }, + "test_int": { + steps: []testStep{ + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_int"), + groupType: apstra.ResourcePoolTypeInt, + poolIds: []string{newIntPool(t)}, + }, + }, + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_int"), + groupType: apstra.ResourcePoolTypeInt, + poolIds: []string{newIntPool(t)}, + }, + }, + }, + }, + "test_vni": { + steps: []testStep{ + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_vni"), + groupType: apstra.ResourcePoolTypeVni, + poolIds: []string{newVniPool(t)}, + }, + }, + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_vni"), + groupType: apstra.ResourcePoolTypeVni, + poolIds: []string{newVniPool(t)}, + }, + }, + }, + }, + "test_ipv4": { + steps: []testStep{ + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_vni"), + groupType: apstra.ResourcePoolTypeIpv4, + poolIds: []string{newIpv4Pool(t)}, + }, + }, + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_vni"), + groupType: apstra.ResourcePoolTypeIpv4, + poolIds: []string{newIpv4Pool(t)}, + }, + }, + }, + }, + "test_ipv6": { + steps: []testStep{ + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_ipv6"), + groupType: apstra.ResourcePoolTypeIpv6, + poolIds: []string{newIpv6Pool(t)}, + }, + }, + { + config: resourceAllocGroup{ + blueprintId: bp.Id().String(), + name: nameByKey("test_ipv6"), + groupType: apstra.ResourcePoolTypeIpv6, + poolIds: []string{newIpv6Pool(t)}, + }, + }, + }, + }, + } + + resourceType := tfapstra.ResourceName(ctx, &tfapstra.ResourceFreeformAllocGroup) + + for tName, tCase := range testCases { + tName, tCase := tName, tCase + t.Run(tName, func(t *testing.T) { + t.Parallel() + if !tCase.apiVersionConstraints.Check(apiVersion) { + t.Skipf("test case %s requires Apstra %s", tName, tCase.apiVersionConstraints.String()) + } + + steps := make([]resource.TestStep, len(tCase.steps)) + for i, step := range tCase.steps { + config := step.config.render(resourceType, tName) + checks := step.config.testChecks(t, resourceType, tName) + + chkLog := checks.string() + stepName := fmt.Sprintf("test case %q step %d", tName, i+1) + + t.Logf("\n// ------ begin config for %s ------%s// -------- end config for %s ------\n\n", stepName, config, stepName) + t.Logf("\n// ------ begin checks for %s ------\n%s// -------- end checks for %s ------\n\n", stepName, chkLog, stepName) + + steps[i] = resource.TestStep{ + Config: insecureProviderConfigHCL + config, + Check: resource.ComposeAggregateTestCheckFunc(checks.checks...), + } + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: steps, + }) + }) + } +} diff --git a/apstra/utils/freeform_resource.go b/apstra/utils/freeform_resource.go index f27f1416..6031d98d 100644 --- a/apstra/utils/freeform_resource.go +++ b/apstra/utils/freeform_resource.go @@ -6,7 +6,7 @@ import ( "github.com/Juniper/apstra-go-sdk/apstra" ) -func AllResourceTypes() []string { +func AllFFResourceTypes() []string { members := apstra.FFResourceTypes.Members() result := make([]string, len(members)) for i, member := range members { diff --git a/apstra/utils/resource_pool.go b/apstra/utils/resource_pool.go new file mode 100644 index 00000000..8fe9019d --- /dev/null +++ b/apstra/utils/resource_pool.go @@ -0,0 +1,17 @@ +package utils + +import ( + "sort" + + "github.com/Juniper/apstra-go-sdk/apstra" +) + +func AllResourcePoolTypes() []string { + members := apstra.ResourcePoolTypes.Members() + result := make([]string, len(members)) + for i, member := range members { + result[i] = StringersToFriendlyString(member) + } + sort.Strings(result) + return result +} diff --git a/apstra/utils/rosetta.go b/apstra/utils/rosetta.go index 59cc302e..ba1b6572 100644 --- a/apstra/utils/rosetta.go +++ b/apstra/utils/rosetta.go @@ -38,6 +38,8 @@ const ( freeformResourceTypeIpv4 = "ipv4" freeformResourceTypeHostIpv4 = "host_ipv4" + + resourcePoolTypeIpv4 = "ipv4" ) type StringerWithFromString interface { @@ -77,6 +79,8 @@ func StringersToFriendlyString(in ...fmt.Stringer) string { return refDesignToFriendlyString(in0) case apstra.ResourceGroupName: return resourceGroupNameToFriendlyString(in0) + case apstra.ResourcePoolType: + return resourcePoolTypeToFriendlyString(in0) case apstra.StorageSchemaPath: return storageSchemaPathToFriendlyString(in0) } @@ -117,6 +121,8 @@ func ApiStringerFromFriendlyString(target StringerWithFromString, in ...string) return refDesignFromFriendlyString(target, in...) case *apstra.ResourceGroupName: return resourceGroupNameFromFriendlyString(target, in...) + case *apstra.ResourcePoolType: + return resourcePoolTypeFromFriendlyString(target, in...) case *apstra.StorageSchemaPath: return target.FromString("aos.sdk.telemetry.schemas." + in[0]) } @@ -246,6 +252,15 @@ func resourceGroupNameToFriendlyString(in apstra.ResourceGroupName) string { return in.String() } +func resourcePoolTypeToFriendlyString(in apstra.ResourcePoolType) string { + switch in { + case apstra.ResourcePoolTypeIpv4: + return resourcePoolTypeIpv4 + } + + return in.String() +} + func asnAllocationSchemeFromFriendlyString(target *apstra.AsnAllocationScheme, in ...string) error { if len(in) == 0 { return target.FromString("") @@ -419,3 +434,18 @@ func resourceGroupNameFromFriendlyString(target *apstra.ResourceGroupName, in .. return nil } + +func resourcePoolTypeFromFriendlyString(target *apstra.ResourcePoolType, in ...string) error { + if len(in) == 0 { + return target.FromString("") + } + + switch in[0] { + case resourcePoolTypeIpv4: + *target = apstra.ResourcePoolTypeIpv4 + default: + return target.FromString(in[0]) + } + + return nil +} diff --git a/apstra/utils/rosetta_test.go b/apstra/utils/rosetta_test.go index 0728cb02..2cbcb3b5 100644 --- a/apstra/utils/rosetta_test.go +++ b/apstra/utils/rosetta_test.go @@ -46,6 +46,8 @@ func TestRosetta(t *testing.T) { {string: "spine_superspine_link_ips_ipv6", stringers: []fmt.Stringer{apstra.ResourceGroupNameSuperspineSpineIp6}}, {string: "to_generic_link_ips_ipv6", stringers: []fmt.Stringer{apstra.ResourceGroupNameToGenericLinkIpv6}}, + {string: "ipv4", stringers: []fmt.Stringer{apstra.ResourcePoolTypeIpv4}}, + {string: "none", stringers: []fmt.Stringer{apstra.InterfaceNumberingIpv4TypeNone}}, {string: "none", stringers: []fmt.Stringer{apstra.InterfaceNumberingIpv6TypeNone}}, } @@ -84,6 +86,9 @@ func TestRosetta(t *testing.T) { case apstra.ResourceGroupName: x := apstra.ResourceGroupName(-1) target = &x + case apstra.ResourcePoolType: + x := apstra.ResourcePoolType{} + target = &x } if target == nil { diff --git a/docs/data-sources/freeform_alloc_group.md b/docs/data-sources/freeform_alloc_group.md new file mode 100644 index 00000000..5b8cba40 --- /dev/null +++ b/docs/data-sources/freeform_alloc_group.md @@ -0,0 +1,85 @@ +--- +page_title: "apstra_freeform_alloc_group Data Source - terraform-provider-apstra" +subcategory: "Reference Design: Freeform" +description: |- + This data source provides details of a Freeform Allocation Group. + At least one optional attribute is required. +--- + +# apstra_freeform_alloc_group (Data Source) + +This data source provides details of a Freeform Allocation Group. + +At least one optional attribute is required. + + +## Example Usage + +```terraform +resource "apstra_freeform_resource_group" "fizz_grp" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + name = "fizz_grp" +} + +resource "apstra_asn_pool" "rfc5398" { + name = "RFC5398 ASNs" + ranges = [ + { + first = 64496 + last = 64511 + }, + { + first = 65536 + last = 65551 + }, + ] +} + +resource "apstra_freeform_alloc_group" "test" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + name = "test_alloc_group2" + type = "asn" + pool_id = [apstra_asn_pool.rfc5398.id] +} + +data "apstra_freeform_alloc_group" "test" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + id = apstra_freeform_alloc_group.test.id +} + +output "test_resource_out" { value = data.apstra_freeform_alloc_group.test } + +# output produced = +#test_resource_out = { +# "blueprint_id" = "6f1e3a4e-2920-451f-b069-c256c9fff938" +# "id" = "rag_asn_test_alloc_group2" +# "name" = "test_alloc_group2" +# "pool_id" = toset([ +# "5d945d83-3361-4e9f-8a35-e935d549c298", +# ]) +# "type" = "asn" +#} +``` + + +## Schema + +### Required + +- `blueprint_id` (String) Apstra Blueprint ID. Used to identify the Blueprint where the Allocation Group lives. + +### Optional + +- `id` (String) Populate this field to look up the Allocation Group by ID. Required when `name` is omitted. +- `name` (String) Populate this field to look up the Allocation Group by Name. Required when `id` is omitted. + +### Read-Only + +- `pool_ids` (Set of String) Pool ID assigned to the allocation group +- `type` (String) type of the Allocation Group, must be one of : + - `asn` + - `integer` + - `ipv4` + - `ipv6` + - `vlan` + - `vni` diff --git a/docs/resources/freeform_alloc_group.md b/docs/resources/freeform_alloc_group.md new file mode 100644 index 00000000..7aa4721f --- /dev/null +++ b/docs/resources/freeform_alloc_group.md @@ -0,0 +1,82 @@ +--- +page_title: "apstra_freeform_alloc_group Resource - terraform-provider-apstra" +subcategory: "Reference Design: Freeform" +description: |- + This resource creates an Allocation Group in a Freeform Blueprint. +--- + +# apstra_freeform_alloc_group (Resource) + +This resource creates an Allocation Group in a Freeform Blueprint. + + +## Example Usage + +```terraform +resource "apstra_freeform_resource_group" "fizz_grp" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + name = "fizz_grp" +} + +resource "apstra_asn_pool" "rfc5398" { + name = "RFC5398 ASNs" + ranges = [ + { + first = 64496 + last = 64511 + }, + { + first = 65536 + last = 65551 + }, + ] +} + +resource "apstra_freeform_alloc_group" "test" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + name = "test_alloc_group2" + type = "asn" + pool_id = [apstra_asn_pool.rfc5398.id] +} + +data "apstra_freeform_alloc_group" "test" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + id = apstra_freeform_alloc_group.test.id +} + +output "test_resource_out" { value = data.apstra_freeform_alloc_group.test } + +# output produced = +#test_resource_out = { +# "blueprint_id" = "6f1e3a4e-2920-451f-b069-c256c9fff938" +# "id" = "rag_asn_test_alloc_group2" +# "name" = "test_alloc_group2" +# "pool_id" = toset([ +# "5d945d83-3361-4e9f-8a35-e935d549c298", +# ]) +# "type" = "asn" +#} +``` + + +## Schema + +### Required + +- `blueprint_id` (String) Apstra Blueprint ID. +- `name` (String) Freeform Allocation Group name as shown in the Web UI. +- `pool_ids` (Set of String) ID of the Pool to associate with this Allocation Group +- `type` (String) type of the Allocation Group, must be one of : + - `asn` + - `integer` + - `ipv4` + - `ipv6` + - `vlan` + - `vni` + +### Read-Only + +- `id` (String) ID of the Freeform Allocation Group. + + + diff --git a/examples/data-sources/apstra_freeform_alloc_group/example.tf b/examples/data-sources/apstra_freeform_alloc_group/example.tf new file mode 100644 index 00000000..d4083cef --- /dev/null +++ b/examples/data-sources/apstra_freeform_alloc_group/example.tf @@ -0,0 +1,43 @@ +resource "apstra_freeform_resource_group" "fizz_grp" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + name = "fizz_grp" +} + +resource "apstra_asn_pool" "rfc5398" { + name = "RFC5398 ASNs" + ranges = [ + { + first = 64496 + last = 64511 + }, + { + first = 65536 + last = 65551 + }, + ] +} + +resource "apstra_freeform_alloc_group" "test" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + name = "test_alloc_group2" + type = "asn" + pool_id = [apstra_asn_pool.rfc5398.id] +} + +data "apstra_freeform_alloc_group" "test" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + id = apstra_freeform_alloc_group.test.id +} + +output "test_resource_out" { value = data.apstra_freeform_alloc_group.test } + +# output produced = +#test_resource_out = { +# "blueprint_id" = "6f1e3a4e-2920-451f-b069-c256c9fff938" +# "id" = "rag_asn_test_alloc_group2" +# "name" = "test_alloc_group2" +# "pool_id" = toset([ +# "5d945d83-3361-4e9f-8a35-e935d549c298", +# ]) +# "type" = "asn" +#} \ No newline at end of file diff --git a/examples/resources/apstra_freeform_alloc_group/example.tf b/examples/resources/apstra_freeform_alloc_group/example.tf new file mode 100644 index 00000000..88b51f8f --- /dev/null +++ b/examples/resources/apstra_freeform_alloc_group/example.tf @@ -0,0 +1,43 @@ +resource "apstra_freeform_resource_group" "fizz_grp" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + name = "fizz_grp" +} + +resource "apstra_asn_pool" "rfc5398" { + name = "RFC5398 ASNs" + ranges = [ + { + first = 64496 + last = 64511 + }, + { + first = 65536 + last = 65551 + }, + ] +} + +resource "apstra_freeform_alloc_group" "test" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + name = "test_alloc_group2" + type = "asn" + pool_id = [apstra_asn_pool.rfc5398.id] +} + +data "apstra_freeform_alloc_group" "test" { + blueprint_id = "6f1e3a4e-2920-451f-b069-c256c9fff938" + id = apstra_freeform_alloc_group.test.id +} + +output "test_resource_out" { value = data.apstra_freeform_alloc_group.test } + +# output produced = +#test_resource_out = { +# "blueprint_id" = "6f1e3a4e-2920-451f-b069-c256c9fff938" +# "id" = "rag_asn_test_alloc_group2" +# "name" = "test_alloc_group2" +# "pool_id" = toset([ +# "5d945d83-3361-4e9f-8a35-e935d549c298", +# ]) +# "type" = "asn" +#} diff --git a/go.mod b/go.mod index 35942cf8..48b27712 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,11 @@ module github.com/Juniper/terraform-provider-apstra go 1.22.5 -replace github.com/Juniper/apstra-go-sdk => ../apstra-go-sdk +//replace github.com/Juniper/apstra-go-sdk => ../apstra-go-sdk require ( github.com/IBM/netaddr v1.5.0 - github.com/Juniper/apstra-go-sdk v0.0.0-20240730135749-72db5614fa0e + github.com/Juniper/apstra-go-sdk v0.0.0-20240801202534-f251446c2750 github.com/chrismarget-j/go-licenses v0.0.0-20240224210557-f22f3e06d3d4 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-version v1.7.0 diff --git a/go.sum b/go.sum index ca0e5a76..bfd180ae 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0 github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 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-20240730135749-72db5614fa0e h1:1jTeweRZ1kFLJvUgM417Kc2DUeyzsvsVbkbs4yWdNaM= -github.com/Juniper/apstra-go-sdk v0.0.0-20240730135749-72db5614fa0e/go.mod h1:Xwj3X8v/jRZWv28o6vQAqD4lz2JmzaSYLZ2ch1SS89w= +github.com/Juniper/apstra-go-sdk v0.0.0-20240801202534-f251446c2750 h1:0p7UP5v6422lyiVX1/nCX+TaawoK6JZuss8RZMfJvCs= +github.com/Juniper/apstra-go-sdk v0.0.0-20240801202534-f251446c2750/go.mod h1:Xwj3X8v/jRZWv28o6vQAqD4lz2JmzaSYLZ2ch1SS89w= github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=