From 72a1297d2e76e3599d6a9778e06cbbdfe529ec77 Mon Sep 17 00:00:00 2001 From: Ujjwal Nasra <125353741+unasra@users.noreply.github.com> Date: Fri, 20 Feb 2026 14:07:16 +0530 Subject: [PATCH 01/13] Initial Commit for List Resource for Record A --- go.mod | 10 +- go.sum | 27 +-- internal/provider/provider.go | 10 + internal/service/dns/record_a_list.go | 153 +++++++++++++ internal/service/dns/record_a_list_test.go | 61 +++++ internal/service/dns/record_a_resource.go | 32 +++ .../github.com/hashicorp/hcl/v2/.go-version | 1 + .../hashicorp/hcl/v2/.golangci.yaml | 18 ++ .../github.com/hashicorp/hcl/v2/CHANGELOG.md | 18 ++ .../github.com/hashicorp/hcl/v2/diagnostic.go | 8 +- .../hashicorp/hcl/v2/diagnostic_text.go | 55 ++++- vendor/github.com/hashicorp/hcl/v2/doc.go | 32 +-- .../hashicorp/hcl/v2/hclsyntax/expression.go | 17 ++ .../hcl/v2/hclsyntax/expression_ops.go | 108 ++++++++- .../hcl/v2/hclsyntax/expression_template.go | 8 +- .../hashicorp/hcl/v2/hclsyntax/structure.go | 2 +- .../hashicorp/hcl/v2/hclsyntax/variables.go | 1 + vendor/github.com/hashicorp/hcl/v2/merged.go | 30 ++- vendor/github.com/hashicorp/hcl/v2/ops.go | 7 +- .../hashicorp/hcl/v2/traversal_for_expr.go | 22 +- .../internal/fieldutils/field_maps.go | 3 + .../internal/hclogutils/args.go | 3 + .../internal/hclogutils/logger_options.go | 3 + .../internal/logging/filtering.go | 3 + .../internal/logging/log.go | 3 + .../internal/logging/options.go | 3 + .../internal/logging/provider.go | 3 + .../internal/logging/sdk.go | 3 + .../internal/logging/sink.go | 3 + .../terraform-plugin-log/tflog/doc.go | 3 + .../terraform-plugin-log/tflog/options.go | 3 + .../terraform-plugin-log/tflog/provider.go | 3 + .../terraform-plugin-log/tflog/subsystem.go | 3 + .../terraform-plugin-log/tfsdklog/doc.go | 3 + .../terraform-plugin-log/tfsdklog/levels.go | 3 + .../terraform-plugin-log/tfsdklog/options.go | 3 + .../terraform-plugin-log/tfsdklog/sdk.go | 3 + .../terraform-plugin-log/tfsdklog/sink.go | 60 ++++- .../tfsdklog/subsystem.go | 3 + .../v2/helper/schema/core_schema.go | 37 +++ .../v2/helper/schema/grpc_provider.go | 154 ++++++++++++- .../v2/helper/schema/resource_data.go | 103 +++++++++ .../v2/internal/plugin/convert/schema.go | 3 +- .../v2/internal/plugin/convert/value.go | 213 ++++++++++++++++++ .../terraform-plugin-sdk/v2/meta/meta.go | 2 +- .../helper/resource/query/query_checks.go | 96 ++++++++ .../helper/resource/testing.go | 13 ++ .../helper/resource/testing_new.go | 42 +++- .../helper/resource/testing_new_config.go | 7 + .../helper/resource/testing_new_query.go | 47 ++++ .../internal/logging/context.go | 2 +- .../internal/plugintest/working_dir.go | 121 +++++++++- .../internal/teststep/config.go | 6 +- .../internal/teststep/directory.go | 4 + .../internal/teststep/file.go | 4 + .../internal/teststep/string.go | 14 ++ .../querycheck/doc.go | 5 + .../querycheck/expect_identity.go | 105 +++++++++ .../querycheck/expect_no_identity.go | 89 ++++++++ .../expect_resource_display_name.go | 70 ++++++ .../expect_resource_known_values.go | 93 ++++++++ .../expect_result_length_atleast.go | 39 ++++ .../querycheck/expect_result_length_exact.go | 39 ++++ .../querycheck/known_value.go | 19 ++ .../querycheck/query_check.go | 43 ++++ .../querycheck/queryfilter/filter.go | 32 +++ .../queryfilter/filter_by_display_name.go | 29 +++ .../filter_by_resource_identity.go | 59 +++++ .../statecheck/expect_identity.go | 8 +- .../tfversion/versions.go | 2 + .../mitchellh/go-wordwrap/wordwrap.go | 26 ++- vendor/modules.txt | 23 +- 72 files changed, 2154 insertions(+), 129 deletions(-) create mode 100644 internal/service/dns/record_a_list.go create mode 100644 internal/service/dns/record_a_list_test.go create mode 100644 vendor/github.com/hashicorp/hcl/v2/.go-version create mode 100644 vendor/github.com/hashicorp/hcl/v2/.golangci.yaml create mode 100644 vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert/value.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/query/query_checks.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_query.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/doc.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_identity.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_no_identity.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_display_name.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_known_values.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_atleast.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_exact.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/known_value.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/query_check.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_display_name.go create mode 100644 vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_resource_identity.go diff --git a/go.mod b/go.mod index 271adb659..c6527f725 100644 --- a/go.mod +++ b/go.mod @@ -10,9 +10,9 @@ require ( github.com/hashicorp/terraform-plugin-framework-timetypes v0.5.0 github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 github.com/hashicorp/terraform-plugin-go v0.29.0 - github.com/hashicorp/terraform-plugin-log v0.9.0 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 - github.com/hashicorp/terraform-plugin-testing v1.13.3 + github.com/hashicorp/terraform-plugin-log v0.10.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 + github.com/hashicorp/terraform-plugin-testing v1.14.0 github.com/infobloxopen/infoblox-nios-go-client v0.1.2-0.20260204052524-0374b49eb8fb golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc ) @@ -45,7 +45,7 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hc-install v0.9.2 // indirect - github.com/hashicorp/hcl/v2 v2.23.0 // indirect + github.com/hashicorp/hcl/v2 v2.24.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.24.0 // indirect github.com/hashicorp/terraform-json v0.27.2 // indirect @@ -59,7 +59,7 @@ require ( github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.2.0 // indirect diff --git a/go.sum b/go.sum index 163947212..8f41e12bd 100644 --- a/go.sum +++ b/go.sum @@ -96,8 +96,8 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24= github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I= -github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= -github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.24.0 h1:mL0xlk9H5g2bn0pPF6JQZk5YlByqSqrO5VoaNtAf8OE= @@ -116,12 +116,12 @@ github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/ github.com/hashicorp/terraform-plugin-framework-validators v0.19.0/go.mod h1:GBKTNGbGVJohU03dZ7U8wHqc2zYnMUawgCN+gC0itLc= github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU= github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM= -github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= -github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsore1ZaRWU9cnB6jFoBnIM= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA= -github.com/hashicorp/terraform-plugin-testing v1.13.3 h1:QLi/khB8Z0a5L54AfPrHukFpnwsGL8cwwswj4RZduCo= -github.com/hashicorp/terraform-plugin-testing v1.13.3/go.mod h1:WHQ9FDdiLoneey2/QHpGM/6SAYf4A7AZazVg7230pLE= +github.com/hashicorp/terraform-plugin-log v0.10.0 h1:eu2kW6/QBVdN4P3Ju2WiB2W3ObjkAsyfBsL3Wh1fj3g= +github.com/hashicorp/terraform-plugin-log v0.10.0/go.mod h1:/9RR5Cv2aAbrqcTSdNmY1NRHP4E3ekrXRGjqORpXyB0= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 h1:mlAq/OrMlg04IuJT7NpefI1wwtdpWudnEmjuQs04t/4= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1/go.mod h1:GQhpKVvvuwzD79e8/NZ+xzj+ZpWovdPAe8nfV/skwNU= +github.com/hashicorp/terraform-plugin-testing v1.14.0 h1:5t4VKrjOJ0rg0sVuSJ86dz5K7PHsMO6OKrHFzDBerWA= +github.com/hashicorp/terraform-plugin-testing v1.14.0/go.mod h1:1qfWkecyYe1Do2EEOK/5/WnTyvC8wQucUkkhiGLg5nk= github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -142,8 +142,8 @@ github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -163,8 +163,8 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1 github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -297,8 +297,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c967eaf76..d6be72a89 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -6,6 +6,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/list" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -35,6 +36,8 @@ import ( // Ensure NIOSProvider satisfies various provider interfaces. var _ provider.Provider = &NIOSProvider{} +var _ provider.ProviderWithListResources = &NIOSProvider{} + const terraformInternalIDEA = "Terraform Internal ID" // NIOSProvider defines the provider implementation. @@ -115,6 +118,7 @@ func (p *NIOSProvider) Configure(ctx context.Context, req provider.ConfigureRequ } resp.DataSourceData = client resp.ResourceData = client + resp.ListResourceData = client } func (p *NIOSProvider) Resources(_ context.Context) []func() resource.Resource { @@ -398,6 +402,12 @@ func (p *NIOSProvider) DataSources(ctx context.Context) []func() datasource.Data } } +func (p *NIOSProvider) ListResources(ctx context.Context) []func() list.ListResource { + return []func() list.ListResource{ + dns.NewRecordAList, + } +} + func New(version, commit string) func() provider.Provider { return func() provider.Provider { return &NIOSProvider{ diff --git a/internal/service/dns/record_a_list.go b/internal/service/dns/record_a_list.go new file mode 100644 index 000000000..1000ed4a5 --- /dev/null +++ b/internal/service/dns/record_a_list.go @@ -0,0 +1,153 @@ +package dns + +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" + niosclient "github.com/infobloxopen/infoblox-nios-go-client/client" + "github.com/infobloxopen/terraform-provider-nios/internal/flex" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ list.ListResource = &RecordAList{} +var _ list.ListResourceWithConfigure = &RecordAList{} + +func NewRecordAList() list.ListResource { + return &RecordAList{} +} + +// RecordAList defines the data source implementation. +type RecordAList struct { + client *niosclient.APIClient +} + +func (l *RecordAList) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + "dns_record_a" +} + +func (l *RecordAList) 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 RecordAListModel struct { + Filters types.Map `tfsdk:"filters"` + ExtAttrFilters types.Map `tfsdk:"extattrfilters"` +} + +func (l *RecordAList) ListResourceConfigSchema(ctx context.Context, req list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Retrieves information about existing DNS A Records.", + Attributes: map[string]schema.Attribute{ + "filters": schema.MapAttribute{ + MarkdownDescription: "Filter parameters for querying DNS A records.", + ElementType: types.StringType, + Optional: true, + }, + "extattrfilters": schema.MapAttribute{ + MarkdownDescription: "Extensible attribute filters for querying DNS A records.", + ElementType: types.StringType, + Optional: true, + }, + }, + } +} + +func (l *RecordAList) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) { + var data RecordAListModel + + diags := req.Config.Get(ctx, &data) + if diags.HasError() { + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + apiRes, _, err := l.client.DNSAPI.RecordAAPI. + List(ctx). + Filters(flex.ExpandFrameworkMapString(ctx, data.Filters, &diags)). + Extattrfilter(flex.ExpandFrameworkMapString(ctx, data.ExtAttrFilters, &diags)). + ReturnAsObject(1). + ReturnFieldsPlus(readableAttributesForRecordA). + MaxResults(int32(req.Limit)). + Execute() + + if err != nil { + diags.AddError("Client Error", fmt.Sprintf("Unable to list RecordA, got error: %s", err)) + stream.Results = list.ListResultsStreamDiagnostics(diags) + return + } + + if apiRes == nil || apiRes.ListRecordAResponseObject == nil { + // No results found, return empty stream + stream.Results = func(push func(list.ListResult) bool) {} + return + } + + res := apiRes.ListRecordAResponseObject.GetResult() + if res == nil { + // No results found, return empty stream + stream.Results = func(push func(list.ListResult) bool) {} + return + } + + stream.Results = func(push func(list.ListResult) bool) { + for _, item := range res { + result := req.NewListResult(ctx) + + //result.Diagnostics.Append(result.Identity.Set(ctx, identityData)...) + result.Diagnostics.Append(result.Identity.SetAttribute(ctx, path.Root("ref"), &item.Ref)...) + if result.Diagnostics.HasError() { + if !push(result) { + return + } + continue + } + + 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 := FlattenRecordA(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/dns/record_a_list_test.go b/internal/service/dns/record_a_list_test.go new file mode 100644 index 000000000..7f7585446 --- /dev/null +++ b/internal/service/dns/record_a_list_test.go @@ -0,0 +1,61 @@ +package dns_test + +import ( + "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/terraform-provider-nios/internal/acctest" +) + +func TestAccRecordAList_basic(t *testing.T) { + //var resourceName = "nios_dns_record_a.test" + //var v dns.RecordA + //name := acctest.RandomName() + ".example.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: basicConfig(), + }, + { + Query: true, + Config: testAccRecordAListBasicConfig(), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_dns_record_a.test", 3), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func basicConfig() string { + return ` + terraform { + required_providers { + nios = { + source = "registry.terraform.io/infobloxopen/nios" + version = "1.0.0" + } + } + required_version = ">= 1.8.0" +} +` +} + +func testAccRecordAListBasicConfig() string { + return ` +list "nios_dns_record_a" "test" { + provider = nios + include_resource = true +} +` +} diff --git a/internal/service/dns/record_a_resource.go b/internal/service/dns/record_a_resource.go index 284ccb4f0..66c844a50 100644 --- a/internal/service/dns/record_a_resource.go +++ b/internal/service/dns/record_a_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" "github.com/hashicorp/terraform-plugin-framework/types" @@ -22,6 +23,7 @@ var readableAttributesForRecordA = "aws_rte53_record_info,cloud_info,comment,cre // Ensure provider defined types fully satisfy framework interfaces. var _ resource.Resource = &RecordAResource{} var _ resource.ResourceWithImportState = &RecordAResource{} +var _ resource.ResourceWithIdentity = &RecordAResource{} func NewRecordAResource() resource.Resource { return &RecordAResource{} @@ -43,6 +45,16 @@ func (r *RecordAResource) Schema(ctx context.Context, req resource.SchemaRequest } } +func (r *RecordAResource) 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 *RecordAResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { @@ -112,6 +124,9 @@ func (r *RecordAResource) Create(ctx context.Context, req resource.CreateRequest data.FuncCall = types.ObjectValueMust(FuncCallAttrTypes, origFunCallAttrs) } + // 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)...) } @@ -182,6 +197,9 @@ func (r *RecordAResource) Read(ctx context.Context, req resource.ReadRequest, 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)...) } @@ -237,6 +255,10 @@ func (r *RecordAResource) ReadByExtAttrs(ctx context.Context, data *RecordAModel } 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 @@ -308,6 +330,9 @@ func (r *RecordAResource) Update(ctx context.Context, req resource.UpdateRequest 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)...) @@ -354,6 +379,13 @@ func (r *RecordAResource) UpdateFuncCallAttributeName(ctx context.Context, data } func (r *RecordAResource) 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"))...) } diff --git a/vendor/github.com/hashicorp/hcl/v2/.go-version b/vendor/github.com/hashicorp/hcl/v2/.go-version new file mode 100644 index 000000000..a1b6e17d6 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/v2/.go-version @@ -0,0 +1 @@ +1.23 diff --git a/vendor/github.com/hashicorp/hcl/v2/.golangci.yaml b/vendor/github.com/hashicorp/hcl/v2/.golangci.yaml new file mode 100644 index 000000000..719498be6 --- /dev/null +++ b/vendor/github.com/hashicorp/hcl/v2/.golangci.yaml @@ -0,0 +1,18 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +version: "2" +issues: + max-issues-per-linter: 0 # show all issues found by each linter + max-same-issues: 0 # don't ignore same issues +linters: + exclusions: + rules: + - path: hclsyntax/scan_string_lit.go # generated file, ignore errors + linters: + - unused + - staticcheck + - path: hclsyntax/scan_tokens.go # generated file, ignore errors + linters: + - unused + - staticcheck diff --git a/vendor/github.com/hashicorp/hcl/v2/CHANGELOG.md b/vendor/github.com/hashicorp/hcl/v2/CHANGELOG.md index b2ff2631d..1845e7df5 100644 --- a/vendor/github.com/hashicorp/hcl/v2/CHANGELOG.md +++ b/vendor/github.com/hashicorp/hcl/v2/CHANGELOG.md @@ -1,5 +1,23 @@ # HCL Changelog +## v2.24.0 (July 7, 2025) + +### Enhancements + +* Add support for decoding block and attribute source ranges when using `gohcl`. ([#703](https://github.com/hashicorp/hcl/pull/703)) +* hclsyntax: Detect and reject invalid nested splat result. ([#724](https://github.com/hashicorp/hcl/pull/724)) + +### Bugs Fixed + +* Correct handling of unknown objects in Index function. ([#763](https://github.com/hashicorp/hcl/pull/763)) + +## v2.23.0 (November 15, 2024) + +### Bugs Fixed + +* Preserve marks when traversing through unknown values. ([#699](https://github.com/hashicorp/hcl/pull/699)) +* Retain marks through conditional and for expressions. ([#710](https://github.com/hashicorp/hcl/pull/710)) + ## v2.22.0 (August 26, 2024) ### Enhancements diff --git a/vendor/github.com/hashicorp/hcl/v2/diagnostic.go b/vendor/github.com/hashicorp/hcl/v2/diagnostic.go index 578f81a2c..a61bb3066 100644 --- a/vendor/github.com/hashicorp/hcl/v2/diagnostic.go +++ b/vendor/github.com/hashicorp/hcl/v2/diagnostic.go @@ -106,10 +106,10 @@ func (d *Diagnostic) Error() string { // APIs that normally deal in vanilla Go errors. func (d Diagnostics) Error() string { count := len(d) - switch { - case count == 0: + switch count { + case 0: return "no diagnostics" - case count == 1: + case 1: return d[0].Error() default: return fmt.Sprintf("%s, and %d other diagnostic(s)", d[0].Error(), count-1) @@ -121,7 +121,7 @@ func (d Diagnostics) Error() string { // This is provided as a convenience for returning from a function that // collects and then returns a set of diagnostics: // -// return nil, diags.Append(&hcl.Diagnostic{ ... }) +// return nil, diags.Append(&hcl.Diagnostic{ ... }) // // Note that this modifies the array underlying the diagnostics slice, so // must be used carefully within a single codepath. It is incorrect (and rude) diff --git a/vendor/github.com/hashicorp/hcl/v2/diagnostic_text.go b/vendor/github.com/hashicorp/hcl/v2/diagnostic_text.go index bdfad42bf..294eb1848 100644 --- a/vendor/github.com/hashicorp/hcl/v2/diagnostic_text.go +++ b/vendor/github.com/hashicorp/hcl/v2/diagnostic_text.go @@ -71,7 +71,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error { severityStr = "???????" } - fmt.Fprintf(w.wr, "%s%s%s: %s\n\n", colorCode, severityStr, resetCode, diag.Summary) + _, err := fmt.Fprintf(w.wr, "%s%s%s: %s\n\n", colorCode, severityStr, resetCode, diag.Summary) + if err != nil { + return fmt.Errorf("write failed: %w", err) + } if diag.Subject != nil { snipRange := *diag.Subject @@ -97,7 +100,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error { file := w.files[diag.Subject.Filename] if file == nil || file.Bytes == nil { - fmt.Fprintf(w.wr, " on %s line %d:\n (source code not available)\n\n", diag.Subject.Filename, diag.Subject.Start.Line) + _, err = fmt.Fprintf(w.wr, " on %s line %d:\n (source code not available)\n\n", diag.Subject.Filename, diag.Subject.Start.Line) + if err != nil { + return fmt.Errorf("write failed: %w", err) + } } else { var contextLine string @@ -108,7 +114,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error { } } - fmt.Fprintf(w.wr, " on %s line %d%s:\n", diag.Subject.Filename, diag.Subject.Start.Line, contextLine) + _, err = fmt.Fprintf(w.wr, " on %s line %d%s:\n", diag.Subject.Filename, diag.Subject.Start.Line, contextLine) + if err != nil { + return fmt.Errorf("write failed: %w", err) + } src := file.Bytes sc := NewRangeScanner(src, diag.Subject.Filename, bufio.ScanLines) @@ -121,23 +130,32 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error { beforeRange, highlightedRange, afterRange := lineRange.PartitionAround(highlightRange) if highlightedRange.Empty() { - fmt.Fprintf(w.wr, "%4d: %s\n", lineRange.Start.Line, sc.Bytes()) + _, err = fmt.Fprintf(w.wr, "%4d: %s\n", lineRange.Start.Line, sc.Bytes()) + if err != nil { + return fmt.Errorf("write failed: %w", err) + } } else { before := beforeRange.SliceBytes(src) highlighted := highlightedRange.SliceBytes(src) after := afterRange.SliceBytes(src) - fmt.Fprintf( + _, err = fmt.Fprintf( w.wr, "%4d: %s%s%s%s%s\n", lineRange.Start.Line, before, highlightCode, highlighted, resetCode, after, ) + if err != nil { + return fmt.Errorf("write failed: %w", err) + } } } - w.wr.Write([]byte{'\n'}) + _, err = w.wr.Write([]byte{'\n'}) + if err != nil { + return fmt.Errorf("write failed: %w", err) + } } if diag.Expression != nil && diag.EvalContext != nil { @@ -182,16 +200,26 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error { for i, stmt := range stmts { switch i { case 0: - w.wr.Write([]byte{'w', 'i', 't', 'h', ' '}) + _, err = w.wr.Write([]byte{'w', 'i', 't', 'h', ' '}) default: - w.wr.Write([]byte{' ', ' ', ' ', ' ', ' '}) + _, err = w.wr.Write([]byte{' ', ' ', ' ', ' ', ' '}) + } + if err != nil { + return fmt.Errorf("write failed: %w", err) + } + + _, err = w.wr.Write([]byte(stmt)) + if err != nil { + return fmt.Errorf("write failed: %w", err) } - w.wr.Write([]byte(stmt)) switch i { case last: - w.wr.Write([]byte{'.', '\n', '\n'}) + _, err = w.wr.Write([]byte{'.', '\n', '\n'}) default: - w.wr.Write([]byte{',', '\n'}) + _, err = w.wr.Write([]byte{',', '\n'}) + } + if err != nil { + return fmt.Errorf("write failed: %w", err) } } } @@ -202,7 +230,10 @@ func (w *diagnosticTextWriter) WriteDiagnostic(diag *Diagnostic) error { if w.width != 0 { detail = wordwrap.WrapString(detail, w.width) } - fmt.Fprintf(w.wr, "%s\n\n", detail) + _, err = fmt.Fprintf(w.wr, "%s\n\n", detail) + if err != nil { + return fmt.Errorf("write failed: %w", err) + } } return nil diff --git a/vendor/github.com/hashicorp/hcl/v2/doc.go b/vendor/github.com/hashicorp/hcl/v2/doc.go index a0e3119f2..665ad6cad 100644 --- a/vendor/github.com/hashicorp/hcl/v2/doc.go +++ b/vendor/github.com/hashicorp/hcl/v2/doc.go @@ -9,25 +9,25 @@ // configurations in either native HCL syntax or JSON syntax into a Go struct // type: // -// package main +// package main // -// import ( -// "log" -// "github.com/hashicorp/hcl/v2/hclsimple" -// ) +// import ( +// "log" +// "github.com/hashicorp/hcl/v2/hclsimple" +// ) // -// type Config struct { -// LogLevel string `hcl:"log_level"` -// } +// type Config struct { +// LogLevel string `hcl:"log_level"` +// } // -// func main() { -// var config Config -// err := hclsimple.DecodeFile("config.hcl", nil, &config) -// if err != nil { -// log.Fatalf("Failed to load configuration: %s", err) -// } -// log.Printf("Configuration is %#v", config) -// } +// func main() { +// var config Config +// err := hclsimple.DecodeFile("config.hcl", nil, &config) +// if err != nil { +// log.Fatalf("Failed to load configuration: %s", err) +// } +// log.Printf("Configuration is %#v", config) +// } // // If your application needs more control over the evaluation of the // configuration, you can use the functions in the subdirectories hclparse, diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go index f4c3a6d79..c5a448863 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression.go @@ -1796,6 +1796,7 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { // both to tuples/lists and to other values, and in the latter case // the value will be treated as an implicit single-item tuple, or as // an empty tuple if the value is null. + //nolint:staticcheck // QF1001: Demorgan's law wouldn't improve readability. autoUpgrade := !(sourceTy.IsTupleType() || sourceTy.IsListType() || sourceTy.IsSetType()) if sourceVal.IsNull() { @@ -1938,6 +1939,22 @@ func (e *SplatExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { diags = append(diags, tyDiags...) return cty.ListValEmpty(ty.ElementType()).WithMarks(marks), diags } + // Unfortunately it's possible for a nested splat on scalar values to + // generate non-homogenously-typed vals, and we discovered this bad + // interaction after the two conflicting behaviors were both + // well-established so it isn't clear how to change them without + // breaking existing code. Therefore we just make that an error for + // now, to avoid crashing trying to constuct an impossible list. + if !cty.CanListVal(vals) { + diags = append(diags, &hcl.Diagnostic{ + Severity: hcl.DiagError, + Summary: "Invalid nested splat expressions", + Detail: "The second level of splat expression produced elements of different types, so it isn't possible to construct a valid list to represent the top-level result.\n\nConsider using a for expression instead, to produce a tuple-typed result which can therefore have non-homogenous element types.", + Subject: e.Each.Range().Ptr(), + Context: e.Range().Ptr(), // encourage a diagnostic renderer to also include the "source" part of the expression in its code snippet + }) + return cty.DynamicVal, diags + } return cty.ListVal(vals).WithMarks(marks), diags default: return cty.TupleVal(vals).WithMarks(marks), diags diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_ops.go b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_ops.go index 6585612c1..da146ef83 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_ops.go +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_ops.go @@ -16,16 +16,86 @@ import ( type Operation struct { Impl function.Function Type cty.Type + + // ShortCircuit is an optional callback for binary operations which, if set, + // will be called with the result of evaluating the LHS and RHS expressions + // and their individual diagnostics. The LHS and RHS values are guaranteed + // to be unmarked and of the correct type. + // + // ShortCircuit may return cty.NilVal to allow evaluation to proceed as + // normal, or it may return a non-nil value with diagnostics to return + // before the main Impl is called. The returned diagnostics should match + // the side of the Operation which was taken. + ShortCircuit func(lhs, rhs cty.Value, lhsDiags, rhsDiags hcl.Diagnostics) (cty.Value, hcl.Diagnostics) } var ( OpLogicalOr = &Operation{ Impl: stdlib.OrFunc, Type: cty.Bool, + + ShortCircuit: func(lhs, rhs cty.Value, lhsDiags, rhsDiags hcl.Diagnostics) (cty.Value, hcl.Diagnostics) { + switch { + // if both are unknown, we don't short circuit anything + case !lhs.IsKnown() && !rhs.IsKnown(): + // short-circuit left-to-right when encountering a good unknown + // value and both are unknown. + if !lhsDiags.HasErrors() { + return cty.UnknownVal(cty.Bool).RefineNotNull(), lhsDiags + } + // If the LHS has an error, the RHS might too. Don't + // short-circuit so both diags get collected. + return cty.NilVal, nil + + // for ||, a single true is the controlling condition + case lhs.IsKnown() && lhs.True(): + return cty.True, lhsDiags + case rhs.IsKnown() && rhs.True(): + return cty.True, rhsDiags + + // if the opposing side is false we can't short-circuit based on + // boolean logic, so an unknown becomes the controlling condition + case !lhs.IsKnown() && rhs.False(): + return cty.UnknownVal(cty.Bool).RefineNotNull(), lhsDiags + case !rhs.IsKnown() && lhs.False(): + return cty.UnknownVal(cty.Bool).RefineNotNull(), rhsDiags + } + + return cty.NilVal, nil + }, } OpLogicalAnd = &Operation{ Impl: stdlib.AndFunc, Type: cty.Bool, + + ShortCircuit: func(lhs, rhs cty.Value, lhsDiags, rhsDiags hcl.Diagnostics) (cty.Value, hcl.Diagnostics) { + + switch { + case !lhs.IsKnown() && !rhs.IsKnown(): + // short-circuit left-to-right when encountering a good unknown + // value and both are unknown. + if !lhsDiags.HasErrors() { + return cty.UnknownVal(cty.Bool).RefineNotNull(), lhsDiags + } + // If the LHS has an error, the RHS might too. Don't + // short-circuit so both diags get collected. + return cty.NilVal, nil + + // For &&, a single false is the controlling condition + case lhs.IsKnown() && lhs.False(): + return cty.False, lhsDiags + case rhs.IsKnown() && rhs.False(): + return cty.False, rhsDiags + + // if the opposing side is true we can't short-circuit based on + // boolean logic, so an unknown becomes the controlling condition + case !lhs.IsKnown() && rhs.True(): + return cty.UnknownVal(cty.Bool).RefineNotNull(), lhsDiags + case !rhs.IsKnown() && lhs.True(): + return cty.UnknownVal(cty.Bool).RefineNotNull(), rhsDiags + } + return cty.NilVal, nil + }, } OpLogicalNot = &Operation{ Impl: stdlib.NotFunc, @@ -145,10 +215,6 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) var diags hcl.Diagnostics givenLHSVal, lhsDiags := e.LHS.Value(ctx) - givenRHSVal, rhsDiags := e.RHS.Value(ctx) - diags = append(diags, lhsDiags...) - diags = append(diags, rhsDiags...) - lhsVal, err := convert.Convert(givenLHSVal, lhsParam.Type) if err != nil { diags = append(diags, &hcl.Diagnostic{ @@ -161,6 +227,8 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) EvalContext: ctx, }) } + + givenRHSVal, rhsDiags := e.RHS.Value(ctx) rhsVal, err := convert.Convert(givenRHSVal, rhsParam.Type) if err != nil { diags = append(diags, &hcl.Diagnostic{ @@ -174,12 +242,38 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) }) } + // diags so far only contains conversion errors, which should cover + // incorrect parameter types. if diags.HasErrors() { - // Don't actually try the call if we have errors already, since the - // this will probably just produce a confusing duplicative diagnostic. + // Add the rest of the diagnostic in case that helps the user, but keep + // them separate as we continue for short-circuit handling. + diags = append(diags, lhsDiags...) + diags = append(diags, rhsDiags...) return cty.UnknownVal(e.Op.Type), diags } + lhsVal, lhsMarks := lhsVal.Unmark() + rhsVal, rhsMarks := rhsVal.Unmark() + + if e.Op.ShortCircuit != nil { + forceResult, diags := e.Op.ShortCircuit(lhsVal, rhsVal, lhsDiags, rhsDiags) + if forceResult != cty.NilVal { + // It would be technically more correct to insert rhs diagnostics if + // forceResult is not known since we didn't really short-circuit. That + // would however not match the behavior of conditional expressions which + // do drop all diagnostics from the unevaluated expressions + return forceResult.WithMarks(lhsMarks, rhsMarks), diags + } + } + + diags = append(diags, lhsDiags...) + diags = append(diags, rhsDiags...) + if diags.HasErrors() { + // Don't actually try the call if we have errors, since the this will + // probably just produce confusing duplicate diagnostics. + return cty.UnknownVal(e.Op.Type).WithMarks(lhsMarks, rhsMarks), diags + } + args := []cty.Value{lhsVal, rhsVal} result, err := impl.Call(args) if err != nil { @@ -195,7 +289,7 @@ func (e *BinaryOpExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) return cty.UnknownVal(e.Op.Type), diags } - return result, diags + return result.WithMarks(lhsMarks, rhsMarks), diags } func (e *BinaryOpExpr) Range() hcl.Range { diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_template.go b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_template.go index a0dc7c229..5713c004d 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_template.go +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/expression_template.go @@ -184,11 +184,9 @@ func (e *TemplateJoinExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnosti if val.IsNull() { diags = append(diags, &hcl.Diagnostic{ - Severity: hcl.DiagError, - Summary: "Invalid template interpolation value", - Detail: fmt.Sprintf( - "An iteration result is null. Cannot include a null value in a string template.", - ), + Severity: hcl.DiagError, + Summary: "Invalid template interpolation value", + Detail: "An iteration result is null. Cannot include a null value in a string template.", Subject: e.Range().Ptr(), Expression: e, EvalContext: ctx, diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go index ff272631d..545ef42f5 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/structure.go @@ -42,7 +42,7 @@ type Body struct { } // Assert that *Body implements hcl.Body -var assertBodyImplBody hcl.Body = &Body{} +var _ hcl.Body = &Body{} func (b *Body) walkChildNodes(w internalWalkFunc) { w(b.Attributes) diff --git a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/variables.go b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/variables.go index b4be92698..af2c4601b 100644 --- a/vendor/github.com/hashicorp/hcl/v2/hclsyntax/variables.go +++ b/vendor/github.com/hashicorp/hcl/v2/hclsyntax/variables.go @@ -20,6 +20,7 @@ func Variables(expr Expression) []hcl.Traversal { }, } + //nolint:errcheck // FIXME: Propogate diagnostics/errors upward. Walk(expr, walker) return vars diff --git a/vendor/github.com/hashicorp/hcl/v2/merged.go b/vendor/github.com/hashicorp/hcl/v2/merged.go index 27fd1ed5e..49474ee74 100644 --- a/vendor/github.com/hashicorp/hcl/v2/merged.go +++ b/vendor/github.com/hashicorp/hcl/v2/merged.go @@ -107,23 +107,21 @@ func (mb mergedBodies) JustAttributes() (Attributes, Diagnostics) { diags = append(diags, thisDiags...) } - if thisAttrs != nil { - for name, attr := range thisAttrs { - if existing := attrs[name]; existing != nil { - diags = diags.Append(&Diagnostic{ - Severity: DiagError, - Summary: "Duplicate argument", - Detail: fmt.Sprintf( - "Argument %q was already set at %s", - name, existing.NameRange.String(), - ), - Subject: &attr.NameRange, - }) - continue - } - - attrs[name] = attr + for name, attr := range thisAttrs { + if existing := attrs[name]; existing != nil { + diags = diags.Append(&Diagnostic{ + Severity: DiagError, + Summary: "Duplicate argument", + Detail: fmt.Sprintf( + "Argument %q was already set at %s", + name, existing.NameRange.String(), + ), + Subject: &attr.NameRange, + }) + continue } + + attrs[name] = attr } } diff --git a/vendor/github.com/hashicorp/hcl/v2/ops.go b/vendor/github.com/hashicorp/hcl/v2/ops.go index 3cd7b205e..9420abdb1 100644 --- a/vendor/github.com/hashicorp/hcl/v2/ops.go +++ b/vendor/github.com/hashicorp/hcl/v2/ops.go @@ -195,9 +195,6 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics) }, } } - if !collection.IsKnown() { - return cty.DynamicVal.WithSameMarks(collection), nil - } if !key.IsKnown() { return cty.DynamicVal.WithSameMarks(collection), nil } @@ -225,6 +222,10 @@ func Index(collection, key cty.Value, srcRange *Range) (cty.Value, Diagnostics) } } + if !collection.IsKnown() { + return cty.UnknownVal(ty.AttributeType(attrName)).WithSameMarks(collection), nil + } + return collection.GetAttr(attrName), nil case ty.IsSetType(): diff --git a/vendor/github.com/hashicorp/hcl/v2/traversal_for_expr.go b/vendor/github.com/hashicorp/hcl/v2/traversal_for_expr.go index 87eeb1599..d3cb4507e 100644 --- a/vendor/github.com/hashicorp/hcl/v2/traversal_for_expr.go +++ b/vendor/github.com/hashicorp/hcl/v2/traversal_for_expr.go @@ -74,7 +74,7 @@ func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) { // For example, the following attribute has an expression that would produce // the keyword "foo": // -// example = foo +// example = foo // // This function is a variant of AbsTraversalForExpr, which uses the same // interface on the given expression. This helper constrains the result @@ -84,16 +84,16 @@ func RelTraversalForExpr(expr Expression) (Traversal, Diagnostics) { // situations where one of a fixed set of keywords is required and arbitrary // expressions are not allowed: // -// switch hcl.ExprAsKeyword(expr) { -// case "allow": -// // (take suitable action for keyword "allow") -// case "deny": -// // (take suitable action for keyword "deny") -// default: -// diags = append(diags, &hcl.Diagnostic{ -// // ... "invalid keyword" diagnostic message ... -// }) -// } +// switch hcl.ExprAsKeyword(expr) { +// case "allow": +// // (take suitable action for keyword "allow") +// case "deny": +// // (take suitable action for keyword "deny") +// default: +// diags = append(diags, &hcl.Diagnostic{ +// // ... "invalid keyword" diagnostic message ... +// }) +// } // // The above approach will generate the same message for both the use of an // unrecognized keyword and for not using a keyword at all, which is usually diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/internal/fieldutils/field_maps.go b/vendor/github.com/hashicorp/terraform-plugin-log/internal/fieldutils/field_maps.go index ac78d6da7..0bdcd47d6 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/internal/fieldutils/field_maps.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/internal/fieldutils/field_maps.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package fieldutils // MergeFieldMaps takes a slice of field maps, diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/internal/hclogutils/args.go b/vendor/github.com/hashicorp/terraform-plugin-log/internal/hclogutils/args.go index 44c81ab8e..43ac23bf3 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/internal/hclogutils/args.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/internal/hclogutils/args.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package hclogutils import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/internal/hclogutils/logger_options.go b/vendor/github.com/hashicorp/terraform-plugin-log/internal/hclogutils/logger_options.go index a0ec34e20..69dad9eb7 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/internal/hclogutils/logger_options.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/internal/hclogutils/logger_options.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package hclogutils import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/filtering.go b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/filtering.go index c7b9c450a..c1eceb1d8 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/filtering.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/filtering.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package logging import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/log.go b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/log.go index 4aa80bb98..6ad6c1d2d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/log.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/log.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package logging import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/options.go b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/options.go index 583dc2cbf..42a79d9c7 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/options.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/options.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package logging import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/provider.go b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/provider.go index c8f45a86c..7353cc6c0 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/provider.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/provider.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package logging import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/sdk.go b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/sdk.go index 217c83ed7..db84026b2 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/sdk.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/sdk.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package logging import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/sink.go b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/sink.go index b56ce8bfe..b1e42de0e 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/sink.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/internal/logging/sink.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package logging import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tflog/doc.go b/vendor/github.com/hashicorp/terraform-plugin-log/tflog/doc.go index 97ca21884..a168965de 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tflog/doc.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tflog/doc.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Package tflog provides helper functions for writing log output and creating // loggers for Terraform plugins. // diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tflog/options.go b/vendor/github.com/hashicorp/terraform-plugin-log/tflog/options.go index 750177812..953786a06 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tflog/options.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tflog/options.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tflog import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tflog/provider.go b/vendor/github.com/hashicorp/terraform-plugin-log/tflog/provider.go index c1a1572ce..b5a083588 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tflog/provider.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tflog/provider.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tflog import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tflog/subsystem.go b/vendor/github.com/hashicorp/terraform-plugin-log/tflog/subsystem.go index 1f66e757a..2d22ef1bb 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tflog/subsystem.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tflog/subsystem.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tflog import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/doc.go b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/doc.go index 2ed6f6fc8..13eb4c636 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/doc.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/doc.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + // Package tfsdklog provides helper functions for logging from SDKs and // frameworks for building Terraform plugins. // diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/levels.go b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/levels.go index ed475a137..71bce01be 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/levels.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/levels.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tfsdklog import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/options.go b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/options.go index b1ba8e51e..09cedb1ca 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/options.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/options.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tfsdklog import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/sdk.go b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/sdk.go index 4ffb2cc2f..9c7d4c24d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/sdk.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/sdk.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tfsdklog import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/sink.go b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/sink.go index 6326901f1..e4b3e6c93 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/sink.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/sink.go @@ -1,9 +1,13 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tfsdklog import ( "context" "fmt" "io" + "log" "os" "strings" "sync" @@ -66,8 +70,49 @@ var invalidLogLevelMessage sync.Once // // RegisterTestSink must be called prior to any loggers being setup or // instantiated. +// +// Deprecated: RegisterTestSink will be removed in a future release in order to +// drop the dependency on github.com/mitchellh/go-testing-interface, which is +// no longer maintained. Use ContextWithTestLogging instead of +// RegisterTestSink. func RegisterTestSink(ctx context.Context, t testing.T) context.Context { - logger, loggerOptions := newSink(t) + logger, loggerOptions := newTestSink(t.Name()) + + ctx = logging.SetSink(ctx, logger) + ctx = logging.SetSinkOptions(ctx, loggerOptions) + + return ctx +} + +// ContextWithStandardLogging sets up a logging sink for use with test sweepers and +// other cases where plugin logs don't get routed through Terraform and the +// built-in Go `log` package is also used. +// +// ContextWithStandardLogging should only ever be called by test sweepers, providers +// should never call it. +// +// ContextWithStandardLogging must be called prior to any loggers being setup or +// instantiated. +func ContextWithStandardLogging(ctx context.Context, testName string) context.Context { + logger, loggerOptions := newStdlogSink() + + ctx = logging.SetSink(ctx, logger) + ctx = logging.SetSinkOptions(ctx, loggerOptions) + + return ctx +} + +// ContextWithTestLogging sets up a logging sink, for use with test frameworks +// and other cases where plugin logs don't get routed through Terraform. This +// applies the same filtering and file output behaviors that Terraform does. +// +// ContextWithTestLogging should only ever be called by test frameworks, +// providers should never call it. +// +// ContextWithTestLogging must be called prior to any loggers being setup or +// instantiated. +func ContextWithTestLogging(ctx context.Context, testName string) context.Context { + logger, loggerOptions := newTestSink(testName) ctx = logging.SetSink(ctx, logger) ctx = logging.SetSinkOptions(ctx, loggerOptions) @@ -75,7 +120,7 @@ func RegisterTestSink(ctx context.Context, t testing.T) context.Context { return ctx } -func newSink(t testing.T) (hclog.Logger, *hclog.LoggerOptions) { +func newTestSink(testName string) (hclog.Logger, *hclog.LoggerOptions) { logOutput := io.Writer(os.Stderr) var json bool var logLevel hclog.Level @@ -99,7 +144,7 @@ func newSink(t testing.T) (hclog.Logger, *hclog.LoggerOptions) { // if TF_LOG_PATH_MASK is set, use a test-name specific logging file, // instead if logPathMask := os.Getenv(envLogPathMask); logPathMask != "" { - testName := strings.Replace(t.Name(), "/", "__", -1) + testName := strings.Replace(testName, "/", "__", -1) logFile = fmt.Sprintf(logPathMask, testName) } @@ -150,3 +195,12 @@ func isValidLogLevel(level string) bool { return false } + +func newStdlogSink() (hclog.Logger, *hclog.LoggerOptions) { + loggerOptions := &hclog.LoggerOptions{ + IndependentLevels: true, + JSONFormat: false, + } + + return hclog.FromStandardLogger(log.Default(), loggerOptions), loggerOptions +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/subsystem.go b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/subsystem.go index 0aeb5463c..bfed8aca7 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/subsystem.go +++ b/vendor/github.com/hashicorp/terraform-plugin-log/tfsdklog/subsystem.go @@ -1,3 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package tfsdklog import ( diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/core_schema.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/core_schema.go index 79f78a1dc..d782491d6 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/core_schema.go +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/core_schema.go @@ -4,11 +4,14 @@ package schema import ( + "context" "fmt" "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-sdk/v2/internal/configs/configschema" + "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert" ) // StringKind represents the format a string is in. @@ -397,3 +400,37 @@ func (r *Resource) coreIdentitySchema() (*configschema.Block, error) { // to convert our schema return schemaMap(r.Identity.SchemaMap()).CoreConfigSchema(), nil } + +// ProtoSchema will return a function that returns the *tfprotov5.Schema +func (r *Resource) ProtoSchema(ctx context.Context) func() *tfprotov5.Schema { + return func() *tfprotov5.Schema { + return &tfprotov5.Schema{ + Version: int64(r.SchemaVersion), + Block: convert.ConfigSchemaToProto(ctx, r.CoreConfigSchema()), + } + } +} + +// ProtoIdentitySchema will return a function that returns the *tfprotov5.ResourceIdentitySchema if the resource supports identity, +// otherwise it will return nil. +func (r *Resource) ProtoIdentitySchema(ctx context.Context) func() *tfprotov5.ResourceIdentitySchema { + // Resource doesn't support identity, return nil + if r.Identity == nil { + return nil + } + + return func() *tfprotov5.ResourceIdentitySchema { + idschema, err := r.CoreIdentitySchema() + + if err != nil { + // This shouldn't be reachable unless there is an implementation error in the provider, which should raise + // a diagnostic prior to reaching this point. + panic(fmt.Sprintf("unexpected error retrieving identity schema: %s", err)) + } + + return &tfprotov5.ResourceIdentitySchema{ + Version: r.Identity.Version, + IdentityAttributes: convert.ConfigIdentitySchemaToProto(ctx, idschema), + } + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/grpc_provider.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/grpc_provider.go index e6334e924..2e02fde7d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/grpc_provider.go +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/grpc_provider.go @@ -7,7 +7,9 @@ import ( "context" "encoding/json" "fmt" + "slices" "strconv" + "strings" "sync" "github.com/hashicorp/go-cty/cty" @@ -195,6 +197,8 @@ func (s *GRPCProviderServer) GetMetadata(ctx context.Context, req *tfprotov5.Get DataSources: make([]tfprotov5.DataSourceMetadata, 0, len(s.provider.DataSourcesMap)), EphemeralResources: make([]tfprotov5.EphemeralResourceMetadata, 0), Functions: make([]tfprotov5.FunctionMetadata, 0), + ListResources: make([]tfprotov5.ListResourceMetadata, 0), + Actions: make([]tfprotov5.ActionMetadata, 0), Resources: make([]tfprotov5.ResourceMetadata, 0, len(s.provider.ResourcesMap)), ServerCapabilities: s.serverCapabilities(), } @@ -223,6 +227,8 @@ func (s *GRPCProviderServer) GetProviderSchema(ctx context.Context, req *tfproto DataSourceSchemas: make(map[string]*tfprotov5.Schema, len(s.provider.DataSourcesMap)), EphemeralResourceSchemas: make(map[string]*tfprotov5.Schema, 0), Functions: make(map[string]*tfprotov5.Function, 0), + ListResourceSchemas: make(map[string]*tfprotov5.Schema, 0), + ActionSchemas: make(map[string]*tfprotov5.ActionSchema, 0), ResourceSchemas: make(map[string]*tfprotov5.Schema, len(s.provider.ResourcesMap)), ServerCapabilities: s.serverCapabilities(), } @@ -880,6 +886,7 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, err) return resp, nil } + // Step 2: Turn cty.Value into flatmap representation identityAttrs := hcl2shim.FlatmapValueFromHCL2(currentIdentityVal) // Step 3: Well, set it in the instanceState @@ -961,8 +968,16 @@ func (s *GRPCProviderServer) ReadResource(ctx context.Context, req *tfprotov5.Re return resp, nil } + if isCtyObjectNullOrEmpty(newIdentityVal) { + resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf( + "Missing Resource Identity After Read: The Terraform provider unexpectedly returned no resource identity after having no errors in the resource read. "+ + "This is always a problem with the provider and should be reported to the provider developer", + )) + return resp, nil + } + // If we're refreshing the resource state (excluding a recently imported resource), validate that the new identity isn't changing - if !res.ResourceBehavior.MutableIdentity && !readFollowingImport && !currentIdentityVal.IsNull() && !currentIdentityVal.RawEquals(newIdentityVal) { + if !res.ResourceBehavior.MutableIdentity && !readFollowingImport && !isCtyObjectNullOrEmpty(currentIdentityVal) && !currentIdentityVal.RawEquals(newIdentityVal) { resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf("Unexpected Identity Change: %s", "During the read operation, the Terraform Provider unexpectedly returned a different identity then the previously stored one.\n\n"+ "This is always a problem with the provider and should be reported to the provider developer.\n\n"+ fmt.Sprintf("Current Identity: %s\n\n", currentIdentityVal.GoString())+ @@ -1304,7 +1319,7 @@ func (s *GRPCProviderServer) PlanResourceChange(ctx context.Context, req *tfprot } // If we're updating or deleting and we already have an identity stored, validate that the planned identity isn't changing - if !res.ResourceBehavior.MutableIdentity && !create && !priorIdentityVal.IsNull() && !priorIdentityVal.RawEquals(plannedIdentityVal) { + if !res.ResourceBehavior.MutableIdentity && !create && !isCtyObjectNullOrEmpty(priorIdentityVal) && !priorIdentityVal.RawEquals(plannedIdentityVal) { resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf( "Unexpected Identity Change: During the planning operation, the Terraform Provider unexpectedly returned a different identity than the previously stored one.\n\n"+ "This is always a problem with the provider and should be reported to the provider developer.\n\n"+ @@ -1544,7 +1559,21 @@ func (s *GRPCProviderServer) ApplyResourceChange(ctx context.Context, req *tfpro return resp, nil } - if !res.ResourceBehavior.MutableIdentity && !create && !plannedIdentityVal.IsNull() && !plannedIdentityVal.RawEquals(newIdentityVal) { + if isCtyObjectNullOrEmpty(newIdentityVal) { + op := "Create" + if !create { + op = "Update" + } + + resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf( + "Missing Resource Identity After %s: The Terraform provider unexpectedly returned no resource identity after having no errors in the resource %s. "+ + "This is always a problem with the provider and should be reported to the provider developer", op, strings.ToLower(op), + )) + + return resp, nil + } + + if !res.ResourceBehavior.MutableIdentity && !create && !isCtyObjectNullOrEmpty(plannedIdentityVal) && !plannedIdentityVal.RawEquals(newIdentityVal) { resp.Diagnostics = convert.AppendProtoDiag(ctx, resp.Diagnostics, fmt.Errorf( "Unexpected Identity Change: During the update operation, the Terraform Provider unexpectedly returned a different identity than the previously stored one.\n\n"+ "This is always a problem with the provider and should be reported to the provider developer.\n\n"+ @@ -1980,6 +2009,110 @@ func (s *GRPCProviderServer) CloseEphemeralResource(ctx context.Context, req *tf return resp, nil } +func (s *GRPCProviderServer) ValidateListResourceConfig(ctx context.Context, req *tfprotov5.ValidateListResourceConfigRequest) (*tfprotov5.ValidateListResourceConfigResponse, error) { + ctx = logging.InitContext(ctx) + + logging.HelperSchemaTrace(ctx, "Returning error for list resource validate") + + resp := &tfprotov5.ValidateListResourceConfigResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Unknown List Resource Type", + Detail: fmt.Sprintf("The %q list resource type is not supported by this provider.", req.TypeName), + }, + }, + } + + return resp, nil +} + +func (s *GRPCProviderServer) ListResource(ctx context.Context, req *tfprotov5.ListResourceRequest) (*tfprotov5.ListResourceServerStream, error) { + ctx = logging.InitContext(ctx) + + logging.HelperSchemaTrace(ctx, "Returning error for list resource list") + + result := make([]tfprotov5.ListResourceResult, 0) + + result = append(result, tfprotov5.ListResourceResult{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Unknown List Resource Type", + Detail: fmt.Sprintf("The %q list resource type is not supported by this provider.", req.TypeName), + }, + }, + }) + + resp := &tfprotov5.ListResourceServerStream{ + Results: slices.Values(result), + } + + return resp, nil +} + +func (s *GRPCProviderServer) ValidateActionConfig(ctx context.Context, req *tfprotov5.ValidateActionConfigRequest) (*tfprotov5.ValidateActionConfigResponse, error) { + ctx = logging.InitContext(ctx) + + logging.HelperSchemaTrace(ctx, "Returning error for action type validate") + + resp := &tfprotov5.ValidateActionConfigResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Unknown Action Type", + Detail: fmt.Sprintf("The %q action type is not supported by this provider.", req.ActionType), + }, + }, + } + + return resp, nil +} + +func (s *GRPCProviderServer) PlanAction(ctx context.Context, req *tfprotov5.PlanActionRequest) (*tfprotov5.PlanActionResponse, error) { + ctx = logging.InitContext(ctx) + + logging.HelperSchemaTrace(ctx, "Returning error for action type plan") + + resp := &tfprotov5.PlanActionResponse{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Unknown Action Type", + Detail: fmt.Sprintf("The %q action type is not supported by this provider.", req.ActionType), + }, + }, + } + + return resp, nil +} + +func (s *GRPCProviderServer) InvokeAction(ctx context.Context, req *tfprotov5.InvokeActionRequest) (*tfprotov5.InvokeActionServerStream, error) { + ctx = logging.InitContext(ctx) + + logging.HelperSchemaTrace(ctx, "Returning error for action invoke") + + event := make([]tfprotov5.InvokeActionEvent, 0) + + event = append(event, tfprotov5.InvokeActionEvent{ + Type: tfprotov5.CompletedInvokeActionEventType{ + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Unknown Action Type", + Detail: fmt.Sprintf("The %q action type is not supported by this provider.", req.ActionType), + }, + }, + }, + }) + + resp := &tfprotov5.InvokeActionServerStream{ + Events: slices.Values(event), + } + + return resp, nil +} + func pathToAttributePath(path cty.Path) *tftypes.AttributePath { var steps []tftypes.AttributePathStep @@ -2345,3 +2478,18 @@ func (s *GRPCProviderServer) upgradeJSONIdentity(ctx context.Context, version in return m, nil } + +// isCtyObjectNullOrEmpty is a helper function that checks if a given cty object is null or if all it's immediate children are null (empty) +func isCtyObjectNullOrEmpty(val cty.Value) bool { + if val.IsNull() { + return true + } + + for _, v := range val.AsValueMap() { + if !v.IsNull() { + return false + } + } + + return true +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/resource_data.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/resource_data.go index 096f449d1..7569b0029 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/resource_data.go +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema/resource_data.go @@ -15,7 +15,11 @@ import ( "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/go-cty/cty/gocty" + "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/internal/configs/configschema" + "github.com/hashicorp/terraform-plugin-sdk/v2/internal/configs/hcl2shim" + "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert" "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) @@ -59,6 +63,105 @@ type getResult struct { Schema *Schema } +// TfTypeIdentityState returns the identity data as a tftypes.Value. +func (d *ResourceData) TfTypeIdentityState() (*tftypes.Value, error) { + s := schemaMap(d.identitySchema).CoreConfigSchema() + + state := d.State() + + if state == nil { + return nil, fmt.Errorf("state is nil, call SetId() on ResourceData first") + } + + stateVal, err := hcl2shim.HCL2ValueFromFlatmap(state.Identity, s.ImpliedType()) + if err != nil { + return nil, fmt.Errorf("converting identity flatmap to cty value: %+v", err) + } + + return convert.ToTfValue(stateVal) +} + +// TfTypeResourceState returns the resource data as a tftypes.Value. +func (d *ResourceData) TfTypeResourceState() (*tftypes.Value, error) { + s := schemaMap(d.schema).CoreConfigSchema() + + // The CoreConfigSchema method on schemaMaps doesn't automatically handle adding the id + // attribute or timeouts like the method on Resource does + if _, ok := s.Attributes["id"]; !ok { + s.Attributes["id"] = &configschema.Attribute{ + Type: cty.String, + Optional: true, + Computed: true, + } + } + + _, timeoutsAttr := s.Attributes[TimeoutsConfigKey] + _, timeoutsBlock := s.BlockTypes[TimeoutsConfigKey] + + if d.timeouts != nil && !timeoutsAttr && !timeoutsBlock { + timeouts := configschema.Block{ + Attributes: map[string]*configschema.Attribute{}, + } + + if d.timeouts.Create != nil { + timeouts.Attributes[TimeoutCreate] = &configschema.Attribute{ + Type: cty.String, + Optional: true, + } + } + + if d.timeouts.Read != nil { + timeouts.Attributes[TimeoutRead] = &configschema.Attribute{ + Type: cty.String, + Optional: true, + } + } + + if d.timeouts.Update != nil { + timeouts.Attributes[TimeoutUpdate] = &configschema.Attribute{ + Type: cty.String, + Optional: true, + } + } + + if d.timeouts.Delete != nil { + timeouts.Attributes[TimeoutDelete] = &configschema.Attribute{ + Type: cty.String, + Optional: true, + } + } + + if d.timeouts.Default != nil { + timeouts.Attributes[TimeoutDefault] = &configschema.Attribute{ + Type: cty.String, + Optional: true, + } + } + + if len(timeouts.Attributes) != 0 { + s.BlockTypes[TimeoutsConfigKey] = &configschema.NestedBlock{ + Nesting: configschema.NestingSingle, + Block: timeouts, + } + } + } + + state := d.State() + if state == nil { + return nil, fmt.Errorf("state is nil, call SetId() on ResourceData first") + } + + // Although we handle adding/omitting timeouts to the schema depending on how it's been defined on the resource + // we don't process or convert the timeout values since they reside in Meta and aren't needed for the purposes + // of this function and in the context of a List. + stateVal, err := hcl2shim.HCL2ValueFromFlatmap(state.Attributes, s.ImpliedType()) + if err != nil { + return nil, fmt.Errorf("converting resource state flatmap to cty value: %+v", err) + } + + return convert.ToTfValue(stateVal) +} + // Get returns the data for the given key, or nil if the key doesn't exist // in the schema. // diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert/schema.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert/schema.go index d6fd7d8d9..563e0d38c 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert/schema.go +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert/schema.go @@ -175,7 +175,8 @@ func ConfigSchemaToProto(ctx context.Context, b *configschema.Block) *tfprotov5. func ConfigIdentitySchemaToProto(ctx context.Context, identitySchema *configschema.Block) []*tfprotov5.ResourceIdentitySchemaAttribute { output := make([]*tfprotov5.ResourceIdentitySchemaAttribute, 0) - for name, a := range identitySchema.Attributes { + for _, name := range sortedKeys(identitySchema.Attributes) { + a := identitySchema.Attributes[name] attr := &tfprotov5.ResourceIdentitySchemaAttribute{ Name: name, diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert/value.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert/value.go new file mode 100644 index 000000000..84d710f96 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert/value.go @@ -0,0 +1,213 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package convert + +import ( + "fmt" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func primitiveTfValue(in cty.Value) (*tftypes.Value, error) { + primitiveType, err := tftypeFromCtyType(in.Type()) + if err != nil { + return nil, err + } + + if in.IsNull() { + return nullTfValue(primitiveType), nil + } + + if !in.IsKnown() { + return unknownTfValue(primitiveType), nil + } + + var val tftypes.Value + switch in.Type() { + case cty.String: + val = tftypes.NewValue(tftypes.String, in.AsString()) + case cty.Bool: + val = tftypes.NewValue(tftypes.Bool, in.True()) + case cty.Number: + val = tftypes.NewValue(tftypes.Number, in.AsBigFloat()) + } + + return &val, nil +} + +func listTfValue(in cty.Value) (*tftypes.Value, error) { + listType, err := tftypeFromCtyType(in.Type()) + if err != nil { + return nil, err + } + + if in.IsNull() { + return nullTfValue(listType), nil + } + + if !in.IsKnown() { + return unknownTfValue(listType), nil + } + + vals := make([]tftypes.Value, 0) + + for _, v := range in.AsValueSlice() { + tfVal, err := ToTfValue(v) + if err != nil { + return nil, err + } + vals = append(vals, *tfVal) + } + + out := tftypes.NewValue(listType, vals) + + return &out, nil +} + +func mapTfValue(in cty.Value) (*tftypes.Value, error) { + mapType, err := tftypeFromCtyType(in.Type()) + if err != nil { + return nil, err + } + + if in.IsNull() { + return nullTfValue(mapType), nil + } + + if !in.IsKnown() { + return unknownTfValue(mapType), nil + } + + vals := make(map[string]tftypes.Value) + + for k, v := range in.AsValueMap() { + tfVal, err := ToTfValue(v) + if err != nil { + return nil, err + } + vals[k] = *tfVal + } + + out := tftypes.NewValue(mapType, vals) + + return &out, nil +} + +func setTfValue(in cty.Value) (*tftypes.Value, error) { + setType, err := tftypeFromCtyType(in.Type()) + if err != nil { + return nil, err + } + + if in.IsNull() { + return nullTfValue(setType), nil + } + + if !in.IsKnown() { + return unknownTfValue(setType), nil + } + + vals := make([]tftypes.Value, 0) + + for _, v := range in.AsValueSlice() { + tfVal, err := ToTfValue(v) + if err != nil { + return nil, err + } + vals = append(vals, *tfVal) + } + + out := tftypes.NewValue(setType, vals) + + return &out, nil +} + +func objectTfValue(in cty.Value) (*tftypes.Value, error) { + objType, err := tftypeFromCtyType(in.Type()) + if err != nil { + return nil, err + } + + if in.IsNull() { + return nullTfValue(objType), nil + } + + if !in.IsKnown() { + return unknownTfValue(objType), nil + } + + vals := make(map[string]tftypes.Value) + + for k, v := range in.AsValueMap() { + tfVal, err := ToTfValue(v) + if err != nil { + return nil, err + } + vals[k] = *tfVal + } + + out := tftypes.NewValue(objType, vals) + + return &out, nil +} + +func tupleTfValue(in cty.Value) (*tftypes.Value, error) { + tupleType, err := tftypeFromCtyType(in.Type()) + if err != nil { + return nil, err + } + + if in.IsNull() { + return nullTfValue(tupleType), nil + } + + if !in.IsKnown() { + return unknownTfValue(tupleType), nil + } + + vals := make([]tftypes.Value, 0) + + for _, v := range in.AsValueSlice() { + tfVal, err := ToTfValue(v) + if err != nil { + return nil, err + } + vals = append(vals, *tfVal) + } + + out := tftypes.NewValue(tupleType, vals) + + return &out, nil +} + +func ToTfValue(in cty.Value) (*tftypes.Value, error) { + ty := in.Type() + switch { + case ty.IsPrimitiveType(): + return primitiveTfValue(in) + case ty.IsListType(): + return listTfValue(in) + case ty.IsObjectType(): + return objectTfValue(in) + case ty.IsMapType(): + return mapTfValue(in) + case ty.IsSetType(): + return setTfValue(in) + case ty.IsTupleType(): + return tupleTfValue(in) + default: + return nil, fmt.Errorf("unsupported type %s", ty) + } +} + +func nullTfValue(ty tftypes.Type) *tftypes.Value { + nullValue := tftypes.NewValue(ty, nil) + return &nullValue +} + +func unknownTfValue(ty tftypes.Type) *tftypes.Value { + unknownValue := tftypes.NewValue(ty, tftypes.UnknownValue) + return &unknownValue +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/meta/meta.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/meta/meta.go index 1ba164987..1338f9838 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/meta/meta.go +++ b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/meta/meta.go @@ -17,7 +17,7 @@ import ( // // Deprecated: Use Go standard library [runtime/debug] package build information // instead. -var SDKVersion = "2.37.0" +var SDKVersion = "2.38.1" // A pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/query/query_checks.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/query/query_checks.go new file mode 100644 index 000000000..79a5b9813 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/query/query_checks.go @@ -0,0 +1,96 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package query + +import ( + "context" + "errors" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter" +) + +func RunQueryChecks(ctx context.Context, t testing.T, query []tfjson.LogMsg, queryChecks []querycheck.QueryResultCheck) error { + t.Helper() + + var result []error + + if query == nil { + result = append(result, fmt.Errorf("no query results found")) + } + + found := make([]tfjson.ListResourceFoundData, 0) + summary := tfjson.ListCompleteData{} + + for _, msg := range query { + switch v := msg.(type) { + case tfjson.ListResourceFoundMessage: + found = append(found, v.ListResourceFound) + case tfjson.ListCompleteMessage: + summary = v.ListComplete + // TODO diagnostics and errors? + default: + continue + } + } + + var reqQueryData []tfjson.ListResourceFoundData + for _, queryCheck := range queryChecks { + reqQueryData = found + if filterCheck, ok := queryCheck.(querycheck.QueryResultCheckWithFilters); ok { + filtered, err := runQueryFilters(ctx, filterCheck, reqQueryData) + if err != nil { + return err + } + reqQueryData = filtered + } + resp := querycheck.CheckQueryResponse{} + queryCheck.CheckQuery(ctx, querycheck.CheckQueryRequest{ + Query: reqQueryData, + QuerySummary: &summary, + }, &resp) + + result = append(result, resp.Error) + } + + return errors.Join(result...) +} + +func runQueryFilters(ctx context.Context, filterCheck querycheck.QueryResultCheckWithFilters, queryResults []tfjson.ListResourceFoundData) ([]tfjson.ListResourceFoundData, error) { + filters := filterCheck.QueryFilters(ctx) + filteredResults := make([]tfjson.ListResourceFoundData, 0) + + // If there are no filters, just return the original results + if len(filters) == 0 { + return queryResults, nil + } + + for _, result := range queryResults { + keepResult := false + + for _, filter := range filters { + + resp := queryfilter.FilterQueryResponse{} + filter.Filter(ctx, queryfilter.FilterQueryRequest{QueryItem: result}, &resp) + + if resp.Include { + keepResult = true + } + + if resp.Error != nil { + return nil, resp.Error + } + } + + if keepResult { + filteredResults = append(filteredResults, result) + } + } + + return filteredResults, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing.go index c3dd3bdc9..4ba1961bf 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing.go @@ -15,6 +15,8 @@ import ( "strings" "time" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/mitchellh/go-testing-interface" "github.com/hashicorp/terraform-plugin-go/tfprotov5" @@ -640,6 +642,10 @@ type TestStep struct { // Custom state checks can be created by implementing the [statecheck.StateCheck] interface, or by using a StateCheck implementation from the provided [statecheck] package. ConfigStateChecks []statecheck.StateCheck + // QueryResultChecks allow assertions to be made against a collection of found resources that were returned by a query using a query check. + // Custom query checks can be created by implementing the [querycheck.QueryResultCheck] interface, or by using a QueryResultCheck implementation from the provided [querycheck] package. + QueryResultChecks []querycheck.QueryResultCheck + // PlanOnly can be set to only run `plan` with this configuration, and not // actually apply it. This is useful for ensuring config changes result in // no-op plans @@ -665,6 +671,10 @@ type TestStep struct { // SkipFunc is called after PreConfig but before applying the Config. SkipFunc func() (bool, error) + // PostApplyFunc is called after the Config is applied and after all plan/apply checks are run. + // This can be used to perform assertions against API values that are not stored in Terraform state. + PostApplyFunc func() + //--------------------------------------------------------------- // ImportState testing //--------------------------------------------------------------- @@ -835,6 +845,9 @@ type TestStep struct { // for performing import testing where the prior TestStep configuration // contained a provider outside the one under test. ExternalProviders map[string]ExternalProvider + + // If true, the test step will run the query command + Query bool } // ConfigPlanChecks defines the different points in a Config TestStep when plan checks can be run. diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new.go index 5ae7a5b4d..0faf05892 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new.go @@ -254,7 +254,9 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest testStepConfig = teststep.Configuration(confRequest) - err = wd.SetConfig(ctx, testStepConfig, step.ConfigVariables) + if !step.Query { + err = wd.SetConfig(ctx, testStepConfig, step.ConfigVariables) + } if err != nil { logging.HelperResourceError(ctx, @@ -356,6 +358,42 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest continue } + if step.Query { + logging.HelperResourceTrace(ctx, "TestStep is Query mode") + + err := testStepNewQuery(ctx, t, wd, step, providers) + + if step.ExpectError != nil { + logging.HelperResourceDebug(ctx, "Checking TestStep ExpectError") + if err == nil { + logging.HelperResourceError(ctx, "Error running query: expected an error but got none") + t.Fatalf("Step %d/%d error running query: expected an error but got none", stepNumber, len(c.Steps)) + } + if !step.ExpectError.MatchString(err.Error()) { + logging.HelperResourceError(ctx, fmt.Sprintf("Error running query: expected an error with pattern (%s)", step.ExpectError.String()), + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Step %d/%d error running query, expected an error with pattern (%s), no match on: %s", stepNumber, len(c.Steps), step.ExpectError.String(), err) + } + } else { + if err != nil && c.ErrorCheck != nil { + logging.HelperResourceDebug(ctx, "Calling TestCase ErrorCheck") + err = c.ErrorCheck(err) + logging.HelperResourceDebug(ctx, "Called TestCase ErrorCheck") + } + if err != nil { + logging.HelperResourceError(ctx, "Error running query", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Step %d/%d error running query checks: %s", stepNumber, len(c.Steps), err) + } + } + + logging.HelperResourceDebug(ctx, "Finished TestStep") + + continue + } + if cfg != nil { logging.HelperResourceTrace(ctx, "TestStep is Config mode") @@ -646,7 +684,7 @@ func copyWorkingDir(ctx context.Context, t testing.T, stepNumber int, wd *plugin dest := filepath.Join(workingDir, fmt.Sprintf("%s%s", "step_", strconv.Itoa(stepNumber))) baseDir := wd.BaseDir() - rootBaseDir := strings.TrimLeft(baseDir, workingDir) + rootBaseDir := strings.TrimPrefix(baseDir, workingDir) err := plugintest.CopyDir(workingDir, dest, rootBaseDir) if err != nil { diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_config.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_config.go index 8b9b979c8..1145c8e4e 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_config.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_config.go @@ -443,6 +443,7 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint // this fails. If refresh isn't read-only, then this will have // caught a different bug. if idRefreshCheck != nil { + fmt.Println("Not Writing by testing ID Refresh") if err := testIDRefresh(ctx, t, c, wd, step, idRefreshCheck, providers, stepIndex, helper); err != nil { return fmt.Errorf( "[ERROR] Test: ID-only test failed: %s", err) @@ -450,6 +451,12 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint } } + if step.PostApplyFunc != nil { + logging.HelperResourceDebug(ctx, "Calling TestCase PostApplyFunc") + step.PostApplyFunc() + logging.HelperResourceDebug(ctx, "Called TestCase PostApplyFunc") + } + if refreshAfterApply && !step.Destroy && !step.PlanOnly { if len(c.Steps) > stepIndex+1 { // If the next step is a refresh, then we have no need to refresh here diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_query.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_query.go new file mode 100644 index 000000000..c8961f943 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_query.go @@ -0,0 +1,47 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource/query" + "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" + "github.com/hashicorp/terraform-plugin-testing/internal/teststep" +) + +func testStepNewQuery(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, step TestStep, providers *providerFactories) error { + t.Helper() + + queryConfigRequest := teststep.ConfigurationRequest{ + Raw: &step.Config, + } + err := wd.SetQuery(ctx, teststep.Configuration(queryConfigRequest), step.ConfigVariables) + if err != nil { + return fmt.Errorf("Error setting query config: %w", err) + } + + err = runProviderCommand(ctx, t, wd, providers, func() error { + return wd.Init(ctx) + }) + if err != nil { + t.Fatalf("Error getting init: %s", err) + } + + var queryOut []tfjson.LogMsg + err = runProviderCommand(ctx, t, wd, providers, func() error { + var err error + queryOut, err = wd.Query(ctx) + return err + }) + if err != nil { + return err + } + + return query.RunQueryChecks(ctx, t, queryOut, step.QueryResultChecks) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/context.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/context.go index 5a3108451..35f416061 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/context.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/context.go @@ -21,7 +21,7 @@ import ( func InitTestContext(ctx context.Context, t testing.T) context.Context { helperlogging.SetOutput(t) - ctx = tfsdklog.RegisterTestSink(ctx, t) + ctx = tfsdklog.ContextWithTestLogging(ctx, t.Name()) ctx = tfsdklog.NewRootSDKLogger(ctx, tfsdklog.WithLevelFromEnv(EnvTfLogSdk)) ctx = tfsdklog.NewSubsystem(ctx, SubsystemHelperResource, // All calls are through the HelperResource* helper functions diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/working_dir.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/working_dir.go index d29425c32..e7fe6b246 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/working_dir.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/working_dir.go @@ -21,6 +21,7 @@ import ( const ( ConfigFileName = "terraform_plugin_test.tf" PlanFileName = "tfplan" + QueryFileName = "terraform_plugin_test.tfquery.hcl" ) // WorkingDir represents a distinct working directory that can be used for @@ -37,6 +38,10 @@ type WorkingDir struct { // was stored; empty until SetConfig is called. configFilename string + // queryFilename is the full filename where the latest query configuration + // was stored; empty until SetQuery is called. + queryFilename string + // tf is the instance of tfexec.Terraform used for running Terraform commands tf *tfexec.Terraform @@ -101,7 +106,7 @@ func (wd *WorkingDir) SetConfig(ctx context.Context, cfg teststep.Config, vars c for _, file := range fi { if file.Mode().IsRegular() { - if filepath.Ext(file.Name()) == ".tf" || filepath.Ext(file.Name()) == ".json" { + if filepath.Ext(file.Name()) == ".tf" || filepath.Ext(file.Name()) == ".json" || filepath.Ext(file.Name()) == ".tfquery.hcl" { err = os.Remove(filepath.Join(d.Name(), file.Name())) if err != nil && !os.IsNotExist(err) { @@ -151,6 +156,80 @@ func (wd *WorkingDir) SetConfig(ctx context.Context, cfg teststep.Config, vars c return nil } +// SetQuery sets a new query configuration for the working directory. +// +// This must be called at least once before any call to Init or Query Destroy +// to establish the query configuration. Any previously-set configuration is +// discarded and any saved plan is cleared. +func (wd *WorkingDir) SetQuery(ctx context.Context, cfg teststep.Config, vars config.Variables) error { + // Remove old config and variables files first + d, err := os.Open(wd.baseDir) + + if err != nil { + return err + } + + defer d.Close() + + fi, err := d.Readdir(-1) + + if err != nil { + return err + } + + for _, file := range fi { + if file.Mode().IsRegular() { + if filepath.Ext(file.Name()) == ".warioform" || filepath.Ext(file.Name()) == ".json" || filepath.Ext(file.Name()) == ".tfquery.hcl" { + err = os.Remove(filepath.Join(d.Name(), file.Name())) + + if err != nil && !os.IsNotExist(err) { + return err + } + } + } + } + + logging.HelperResourceTrace(ctx, "Setting Terraform query configuration", map[string]any{logging.KeyTestTerraformConfiguration: cfg}) + + outFilename := filepath.Join(wd.baseDir, QueryFileName) + + // This file has to be written otherwise wd.Init() will return an error. + err = os.WriteFile(outFilename, nil, 0700) + + if err != nil { + return err + } + + // wd.configFilename must be set otherwise wd.Init() will return an error. + wd.queryFilename = outFilename + wd.configFilename = outFilename + + // Write configuration + if cfg != nil { + err = cfg.WriteQuery(ctx, wd.baseDir) + + if err != nil { + return err + } + } + + //Write configuration variables + err = vars.Write(wd.baseDir) + + if err != nil { + return err + } + + // Changing configuration invalidates any saved plan. + err = wd.ClearPlan(ctx) + + if err != nil { + return err + } + + return nil +} + // ClearState deletes any Terraform state present in the working directory. // // Any remote objects tracked by the state are not destroyed first, so this @@ -444,3 +523,43 @@ func (wd *WorkingDir) Schemas(ctx context.Context) (*tfjson.ProviderSchemas, err return providerSchemas, err } + +func (wd *WorkingDir) Query(ctx context.Context) ([]tfjson.LogMsg, error) { + var messages []tfjson.LogMsg + var diags []tfjson.LogMsg + + logging.HelperResourceTrace(ctx, "Calling Terraform CLI providers query command") + + args := []tfexec.QueryOption{tfexec.Reattach(wd.reattachInfo)} + + logs, err := wd.tf.QueryJSON(context.Background(), args...) + + if err != nil { + return nil, fmt.Errorf("running terraform query command: %w", err) + } + + for msg := range logs { + if msg.Msg == nil { + continue + } + + if msg.Err != nil { + return nil, fmt.Errorf("retrieving message: %w", msg.Err) + } + + if msg.Msg.Level() == tfjson.Error { + // TODO reimplement missing .tf config error + diags = append(diags, msg.Msg) + continue + } + messages = append(messages, msg.Msg) + } + + if len(diags) > 0 { + return nil, fmt.Errorf("running terraform query command returned diagnostics: %+v", diags) + } + + logging.HelperResourceTrace(ctx, "Called Terraform CLI providers query command") + + return messages, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/config.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/config.go index 91a708e26..5df477f08 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/config.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/config.go @@ -16,8 +16,9 @@ import ( ) const ( - rawConfigFileName = "terraform_plugin_test.tf" - rawConfigFileNameJSON = rawConfigFileName + ".json" + rawConfigFileName = "terraform_plugin_test.tf" + rawConfigFileNameJSON = rawConfigFileName + ".json" + rawQueryConfigFileName = "terraform_plugin_test.tfquery.hcl" ) var ( @@ -46,6 +47,7 @@ type Config interface { HasTerraformBlock(context.Context) (bool, error) Write(context.Context, string) error Append(string) Config + WriteQuery(context.Context, string) error } // PrepareConfigurationRequest is used to simplify the generation of diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/directory.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/directory.go index 67ecc5ccd..1afc45a2d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/directory.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/directory.go @@ -75,6 +75,10 @@ func (c configurationDirectory) HasTerraformBlock(ctx context.Context) (bool, er return contains, nil } +func (c configurationDirectory) WriteQuery(ctx context.Context, dest string) error { + panic("WriteQuery not supported for configurationDirectory") +} + // Write copies all files from directory to destination. func (c configurationDirectory) Write(ctx context.Context, dest string) error { configDirectory := c.directory diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/file.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/file.go index 75ee6f7d6..a4862b05d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/file.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/file.go @@ -71,6 +71,10 @@ func (c configurationFile) HasTerraformBlock(ctx context.Context) (bool, error) return contains, nil } +func (c configurationFile) WriteQuery(ctx context.Context, dest string) error { + panic("WriteQuery not supported for configurationFile") +} + // Write copies file from c.file to destination. func (c configurationFile) Write(ctx context.Context, dest string) error { configFile := c.file diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/string.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/string.go index 39028682a..ccb8fb3e8 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/string.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/string.go @@ -61,6 +61,20 @@ func (c configurationString) Write(ctx context.Context, dest string) error { return nil } +// WriteQuery creates a file and writes c.raw into it. +func (c configurationString) WriteQuery(ctx context.Context, dest string) error { + outFilename := filepath.Join(dest, rawQueryConfigFileName) + + bCfg := []byte(c.raw) + + err := os.WriteFile(outFilename, bCfg, 0700) + if err != nil { + return err + } + + return nil +} + func (c configurationString) Append(config string) Config { return configurationString{ raw: strings.Join([]string{c.raw, config}, "\n"), diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/doc.go new file mode 100644 index 000000000..67aa0cede --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package querycheck contains the query check interface, request/response structs, and common query check implementations. +package querycheck diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_identity.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_identity.go new file mode 100644 index 000000000..394390991 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_identity.go @@ -0,0 +1,105 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "errors" + "fmt" + "sort" + "strings" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +var _ QueryResultCheck = expectIdentity{} + +type expectIdentity struct { + listResourceAddress string + check map[string]knownvalue.Check +} + +// CheckQuery implements the query check logic. +func (e expectIdentity) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + for _, res := range req.Query { + var errCollection []error + + if e.listResourceAddress != strings.TrimPrefix(res.Address, "list.") { + continue + } + + if len(res.Identity) != len(e.check) { + deltaMsg := "" + if len(res.Identity) > len(e.check) { + deltaMsg = statecheck.CreateDeltaString(res.Identity, e.check, "actual identity has extra attribute(s): ") + } else { + deltaMsg = statecheck.CreateDeltaString(e.check, res.Identity, "actual identity is missing attribute(s): ") + } + + resp.Error = fmt.Errorf("%s - Expected %d attribute(s) in the actual identity object, got %d attribute(s): %s", e.listResourceAddress, len(e.check), len(res.Identity), deltaMsg) + return + } + + var keys []string + + for k := range e.check { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + actualIdentityVal, ok := res.Identity[k] + + if !ok { + resp.Error = fmt.Errorf("%s - missing attribute %q in actual identity object", e.listResourceAddress, k) + return + } + + if err := e.check[k].CheckValue(actualIdentityVal); err != nil { + errCollection = append(errCollection, fmt.Errorf("%s - %q identity attribute: %s", e.listResourceAddress, k, err)) + } + } + + if errCollection == nil { + return + } + } + + var errCollection []error + errCollection = append(errCollection, fmt.Errorf("an identity with the following attributes was not found")) + + var keys []string + + for k := range e.check { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + // wrap errors for each check + for _, attr := range keys { + check := e.check[attr] + errCollection = append(errCollection, fmt.Errorf("attribute %q: %s", attr, check)) + } + + errCollection = append(errCollection, fmt.Errorf("address: %s\n", e.listResourceAddress)) + resp.Error = errors.Join(errCollection...) +} + +// ExpectIdentity returns a query check that asserts that the identity at the given resource matches a known object, where each +// map key represents an identity attribute name. The identity in query must exactly match the given object. +// +// This query check can only be used with managed resources that support resource identity and query. Query is only supported in Terraform v1.14+ +func ExpectIdentity(resourceAddress string, identity map[string]knownvalue.Check) QueryResultCheck { + return expectIdentity{ + listResourceAddress: resourceAddress, + check: identity, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_no_identity.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_no_identity.go new file mode 100644 index 000000000..f5d92f1f3 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_no_identity.go @@ -0,0 +1,89 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "errors" + "fmt" + "sort" + "strings" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +var _ QueryResultCheck = expectNoIdentity{} + +type expectNoIdentity struct { + listResourceAddress string + check map[string]knownvalue.Check +} + +// CheckQuery implements the query check logic. +func (e expectNoIdentity) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + for _, res := range req.Query { + var errCollection []error + + if e.listResourceAddress != strings.TrimPrefix(res.Address, "list.") { + continue + } + + if len(res.Identity) != len(e.check) { + deltaMsg := "" + if len(res.Identity) > len(e.check) { + deltaMsg = statecheck.CreateDeltaString(res.Identity, e.check, "actual identity has extra attribute(s): ") + } else { + deltaMsg = statecheck.CreateDeltaString(e.check, res.Identity, "actual identity is missing attribute(s): ") + } + + resp.Error = fmt.Errorf("%s - Expected %d attribute(s) in the actual identity object, got %d attribute(s): %s", e.listResourceAddress, len(e.check), len(res.Identity), deltaMsg) + return + } + + var keys []string + + for k := range e.check { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + actualIdentityVal, ok := res.Identity[k] + + if !ok { + resp.Error = fmt.Errorf("%s - missing attribute %q in actual identity object", e.listResourceAddress, k) + return + } + + if err := e.check[k].CheckValue(actualIdentityVal); err != nil { + errCollection = append(errCollection, fmt.Errorf("%s - %q identity attribute: %s", e.listResourceAddress, k, err)) + } + } + + if errCollection == nil { + errs := []error{fmt.Errorf("an unexpected identity matching the given attributes was found")} + // wrap errors for each check + for attr, check := range e.check { + errs = append(errs, fmt.Errorf("attribute %q: %s", attr, check)) + } + errs = append(errs, fmt.Errorf("address: %s\n", e.listResourceAddress)) + resp.Error = errors.Join(errs...) + } + } +} + +// ExpectNoIdentity returns a query check that asserts that the identity at the given resource does not match a known object, where each +// map key represents an identity attribute name. The identity in query must exactly match the given object. +// +// This query check can only be used with managed resources that support resource identity and query. Query is only supported in Terraform v1.14+ +func ExpectNoIdentity(resourceAddress string, identity map[string]knownvalue.Check) QueryResultCheck { + return expectNoIdentity{ + listResourceAddress: resourceAddress, + check: identity, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_display_name.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_display_name.go new file mode 100644 index 000000000..89f3f00c7 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_display_name.go @@ -0,0 +1,70 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "fmt" + "strings" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter" +) + +var _ QueryResultCheck = expectResourceDisplayName{} +var _ QueryResultCheckWithFilters = expectResourceDisplayName{} + +type expectResourceDisplayName struct { + listResourceAddress string + filter queryfilter.QueryFilter + displayName knownvalue.Check +} + +func (e expectResourceDisplayName) QueryFilters(ctx context.Context) []queryfilter.QueryFilter { + if e.filter == nil { + return []queryfilter.QueryFilter{} + } + + return []queryfilter.QueryFilter{ + e.filter, + } +} + +func (e expectResourceDisplayName) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + listRes := make([]tfjson.ListResourceFoundData, 0) + for _, result := range req.Query { + if strings.TrimPrefix(result.Address, "list.") == e.listResourceAddress { + listRes = append(listRes, result) + } + } + + if len(listRes) == 0 { + resp.Error = fmt.Errorf("%s - no query results found after filtering", e.listResourceAddress) + return + } + + if len(listRes) > 1 { + resp.Error = fmt.Errorf("%s - more than 1 query result found after filtering", e.listResourceAddress) + return + } + res := listRes[0] + if err := e.displayName.CheckValue(res.DisplayName); err != nil { + resp.Error = fmt.Errorf("error checking value for display name %s, err: %s", e.displayName.String(), err) + return + } + +} + +// ExpectResourceDisplayName returns a query check that asserts that a resource with a given display name exists within the returned results of the query. +// +// This query check can only be used with managed resources that support query. Query is only supported in Terraform v1.14+ +func ExpectResourceDisplayName(listResourceAddress string, filter queryfilter.QueryFilter, displayName knownvalue.Check) QueryResultCheck { + return expectResourceDisplayName{ + listResourceAddress: listResourceAddress, + filter: filter, + displayName: displayName, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_known_values.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_known_values.go new file mode 100644 index 000000000..8185d8f04 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_known_values.go @@ -0,0 +1,93 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "fmt" + tfjson "github.com/hashicorp/terraform-json" + "strings" + + "github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ QueryResultCheck = expectResourceKnownValues{} +var _ QueryResultCheckWithFilters = expectResourceKnownValues{} + +type expectResourceKnownValues struct { + listResourceAddress string + filter queryfilter.QueryFilter + knownValueChecks []KnownValueCheck +} + +func (e expectResourceKnownValues) QueryFilters(ctx context.Context) []queryfilter.QueryFilter { + if e.filter == nil { + return []queryfilter.QueryFilter{} + } + + return []queryfilter.QueryFilter{ + e.filter, + } +} + +func (e expectResourceKnownValues) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + listRes := make([]tfjson.ListResourceFoundData, 0) + var diags []error + for _, res := range req.Query { + if e.listResourceAddress == strings.TrimPrefix(res.Address, "list.") { + listRes = append(listRes, res) + } + } + + if len(listRes) == 0 { + resp.Error = fmt.Errorf("%s - no query results found after filtering", e.listResourceAddress) + return + } + + if len(listRes) > 1 { + resp.Error = fmt.Errorf("%s - more than 1 query result found after filtering", e.listResourceAddress) + return + } + + res := listRes[0] + + if res.ResourceObject == nil { + resp.Error = fmt.Errorf("%s - no resource object was returned, ensure `include_resource` has been set to `true` in the list resource config`", e.listResourceAddress) + return + } + + for _, c := range e.knownValueChecks { + resource, err := tfjsonpath.Traverse(res.ResourceObject, c.Path) + if err != nil { + resp.Error = err + return + } + + if err := c.KnownValue.CheckValue(resource); err != nil { + diags = append(diags, fmt.Errorf("error checking value for attribute at path: %s for resource with identity %s, err: %s", c.Path.String(), e.filter, err)) + } + } + + if diags != nil { + var diagsStr string + for _, diag := range diags { + diagsStr += diag.Error() + "; " + } + resp.Error = fmt.Errorf("the following errors were found while checking values: %s", diagsStr) + return + } +} + +// ExpectResourceKnownValues returns a query check which asserts that a resource object identified by a query filter +// passes the given query checks. +// +// This query check can only be used with managed resources that support resource identity and query. Query is only supported in Terraform v1.14+ +func ExpectResourceKnownValues(listResourceAddress string, filter queryfilter.QueryFilter, knownValues []KnownValueCheck) QueryResultCheck { + return expectResourceKnownValues{ + listResourceAddress: listResourceAddress, + filter: filter, + knownValueChecks: knownValues, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_atleast.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_atleast.go new file mode 100644 index 000000000..6b3dbde8a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_atleast.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "fmt" +) + +var _ QueryResultCheck = expectLengthAtLeast{} + +type expectLengthAtLeast struct { + resourceAddress string + check int +} + +// CheckQuery implements the query check logic. +func (e expectLengthAtLeast) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + if req.QuerySummary == nil { + resp.Error = fmt.Errorf("no completed query information available") + return + } + + if req.QuerySummary.Total < e.check { + resp.Error = fmt.Errorf("Query result of at least length %v - expected but got %v.", e.check, req.QuerySummary.Total) + return + } +} + +// ExpectLengthAtLeast returns a query check that asserts that the length of the query result is at least the given value. +// +// This query check can only be used with managed resources that support query. Query is only supported in Terraform v1.14+ +func ExpectLengthAtLeast(resourceAddress string, length int) QueryResultCheck { + return expectLengthAtLeast{ + resourceAddress: resourceAddress, + check: length, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_exact.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_exact.go new file mode 100644 index 000000000..f10546ac2 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_exact.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "fmt" +) + +var _ QueryResultCheck = expectLength{} + +type expectLength struct { + resourceAddress string + check int +} + +// CheckQuery implements the query check logic. +func (e expectLength) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + if req.QuerySummary == nil { + resp.Error = fmt.Errorf("no query summary information available") + return + } + + if e.check != req.QuerySummary.Total { + resp.Error = fmt.Errorf("number of found resources %v - expected but got %v.", e.check, req.QuerySummary.Total) + return + } +} + +// ExpectLength returns a query check that asserts that the length of the query result is exactly the given value. +// +// This query check can only be used with managed resources that support query. Query is only supported in Terraform v1.14+ +func ExpectLength(resourceAddress string, length int) QueryResultCheck { + return expectLength{ + resourceAddress: resourceAddress, + check: length, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/known_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/known_value.go new file mode 100644 index 000000000..67179ec29 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/known_value.go @@ -0,0 +1,19 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// KnownValueCheck represents a check of a known value at a specific JSON path +// and is used to specify multiple known value checks to assert against a +// single resource object returned by a query. +type KnownValueCheck struct { + // Path specifies the JSON path to check within the resource object. + Path tfjsonpath.Path + // KnownValue specifies the expected known value check to perform at the given path. + KnownValue knownvalue.Check +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/query_check.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/query_check.go new file mode 100644 index 000000000..9728f11d9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/query_check.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter" +) + +// QueryResultCheck defines an interface for implementing test logic to apply an assertion against a collection of found +// resources that were returned by a query. It returns an error if the query results do not match what is expected. +type QueryResultCheck interface { + // CheckQuery should perform the query check. + CheckQuery(context.Context, CheckQueryRequest, *CheckQueryResponse) +} + +// QueryResultCheckWithFilters is an interface type that extends QueryResultCheck to include declarative query filters. +type QueryResultCheckWithFilters interface { + QueryResultCheck + + // QueryFilters should return a slice of queryfilter.QueryFilter that will be applied to the check. + QueryFilters(context.Context) []queryfilter.QueryFilter +} + +// CheckQueryRequest is a request for an invoke of the CheckQuery function. +type CheckQueryRequest struct { + // Query represents the parsed log messages relating to found resources returned by the `terraform query -json` command. + Query []tfjson.ListResourceFoundData + + // QuerySummary contains a summary of the completed query operation + QuerySummary *tfjson.ListCompleteData +} + +// CheckQueryResponse is a response to an invoke of the CheckQuery function. +type CheckQueryResponse struct { + // Error is used to report the failure of a query check assertion and is combined with other QueryResultCheck errors + // to be reported as a test failure. + Error error +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter.go new file mode 100644 index 000000000..716ccc2a6 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter.go @@ -0,0 +1,32 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package queryfilter + +import ( + "context" + + tfjson "github.com/hashicorp/terraform-json" +) + +// QueryFilter defines an interface for implementing declarative filtering logic to apply to query results before +// the results are passed to a query check request. +type QueryFilter interface { + Filter(context.Context, FilterQueryRequest, *FilterQueryResponse) +} + +// FilterQueryRequest is a request to a filter function. +type FilterQueryRequest struct { + // QueryItem represents a single parsed log message relating to a found resource returned by the `terraform query -json` command. + QueryItem tfjson.ListResourceFoundData +} + +// FilterQueryResponse is a response to a filter function. +type FilterQueryResponse struct { + // Include indicates whether the QueryItem should be included in CheckQueryRequest.Query + Include bool + + // Error is used to report the failure of filtering and is combined with other QueryFilter errors + // to be reported as a test failure. + Error error +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_display_name.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_display_name.go new file mode 100644 index 000000000..025559b7c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_display_name.go @@ -0,0 +1,29 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package queryfilter + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" +) + +type filterByDisplayName struct { + displayNameCheck knownvalue.Check +} + +func (f filterByDisplayName) Filter(ctx context.Context, req FilterQueryRequest, resp *FilterQueryResponse) { + if err := f.displayNameCheck.CheckValue(req.QueryItem.DisplayName); err == nil { + resp.Include = true + return + } +} + +// ByDisplayNameExact returns a query filter that only includes query items that match +// the specified display name. +func ByDisplayName(displayNameCheck knownvalue.Check) QueryFilter { + return filterByDisplayName{ + displayNameCheck: displayNameCheck, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_resource_identity.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_resource_identity.go new file mode 100644 index 000000000..119da9b3e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_resource_identity.go @@ -0,0 +1,59 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package queryfilter + +import ( + "context" + "sort" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" +) + +type filterByResourceIdentity struct { + identity map[string]knownvalue.Check +} + +func (f filterByResourceIdentity) Filter(ctx context.Context, req FilterQueryRequest, resp *FilterQueryResponse) { + if len(req.QueryItem.Identity) != len(f.identity) { + resp.Include = false + return + } + + var keys []string + + for k := range f.identity { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + actualIdentityVal, ok := req.QueryItem.Identity[k] + + if !ok { + resp.Include = false + return + } + + if err := f.identity[k].CheckValue(actualIdentityVal); err != nil { + resp.Include = false + return + } + } + + resp.Include = true +} + +// ByResourceIdentity returns a query filter that only includes query items that match +// the given resource identity. +// +// Errors thrown by the given known value checks are only used to filter out non-matching query +// items and are otherwise ignored. +func ByResourceIdentity(identity map[string]knownvalue.Check) QueryFilter { + return filterByResourceIdentity{ + identity: identity, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity.go index df5147b23..a89a06e6d 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity.go @@ -67,9 +67,9 @@ func (e expectIdentity) CheckState(ctx context.Context, req CheckStateRequest, r if len(resource.IdentityValues) != len(e.identity) { deltaMsg := "" if len(resource.IdentityValues) > len(e.identity) { - deltaMsg = createDeltaString(resource.IdentityValues, e.identity, "actual identity has extra attribute(s): ") + deltaMsg = CreateDeltaString(resource.IdentityValues, e.identity, "actual identity has extra attribute(s): ") } else { - deltaMsg = createDeltaString(e.identity, resource.IdentityValues, "actual identity is missing attribute(s): ") + deltaMsg = CreateDeltaString(e.identity, resource.IdentityValues, "actual identity is missing attribute(s): ") } resp.Error = fmt.Errorf("%s - Expected %d attribute(s) in the actual identity object, got %d attribute(s): %s", e.resourceAddress, len(e.identity), len(resource.IdentityValues), deltaMsg) @@ -113,8 +113,8 @@ func ExpectIdentity(resourceAddress string, identity map[string]knownvalue.Check } } -// createDeltaString prints the map keys that are present in mapA and not present in mapB -func createDeltaString[T any, V any](mapA map[string]T, mapB map[string]V, msgPrefix string) string { +// CreateDeltaString prints the map keys that are present in mapA and not present in mapB +func CreateDeltaString[T any, V any](mapA map[string]T, mapB map[string]V, msgPrefix string) string { deltaMsg := "" deltaMap := make(map[string]T, len(mapA)) diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/versions.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/versions.go index ffb625c8d..dc5cc7dfe 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/versions.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/versions.go @@ -39,4 +39,6 @@ var ( Version1_10_0 *version.Version = version.Must(version.NewVersion("1.10.0")) Version1_11_0 *version.Version = version.Must(version.NewVersion("1.11.0")) Version1_12_0 *version.Version = version.Must(version.NewVersion("1.12.0")) + Version1_13_0 *version.Version = version.Must(version.NewVersion("1.13.0")) + Version1_14_0 *version.Version = version.Must(version.NewVersion("1.14.0")) ) diff --git a/vendor/github.com/mitchellh/go-wordwrap/wordwrap.go b/vendor/github.com/mitchellh/go-wordwrap/wordwrap.go index ac67205bc..f7bedda38 100644 --- a/vendor/github.com/mitchellh/go-wordwrap/wordwrap.go +++ b/vendor/github.com/mitchellh/go-wordwrap/wordwrap.go @@ -5,6 +5,8 @@ import ( "unicode" ) +const nbsp = 0xA0 + // WrapString wraps the given string within lim width in characters. // // Wrapping is currently naive and only happens at white-space. A future @@ -18,50 +20,58 @@ func WrapString(s string, lim uint) string { var current uint var wordBuf, spaceBuf bytes.Buffer + var wordBufLen, spaceBufLen uint for _, char := range s { if char == '\n' { if wordBuf.Len() == 0 { - if current+uint(spaceBuf.Len()) > lim { + if current+spaceBufLen > lim { current = 0 } else { - current += uint(spaceBuf.Len()) + current += spaceBufLen spaceBuf.WriteTo(buf) } spaceBuf.Reset() + spaceBufLen = 0 } else { - current += uint(spaceBuf.Len() + wordBuf.Len()) + current += spaceBufLen + wordBufLen spaceBuf.WriteTo(buf) spaceBuf.Reset() + spaceBufLen = 0 wordBuf.WriteTo(buf) wordBuf.Reset() + wordBufLen = 0 } buf.WriteRune(char) current = 0 - } else if unicode.IsSpace(char) { + } else if unicode.IsSpace(char) && char != nbsp { if spaceBuf.Len() == 0 || wordBuf.Len() > 0 { - current += uint(spaceBuf.Len() + wordBuf.Len()) + current += spaceBufLen + wordBufLen spaceBuf.WriteTo(buf) spaceBuf.Reset() + spaceBufLen = 0 wordBuf.WriteTo(buf) wordBuf.Reset() + wordBufLen = 0 } spaceBuf.WriteRune(char) + spaceBufLen++ } else { - wordBuf.WriteRune(char) + wordBufLen++ - if current+uint(spaceBuf.Len()+wordBuf.Len()) > lim && uint(wordBuf.Len()) < lim { + if current+wordBufLen+spaceBufLen > lim && wordBufLen < lim { buf.WriteRune('\n') current = 0 spaceBuf.Reset() + spaceBufLen = 0 } } } if wordBuf.Len() == 0 { - if current+uint(spaceBuf.Len()) <= lim { + if current+spaceBufLen <= lim { spaceBuf.WriteTo(buf) } } else { diff --git a/vendor/modules.txt b/vendor/modules.txt index 43237dc25..3434771ef 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -142,8 +142,8 @@ github.com/hashicorp/hc-install/product github.com/hashicorp/hc-install/releases github.com/hashicorp/hc-install/src github.com/hashicorp/hc-install/version -# github.com/hashicorp/hcl/v2 v2.23.0 -## explicit; go 1.18 +# github.com/hashicorp/hcl/v2 v2.24.0 +## explicit; go 1.23.0 github.com/hashicorp/hcl/v2 github.com/hashicorp/hcl/v2/ext/customdecode github.com/hashicorp/hcl/v2/hclsyntax @@ -261,15 +261,15 @@ github.com/hashicorp/terraform-plugin-go/tfprotov6/internal/tfplugin6 github.com/hashicorp/terraform-plugin-go/tfprotov6/internal/toproto github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server github.com/hashicorp/terraform-plugin-go/tftypes -# github.com/hashicorp/terraform-plugin-log v0.9.0 -## explicit; go 1.19 +# github.com/hashicorp/terraform-plugin-log v0.10.0 +## explicit; go 1.24.0 github.com/hashicorp/terraform-plugin-log/internal/fieldutils github.com/hashicorp/terraform-plugin-log/internal/hclogutils github.com/hashicorp/terraform-plugin-log/internal/logging github.com/hashicorp/terraform-plugin-log/tflog github.com/hashicorp/terraform-plugin-log/tfsdklog -# github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 -## explicit; go 1.23.0 +# github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 +## explicit; go 1.24.0 github.com/hashicorp/terraform-plugin-sdk/v2/diag github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry @@ -285,11 +285,12 @@ github.com/hashicorp/terraform-plugin-sdk/v2/internal/tfdiags github.com/hashicorp/terraform-plugin-sdk/v2/meta github.com/hashicorp/terraform-plugin-sdk/v2/plugin github.com/hashicorp/terraform-plugin-sdk/v2/terraform -# github.com/hashicorp/terraform-plugin-testing v1.13.3 -## explicit; go 1.23.0 +# github.com/hashicorp/terraform-plugin-testing v1.14.0 +## explicit; go 1.24.0 github.com/hashicorp/terraform-plugin-testing/compare github.com/hashicorp/terraform-plugin-testing/config github.com/hashicorp/terraform-plugin-testing/helper/resource +github.com/hashicorp/terraform-plugin-testing/helper/resource/query github.com/hashicorp/terraform-plugin-testing/internal/addrs github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim @@ -299,6 +300,8 @@ github.com/hashicorp/terraform-plugin-testing/internal/teststep github.com/hashicorp/terraform-plugin-testing/internal/tfdiags github.com/hashicorp/terraform-plugin-testing/knownvalue github.com/hashicorp/terraform-plugin-testing/plancheck +github.com/hashicorp/terraform-plugin-testing/querycheck +github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter github.com/hashicorp/terraform-plugin-testing/statecheck github.com/hashicorp/terraform-plugin-testing/terraform github.com/hashicorp/terraform-plugin-testing/tfjsonpath @@ -357,8 +360,8 @@ github.com/mitchellh/copystructure # github.com/mitchellh/go-testing-interface v1.14.1 ## explicit; go 1.14 github.com/mitchellh/go-testing-interface -# github.com/mitchellh/go-wordwrap v1.0.0 -## explicit +# github.com/mitchellh/go-wordwrap v1.0.1 +## explicit; go 1.14 github.com/mitchellh/go-wordwrap # github.com/mitchellh/mapstructure v1.5.0 ## explicit; go 1.14 From f98563a9ed2dcb89d13cc7f7932b473ccf96200b Mon Sep 17 00:00:00 2001 From: Ujjwal Nasra <125353741+unasra@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:33:49 +0530 Subject: [PATCH 02/13] Added Pagination and Additional Tests --- internal/service/dns/record_a_list.go | 91 +++++++++---- internal/service/dns/record_a_list_test.go | 141 ++++++++++++++++++--- internal/utils/utils.go | 13 ++ 3 files changed, 202 insertions(+), 43 deletions(-) diff --git a/internal/service/dns/record_a_list.go b/internal/service/dns/record_a_list.go index 1000ed4a5..5b13e02ea 100644 --- a/internal/service/dns/record_a_list.go +++ b/internal/service/dns/record_a_list.go @@ -9,8 +9,11 @@ import ( "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/dns" "github.com/infobloxopen/terraform-provider-nios/internal/flex" + "github.com/infobloxopen/terraform-provider-nios/internal/utils" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -74,6 +77,8 @@ func (l *RecordAList) ListResourceConfigSchema(ctx context.Context, req list.Lis func (l *RecordAList) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) { var data RecordAListModel + pageCount := 0 + limit := int32(req.Limit) diags := req.Config.Get(ctx, &data) if diags.HasError() { @@ -81,14 +86,65 @@ func (l *RecordAList) List(ctx context.Context, req list.ListRequest, stream *li return } - apiRes, _, err := l.client.DNSAPI.RecordAAPI. - List(ctx). - Filters(flex.ExpandFrameworkMapString(ctx, data.Filters, &diags)). - Extattrfilter(flex.ExpandFrameworkMapString(ctx, data.ExtAttrFilters, &diags)). - ReturnAsObject(1). - ReturnFieldsPlus(readableAttributesForRecordA). - MaxResults(int32(req.Limit)). - Execute() + allResults, err := utils.ReadWithPages( + func(pageID string, maxResultsPerPage int32) ([]dns.RecordA, 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.DNSAPI.RecordAAPI. + List(ctx). + Filters(flex.ExpandFrameworkMapString(ctx, data.Filters, &diags)). + Extattrfilter(flex.ExpandFrameworkMapString(ctx, data.ExtAttrFilters, &diags)). + ReturnAsObject(1). + ReturnFieldsPlus(readableAttributesForRecordA). + 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.ListRecordAResponseObject.GetResult() + tflog.Info(ctx, fmt.Sprintf("Page %d : Retrieved %d results", pageCount, len(res))) + + // Check for next page ID in additional properties + additionalProperties := apiRes.ListRecordAResponseObject.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 RecordA, got error: %s", err)) @@ -96,24 +152,11 @@ func (l *RecordAList) List(ctx context.Context, req list.ListRequest, stream *li return } - if apiRes == nil || apiRes.ListRecordAResponseObject == nil { - // No results found, return empty stream - stream.Results = func(push func(list.ListResult) bool) {} - return - } - - res := apiRes.ListRecordAResponseObject.GetResult() - if res == nil { - // No results found, return empty stream - stream.Results = func(push func(list.ListResult) bool) {} - return - } - stream.Results = func(push func(list.ListResult) bool) { - for _, item := range res { + for _, item := range allResults { result := req.NewListResult(ctx) - //result.Diagnostics.Append(result.Identity.Set(ctx, identityData)...) + // 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) { @@ -122,6 +165,8 @@ func (l *RecordAList) List(ctx context.Context, req list.ListRequest, stream *li continue } + // By default, list only returns the identity. + // If IncludeResource is true, it gets the full resource and set it in the result.Resource if req.IncludeResource { var extAttrsAll types.Map item.ExtAttrs, extAttrsAll, diags = RemoveInheritedExtAttrs(ctx, extAttrsAll, *item.ExtAttrs) diff --git a/internal/service/dns/record_a_list_test.go b/internal/service/dns/record_a_list_test.go index 7f7585446..4d82d1694 100644 --- a/internal/service/dns/record_a_list_test.go +++ b/internal/service/dns/record_a_list_test.go @@ -1,35 +1,48 @@ package dns_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/dns" + "github.com/infobloxopen/terraform-provider-nios/internal/acctest" + "github.com/infobloxopen/terraform-provider-nios/internal/utils" ) func TestAccRecordAList_basic(t *testing.T) { - //var resourceName = "nios_dns_record_a.test" - //var v dns.RecordA - //name := acctest.RandomName() + ".example.com" + var resourceName = "nios_dns_record_a.test" + var v dns.RecordA + name := acctest.RandomName() + ".example.com" - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.SkipBelow(tfversion.Version0_14_0), }, Steps: []resource.TestStep{ - // Provider Setup + //Provider Setup { - Config: basicConfig(), + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccRecordABasicConfig(name, "10.0.0.20", "default"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRecordAExists(context.Background(), resourceName, &v), + ), }, { Query: true, Config: testAccRecordAListBasicConfig(), QueryResultChecks: []querycheck.QueryResultCheck{ - querycheck.ExpectLength("nios_dns_record_a.test", 3), + querycheck.ExpectLengthAtLeast("nios_dns_record_a.test", 1), }, }, // Delete testing automatically occurs in TestCase @@ -37,25 +50,113 @@ func TestAccRecordAList_basic(t *testing.T) { }) } -func basicConfig() string { - return ` - terraform { - required_providers { - nios = { - source = "registry.terraform.io/infobloxopen/nios" - version = "1.0.0" - } - } - required_version = ">= 1.8.0" +func TestAccRecordAList_Filters(t *testing.T) { + var resourceName = "nios_dns_record_a.test" + var v dns.RecordA + name := acctest.RandomName() + ".example.com" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccRecordABasicConfig(name, "10.0.0.21", "default"), + Check: resource.ComposeTestCheckFunc( + testAccCheckRecordAExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "name", name), + ), + }, + { + Query: true, + Config: testAccRecordAListConfigFilters(name), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_dns_record_a.test", 1), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) } -` + +func TestAccRecordAList_ExtAttrFilters(t *testing.T) { + var resourceName = "nios_dns_record_a.test_extattrs" + var v dns.RecordA + name := acctest.RandomName() + ".example.com" + extAttrValue := acctest.RandomName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccRecordAExtattrs(name, "10.0.0.22", "default", map[string]string{ + "Site": extAttrValue, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheckRecordAExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "extattrs.Site", extAttrValue), + ), + }, + { + Query: true, + + Config: testAccRecordAListConfigExtAttrFilters(extAttrValue), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_dns_record_a.test", 1), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) } func testAccRecordAListBasicConfig() string { return ` list "nios_dns_record_a" "test" { - provider = nios - include_resource = true + provider = nios + limit = 5 } ` } + +func testAccRecordAListConfigFilters(name string) string { + return fmt.Sprintf(` +list "nios_dns_record_a" "test" { + provider = nios + config { + filters = { + name = %q + } + } +} +`, name) +} + +func testAccRecordAListConfigExtAttrFilters(name string) string { + return fmt.Sprintf(` +list "nios_dns_record_a" "test" { + provider = nios + config { + extattrfilters = { + Site = %q + } + } +} +`, name) +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index b64f0f083..9cf766fb9 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -877,3 +877,16 @@ func ReorderAndFilterDHCPOptions( return newList, &diags } + +func ProviderSetup() string { + return ` + terraform { + required_providers { + nios = { + source = "registry.terraform.io/infobloxopen/nios" + version = "1.0.0" + } + } +} +` +} From ac9bf3e56e3efadf6392e329080d0dd318b1892b Mon Sep 17 00:00:00 2001 From: Ujjwal Nasra <125353741+unasra@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:52:18 +0530 Subject: [PATCH 03/13] Updated Documentation --- docs/list-resources/dns_record_a.md | 47 ++++++++++++++ .../list-resource.tfquery.hcl | 25 +++++++ internal/service/dns/record_a_list.go | 4 +- templates/list-resources.md.tmpl | 65 +++++++++++++++++++ 4 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 docs/list-resources/dns_record_a.md create mode 100644 examples/list-resources/nios_dns_record_a/list-resource.tfquery.hcl create mode 100644 templates/list-resources.md.tmpl diff --git a/docs/list-resources/dns_record_a.md b/docs/list-resources/dns_record_a.md new file mode 100644 index 000000000..98d0843e3 --- /dev/null +++ b/docs/list-resources/dns_record_a.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nios_dns_record_a List Resource - nios" +subcategory: "DNS" +--- + +# nios_dns_record_a (List Resource) + +Retrieves information about existing DNS A Records. + +## Example Usage + +```terraform +// List specific A Records using filters +list "nios_dns_record_a" "list_records_using_filters" { + provider = nios + config { + filters = { + name = "example_record.example.com" + } + } +} + +// List specific A Records using Extensible Attributes +list "nios_dns_record_a" "list_records_using_extensible_attributes" { + provider = nios + config { + extattrfilters = { + Site = "location-1" + } + } +} + +// List A records with resource details included +list "nios_dns_record_a" "list_records_with_resource" { + provider = nios + include_resource = true +} +``` + + +## Schema + +### Optional + +- `extattrfilters` (Map of String) Extensible attribute filters for querying DNS A records. +- `filters` (Map of String) Filter parameters for querying DNS A records. diff --git a/examples/list-resources/nios_dns_record_a/list-resource.tfquery.hcl b/examples/list-resources/nios_dns_record_a/list-resource.tfquery.hcl new file mode 100644 index 000000000..0ec0f5c66 --- /dev/null +++ b/examples/list-resources/nios_dns_record_a/list-resource.tfquery.hcl @@ -0,0 +1,25 @@ +// List specific A Records using filters +list "nios_dns_record_a" "list_records_using_filters" { + provider = nios + config { + filters = { + name = "example_record.example.com" + } + } +} + +// List specific A Records using Extensible Attributes +list "nios_dns_record_a" "list_records_using_extensible_attributes" { + provider = nios + config { + extattrfilters = { + Site = "location-1" + } + } +} + +// List A records with resource details included +list "nios_dns_record_a" "list_records_with_resource" { + provider = nios + include_resource = true +} \ No newline at end of file diff --git a/internal/service/dns/record_a_list.go b/internal/service/dns/record_a_list.go index 5b13e02ea..c1aa853e4 100644 --- a/internal/service/dns/record_a_list.go +++ b/internal/service/dns/record_a_list.go @@ -24,7 +24,7 @@ func NewRecordAList() list.ListResource { return &RecordAList{} } -// RecordAList defines the data source implementation. +// RecordAList defines the List implementation. type RecordAList struct { client *niosclient.APIClient } @@ -59,7 +59,7 @@ type RecordAListModel struct { func (l *RecordAList) ListResourceConfigSchema(ctx context.Context, req list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "Retrieves information about existing DNS A Records.", + MarkdownDescription: "Query existing DNS A Records.", Attributes: map[string]schema.Attribute{ "filters": schema.MapAttribute{ MarkdownDescription: "Filter parameters for querying DNS A records.", diff --git a/templates/list-resources.md.tmpl b/templates/list-resources.md.tmpl new file mode 100644 index 000000000..fe76c34f3 --- /dev/null +++ b/templates/list-resources.md.tmpl @@ -0,0 +1,65 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "{{.Name}} {{.Type}} - {{.RenderedProviderName}}" +subcategory: "{{- $group := index (split .Name "_") 1 -}} +{{- if eq $group "acl" -}} + ACL +{{- else if eq $group "cloud" -}} + CLOUD +{{- else if eq $group "dhcp" -}} + DHCP +{{- else if eq $group "discovery" -}} + DISCOVERY +{{- else if eq $group "dns" -}} + DNS +{{- else if eq $group "dtc" -}} + DTC +{{- else if eq $group "federatedrealms" -}} + FEDERATED REALMS +{{- else if eq $group "grid" -}} + GRID +{{- else if eq $group "ipam" -}} + IPAM +{{- else if eq $group "microsoftserver" -}} + MICROSOFT SERVER +{{- else if eq $group "misc" -}} + MISC +{{- else if eq $group "notification" -}} + NOTIFICATION +{{- else if eq $group "parentalcontrol" -}} + PARENTAL CONTROL +{{- else if eq $group "rir" -}} + RIR +{{- else if eq $group "rpz" -}} + RPZ +{{- else if eq $group "security" -}} + SECURITY +{{- else if eq $group "smartfolder" -}} + SMART FOLDER +{{- else if eq $group "threatinsight" -}} + THREAT INSIGHT +{{- else if eq $group "threatprotection" -}} + THREAT PROTECTION +{{- end -}}" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{codefile "terraform" .ExampleFile}} +{{- end }} + +{{ .SchemaMarkdown | trimspace -}} +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: + +{{codefile "shell" .ImportFile}} +{{- end }} From 25a5239caa7bfe9970dc39b97703f632e5cfc5d1 Mon Sep 17 00:00:00 2001 From: Ujjwal Nasra <125353741+unasra@users.noreply.github.com> Date: Mon, 23 Feb 2026 12:45:43 +0530 Subject: [PATCH 04/13] Updated Documentation --- docs/list-resources/dns_record_a.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/list-resources/dns_record_a.md b/docs/list-resources/dns_record_a.md index 98d0843e3..11a982fdb 100644 --- a/docs/list-resources/dns_record_a.md +++ b/docs/list-resources/dns_record_a.md @@ -2,11 +2,13 @@ # generated by https://github.com/hashicorp/terraform-plugin-docs page_title: "nios_dns_record_a List Resource - nios" subcategory: "DNS" +description: |- + Query existing DNS A Records. --- # nios_dns_record_a (List Resource) -Retrieves information about existing DNS A Records. +Query existing DNS A Records. ## Example Usage From 0331e7459e5ae83dea259289ab29cf2bfc430c83 Mon Sep 17 00:00:00 2001 From: Ujjwal Nasra <125353741+unasra@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:57:13 +0530 Subject: [PATCH 05/13] Updated Descriptions and Comments --- internal/service/dns/record_a_list.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/internal/service/dns/record_a_list.go b/internal/service/dns/record_a_list.go index c1aa853e4..53f773dc4 100644 --- a/internal/service/dns/record_a_list.go +++ b/internal/service/dns/record_a_list.go @@ -10,8 +10,10 @@ import ( "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/dns" + "github.com/infobloxopen/terraform-provider-nios/internal/flex" "github.com/infobloxopen/terraform-provider-nios/internal/utils" ) @@ -62,12 +64,12 @@ func (l *RecordAList) ListResourceConfigSchema(ctx context.Context, req list.Lis MarkdownDescription: "Query existing DNS A Records.", Attributes: map[string]schema.Attribute{ "filters": schema.MapAttribute{ - MarkdownDescription: "Filter parameters for querying DNS A records.", + 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: "Extensible attribute filters for querying DNS A records.", + 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, }, @@ -100,7 +102,8 @@ func (l *RecordAList) List(ctx context.Context, req list.ListRequest, stream *li //Increment the page count pageCount++ - request := l.client.DNSAPI.RecordAAPI. + request := l.client.DNSAPI. + RecordAAPI. List(ctx). Filters(flex.ExpandFrameworkMapString(ctx, data.Filters, &diags)). Extattrfilter(flex.ExpandFrameworkMapString(ctx, data.ExtAttrFilters, &diags)). @@ -166,7 +169,7 @@ func (l *RecordAList) List(ctx context.Context, req list.ListRequest, stream *li } // By default, list only returns the identity. - // If IncludeResource is true, it gets the full resource and set it in the result.Resource + // 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) From 9e76e07a2b80729c7545ab55a0273ea66ccff4d9 Mon Sep 17 00:00:00 2001 From: Ujjwal Nasra <125353741+unasra@users.noreply.github.com> Date: Wed, 25 Feb 2026 16:24:22 +0530 Subject: [PATCH 06/13] Builder fixes --- docs/list-resources/dns_record_a.md | 4 ++-- .../nios_dns_record_a/list-resource.tfquery.hcl | 2 +- internal/service/dns/record_a_list_test.go | 2 -- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/list-resources/dns_record_a.md b/docs/list-resources/dns_record_a.md index 11a982fdb..400d22a63 100644 --- a/docs/list-resources/dns_record_a.md +++ b/docs/list-resources/dns_record_a.md @@ -45,5 +45,5 @@ list "nios_dns_record_a" "list_records_with_resource" { ### Optional -- `extattrfilters` (Map of String) Extensible attribute filters for querying DNS A records. -- `filters` (Map of String) Filter parameters for querying DNS A records. +- `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_dns_record_a/list-resource.tfquery.hcl b/examples/list-resources/nios_dns_record_a/list-resource.tfquery.hcl index 0ec0f5c66..e96c74752 100644 --- a/examples/list-resources/nios_dns_record_a/list-resource.tfquery.hcl +++ b/examples/list-resources/nios_dns_record_a/list-resource.tfquery.hcl @@ -22,4 +22,4 @@ list "nios_dns_record_a" "list_records_using_extensible_attributes" { list "nios_dns_record_a" "list_records_with_resource" { provider = nios include_resource = true -} \ No newline at end of file +} diff --git a/internal/service/dns/record_a_list_test.go b/internal/service/dns/record_a_list_test.go index 4d82d1694..531983d66 100644 --- a/internal/service/dns/record_a_list_test.go +++ b/internal/service/dns/record_a_list_test.go @@ -81,7 +81,6 @@ func TestAccRecordAList_Filters(t *testing.T) { querycheck.ExpectLength("nios_dns_record_a.test", 1), }, }, - // Delete testing automatically occurs in TestCase }, }) } @@ -121,7 +120,6 @@ func TestAccRecordAList_ExtAttrFilters(t *testing.T) { querycheck.ExpectLength("nios_dns_record_a.test", 1), }, }, - // Delete testing automatically occurs in TestCase }, }) } From 769e6ffa5bc02969ab49468ab41f647720ea9d65 Mon Sep 17 00:00:00 2001 From: Ujjwal Nasra <125353741+unasra@users.noreply.github.com> Date: Mon, 27 Apr 2026 15:46:06 +0530 Subject: [PATCH 07/13] Added Fixed Address as a List Resource --- docs/list-resources/dhcp_fixed_address.md | 49 +++++ .../list-resource.tfquery.hcl | 25 +++ internal/provider/provider.go | 2 + internal/service/dhcp/fixedaddress_list.go | 201 ++++++++++++++++++ .../service/dhcp/fixedaddress_list_test.go | 167 +++++++++++++++ .../service/dhcp/fixedaddress_resource.go | 47 +++- internal/service/dhcp/model_fixedaddress.go | 1 - internal/service/dns/record_a_list_test.go | 5 +- internal/utils/utils.go | 14 +- 9 files changed, 495 insertions(+), 16 deletions(-) create mode 100644 docs/list-resources/dhcp_fixed_address.md create mode 100644 examples/list-resources/nios_dhcp_fixed_address/list-resource.tfquery.hcl create mode 100644 internal/service/dhcp/fixedaddress_list.go create mode 100644 internal/service/dhcp/fixedaddress_list_test.go diff --git a/docs/list-resources/dhcp_fixed_address.md b/docs/list-resources/dhcp_fixed_address.md new file mode 100644 index 000000000..71fab14eb --- /dev/null +++ b/docs/list-resources/dhcp_fixed_address.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "nios_dhcp_fixed_address List Resource - nios" +subcategory: "DHCP" +description: |- + Query existing DHCP Fixed Addresses. +--- + +# nios_dhcp_fixed_address (List Resource) + +Query existing DHCP Fixed Addresses. + +## Example Usage + +```terraform +// List specific Fixed Addresses using filters +list "nios_dhcp_fixed_address" "list_fixed_addresses_using_filters" { + provider = nios + config { + filters = { + ipv4addr = "10.0.0.1" + } + } +} + +// List specific Fixed Addresses using Extensible Attributes +list "nios_dhcp_fixed_address" "list_fixed_addresses_using_extensible_attributes" { + provider = nios + config { + extattrfilters = { + Site = "location-1" + } + } +} + +// List Fixed Addresses with resource details included +list "nios_dhcp_fixed_address" "list_fixed_addresses_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_dhcp_fixed_address/list-resource.tfquery.hcl b/examples/list-resources/nios_dhcp_fixed_address/list-resource.tfquery.hcl new file mode 100644 index 000000000..343ba4f4a --- /dev/null +++ b/examples/list-resources/nios_dhcp_fixed_address/list-resource.tfquery.hcl @@ -0,0 +1,25 @@ +// List specific Fixed Addresses using filters +list "nios_dhcp_fixed_address" "list_fixed_addresses_using_filters" { + provider = nios + config { + filters = { + ipv4addr = "10.0.0.1" + } + } +} + +// List specific Fixed Addresses using Extensible Attributes +list "nios_dhcp_fixed_address" "list_fixed_addresses_using_extensible_attributes" { + provider = nios + config { + extattrfilters = { + Site = "location-1" + } + } +} + +// List Fixed Addresses with resource details included +list "nios_dhcp_fixed_address" "list_fixed_addresses_with_resource" { + provider = nios + include_resource = true +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 57629e960..69eed7855 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -447,6 +447,8 @@ func (p *NIOSProvider) DataSources(ctx context.Context) []func() datasource.Data func (p *NIOSProvider) ListResources(ctx context.Context) []func() list.ListResource { return []func() list.ListResource{ dns.NewRecordAList, + + dhcp.NewFixedaddressList, } } diff --git a/internal/service/dhcp/fixedaddress_list.go b/internal/service/dhcp/fixedaddress_list.go new file mode 100644 index 000000000..028ef29b4 --- /dev/null +++ b/internal/service/dhcp/fixedaddress_list.go @@ -0,0 +1,201 @@ +package dhcp + +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/dhcp" + + "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 = &FixedaddressList{} +var _ list.ListResourceWithConfigure = &FixedaddressList{} + +func NewFixedaddressList() list.ListResource { + return &FixedaddressList{} +} + +// FixedaddressList defines the List implementation. +type FixedaddressList struct { + client *niosclient.APIClient +} + +func (l *FixedaddressList) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + "dhcp_fixed_address" +} + +func (l *FixedaddressList) 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 FixedaddressListModel struct { + Filters types.Map `tfsdk:"filters"` + ExtAttrFilters types.Map `tfsdk:"extattrfilters"` +} + +func (l *FixedaddressList) ListResourceConfigSchema(ctx context.Context, req list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Query existing DHCP Fixed Addresses.", + 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 *FixedaddressList) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) { + var data FixedaddressListModel + 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) ([]dhcp.Fixedaddress, 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.DHCPAPI. + FixedaddressAPI. + List(ctx). + Filters(flex.ExpandFrameworkMapString(ctx, data.Filters, &diags)). + Extattrfilter(flex.ExpandFrameworkMapString(ctx, data.ExtAttrFilters, &diags)). + ReturnAsObject(1). + ReturnFieldsPlus(readableAttributesForFixedaddress). + 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.ListFixedaddressResponseObject.GetResult() + tflog.Info(ctx, fmt.Sprintf("Page %d : Retrieved %d results", pageCount, len(res))) + + // Check for next page ID in additional properties + additionalProperties := apiRes.ListFixedaddressResponseObject.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 Fixedaddress, 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 := FlattenFixedaddress(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/dhcp/fixedaddress_list_test.go b/internal/service/dhcp/fixedaddress_list_test.go new file mode 100644 index 000000000..bc642d076 --- /dev/null +++ b/internal/service/dhcp/fixedaddress_list_test.go @@ -0,0 +1,167 @@ +package dhcp_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/dhcp" + + "github.com/infobloxopen/terraform-provider-nios/internal/acctest" + "github.com/infobloxopen/terraform-provider-nios/internal/utils" +) + +func TestAccFixedaddressList_basic(t *testing.T) { + var resourceName = "nios_dhcp_fixed_address.test" + var v dhcp.Fixedaddress + ip := "15.0.0.111" + agentCircuitID := acctest.RandomNumber(1000) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccFixedaddressBasicConfig(ip, "CIRCUIT_ID", agentCircuitID), + Check: resource.ComposeTestCheckFunc( + testAccCheckFixedaddressExists(context.Background(), resourceName, &v), + ), + }, + // Query the object + { + Query: true, + Config: testAccFixedaddressListBasicConfig(), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLengthAtLeast("nios_dhcp_fixed_address.test", 1), + }, + }, + }, + }) +} + +func TestAccFixedaddressList_Filters(t *testing.T) { + var resourceName = "nios_dhcp_fixed_address.test" + var v dhcp.Fixedaddress + ip := "15.0.0.112" + agentCircuitID := acctest.RandomNumber(1000) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccFixedaddressBasicConfig(ip, "CIRCUIT_ID", agentCircuitID), + Check: resource.ComposeTestCheckFunc( + testAccCheckFixedaddressExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "ipv4addr", ip), + ), + }, + // Query the object + { + Query: true, + Config: testAccFixedaddressListConfigFilters(ip), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_dhcp_fixed_address.test", 1), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccFixedaddressList_ExtAttrFilters(t *testing.T) { + var resourceName = "nios_dhcp_fixed_address.test_extattrs" + var v dhcp.Fixedaddress + ip := "15.0.0.113" + agentCircuitID := acctest.RandomNumber(1000) + + extAttrValue := acctest.RandomName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccFixedaddressExtAttrs(ip, "CIRCUIT_ID", agentCircuitID, map[string]string{ + "Site": extAttrValue, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheckFixedaddressExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "extattrs.Site", extAttrValue), + ), + }, + // Query the object + { + Query: true, + Config: testAccFixedaddressListConfigExtAttrFilters(extAttrValue), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_dhcp_fixed_address.test", 1), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccFixedaddressListBasicConfig() string { + return ` +list "nios_dhcp_fixed_address" "test" { + provider = nios + limit = 5 +} +` +} + +func testAccFixedaddressListConfigFilters(ip4addr string) string { + return fmt.Sprintf(` +list "nios_dhcp_fixed_address" "test" { + provider = nios + config { + filters = { + ipv4addr = %q + } + } +} +`, ip4addr) +} + +func testAccFixedaddressListConfigExtAttrFilters(extAttrVal string) string { + return fmt.Sprintf(` +list "nios_dhcp_fixed_address" "test" { + provider = nios + config { + extattrfilters = { + Site = %q + } + } +} +`, extAttrVal) +} diff --git a/internal/service/dhcp/fixedaddress_resource.go b/internal/service/dhcp/fixedaddress_resource.go index 9c81b46f9..08d88c4ee 100644 --- a/internal/service/dhcp/fixedaddress_resource.go +++ b/internal/service/dhcp/fixedaddress_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" "github.com/hashicorp/terraform-plugin-framework/types" @@ -22,6 +23,7 @@ var readableAttributesForFixedaddress = "agent_circuit_id,agent_remote_id,allow_ // Ensure provider defined types fully satisfy framework interfaces. var _ resource.Resource = &FixedaddressResource{} var _ resource.ResourceWithImportState = &FixedaddressResource{} +var _ resource.ResourceWithIdentity = &FixedaddressResource{} func NewFixedaddressResource() resource.Resource { return &FixedaddressResource{} @@ -43,6 +45,16 @@ func (r *FixedaddressResource) Schema(ctx context.Context, req resource.SchemaRe } } +func (r *FixedaddressResource) 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 *FixedaddressResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { @@ -77,6 +89,7 @@ func (r *FixedaddressResource) Create(ctx context.Context, req resource.CreateRe // 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 } @@ -101,7 +114,8 @@ func (r *FixedaddressResource) Create(ctx context.Context, req resource.CreateRe res := apiRes.CreateFixedaddressResponseAsObject.GetResult() res.ExtAttrs, data.ExtAttrsAll, diags = RemoveInheritedExtAttrs(ctx, data.ExtAttrs, *res.ExtAttrs) if diags.HasError() { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error while create Fixedaddress due inherited Extensible attributes, got error: %s", err)) + resp.Diagnostics.Append(diags...) + resp.Diagnostics.AddError("Client Error", "Error while creating Fixedaddress due to inherited Extensible attributes") return } @@ -112,6 +126,9 @@ func (r *FixedaddressResource) Create(ctx context.Context, req resource.CreateRe data.FuncCall = types.ObjectValueMust(FuncCallAttrTypes, origFunCallAttrs) } + // 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)...) } @@ -177,12 +194,16 @@ func (r *FixedaddressResource) Read(ctx context.Context, req resource.ReadReques res.ExtAttrs, data.ExtAttrsAll, diags = RemoveInheritedExtAttrs(ctx, data.ExtAttrs, *res.ExtAttrs) if diags.HasError() { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error while reading Fixedaddress due inherited Extensible attributes, got error: %s", diags)) + resp.Diagnostics.Append(diags...) + resp.Diagnostics.AddError("Client Error", "Error while reading Fixedaddress due to inherited Extensible attributes") 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)...) } @@ -238,6 +259,10 @@ func (r *FixedaddressResource) ReadByExtAttrs(ctx context.Context, data *Fixedad } 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 @@ -266,14 +291,16 @@ func (r *FixedaddressResource) Update(ctx context.Context, req resource.UpdateRe resp.Diagnostics.Append(diags...) return } + 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 } } @@ -301,15 +328,18 @@ func (r *FixedaddressResource) Update(ctx context.Context, req resource.UpdateRe res.ExtAttrs, data.ExtAttrsAll, diags = RemoveInheritedExtAttrs(ctx, planExtAttrs, *res.ExtAttrs) if diags.HasError() { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error while update Fixedaddress due inherited Extensible attributes, got error: %s", diags)) + resp.Diagnostics.Append(diags...) + resp.Diagnostics.AddError("Client Error", "Error while updating Fixedaddress due to inherited Extensible attributes") 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 { resp.Diagnostics.Append(resp.Private.SetKey(ctx, "associate_internal_id", nil)...) } @@ -353,6 +383,13 @@ func (r *FixedaddressResource) UpdateFuncCallAttributeName(ctx context.Context, } func (r *FixedaddressResource) 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"))...) } diff --git a/internal/service/dhcp/model_fixedaddress.go b/internal/service/dhcp/model_fixedaddress.go index c60738d23..51acfb043 100644 --- a/internal/service/dhcp/model_fixedaddress.go +++ b/internal/service/dhcp/model_fixedaddress.go @@ -672,7 +672,6 @@ func (m *FixedaddressModel) Expand(ctx context.Context, diags *diag.Diagnostics, LogicFilterRules: flex.ExpandFrameworkListNestedBlock(ctx, m.LogicFilterRules, diags, ExpandFixedaddressLogicFilterRules), Mac: flex.ExpandMACAddress(m.Mac), MatchClient: flex.ExpandStringPointer(m.MatchClient), - MsAdUserData: ExpandFixedaddressMsAdUserData(ctx, m.MsAdUserData, diags), MsOptions: flex.ExpandFrameworkListNestedBlock(ctx, m.MsOptions, diags, ExpandFixedaddressMsOptions), MsServer: ExpandFixedaddressMsServer(ctx, m.MsServer, diags), Name: flex.ExpandStringPointer(m.Name), diff --git a/internal/service/dns/record_a_list_test.go b/internal/service/dns/record_a_list_test.go index 531983d66..fb7842831 100644 --- a/internal/service/dns/record_a_list_test.go +++ b/internal/service/dns/record_a_list_test.go @@ -27,7 +27,7 @@ func TestAccRecordAList_basic(t *testing.T) { tfversion.SkipBelow(tfversion.Version0_14_0), }, Steps: []resource.TestStep{ - //Provider Setup + // Provider Setup { Config: utils.ProviderSetup(), }, @@ -113,8 +113,7 @@ func TestAccRecordAList_ExtAttrFilters(t *testing.T) { ), }, { - Query: true, - + Query: true, Config: testAccRecordAListConfigExtAttrFilters(extAttrValue), QueryResultChecks: []querycheck.QueryResultCheck{ querycheck.ExpectLength("nios_dns_record_a.test", 1), diff --git a/internal/utils/utils.go b/internal/utils/utils.go index e88719e13..1fdae67a3 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -889,12 +889,12 @@ func ReorderAndFilterDHCPOptions( func ProviderSetup() string { return ` terraform { - required_providers { - nios = { - source = "registry.terraform.io/infobloxopen/nios" - version = "1.0.0" - } - } -} + required_providers { + nios = { + source = "registry.terraform.io/infobloxopen/nios" + version = "1.1.0" + } + } + } ` } From 213919658eac4b4f9f09c8fc06e07b1b9085ffb3 Mon Sep 17 00:00:00 2001 From: Ujjwal Nasra <125353741+unasra@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:53:47 +0530 Subject: [PATCH 08/13] Added List Resource for Network View --- .../list-resource.tfquery.hcl | 25 +++ internal/provider/provider.go | 2 + internal/service/ipam/networkview_list.go | 201 ++++++++++++++++++ .../service/ipam/networkview_list_test.go | 165 ++++++++++++++ internal/service/ipam/networkview_resource.go | 47 +++- 5 files changed, 434 insertions(+), 6 deletions(-) create mode 100644 examples/list-resources/nios_ipam_network_view/list-resource.tfquery.hcl create mode 100644 internal/service/ipam/networkview_list.go create mode 100644 internal/service/ipam/networkview_list_test.go diff --git a/examples/list-resources/nios_ipam_network_view/list-resource.tfquery.hcl b/examples/list-resources/nios_ipam_network_view/list-resource.tfquery.hcl new file mode 100644 index 000000000..7ecc2844c --- /dev/null +++ b/examples/list-resources/nios_ipam_network_view/list-resource.tfquery.hcl @@ -0,0 +1,25 @@ +// List specific Network Views using filters +list "nios_ipam_network_view" "list_network_views_using_filters" { + provider = nios + config { + filters = { + name = "example_network_view" + } + } +} + +// List specific Network Views using Extensible Attributes +list "nios_ipam_network_view" "list_network_views_using_extensible_attributes" { + provider = nios + config { + extattrfilters = { + Site = "location-1" + } + } +} + +// List Network Views with resource details included +list "nios_ipam_network_view" "list_network_views_with_resource" { + provider = nios + include_resource = true +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 69eed7855..dabea8a60 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -449,6 +449,8 @@ func (p *NIOSProvider) ListResources(ctx context.Context) []func() list.ListReso dns.NewRecordAList, dhcp.NewFixedaddressList, + + ipam.NewNetworkviewList, } } diff --git a/internal/service/ipam/networkview_list.go b/internal/service/ipam/networkview_list.go new file mode 100644 index 000000000..0425554bd --- /dev/null +++ b/internal/service/ipam/networkview_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 = &NetworkviewList{} +var _ list.ListResourceWithConfigure = &NetworkviewList{} + +func NewNetworkviewList() list.ListResource { + return &NetworkviewList{} +} + +// NetworkviewList defines the List implementation. +type NetworkviewList struct { + client *niosclient.APIClient +} + +func (l *NetworkviewList) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + "ipam_network_view" +} + +func (l *NetworkviewList) 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 NetworkviewListModel struct { + Filters types.Map `tfsdk:"filters"` + ExtAttrFilters types.Map `tfsdk:"extattrfilters"` +} + +func (l *NetworkviewList) ListResourceConfigSchema(ctx context.Context, req list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Query existing IPAM Network Views.", + 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 *NetworkviewList) List(ctx context.Context, req list.ListRequest, stream *list.ListResultsStream) { + var data NetworkviewListModel + 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.Networkview, 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. + NetworkviewAPI. + List(ctx). + Filters(flex.ExpandFrameworkMapString(ctx, data.Filters, &diags)). + Extattrfilter(flex.ExpandFrameworkMapString(ctx, data.ExtAttrFilters, &diags)). + ReturnAsObject(1). + ReturnFieldsPlus(readableAttributesForNetworkview). + 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.ListNetworkviewResponseObject.GetResult() + tflog.Info(ctx, fmt.Sprintf("Page %d : Retrieved %d results", pageCount, len(res))) + + // Check for next page ID in additional properties + additionalProperties := apiRes.ListNetworkviewResponseObject.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 Networkview, 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 := FlattenNetworkview(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/networkview_list_test.go b/internal/service/ipam/networkview_list_test.go new file mode 100644 index 000000000..9ae2041ef --- /dev/null +++ b/internal/service/ipam/networkview_list_test.go @@ -0,0 +1,165 @@ +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" + "github.com/infobloxopen/terraform-provider-nios/internal/utils" +) + +func TestAccNetworkviewList_basic(t *testing.T) { + var resourceName = "nios_ipam_network_view.test" + var v ipam.Networkview + name := acctest.RandomNameWithPrefix("test-network-view") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + //Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccNetworkviewBasicConfig(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkviewExists(context.Background(), resourceName, &v), + ), + }, + // Query the object + { + Query: true, + Config: testAccNetworkviewListBasicConfig(), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLengthAtLeast("nios_ipam_network_view.test", 1), + }, + }, + }, + }) +} + +func TestAccNetworkviewList_Filters(t *testing.T) { + var resourceName = "nios_ipam_network_view.test" + var v ipam.Networkview + name := acctest.RandomNameWithPrefix("test-network-view") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccNetworkviewBasicConfig(name), + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkviewExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "name", name), + ), + }, + // Query the object + { + Query: true, + Config: testAccNetworkviewListConfigFilters(name), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_ipam_network_view.test", 1), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func TestAccNetworkviewList_ExtAttrFilters(t *testing.T) { + var resourceName = "nios_ipam_network_view.test_extattrs" + var v ipam.Networkview + name := acctest.RandomNameWithPrefix("test-network-view") + + extAttrValue := acctest.RandomName() + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccNetworkviewExtAttrs(name, map[string]string{ + "Site": extAttrValue, + }), + Check: resource.ComposeTestCheckFunc( + testAccCheckNetworkviewExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "extattrs.Site", extAttrValue), + ), + }, + // Query the object + { + Query: true, + + Config: testAccNetworkviewListConfigExtAttrFilters(extAttrValue), + QueryResultChecks: []querycheck.QueryResultCheck{ + querycheck.ExpectLength("nios_ipam_network_view.test", 1), + }, + }, + // Delete testing automatically occurs in TestCase + }, + }) +} + +func testAccNetworkviewListBasicConfig() string { + return ` +list "nios_ipam_network_view" "test" { + provider = nios + limit = 5 +} +` +} + +func testAccNetworkviewListConfigFilters(name string) string { + return fmt.Sprintf(` +list "nios_ipam_network_view" "test" { + provider = nios + config { + filters = { + name = %q + } + } +} +`, name) +} + +func testAccNetworkviewListConfigExtAttrFilters(name string) string { + return fmt.Sprintf(` +list "nios_ipam_network_view" "test" { + provider = nios + config { + extattrfilters = { + Site = %q + } + } +} +`, name) +} diff --git a/internal/service/ipam/networkview_resource.go b/internal/service/ipam/networkview_resource.go index d5990d75b..34b820086 100644 --- a/internal/service/ipam/networkview_resource.go +++ b/internal/service/ipam/networkview_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" @@ -21,6 +22,7 @@ var readableAttributesForNetworkview = "associated_dns_views,associated_members, // Ensure provider defined types fully satisfy framework interfaces. var _ resource.Resource = &NetworkviewResource{} var _ resource.ResourceWithImportState = &NetworkviewResource{} +var _ resource.ResourceWithIdentity = &NetworkviewResource{} func NewNetworkviewResource() resource.Resource { return &NetworkviewResource{} @@ -42,6 +44,16 @@ func (r *NetworkviewResource) Schema(ctx context.Context, req resource.SchemaReq } } +func (r *NetworkviewResource) 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 *NetworkviewResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { @@ -76,6 +88,7 @@ func (r *NetworkviewResource) Create(ctx context.Context, req resource.CreateReq // 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 } @@ -100,6 +113,9 @@ func (r *NetworkviewResource) Create(ctx context.Context, req resource.CreateReq 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)...) } @@ -145,9 +161,8 @@ func (r *NetworkviewResource) Read(ctx context.Context, req resource.ReadRequest apiTerraformId.Value = "" } - stateExtAttrs := ExpandExtAttrs(ctx, data.ExtAttrsAll, &diags) - if associateInternalId == nil { + stateExtAttrs := ExpandExtAttrs(ctx, data.ExtAttrsAll, &diags) if stateExtAttrs == nil { resp.Diagnostics.AddError( "Missing Internal ID", @@ -155,6 +170,7 @@ func (r *NetworkviewResource) Read(ctx context.Context, req resource.ReadRequest ) return } + stateTerraformId := (*stateExtAttrs)[terraformInternalIDEA] if apiTerraformId.Value != stateTerraformId.Value { if r.ReadByExtAttrs(ctx, &data, resp) { @@ -165,12 +181,16 @@ func (r *NetworkviewResource) Read(ctx context.Context, req resource.ReadRequest res.ExtAttrs, data.ExtAttrsAll, diags = RemoveInheritedExtAttrs(ctx, data.ExtAttrs, *res.ExtAttrs) if diags.HasError() { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error while reading Networkview due inherited Extensible attributes, got error: %s", diags)) + resp.Diagnostics.Append(diags...) + resp.Diagnostics.AddError("Client Error", "Error while reading Networkview due to inherited Extensible attributes") 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)...) } @@ -226,6 +246,10 @@ func (r *NetworkviewResource) ReadByExtAttrs(ctx context.Context, data *Networkv } 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 @@ -256,13 +280,14 @@ func (r *NetworkviewResource) Update(ctx context.Context, req resource.UpdateReq } 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 } } @@ -290,15 +315,18 @@ func (r *NetworkviewResource) Update(ctx context.Context, req resource.UpdateReq res.ExtAttrs, data.ExtAttrsAll, diags = RemoveInheritedExtAttrs(ctx, planExtAttrs, *res.ExtAttrs) if diags.HasError() { - resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Error while update Networkview due inherited Extensible attributes, got error: %s", diags)) + resp.Diagnostics.Append(diags...) + resp.Diagnostics.AddError("Client Error", "Error while updating Networkview due to inherited Extensible attributes") 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 { resp.Diagnostics.Append(resp.Private.SetKey(ctx, "associate_internal_id", nil)...) } @@ -328,6 +356,13 @@ func (r *NetworkviewResource) Delete(ctx context.Context, req resource.DeleteReq } func (r *NetworkviewResource) 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"))...) } From 3528ee6688ddd491cad31e25a3f92b3261fe640e Mon Sep 17 00:00:00 2001 From: Ujjwal Nasra <125353741+unasra@users.noreply.github.com> Date: Tue, 28 Apr 2026 13:54:19 +0530 Subject: [PATCH 09/13] Updated Documentation --- internal/service/ipam/networkview_list_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/service/ipam/networkview_list_test.go b/internal/service/ipam/networkview_list_test.go index 9ae2041ef..80193ec91 100644 --- a/internal/service/ipam/networkview_list_test.go +++ b/internal/service/ipam/networkview_list_test.go @@ -27,7 +27,7 @@ func TestAccNetworkviewList_basic(t *testing.T) { tfversion.SkipBelow(tfversion.Version0_14_0), }, Steps: []resource.TestStep{ - //Provider Setup + // Provider Setup { Config: utils.ProviderSetup(), }, @@ -117,8 +117,7 @@ func TestAccNetworkviewList_ExtAttrFilters(t *testing.T) { }, // Query the object { - Query: true, - + Query: true, Config: testAccNetworkviewListConfigExtAttrFilters(extAttrValue), QueryResultChecks: []querycheck.QueryResultCheck{ querycheck.ExpectLength("nios_ipam_network_view.test", 1), From de6eaf24c16a11e7bd2653a47d04d5cd4334336d Mon Sep 17 00:00:00 2001 From: Anil Gadiyar Date: Wed, 13 May 2026 10:52:31 +0530 Subject: [PATCH 10/13] bulk host name template add TS --- internal/provider/provider.go | 1 + .../service/ipam/bulkhostnametemplate_list.go | 197 ++++++++++++++++++ .../ipam/bulkhostnametemplate_list_test.go | 112 ++++++++++ .../ipam/bulkhostnametemplate_resource.go | 30 ++- 4 files changed, 339 insertions(+), 1 deletion(-) create mode 100644 internal/service/ipam/bulkhostnametemplate_list.go create mode 100644 internal/service/ipam/bulkhostnametemplate_list_test.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 20250d851..e41e133fc 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -464,6 +464,7 @@ func (p *NIOSProvider) ListResources(ctx context.Context) []func() list.ListReso dhcp.NewFixedaddressList, ipam.NewNetworkviewList, + ipam.NewBulkhostnametemplateList, } } 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..a2e5b137a --- /dev/null +++ b/internal/service/ipam/bulkhostnametemplate_list_test.go @@ -0,0 +1,112 @@ +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" + "github.com/infobloxopen/terraform-provider-nios/internal/utils" +) + +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) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccBulkhostnametemplateBasicConfig(templateName, templateFormat), + Check: resource.ComposeTestCheckFunc( + testAccCheckBulkhostnametemplateExists(context.Background(), resourceName, &v), + ), + }, + // Query the object + { + 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) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccBulkhostnametemplateBasicConfig(templateName, templateFormat), + Check: resource.ComposeTestCheckFunc( + testAccCheckBulkhostnametemplateExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "template_name", templateName), + ), + }, + // Query the object + { + 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) +} \ No newline at end of file diff --git a/internal/service/ipam/bulkhostnametemplate_resource.go b/internal/service/ipam/bulkhostnametemplate_resource.go index fe682f800..308385470 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{} @@ -44,6 +46,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 +129,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)...) } @@ -154,7 +169,7 @@ func (r *BulkhostnametemplateResource) Read(ctx context.Context, req resource.Re return 0, callErr }) - // Handle not found case + // Handle not found case if err != nil { if httpRes != nil && httpRes.StatusCode == http.StatusNotFound { // Resource no longer exists, remove from state @@ -169,6 +184,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 +245,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 +286,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) } From 080c18f64ceb09b43d7b30232a9a7d15022ddd55 Mon Sep 17 00:00:00 2001 From: Anil Gadiyar Date: Wed, 13 May 2026 12:04:21 +0530 Subject: [PATCH 11/13] vlanview --- internal/provider/provider.go | 1 + internal/service/ipam/vlanview_list.go | 201 ++++++++++++++++++++ internal/service/ipam/vlanview_list_test.go | 164 ++++++++++++++++ internal/service/ipam/vlanview_resource.go | 39 +++- 4 files changed, 404 insertions(+), 1 deletion(-) create mode 100644 internal/service/ipam/vlanview_list.go create mode 100644 internal/service/ipam/vlanview_list_test.go diff --git a/internal/provider/provider.go b/internal/provider/provider.go index e41e133fc..f9bb22c5d 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -465,6 +465,7 @@ func (p *NIOSProvider) ListResources(ctx context.Context) []func() list.ListReso ipam.NewNetworkviewList, ipam.NewBulkhostnametemplateList, + ipam.NewVlanviewList, } } 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..24510d073 --- /dev/null +++ b/internal/service/ipam/vlanview_list_test.go @@ -0,0 +1,164 @@ +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" + "github.com/infobloxopen/terraform-provider-nios/internal/utils" +) + +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) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccVlanviewBasicConfig(15, name, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckVlanviewExists(context.Background(), resourceName, &v), + ), + }, + // Query the object + { + 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) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + Config: testAccVlanviewBasicConfig(15, name, 10), + Check: resource.ComposeTestCheckFunc( + testAccCheckVlanviewExists(context.Background(), resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "name", name), + ), + }, + // Query the object + { + 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) }, + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version0_14_0), + }, + Steps: []resource.TestStep{ + // Provider Setup + { + Config: utils.ProviderSetup(), + }, + // Create and Read + { + 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 + { + 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..22bdae28f 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{} @@ -45,6 +47,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 +91,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 +137,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 +227,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 +292,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 +326,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 +382,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 +460,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"))...) } From 7e007497ec972ca404a4ce7dc37a49cfd7ffe1dc Mon Sep 17 00:00:00 2001 From: Anil Gadiyar Date: Wed, 20 May 2026 12:06:50 +0530 Subject: [PATCH 12/13] added examples --- .../ipam_bulk_hostname_template.md | 39 +++++++++++++++ docs/list-resources/ipam_vlanview.md | 49 +++++++++++++++++++ .../list-resource.tfquery.hcl | 15 ++++++ .../list-resource.tfquery.hcl | 26 ++++++++++ 4 files changed, 129 insertions(+) create mode 100644 docs/list-resources/ipam_bulk_hostname_template.md create mode 100644 docs/list-resources/ipam_vlanview.md create mode 100644 examples/list-resources/nios_ipam_bulk_hostname_template/list-resource.tfquery.hcl create mode 100644 examples/list-resources/nios_ipam_vlanview/list-resource.tfquery.hcl 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 From ca53146a437d8b70c9d763f471e03650128adcef Mon Sep 17 00:00:00 2001 From: Anil Gadiyar Date: Wed, 20 May 2026 12:59:12 +0530 Subject: [PATCH 13/13] changed test run code --- .../ipam/bulkhostnametemplate_list_test.go | 26 ++++------ .../ipam/bulkhostnametemplate_resource.go | 5 +- .../service/ipam/networkview_list_test.go | 44 +++++++--------- internal/service/ipam/networkview_resource.go | 3 ++ internal/service/ipam/vlanview_list_test.go | 50 ++++++++----------- internal/service/ipam/vlanview_resource.go | 3 ++ internal/utils/utils.go | 12 ----- 7 files changed, 57 insertions(+), 86 deletions(-) diff --git a/internal/service/ipam/bulkhostnametemplate_list_test.go b/internal/service/ipam/bulkhostnametemplate_list_test.go index a2e5b137a..c551dd9ab 100644 --- a/internal/service/ipam/bulkhostnametemplate_list_test.go +++ b/internal/service/ipam/bulkhostnametemplate_list_test.go @@ -12,7 +12,6 @@ import ( "github.com/infobloxopen/infoblox-nios-go-client/ipam" "github.com/infobloxopen/terraform-provider-nios/internal/acctest" - "github.com/infobloxopen/terraform-provider-nios/internal/utils" ) func TestAccBulkhostnametemplateList_basic(t *testing.T) { @@ -22,18 +21,14 @@ func TestAccBulkhostnametemplateList_basic(t *testing.T) { templateFormat := "host-$4" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version0_14_0), + tfversion.SkipBelow(tfversion.Version1_14_0), }, Steps: []resource.TestStep{ - // Provider Setup - { - Config: utils.ProviderSetup(), - }, // Create and Read { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Config: testAccBulkhostnametemplateBasicConfig(templateName, templateFormat), Check: resource.ComposeTestCheckFunc( testAccCheckBulkhostnametemplateExists(context.Background(), resourceName, &v), @@ -41,6 +36,7 @@ func TestAccBulkhostnametemplateList_basic(t *testing.T) { }, // Query the object { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Query: true, Config: testAccBulkhostnametemplateListBasicConfig(), QueryResultChecks: []querycheck.QueryResultCheck{ @@ -58,18 +54,14 @@ func TestAccBulkhostnametemplateList_Filters(t *testing.T) { templateFormat := "host-$4" resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version0_14_0), + tfversion.SkipBelow(tfversion.Version1_14_0), }, Steps: []resource.TestStep{ - // Provider Setup - { - Config: utils.ProviderSetup(), - }, // Create and Read { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Config: testAccBulkhostnametemplateBasicConfig(templateName, templateFormat), Check: resource.ComposeTestCheckFunc( testAccCheckBulkhostnametemplateExists(context.Background(), resourceName, &v), @@ -78,6 +70,8 @@ func TestAccBulkhostnametemplateList_Filters(t *testing.T) { }, // Query the object { + + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Query: true, Config: testAccBulkhostnametemplateListConfigFilters(templateName), QueryResultChecks: []querycheck.QueryResultCheck{ @@ -109,4 +103,4 @@ list "nios_ipam_bulk_hostname_template" "test" { } } `, name) -} \ No newline at end of file +} diff --git a/internal/service/ipam/bulkhostnametemplate_resource.go b/internal/service/ipam/bulkhostnametemplate_resource.go index 308385470..e8d4449ed 100644 --- a/internal/service/ipam/bulkhostnametemplate_resource.go +++ b/internal/service/ipam/bulkhostnametemplate_resource.go @@ -37,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) { @@ -169,7 +172,7 @@ func (r *BulkhostnametemplateResource) Read(ctx context.Context, req resource.Re return 0, callErr }) - // Handle not found case + // Handle not found case if err != nil { if httpRes != nil && httpRes.StatusCode == http.StatusNotFound { // Resource no longer exists, remove from state diff --git a/internal/service/ipam/networkview_list_test.go b/internal/service/ipam/networkview_list_test.go index 80193ec91..a61d9052f 100644 --- a/internal/service/ipam/networkview_list_test.go +++ b/internal/service/ipam/networkview_list_test.go @@ -12,7 +12,6 @@ import ( "github.com/infobloxopen/infoblox-nios-go-client/ipam" "github.com/infobloxopen/terraform-provider-nios/internal/acctest" - "github.com/infobloxopen/terraform-provider-nios/internal/utils" ) func TestAccNetworkviewList_basic(t *testing.T) { @@ -22,26 +21,23 @@ func TestAccNetworkviewList_basic(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version0_14_0), + tfversion.SkipBelow(tfversion.Version1_14_0), }, Steps: []resource.TestStep{ - // Provider Setup - { - Config: utils.ProviderSetup(), - }, // Create and Read { - Config: testAccNetworkviewBasicConfig(name), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Config: testAccNetworkviewBasicConfig(name), Check: resource.ComposeTestCheckFunc( testAccCheckNetworkviewExists(context.Background(), resourceName, &v), ), }, // Query the object { - Query: true, - Config: testAccNetworkviewListBasicConfig(), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccNetworkviewListBasicConfig(), QueryResultChecks: []querycheck.QueryResultCheck{ querycheck.ExpectLengthAtLeast("nios_ipam_network_view.test", 1), }, @@ -57,18 +53,14 @@ func TestAccNetworkviewList_Filters(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version0_14_0), + tfversion.SkipBelow(tfversion.Version1_14_0), }, Steps: []resource.TestStep{ - // Provider Setup - { - Config: utils.ProviderSetup(), - }, // Create and Read { - Config: testAccNetworkviewBasicConfig(name), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Config: testAccNetworkviewBasicConfig(name), Check: resource.ComposeTestCheckFunc( testAccCheckNetworkviewExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", name), @@ -76,8 +68,9 @@ func TestAccNetworkviewList_Filters(t *testing.T) { }, // Query the object { - Query: true, - Config: testAccNetworkviewListConfigFilters(name), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccNetworkviewListConfigFilters(name), QueryResultChecks: []querycheck.QueryResultCheck{ querycheck.ExpectLength("nios_ipam_network_view.test", 1), }, @@ -96,17 +89,13 @@ func TestAccNetworkviewList_ExtAttrFilters(t *testing.T) { resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version0_14_0), + tfversion.SkipBelow(tfversion.Version1_14_0), }, Steps: []resource.TestStep{ - // Provider Setup - { - Config: utils.ProviderSetup(), - }, // Create and Read { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Config: testAccNetworkviewExtAttrs(name, map[string]string{ "Site": extAttrValue, }), @@ -117,8 +106,9 @@ func TestAccNetworkviewList_ExtAttrFilters(t *testing.T) { }, // Query the object { - Query: true, - Config: testAccNetworkviewListConfigExtAttrFilters(extAttrValue), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccNetworkviewListConfigExtAttrFilters(extAttrValue), QueryResultChecks: []querycheck.QueryResultCheck{ querycheck.ExpectLength("nios_ipam_network_view.test", 1), }, diff --git a/internal/service/ipam/networkview_resource.go b/internal/service/ipam/networkview_resource.go index 6c0c30a0f..b7a91eec8 100644 --- a/internal/service/ipam/networkview_resource.go +++ b/internal/service/ipam/networkview_resource.go @@ -37,6 +37,9 @@ type NetworkviewResource struct { func (r *NetworkviewResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_" + "ipam_network_view" + resp.ResourceBehavior = resource.ResourceBehavior{ + MutableIdentity: true, + } } func (r *NetworkviewResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { diff --git a/internal/service/ipam/vlanview_list_test.go b/internal/service/ipam/vlanview_list_test.go index 24510d073..bac0b8a03 100644 --- a/internal/service/ipam/vlanview_list_test.go +++ b/internal/service/ipam/vlanview_list_test.go @@ -12,7 +12,6 @@ import ( "github.com/infobloxopen/infoblox-nios-go-client/ipam" "github.com/infobloxopen/terraform-provider-nios/internal/acctest" - "github.com/infobloxopen/terraform-provider-nios/internal/utils" ) func TestAccVlanviewList_basic(t *testing.T) { @@ -21,27 +20,24 @@ func TestAccVlanviewList_basic(t *testing.T) { name := acctest.RandomNameWithPrefix("vlan_view") resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version0_14_0), + tfversion.SkipBelow(tfversion.Version1_14_0), }, Steps: []resource.TestStep{ - // Provider Setup - { - Config: utils.ProviderSetup(), - }, // Create and Read { - Config: testAccVlanviewBasicConfig(15, name, 10), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Config: testAccVlanviewBasicConfig(15, name, 10), Check: resource.ComposeTestCheckFunc( testAccCheckVlanviewExists(context.Background(), resourceName, &v), ), }, // Query the object { - Query: true, - Config: testAccVlanviewListBasicConfig(), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccVlanviewListBasicConfig(), QueryResultChecks: []querycheck.QueryResultCheck{ querycheck.ExpectLengthAtLeast("nios_ipam_vlanview.test", 1), }, @@ -56,19 +52,15 @@ func TestAccVlanviewList_Filters(t *testing.T) { name := acctest.RandomNameWithPrefix("vlan_view") resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version0_14_0), + tfversion.SkipBelow(tfversion.Version1_14_0), }, Steps: []resource.TestStep{ - // Provider Setup - { - Config: utils.ProviderSetup(), - }, // Create and Read { - Config: testAccVlanviewBasicConfig(15, name, 10), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Config: testAccVlanviewBasicConfig(15, name, 10), Check: resource.ComposeTestCheckFunc( testAccCheckVlanviewExists(context.Background(), resourceName, &v), resource.TestCheckResourceAttr(resourceName, "name", name), @@ -76,8 +68,9 @@ func TestAccVlanviewList_Filters(t *testing.T) { }, // Query the object { - Query: true, - Config: testAccVlanviewListConfigFilters(name), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccVlanviewListConfigFilters(name), QueryResultChecks: []querycheck.QueryResultCheck{ querycheck.ExpectLength("nios_ipam_vlanview.test", 1), }, @@ -95,18 +88,14 @@ func TestAccVlanviewList_ExtAttrFilters(t *testing.T) { extAttrValue := acctest.RandomName() resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + PreCheck: func() { acctest.PreCheck(t) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.SkipBelow(tfversion.Version0_14_0), + tfversion.SkipBelow(tfversion.Version1_14_0), }, Steps: []resource.TestStep{ - // Provider Setup - { - Config: utils.ProviderSetup(), - }, // Create and Read { + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, Config: testAccVlanviewExtAttrs(15, name, 10, map[string]string{ "Site": extAttrValue, }), @@ -117,8 +106,9 @@ func TestAccVlanviewList_ExtAttrFilters(t *testing.T) { }, // Query the object { - Query: true, - Config: testAccVlanviewListConfigExtAttrFilters(extAttrValue), + ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories, + Query: true, + Config: testAccVlanviewListConfigExtAttrFilters(extAttrValue), QueryResultChecks: []querycheck.QueryResultCheck{ querycheck.ExpectLength("nios_ipam_vlanview.test", 1), }, diff --git a/internal/service/ipam/vlanview_resource.go b/internal/service/ipam/vlanview_resource.go index 22bdae28f..79c00c37f 100644 --- a/internal/service/ipam/vlanview_resource.go +++ b/internal/service/ipam/vlanview_resource.go @@ -38,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) { diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 39f86f64a..597ac8318 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -886,15 +886,3 @@ func ReorderAndFilterDHCPOptions( return newList, &diags } -func ProviderSetup() string { - return ` - terraform { - required_providers { - nios = { - source = "registry.terraform.io/infobloxopen/nios" - version = "1.1.0" - } - } - } -` -}