diff --git a/docs/list-resources/ipam_bulk_hostname_template.md b/docs/list-resources/ipam_bulk_hostname_template.md new file mode 100644 index 000000000..1c80509a9 --- /dev/null +++ b/docs/list-resources/ipam_bulk_hostname_template.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nios_ipam_bulk_hostname_template List Resource - nios" +subcategory: "IPAM" +description: |- + Query existing ipam Bulkhostnametemplate. +--- + +# nios_ipam_bulk_hostname_template (List Resource) + +Query existing ipam Bulkhostnametemplate. + +## Example Usage + +```terraform +// List specific Bulk Hostname Templates using filters +list "nios_ipam_bulk_hostname_template" "list_bulk_hostname_templates_using_filters" { + provider = nios + config { + filters = { + template_name = "example_template" + } + } +} + +// List Bulk Hostname Templates with resource details included +list "nios_ipam_bulk_hostname_template" "list_bulk_hostname_templates_with_resource" { + provider = nios + include_resource = true +} +``` + + +## Schema + +### Optional + +- `extattrfilters` (Map of String) External Attribute Filters are used to return a more specific list of results by filtering on external attributes. If you specify multiple filters, the results returned will have only resources that match all the specified filters. +- `filters` (Map of String) Filters are used to return a more specific list of results. Filters can be used to match resources by specific attributes, e.g. name. If you specify multiple filters, the results returned will have only resources that match all the specified filters. diff --git a/docs/list-resources/ipam_vlanview.md b/docs/list-resources/ipam_vlanview.md new file mode 100644 index 000000000..867ec2808 --- /dev/null +++ b/docs/list-resources/ipam_vlanview.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nios_ipam_vlanview List Resource - nios" +subcategory: "IPAM" +description: |- + Query existing ipam Vlanview. +--- + +# nios_ipam_vlanview (List Resource) + +Query existing ipam Vlanview. + +## Example Usage + +```terraform +// List specific VLAN Views using filters +list "nios_ipam_vlanview" "list_vlanviews_using_filters" { + provider = nios + config { + filters = { + name = "example_vlan_view" + } + } +} + +// List specific VLAN Views using Extensible Attributes +list "nios_ipam_vlanview" "list_vlanviews_using_extensible_attributes" { + provider = nios + config { + extattrfilters = { + Site = "location-1" + } + } +} + +// List VLAN Views with resource details included +list "nios_ipam_vlanview" "list_vlanviews_with_resource" { + provider = nios + include_resource = true +} +``` + + +## Schema + +### Optional + +- `extattrfilters` (Map of String) External Attribute Filters are used to return a more specific list of results by filtering on external attributes. If you specify multiple filters, the results returned will have only resources that match all the specified filters. +- `filters` (Map of String) Filters are used to return a more specific list of results. Filters can be used to match resources by specific attributes, e.g. name. If you specify multiple filters, the results returned will have only resources that match all the specified filters. diff --git a/examples/list-resources/nios_ipam_bulk_hostname_template/list-resource.tfquery.hcl b/examples/list-resources/nios_ipam_bulk_hostname_template/list-resource.tfquery.hcl new file mode 100644 index 000000000..56fe69051 --- /dev/null +++ b/examples/list-resources/nios_ipam_bulk_hostname_template/list-resource.tfquery.hcl @@ -0,0 +1,15 @@ +// List specific Bulk Hostname Templates using filters +list "nios_ipam_bulk_hostname_template" "list_bulk_hostname_templates_using_filters" { + provider = nios + config { + filters = { + template_name = "example_template" + } + } +} + +// List Bulk Hostname Templates with resource details included +list "nios_ipam_bulk_hostname_template" "list_bulk_hostname_templates_with_resource" { + provider = nios + include_resource = true +} diff --git a/examples/list-resources/nios_ipam_vlanview/list-resource.tfquery.hcl b/examples/list-resources/nios_ipam_vlanview/list-resource.tfquery.hcl new file mode 100644 index 000000000..337a2bbf3 --- /dev/null +++ b/examples/list-resources/nios_ipam_vlanview/list-resource.tfquery.hcl @@ -0,0 +1,26 @@ +// List specific VLAN Views using filters +list "nios_ipam_vlanview" "list_vlanviews_using_filters" { + provider = nios + config { + filters = { + name = "example_vlan_view" + } + } +} + +// List specific VLAN Views using Extensible Attributes +list "nios_ipam_vlanview" "list_vlanviews_using_extensible_attributes" { + provider = nios + config { + extattrfilters = { + Site = "location-1" + } + } +} + +// List VLAN Views with resource details included +list "nios_ipam_vlanview" "list_vlanviews_with_resource" { + provider = nios + include_resource = true +} + \ No newline at end of file diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 20250d851..f9bb22c5d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -464,6 +464,8 @@ func (p *NIOSProvider) ListResources(ctx context.Context) []func() list.ListReso dhcp.NewFixedaddressList, ipam.NewNetworkviewList, + ipam.NewBulkhostnametemplateList, + ipam.NewVlanviewList, } } diff --git a/internal/service/ipam/bulkhostnametemplate_list.go b/internal/service/ipam/bulkhostnametemplate_list.go new file mode 100644 index 000000000..f5351fe52 --- /dev/null +++ b/internal/service/ipam/bulkhostnametemplate_list.go @@ -0,0 +1,197 @@ +package ipam + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/list" + "github.com/hashicorp/terraform-plugin-framework/list/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + niosclient "github.com/infobloxopen/infoblox-nios-go-client/client" + "github.com/infobloxopen/infoblox-nios-go-client/ipam" + + "github.com/infobloxopen/terraform-provider-nios/internal/flex" + "github.com/infobloxopen/terraform-provider-nios/internal/utils" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ list.ListResource = &BulkhostnametemplateList{} +var _ list.ListResourceWithConfigure = &BulkhostnametemplateList{} + +func NewBulkhostnametemplateList() list.ListResource { + return &BulkhostnametemplateList{} +} + +// BulkhostnametemplateList defines the List implementation. +type BulkhostnametemplateList struct { + client *niosclient.APIClient +} + +func (l *BulkhostnametemplateList) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + "ipam_bulk_hostname_template" +} + +func (l *BulkhostnametemplateList) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*niosclient.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected List Resource Configure Type", + fmt.Sprintf("Expected *niosclient.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + l.client = client +} + +type BulkhostnametemplateListModel struct { + Filters types.Map `tfsdk:"filters"` + ExtAttrFilters types.Map `tfsdk:"extattrfilters"` +} + +func (l *BulkhostnametemplateList) ListResourceConfigSchema(ctx context.Context, req list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Query existing ipam Bulkhostnametemplate.", + Attributes: map[string]schema.Attribute{ + "filters": schema.MapAttribute{ + MarkdownDescription: "Filters are used to return a more specific list of results. Filters can be used to match resources by specific attributes, e.g. name. If you specify multiple filters, the results returned will have only resources that match all the specified filters.", + ElementType: types.StringType, + Optional: true, + }, + "extattrfilters": schema.MapAttribute{ + MarkdownDescription: "External Attribute Filters are used to return a more specific list of results by filtering on external attributes. If you specify multiple filters, the results returned will have only resources that match all the specified filters.", + ElementType: types.StringType, + Optional: true, + }, + }, + } +} + +func (l *BulkhostnametemplateList) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) { + var data BulkhostnametemplateListModel + pageCount := 0 + limit := int32(req.Limit) + + diags := req.Config.Get(ctx, &data) + if diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + allResults, err := utils.ReadWithPages( + func(pageID string, maxResultsPerPage int32) ([]ipam.Bulkhostnametemplate, string, error) { + + var paging int32 = 1 + + // If total limit is set by user and is less than maxResultsPerPage, use it as maxResultsPerPage for API call to optimize the number of results. + // If limit > maxResultsPerPage, terraform automatically breaks connection to the provider after limit is reached. + if limit < maxResultsPerPage { + maxResultsPerPage = limit + } + + //Increment the page count + pageCount++ + + request := l.client.IPAMAPI. + BulkhostnametemplateAPI. + List(ctx). + Filters(flex.ExpandFrameworkMapString(ctx, data.Filters, &diags)). + ReturnAsObject(1). + ReturnFieldsPlus(readableAttributesForBulkhostnametemplate). + Paging(paging). + MaxResults(maxResultsPerPage) + + // Add page ID if provided + if pageID != "" { + request = request.PageId(pageID) + } + + // Execute the request + apiRes, _, err := request.Execute() + if err != nil { + return nil, "", err + } + + res := apiRes.ListBulkhostnametemplateResponseObject.GetResult() + tflog.Info(ctx, fmt.Sprintf("Page %d : Retrieved %d results", pageCount, len(res))) + + // Check for next page ID in additional properties + additionalProperties := apiRes.ListBulkhostnametemplateResponseObject.AdditionalProperties + var nextPageID string + + // If limit is reached , we do not need to continue to make API calls, we can return the results and empty nextPageID to stop pagination. + if len(res) >= int(limit) { + nextPageID = "" + tflog.Info(ctx, "Limit reached, stopped fetching more pages.") + return res, nextPageID, nil + } + + npId, ok := additionalProperties["next_page_id"] + if ok { + if npIdStr, ok := npId.(string); ok { + nextPageID = npIdStr + } + } else { + tflog.Info(ctx, "No next page ID found. This is the last page.") + } + return res, nextPageID, nil + }, + ) + + if err != nil { + diags.AddError("Client Error", fmt.Sprintf("Unable to list Bulkhostnametemplate, got error: %s", err)) + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + stream.Results = func(push func(list.ListResult) bool) { + for _, item := range allResults { + result := req.NewListResult(ctx) + + // Set the Identity for each result + result.Diagnostics.Append(result.Identity.SetAttribute(ctx, path.Root("ref"), &item.Ref)...) + if result.Diagnostics.HasError() { + if !push(result) { + return + } + continue + } + + // By default, list only returns the identity. + // If IncludeResource is true, it gets the full resource and sets it in the result.Resource + if req.IncludeResource { + if result.Diagnostics.HasError() { + if !push(result) { + return + } + continue + } + result1 := FlattenBulkhostnametemplate(ctx, &item, &result.Diagnostics) + result.Diagnostics.Append(result.Resource.Set(ctx, &result1)...) + if result.Diagnostics.HasError() { + if !push(result) { + return + } + continue + } + + } + + // Push the result to the stream + if !push(result) { + return + } + } + } + +} diff --git a/internal/service/ipam/bulkhostnametemplate_list_test.go b/internal/service/ipam/bulkhostnametemplate_list_test.go new file mode 100644 index 000000000..c551dd9ab --- /dev/null +++ b/internal/service/ipam/bulkhostnametemplate_list_test.go @@ -0,0 +1,106 @@ +package ipam_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + "github.com/infobloxopen/infoblox-nios-go-client/ipam" + + "github.com/infobloxopen/terraform-provider-nios/internal/acctest" +) + +func TestAccBulkhostnametemplateList_basic(t *testing.T) { + var resourceName = "nios_ipam_bulk_hostname_template.test" + var v ipam.Bulkhostnametemplate + templateName := acctest.RandomNameWithPrefix("test-template") + templateFormat := "host-$4" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + Steps: []resource.TestStep{ + // Create and Read + { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Config: testAccBulkhostnametemplateBasicConfig(templateName, templateFormat), + Check: resource.ComposeTestCheckFunc( + testAccCheckBulkhostnametemplateExists(context.Background(), resourceName, &v), + ), + }, + // Query the object + { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccBulkhostnametemplateListBasicConfig(), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLengthAtLeast("nios_ipam_bulk_hostname_template.test", 1), + }, + }, + }, + }) +} + +func TestAccBulkhostnametemplateList_Filters(t *testing.T) { + var resourceName = "nios_ipam_bulk_hostname_template.test" + var v ipam.Bulkhostnametemplate + templateName := acctest.RandomNameWithPrefix("test-template") + templateFormat := "host-$4" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + Steps: []resource.TestStep{ + // Create and Read + { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Config: testAccBulkhostnametemplateBasicConfig(templateName, templateFormat), + Check: resource.ComposeTestCheckFunc( + testAccCheckBulkhostnametemplateExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "template_name", templateName), + ), + }, + // Query the object + { + + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccBulkhostnametemplateListConfigFilters(templateName), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_ipam_bulk_hostname_template.test", 1), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccBulkhostnametemplateListBasicConfig() string { + return ` +list "nios_ipam_bulk_hostname_template" "test" { + provider = nios + limit = 5 +} +` +} + +func testAccBulkhostnametemplateListConfigFilters(name string) string { + return fmt.Sprintf(` +list "nios_ipam_bulk_hostname_template" "test" { + provider = nios + config { + filters = { + template_name = %q + } + } +} +`, name) +} diff --git a/internal/service/ipam/bulkhostnametemplate_resource.go b/internal/service/ipam/bulkhostnametemplate_resource.go index fe682f800..e8d4449ed 100644 --- a/internal/service/ipam/bulkhostnametemplate_resource.go +++ b/internal/service/ipam/bulkhostnametemplate_resource.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-framework/resource/schema" niosclient "github.com/infobloxopen/infoblox-nios-go-client/client" @@ -23,6 +24,7 @@ var readableAttributesForBulkhostnametemplate = "is_grid_default,pre_defined,tem // Ensure provider defined types fully satisfy framework interfaces. var _ resource.Resource = &BulkhostnametemplateResource{} var _ resource.ResourceWithImportState = &BulkhostnametemplateResource{} +var _ resource.ResourceWithIdentity = &BulkhostnametemplateResource{} func NewBulkhostnametemplateResource() resource.Resource { return &BulkhostnametemplateResource{} @@ -35,6 +37,9 @@ type BulkhostnametemplateResource struct { func (r *BulkhostnametemplateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_" + "ipam_bulk_hostname_template" + resp.ResourceBehavior = resource.ResourceBehavior{ + MutableIdentity: true, + } } func (r *BulkhostnametemplateResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -44,6 +49,16 @@ func (r *BulkhostnametemplateResource) Schema(ctx context.Context, req resource. } } +func (r *BulkhostnametemplateResource) IdentitySchema(ctx context.Context, req resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { + resp.IdentitySchema = identityschema.Schema{ + Attributes: map[string]identityschema.Attribute{ + "ref": identityschema.StringAttribute{ + RequiredForImport: true, + }, + }, + } +} + func (r *BulkhostnametemplateResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { @@ -117,6 +132,9 @@ func (r *BulkhostnametemplateResource) Create(ctx context.Context, req resource. data.Flatten(ctx, &res, &resp.Diagnostics) + // Save the Identity of the Resource + resp.Diagnostics.Append(resp.Identity.SetAttribute(ctx, path.Root("ref"), &data.Ref)...) + // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -169,6 +187,9 @@ func (r *BulkhostnametemplateResource) Read(ctx context.Context, req resource.Re data.Flatten(ctx, &res, &resp.Diagnostics) + // Save the Identity of the Resource + resp.Diagnostics.Append(resp.Identity.SetAttribute(ctx, path.Root("ref"), &data.Ref)...) + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -227,6 +248,9 @@ func (r *BulkhostnametemplateResource) Update(ctx context.Context, req resource. data.Flatten(ctx, &res, &resp.Diagnostics) + // Save the Identity of the Resource + resp.Diagnostics.Append(resp.Identity.SetAttribute(ctx, path.Root("ref"), &data.Ref)...) + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -265,5 +289,12 @@ func (r *BulkhostnametemplateResource) Delete(ctx context.Context, req resource. } func (r *BulkhostnametemplateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + if req.Identity.Raw.IsKnown() { + diags := req.Identity.GetAttribute(ctx, path.Root("ref"), &req.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + } resource.ImportStatePassthroughID(ctx, path.Root("ref"), req, resp) } diff --git a/internal/service/ipam/vlanview_list.go b/internal/service/ipam/vlanview_list.go new file mode 100644 index 000000000..2b62741ba --- /dev/null +++ b/internal/service/ipam/vlanview_list.go @@ -0,0 +1,201 @@ +package ipam + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/list" + "github.com/hashicorp/terraform-plugin-framework/list/schema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + niosclient "github.com/infobloxopen/infoblox-nios-go-client/client" + "github.com/infobloxopen/infoblox-nios-go-client/ipam" + + "github.com/infobloxopen/terraform-provider-nios/internal/flex" + "github.com/infobloxopen/terraform-provider-nios/internal/utils" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ list.ListResource = &VlanviewList{} +var _ list.ListResourceWithConfigure = &VlanviewList{} + +func NewVlanviewList() list.ListResource { + return &VlanviewList{} +} + +// VlanviewList defines the List implementation. +type VlanviewList struct { + client *niosclient.APIClient +} + +func (l *VlanviewList) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + "ipam_vlanview" +} + +func (l *VlanviewList) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*niosclient.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected List Resource Configure Type", + fmt.Sprintf("Expected *niosclient.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + l.client = client +} + +type VlanviewListModel struct { + Filters types.Map `tfsdk:"filters"` + ExtAttrFilters types.Map `tfsdk:"extattrfilters"` +} + +func (l *VlanviewList) ListResourceConfigSchema(ctx context.Context, req list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Query existing ipam Vlanview.", + Attributes: map[string]schema.Attribute{ + "filters": schema.MapAttribute{ + MarkdownDescription: "Filters are used to return a more specific list of results. Filters can be used to match resources by specific attributes, e.g. name. If you specify multiple filters, the results returned will have only resources that match all the specified filters.", + ElementType: types.StringType, + Optional: true, + }, + "extattrfilters": schema.MapAttribute{ + MarkdownDescription: "External Attribute Filters are used to return a more specific list of results by filtering on external attributes. If you specify multiple filters, the results returned will have only resources that match all the specified filters.", + ElementType: types.StringType, + Optional: true, + }, + }, + } +} + +func (l *VlanviewList) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) { + var data VlanviewListModel + pageCount := 0 + limit := int32(req.Limit) + + diags := req.Config.Get(ctx, &data) + if diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + allResults, err := utils.ReadWithPages( + func(pageID string, maxResultsPerPage int32) ([]ipam.Vlanview, string, error) { + + var paging int32 = 1 + + // If total limit is set by user and is less than maxResultsPerPage, use it as maxResultsPerPage for API call to optimize the number of results. + // If limit > maxResultsPerPage, terraform automatically breaks connection to the provider after limit is reached. + if limit < maxResultsPerPage { + maxResultsPerPage = limit + } + + //Increment the page count + pageCount++ + + request := l.client.IPAMAPI. + VlanviewAPI. + List(ctx). + Filters(flex.ExpandFrameworkMapString(ctx, data.Filters, &diags)). + Extattrfilter(flex.ExpandFrameworkMapString(ctx, data.ExtAttrFilters, &diags)). + ReturnAsObject(1). + ReturnFieldsPlus(readableAttributesForVlanview). + Paging(paging). + MaxResults(maxResultsPerPage) + + // Add page ID if provided + if pageID != "" { + request = request.PageId(pageID) + } + + // Execute the request + apiRes, _, err := request.Execute() + if err != nil { + return nil, "", err + } + + res := apiRes.ListVlanviewResponseObject.GetResult() + tflog.Info(ctx, fmt.Sprintf("Page %d : Retrieved %d results", pageCount, len(res))) + + // Check for next page ID in additional properties + additionalProperties := apiRes.ListVlanviewResponseObject.AdditionalProperties + var nextPageID string + + // If limit is reached , we do not need to continue to make API calls, we can return the results and empty nextPageID to stop pagination. + if len(res) >= int(limit) { + nextPageID = "" + tflog.Info(ctx, "Limit reached, stopped fetching more pages.") + return res, nextPageID, nil + } + + npId, ok := additionalProperties["next_page_id"] + if ok { + if npIdStr, ok := npId.(string); ok { + nextPageID = npIdStr + } + } else { + tflog.Info(ctx, "No next page ID found. This is the last page.") + } + return res, nextPageID, nil + }, + ) + + if err != nil { + diags.AddError("Client Error", fmt.Sprintf("Unable to list Vlanview, got error: %s", err)) + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + stream.Results = func(push func(list.ListResult) bool) { + for _, item := range allResults { + result := req.NewListResult(ctx) + + // Set the Identity for each result + result.Diagnostics.Append(result.Identity.SetAttribute(ctx, path.Root("ref"), &item.Ref)...) + if result.Diagnostics.HasError() { + if !push(result) { + return + } + continue + } + + // By default, list only returns the identity. + // If IncludeResource is true, it gets the full resource and sets it in the result.Resource + if req.IncludeResource { + var extAttrsAll types.Map + item.ExtAttrs, extAttrsAll, diags = RemoveInheritedExtAttrs(ctx, extAttrsAll, *item.ExtAttrs) + result.Diagnostics.Append(result.Resource.SetAttribute(ctx, path.Root("extattrs_all"), extAttrsAll)...) + if result.Diagnostics.HasError() { + if !push(result) { + return + } + continue + } + result1 := FlattenVlanview(ctx, &item, &result.Diagnostics) + result.Diagnostics.Append(result.Resource.Set(ctx, &result1)...) + if result.Diagnostics.HasError() { + if !push(result) { + return + } + continue + } + + } + + // Push the result to the stream + if !push(result) { + return + } + } + } + +} diff --git a/internal/service/ipam/vlanview_list_test.go b/internal/service/ipam/vlanview_list_test.go new file mode 100644 index 000000000..bac0b8a03 --- /dev/null +++ b/internal/service/ipam/vlanview_list_test.go @@ -0,0 +1,154 @@ +package ipam_test + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + "github.com/infobloxopen/infoblox-nios-go-client/ipam" + + "github.com/infobloxopen/terraform-provider-nios/internal/acctest" +) + +func TestAccVlanviewList_basic(t *testing.T) { + var resourceName = "nios_ipam_vlanview.test" + var v ipam.Vlanview + name := acctest.RandomNameWithPrefix("vlan_view") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + Steps: []resource.TestStep{ + // Create and Read + { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Config: testAccVlanviewBasicConfig(15, name, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckVlanviewExists(context.Background(), resourceName, &v), + ), + }, + // Query the object + { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccVlanviewListBasicConfig(), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLengthAtLeast("nios_ipam_vlanview.test", 1), + }, + }, + }, + }) +} + +func TestAccVlanviewList_Filters(t *testing.T) { + var resourceName = "nios_ipam_vlanview.test" + var v ipam.Vlanview + name := acctest.RandomNameWithPrefix("vlan_view") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + Steps: []resource.TestStep{ + // Create and Read + { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Config: testAccVlanviewBasicConfig(15, name, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckVlanviewExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "name", name), + ), + }, + // Query the object + { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccVlanviewListConfigFilters(name), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_ipam_vlanview.test", 1), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccVlanviewList_ExtAttrFilters(t *testing.T) { + var resourceName = "nios_ipam_vlanview.test_extattrs" + var v ipam.Vlanview + name := acctest.RandomNameWithPrefix("vlan_view") + + extAttrValue := acctest.RandomName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_14_0), + }, + Steps: []resource.TestStep{ + // Create and Read + { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Config: testAccVlanviewExtAttrs(15, name, 10, map[string]string{ + "Site": extAttrValue, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheckVlanviewExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "extattrs.Site", extAttrValue), + ), + }, + // Query the object + { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccVlanviewListConfigExtAttrFilters(extAttrValue), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_ipam_vlanview.test", 1), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccVlanviewListBasicConfig() string { + return ` +list "nios_ipam_vlanview" "test" { + provider = nios + limit = 5 +} +` +} + +func testAccVlanviewListConfigFilters(name string) string { + return fmt.Sprintf(` +list "nios_ipam_vlanview" "test" { + provider = nios + config { + filters = { + name = %q + } + } +} +`, name) +} + +func testAccVlanviewListConfigExtAttrFilters(name string) string { + return fmt.Sprintf(` +list "nios_ipam_vlanview" "test" { + provider = nios + config { + extattrfilters = { + Site = %q + } + } +} +`, name) +} diff --git a/internal/service/ipam/vlanview_resource.go b/internal/service/ipam/vlanview_resource.go index 5b1aefcae..79c00c37f 100644 --- a/internal/service/ipam/vlanview_resource.go +++ b/internal/service/ipam/vlanview_resource.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" "github.com/hashicorp/terraform-plugin-framework/resource/schema" niosclient "github.com/infobloxopen/infoblox-nios-go-client/client" @@ -24,6 +25,7 @@ var readableAttributesForVlanview = "allow_range_overlapping,comment,end_vlan_id var _ resource.Resource = &VlanviewResource{} var _ resource.ResourceWithImportState = &VlanviewResource{} var _ resource.ResourceWithValidateConfig = &VlanviewResource{} +var _ resource.ResourceWithIdentity = &VlanviewResource{} func NewVlanviewResource() resource.Resource { return &VlanviewResource{} @@ -36,6 +38,9 @@ type VlanviewResource struct { func (r *VlanviewResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_" + "ipam_vlanview" + resp.ResourceBehavior = resource.ResourceBehavior{ + MutableIdentity: true, + } } func (r *VlanviewResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { @@ -45,6 +50,16 @@ func (r *VlanviewResource) Schema(ctx context.Context, req resource.SchemaReques } } +func (r *VlanviewResource) IdentitySchema(ctx context.Context, req resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { + resp.IdentitySchema = identityschema.Schema{ + Attributes: map[string]identityschema.Attribute{ + "ref": identityschema.StringAttribute{ + RequiredForImport: true, + }, + }, + } +} + func (r *VlanviewResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { @@ -79,6 +94,7 @@ func (r *VlanviewResource) Create(ctx context.Context, req resource.CreateReques // Add internal ID exists in the Extensible Attributes if not already present data.ExtAttrs, diags = AddInternalIDToExtAttrs(ctx, data.ExtAttrs, diags) if diags.HasError() { + resp.Diagnostics.Append(diags...) return } @@ -124,12 +140,16 @@ func (r *VlanviewResource) Create(ctx context.Context, req resource.CreateReques res := apiRes.CreateVlanviewResponseAsObject.GetResult() res.ExtAttrs, data.ExtAttrsAll, diags = RemoveInheritedExtAttrs(ctx, data.ExtAttrs, *res.ExtAttrs) if diags.HasError() { + resp.Diagnostics.Append(diags...) resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error while create Vlanview due inherited Extensible attributes, got error: %s", err)) return } data.Flatten(ctx, &res, &resp.Diagnostics) + // Save the Identity of the Resource + resp.Diagnostics.Append(resp.Identity.SetAttribute(ctx, path.Root("ref"), &data.Ref)...) + // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -210,12 +230,16 @@ func (r *VlanviewResource) Read(ctx context.Context, req resource.ReadRequest, r res.ExtAttrs, data.ExtAttrsAll, diags = RemoveInheritedExtAttrs(ctx, data.ExtAttrs, *res.ExtAttrs) if diags.HasError() { + resp.Diagnostics.Append(diags...) resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error while reading Vlanview due inherited Extensible attributes, got error: %s", diags)) return } data.Flatten(ctx, &res, &resp.Diagnostics) + // Save the Identity of the Resource + resp.Diagnostics.Append(resp.Identity.SetAttribute(ctx, path.Root("ref"), &data.Ref)...) + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -271,6 +295,10 @@ func (r *VlanviewResource) ReadByExtAttrs(ctx context.Context, data *VlanviewMod } data.Flatten(ctx, &res, &resp.Diagnostics) + + // Save the Identity of the Resource + resp.Diagnostics.Append(resp.Identity.SetAttribute(ctx, path.Root("ref"), &data.Ref)...) + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) return true @@ -301,13 +329,14 @@ func (r *VlanviewResource) Update(ctx context.Context, req resource.UpdateReques } associateInternalId, diags := req.Private.GetKey(ctx, "associate_internal_id") - resp.Diagnostics.Append(diags...) if diags.HasError() { + resp.Diagnostics.Append(diags...) return } if associateInternalId != nil { data.ExtAttrs, diags = AddInternalIDToExtAttrs(ctx, data.ExtAttrs, diags) if diags.HasError() { + resp.Diagnostics.Append(diags...) return } } @@ -356,12 +385,16 @@ func (r *VlanviewResource) Update(ctx context.Context, req resource.UpdateReques res.ExtAttrs, data.ExtAttrsAll, diags = RemoveInheritedExtAttrs(ctx, planExtAttrs, *res.ExtAttrs) if diags.HasError() { + resp.Diagnostics.Append(diags...) resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error while update Vlanview due inherited Extensible attributes, got error: %s", diags)) return } data.Flatten(ctx, &res, &resp.Diagnostics) + // Save the Identity of the Resource + resp.Diagnostics.Append(resp.Identity.SetAttribute(ctx, path.Root("ref"), &data.Ref)...) + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) if associateInternalId != nil { @@ -430,6 +463,13 @@ func (r *VlanviewResource) ValidateConfig(ctx context.Context, req resource.Vali } func (r *VlanviewResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + if req.Identity.Raw.IsKnown() { + diags := req.Identity.GetAttribute(ctx, path.Root("ref"), &req.ID) + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + } resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("ref"), req.ID)...) resp.Diagnostics.Append(resp.Private.SetKey(ctx, "associate_internal_id", []byte("true"))...) }