terraform-provider-elasticstack icon indicating copy to clipboard operation
terraform-provider-elasticstack copied to clipboard

Improve documentation around configuring deletion_protection = false

Open tempoivo opened this issue 1 year ago • 1 comments

Clarify the intended workflow required to delete/replace indices managed with deletion_protection=true.

Original issue

Describe the bug If you create an index initially with deletion_protection = true, you are unable to delete it after changing to deletion_protection = false. It keeps returning: Error: cannot destroy index without setting deletion_protection=false and running terraform apply

Same behaviour if you initially create the index with deletion_protection = false; after you turn it to true, you are still able to delete the index without any error, which should be prevented.

To Reproduce Steps to reproduce the behavior:

  1. TF configuration used:
terraform {
  required_version = ">= 1.0.0"
  required_providers {
    elasticstack = {
      source  = "elastic/elasticstack"
      version = "~>0.9"
    }
  }
}

provider "elasticstack" {
  elasticsearch {
    endpoints = ["http://elastic01.internal:9200"]
    username = var.admin_username
    password = var.admin_password
  }
}

resource "elasticstack_elasticsearch_index" "index-0001" {
  name = "index-0001"

  deletion_protection = true

  mappings = jsonencode({
    properties = {
      field1 = { type = "date" }
    }
  })

  number_of_shards = 1
  number_of_replicas = 0
}
  1. TF operations to execute to get the error:
  • terraform apply
  • Change any value, like mapping field type, to force replacement, and also set deletion_protection = false
  • terraform apply
  1. See the error in the output:
elasticstack_elasticsearch_index.index-0001: Destroying... [id=SAv5uMFYSA-prywrU6dE_g/index-0001]
│ Error: cannot destroy index without setting deletion_protection=false and running `terraform apply`

Expected behavior It should be able to destroy the index when deletion_protection changed to false.

Versions:

  • OS: MacOS
  • Terraform Version: 1.9.3
  • Provider Version: 0.11.4
  • Elasticsearch Version: 8.14.3

tempoivo avatar Aug 09 '24 16:08 tempoivo

This behaviour was intentional by the original author of this part of the code. It forces a 2 phased application for any action resulting in index deletion and IIRC mimics the behaviour on the GCP provider (and potentially others with similar attributes).

The intended workflow is:

  1. terraform apply with deletion_protection=true
  2. Reset deletion_protection by terraform apply with deletion_protection=false and not changes forcing replacement
  3. Delete the index or apply changes requiring replacement (like mapping field type).

tobio avatar Aug 11 '24 23:08 tobio

I've ran into this deadlock, most likely because I've used a resource lifecycle, here's my resource definition:

resource "elasticstack_elasticsearch_index" "index_bootstraped" {
  for_each = var.index

  name = "${each.value.name}-000001"

  alias {
    name           = each.value.name
    is_write_index = true
  }

  lifecycle {
    ignore_changes = [name]
  }
}

Then after noticing that I needed to get rid of that resource, I've only added deletion_protection = false to the resource, but the plan/apply presents me that replacement needs to happen because of the deletion protection:

 # module.es_index.elasticstack_elasticsearch_index.index_bootstraped["logs-ecs"] is tainted, so must be replaced
