diff --git a/CHANGELOG.md b/CHANGELOG.md index feaedb045..2e7aa571d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ ## [Unreleased] - Add `ignore_missing_component_templates` to `elasticstack_elasticsearch_index_template` ([#1206](https://github.com/elastic/terraform-provider-elasticstack/pull/1206)) +- Migrate `elasticstack_elasticsearch_enrich_policy` resource and data source to Terraform Plugin Framework ([#1220](https://github.com/elastic/terraform-provider-elasticstack/pull/1220)) - Prevent provider panic when a script exists in state, but not in Elasticsearch ([#1218](https://github.com/elastic/terraform-provider-elasticstack/pull/1218)) ## [0.11.17] - 2025-07-21 diff --git a/docs/data-sources/elasticsearch_enrich_policy.md b/docs/data-sources/elasticsearch_enrich_policy.md index be4f00bb7..9c286ae92 100644 --- a/docs/data-sources/elasticsearch_enrich_policy.md +++ b/docs/data-sources/elasticsearch_enrich_policy.md @@ -74,11 +74,35 @@ output "query" { - `name` (String) The name of the policy. +### Optional + +- `elasticsearch_connection` (Block List, Deprecated) Elasticsearch connection configuration block. (see [below for nested schema](#nestedblock--elasticsearch_connection)) + ### Read-Only - `enrich_fields` (Set of String) Fields to add to matching incoming documents. These fields must be present in the source indices. - `id` (String) Internal identifier of the resource - `indices` (Set of String) Array of one or more source indices used to create the enrich index. -- `match_field` (String) Field in source indices used to match incoming documents. +- `match_field` (String) Field from the source indices used to match incoming documents. - `policy_type` (String) The type of enrich policy, can be one of geo_match, match, range. - `query` (String) Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query. + + +### Nested Schema for `elasticsearch_connection` + +Optional: + +- `api_key` (String, Sensitive) API Key to use for authentication to Elasticsearch +- `bearer_token` (String, Sensitive) Bearer Token to use for authentication to Elasticsearch +- `ca_data` (String) PEM-encoded custom Certificate Authority certificate +- `ca_file` (String) Path to a custom Certificate Authority certificate +- `cert_data` (String) PEM encoded certificate for client auth +- `cert_file` (String) Path to a file containing the PEM encoded certificate for client auth +- `endpoints` (List of String, Sensitive) A list of endpoints where the terraform provider will point to, this must include the http(s) schema and port number. +- `es_client_authentication` (String, Sensitive) ES Client Authentication field to be used with the JWT token +- `headers` (Map of String, Sensitive) A list of headers to be sent with each request to Elasticsearch. +- `insecure` (Boolean) Disable TLS certificate validation +- `key_data` (String, Sensitive) PEM encoded private key for client auth +- `key_file` (String) Path to a file containing the PEM encoded private key for client auth +- `password` (String, Sensitive) Password to use for API authentication to Elasticsearch. +- `username` (String) Username to use for API authentication to Elasticsearch. diff --git a/docs/resources/elasticsearch_enrich_policy.md b/docs/resources/elasticsearch_enrich_policy.md index 0fb926ffa..1845fc54e 100644 --- a/docs/resources/elasticsearch_enrich_policy.md +++ b/docs/resources/elasticsearch_enrich_policy.md @@ -58,13 +58,13 @@ resource "elasticstack_elasticsearch_enrich_policy" "policy1" { ### Optional -- `elasticsearch_connection` (Block List, Max: 1, Deprecated) Elasticsearch connection configuration block. This property will be removed in a future provider version. Configure the Elasticsearch connection via the provider configuration instead. (see [below for nested schema](#nestedblock--elasticsearch_connection)) +- `elasticsearch_connection` (Block List, Deprecated) Elasticsearch connection configuration block. (see [below for nested schema](#nestedblock--elasticsearch_connection)) - `execute` (Boolean) Whether to call the execute API function in order to create the enrich index. - `query` (String) Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query. ### Read-Only -- `id` (String) The ID of this resource. +- `id` (String) Internal identifier of the resource ### Nested Schema for `elasticsearch_connection` diff --git a/internal/elasticsearch/enrich/acc_test.go b/internal/elasticsearch/enrich/acc_test.go new file mode 100644 index 000000000..cfbaf346e --- /dev/null +++ b/internal/elasticsearch/enrich/acc_test.go @@ -0,0 +1,200 @@ +package enrich_test + +import ( + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/elastic/terraform-provider-elasticstack/internal/acctest" + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccResourceEnrichPolicyFW(t *testing.T) { + name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + CheckDestroy: checkEnrichPolicyDestroyFW(name), + ProtoV6ProviderFactories: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccEnrichPolicyFW(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "name", name), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "policy_type", "match"), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "match_field", `email`), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "indices.0", name), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "enrich_fields.0", "first_name"), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "enrich_fields.1", "last_name"), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "query", "{\"match_all\": {}}\n"), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "execute", "true"), + ), + }, + }, + }) +} + +func TestAccDataSourceEnrichPolicyFW(t *testing.T) { + name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ProtoV6ProviderFactories: acctest.Providers, + Steps: []resource.TestStep{ + { + Config: testAccEnrichPolicyDataSourceFW(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "name", name), + resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "policy_type", "match"), + resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "match_field", "email"), + resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "indices.0", name), + resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "enrich_fields.0", "first_name"), + resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "enrich_fields.1", "last_name"), + resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "query", "{\"match_all\":{}}"), + ), + }, + }, + }) +} + +func TestAccResourceEnrichPolicyFromSDK(t *testing.T) { + name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + Steps: []resource.TestStep{ + { + // Create the enrich policy with the last provider version where the enrich policy resource was built on the SDK + ExternalProviders: map[string]resource.ExternalProvider{ + "elasticstack": { + Source: "elastic/elasticstack", + VersionConstraint: "0.11.17", + }, + }, + Config: testAccEnrichPolicyFW(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "name", name), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "policy_type", "match"), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "execute", "true"), + ), + }, + { + ProtoV6ProviderFactories: acctest.Providers, + Config: testAccEnrichPolicyFW(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "name", name), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "policy_type", "match"), + resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "execute", "true"), + ), + }, + }, + }) +} + +func testAccEnrichPolicyFW(name string) string { + return fmt.Sprintf(` +provider "elasticstack" { + elasticsearch {} +} + +resource "elasticstack_elasticsearch_index" "my_index" { + name = "%s" + + mappings = jsonencode({ + properties = { + email = { type = "text" } + first_name = { type = "text" } + last_name = { type = "text" } + } + }) + deletion_protection = false +} + +resource "elasticstack_elasticsearch_enrich_policy" "policy" { + name = "%s" + policy_type = "match" + indices = [elasticstack_elasticsearch_index.my_index.name] + match_field = "email" + enrich_fields = ["first_name", "last_name"] + query = <<-EOD + {"match_all": {}} + EOD +} + `, name, name) +} + +func testAccEnrichPolicyDataSourceFW(name string) string { + return fmt.Sprintf(` +provider "elasticstack" { + elasticsearch {} +} + +resource "elasticstack_elasticsearch_index" "my_index" { + name = "%s" + + mappings = jsonencode({ + properties = { + email = { type = "text" } + first_name = { type = "text" } + last_name = { type = "text" } + } + }) + deletion_protection = false +} + +resource "elasticstack_elasticsearch_enrich_policy" "policy" { + name = "%s" + policy_type = "match" + indices = [elasticstack_elasticsearch_index.my_index.name] + match_field = "email" + enrich_fields = ["first_name", "last_name"] + query = <<-EOD + {"match_all": {}} + EOD +} + +data "elasticstack_elasticsearch_enrich_policy" "test" { + name = elasticstack_elasticsearch_enrich_policy.policy.name +} + `, name, name) +} + +func checkEnrichPolicyDestroyFW(name string) func(s *terraform.State) error { + return func(s *terraform.State) error { + client, err := clients.NewAcceptanceTestingClient() + if err != nil { + return err + } + + for _, rs := range s.RootModule().Resources { + if rs.Type != "elasticstack_elasticsearch_enrich_policy" { + continue + } + compId, _ := clients.CompositeIdFromStr(rs.Primary.ID) + if compId.ResourceId != name { + return fmt.Errorf("Found unexpectedly enrich policy: %s", compId.ResourceId) + } + esClient, err := client.GetESClient() + if err != nil { + return err + } + req := esClient.EnrichGetPolicy.WithName(compId.ResourceId) + res, err := esClient.EnrichGetPolicy(req) + if err != nil { + return err + } + defer res.Body.Close() + if res.StatusCode == http.StatusFound { + var policiesResponse map[string]any + if err := json.NewDecoder(res.Body).Decode(&policiesResponse); err != nil { + return err + } + if len(policiesResponse["policies"].([]any)) != 0 { + return fmt.Errorf("Enrich policy (%s) still exists", compId.ResourceId) + } + } + } + return nil + } +} diff --git a/internal/elasticsearch/enrich/create.go b/internal/elasticsearch/enrich/create.go new file mode 100644 index 000000000..1f1f2e368 --- /dev/null +++ b/internal/elasticsearch/enrich/create.go @@ -0,0 +1,94 @@ +package enrich + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch" + "github.com/elastic/terraform-provider-elasticstack/internal/models" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "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/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *enrichPolicyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + diags := r.upsert(ctx, req.Plan, &resp.State) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *enrichPolicyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + diags := r.upsert(ctx, req.Plan, &resp.State) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (r *enrichPolicyResource) upsert(ctx context.Context, plan tfsdk.Plan, state *tfsdk.State) diag.Diagnostics { + var data EnrichPolicyDataWithExecute + var diags diag.Diagnostics + diags.Append(plan.Get(ctx, &data)...) + if diags.HasError() { + return diags + } + + policyName := data.Name.ValueString() + id, sdkDiags := r.client.ID(ctx, policyName) + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + if diags.HasError() { + return diags + } + + client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client) + diags.Append(diags...) + if diags.HasError() { + return diags + } + + // Convert framework types to model + indices := utils.SetTypeAs[string](ctx, data.Indices, path.Empty(), &diags) + if diags.HasError() { + return diags + } + + enrichFields := utils.SetTypeAs[string](ctx, data.EnrichFields, path.Empty(), &diags) + if diags.HasError() { + return diags + } + + policy := &models.EnrichPolicy{ + Type: data.PolicyType.ValueString(), + Name: policyName, + Indices: indices, + MatchField: data.MatchField.ValueString(), + EnrichFields: enrichFields, + } + + if !data.Query.IsNull() && !data.Query.IsUnknown() { + policy.Query = data.Query.ValueString() + } + + if sdkDiags := elasticsearch.PutEnrichPolicy(ctx, client, policy); sdkDiags.HasError() { + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + return diags + } + + data.Id = types.StringValue(id.String()) + + // Execute policy if requested + if !data.Execute.IsNull() && !data.Execute.IsUnknown() && data.Execute.ValueBool() { + if sdkDiags := elasticsearch.ExecuteEnrichPolicy(ctx, client, policyName); sdkDiags.HasError() { + diags.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + return diags + } + } + + diags.Append(state.Set(ctx, &data)...) + return diags +} diff --git a/internal/elasticsearch/enrich/data_source.go b/internal/elasticsearch/enrich/data_source.go new file mode 100644 index 000000000..446754661 --- /dev/null +++ b/internal/elasticsearch/enrich/data_source.go @@ -0,0 +1,122 @@ +package enrich + +import ( + "context" + "fmt" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + + providerschema "github.com/elastic/terraform-provider-elasticstack/internal/schema" +) + +func NewEnrichPolicyDataSource() datasource.DataSource { + return &enrichPolicyDataSource{} +} + +type enrichPolicyDataSource struct { + client *clients.ApiClient +} + +func (d *enrichPolicyDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_elasticsearch_enrich_policy" +} + +func (d *enrichPolicyDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + client, diags := clients.ConvertProviderData(req.ProviderData) + resp.Diagnostics.Append(diags...) + d.client = client +} + +func (d *enrichPolicyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = GetDataSourceSchema() +} + +func GetDataSourceSchema() schema.Schema { + return schema.Schema{ + MarkdownDescription: "Returns information about an enrich policy. See: https://www.elastic.co/guide/en/elasticsearch/reference/current/get-enrich-policy-api.html", + Blocks: map[string]schema.Block{ + "elasticsearch_connection": providerschema.GetEsFWConnectionBlock("elasticsearch_connection", false), + }, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "Internal identifier of the resource", + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "The name of the policy.", + Required: true, + }, + "policy_type": schema.StringAttribute{ + MarkdownDescription: "The type of enrich policy, can be one of geo_match, match, range.", + Computed: true, + }, + "indices": schema.SetAttribute{ + MarkdownDescription: "Array of one or more source indices used to create the enrich index.", + ElementType: types.StringType, + Computed: true, + }, + "match_field": schema.StringAttribute{ + MarkdownDescription: "Field from the source indices used to match incoming documents.", + Computed: true, + }, + "enrich_fields": schema.SetAttribute{ + MarkdownDescription: "Fields to add to matching incoming documents. These fields must be present in the source indices.", + ElementType: types.StringType, + Computed: true, + }, + "query": schema.StringAttribute{ + MarkdownDescription: "Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.", + CustomType: jsontypes.NormalizedType{}, + Computed: true, + }, + }, + } +} + +func (d *enrichPolicyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data EnrichPolicyData + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + policyName := data.Name.ValueString() + client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, d.client) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + id, sdkDiags := client.ID(ctx, policyName) + resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + if resp.Diagnostics.HasError() { + return + } + data.Id = types.StringValue(id.String()) + + // Use the same read logic as the resource + policy, sdkDiags := elasticsearch.GetEnrichPolicy(ctx, client, policyName) + resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + if resp.Diagnostics.HasError() { + return + } + + if policy == nil { + resp.Diagnostics.AddError("Policy not found", fmt.Sprintf("Enrich policy '%s' not found", policyName)) + return + } + + // Convert model to framework types using shared function + data.populateFromPolicy(ctx, policy, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/elasticsearch/enrich/delete.go b/internal/elasticsearch/enrich/delete.go new file mode 100644 index 000000000..aa249f6d0 --- /dev/null +++ b/internal/elasticsearch/enrich/delete.go @@ -0,0 +1,34 @@ +package enrich + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +func (r *enrichPolicyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data EnrichPolicyDataWithExecute + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + policyName := compId.ResourceId + + client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + sdkDiags := elasticsearch.DeleteEnrichPolicy(ctx, client, policyName) + resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) +} diff --git a/internal/elasticsearch/enrich/models.go b/internal/elasticsearch/enrich/models.go new file mode 100644 index 000000000..ca76c4216 --- /dev/null +++ b/internal/elasticsearch/enrich/models.go @@ -0,0 +1,49 @@ +package enrich + +import ( + "context" + + "github.com/elastic/terraform-provider-elasticstack/internal/models" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type EnrichPolicyData struct { + Id types.String `tfsdk:"id"` + ElasticsearchConnection types.List `tfsdk:"elasticsearch_connection"` + Name types.String `tfsdk:"name"` + PolicyType types.String `tfsdk:"policy_type"` + Indices types.Set `tfsdk:"indices"` + MatchField types.String `tfsdk:"match_field"` + EnrichFields types.Set `tfsdk:"enrich_fields"` + Query jsontypes.Normalized `tfsdk:"query"` +} + +type EnrichPolicyDataWithExecute struct { + EnrichPolicyData + Execute types.Bool `tfsdk:"execute"` +} + +// populateFromPolicy converts models.EnrichPolicy to EnrichPolicyData fields +func (data *EnrichPolicyData) populateFromPolicy(ctx context.Context, policy *models.EnrichPolicy, diagnostics *diag.Diagnostics) { + data.Name = types.StringValue(policy.Name) + data.PolicyType = types.StringValue(policy.Type) + data.MatchField = types.StringValue(policy.MatchField) + + if policy.Query != "" && policy.Query != "null" { + data.Query = jsontypes.NewNormalizedValue(policy.Query) + } else { + data.Query = jsontypes.NewNormalizedNull() + } + + // Convert string slices to Set + data.Indices = utils.SetValueFrom(ctx, policy.Indices, types.StringType, path.Empty(), diagnostics) + if diagnostics.HasError() { + return + } + + data.EnrichFields = utils.SetValueFrom(ctx, policy.EnrichFields, types.StringType, path.Empty(), diagnostics) +} diff --git a/internal/elasticsearch/enrich/policy.go b/internal/elasticsearch/enrich/policy.go deleted file mode 100644 index 16179c441..000000000 --- a/internal/elasticsearch/enrich/policy.go +++ /dev/null @@ -1,182 +0,0 @@ -package enrich - -import ( - "context" - "fmt" - - "github.com/elastic/terraform-provider-elasticstack/internal/clients" - "github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch" - "github.com/elastic/terraform-provider-elasticstack/internal/models" - "github.com/elastic/terraform-provider-elasticstack/internal/utils" - "github.com/hashicorp/terraform-plugin-log/tflog" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -func ResourceEnrichPolicy() *schema.Resource { - policySchema := map[string]*schema.Schema{ - "name": { - Description: "Name of the enrich policy to manage.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "policy_type": { - Description: "The type of enrich policy, can be one of geo_match, match, range.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{"geo_match", "match", "range"}, false), - }, - "indices": { - Description: "Array of one or more source indices used to create the enrich index.", - Type: schema.TypeSet, - Required: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "match_field": { - Description: "Field in source indices used to match incoming documents.", - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 255), - ), - }, - "enrich_fields": { - Description: "Fields to add to matching incoming documents. These fields must be present in the source indices.", - Type: schema.TypeSet, - Required: true, - ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "query": { - Description: "Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.", - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ValidateFunc: validation.StringIsJSON, - DiffSuppressFunc: utils.DiffJsonSuppress, - }, - "execute": { - Description: "Whether to call the execute API function in order to create the enrich index.", - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - Default: true, - }, - } - - utils.AddConnectionSchema(policySchema) - - return &schema.Resource{ - Description: "Managing Elasticsearch enrich policies, see: https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-apis.html", - - CreateContext: resourceEnrichPolicyPut, - UpdateContext: resourceEnrichPolicyPut, - ReadContext: resourceEnrichPolicyRead, - DeleteContext: resourceEnrichPolicyDelete, - - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - - Schema: policySchema, - } -} - -func resourceEnrichPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client, diags := clients.NewApiClientFromSDKResource(d, meta) - if diags.HasError() { - return diags - } - - compName, diags := clients.CompositeIdFromStr(d.Id()) - if diags.HasError() { - return diags - } - policy, diags := elasticsearch.GetEnrichPolicy(ctx, client, compName.ResourceId) - if policy == nil && diags == nil { - tflog.Warn(ctx, fmt.Sprintf(`Enrich policy "%s" not found, removing from state`, compName.ResourceId)) - d.SetId("") - return diags - } - if diags.HasError() { - return diags - } - - if err := d.Set("name", policy.Name); err != nil { - return diag.FromErr(err) - } - if err := d.Set("policy_type", policy.Type); err != nil { - return diag.FromErr(err) - } - if err := d.Set("indices", policy.Indices); err != nil { - return diag.FromErr(err) - } - if err := d.Set("match_field", policy.MatchField); err != nil { - return diag.FromErr(err) - } - if err := d.Set("enrich_fields", policy.EnrichFields); err != nil { - return diag.FromErr(err) - } - if err := d.Set("query", policy.Query); err != nil { - return diag.FromErr(err) - } - return diags -} - -func resourceEnrichPolicyPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client, diags := clients.NewApiClientFromSDKResource(d, meta) - if diags.HasError() { - return diags - } - - name := d.Get("name").(string) - id, diags := client.ID(ctx, name) - if diags.HasError() { - return diags - } - policy := &models.EnrichPolicy{ - Type: d.Get("policy_type").(string), - Name: name, - Indices: utils.ExpandStringSet(d.Get("indices").(*schema.Set)), - MatchField: d.Get("match_field").(string), - EnrichFields: utils.ExpandStringSet(d.Get("enrich_fields").(*schema.Set)), - } - - if query, ok := d.GetOk("query"); ok { - policy.Query = query.(string) - } - - if diags = elasticsearch.PutEnrichPolicy(ctx, client, policy); diags.HasError() { - return diags - } - d.SetId(id.String()) - if d.Get("execute").(bool) { - diags := elasticsearch.ExecuteEnrichPolicy(ctx, client, name) - if diags.HasError() { - return diags - } - } - return resourceEnrichPolicyRead(ctx, d, meta) -} - -func resourceEnrichPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client, diags := clients.NewApiClientFromSDKResource(d, meta) - if diags.HasError() { - return diags - } - - compName, diags := clients.CompositeIdFromStr(d.Id()) - if diags.HasError() { - return diags - } - return elasticsearch.DeleteEnrichPolicy(ctx, client, compName.ResourceId) -} diff --git a/internal/elasticsearch/enrich/policy_data_source.go b/internal/elasticsearch/enrich/policy_data_source.go deleted file mode 100644 index 975c50294..000000000 --- a/internal/elasticsearch/enrich/policy_data_source.go +++ /dev/null @@ -1,76 +0,0 @@ -package enrich - -import ( - "context" - - "github.com/elastic/terraform-provider-elasticstack/internal/clients" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -func DataSourceEnrichPolicy() *schema.Resource { - policySchema := map[string]*schema.Schema{ - "id": { - Description: "Internal identifier of the resource", - Type: schema.TypeString, - Computed: true, - }, - "name": { - Description: "The name of the policy.", - Type: schema.TypeString, - Required: true, - }, - "policy_type": { - Description: "The type of enrich policy, can be one of geo_match, match, range.", - Type: schema.TypeString, - Computed: true, - }, - "indices": { - Description: "Array of one or more source indices used to create the enrich index.", - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "match_field": { - Description: "Field in source indices used to match incoming documents.", - Type: schema.TypeString, - Computed: true, - }, - "enrich_fields": { - Description: "Fields to add to matching incoming documents. These fields must be present in the source indices.", - Type: schema.TypeSet, - Computed: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - }, - "query": { - Description: "Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.", - Type: schema.TypeString, - Computed: true, - }, - } - - return &schema.Resource{ - Description: "Returns information about an enrich policy. See: https://www.elastic.co/guide/en/elasticsearch/reference/current/get-enrich-policy-api.html", - ReadContext: dataSourceEnrichPolicyRead, - Schema: policySchema, - } -} - -func dataSourceEnrichPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - client, diags := clients.NewApiClientFromSDKResource(d, meta) - if diags.HasError() { - return diags - } - - policyId := d.Get("name").(string) - id, diags := client.ID(ctx, policyId) - if diags.HasError() { - return diags - } - d.SetId(id.String()) - return resourceEnrichPolicyRead(ctx, d, meta) -} diff --git a/internal/elasticsearch/enrich/policy_data_source_test.go b/internal/elasticsearch/enrich/policy_data_source_test.go deleted file mode 100644 index be51d1094..000000000 --- a/internal/elasticsearch/enrich/policy_data_source_test.go +++ /dev/null @@ -1,68 +0,0 @@ -package enrich_test - -import ( - "fmt" - "testing" - - "github.com/elastic/terraform-provider-elasticstack/internal/acctest" - sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAccDataSourceEnrichPolicy(t *testing.T) { - name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ProtoV6ProviderFactories: acctest.Providers, - Steps: []resource.TestStep{ - { - Config: testAccEnrichPolicyDataSource(name), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "name", name), - resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "policy_type", "match"), - resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "match_field", "email"), - resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "indices.0", name), - resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "enrich_fields.0", "first_name"), - resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "enrich_fields.1", "last_name"), - resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_enrich_policy.test", "query", "{\"match_all\":{}}"), - ), - }, - }, - }) -} - -func testAccEnrichPolicyDataSource(name string) string { - return fmt.Sprintf(` -provider "elasticstack" { - elasticsearch {} -} - -resource "elasticstack_elasticsearch_index" "my_index" { - name = "%s" - - mappings = jsonencode({ - properties = { - email = { type = "text" } - first_name = { type = "text" } - last_name = { type = "text" } - } - }) - deletion_protection = false -} - -resource "elasticstack_elasticsearch_enrich_policy" "policy" { - name = "%s" - policy_type = "match" - indices = [elasticstack_elasticsearch_index.my_index.name] - match_field = "email" - enrich_fields = ["first_name", "last_name"] - query = <<-EOD - {"match_all": {}} - EOD -} - -data "elasticstack_elasticsearch_enrich_policy" "test" { - name = elasticstack_elasticsearch_enrich_policy.policy.name -} - `, name, name) -} diff --git a/internal/elasticsearch/enrich/policy_test.go b/internal/elasticsearch/enrich/policy_test.go deleted file mode 100644 index a33a6e3bb..000000000 --- a/internal/elasticsearch/enrich/policy_test.go +++ /dev/null @@ -1,109 +0,0 @@ -package enrich_test - -import ( - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/elastic/terraform-provider-elasticstack/internal/acctest" - "github.com/elastic/terraform-provider-elasticstack/internal/clients" - sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -func TestAccResourceEnrichPolicy(t *testing.T) { - name := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - CheckDestroy: checkEnrichPolicyDestroy(name), - ProtoV6ProviderFactories: acctest.Providers, - Steps: []resource.TestStep{ - { - Config: testAccEnrichPolicy(name), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "name", name), - resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "policy_type", "match"), - resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "match_field", `email`), - resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "indices.0", name), - resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "enrich_fields.0", "first_name"), - resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "enrich_fields.1", "last_name"), - resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "query", "{\"match_all\":{}}"), - resource.TestCheckResourceAttr("elasticstack_elasticsearch_enrich_policy.policy", "execute", "true"), - ), - }, - }, - }) -} - -func testAccEnrichPolicy(name string) string { - return fmt.Sprintf(` -provider "elasticstack" { - elasticsearch {} -} - -resource "elasticstack_elasticsearch_index" "my_index" { - name = "%s" - - mappings = jsonencode({ - properties = { - email = { type = "text" } - first_name = { type = "text" } - last_name = { type = "text" } - } - }) - deletion_protection = false -} - -resource "elasticstack_elasticsearch_enrich_policy" "policy" { - name = "%s" - policy_type = "match" - indices = [elasticstack_elasticsearch_index.my_index.name] - match_field = "email" - enrich_fields = ["first_name", "last_name"] - query = <<-EOD - {"match_all": {}} - EOD -} - `, name, name) -} - -func checkEnrichPolicyDestroy(name string) func(s *terraform.State) error { - return func(s *terraform.State) error { - client, err := clients.NewAcceptanceTestingClient() - if err != nil { - return err - } - - for _, rs := range s.RootModule().Resources { - if rs.Type != "elasticstack_elasticsearch_enrich_policy" { - continue - } - compId, _ := clients.CompositeIdFromStr(rs.Primary.ID) - if compId.ResourceId != name { - return fmt.Errorf("Found unexpectedly enrich policy: %s", compId.ResourceId) - } - esClient, err := client.GetESClient() - if err != nil { - return err - } - req := esClient.EnrichGetPolicy.WithName(compId.ResourceId) - res, err := esClient.EnrichGetPolicy(req) - if err != nil { - return err - } - defer res.Body.Close() - if res.StatusCode == http.StatusFound { - var policiesResponse map[string]any - if err := json.NewDecoder(res.Body).Decode(&policiesResponse); err != nil { - return err - } - if len(policiesResponse["policies"].([]any)) != 0 { - return fmt.Errorf("Enrich policy (%s) still exists", compId.ResourceId) - } - } - } - return nil - } -} diff --git a/internal/elasticsearch/enrich/read.go b/internal/elasticsearch/enrich/read.go new file mode 100644 index 000000000..7e7f366e9 --- /dev/null +++ b/internal/elasticsearch/enrich/read.go @@ -0,0 +1,53 @@ +package enrich + +import ( + "context" + "fmt" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/elastic/terraform-provider-elasticstack/internal/clients/elasticsearch" + "github.com/elastic/terraform-provider-elasticstack/internal/utils" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func (r *enrichPolicyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data EnrichPolicyDataWithExecute + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + compId, diags := clients.CompositeIdFromStrFw(data.Id.ValueString()) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + policyName := compId.ResourceId + + client, diags := clients.MaybeNewApiClientFromFrameworkResource(ctx, data.ElasticsearchConnection, r.client) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + policy, sdkDiags := elasticsearch.GetEnrichPolicy(ctx, client, policyName) + resp.Diagnostics.Append(utils.FrameworkDiagsFromSDK(sdkDiags)...) + if resp.Diagnostics.HasError() { + return + } + + if policy == nil { + tflog.Warn(ctx, fmt.Sprintf(`Enrich policy "%s" not found, removing from state`, policyName)) + resp.State.RemoveResource(ctx) + return + } + + // Convert model to framework types using shared function + data.populateFromPolicy(ctx, policy, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/elasticsearch/enrich/resource.go b/internal/elasticsearch/enrich/resource.go new file mode 100644 index 000000000..28da90c60 --- /dev/null +++ b/internal/elasticsearch/enrich/resource.go @@ -0,0 +1,127 @@ +package enrich + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/elastic/terraform-provider-elasticstack/internal/clients" + providerschema "github.com/elastic/terraform-provider-elasticstack/internal/schema" +) + +func NewEnrichPolicyResource() resource.Resource { + return &enrichPolicyResource{} +} + +type enrichPolicyResource struct { + client *clients.ApiClient +} + +func (r *enrichPolicyResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_elasticsearch_enrich_policy" +} + +func (r *enrichPolicyResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + client, diags := clients.ConvertProviderData(req.ProviderData) + resp.Diagnostics.Append(diags...) + r.client = client +} + +func (r *enrichPolicyResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = GetResourceSchema() +} + +func GetResourceSchema() schema.Schema { + return schema.Schema{ + MarkdownDescription: "Managing Elasticsearch enrich policies. See: https://www.elastic.co/guide/en/elasticsearch/reference/current/enrich-apis.html", + Blocks: map[string]schema.Block{ + "elasticsearch_connection": providerschema.GetEsFWConnectionBlock("elasticsearch_connection", false), + }, + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "Internal identifier of the resource", + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the enrich policy to manage.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 255), + }, + }, + "policy_type": schema.StringAttribute{ + MarkdownDescription: "The type of enrich policy, can be one of geo_match, match, range.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.OneOf("geo_match", "match", "range"), + }, + }, + "indices": schema.SetAttribute{ + MarkdownDescription: "Array of one or more source indices used to create the enrich index.", + ElementType: types.StringType, + Required: true, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), + }, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + }, + }, + "match_field": schema.StringAttribute{ + MarkdownDescription: "Field in source indices used to match incoming documents.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Validators: []validator.String{ + stringvalidator.LengthBetween(1, 255), + }, + }, + "enrich_fields": schema.SetAttribute{ + MarkdownDescription: "Fields to add to matching incoming documents. These fields must be present in the source indices.", + ElementType: types.StringType, + Required: true, + PlanModifiers: []planmodifier.Set{ + setplanmodifier.RequiresReplace(), + }, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + }, + }, + "query": schema.StringAttribute{ + MarkdownDescription: "Query used to filter documents in the enrich index. The policy only uses documents matching this query to enrich incoming documents. Defaults to a match_all query.", + Optional: true, + CustomType: jsontypes.NormalizedType{}, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "execute": schema.BoolAttribute{ + MarkdownDescription: "Whether to call the execute API function in order to create the enrich index.", + Optional: true, + Computed: true, + Default: booldefault.StaticBool(true), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + }, + } +} diff --git a/internal/utils/tfsdk.go b/internal/utils/tfsdk.go index 275d467ea..eb594da44 100644 --- a/internal/utils/tfsdk.go +++ b/internal/utils/tfsdk.go @@ -12,6 +12,11 @@ import ( "github.com/hashicorp/terraform-plugin-framework/types/basetypes" ) +type Elementable interface { + attr.Value + ElementsAs(ctx context.Context, target interface{}, allowUnhandled bool) diag.Diagnostics +} + type ListMeta struct { Context context.Context Index int @@ -46,6 +51,22 @@ func ValueStringPointer(value types.String) *string { return value.ValueStringPointer() } +// ================ +// ==== Generic === +// ================ + +// MapTypeAs converts a types.Map into a tfsdk aware map[string]T. +func elementsAs[T any](ctx context.Context, value Elementable, p path.Path, diags *diag.Diagnostics) T { + var result T + if !IsKnown(value) { + return result + } + + d := value.ElementsAs(ctx, &result, false) + diags.Append(ConvertToAttrDiags(d, p)...) + return result +} + // ================ // ===== Maps ===== // ================ @@ -107,14 +128,7 @@ func MapTypeToMap[T1 any, T2 any](ctx context.Context, value types.Map, p path.P // MapTypeAs converts a types.Map into a tfsdk aware map[string]T. func MapTypeAs[T any](ctx context.Context, value types.Map, p path.Path, diags *diag.Diagnostics) map[string]T { - if !IsKnown(value) { - return nil - } - - var items map[string]T - d := value.ElementsAs(ctx, &items, false) - diags.Append(ConvertToAttrDiags(d, p)...) - return items + return elementsAs[map[string]T](ctx, value, p, diags) } // MapValueFrom converts a tfsdk aware map[string]T to a types.Map. @@ -145,10 +159,7 @@ func SliceToListType[T1 any, T2 any](ctx context.Context, value []T1, elemType a // SliceToListType_String converts a tfsdk naive []string into a types.List. // This is a shorthand SliceToListType helper for strings. func SliceToListType_String(ctx context.Context, value []string, p path.Path, diags *diag.Diagnostics) types.List { - return SliceToListType(ctx, value, types.StringType, p, diags, - func(item string, meta ListMeta) types.String { - return types.StringValue(item) - }) + return ListValueFrom(ctx, value, types.StringType, p, diags) } // ListTypeToMap converts a types.List first into a tfsdk aware map[string]T1 @@ -184,21 +195,12 @@ func ListTypeToSlice[T1 any, T2 any](ctx context.Context, value types.List, p pa // ListTypeToSlice_String converts a types.List into a []string. // This is a shorthand ListTypeToSlice helper for strings. func ListTypeToSlice_String(ctx context.Context, value types.List, p path.Path, diags *diag.Diagnostics) []string { - return ListTypeToSlice(ctx, value, p, diags, func(item types.String, meta ListMeta) string { - return item.ValueString() - }) + return ListTypeAs[string](ctx, value, p, diags) } // ListTypeAs converts a types.List into a tfsdk aware []T. func ListTypeAs[T any](ctx context.Context, value types.List, p path.Path, diags *diag.Diagnostics) []T { - if !IsKnown(value) { - return nil - } - - var items []T - nd := value.ElementsAs(ctx, &items, false) - diags.Append(ConvertToAttrDiags(nd, p)...) - return items + return elementsAs[[]T](ctx, value, p, diags) } // ListValueFrom converts a tfsdk aware []T to a types.List. @@ -208,6 +210,21 @@ func ListValueFrom[T any](ctx context.Context, value []T, elemType attr.Type, p return list } +// =================== +// ===== Sets ===== +// =================== + +// SetTypeAs converts a types.Set into a tfsdk aware []T. +func SetTypeAs[T any](ctx context.Context, value types.Set, p path.Path, diags *diag.Diagnostics) []T { + return elementsAs[[]T](ctx, value, p, diags) +} + +func SetValueFrom[T any](ctx context.Context, value []T, elemType attr.Type, p path.Path, diags *diag.Diagnostics) types.Set { + list, d := types.SetValueFrom(ctx, elemType, value) + diags.Append(ConvertToAttrDiags(d, p)...) + return list +} + // =================== // ===== Objects ===== // =================== diff --git a/internal/utils/tfsdk_test.go b/internal/utils/tfsdk_test.go index 349cc8f8a..33aa3a5f1 100644 --- a/internal/utils/tfsdk_test.go +++ b/internal/utils/tfsdk_test.go @@ -100,6 +100,15 @@ var ( types.StringValue("v3"), }) + awareSetUnk = types.SetUnknown(awareType) + awareSetNil = types.SetNull(awareType) + awareSetEmpty = types.SetValueMust(awareType, []attr.Value{}) + awareSetFull = types.SetValueMust(awareType, []attr.Value{ + types.ObjectValueMust(awareType.AttrTypes, map[string]attr.Value{"id": types.StringValue("id1")}), + types.ObjectValueMust(awareType.AttrTypes, map[string]attr.Value{"id": types.StringValue("id2")}), + types.ObjectValueMust(awareType.AttrTypes, map[string]attr.Value{"id": types.StringValue("id3")}), + }) + normUnk = jsontypes.NewNormalizedUnknown() normNil = jsontypes.NewNormalizedNull() normEmpty = jsontypes.NewNormalizedValue(`{}`) @@ -699,3 +708,52 @@ func TestTransformMapToSlice(t *testing.T) { }) } } + +// Sets + +func TestSetTypeAs(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input types.Set + want []aware + }{ + {name: "converts unknown", input: awareSetUnk, want: awareSliceNil}, + {name: "converts nil", input: awareSetNil, want: awareSliceNil}, + {name: "converts empty", input: awareSetEmpty, want: awareSliceEmpty}, + {name: "converts struct", input: awareSetFull, want: awareSliceFull}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var diags diag.Diagnostics + got := utils.SetTypeAs[aware](context.Background(), tt.input, path.Empty(), &diags) + require.Equal(t, tt.want, got) + require.Empty(t, diags) + }) + } +} + +func TestSetValueFrom(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + input []aware + want types.Set + }{ + {name: "converts nil", input: awareSliceNil, want: awareSetNil}, + {name: "converts empty", input: awareSliceEmpty, want: awareSetEmpty}, + {name: "converts struct", input: awareSliceFull, want: awareSetFull}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var diags diag.Diagnostics + got := utils.SetValueFrom(context.Background(), tt.input, awareType, path.Empty(), &diags) + require.Equal(t, tt.want, got) + require.Empty(t, diags) + }) + } +} diff --git a/provider/plugin_framework.go b/provider/plugin_framework.go index 1da87c988..d7031c9ce 100644 --- a/provider/plugin_framework.go +++ b/provider/plugin_framework.go @@ -6,6 +6,7 @@ import ( "github.com/elastic/terraform-provider-elasticstack/internal/apm/agent_configuration" "github.com/elastic/terraform-provider-elasticstack/internal/clients" "github.com/elastic/terraform-provider-elasticstack/internal/clients/config" + "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/enrich" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/data_stream_lifecycle" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/index" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index/indices" @@ -86,6 +87,7 @@ func (p *Provider) DataSources(ctx context.Context) []func() datasource.DataSour spaces.NewDataSource, enrollment_tokens.NewDataSource, integration_ds.NewDataSource, + enrich.NewEnrichPolicyDataSource, } } @@ -106,5 +108,6 @@ func (p *Provider) Resources(ctx context.Context) []func() resource.Resource { output.NewResource, server_host.NewResource, system_user.NewSystemUserResource, + enrich.NewEnrichPolicyResource, } } diff --git a/provider/provider.go b/provider/provider.go index f5bc7fba7..2cd6441ca 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -3,7 +3,6 @@ package provider import ( "github.com/elastic/terraform-provider-elasticstack/internal/clients" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/cluster" - "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/enrich" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/ingest" "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/logstash" @@ -78,7 +77,6 @@ func New(version string) *schema.Provider { "elasticstack_elasticsearch_security_user": security.DataSourceUser(), "elasticstack_elasticsearch_snapshot_repository": cluster.DataSourceSnapshotRespository(), "elasticstack_elasticsearch_info": cluster.DataSourceClusterInfo(), - "elasticstack_elasticsearch_enrich_policy": enrich.DataSourceEnrichPolicy(), "elasticstack_kibana_action_connector": kibana.DataSourceConnector(), "elasticstack_kibana_security_role": kibana.DataSourceRole(), @@ -97,7 +95,6 @@ func New(version string) *schema.Provider { "elasticstack_elasticsearch_snapshot_lifecycle": cluster.ResourceSlm(), "elasticstack_elasticsearch_snapshot_repository": cluster.ResourceSnapshotRepository(), "elasticstack_elasticsearch_script": cluster.ResourceScript(), - "elasticstack_elasticsearch_enrich_policy": enrich.ResourceEnrichPolicy(), "elasticstack_elasticsearch_transform": transform.ResourceTransform(), "elasticstack_elasticsearch_watch": watcher.ResourceWatch(),