Terraform modules


Figure 1040. Example: Creating a JSON host meta data file Slide presentation
# main.tf
resource "hcloud_server" "myServer" { ... }

resource "local_file" "hostdata" {
  content = templatefile("Tpl/hostdata.json", {
    ip4      = hcloud_server.helloServer.ipv4_address
    ip6      = hcloud_server.helloServer.ipv6_address
    location = hcloud_server.helloServer.location
  })
  filename   = "Gen/hostdata.json"
}
{ "network": {
     "ipv4": "${ip4}","ipv6": "${ip6}"},
  "location": "${location}"}
{
"network": {
  "ipv4": "157.180.78.16",
  "ipv6": "2a01:4f9:c013:be69::1"
  },
  "location": "hel1"
}

Figure 1041. Parent module / sub module layout Slide presentation
├── CreateHostByModule├── main.tf
├── Readme.md└── variables.tf
└── Modules
    └── HostMetaData
        ├── main.tf
        ├── outputs.tf 
        ├── providers.tf
        ├── Readme.md
        ├── Tpl
        │   └── hostdata.json
        └── variables.tf

Figure 1042. Parent module implementation Slide presentation
# variable.tf
variable "hcloud_token" { sensitive = true }
# main.tf
module "createHostAmongMetaData" {
  source       = "../Modules/HostMetaData"
  name         = "myServer"
  hcloud_token = var.hcloud_token
}

Figure 1043. Sub module implementation Slide presentation
# variable.tf
variable "name" {
  type = string
  nullable = false
}

variable "hcloud_token" { 
  type = string
  nullable = false
}
# main.tf
resource "hcloud_server" "newServer" { 
  name         = var.name
  image        = "debian-12"
  server_type  = "cx22" }

resource "local_file" "hostdata" {
  content = templatefile("${path.module}/Tpl/hostdata.json", {
    ip4   = hcloud_server.newServer.ipv4_address
    ip6   = hcloud_server.newServer.ipv6_address
    location = hcloud_server.newServer.location
  })
  filename        = "Gen/${var.name}.json" }

Figure 1044. Sub module, JSON template file Tpl/hostdata.json and result Slide presentation
{
  "network": {
    "ipv4": "${ip4}",
    "ipv6": "${ip6}"
  },
  "location": "${location}"
}
{
  "network": {
    "ipv4": "157.180.78.16",
    "ipv6": "2a01:4f9:c013:be69::1"
  },
  "location": "hel1"
}

Figure 1045. Parent module vs. sub module context Slide presentation
resource "local_file" "hostdata" {
  content = templatefile(
     "${path.module}/Tpl/hostdata.json" ...
}
├── CreateHostByModule
│   ├── main.tf
│   ├── Readme.md
│   └── variables.tf
│   └── Tpl└── hostdata.json
└── Modules
    └── HostMetaData
        ├── main.tf
        ├── outputs.tf 
        ├── providers.tf
        ├── Readme.md
        ├── Tpl└── hostdata.json
        └── variables.tf

exercise No. 17

A module for ssh host key handling

Q:

Avoiding ssh host key issues on login we require the creation of a known_hosts file among with each server being created:

157.180.78.16 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJDd+x7b80BM97rTU4RCM/CP+K7u4QAqx8ulDdXm9JDv

We assume this file being generated at gen/known_hosts. A corresponding ssh bash script shall be generated at the same level in bin/ssh:

#!/usr/bin/env bash

GEN_DIR=$(dirname "$0")/../gen

ssh -o UserKnownHostsFile="$GEN_DIR/known_hosts" devops@157.180.78.16 "$@"

# end of script

This way invoking ./bin/ssh will instruct the ssh client to read gen/known_hosts and thus avoiding unknown host key issues. The "$@" allows for optional user provided parameters.

For generating this script we start from a corresponding tpl/ssh.sh template file:

#!/usr/bin/env bash

GEN_DIR=$(dirname "$0")/../gen

ssh -o UserKnownHostsFile="$GEN_DIR/known_hosts" ${devopsUsername}@${ip} "$@"

# end of script

We then let Terraform use this template replacing ${devopsUsername} and ${ip} by your deployment's actual values.

As shown in Figure 1045, “Parent module vs. sub module context ” point to your child module's local tpl path by using ${path.module}:

resource "local_file" "ssh_script" {
  content = templatefile("${path.module}/tpl/ssh.sh", {
    ...
}

Likewise create a bin/scp script to be used for file transfer purposes as well:

#!/usr/bin/env bash

GEN_DIR=$(dirname "$0")/../gen

if [ $# -lt 2 ]; then
   echo usage: .../bin/scp ... devops@157.180.78.16 ...
else
   scp -o UserKnownHostsFile="$GEN_DIR/known_hosts" $@
fi

# end of script

Since copying objects requires at least a source and a destination parameter this script checks for the presence of at least two extra user supplied values by means of if [ $# -lt 2 ].

Your overall project layout may look like:

.
├── KnownHostsByModule
│   ├── bin
│   │   ├── scp
│   │   └── ssh
│   ├── gen
│   │   └── known_hosts
│   ├── main.tf
│   ├── network.tf
│   ├── outputs.tf
│   ├── providers.tf
│   ├── Readme.md
│   ├── tpl
│   │   └── userData.yml
│   └── variables.tf
└── Modules
    └── SshKnownHosts
        ├── main.tf
        ├── Readme.md
        ├── tpl
        │   ├── scp.sh
        │   └── ssh.sh
        └── variables.tf

Red indicates generated resources. When you are finished the following code should be sufficient for generating all required ssh login assets:

...
resource "hcloud_server" "helloServer" {
  name         = "hello"
  ...
}

module "createSshKnownHosts" {
  source = "../Modules/SshKnownHosts"
  loginUserName        = hcloud_ssh_key.loginUser.name
  serverNameOrIp       = hcloud_server.helloServer.ipv4_address
  serverHostPublicKey  = tls_private_key.host.public_key_openssh
}