From 28ed75674513ff0bf812fb1e009a3bc3ff2f5a60 Mon Sep 17 00:00:00 2001 From: Anton Babenko Date: Tue, 14 Dec 2021 21:48:19 +0000 Subject: [PATCH] GitBook: [#1] Updated first part --- README.md | 29 +++--- SUMMARY.md | 6 +- code-structure.md | 57 ++++++------ code-styling.md | 12 ++- examples/terraform/README.md | 4 + key-concepts.md | 35 +++---- naming.md | 175 ++++++++++++++--------------------- 7 files changed, 142 insertions(+), 176 deletions(-) create mode 100644 examples/terraform/README.md diff --git a/README.md b/README.md index d68402d..64437a3 100644 --- a/README.md +++ b/README.md @@ -7,31 +7,33 @@ description: >- # Welcome -[Terraform](https://www.terraform.io/) is a fairly new project \(as most of DevOps tools actually\) which was started in 2014. +[Terraform](https://www.terraform.io) is a fairly new project (as most DevOps tools actually) that was started in 2014. -Terraform is powerful \(if not the most powerful out there now\) and one of the most used tool which allows to manage infrastructure as code. It allows developers to do a lot of things and does not restrict them from doing things in ways which will be hard to support or integrate with. +Terraform is powerful (if not the most powerful out there now) and one of the most used tools which allows to manage infrastructure as code. It allows developers to do a lot of things and does not restrict them from doing things in ways that will be hard to support or integrate with. -Some information described in this book may not seem like the best practices. I know this, and to help reader to separate what are established best practices and what is just another opinionated way of doing things, I sometimes use hints to provide some context and icons to specify the level of maturity on each subsection related to best practices. +Some information described in this book may not seem like the best practices. I know this, and to help readers to separate what are established best practices and what is just another opinionated way of doing things, I sometimes use hints to provide some context and icons to specify the level of maturity on each subsection related to best practices. -The book is available for free here - [https://www.terraform-best-practices.com/](https://www.terraform-best-practices.com/) +The book has started in sunny Madrid in 2018 and is available for free here - [https://www.terraform-best-practices.com/](https://www.terraform-best-practices.com) . -## Status +A few years later it has been updated with more actual best practices available with Terraform 1.0. Eventually, this book should contain most of the indisputable best practices and recommendations for Terraform users. -This book eventually should contain most of best-practices and recommendations for Terraform users, but nobody knows when exactly it will happen. I aim to make it mostly ready by the end of 2018. +## Translations + +{% content-ref url="https://app.gitbook.com/o/-LMqIrDlzEiI-N4uHrWg/s/D440IbeO4vxZpA715aMx/" %} +[Español](https://app.gitbook.com/o/-LMqIrDlzEiI-N4uHrWg/s/D440IbeO4vxZpA715aMx/) +{% endcontent-ref %} ## Contributions -I want to get feedback and update this document as community mature and new ideas are implemented and verified. I write about topics which are the most popular. +I always want to get feedback and update this book as the community mature and new ideas are implemented and verified over time. -If you are interested in certain topics please [open an issue](https://github.com/antonbabenko/terraform-best-practices/issues), or thumb-up on an issue you want to be covered most. If you feel that **you have content** which you want to include - write a draft and submit a pull-request \(don't worry about writing good text at this point!\) +If you are interested in certain topics please [open an issue](https://github.com/antonbabenko/terraform-best-practices/issues), or thumb up an issue you want to be covered most. If you feel that **you have content** and you want to contribute, write a draft and submit a pull request (don't worry about writing good text at this point!) ## Authors -This book is maintained by [Anton Babenko](https://github.com/antonbabenko) with the help of different contributors. - -## Need help? +This book is maintained by [Anton Babenko](https://github.com/antonbabenko) with the help of different contributors and translators. -If you're looking for help or commercial support for Terraform and AWS, send an email to [anton@antonbabenko.com](mailto:anton@antonbabenko.com). +Contact me ([email](https://app.gitbook.com/u/564c621e10276010007cd30b) or [twitter](https://twitter.com/antonbabenko)) if you want to translate this book. ## License @@ -39,5 +41,4 @@ This work is licensed under Apache 2 License. See LICENSE for full details. The authors and contributors to this content cannot guarantee the validity of the information found here. Please make sure that you understand that the information provided here is being provided freely, and that no kind of agreement or contract is created between you and any persons associated with this content or project. The authors and contributors do not assume and hereby disclaim any liability to any party for any loss, damage, or disruption caused by errors or omissions in the information contained in, associated with, or linked from this content, whether such errors or omissions result from negligence, accident, or any other cause. -Copyright © 2018 Anton Babenko. - +Copyright © 2018-2022 Anton Babenko. diff --git a/SUMMARY.md b/SUMMARY.md index 02e9508..4fac7be 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -2,13 +2,10 @@ * [Welcome](README.md) * [Key concepts](key-concepts.md) - * [Resource modules](key-concepts/resource-modules.md) - * [Infrastructure modules](key-concepts/infrastructure-modules.md) - * [Compositions](key-concepts/compositions.md) * [Code structure](code-structure.md) * [Code structure examples](examples/README.md) * [Terragrunt](examples/terragrunt.md) - * [Terraform](examples/terraform.md) + * [Terraform](examples/terraform/README.md) * [Small-size infrastructure with Terraform](examples/terraform/small-size-infrastructure.md) * [Medium-size infrastructure with Terraform](examples/terraform/medium-size-infrastructure.md) * [Large-size infrastructure with Terraform](examples/terraform/large-size-infrastructure-with-terraform.md) @@ -20,4 +17,3 @@ * [References](references.md) * [Writing Terraform configurations](writing-terraform-configurations.md) * [Workshop](workshop.md) - diff --git a/code-structure.md b/code-structure.md index 31bae69..601005b 100644 --- a/code-structure.md +++ b/code-structure.md @@ -1,77 +1,78 @@ # Code structure -Questions related to Terraform code structure are by far the most frequent in the community. Everybody probably thought about best code structure at some point also. +Questions related to Terraform code structure are by far the most frequent in the community. Everyone thought about the best code structure for the project at some point also. ## How should I structure my Terraform configurations? -This is one of the questions where lots of solutions exist and it is very hard to get general advice, so let's start with understanding what are we dealing with. +This is one of the questions where lots of solutions exist and it is very hard to give universal advice, so let's start with understanding what are we dealing with. * What is the complexity of your project? * Number of related resources - * Number of Terraform providers + * Number of Terraform providers (see note below about "logical providers") * How often does your infrastructure change? * **From** once a month/week/day - * **To** continuously \(every time when there is a new commit\) -* Code change initiators? _Do you let CI server to update the repository when new artifact is built?_ - * Only developers can push to infrastructure repository - * Everyone can propose change to anything by opening a PR \(including automated tasks running on CI server\) + * **To** continuously (every time when there is a new commit) +* Code change initiators? _Do you let the CI server update the repository when a new artifact is built?_ + * Only developers can push to the infrastructure repository + * Everyone can propose a change to anything by opening a PR (including automated tasks running on the CI server) * Which deployment platform or deployment service do you use? - * AWS CodeDeploy, Kubernetes or OpenShift require slightly different approach + * AWS CodeDeploy, Kubernetes, or OpenShift require a slightly different approach * How environments are grouped? * By environment, region, project {% hint style="info" %} -_Logical providers_ work entirely within Terraform's logic, and doesn't interact with any other services, so we can think about their complexity as O\(1\). Most common logical providers include [random](https://www.terraform.io/docs/providers/random/index.html), [template](https://www.terraform.io/docs/providers/template/index.html), [terraform](https://www.terraform.io/docs/providers/terraform/index.html), [null](https://www.terraform.io/docs/providers/null/index.html). +_Logical providers_ work entirely within Terraform's logic and very often don't interact with any other services, so we can think about their complexity as O(1). The most common logical providers include [random](https://registry.terraform.io/providers/hashicorp/random/latest/docs), [local](https://registry.terraform.io/providers/hashicorp/local/latest/docs), [terraform](https://www.terraform.io/docs/providers/terraform/index.html), [null](https://registry.terraform.io/providers/hashicorp/null/latest/docs), [time](https://registry.terraform.io/providers/hashicorp/time/latest). {% endhint %} -## Getting started with structuring of Terraform configurations +## Getting started with the structuring of Terraform configurations Putting all code in `main.tf` is a good idea when you are getting started or writing an example code. In all other cases you will be better having several files split logically like this: -* `main.tf` - call modules, locals and data-sources to create all resources +* `main.tf` - call modules, locals, and data sources to create all resources * `variables.tf` - contains declarations of variables used in `main.tf` * `outputs.tf` - contains outputs from the resources created in `main.tf` +* `versions.tf` - contains version requirements for Terraform and providers `terraform.tfvars` should not be used anywhere except [composition](key-concepts.md#composition). ## How to think about Terraform configurations structure? {% hint style="info" %} -Please make sure that you understand key concepts - [resource module](key-concepts.md#resource-module), [infrastructure module](key-concepts.md#infrastructure-module) and [composition](key-concepts.md#composition), as they will be used in the following examples. +Please make sure that you understand key concepts - [resource module](key-concepts.md#resource-module), [infrastructure module](key-concepts.md#infrastructure-module), and [composition](key-concepts.md#composition), as they are used in the following examples. {% endhint %} ### Common recommendations for structuring code -* It is easier and faster to work with smaller number of resources +* It is easier and faster to work with a smaller number of resources * `terraform plan` and `terraform apply` both make cloud API calls to verify the status of resources - * If you have your entire infrastructure in a single composition this can take many minutes -* Blast radius is smaller with fewer resources + * If you have your entire infrastructure in a single composition this can take some time +* A blast radius is smaller with fewer resources * Insulating unrelated resources from each other by placing them in separate compositions reduces the risk if something goes wrong -* Start your project using remote state +* Start your project using remote state because: * Your laptop is no place for your infrastructure source of truth - * Managing a tfstate file in git is a nightmare - * Later when infrastructure layers starts to grow in any direction \(number of dependencies or resources\) -* Try to practise a consistent structure and [naming](naming.md) convention + * Managing a `tfstate` file in git is a nightmare + * Later when infrastructure layers start to grow in multiple directions (number of dependencies or resources) it will be easier to keep things under control +* Practice a consistent structure and [naming](naming.md) convention: * Like procedural code, Terraform code should be written for people to read first, consistency will help when changes happen six months from now * It is possible to move resources in Terraform state file but it may be harder to do if you have inconsistent structure and naming * Keep resource modules as plain as possible -* Don't hardcode values which can be passed as variables or discovered using data sources -* Use data sources and `terraform_remote_state` specifically as a glue between infrastructure modules within composition -* \(add links to other blog posts\) +* Don't hardcode values that can be passed as variables or discovered using data sources +* Use data sources and `terraform_remote_state` specifically as a glue between infrastructure modules within the composition -We will group example projects by the _complexity_ - from small to very-large infrastructures. This separation is not strict, so check other structures also. +In this book, example projects are grouped by _complexity_ - from small to very-large infrastructures. This separation is not strict, so check other structures also. ### Orchestration of infrastructure modules and compositions -Having small infrastructure means that there are a small number of dependencies and few resources. As the project grows the need to chain the execution of Terraform configurations, connecting different infrastructure modules, and passing values within a composition becomes visible. +Having a small infrastructure means that there is a small number of dependencies and few resources. As the project grows the need to chain the execution of Terraform configurations, connecting different infrastructure modules, and passing values within a composition becomes obvious. -There are at least 4 distinct groups of orchestration solutions which developers use: +There are at least 5 distinct groups of orchestration solutions that developers use: -1. Terraform only. Very straightforward, developers have to know only Terraform to get job done. +1. Terraform only. Very straightforward, developers have to know only Terraform to get the job done. 2. Terragrunt. Pure orchestration tool which can be used to orchestrate the entire infrastructure as well as handle dependencies. Terragrunt operates with infrastructure modules and compositions natively, so it reduces duplication of code. 3. In-house scripts. Often this happens as a starting point towards orchestration and before discovering Terragrunt. 4. Ansible or similar general purpose automation tool. Usually used when Terraform is adopted after Ansible, or when Ansible UI is actively used. +5. [Crossplane](https://crossplane.io) and other Kubernetes-inspired solutions. Sometimes it makes sense to utilize the Kubernetes ecosystem and employ a reconciliation loop feature to achieve desired state of your Terraform configurations. View video [Crossplane vs Terraform](https://www.youtube.com/watch?v=ELhVbSdcqSY) for more information. -With that in mind we will reviewing the first two of these project structures, Terraform only and Terragrunt. +With that in mind, this book reviews the first two of these project structures, Terraform only and Terragrunt. -See examples of code structures for [Terraform](examples/terraform.md) or [Terragrunt](examples/terragrunt.md) in the next chapter. +See examples of code structures for [Terraform](examples/terraform/) or [Terragrunt](examples/terragrunt.md) in the next chapter. diff --git a/code-styling.md b/code-styling.md index df25d70..d017fc9 100644 --- a/code-styling.md +++ b/code-styling.md @@ -1,4 +1,4 @@ -# Notes about code styling and documentation +# Code styling {% hint style="success" %} * Examples and Terraform modules should contain documentation explaining features and how to use them. @@ -11,7 +11,7 @@ ### Automatically generated documentation -[pre-commit](https://pre-commit.com/) is a framework for managing and maintaining multi-language pre-commit hooks. It is written in Python and is a powerful tool to do something automatically on developer's machine before code is committed to git repository. Normally, it is used run linters and format code (see [supported hooks](https://pre-commit.com/hooks.html)). +[pre-commit](https://pre-commit.com) is a framework for managing and maintaining multi-language pre-commit hooks. It is written in Python and is a powerful tool to do something automatically on developer's machine before code is committed to git repository. Normally, it is used run linters and format code (see [supported hooks](https://pre-commit.com/hooks.html)). With Terraform configurations pre-commit can be used to format and validate code, as well as to update documentation. @@ -21,8 +21,10 @@ Check out [pre-commit-terraform repository](https://github.com/antonbabenko/pre- [terraform-docs](https://github.com/segmentio/terraform-docs) is a tool which does generation of documentation from Terraform modules in various output formats. You can run it manually (without pre-commit hooks), or using [pre-commit-terraform hooks](https://github.com/antonbabenko/pre-commit-terraform) to get documentation updated automatically. +@todo: Document module versions, release, GH actions + ## Resources -1. [pre-commit framework homepage](https://pre-commit.com/) -1. [Collection of git hooks for Terraform to be used with pre-commit framework](https://github.com/antonbabenko/pre-commit-terraform) -1. Blog post by [Dean Wilson](https://github.com/deanwilson): [pre-commit hooks and terraform - a safety net for your repositories](https://www.unixdaemon.net/tools/terraform-precommit-hooks/) +1. [pre-commit framework homepage](https://pre-commit.com) +2. [Collection of git hooks for Terraform to be used with pre-commit framework](https://github.com/antonbabenko/pre-commit-terraform) +3. Blog post by [Dean Wilson](https://github.com/deanwilson): [pre-commit hooks and terraform - a safety net for your repositories](https://www.unixdaemon.net/tools/terraform-precommit-hooks/) diff --git a/examples/terraform/README.md b/examples/terraform/README.md new file mode 100644 index 0000000..971459a --- /dev/null +++ b/examples/terraform/README.md @@ -0,0 +1,4 @@ +# Terraform + + + diff --git a/key-concepts.md b/key-concepts.md index eb8c478..fc0eb9d 100644 --- a/key-concepts.md +++ b/key-concepts.md @@ -6,53 +6,57 @@ This section describes key concepts which are used inside the book. ## Resource -Resource is `aws_vpc`, `aws_db_instance`, etc. Resource belongs to provider, accepts arguments, outputs attributes, has lifecycles. Resource can be created, retrieved, updated, and deleted. +Resource is `aws_vpc`, `aws_db_instance`, etc. A resource belongs to a provider, accepts arguments, outputs attributes, and has lifecycles. A resource can be created, retrieved, updated, and deleted. ## Resource module -Resource module is a collection of connected resources which together perform the common action \(for eg, [AWS VPC Terraform module](https://github.com/terraform-aws-modules/terraform-aws-vpc/) creates VPC, subnets, NAT gateway, etc\). It depends on provider configuration, which can be defined in it, or in higher level structures \(eg, in infrastructure module\). +Resource module is a collection of connected resources which together perform the common action (for eg, [AWS VPC Terraform module](https://github.com/terraform-aws-modules/terraform-aws-vpc/) creates VPC, subnets, NAT gateway, etc). It depends on provider configuration, which can be defined in it, or in higher-level structures (eg, in infrastructure module). ## Infrastructure module -Infrastructure module is a collection of resource modules, which can be logically not connected, but in the current situation/project/setup are serving the same purpose. It defines configuration for providers, which is passed to the downstream resource modules and to resources. It is normally limited to work in one entity per logical separator \(eg, AWS Region, Google Project\). An example is [terraform-aws-atlantis](https://github.com/terraform-aws-modules/terraform-aws-atlantis/) which uses resource modules like [terraform-aws-vpc](https://github.com/terraform-aws-modules/terraform-aws-vpc/) and [terraform-aws-security-group](https://github.com/terraform-aws-modules/terraform-aws-security-group/) to create infrastructure required for running [Atlantis](https://www.runatlantis.io) on [AWS Fargate](https://aws.amazon.com/fargate/). +An infrastructure module is a collection of resource modules, which can be logically not connected, but in the current situation/project/setup serves the same purpose. It defines the configuration for providers, which is passed to the downstream resource modules and to resources. It is normally limited to work in one entity per logical separator (eg, AWS Region, Google Project). + +For example, [terraform-aws-atlantis](https://github.com/terraform-aws-modules/terraform-aws-atlantis/) module uses resource modules like [terraform-aws-vpc](https://github.com/terraform-aws-modules/terraform-aws-vpc/) and [terraform-aws-security-group](https://github.com/terraform-aws-modules/terraform-aws-security-group/) to manage the infrastructure required for running [Atlantis](https://www.runatlantis.io) on [AWS Fargate](https://aws.amazon.com/fargate/). + +Another example is [terraform-aws-cloudquery](https://github.com/cloudquery/terraform-aws-cloudquery) module where multiple modules by [terraform-aws-modules](https://github.com/terraform-aws-modules/) are being used together to manage the infrastructure as well as using Docker resources to build, push, and deploy Docker images. All in one set. ## Composition -Composition is a collection of infrastructure modules, which can span across several logically separated areas \(eg., AWS Regions, several AWS accounts\). Composition is used to describe the complete infrastructure required for the whole organization or project. +Composition is a collection of infrastructure modules, which can span across several logically separated areas (eg., AWS Regions, several AWS accounts). Composition is used to describe the complete infrastructure required for the whole organization or project. -Composition consists of infrastructure modules, which consist of resources modules, which implement individual resources. +A composition consists of infrastructure modules, which consist of resources modules, which implement individual resources. ![Simple infrastructure composition](.gitbook/assets/composition-1.png) ## Data source -Data source performs read-only operation and is dependant on provider configuration, it is used in a resource module and an infrastructure module. +Data source performs a read-only operation and is dependant on provider configuration, it is used in a resource module and an infrastructure module. -Data source `terraform_remote_state` acts as a glue for higher level modules and compositions. +Data source `terraform_remote_state` acts as a glue for higher-level modules and compositions. -The [external](https://www.terraform.io/docs/providers/external/data_source.html) data source allows an external program to act as a data source, exposing arbitrary data for use elsewhere in the Terraform configuration. +The [external](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data\_source) data source allows an external program to act as a data source, exposing arbitrary data for use elsewhere in the Terraform configuration. Here is an example from the [terraform-aws-lambda module](https://github.com/terraform-aws-modules/terraform-aws-lambda/blob/258e82b50adc451f51544a2b57fd1f6f8f4a61e4/package.tf#L5-L7) where the filename is computed by calling an external Python script. -The [http](https://www.terraform.io/docs/providers/http/data_source.html) data source makes an HTTP GET request to the given URL and exports information about the response which is often useful to get information from endpoints where native Terraform provider does not exist. +The [http](https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http) data source makes an HTTP GET request to the given URL and exports information about the response which is often useful to get information from endpoints where a native Terraform provider does not exist. ## Remote state -Infrastructure modules and compositions should persist their state in a remote location which can be reached by others in a controllable way \(ACL, versioning, logging\). +Infrastructure modules and compositions should persist their [Terraform state](https://www.terraform.io/docs/language/state/index.html) in a remote location where it can be retrieved by others in a controllable way (e.g. specify ACL, versioning, logging). ## Provider, provisioner, etc -Providers, provisioners and few other terms are described very well on the official documentation and there is no point to repeat it here. To my opinion they have little to do with writing good Terraform modules. More details will be provided later. +Providers, provisioners, and a few other terms are described very well on the official documentation and there is no point to repeat it here. To my opinion, they have little to do with writing good Terraform modules. ## Why so _difficult_? -While individual resources are like atoms in the infrastructure, resource modules are molecules. Module is a smallest versioned and shareable unit. It has exact list of arguments, implement basic logic for such unit to do required function. Eg. [terraform-aws-security-group](https://github.com/terraform-aws-modules/terraform-aws-security-group) creates `aws_security_group` and `aws_security_group_list` resources based on input. This resource module by itself can be used together with other modules to create infrastructure module. +While individual resources are like atoms in the infrastructure, resource modules are molecules. A module is the smallest versioned and shareable unit. It has an exact list of arguments, implement basic logic for such a unit to do the required function. Eg. [terraform-aws-security-group](https://github.com/terraform-aws-modules/terraform-aws-security-group) module creates `aws_security_group` and `aws_security_group_list` resources based on input. This resource module by itself can be used together with other modules to create the infrastructure module. -Access to data across molecules \(resource modules and infrastructure modules\) is performed using (module) outputs and data sources. +Access to data across molecules (resource modules and infrastructure modules) is performed using the modules' outputs and data sources. -Access between compositions is performed using remote state data sources. +Access between compositions is often performed using remote state data sources. There are [multiple ways to share data between configurations](https://www.terraform.io/docs/language/state/remote-state-data.html#alternative-ways-to-share-data-between-configurations). When putting concepts described above in pseudo-relations it may look like this: -```text +``` composition-1 { infrastructure-module-1 { data-source-1 => d1 @@ -72,4 +76,3 @@ composition-1 { } ``` - diff --git a/naming.md b/naming.md index dafd07a..c41ed59 100644 --- a/naming.md +++ b/naming.md @@ -3,50 +3,52 @@ ## General conventions {% hint style="info" %} -There should be no reason to not follow at least these :\) +There should be no reason to not follow at least these conventions :) {% endhint %} -1. Use `_` \(underscore\) instead of `-` \(dash\) in all: resource names, data source names, variable names, outputs. - * Beware that actual cloud resources have many hidden restrictions in their naming conventions. Some cannot contain dashes, some must be camel cased. These conventions refer to Terraform names themselves. -2. Only use lowercase letters and numbers. +1. Use `_` (underscore) instead of `-` (dash) everywhere (resource names, data source names, variable names, outputs, etc). + +* Beware that actual cloud resources often have restrictions in allowed names. Some resources, for example, can't contain dashes, some must be camel-cased. The conventions in this book refer to Terraform names themselves. + +1. Only use lowercase letters and numbers. ## Resource and data source arguments -1. Do not repeat resource type in resource name \(not partially, nor completely\): +1. Do not repeat resource type in resource name (not partially, nor completely): * Good: `resource "aws_route_table" "public" {}` * Bad: `resource "aws_route_table" "public_route_table" {}` * Bad: `resource "aws_route_table" "public_aws_route_table" {}` -2. Resource name should be named `this` if there is no more descriptive and general name available, or if resource module creates single resource of this type \(eg, there is single resource of type `aws_nat_gateway` and multiple resources of type`aws_route_table`, so `aws_nat_gateway` should be named `this` and `aws_route_table` should have more descriptive names - like `private`, `public`, `database`\). +2. Resource name should be named `this` if there is no more descriptive and general name available, or if the resource module creates a single resource of this type (eg, in [AWS VPC module](https://github.com/terraform-aws-modules/terraform-aws-vpc) there is a single resource of type `aws_nat_gateway` and multiple resources of type`aws_route_table`, so `aws_nat_gateway` should be named `this` and `aws_route_table` should have more descriptive names - like `private`, `public`, `database`). 3. Always use singular nouns for names. -4. Use `-` inside arguments values and in places where value will be exposed to a human \(eg, inside DNS name of RDS instance\). -5. Include `count` argument inside resource blocks as the first argument at the top and separate by newline after it. See [example](naming.md#usage-of-count). -6. Include `tags` argument, if supported by resource as the last real argument, following by `depends_on` and `lifecycle`, if necessary. All of these should be separated by single empty line. See [example](naming.md#placement-of-tags). -7. When using condition in `count` argument use boolean value, if it makes sense, otherwise use `length` or other interpolation. See [example](naming.md#conditions-in-count). -8. To make inverted conditions don't introduce another variable unless really necessary, use `1 - boolean value` instead. For example, `count = "${1 - var.create_public_subnets}"` +4. Use `-` inside arguments values and in places where value will be exposed to a human (eg, inside DNS name of RDS instance). +5. Include argument `count` / `for_each` inside resource or data source block as the first argument at the top and separate by newline after it. +6. Include argument `tags,` if supported by resource, as the last real argument, following by `depends_on` and `lifecycle`, if necessary. All of these should be separated by a single empty line. +7. When using conditions in an argument`count` / `for_each` prefer boolean values over using `length` or other expressions. ## Code examples of `resource` -### Usage of `count` +### Usage of `count` / `for_each` {% hint style="success" %} -{% code-tabs %} -{% code-tabs-item title="main.tf" %} -```text +``` resource "aws_route_table" "public" { - count = "2" + count = 2 + + vpc_id = "vpc-12345678" + # ... remaining arguments omitted +} + +resource "aws_route_table" "private" { + for_each = toset(["one", "two"]) vpc_id = "vpc-12345678" # ... remaining arguments omitted } ``` -{% endcode-tabs-item %} -{% endcode-tabs %} {% endhint %} {% hint style="danger" %} -{% code-tabs %} -{% code-tabs-item title="main.tf" %} -```text +``` resource "aws_route_table" "public" { vpc_id = "vpc-12345678" count = "2" @@ -54,18 +56,14 @@ resource "aws_route_table" "public" { # ... remaining arguments omitted } ``` -{% endcode-tabs-item %} -{% endcode-tabs %} {% endhint %} ### Placement of `tags` {% hint style="success" %} -{% code-tabs %} -{% code-tabs-item title="main.tf" %} -```text +``` resource "aws_nat_gateway" "this" { - count = "1" + count = 1 allocation_id = "..." subnet_id = "..." @@ -74,27 +72,23 @@ resource "aws_nat_gateway" "this" { Name = "..." } - depends_on = ["aws_internet_gateway.this"] + depends_on = [aws_internet_gateway.this] lifecycle { create_before_destroy = true } } ``` -{% endcode-tabs-item %} -{% endcode-tabs %} {% endhint %} {% hint style="danger" %} -{% code-tabs %} -{% code-tabs-item title="main.tf" %} -```text +``` resource "aws_nat_gateway" "this" { - count = "1" + count = 1 tags = "..." - depends_on = ["aws_internet_gateway.this"] + depends_on = [aws_internet_gateway.this] lifecycle { create_before_destroy = true @@ -104,119 +98,84 @@ resource "aws_nat_gateway" "this" { subnet_id = "..." } ``` -{% endcode-tabs-item %} -{% endcode-tabs %} {% endhint %} ### Conditions in `count` {% hint style="success" %} -* {% code-tabs %} - {% code-tabs-item title="main.tf" %} - ```text - count = "${length(var.public_subnets) > 0 ? 1 : 0}" - ``` - {% endcode-tabs-item %} - {% endcode-tabs %} -* {% code-tabs %} - {% code-tabs-item title="main.tf" %} - ``` - count = "${var.create_public_subnets}" - ``` - {% endcode-tabs-item %} - {% endcode-tabs %} +* + +``` +count = length(var.public_subnets) > 0 ? 1 : 0 +``` + +* + +``` +count = var.create_public_subnets +``` {% endhint %} ## Variables -1. Don't reinvent the wheel in resource modules - use the same variable names, description and default as defined in "Argument Reference" section for the resource you are working on. -2. Omit `type = "list"` declaration if there is `default = []` also. -3. Omit `type = "map"` declaration if there is `default = {}` also. -4. Use plural form in name of variables of type `list` and `map`. -5. When defining variables order the keys: `description` , `type`, `default` . -6. Always include `description` for all variables even if you think it is obvious. +1. Don't reinvent the wheel in resource modules: use `name`, `description`, and `default` value for variables as defined in the "Argument Reference" section for the resource you are working with. +2. Support for validation in variables is rather limited (e.g. can't access other variables or do lookups). Plan accordingly because in many cases this feature is useless. +3. Use the plural form in a variable name when type is `list(...)` or `map(...)`. +4. Keys in variable block in order: `description` , `type`, `default`, `validation`. +5. Always include `description` on all variables even if you think it is obvious (you will need it in the future). +6. Prefer using simple types (`number`, `string`, `list(...)`, `map(...)`, `any`) over specific type like `object()` unless you need to have strict constraints on each key. +7. Use specific types like `map(map(string))` if all elements of the map have the same type (e.g. `string`) or can be converted to it (e.g. `number` type can be converted to `string`). +8. Use type `any` to disable type validation starting from a certain depth or when multiple types should be supported. +9. Value `{}` is sometimes a map but sometimes an object. Use `tomap(...)` to make a map because there is no way to make an object. ## Outputs -Name for the outputs is important to make them consistent and understandable outside of its scope \(when user is using a module it should be obvious what type and attribute of the value is returned\). +Make outputs consistent and understandable outside of its scope (when user is using a module it should be obvious what type and attribute of the value is returned). -1. The general recommendation for the names of outputs is that it should be descriptive for the value it contains and be less free-form than you would normally want. -2. Good structure for names of output looks like `{name}_{type}_{attribute}` , where: - 1. `{name}` is a resource or data source name without provider prefix. `{name}` for `aws_subnet` is `subnet`, for`aws_vpc` it is `vpc`. +1. The name of output should describe the property it contains and be less free-form than you would normally want. +2. Good structure for the name of output looks like `{name}_{type}_{attribute}` , where: + 1. `{name}` is a resource or data source name without a provider prefix. `{name}` for `aws_subnet` is `subnet`, for`aws_vpc` it is `vpc`. 2. `{type}` is a type of a resource sources 3. `{attribute}` is an attribute returned by the output 4. [See examples](naming.md#code-examples-of-output). -3. If output is returning a value with interpolation functions and multiple resources, the `{name}` and `{type}` there should be as generic as possible \(`this` is often the most generic and should be preferred\). [See example](naming.md#code-examples-of-output). -4. If the returned value is a list it should have plural name. [See example](naming.md#use-plural-name-if-the-returning-value-is-a-list). +3. If the output is returning a value with interpolation functions and multiple resources, `{name}` and `{type}` there should be as generic as possible (`this` as prefix should be omitted). [See example](naming.md#code-examples-of-output). +4. If the returned value is a list it should have a plural name. [See example](naming.md#use-plural-name-if-the-returning-value-is-a-list). 5. Always include `description` for all outputs even if you think it is obvious. +6. Avoid setting `sensitive` argument unless you fully control usage of this output in all places in all modules. +7. Prefer `try()` (available since Terraform 0.13) over `element(concat(...))` ### Code examples of `output` Return at most one ID of security group: {% hint style="success" %} -{% code-tabs %} -{% code-tabs-item title="outputs.tf" %} -```text -output "this_security_group_id" { +``` +output "security_group_id" { description = "The ID of the security group" - value = "${element(concat(coalescelist(aws_security_group.this.*.id, aws_security_group.this_name_prefix.*.id), list("")), 0)}" + value = try(aws_security_group.this[0].id, aws_security_group.name_prefix[0].id, "") } ``` -{% endcode-tabs-item %} -{% endcode-tabs %} {% endhint %} -When there are multiple resources of the same type, `this` should be preferred and it should be part of name in output, also `another_security_group_id` should be named `web_security_group_id`: +When having multiple resources of the same type, `this` should be omitted in the name of output: {% hint style="danger" %} -{% code-tabs %} -{% code-tabs-item title="outputs.tf" %} -```text -output "security_group_id" { +``` +output "this_security_group_id" { description = "The ID of the security group" - value = "${element(concat(coalescelist(aws_security_group.this.*.id, aws_security_group.web.*.id), list("")), 0)}" -} - -output "another_security_group_id" { - description = "The ID of web security group" - value = "${element(concat(aws_security_group.web.*.id, list("")), 0)}" + value = element(concat(coalescelist(aws_security_group.this.*.id, aws_security_group.web.*.id), [""]), 0) } ``` -{% endcode-tabs-item %} -{% endcode-tabs %} {% endhint %} ### Use plural name if the returning value is a list {% hint style="success" %} -{% code-tabs %} -{% code-tabs-item title="outputs.tf" %} -```text -output "this_rds_cluster_instance_endpoints" { - description = "A list of all cluster instance endpoints" - value = ["${aws_rds_cluster_instance.this.*.endpoint}"] -} ``` -{% endcode-tabs-item %} -{% endcode-tabs %} -{% endhint %} - -### Conditions in `output` - -There are two resources of type `aws_db_instance` with names `this` and `this_mssql` where at most one resource can be created at the same time. - -{% hint style="success" %} -{% code-tabs %} -{% code-tabs-item title="outputs.tf" %} -```text -output "this_db_instance_id" { - description = "The RDS instance ID" - value = "${element(concat(coalescelist(aws_db_instance.this_mssql.*.id, aws_db_instance.this.*.id), list("")), 0)}" +output "rds_cluster_instance_endpoints" { + description = "A list of all cluster instance endpoints" + value = aws_rds_cluster_instance.this.*.endpoint } ``` -{% endcode-tabs-item %} -{% endcode-tabs %} {% endhint %} -\*\*\*\*