Create a kit module

Note: this section of the documentation is under constrution

In this tutorial you will learn how to create a new landing zone kit module and apply it to one of your cloud platforms.

In this tutorial, we will build a kit module to set up a GCP organization structure with folders. We will leverage this folder to enforce an organization-wide policy about the allowed resource locations so that customers of your landing zone can only deploy to EU regions.

You can of course also follow the steps of this tutorial for another platform and context.

Prerequisites

This tutorial assumes you have

  • the collie cli tool installed
  • an initialized collie repository with a foundation
  • your foundation has a bootstrapped GCP cloud platform

1. Create a new kit module template

As a first step, we have to create a new kit module. collie can help us create the required template with terraform files, a README.md and some helpful boilerplate.

$ collie kit new "gcp/admin/organiization"
 ? Choose a human-friendly name for this module (gcp/admin/organization) › GCP Organization Setup

generated new kit module at kit/gcp/admin/organization/README.md
Tip: add terraform code to your kit module at kit/gcp/admin/organization/main.tf

$ tree kit/gcp/admin/organization/
kit/gcp/admin/organization/
├── README.md
├── documentation.tf
├── main.tf
├── outputs.tf
└── variables.tf

2. Write terraform code

We now write some terraform code to create a new folder in the GCP organization and setup some policies on it. Put the following content into the terraform files in kit/gcp/admin/organization/:

resource "google_folder" "root" {
  display_name = var.root_folder_name
  parent       = "organizations/${var.organization_id}"
}

module "allowed-policy-resource-locations" {
  source            = "terraform-google-modules/org-policy/google"
  version           = "~> 5.1.0"
  policy_for        = "folder"
  folder_id         = google_folder.root.id
  constraint        = "constraints/gcp.resourceLocations"
  policy_type       = "list"
  allow             = var.resource_locations_to_allow
  allow_list_length = length(var.resource_locations_to_allow)
}
variable "root_folder_name" {
  type        = string
  nullable    = false
  description = <<EOF
    Create a folder of the specified name and treat it as the root of all resources managed as part of this kit.
  EOF
}

variable "resource_locations_to_allow" {
  type        = list(string)
  description = "The list of GCP resource locations to allow"
}
output "organization_id" {
    value = var.organization_id
}

output "root_folder_id" {
    value = google_folder.root.id
}

As you can see, we're leveraging the official google org-policy moduleopen in new window from the terraform registry to set up a list of allowed resource locationsopen in new window.

TIP

Leverage existing terraform modules can be a powerful approach for building your own landing zones.

3. Documenting your kit module

It's a best practice to provide human-readable documentation for consumers of your landing zones. For example, an application team building on your landing zone needs to be aware of the resource location constraint enforced in this module. This allows teams to determine if they can build on your landing zone ahead of time, not until they learn about the constraint the hard way with a failed deployment.

The landing zone construction kit contains a markdown-based documentation workflow. All you have to do is generate documentation from terraform into an output.md file. The documentation.tf file generated by collie already includes a template for this, we just have to fill it in to document our module accordingly.

variable "output_md_file" {
  type        = string
  description = "location of the file where this cloud foundation kit module generates its documentation output"
}

resource "local_file" "output_md" {
  filename = var.output_md_file
  content = <<EOF
All resources of this platform are nested under the top-level GCP folder `${var.root_folder_name}`.
All policies described below are also set at this folder level.

### Resource Locations

[Resource Locations](https://cloud.google.com/resource-manager/docs/organization-policy/defining-locations) restrics deployment of resources to whitelisted regions.
This prevents deployment of resources outside of approved locations.

The allowed resource locations are

${join("\n", formatlist("- `%s`", var.resource_locations_to_allow))}

EOF
}

Great, we have now built a re-usable and self-documenting kit module with terraform.

4. Applying your kit module

Now let's deploy the module to our our GCP platform. Again, collie can help us generate the necessary terragrunt code to apply the kit module with the interactive collie kit apply command.

TIP

We call the terragrunt code to apply a kit module a platform a platform module.

$ collie kit apply
parsing kit modules ...
parsing kit modules DONE 48ms
 ? Select a kit module from your repository ❯ gcp/admin/organization
 ? Select a foundation ❯ my-foundation
 ? Select a platform › gcp
 
applied module kit/gcp/admin/organization to foundations/my-foundation/platforms/gcp/admin/organization
Tip: edit the terragrunt configuration invoking the kit module at foundations/my-foundation/platforms/gcp/admin/organization/terragrunt.hcl

We now need to set up terragrunt to execute our module with the right inputs.

locals {
  # make collie's platform config available here in terraform by parsing its frontmatter
  platform = yamldecode(regex("^---([\\s\\S]*)\\n---\\n[\\s\\S]*$", file(".//README.md"))[0])
}

# invoke the kit module
terraform {
  source = "${get_repo_root()}//kit/gcp/admin/organization"
}

# specify input variables for kit module
inputs = {
  organization_id             = locals.platform.gcp.organization
  root_folder_name            = "my-foundation"
  resource_locations_to_allow = ["in:eu-locations"]
  output_md_file              = "${get_terragrunt_dir()}/output.md"
}

# boilerplate for setting up terraform 
## configure provider with credentials from bootstrapping, customize as required
generate "provider" {
  path      = "provider.tf"
  if_exists = "overwrite"
  contents  = <<EOF
provider "google" {
  project     = "${include.root.locals.platform.gcp.project}"
  region      = "europe-west3"
}
EOF
}
## configure terraform state backend, customize as required
remote_state {
  backend = "gcs"
  generate = {
    path      = "backend.tf"
    if_exists = "overwrite"
  }
  config = {
    bucket = "my-foundation-states"
    prefix = "my-foundation/gcp/admin/organization"
  }
}

5. Deploying your foundation

By now we have everything assembled to deploy the module to our cloud foundation with a simple collie foundation deploy command

$ collie foundation deploy my-foundation
deploying (apply) foundations/my-foundation/platforms/gcp ...
deploying (apply) foundations/my-foundation/platforms/gcp ...
INFO[0000] The stack at /Users/jrudolph/dev/mc/internal-cloudfoundation/foundations/my-foundation/platforms/gcp will be processed in the following order for command apply:
Group 1
- Module /Users/jrudolph/dev/mc/internal-cloudfoundation/foundations/my-foundation/platforms/gcp/admin/organization

Are you sure you want to run 'terragrunt apply' in each folder of the stack described above? (y/n) y

...

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Next Steps

That's it, you now know how to create new kit modules and deploy them to your cloud platforms. Here's some next steps to go from here

  • Tutorial: document compliance
  • Create a separate dev/prod foundation for testing your kit modules
  • Leverage terragrunt to DRY your platform configuration code