amazon-web-servicesterraformterraform-provider-awsamazon-ecr

Terraform attempts to recreate ECR repositories after code refactoring


I have the following piece of code in TFE v.1.13. It works as intended but feels a bit repetitive:

resource "aws_ecr_repository" "repository-a" {
  name                 = "repo-a"
  #skipped 
}
    
resource "aws_ecr_repository" "repository-b" {
  name                 = "repo-b"
  #skipped
}
    
resource "aws_ecr_repository" "repository-c" {
  name                 = "repo-c"
  #skipped
}

So, I have refactored it as follows:

locals {
  repo_table = [
    {
      repo_id = "repo-id-a"
      repo_name = "repo-a"
    },
    {
      repo_id = "repo-id-b"
      repo_name = "repo-b"
    },
    {
      repo_id = "repo-id-c" 
      repo_name = "repo-c"
    }
  ]
}

resource "aws_ecr_repository" "repositories" {
  for_each = {for repo in local.repo_table : repo.repo_id => repo }
    
  name                 = each.value.repo_name
  #skipped
}

I got the following pair of errors on apply for each of the three:

Error: ECR Repository (repo-a) not empty, consider using force_delete: RepositoryNotEmptyException: The repository with name 'repo-a' in registry with id '123456789' cannot be deleted because it still contains images

Error: creating ECR Repository (repo-a): RepositoryAlreadyExistsException: The repository with name 'repo-a' already exists in the registry with id '123456789'

So, Terraform tries to delete and recreate each of them, but it can't do that because there are images in there. Is there any way to refactor the code but avoid deleting existing images/repos ?


Solution

  • Terraform's main mechanism for dealing with change is to compare the prior state (as produced by the previous plan/apply round, if any) with the desired state (as described by the configuration) and then propose a set of actions to try to reach a point where the two are "converged", meaning that the actual state matches the desired state.

    For some differences there are several possible ways to interpret and them. That is true in your case: your prior state includes resource instances like aws_ecr_repository.repository-a and aws_ecr_repository.repository-b, while your desired state includes instances like aws_ecr_repository.repositories["repo-id-a"] and aws_ecr_repository.repositories["repo-id-b"], and Terraform can't automatically infer any relationship between those and so by default it guesses that your intention is to destroy the old ones and create the new ones.

    However, you can influence Terraform's proposals by giving it more information. In this case, you can use moved blocks to tell Terraform that you intended this difference to be understood as a renaming of the addresses of the existing objects in Terraform, like this:

    moved {
      from = aws_ecr_repository.repository-a
      to   = aws_ecr_repository.repositories["repo-id-a"]
    }
    
    moved {
      from = aws_ecr_repository.repository-b
      to   = aws_ecr_repository.repositories["repo-id-b"]
    }
    
    moved {
      from = aws_ecr_repository.repository-c
      to   = aws_ecr_repository.repositories["repo-id-c"]
    }
    

    This extra information then allows Terraform to make a different proposal: to leave the existing objects unchanged and just bind them to different addresses in the Terraform state.

    You can find out more about this feature in Refactoring.