DEV Community

Cover image for Terraform: Is it ok to "read or create" a resource?
GF
GF

Posted on

Terraform: Is it ok to "read or create" a resource?

I'm very new in Terraform and I suppose that I can bring unsuitable conceptions from programming languages to terraform configs, but I want to tell a little bit about my problem which I've solved in a controversial way, invite you to discuss that solution and, possible, explain that I was wrong.

Recently I needed to deploy a few services in AWS and I used terraform for creating infrastructure. I decided to create a separate terraform config for each application/service I want to deploy in AWS and while I was writing configs it became clear that different services could require the same AWS resource, like a role to access DB, for example. For this case, in order to have the ability to reuse parts of Terraform config as a module, I moved that resource creation into independent configs and started to use it as a module in my services configs.
But very soon I met a problem - when I have the same AWS resources creation statements in different configs, terraform apply will fail with the error Resource already exists. Ok, we need something like "read or create" for resources in terraform config. In that case, we can reuse modules without errors and have independent service configs, each of them would be able to deploy independently. While I was searching for a solution for that case, I found that githib issue, where the same situation discussed, but Terraform closed this issue with an explanation:

The sort of dynamic decision-making that is being requested here runs counter to Terraform's design goals since it makes the configuration a description of what might possibly be rather than what is. We are not going to change the behavior of data sources when the response is empty or 404, and will close this issue.

Ok, we can't make read or create a resource with terraform, but I have found a way which could be controversial and I don't have a definite answer for me is it worth to do terrafrom that way.

The solution is terraform shell provider which allows to do something in shell and return json to terraform. Thus we can do something like this:

data "shell_script" "check_existing" {
  lifecycle_commands {
    read = <<-EOF
      aws iam get-role --role-name ${var.role_name} > /dev/null 2>&1
      [ $? -eq 0 ] && echo "{\"isExist\": true}" || echo "{\"isExist\": false}"
    EOF
  }
}
Enter fullscreen mode Exit fullscreen mode

The script above uses AWS CLI to check if a particular role exists, and return a boolean. This value will be able in other terraform config statements as: data.shell_script.check_existing.output.isExist.
And when I read or create some resource I can use a trick with resource count, for example (with configs separated into modules):

module "is_role_exist" {
  source = "../isExist"
  role_name = var.role_name
}

module "create_role" {
  source = "../create"
  role_name = var.role_name
  count = module.is_role_exist.is_role_exist ? 0 : 1
}

module "read_role" {
  source = "../read"
  role_name = var.role_name
  count = module.is_role_exist.is_role_exist ? 1 : 0
}

Enter fullscreen mode Exit fullscreen mode

Now I have a few modules different applications depends on, with the following structure:

module
  - read
  - create
  - isExist
  - readOrCreate
Enter fullscreen mode Exit fullscreen mode

It is the working solution for conditional creating resources but it contradicts terraform intention, as far as I understood, probably could be useful, but I will be glad to know other opinions 🙂 (There I ask you to share your opinion)

Top comments (3)

Collapse
 
junkern profile image
Martin • Edited

I can totally see where you are coming from. And your solution is (from a solution-oriented perspective) cool, but it is not how you should approach terraform resources.

Short version: Either create a different role for each service or you create the "dynamo DB-access" role and simply use data sources to import the role-name into your services.

(I submitted too early, longer version is in the making)
Long version: You are experiencing that error, because you are trying to create the same role in every instance of the module. So you either have to create the role at a centralized point in your terraform code and then simply pass the role-name into your module, so that the module can attach the role name to an instance (or whatever you want to do with that role).

Another alternative would be to create roles for each service ("service-a-role", "service-b-role") and then every role would have the needed policies for the service (access DynamoDB, access S3). That way the role creation can also happen within the module and you won't get resource clashes, as every role would have a unique name. To my knowledge, this is also the recommendation of AWS, because you can better manage what every service is able to access.

It really depends on your architecture, whether service-specific roles is a possible thing.

In case you haven't heard about terragrunt, I would definitely check it out! It makes working with terraform a lot easier: terragrunt.gruntwork.io/

Collapse
 
gf_developer profile image
GF

Thanks for the detailed response!
Yes, a unique name is really good, and I thought about creating common resources apart from a service config and then use some id, like arn or name in service config, but in this case, it could be tough to detect from which modules application depends on. Also, it is difficult for me, at least right now, to accept the fact that the application config won't work until a few other configs are applied, moreover, if the order of appllying is important, it probably will worth to store that information somewhere
Thanks for terragrunt too, I'll try everything that reduces routine with terraform :)

Collapse
 
miketysonofthecloud profile image
Mike Tyson of the Cloud

Your approach to "read or create" resources in Terraform using a shell provider is innovative, but as you mentioned, it goes against Terraform's intended design. Terraform configurations aim to be declarative, specifying what the infrastructure should look like rather than the steps to achieve it. This makes your solution somewhat controversial since it introduces imperative logic into a declarative tool.

Using the shell provider to check resource existence and conditionally create or read resources is clever, but it adds complexity and potential maintenance challenges. A more idiomatic approach would be to use Terraform's data sources to fetch existing resources and avoid duplication by planning your configurations to be aware of shared resources from the beginning.

Consider refactoring your configurations to define shared resources in a common module or workspace and referencing them where needed. This way, you can avoid the need for conditional logic and maintain clearer, more manageable Terraform configurations. Anyway, it's a best practice integrated in Brainboard.co!