2

I am just getting my feet wet with Terraform but I don't see an obvious way to keep me from repeating myself.

I have a bunch of zones in Cloudlfare that I want to manage. These zones will all have very similar settings and I want my .tf files to be both short and readable.

Lets say I have example1.com example2.com example3.com... I add them with the following code:

resource "cloudflare_zone" "example1"{
    zone = "example1.com"
}

resource "cloudflare_zone" "example2"{
    zone = "example2.com"
}

resource "cloudflare_zone" "example3"{
    zone = "example3.com"
}

So far so good.

Now I want to apply some identical settings to all my zones using the cloudflare_zone_settings_override provider.

Looking at the documentation this is straight forward for one zone. But I would rather not have to do this for each zone:

resource "cloudflare_zone_settings_override" "example1" {
    name = "$example1.com"
    settings {
        brotli = "on"
        security_level = "high"
        opportunistic_encryption = "on"
        automatic_https_rewrites = "on"
        mirage = "on"
        waf = "on"
        minify {
            css = "on"
            js = "off"
            html = "off"
        }
    }
}

What is the best way to apply these to all (or some) of the zones in Cloudflare?

Thanks

ScruffyDan
  • 23
  • 2

3 Answers3

2

Terraform has a couple of ways of minimising repeating yourself.

You could loop through a list, using the count meta-parameter creating resources as necessary:

variable "zones" {
  type = "list"
}

resource "cloudflare_zone" "zones" {
  count = "${length(var.zones)}"
  zone  = "${var.zones[count.index]}"
}

resource "cloudflare_zone_settings_override" "settings" {
  count = "${length(var.zones)}"
  name  = "${cloudflare_zone.zones.*.zone[count.index]}"

  settings {
    brotli                   = "on"
    security_level           = "high"
    opportunistic_encryption = "on"
    automatic_https_rewrites = "on"
    mirage                   = "on"
    waf                      = "on"

    minify {
      css  = "on"
      js   = "off"
      html = "off"
    }
  }
}

Note the use of "${cloudflare_zone.zones.*.zone[count.index]}" in the zone settings for the zone name. This will make sure that Terraform knows that it needs to create the Cloudflare zone before creating the zone settings override rather than not seeing a dependency between the two and trying to create them both at the same time which may potentially fail as the zone is not yet created when Terraform attempts to create the zone settings override.

Or you could move the zone configuration out to a module, allowing you to abstract things, providing a more constrained amount of control of a resource to the module caller:

modules/cloudflare-zone/main.tf

variable "zone" {}

variable "waf" {
  default = "on"
}

resource "cloudflare_zone" "zone" {
  zone = "${var.zone}"
}

resource "cloudflare_zone_settings_override" "settings" {
  name = "${cloudflare_zone.zone.zone}"

  settings {
    brotli                   = "on"
    security_level           = "high"
    opportunistic_encryption = "on"
    automatic_https_rewrites = "on"
    mirage                   = "on"
    waf                      = "${var.waf}"

    minify {
      css  = "on"
      js   = "off"
      html = "off"
    }
  }
}

This module then has a required variable of zone and an optional, defaulted variable of waf that controls whether to enable the WAF for the zone. All the other options are set for the caller so the module can simply be called multiple times like this:

module "cloudflare_zone_example1" {
  source = "path/to/module"
  zone   = "example1.com"
}

module "cloudflare_zone_example2" {
  source = "path/to/module"
  zone   = "example2.com"
  waf    = "off"
}
ydaetskcoR
  • 53,225
  • 8
  • 158
  • 177
  • Thanks. I will try out this as soon as I can (probably tomorrow). In general is it better to use count or the module method? Which one would scale better as my infrastructure grows? Thanks – ScruffyDan Dec 31 '18 at 18:18
  • It largely depends on how you want to separate responsibilities. If you'd like to minimise blast radius or only make changes to one zone at a time you'll want to wrap it in a module. If you don't care about that then the loop is simpler. – ydaetskcoR Dec 31 '18 at 20:30
  • I went ahead and used a module. This worked very well. Thanks! – ScruffyDan Jan 01 '19 at 21:44
  • guys can someone just explain how the directory structure is? I have created "modules/cloudflare-zone/main.tf" but in which dir and file should be the "module "cloudflare_zone_example1""... and variable "zones" { type = "list" } – Rami Sep 09 '22 at 16:28
0

In order to avoid repetition of Terraform code, you could:

  • create a module and use it three times
  • set the count property to three and create a list of your domain names

Example for the second solution:

local {
    domain_names = [
        example1.com,
        example2.com,
        example3.com
    ]
}

resource "cloudflare_zone" "these_zones" {
    count = "${length(local.domain_names)}"
    zone = "${element(local.domain_names, count.index)}"
}

resource "cloudflare_zone_settings_override" "these_zones_settings" {
    count = "${length(local.domain_names)}"
    name = "${element(local.domain_names, count.index)}"
    settings {
        brotli = "on"
        security_level = "high"
        opportunistic_encryption = "on"
        automatic_https_rewrites = "on"
        mirage = "on"
        waf = "on"
        minify {
            css = "on"
            js = "off"
            html = "off"
        }
    }
}

PS: This code is written for terraform < 0.12

Quentin Revel
  • 1,400
  • 8
  • 11
  • Looks like you and @ydaetskcoR came up with the same 2 solutions. I will have to try out both solutions and see which one works best for me. Thanks – ScruffyDan Dec 31 '18 at 18:23
  • I am sorry I can't answer you comment in @ydaetskcoR thread. I would use the count for simplicity but the module for better reusability. If your infrastructure grows quickly, take the time to create a module now. – Quentin Revel Dec 31 '18 at 18:28
0

Alternative solution using this module. Here is a ready-to-use example:

terraform {
  required_providers {
    cloudflare = {
      source  = "cloudflare/cloudflare"
      version = ">= 3.12.1"
    }
  }
}

variable "cloudflare_api_token" {
  type      = string
  sensitive = true
}

provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

locals {
  # All your zones go here
  zones = ["acme.com", "example.com"]
}

module "zones" {
  source  = "registry.terraform.io/alex-feel/zone/cloudflare"
  version = "1.7.0"

  for_each = toset(local.zones)

  zone = each.value

  brotli                   = "on"
  security_level           = "high"
  opportunistic_encryption = "on"
  automatic_https_rewrites = "on"
  mirage                   = "on"
  waf                      = "on"
  minify = {
    css = "on"
  }
}
fallincode
  • 379
  • 4
  • 14