-/+ resource "elasticstack_elasticsearch_index" "index_bootstraped" {
      ~ deletion_protection    = true -> false
      ~ id                     = "HwB2f7HKRcGq8HBkGSFraQ/logs-ecs-000001" -> (known after apply)
      ~ mappings               = jsonencode(
          ~ {
              - dynamic           = "strict"
              - dynamic_templates = [
                  - {
                      - ecs_timestamp = {
                          - mapping = {
                              - ignore_malformed = false
                              - type             = "date"
                            }
                          - match   = "@timestamp"
                        }
                    },
                  - {
                      - ecs_message_match_only_text = {
                          - mapping              = {
                              - type = "match_only_text"
                            }
                          - path_match           = [
                              - "message",
                              - "*.message",
                            ]
                          - unmatch_mapping_type = "object"
                        }
                    },
                  - {
                      - ecs_non_indexed_keyword = {
                          - mapping    = {
                              - doc_values = false
                              - index      = false
                              - type       = "keyword"
                            }
                          - path_match = "event.original"
                        }
                    },
                  - {
                      - ecs_non_indexed_long = {
                          - mapping    = {
                              - doc_values = false
                              - index      = false
                              - type       = "long"
                            }
                          - path_match = "*.x509.public_key_exponent"
                        }
                    },
                  - {
                      - ecs_ip = {
                          - mapping            = {
                              - type = "ip"
                            }
                          - match_mapping_type = "string"
                          - path_match         = [
                              - "ip",
                              - "*.ip",
                              - "*_ip",
                            ]
                        }
                    },
                  - {
                      - ecs_wildcard = {
                          - mapping              = {
                              - type = "wildcard"
                            }
                          - path_match           = [
                              - "*.io.text",
                              - "*.message_id",
                              - "*registry.data.strings",
                              - "*url.path",
                            ]
                          - unmatch_mapping_type = "object"
                        }
                    },
                  - {
                      - ecs_path_match_wildcard_and_match_only_text = {
                          - mapping              = {
                              - fields = {
                                  - text = {
                                      - type = "match_only_text"
                                    }
                                }
                              - type   = "wildcard"
                            }
                          - path_match           = [
                              - "*.body.content",
                              - "*url.full",
                              - "*url.original",
                            ]
                          - unmatch_mapping_type = "object"
                        }
                    },
                  - {
                      - ecs_match_wildcard_and_match_only_text = {
                          - mapping              = {
                              - fields = {
                                  - text = {
                                      - type = "match_only_text"
                                    }
                                }
                              - type   = "wildcard"
                            }
                          - match                = [
                              - "*command_line",
                              - "*stack_trace",
                            ]
                          - unmatch_mapping_type = "object"
                        }
                    },
                  - {
                      - ecs_path_match_keyword_and_match_only_text = {
                          - mapping              = {
                              - fields = {
                                  - text = {
                                      - type = "match_only_text"
                                    }
                                }
                              - type   = "keyword"
                            }
                          - path_match           = [
                              - "*.title",
                              - "*.executable",
                              - "*.name",
                              - "*.working_directory",
                              - "*.full_name",
                              - "*file.path",
                              - "*file.target_path",
                              - "*os.full",
                              - "email.subject",
                              - "vulnerability.description",
                              - "user_agent.original",
                            ]
                          - unmatch_mapping_type = "object"
                        }
                    },
                  - {
                      - ecs_date = {
                          - mapping              = {
                              - type = "date"
                            }
                          - path_match           = [
                              - "*.timestamp",
                              - "*_timestamp",
                              - "*.not_after",
                              - "*.not_before",
                              - "*.accessed",
                              - "created",
                              - "*.created",
                              - "*.installed",
                              - "*.creation_date",
                              - "*.ctime",
                              - "*.mtime",
                              - "ingested",
                              - "*.ingested",
                              - "*.start",
                              - "*.end",
                              - "*.indicator.first_seen",
                              - "*.indicator.last_seen",
                              - "*.indicator.modified_at",
                              - "*threat.enrichments.matched.occurred",
                            ]
                          - unmatch_mapping_type = "object"
                        }
                    },
                  - {
                      - ecs_path_match_float = {
                          - mapping              = {
                              - type = "float"
                            }
                          - path_match           = [
                              - "*.score.*",
                              - "*_score*",
                            ]
                          - path_unmatch         = "*.version"
                          - unmatch_mapping_type = "object"
                        }
                    },
                  - {
                      - ecs_usage_double_scaled_float = {
                          - mapping            = {
                              - scaling_factor = 1000
                              - type           = "scaled_float"
                            }
                          - match_mapping_type = [
                              - "double",
                              - "long",
                              - "string",
                            ]
                          - path_match         = "*.usage"
                        }
                    },
                  - {
                      - ecs_geo_point = {
                          - mapping    = {
                              - type = "geo_point"
                            }
                          - path_match = "*.geo.location"
                        }
                    },
                  - {
                      - ecs_flattened = {
                          - mapping            = {
                              - type = "flattened"
                            }
                          - match_mapping_type = "object"
                          - path_match         = [
                              - "*structured_data",
                              - "*exports",
                              - "*imports",
                            ]
                        }
                    },
                  - {
                      - all_strings_to_keywords = {
                          - mapping            = {
                              - ignore_above = 1024
                              - type         = "keyword"
                            }
                          - match_mapping_type = "string"
                        }
                    },
                ]
            }
        )
        name                   = "logs-ecs-000001"
      ~ settings_raw           = jsonencode(
            {
              - "index.creation_date"                               = "1732288963831"
              - "index.lifecycle.name"                              = "logs"
              - "index.lifecycle.rollover_alias"                    = "logs-ecs"
              - "index.number_of_replicas"                          = "1"
              - "index.number_of_shards"                            = "2"
              - "index.provided_name"                               = "logs-ecs-000001"
              - "index.routing.allocation.include._tier_preference" = "data_content"
              - "index.uuid"                                        = "sUJh4ZTCQ6qo859pSD9GRQ"
              - "index.version.created"                             = "8512000"
            }
        ) -> (known after apply)
        # (4 unchanged attributes hidden)

        # (1 unchanged block hidden)
    }

Plan: 1 to add, 0 to change, 1 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

module.es_index.elasticstack_elasticsearch_index.index_bootstraped["logs-ecs"]: Destroying... [id=HwB2f7HKRcGq8HBkGSFraQ/logs-ecs-000001]
╷
│ Error: cannot destroy index without setting deletion_protection=false and running `terraform apply`
│
│ cannot destroy index without setting deletion_protection=false and running `terraform apply`

Now I'll probably have to remove it from the state file

And for the record, I've added the resource lifecycle in an attempt to deal with the fact that we bootstrap the indexes only once, and eventually the 000001 will go away with time

lucasmat7 avatar Nov 22 '24 15:11 lucasmat7

but the plan/apply presents me that replacement needs to happen because of the deletion protection: # module.es_index.elasticstack_elasticsearch_index.index_bootstraped["logs-ecs"] is tainted, so must be replaced

The index is being recreated because it's tainted in state, not because deletion protection has been changed.

tobio avatar Nov 23 '24 22:11 tobio