Skip to main content

Terraform

Analytical Platform’s Core Infrastructure team offer a standardised pipeline for running Terraform. This is done via GitHub Actions and is available to all teams.

Terraform components are scanned by Checkov and tfsec

Terraform components are also linted by the Super Linter with fmt, tflint and terrascan

Style guide

Structure

  • data sources should be placed in data.tf

  • providers and versions should be placed in terraform.tf

  • variables should be placed in variables.tf

  • outputs should be placed in outputs.tf

  • resources should be grouped in files which are based from their name, e.g.

    • IAM policies should be placed in iam-policies.tf
    • IAM roles should be placed in iam-roles.tf
    • KMS keys should be placed in kms-keys.tf
  • locals should be placed in locals.tf

  • modules should be placed in modules/

Naming

  • resources should be named using snake case, e.g. resource "aws_iam_role" "example_role_name" {}

  • resources shouldn’t contain what they are, e.g. resource "aws_iam_role" "example_role_name" {} is preferred over resource "aws_iam_role" "example_role_name_role" {}

  • modules should be named using snake case, e.g. module "example_module_name" {}

  • modules should be name relative to what they are doing, e.g. module "example_iam_role" {}

Creating a new Terraform component

To create a new Terraform component, you will need to:

  1. Create a directory in terraform, e.g. terraform/aws/${AWS_ACCOUNT_NAME}/${COMPONENT}

  2. Using the table below for reference, create the required files in the component directory, replacing the placeholders with the correct values

    Key Value Example
    AWS_ACCOUNT_NAME Name of the account as it appears in
    AWS IAM Identity Center
    analytical-platform-development
    AWS_ACCOUNT_ID ID of the account as it appears in
    AWS IAM Identity Center
    123456789012
    COMPONENT Name of the component
    (We refer to a component as a collection of resources that create a service)
    eks
    ENVIRONMENT Name of the environment
    development
    IS_PRODUCTION Whether the environment is production
    false

    data.tf (Example)

    Expand to see code block

      data "aws_caller_identity" "session" {
        provider = aws.session
      }
    
      data "aws_iam_session_context" "session" {
        provider = aws.session
    
        arn = data.aws_caller_identity.session.arn
      }
    

    terraform.tf (Example)

    Expand to see code block

      terraform {
        backend "s3" {
          acl            = "private"
          bucket         = "global-tf-state-aqsvzyd5u9"
          encrypt        = true
          key            = "aws/${AWS_ACCOUNT_NAME}/${COMPONENT}/terraform.tfstate"
          region         = "eu-west-2"
          dynamodb_table = "global-tf-state-aqsvzyd5u9-locks"
        }
        required_providers {
          aws = {
            source  = "hashicorp/aws"
            version = "${LATEST_VERSION}" # e.g. 5.9.0 can be found at https://registry.terraform.io/providers/hashicorp/aws/latest
          }
        }
        required_version = "~> 1.5"
      }
    
      provider "aws" {
        alias = "session"
      }
    
      provider "aws" {
        region = "eu-west-2"
        assume_role {
          role_arn = "arn:aws:iam::${var.account_ids["${AWS_ACCOUNT_NAME}"]}:role/GlobalGitHubActionAdmin"
        }
        default_tags {
          tags = var.tags
        }
      }
    
      provider "aws" {
        alias  = "analytical-platform-management-production"
        region = "eu-west-2"
        assume_role {
          role_arn = can(regex("AdministratorAccess", data.aws_iam_session_context.session.issuer_arn)) ? null : "arn:aws:iam::${var.account_ids["analytical-platform-management-production"]}:role/GlobalGitHubActionAdmin"
        }
        default_tags {
          tags = var.tags
        }
      }
    

    terraform.tfvars (Example)

    Expand to see code block

      account_ids = {
        ${AWS_ACCOUNT_NAME}                       = "${AWS_ACCOUNT_ID}"
        analytical-platform-management-production = "042130406152"
      }
    
      tags = {
        business-unit          = "Platforms"
        application            = "Data Platform"
        component              = "${COMPONENT}"
        environment            = "${ENVIRONMENT}"
        is-production          = "${IS_PRODUCTION}"
        owner                  = "data-platform:data-platform-tech@digital.justice.gov.uk"
        infrastructure-support = "data-platform:data-platform-tech@digital.justice.gov.uk"
        source-code            = "github.com/ministryofjustice/analytical-platform/terraform/aws/${AWS_ACCOUNT_NAME}/${COMPONENT}"
      }
    

    variables.tf (Example)

    Expand to see code block

      variable "account_ids" {
        type        = map(string)
        description = "Map of account names to account IDs"
      }
    
      variable "tags" {
        type        = map(string)
        description = "Map of tags to apply to resources"
      }
    

  3. Generate a Terraform lock file by running the following command in the component’s directory

    terraform init -upgrade -backend=false
    
  4. Submit your changes using a pull request

Updating a Terraform component

  1. Make the changes required to the component

  2. Submit your changes using a pull request

Static Analysis

Static analysis was introduced in #866, however the components that make up Analytical Platform have not been remediated yet, this is addressed in #886

If you are working on a component that has not yet been addressed, you will need to add the label override-static-analysis to your pull request

This will allow the pull request to be merged without the static analysis checks failing

This page was last reviewed on 27 May 2024. It needs to be reviewed again on 27 November 2024 by the page owner #analytical-platform-notifications .