Documentation
Installation
How to install the app
The process is extremely simple. You only need a Github repository where you have admin access. The LastDevOps Terraform Guard requires the following permissions to your repository:
- Read-Only for Content (meaning repository content, aka your codebase)
- Read-Write for PRs (for publishing PR comments)
- Read-Write for Checks (for submitting code reviews and block/unblocking PRs)
- Read-Only for Metadata (this is a mandatory access required by Github)
To install the app proceed with the following steps:
- Navigate to Github Marketplace and hit Add
- Select a repository you want the app to be installed (1 repo is free, transparent per-repo pricing for more)
- Confirm
Thats it. It probably took you 20-30 secods, right? Now, the app will communicate through PR comments and Checks.
You’ll receive a one-time welcoming message in your next PR, then you should expect to recieve only information about your repository, or a new feature available.
How does it work?
PR Reviews
PR review are dreadful, they are never fast. You have to wait for your feedback for hours. Not anymore. The LstDevOps Terraform Guard is blazingly fast:
- It takes your commit when you open or update a PR
- The app then checks your code for violations
- The app lastly gives you feedback and insights about your code quality
Maintainability Score
Maintainability score is a vital metric often overlooked. We calculate it based on a few factors:
- Violations severity (for example missing type for variable affects maintainability than complex filters in for_each for modules)
- Violations density (files with more violations affect maintainability more)
- Repeated issues worsen the score
It’s OK to have a maintainability score less than 100. The codebase always evolves and maintainability can’t be perfect. When the maintainability drops to < 50 it’s still not the end of the world, but the App will require an action on a PR (you can choose to ignore it to proceed if necessary).
50 was selected as a middle ground to ensure codebase doesn’t spiral out of control. We might change the treshold in the future.
Support
You might have questions, feedback, or just want to learn more. Feel free to contact support@lastdevops.com (the Founder answers all emails). If you’re struggling with your codebase, just message, the more permanent communication channel can be established.
What do we check for?
Follow well-known terraform file structure
Severity: 🟥 Won’t affect the state. Can be fixed in the scope of a PR
Description Terraform files structure is vital. It’s extremely important for maintainability and doesn’t affect the state at all. Clear resources separation allows easy & productive code review as well as improves code learning curve.
- variable should in variables.tf and nothing else
- output should in outputs.tf and nothing else
- provider should in providers.tf and nothing else
- required_providers should in versions.tf and nothing else
Keep terraform project size in check
Severity: 🟧 Affects the state. Could be fixed in the scope of another PR
Description: Complex terraform projects (20+ resource blocks) have several issues.
- Blast radius. The more resources you control in a single state the more there’s a chance to blew everything.
- Eternal terraform plan execution. 1 change and terraform has to evaluate everything.
- Complex navigation. The more files and code there is the harder it is to find something. Even clear documentation doesn’t always help when you have 300+ lines of terraform code in a single file.
Variables must have description and type
Severity: 🟥 Won’t affect the state. Can be fixed in the scope of a PR
Description: Terraform variables are the interface for modules and projects. Having variables without description (no matter how descriptive the name is) is similar to having no documentation at all.
Ensure you set appropriate description and type.
Keep resource/module definition under 50 lines
Severity: 🟥 Won’t affect the state. Can be fixed in the scope of a PR
Description: Large resource definitions are similar to large functions and methods - they are barely readable, and even less maintainable. Of course there’s a fine line and there’s no need to go extreme and keep only 10 lines per resource (it’s not always possible), but it’s important to keep in mind that long code blocks are extremely hard to read and understand.
Keep your code DRY
Severity: 🟥 Won’t affect the state. Can be fixed in the scope of a PR
Description: Terraform is descriptive, and this inevitably leads to copy-paste in many cases. Consider using locals, variables and data resources for repeated expressions to avoid copy paste and keep the codebase DRY (without going extreme).
Prefer specific references over broad in for_each and count
Severity: 🟧 Might affect the state. Could be fixed in the scope of another PR
Description: Splat expressions are an amazing tool to shorten the codebase, however in loops and conditional creates it brings implicit complication and unexpected behaviour becase it often requires filtering. It’s adviced to avoid splat expressions directly in for_each or count. It’s ok to use splat expressions in locals and then use those locas in loops and conditional attributes.
Prefer for_each over count
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: count introduces unexpected behaviour when items in a middle of an array are removed, thus causing resources recreation. It’s adviced to always prefer for_each unless it’s conditional create.
Avoid hardcoding values in attributes
Severity: 🟥 Won’t affect the state. Can be fixed in the scope of a PR
Description: If there’s a repeated value such as a name, or an expression, it’s adviced to move the value to local or a variable. There are cases when flexibility to change the value in one place without affecting all the others is required so there’s no need to go extreme.
Keep project environment-averse
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: Resources such as resource “aws_s3_bucket” “dev_files” or count = var.env == “dev” ? 1 : 0 should be avoided. It’s better to have “environment-independed” code base that recieves environment-specific variable inputs. This will ensure a given terraform project can be reliably recreated if needed.
Use for_each to group resources
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: When you have the same resource type (for example resource “aws_security_group” “db”) and similar attributes it’s better to group them using a for_each attribute. This will keep the codebase DRY and allow efficiently add new resources without extensive copy-paste.
Don’t overpin versions for providers
Severity: 🟧 Might affect the state. Could be fixed in the scope of another PR
Description: There’s a popular take that versions have to be as explicit as possible. LastDevOps have a different take: it’s a security concerns that worsens maintainability, so careful trade off is crucial. Strict version pinning in providers deprives a terraform project from valuable automatic security updates. Major providers are updated often, and new bug fixes and feature are introduced often. So, a PR to manually update a version is required every time a new version is release.
We don’t advice or suggest violating existing security & compliance requirements if they exist, however this affects maintainability of the project. If strict versioning is not required it’s better to use more relaxed versions pinning such as ~> 3.5
Avoid provisioners if favor of cloud-native blocks
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: Provisioners are often used to executed local CLI. They can be used to login to an instance and perform software installation or to populate a database. It’s advised to avoid using provisioners in favor of user_data and similar attributes, because provisioners are environment-dependend and bring unnecessary local or remote software and configuration management.
In addition to that, provisioners worsen project’s security posture by requiring access to remote resources from the place this terraform project is managed (often a local machine or a CI system)
Enforce unqiue state per environment
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: Keeping environments separate is a must to avoid managing access to multiple environments from a single place. A terraform project becomes vulnerable for accidentally affecting production resources by changing anything when all environments are managed in a single state.
Avoid circular dependencies
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: Circular dependencies are the enemy of terraform infrastructure reproducability. Example of a circular dependency is an EC2 instance with attached IAM role, and this IAM role with condition to work only when attached to that instance. By introducing circular dependencies infrastructure becomes unreproducable, thus much less maintainable.
A Few good ways to avoid circular dependencies:
- multiple states & data resources
- depend on something predictable rathen than direct reference
- modules composition
The bottom line is to make a dependency linear.
In case of Ec2 -> IAM Role example, it’s adviced to either
- use tags instead of ARNs in IAM conditions
- construct ARN in advance since it’s a known pattern.
Limit dynamic blocks usage
Severity: 🟧 Might affect the state. Could be fixed in the scope of another PR
Description: Dynamic blocks are a great way to keep complex resources definition DRY. However if they are overused the codebase becomes less readable and maintainable. It’s adviced to keep the number of dynamic blocks below 3 per resource.
Enfore Cost Tags everywhere
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: It’s essential to keep track of costs and been able to slice infrasctructure to localize spiraling costs. Tagging is one of the only mechanisms that is often available. So, it’s important to set tags for every available resource.
AWS provider, for example, allows to set such tags on a “provider” level and thus automatically adding them to all applicable resources. This capability doesn’t exist in all available providers, so an alternative of having a local/module representing basic tags structure is an acceptable alternative.
Limit number of resources in a module
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: Module are a great way to follow composition best practices, however it’s important to not loose track of abstraction and keep resources number under control to ensure modules maintainability and readability. It’s adviced to keep the number of resources in a module under 20 (excluding other modules, data resources, locals and variables)
Don’t use dynamic mapping for modules
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: Modules inherinetely abstract resources creation. It’s important to keep interactions with modules as explicit as possible. So, introduction of conditions, filtering and complex functions in modules for_each attribute often leads to unpredictabled behaviour. And with plain resources this behaviour can be kept under control, but with modules abstraction it becomes significantly more complicated.
It’s adviced to keep for_each attributes values for modules straightforward and explicit.
Relax module versions constraints
Severity: 🟧 Might affect the state. Could be fixed in the scope of another PR
Description: When using external modules it’s typical to set specific references. However, the same way it worsens maintainability in the case of providers, it also worsens maintainability for modules. We don’t advice, nor suggest violating security or/and compliance rules, however when not applicable, terraform projects greately benefit from automatic minor updates.
Use dynamic blocks to keep resources DRY
Severity: 🟧 Might affect the state. Could be fixed in the scope of another PR
Description: In case resources require multiple configuration blocks to add repeated values such as IPs or references to other resources, it’s adviced to use dynamic blocks and group those values in a local variable to keep code organized and DRY. In most cases it doesn’t affect the state, but it potentially might in case of unequal number of attributes updated.
Limit nested modules calls
Severity: 🟧 Will affect the state. Could be fixed in the scope of another PR
Description: Terraform modules composition is an amazing way to struture both simple and complext codebase. However projects tend to grow complex over time and it’s easy to spiral out of control and end up 4+ levels deep into module calls making the codebase barely maintainable. It’s adviced to keep nested level under 3.
A typical structure is:
- Shared
- Apps
- Environments
And Environments use modules from Apps and Apps use modules from Shared.
What about remote modules? Consider then 1 level. So, if there’s a Dev environment that uses Backend App module that uses Networking, Container and Database modules, the IaC remains maintainable.
However, if Dev environment uses Backend App that uses Networking that uses Subnet Modules that uses IPRange Provider module, infrastructure maintainability drops significantly.