Skip to content

Instantly share code, notes, and snippets.

@shalomb
Last active September 6, 2021 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save shalomb/abad3287c16dba246ca550b8fb8ce824 to your computer and use it in GitHub Desktop.
Save shalomb/abad3287c16dba246ca550b8fb8ce824 to your computer and use it in GitHub Desktop.
Terraform hackery - null_resource/local-exec + external/program data sources
# Demo of the external data source
# https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data_source
data "external" "passwd_field" {
program = [ "./passwd_field.sh" ]
query = {
user = var.user
field = var.field
file = local.target_file
}
depends_on = [
null_resource.copy
]
}
# Demo of the null_resource provider
# https://stackoverflow.com/a/68052896/742600
resource "null_resource" "copy" {
provisioner "local-exec" {
command = "cp ${var.source_file} ${local.target_file}"
}
# because we rm target_file, we need to ensure this step always runs
triggers = {
always_run = timestamp()
}
}
# Demo of the null_resource provider
resource "null_resource" "remove" {
provisioner "local-exec" {
command = "rm ${local.target_file}"
}
depends_on = [
data.external.passwd_field
]
# because we rm target_file, we need to ensure this step always runs
triggers = {
always_run = timestamp()
}
}
output "passwd_field" {
value = data.external.passwd_field.result
description = "Value of the field extracted from /etc/paswd"
sensitive = false
depends_on = [
data.external.passwd_field
]
}
variable "source_file" {
type = string
default = "/etc/passwd"
}
resource "random_string" "mktemp" {
length = 10
special = false
}
locals {
target_file = "${random_string.mktemp.result}-${replace(timestamp(), "/[^0-9]/", "")}.tmp"
}
variable "user" {
type = string
default = "root"
validation {
condition = can(regex("^.{4}$", var.user))
error_message = "Invalid user specified, must be 4 chars long!"
}
}
variable "field" {
type = string
default = 7
validation {
condition = var.field > 0 && var.field < 8
error_message = "Invalid field range, must be >0 and <8!"
}
}
090190720 f - Σ terraform output -json
{
"passwd_field": {
"sensitive": false,
"type": [
"map",
"string"
],
"value": {
"field": "7",
"file": "OsrUKa6yYw-20210906190455.tmp",
"value": "/bin/bash"
}
}
}
#!/bin/bash
# Exit with an error if any commands fail
set -eu -o pipefail
tmpfile="$(mktemp)"
trap "rm '$tmpfile'" EXIT
# Extract the input parameters
jq -r '@sh "
user=\(.user)
field=\(.field)
file=\(.file)
"' > "$tmpfile"
source "$tmpfile"
# Do some processing
value=$( awk -vuser="$user" -F: '$1 == user{ print $'"$field"' }' "$file")
# Construct a return object
jq -n \
--arg field "$field" \
--arg value "$value" \
--arg file "$file" \
'{ field: $field,
value: $value,
file: $file
}'
@shalomb
Copy link
Author

shalomb commented Sep 6, 2021

Demonstrating how to make use of

  • null_resource provisioners using hand-rolled scripts for local-exec provisioning
  • programs (scripts) as sources of data

It's not a recommended terraform approach and possibly why I've not had to use it (ever) - but it seems to make some problems easier.

Σ  TF_VAR_user=root terraform apply; terraform output -json | jq -Scer '.passwd_field.value.value'
/bin/bash
Σ  TF_VAR_user=tcpdump terraform apply; terraform output -json | jq -Scer '.passwd_field.value.value'
/usr/sbin/nologin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment