Terraform Pihole Records

guides/terraform/cover.png

This Guide setups terraform and a CICD pipeline to Control DNS records using Gitlab remote state backend and Gitops

Requirements

  • Terraform CLI
  • Gitlab Runner
  • 2 Instances of pihole
  • Runner can talk to Piholes on network


Setup Main

Lets configure the main.tf file and add the required providers. We will be using Gitlab for a state backend

main.tf

 1terraform {
 2  required_providers {
 3    pihole = {
 4      source = "ryanwholey/pihole"
 5    }
 6    kubectl = {
 7      source  = "gavinbunney/kubectl"
 8      version = ">= 1.7.0"
 9    }
10  }
11
12  backend "http" {
13  }
14}

Locals

The locals.tf file needs to be configured with the following

Change to you pihole ips and correct email and the domain ip is the one you gave traefik. mine is 10.10.1.60

1locals {
2  pihole_primary_url   = "http://x.x.x.x:8888" # IF you have it on a different port
3  pihole_secondary_url = "http://x.x.x.x" # If you didnt change the port setting
4  domain_name          = "domain.name"
5  domain_ip            = "x.x.x.x"
6}
7 
FieldDescription
pihole_primary_urlUrl to the admin console of your piholes
domain_nameThe Domain Name to be used for creating the A record
domain_ipThe target the A record will point to, example our traefik reverse proxy load balancer ip

Variables

add this to variables.tf

1variable "PIHOLE_PASSWORD" {
2  type      = string
3  sensitive = true
4}

Providers

Create a providers.tf

 1provider "pihole" {
 2  url      = local.pihole_primary_url # PIHOLE_URL
 3  password = var.PIHOLE_PASSWORD      # PIHOLE_PASSWORD
 4}
 5
 6provider "pihole" {
 7  url      = local.pihole_secondary_url # PIHOLE_URL
 8  password = var.PIHOLE_PASSWORD        # PIHOLE_PASSWORD
 9  alias    = "secondary"
10}

Modules

Now lets create a modules reference for our code to run

dns.tf

 1module "pihole_1_io" {
 2  source      = "./modules/pihole"
 3  domain_name = local.domain_name
 4  domain_ip = local.domain_ip
 5  providers = {
 6    pihole = pihole
 7  }
 8}
 9
10module "pihole_2_io" {
11  source      = "./modules/pihole"
12  domain_name = local.domain_name
13  domain_ip = local.domain_ip
14  providers = {
15    pihole = pihole.secondary
16  }
17}

Pihole Module

Create Folders modules/pihole/

create files

  • a-records.tf
  • cname.tf
  • main.tf
  • outputs.tf
  • variables.tf

a-recoords.tf

point a record to our load balancer

1resource "pihole_dns_record" "mantisd-io" {
2  domain = var.domain_name
3  ip = var.domain_ip
4}

cname.tf

Create records to both our piholes admin dashboard

1resource "pihole_cname_record" "pihole" {
2  domain = "pihole.${var.domain_name}"
3  target = "${var.domain_name}"
4}
5resource "pihole_cname_record" "pihole-2" {
6  domain = "pihole-2.${var.domain_name}"
7  target = "${var.domain_name}"
8}

main.tf

 1terraform {
 2  required_version = ">=1.1.4"
 3  required_providers {
 4    pihole = {
 5      source = "ryanwholey/pihole"
 6    }
 7  }
 8}
 9
10
11data "pihole_cname_records" "cname_records" {
12  
13}
14
15data "pihole_dns_records" "dns_records" {
16  
17}

outputs.tf

1output "dns_records" {
2  value = data.pihole_dns_records.dns_records
3}
4
5output "cname_record" {
6  value = data.pihole_cname_records.cname_records
7}

variables.tf

1variable "domain_name" {
2    type = string
3}
4
5variable "domain_ip" {
6    type = string
7}

Add new Records to cname.tf and a-records.tf

CICD

Now lets create a pipeline to automatically deploy new routes in the main branch when they are commited to our git repo

gitlab-ci.yml

 1
 2stages:
 3  - pre
 4  - build
 5  - deploy
 6
 7default:
 8  tags:
 9    - your
10    - runner
11    - tags
12
13variables:
14  TF_ROOT: $CI_PROJECT_DIR  # The relative path to the root directory of the Terraform project
15  TF_STATE_NAME: pihole-records  # The name of the state file used by the GitLab Managed Terraform state backend
16  TF_IMAGE: registry.gitlab.com/gitlab-org/terraform-images/releases/terraform:1.1.9
17  ARTIFACT_VERSION: "1.0.0"
18  REALEASE_VERSION: $ARTIFACT_VERSION
19  TF_ADDRESS: "https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}"
20
21
22tf_init_var_check:
23    stage: .pre 
24    script:
25      - if [ -z "${TF_ADDRESS}"]; then echo "ERROR - TF_ADDRESS does not exist, add to .gitlab-ci.yml"; exit 1; fi
26      - if [ -z "${TF_USERNAME}"]; then echo "ERROR - TF_USERNAME does not exist, add to .gitlab-ci.yml"; exit 1; fi
27      - if [ -z "${TF_PASSWORD}"]; then echo "ERROR - TF_PASSWORD does not exist, add to .gitlab-ci.yml"; exit 1; fi
28
29.terraform_init:
30  script:
31  - echo $GITLAB_USER_NAME
32  - rm -rf .terraform
33  - terraform --version
34  - |
35      terraform init \
36      -backend-config=address=${TF_ADDRESS} \
37      -backend-config=lock_address=${TF_ADDRESS}/lock \
38      -backend-config=unlock_address=${TF_ADDRESS}/lock \
39      -backend-config=username=${TF_USERNAME} \
40      -backend-config=password=${TF_PASSWORD} \
41      -backend-config=lock_method=POST \
42      -backend-config=unlock_method=DELETE \
43      -backend-config=retry_wait_min=5      
44
45tf_base_config_var_check:
46    stage: .pre 
47    script:
48      - if [ -z "${TF_IMAGE}"]; then echo "ERROR - TF_IMAGE does not exist, add to .gitlab-ci.yml"; exit 1; fi
49      - if [ -z "${TF_ROOT}"]; then echo "ERROR - TF_ROOT does not exist, add to .gitlab-ci.yml"; exit 1; fi
50
51.tf_base_config:
52    image: ${TF_IMAGE}
53    cache:
54      key: "${TF_ROOT}"
55      paths:
56        - ${TF_ROOT}/.terraform/
57
58
59plan:
60  stage: build
61  extends:
62      - .tf_base_config
63  script:
64    - !reference [.terraform_init, script]
65    - terraform plan -out "plans"
66  artifacts:
67    paths:
68      - plans
69    expire_in: 20m
70
71
72deploy:
73  stage: deploy
74  extends:
75      - .tf_base_config
76  script:
77    - !reference [.terraform_init, script]
78    - terraform apply -input=false "plans"
79  only:
80    - master

Now When changes are added to master branch the pipeline will execute and deploy to your piholes