Skip to content

elasticstack_elasticsearch_index_mappings: state stores template-injected dynamic_templates, not just user-declared subset #4588

Description

@tobio

Summary

elasticstack_elasticsearch_index_mappings's schema documents: "Only the keys and fields declared here are tracked; dynamic extras added by Elasticsearch are ignored." This holds for properties (via intersectProperties), but not for dynamic_templates: after a Read, state ends up containing the full API-returned dynamic_templates array — including entries contributed by an index template — instead of just the templates the user declared in config.

Root cause

internal/elasticsearch/index/indexmappings/intersect.go's intersectMappings special-cases properties (name-aware intersection), but falls back to index.FieldSemanticallyEqual(stateVal, apiVal) for every other top-level key:

if index.FieldSemanticallyEqual(stateVal, apiVal) {
    result[key] = stateVal
    continue
}
result[key] = apiVal

FieldSemanticallyEqual/fieldSemanticallyEqual type-asserts both sides to map[string]any. dynamic_templates is a []any, so the assertion always fails, FieldSemanticallyEqual always returns false, and result[key] = apiVal unconditionally stores the entire API array (user templates + template-injected extras) into state.

Repro

Given:

  • stateMap: {"dynamic_templates": [{"text_ja_example": {...}}]} (user-declared)
  • apiMap: {"dynamic_templates": [{"template_default": {...}}, {"text_ja_example": {...}}]} (API, with an index-template-injected extra)

intersectMappings(apiMap, stateMap) returns dynamic_templates containing both template_default and text_ja_example, not just the user-owned text_ja_example.

Confirmed with a standalone probe test against intersectMappings:

result: {"dynamic_templates":[{"template_default":{"mapping":{"type":"keyword"},"match_mapping_type":"string"}},{"text_ja_example":{"mapping":{"type":"text"},"path_match":"hoge.example_field.freetext"}}]}

Impact

  • terraform state show / terraform show -json for this resource can contain dynamic_templates entries the user never declared and doesn't own, contradicting the documented "user-declared subset" contract.
  • The existing acceptance test coverage doesn't catch this: checkStateMappingsDynamicTemplates(minCount) in acc_test.go asserts len(templates) >= minCount, which can't distinguish "state has just the user's template" from "state has the user's template plus injected extras."

Suggested fix

Reuse the name-keyed comparison introduced in #4581 (dynamicTemplatesByName in internal/elasticsearch/index/mappings_value.go) inside intersectMappings/intersectProperties so dynamic_templates is intersected by template name the same way properties is intersected by field name — keeping only the user-declared template names in state. Also tighten checkStateMappingsDynamicTemplates to assert an exact set of template names rather than a minimum count, so a regression here is actually caught.

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    ElasticsearchElasticsearch related APIsbugSomething isn't workingneeds-specWell-specified issue ready for implementation planningphase-specificationIssue is in the specification phase of the factory pipelinetriagedIssue has been classified and routed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions