Skip to content

Commit

Permalink
inventory.yml.tftpl default template for edb-ansible and related te…
Browse files Browse the repository at this point in the history
…mplating changes (#70)

Templates can now be passed in through the CLI with `--user-templates`
or `ET_USER_TEMPLATES` as a list of template files or a directory with
template files. Users can still make use of the infrastructure file to
specify templates paths but they will be copied over to the projects
template directory and removed from the final terraform variables file
as it is unused within terraform.
All templates will be rendered if they are within the projects
`template` directory, if it ends with the `.tftpl` extension.
Preferably, templates should not cause an infrastructure configuration
to fail if is provided as a sample or a default.

`inventory.yml.tftpl` has been added as an example which works with
`edb-ansible`. To better work alongside ansible, additional outputs were
passed through with our machines so they could be added to the inventory
file, including `ansible_user` and `ansible_ssh_private_key_file`.

Fixes: AWS/Azure/Gcloud - `setup_volume.sh` script updated to handle dropping of `/dev/` from device name when checking with nvme cli tools.
  • Loading branch information
bryan-bar authored Jun 26, 2023
1 parent 6c14679 commit a1278a1
Show file tree
Hide file tree
Showing 25 changed files with 606 additions and 61 deletions.
4 changes: 2 additions & 2 deletions edbterraform/CLI.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import json
import textwrap

from edbterraform import __project_name__
from edbterraform import __dot_project__
from edbterraform.utils.logs import logger

Version = namedtuple('Version', ['major', 'minor', 'patch'])
Expand Down Expand Up @@ -133,7 +133,7 @@ class TerraformCLI:
arch_alias = {
'x86_64': 'amd64',
}
DEFAULT_PATH=f'{Path.home()}/.{__project_name__}'
DEFAULT_PATH = __dot_project__

def __init__(self, binary_dir=None):
self.bin_dir = binary_dir if binary_dir else TerraformCLI.DEFAULT_PATH
Expand Down
2 changes: 2 additions & 0 deletions edbterraform/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
__version__ = "1.3.0"
__project_name__ = 'edb-terraform'
from pathlib import Path
__dot_project__ = f'{Path.home()}/.{__project_name__}'
25 changes: 23 additions & 2 deletions edbterraform/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from edbterraform.lib import generate_terraform
from edbterraform.CLI import TerraformCLI
from edbterraform import __project_name__
from edbterraform import __dot_project__
from edbterraform.utils import logs

ENVIRONMENT_PREFIX = 'ET_' # Appended to allow overrides of defaults
Expand All @@ -35,6 +35,7 @@ class ArgumentConfig:
choices: list = None
required: bool = None
action: str = None
nargs: str = None

def __post_init__(self) -> None:
# Allow overriding of variables with environment variables
Expand Down Expand Up @@ -109,6 +110,17 @@ def __getitem__(self, key):
help="Project path. Default: %(default)s",
)

UserTemplatesPath = ArgumentConfig(
names = ['--user-templates',],
metavar='USER_TEMPLATE_FILES',
dest='user_templates',
type=Path,
nargs='+',
required=False,
default=[f'{__dot_project__}/templates',],
help="Users can pass in a list of template files or template directories, which will be rendered with the servers output. Default: %(default)s",
)

