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 inAWS IAM Identity Center analytical-platform-development
    AWS_ACCOUNT_ID ID of the account as it appears inAWS 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)

    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)

    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)

    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)

    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

Last reviewed: 9 December 2024Review status: ✗ Review overdueOwner: #analytical-platform-notificationsSource: View source on GitHub

Was this page useful?