diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bcd4541b..88d7acee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## [Unreleased] +- Add `ignore_missing_component_templates` to `elasticstack_elasticsearch_index_template` ([#1206](https://github.com/elastic/terraform-provider-elasticstack/pull/1206)) + ## [0.11.17] - 2025-07-21 - Add `elasticstack_apm_agent_configuration` resource ([#1196](https://github.com/elastic/terraform-provider-elasticstack/pull/1196)) diff --git a/docs/data-sources/elasticsearch_index_template.md b/docs/data-sources/elasticsearch_index_template.md index c1363c3cd..9beebbb90 100644 --- a/docs/data-sources/elasticsearch_index_template.md +++ b/docs/data-sources/elasticsearch_index_template.md @@ -42,6 +42,7 @@ output "template" { - `composed_of` (List of String) An ordered list of component template names. - `data_stream` (List of Object) If this object is included, the template is used to create data streams and their backing indices. Supports an empty object. (see [below for nested schema](#nestedatt--data_stream)) - `id` (String) Internal identifier of the resource +- `ignore_missing_component_templates` (List of String) A list of component template names that are ignored if missing. - `index_patterns` (Set of String) Array of wildcard (*) expressions used to match the names of data streams and indices during creation. - `metadata` (String) Optional user metadata about the index template. - `priority` (Number) Priority to determine index template precedence when a new data stream or index is created. diff --git a/docs/resources/elasticsearch_index_template.md b/docs/resources/elasticsearch_index_template.md index 17cc732de..08715c32e 100644 --- a/docs/resources/elasticsearch_index_template.md +++ b/docs/resources/elasticsearch_index_template.md @@ -58,6 +58,7 @@ resource "elasticstack_elasticsearch_index_template" "my_data_stream" { - `composed_of` (List of String) An ordered list of component template names. - `data_stream` (Block List, Max: 1) If this object is included, the template is used to create data streams and their backing indices. Supports an empty object. (see [below for nested schema](#nestedblock--data_stream)) - `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)) +- `ignore_missing_component_templates` (List of String) A list of component template names that are ignored if missing. - `metadata` (String) Optional user metadata about the index template. - `priority` (Number) Priority to determine index template precedence when a new data stream or index is created. - `template` (Block List, Max: 1) Template to be applied. It may optionally include an aliases, mappings, lifecycle, or settings configuration. (see [below for nested schema](#nestedblock--template)) diff --git a/internal/elasticsearch/index/template.go b/internal/elasticsearch/index/template.go index d5a70cfc9..30fecfa24 100644 --- a/internal/elasticsearch/index/template.go +++ b/internal/elasticsearch/index/template.go @@ -10,12 +10,17 @@ import ( "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/go-version" "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" ) +var ( + MinSupportedIgnoreMissingComponentTemplateVersion = version.Must(version.NewVersion("8.7.0")) +) + func ResourceTemplate() *schema.Resource { templateSchema := map[string]*schema.Schema{ "id": { @@ -38,6 +43,15 @@ func ResourceTemplate() *schema.Resource { Type: schema.TypeString, }, }, + "ignore_missing_component_templates": { + Description: "A list of component template names that are ignored if missing.", + Type: schema.TypeList, + Optional: true, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, "data_stream": { Description: "If this object is included, the template is used to create data streams and their backing indices. Supports an empty object.", Type: schema.TypeList, @@ -210,6 +224,12 @@ func resourceIndexTemplatePut(ctx context.Context, d *schema.ResourceData, meta if diags.HasError() { return diags } + + serverVersion, diags := client.ServerVersion(ctx) + if diags.HasError() { + return diags + } + var indexTemplate models.IndexTemplate indexTemplate.Name = templateId @@ -221,6 +241,18 @@ func resourceIndexTemplatePut(ctx context.Context, d *schema.ResourceData, meta } indexTemplate.ComposedOf = compsOf + if v, ok := d.GetOk("ignore_missing_component_templates"); ok { + compsOfIgnore := make([]string, 0) + for _, c := range v.([]interface{}) { + compsOfIgnore = append(compsOfIgnore, c.(string)) + } + + if len(compsOfIgnore) > 0 && serverVersion.LessThan(MinSupportedIgnoreMissingComponentTemplateVersion) { + return diag.FromErr(fmt.Errorf("'ignore_missing_component_templates' is supported only for Elasticsearch v%s and above", MinSupportedIgnoreMissingComponentTemplateVersion.String())) + } + indexTemplate.IgnoreMissingComponentTemplates = compsOfIgnore + } + if v, ok := d.GetOk("data_stream"); ok { // 8.x workaround hasAllowCustomRouting := false @@ -371,6 +403,9 @@ func resourceIndexTemplateRead(ctx context.Context, d *schema.ResourceData, meta if err := d.Set("composed_of", tpl.IndexTemplate.ComposedOf); err != nil { return diag.FromErr(err) } + if err := d.Set("ignore_missing_component_templates", tpl.IndexTemplate.IgnoreMissingComponentTemplates); err != nil { + return diag.FromErr(err) + } if stream := tpl.IndexTemplate.DataStream; stream != nil { ds := make([]interface{}, 1) dSettings := make(map[string]interface{}) diff --git a/internal/elasticsearch/index/template_data_source.go b/internal/elasticsearch/index/template_data_source.go index 8d1630268..14bc02c37 100644 --- a/internal/elasticsearch/index/template_data_source.go +++ b/internal/elasticsearch/index/template_data_source.go @@ -29,6 +29,14 @@ func DataSourceTemplate() *schema.Resource { Type: schema.TypeString, }, }, + "ignore_missing_component_templates": { + Description: "A list of component template names that are ignored if missing.", + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, "data_stream": { Description: "If this object is included, the template is used to create data streams and their backing indices. Supports an empty object.", Type: schema.TypeList, diff --git a/internal/elasticsearch/index/template_data_source_test.go b/internal/elasticsearch/index/template_data_source_test.go index c37ee5797..6cc4aa0cf 100644 --- a/internal/elasticsearch/index/template_data_source_test.go +++ b/internal/elasticsearch/index/template_data_source_test.go @@ -5,6 +5,8 @@ import ( "testing" "github.com/elastic/terraform-provider-elasticstack/internal/acctest" + "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index" + "github.com/elastic/terraform-provider-elasticstack/internal/versionutils" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -12,6 +14,7 @@ import ( func TestAccIndexTemplateDataSource(t *testing.T) { // generate a random role name templateName := "test-template-" + sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) + templateNameComponent := "test-template-" + sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -25,6 +28,16 @@ func TestAccIndexTemplateDataSource(t *testing.T) { resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_index_template.test", "priority", "100"), ), }, + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(index.MinSupportedIgnoreMissingComponentTemplateVersion), + Config: testAccIndexTemplateDataSourceWithIgnoreComponentConfig(templateNameComponent), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.elasticstack_elasticsearch_index_template.test_component", "name", templateNameComponent), + resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_index_template.test_component", "index_patterns.*", fmt.Sprintf("tf-acc-component-%s-*", templateNameComponent)), + resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_index_template.test_component", "composed_of.*", fmt.Sprintf("%s-logscomponent@custom", templateNameComponent)), + resource.TestCheckTypeSetElemAttr("data.elasticstack_elasticsearch_index_template.test_component", "ignore_missing_component_templates.*", fmt.Sprintf("%s-logscomponent@custom", templateNameComponent)), + ), + }, }, }) } @@ -47,3 +60,22 @@ data "elasticstack_elasticsearch_index_template" "test" { } `, templateName, templateName) } + +func testAccIndexTemplateDataSourceWithIgnoreComponentConfig(templateName string) string { + return fmt.Sprintf(` +provider "elasticstack" { + elasticsearch {} +} + +resource "elasticstack_elasticsearch_index_template" "test_component" { + name = "%s" + index_patterns = ["tf-acc-component-%s-*"] + composed_of = ["%s-logscomponent@custom"] + ignore_missing_component_templates = ["%s-logscomponent@custom"] +} + +data "elasticstack_elasticsearch_index_template" "test_component" { + name = elasticstack_elasticsearch_index_template.test_component.name +} + `, templateName, templateName, templateName, templateName) +} diff --git a/internal/elasticsearch/index/template_test.go b/internal/elasticsearch/index/template_test.go index e0d3ce7a4..7e0ffd8f2 100644 --- a/internal/elasticsearch/index/template_test.go +++ b/internal/elasticsearch/index/template_test.go @@ -6,6 +6,8 @@ import ( "github.com/elastic/terraform-provider-elasticstack/internal/acctest" "github.com/elastic/terraform-provider-elasticstack/internal/clients" + "github.com/elastic/terraform-provider-elasticstack/internal/elasticsearch/index" + "github.com/elastic/terraform-provider-elasticstack/internal/versionutils" 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" @@ -14,6 +16,7 @@ import ( func TestAccResourceIndexTemplate(t *testing.T) { // generate random template name templateName := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) + templateNameComponent := sdkacctest.RandStringFromCharSet(10, sdkacctest.CharSetAlphaNum) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -41,6 +44,26 @@ func TestAccResourceIndexTemplate(t *testing.T) { resource.TestCheckResourceAttr("elasticstack_elasticsearch_index_template.test2", "data_stream.0.hidden", "false"), ), }, + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(index.MinSupportedIgnoreMissingComponentTemplateVersion), + Config: testAccResourceIndexTemplateCreateWithIgnoreComponent(templateNameComponent), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_elasticsearch_index_template.test_component", "name", templateNameComponent), + resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_index_template.test_component", "index_patterns.*", fmt.Sprintf("%s-logscomponent-*", templateNameComponent)), + resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_index_template.test_component", "composed_of.*", fmt.Sprintf("%s-logscomponent@custom", templateNameComponent)), + resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_index_template.test_component", "ignore_missing_component_templates.*", fmt.Sprintf("%s-logscomponent@custom", templateNameComponent)), + ), + }, + { + SkipFunc: versionutils.CheckIfVersionIsUnsupported(index.MinSupportedIgnoreMissingComponentTemplateVersion), + Config: testAccResourceIndexTemplateUpdateWithIgnoreComponent(templateNameComponent), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("elasticstack_elasticsearch_index_template.test_component", "name", templateNameComponent), + resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_index_template.test_component", "index_patterns.*", fmt.Sprintf("%s-logscomponent-*", templateNameComponent)), + resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_index_template.test_component", "composed_of.*", fmt.Sprintf("%s-logscomponent-updated@custom", templateNameComponent)), + resource.TestCheckTypeSetElemAttr("elasticstack_elasticsearch_index_template.test_component", "ignore_missing_component_templates.*", fmt.Sprintf("%s-logscomponent-updated@custom", templateNameComponent)), + ), + }, }, }) } @@ -117,6 +140,41 @@ resource "elasticstack_elasticsearch_index_template" "test2" { `, name, name, name) } +func testAccResourceIndexTemplateCreateWithIgnoreComponent(name string) string { + return fmt.Sprintf(` +provider "elasticstack" { + elasticsearch {} +} + +resource "elasticstack_elasticsearch_index_template" "test_component" { + name = "%s" + index_patterns = ["%s-logscomponent-*"] + + composed_of = ["%s-logscomponent@custom"] + ignore_missing_component_templates = ["%s-logscomponent@custom"] +} + `, name, name, name, name) +} + +func testAccResourceIndexTemplateUpdateWithIgnoreComponent(name string) string { + return fmt.Sprintf(` +provider "elasticstack" { + elasticsearch {} +} + +resource "elasticstack_elasticsearch_index_template" "test_component" { + name = "%s" + index_patterns = ["%s-logscomponent-*"] + + composed_of = ["%s-logscomponent-updated@custom"] + ignore_missing_component_templates = ["%s-logscomponent-updated@custom"] + + template { + } +} + `, name, name, name, name) +} + func checkResourceIndexTemplateDestroy(s *terraform.State) error { client, err := clients.NewAcceptanceTestingClient() if err != nil { diff --git a/internal/models/models.go b/internal/models/models.go index 3cc9c1e18..5608df749 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -163,16 +163,17 @@ type Application struct { } type IndexTemplate struct { - Name string `json:"-"` - Create bool `json:"-"` - Timeout string `json:"-"` - ComposedOf []string `json:"composed_of"` - DataStream *DataStreamSettings `json:"data_stream,omitempty"` - IndexPatterns []string `json:"index_patterns"` - Meta map[string]interface{} `json:"_meta,omitempty"` - Priority *int `json:"priority,omitempty"` - Template *Template `json:"template,omitempty"` - Version *int `json:"version,omitempty"` + Name string `json:"-"` + Create bool `json:"-"` + Timeout string `json:"-"` + ComposedOf []string `json:"composed_of"` + IgnoreMissingComponentTemplates []string `json:"ignore_missing_component_templates,omitempty"` + DataStream *DataStreamSettings `json:"data_stream,omitempty"` + IndexPatterns []string `json:"index_patterns"` + Meta map[string]interface{} `json:"_meta,omitempty"` + Priority *int `json:"priority,omitempty"` + Template *Template `json:"template,omitempty"` + Version *int `json:"version,omitempty"` } type DataStreamSettings struct {