InfrastructureFilePath = ArgumentConfig(
names = ['--infra-file',],
metavar='INFRA_FILE_YAML',
Expand Down Expand Up @@ -177,7 +189,7 @@ def __getitem__(self, key):
names = ['--log-directory',],
dest='log_directory',
required=False,
default=f'{Path.home()}/.{__project_name__}/logs',
default=f'{__dot_project__}/logs',
help='''
Default: %(default)s
'''
Expand Down Expand Up @@ -220,6 +232,7 @@ class Arguments:
LogFile,
LogDirectory,
LogStdout,
UserTemplatesPath,
]],
'setup': ['Install needed software such as Terraform inside a bin directory\n',[
BinPath,
Expand Down Expand Up @@ -287,6 +300,13 @@ def get_env(self, key, default=None):
Get environment variables which are available after parse_args() is called
'''
return getattr(self.env, key, default)

def get_kwargs(self):
'''
Returns the parsed arguments as a dictionary.
_get_kwargs not used as it returns a list of dictionary items.
'''
return self.env.__dict__.copy()

def process_args(self):
logs.setup_logs(
Expand All @@ -311,6 +331,7 @@ def process_args(self):
self.get_env('csp'),
self.get_env('run_validation'),
self.get_env('bin_path'),
self.get_env('user_templates')
)
return outputs

Expand Down
2 changes: 1 addition & 1 deletion edbterraform/data/templates/aws/main.tf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ resource "local_file" "user_templates" {
output_name default made through jinja2 templating with edb-terraform: 'servers'
terraform output -json <output_name>
*/
for_each = toset(module.spec.base.templates)
for_each = fileset(path.root, "templates/*.tftpl")
content = templatefile(each.value, local.servers)
filename = "${abspath(path.root)}/${trimsuffix(basename(each.value), ".tftpl")}"
file_permission = "0600"
Expand Down
2 changes: 1 addition & 1 deletion edbterraform/data/templates/azure/main.tf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ resource "local_file" "user_templates" {
output_name default made through jinja2 templating with edb-terraform: 'servers'
terraform output -json <output_name>
*/
for_each = toset(module.spec.base.templates)
for_each = fileset(path.root, "templates/*.tftpl")
content = templatefile(each.value, local.servers)
filename = "${abspath(path.root)}/${trimsuffix(basename(each.value), ".tftpl")}"
file_permission = "0600"
Expand Down
2 changes: 1 addition & 1 deletion edbterraform/data/templates/gcloud/main.tf.j2
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ resource "local_file" "user_templates" {
output_name default made through jinja2 templating with edb-terraform: 'servers'
terraform output -json <output_name>
*/
for_each = toset(module.spec.base.templates)
for_each = fileset(path.root, "templates/*.tftpl")
content = templatefile(each.value, local.servers)
filename = "${abspath(path.root)}/${trimsuffix(basename(each.value), ".tftpl")}"
file_permission = "0600"
Expand Down
217 changes: 217 additions & 0 deletions edbterraform/data/templates/user/inventory.yml.tftpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# Original filename: inventory.yml.tftpl
# This inventory file is meant to be used alongside edb-ansible: https://github.com/EnterpriseDB/edb-ansible
# and relies on tags set within the infrastructure file to properly generate.
# Sample infrastructure file for this template can be found under infrastructure-examples/aws-edb-ra-3.yml
#
# As a sample template, it will not cause terraform CLI and so it might be invalid.
# To fail upon errors, remove any try() function.
---
all:
children:
# PEM servers: tags.type.pem_server
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "pem_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
pemserver:
hosts:
%{ if lower(try(values.tags.reference_architecture, "")) == "edb-always-on" ~}
pemserver${ 1 + tonumber(try(values.tags.index, -2)) }:
%{ else ~}
pemserver${ 1 + tonumber(values.tags.index) }.${ values.tags.cluster_name }.internal:
%{ endif ~}
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ endfor ~}

# Barman servers: tags.type.barman_server
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "barman_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
barmanserver:
hosts:
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "barman_server"} ~}
%{ if lower(try(values.tags.reference_architecture, "")) == "edb-always-on" ~}
barmandc${ 1 + tonumber(try(values.tags.index -2)) }:
%{ else ~}
barmanserver${ 1 + tonumber(try(values.tags.index, "")) }.${ values.tags.cluster_name }.internal:
%{ endif ~}
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "pem_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
pem_agent: true
pem_server_private_ip: ${ values.private_ip }
%{ endfor ~}
%{ endfor ~}

# Postgres servers: tags.type.postgres_server
%{ for key,values in { for key,values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "postgres_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
primary:
hosts:
%{ if lower(try(values.tags.pg_type, "")) == "epas" ~}
epas${ 1 + tonumber(values.tags.index) }.${ values.tags.cluster_name }.internal:
%{ else ~}
pgsql${ 1 + tonumber(values.tags.index) }.${ values.tags.cluster_name }.internal:
%{ endif ~}
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "barman_server" && tonumber(try(values.tags.index, -1)) == 0} ~}
barman: true
barman_server_private_ip: ${ values.private_ip }
barman_backup_method: postgres
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "dbt2_client" } ~}
%{ if tonumber(try(values.tags.index, -1)) == 0 ~}
dbt2: true
%{ endif ~}
dbt2_client_private_ip${ 1 + tonumber(try(values.tags.index, -2)) }: ${ values.private_ip }
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "hammerdb_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
hammerdb: true
hammerdb_server_private_ip: ${ values.private_ip }
%{ endfor ~}
%{ if tobool(try(values.tags.pooler_local, false)) && lower(try(values.tags.pg_pooler, "")) == "pgbouncer" ~}
pgbouncer: true
%{ endif ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "pem_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
pem_agent: true
pem_server_private_ip: ${ values.private_ip }
%{ endfor ~}
standby:
hosts:
%{ endfor ~}
%{ for key,values in { for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "postgres_server" && tonumber(try(values.tags.index, 0)) != 0 } ~}
%{ if lower(try(values.tags.pg_type, "")) == "epas" ~}
epas${ 1 + tonumber(values.tags.index) }.${ values.tags.cluster_name }.internal:
%{ else ~}
pgsql${ 1 + tonumber(values.tags.index) }.${ values.tags.cluster_name }.internal:
%{ endif ~}
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "barman_server" && tonumber(try(values.tags.index, -1)) == 0} ~}
barman: true
barman_server_private_ip: ${ values.private_ip }
barman_backup_method: postgres
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "dbt2_client" } ~}
dbt2_client_private_ip${ 1 + tonumber(try(values.tags.index, -2)) }: ${ values.private_ip }
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "hammerdb_server" && tonumber(try(values.tags.index, -1)) == 0} ~}
hammerdb: true
hammerdb_server_private_ip: ${ values.private_ip }
%{ endfor ~}
%{ if tobool(try(values.tags.pooler_local, false)) && lower(try(values.tags.pg_pooler, "")) == "pgbouncer" ~}
pgbouncer: true
%{ endif ~}
replication_type: ${ values.tags.replication_type }
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "postgres_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
upstream_node_private_ip: ${ values.private_ip }
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "pem_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
pem_agent: true
pem_server_private_ip: ${ values.private_ip }
%{ endfor ~}
%{ endfor ~}

# BDR servers: tags.type.bdr_server
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "bdr_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
primary:
hosts:
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "bdr_server" } ~}
%{ if lower(try(values.tags.pg_type, "")) == "epas" ~}
epas${ 1 + tonumber(try(values.tags.index, -2)) ~}:
%{ else ~}
pgsql${ 1 + tonumber(try(values.tags.index, -2)) ~}:
%{ endif ~}
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "pem_server" && tonumber(try(values.tags.index), -1) == 0 } ~}
pem_agent: true
pem_server_private_ip: ${ values.private_ip }
%{ endfor ~}
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "bdr_witness_server" } ~}
%{ if lower(try(values.tags.pg_type, "")) == "epas" ~}
epas${ 1 + tonumber(try(values.tags.index, -2)) + length([ for key, values in servers.machines: key if lower(try(values.tags.type, "")) == "bdr_server"]) }:
%{ else ~}
pgsql${ 1 + tonumber(try(values.tags.index,-2)) + length([ for key, values in servers.machines: key if lower(try(values.tags.type, "")) == "bdr_server"]) }:
%{ endif ~}
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ for key, values in { for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "pem_server" && tonumber(try(values.tags.index), -1) == 0 } ~}
pem_agent: true
pem_server_private_ip: ${ values.private_ip }
%{ endfor ~}
%{ endfor ~}

# Pool connection servers: tags.type.pooler_server
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "pooler_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
${ values.tags.pooler_type }:
hosts:
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "pooler_server" } ~}
%{ if length([ for key, values in servers.machines: key if lower(try(values.tags.type, "")) == "bdr_server"]) > 0 ~}
pgbouncer${ 1 + tonumber(try(values.tags.index, -2)) }:
%{ else ~}
${ lower(try(values.tags.pooler_type, "")) }-${ 1 + tonumber(try(values.tags.index, -2)) }.${ values.tags.cluster_name }.internal:
%{ endif ~}
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ for key,values in { for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "postgres_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
primary_private_ip: ${ values.private_ip }
%{ endfor ~}
%{ for key, values in {for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "pem_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
pem_agent: true
pem_server_private_ip: ${ values.private_ip }
%{ endfor ~}
%{ endfor ~}

# DBT2 servers: tags.type.dbt2_client
%{ for key, values in { for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "dbt2_client" && tonumber(try(values.tags.index, -1)) == 0 } ~}
dbt2_client:
hosts:
%{ endfor ~}
%{ for key, values in { for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "dbt2_client" } ~}
dbt2_client${ 1 + tonumber(try(values.tags.index, -2)) }.${ values.tags.cluster_name }.internal:
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ endfor ~}

# DBT2 servers: tags.type.dbt2_driver
%{ for key, values in { for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "dbt2_driver" && tonumber(try(values.tags.index, -1)) == 0 } ~}
dbt2_driver:
hosts:
%{ endfor ~}
%{ for key, values in { for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "dbt2_driver" } ~}
dbt2_driver${ 1 + tonumber(try(values.tags.index, -2)) }.${ values.tags.cluster_name }.internal:
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ endfor ~}

# HammerDB servers: tags.type.hammerdb_server
%{ for key, values in { for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "hammerdb_server" && tonumber(try(values.tags.index, -1)) == 0 } ~}
hammerdbserver:
hosts:
%{ endfor ~}
%{ for key, values in { for key, values in servers.machines: key=>values if lower(try(values.tags.type, "")) == "hammerdb_server" } ~}
hammerdbserver${ 1 + tonumber(try(values.tags.index, -2)) }.${ values.tags.cluster_name }.internal:
ansible_host: ${ values.public_ip }
private_ip: ${ values.private_ip }
ansible_user: ${ values.operating_system.ssh_user }
ansible_ssh_private_key_file: ${ values.operating_system.ssh_private_key_file }
%{ endfor ~}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ done
# NVME device.
for NVME_DEVICE in $(sudo ls /dev/nvme*n*); do
EBS_DEVICE=$(sudo nvme id-ctrl -v ${NVME_DEVICE} | grep "0000:" | awk '{ print $18 }' | sed 's/["\.]//g')
if [ "$EBS_DEVICE" = "${TARGET_EBS_DEVICES[0]}" ]; then

# /dev/ might be dropped at times so we need to check both cases
if [ "$EBS_DEVICE" = "${TARGET_EBS_DEVICES[0]}" ] || [ "/dev/$EBS_DEVICE" = "${TARGET_EBS_DEVICES[0]}" ]; then
TARGET_NVME_DEVICE=${NVME_DEVICE}
break
fi
Expand Down
10 changes: 6 additions & 4 deletions edbterraform/data/terraform/aws/modules/specification/files.tf
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ locals {
var.spec.ssh_key.public_path != null ||
var.spec.ssh_key.private_path != null ? 1 : 0
)
private_ssh_path = "${abspath(path.root)}/${var.spec.ssh_key.output_name}"
public_ssh_path = "${abspath(path.root)}/${var.spec.ssh_key.output_name}.pub"
}

resource "tls_private_key" "default" {
Expand All @@ -18,23 +20,23 @@ resource "tls_private_key" "default" {
resource "local_sensitive_file" "default_private" {
count = local.ssh_user_count

filename = "${abspath(path.root)}/${var.spec.ssh_key.output_name}"
filename = local.private_ssh_path
file_permission = "0600"
content = try(tls_private_key.default[0].private_key_openssh, "")
}

resource "local_file" "default_public" {
count = local.ssh_user_count

filename = "${abspath(path.root)}/${var.spec.ssh_key.output_name}.pub"
filename = local.public_ssh_path
file_permission = "0644"
content = try(tls_private_key.default[0].public_key_openssh, "")
}

resource "local_sensitive_file" "private_key" {
count = local.ssh_keys_count

filename = "${abspath(path.root)}/${var.spec.ssh_key.output_name}"
filename = local.private_ssh_path
file_permission = "0600"
source = var.spec.ssh_key.private_path

Expand All @@ -59,7 +61,7 @@ resource "local_sensitive_file" "private_key" {
resource "local_file" "public_key" {
count = local.ssh_keys_count

filename = "${abspath(path.root)}/${var.spec.ssh_key.output_name}.pub"
filename = local.public_ssh_path
file_permission = "0644"
source = var.spec.ssh_key.public_path

Expand Down
Loading

0 comments on commit a1278a1

Please sign in to comment.