Is there any way for external data sources to find if this is a plan, apply or destro operation - terraform

I have a Python program that is being called by an "External Data Source" explained here:
https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data_source
The code looks like something like this:
data "external" "example" {
program = ["python", "${path.module}/example-data-source.py"]
query = {
# arbitrary map from strings to strings, passed
# to the external program as the data query.
id = "abc123"
}
}
Is there technique or workaround for my external Python program to know which terraform operation (plan, apply or destroy) is calling the external data?
Is there any way for a hcl code to find what operation is it being called under?
Update #1
I understand that we can pass parameters as query argument.
The question is that is there any way a Terraform script know which Terraform operation type is it being called under? so that I have the operation (Apply/Destroy) in a terraform variable then pass it as query argument to the Python program?

The external data source isn't able to infer what the Terraform operation type is as it is only sent the data in the query argument, as mentioned and shown in the data source's documentation.
As an example, if we look at the example in the documentation:
#!/bin/bash
# Exit if any of the intermediate steps fail
set -e
# Extract "foo" and "baz" arguments from the input into
# FOO and BAZ shell variables.
# jq will ensure that the values are properly quoted
# and escaped for consumption by the shell.
eval "$(jq -r '#sh "FOO=\(.foo) BAZ=\(.baz)"')"
# Placeholder for whatever data-fetching logic your script implements
FOOBAZ="$FOO $BAZ"
# Safely produce a JSON object containing the result value.
# jq will ensure that the value is properly quoted
# and escaped to produce a valid JSON string.
jq -n --arg foobaz "$FOOBAZ" '{"foobaz":$foobaz}'
We can see that it's reading the foo and baz keys from whatever is sent to the program via stdin.
If we ran this in the most simple way we can see that it does as we expect:
data "external" "example" {
program = ["sh", "${path.module}/example-data-source.sh"]
query = {
foo = "abc1234"
baz = "123abc"
}
}
output "example" {
value = data.external.example
}
Gives the following output:
Outputs:
example = {
"id" = "-"
"program" = tolist([
"sh",
"./example-data-source.sh",
])
"query" = tomap({
"baz" = "123abc"
"foo" = "abc1234"
})
"result" = tomap({
"foobaz" = "abc1234 123abc"
})
"working_dir" = tostring(null)
}
Just to check that there's nothing sneaky there that Terraform communicates over stdin we can modify the script to log out to a file what is passed on stdin (note that this will break the example script so that jq no longer sees the input from Terraform):
#!/bin/bash
# Exit if any of the intermediate steps fail
set -e
# Log stdin inputs to an output file so we only output the formatted
# JSON on stdout for the external data source to work correctly.
cat - >> log.out
# Extract "foo" and "baz" arguments from the input into
# FOO and BAZ shell variables.
# jq will ensure that the values are properly quoted
# and escaped for consumption by the shell.
eval "$(jq -r '#sh "FOO=\(.foo) BAZ=\(.baz)"')"
# Placeholder for whatever data-fetching logic your script implements
FOOBAZ="$FOO $BAZ"
# Safely produce a JSON object containing the result value.
# jq will ensure that the value is properly quoted
# and escaped to produce a valid JSON string.
jq -n --arg foobaz "$FOOBAZ" '{"foobaz":$foobaz}'
And then if we run a plan we can see just the expected JSON in the log.out file:
{"baz":"123abc","foo":"abc1234"}
If you could access the operation type in your Terraform code instead then you could pass that in as a parameter to your external data source but that's also not exposed anywhere that you can access unfortunately.
It's also worth mentioning that as a data source, it's expected to be side effect free to run the script. This means it shouldn't really matter what operation Terraform is running when it invokes your data source. If you need to do something else that isn't side effect free then you might need to consider another option such as a provisioner, potentially attached to a null_resource but all of these options are meant to be last resorts where you need an escape hatch. Most of the time it's better to either raise a feature request in the appropriate provider or to call these things separately and orchestrate them together via a wrapper script or some orchestration software that calls them in succession.

Haven't tested this, but you should be able to get terraform's pid with os.getppid(). Then you can get more information with psutil (available in pip), /proc (linux only) or subprocessing a ps command. Finding the verb on the command line may be brittle, but depending on context, it can probably work.
That said, if you need to do this, you should reconsider how your code is organized. A pure data source shouldn't care.

Related

Can you use a templatefile as an output value in terraform without the EOF and EOT artifacts?

I'm building out my infrastructure in terraform and one of the outputs that I would like is the ssh config formatted for the newly created server.
I created a template file:
Host ${serverLabel}
HostName ${hostname}
User ${user}
IdentityFile ${identityFile}
And used it in an output:
output "ssh-config" {
description = "The new server's ssh config"
value = templatefile("${path.module}/ssh-config.tpl", {
serverLabel=aws_instance.minecraft_ec2.tags.Name
hostname= aws_instance.minecraft_ec2.public_ip
user= "ec2-user",
identityFile = "~/.ssh/minecraft_key"
})
}
Which works fine except for one little hiccup. I end up with some heredoc tags in the output:
It's not the end of the world because I can just copy formatted text between the heredocs, but is there a way of using a template file as an output value without getting the tags??
Update
In reply to Mark B's question:
What happens if you run terraform output --raw?
Apparently when I output it with the --raw flag the heredoc tags go away
Which feels weird, but reading the description for the output command I guess makes sense:
-raw - If specified, Terraform will convert the specified output value to a string and print that string directly to the output, without any special formatting. This can be convenient when working with shell scripts, but it only supports string, number, and boolean values. Use -json instead for processing complex data types.
So I guess the heredoc tags are considered "special formatting". Makes sense, thought it was not what I expected.
It appears this is how Terraform now outputs multiline values, similar to how regular values had quotes added to them a few versions ago. To get the values without quotes or heredoc tags wrapping them, use terraform output --raw.

Get specific value out of the Terraform state file

I've deployed my infra using Terraform and I noticed that I have some interesting information in the state (terraform.tfstate) file of terraform which I would like to extract. For example
$ terraform state show 'packet_device.worker'
id = 6015bg2b-b8c4-4925-aad2-f0671d5d3b13
billing_cycle = hourly
created = 2015-12-17T00:06:56Z
facility = ewr1
...
which I would like to transform somehow to
$ terraform state show 'packet_device.worker.id'
6015bg2b-b8c4-4925-aad2-f0671d5d3b13
But adding the id at the end doesn't seem to work. Any suggestions how I can achieve this behaviour?
Terraform state show command is used to retrieve all the attributes of a given resource and you won't be able to fetch a single attribute from it as the argument is resource ADDRESS and is used to refer a resource specifically. Documented in https://www.terraform.io/docs/internals/resource-addressing.html
What you can do is store the resource attribute in output value and use the command
terraform output {output-valaue-extractor}
Refer: https://www.terraform.io/docs/configuration/outputs.html
You can utilize terraform show -json and jq to get a specific value out of a Terraform state file.
terraform show -json <state_file> | jq '.values.root_module.resources[] | select(.address=="<terraform_resource_name>") | .values.<property_name>'
You have a state file named terraform.tfstate and a Terraform resource as packet_device.worker and you want to get id. Then it would be as follows:
terraform show -json terraform.tfstate | jq '.values.root_module.resources[] | select(.address=="packet_device.worker") | .values.id'
terraform.tfstate also can be omitted since it is a default name for a state file.
The primary way to export information from a Terraform configuration is to declare Output Values in your root module. You can then access them using terraform output once the apply has completed. If you need that information in a machine-readable way, you can alternatively run terraform output -json from the consuming program and parse the output as JSON.
If you are in an unusual situation where you need programmatic access to all values in the state (for example, if you were implementing some sort of generic Terraform state visualization tool) then you can instead use terraform show -json, which will print out all of the data from the state in a JSON format.
If you are accessing only specific values, perhaps to integrate with some other system in an automation solution, I'd recommend using explicit Output Values because then it's explicit to future maintainers what the interface with the caller is, and so they are less likely to accidentally break the caller by e.g. refactoring the packet_device.worker resource into a child module, which would cause it to appear in a different place in the state. The usual assumption is that the resources inside a module are an implementation detail of that module and thus that you can safely refactor them as needed as long as the output values remain unchanged.
If you want to get the exact value and are willing to install jq, the other answers here are great!
If you're looking for a quick answer to manually copy/paste, etc., piping to grep does the trick.
ex:
terraform state show 'packet_device.worker' | grep "id"
which would show the relevant line(s), like:
id = 6015bg2b-b8c4-4925-aad2-f0671d5d3b13

Unix & Linux ksh shell: How to use eval in CASE expression when variable is derived from a string plus another variable? [duplicate]

How can I retrieve a bash variable value if I have the variable name as string? var1="this is the real value"
a="var1"
Do something to get value of var1 just using variable a.
Context:
I have some AMI's (Amazon Machine Image) and I want to fire up a few instances of each AMI. As soon as they finish booting, I want to setup each instance according to its AMI type. I don't want to bake lots of scripts or secret keys inside any AMI so I prepared a generalized startup script and I put it on S3 with a publicly accessible link. In rc.local I put small piece of code which fetches the startup script and executes it. This is all I have in the AMIs. Then each AMI accesses a common configuration script which is applicable to all AMIs and special setup scripts for each. These scripts are private and require a signed URL to access them.
So now, when I fire an instance of an AMI (my_private_ami_1), I pass a signed URL for one more file presented on S3 which contains signed URL for all private scripts in terms of key/value pair.config_url="http://s3.amazo.../config?signature"
my_private_ami_1="http://s3.amazo.../ami_1?signature"
...
When the startup script runs, it downloads the above file and source's it. Then it checks for its AMI type and picks the correct setup script for itself.
ami\_type=GET AMI TYPE #ex: sets ami\_type to my\_private\_ami\_1
setup\_url=GET THE SETUP FILE URL BASED ON AMI\_TYPE # this is where this problem arises
So now I can have a generic code which can fire instances irrespective of their AMI types and instances can take care of themselves.
You can use ${!a}:
var1="this is the real value"
a="var1"
echo "${!a}" # outputs 'this is the real value'
This is an example of indirect parameter expansion:
The basic form of parameter expansion is ${parameter}. The value of
parameter is substituted.
If the first character of parameter is an exclamation point (!), it
introduces a level of variable indirection. Bash uses the value of the
variable formed from the rest of parameter as the name of the
variable; this variable is then expanded and that value is used in the
rest of the substitution, rather than the value of parameter itself.
X=foo
Y=X
eval "Z=\$$Y"
sets Z to foo.
For my fellow zsh users, the way to accomplish the same thing as the accepted answer is to use:
echo ${(P)a} # outputs 'this is the real value'
It is appropriately called Parameter name replacement
This forces the value of the parameter name to be interpreted as a
further parameter name, whose value will be used where appropriate.
Note that flags set with one of the typeset family of commands (in
particular case transformations) are not applied to the value of name
used in this fashion.
If used with a nested parameter or command substitution, the result of
that will be taken as a parameter name in the same way. For example,
if you have ‘foo=bar’ and ‘bar=baz’, the strings ${(P)foo},
${(P)${foo}}, and ${(P)$(echo bar)} will be expanded to ‘baz’.
Likewise, if the reference is itself nested, the expression with the
flag is treated as if it were directly replaced by the parameter name.
It is an error if this nested substitution produces an array with more
than one word. For example, if ‘name=assoc’ where the parameter assoc
is an associative array, then ‘${${(P)name}[elt]}’ refers to the
element of the associative subscripted ‘elt’.
Modified my search keywords and Got it :). eval a=\$$a Thanks for your time.
In bash 4.3+, you can use declare -n:
#!/usr/bin/env bash
var="this is the real value"
var_name="var"
declare -n var_ref=$var_name
echo "${var_ref}"
Had the same issue with arrays, here is how to do it if you're manipulating arrays too :
array_name="ARRAY_NAME"
ARRAY_NAME=("Val0" "Val1" "Val2")
ARRAY=$array_name[#]
echo "ARRAY=${ARRAY}"
ARRAY=("${!ARRAY}")
echo "ARRAY=${ARRAY[#]}"
echo "ARRAY[0]=${ARRAY[0]}"
echo "ARRAY[1]=${ARRAY[1]}"
echo "ARRAY[2]=${ARRAY[2]}"
This will output :
ARRAY=ARRAY_NAME[#]
ARRAY=Val0 Val1 Val2
ARRAY[0]=Val0
ARRAY[1]=Val1
ARRAY[2]=Val2
In bash 4.3, the '-v' test for set variables was introduced. At the same time, 'nameref' declaration was added. These two features together with the indirection operator (!) enable a simplified version of the previous example:
get_value()
{
declare -n var_name=$1
if [[ -v var_name ]]
then
echo "${var_name}"
else
echo "variable with name <${!var_name}> is not set"
fi
}
test=123
get_value test
123
test="\$(echo \"something nasty\")"
get_value test
$(echo "something nasty")
unset test
get_value test
variable with name <test> is not set
As this approach eliminates the need for 'eval', it is safer.
This code checked under bash 5.0.3(1).
modern shells already support arrays( and even associative arrays). So please do use them, and use less of eval.
var1="this is the real value"
array=("$var1")
# or array[0]="$var1"
then when you want to call it , echo ${array[0]}
Based on the answer: https://unix.stackexchange.com/a/111627
###############################################################################
# Summary: Returns the value of a variable given it's name as a string.
# Required Positional Argument:
# variable_name - The name of the variable to return the value of
# Returns: The value if variable exists; otherwise, empty string ("").
###############################################################################
get_value_of()
{
variable_name=$1
variable_value=""
if set | grep -q "^$variable_name="; then
eval variable_value="\$$variable_name"
fi
echo "$variable_value"
}
test=123
get_value_of test
# 123
test="\$(echo \"something nasty\")"
get_value_of test
# $(echo "something nasty")
VALUE=$(eval "echo \$$SOME_VAR_NAME")
Example
SSH_KEY_FILE_PATH_FOO="/tmp/key"
SSH_KEY_FILE_PATH_VAR_NAME_PREFIX="SSH_KEY_FILE_PATH"
SSH_KEY_FILE_PATH_VAR_NAME_SUFFIX="FOO"
SSH_KEY_FILE_PATH=$(eval "echo \$${SSH_KEY_FILE_PATH_VAR_NAME_PREFIX}_${SSH_KEY_FILE_PATH_VAR_NAME_SUFFIX}")
echo "$SSH_KEY_FILE_PATH"
/tmp/key

returning values in a bash function

I'm working with a growing bash script and within this script I have a number of functions. One of these functions is supposed to return a variables value, but I am running into some issues with the syntax. Below is an example of the code.
ShowTags() {
local tag=0
read tag
echo "$tag"
}
selected_tag=$(ShowTags)
echo "$selected_tag"
pulled this code from a Linux Journal article, but the problem is it doesn't seem to work, or perhaps it does and im missing something. Essentially whenever the function is called the script hangs up and does not output anything, I need to CTRL+C to drop back to CLI.
The article in question is below.
http://www.linuxjournal.com/content/return-values-bash-functions
So my question is this the proper way to return a value? Is there a better or more dependable way of doing this? And if there is please give me an example so I can figure this out without using global variables.
EDIT:
The behavior of this is really getting to me now. I am using the following script.
ShowTags() {
echo "hi"
local tag=0
read tag
echo "$tag"
}
selected_tag=$(ShowTags)
echo "$selected_tag
Basically what happens is bash will act as if the read command is taking place before the echo tag at the top of the function. As soon as I pass something to read though it will run the top echo, and complete the rest of the script. I am not sure why this is happening. This is exactly what is happening in my main script.
Change echo "hi" to echo "hi" >/dev/tty.
The reason you're not seeing it immediately is that $(ShowTags) captures all the standard output of the function, and that gets assigned to selected_tag. So you don't see any of it until you echo that variable.
By redirecting the prompt to /dev/tty, it's always displayed immediately on the terminal, not sent to the function's stdout, so it doesn't get captured by the command substitution.
You are trying to define a function with Name { ... ]. You have to use name() { ... }:
ShowTags() { # add ()
local tag=0
read tag
echo "$tag"
} # End with }
selected_tag=$(ShowTags)
echo "$selected_tag"
It now lets the user type in a string and have it written back:
$ bash myscript
hello world # <- my input
hello world # script's output
You can add a prompt with read -p "Enter tag: " tag to make it more obvious when to write your input.
As #thatotherguy pointed out, your function declaration syntax is off; but I suspect that's a transcription error, as if it was wrong in the script you'd get different problems. I think what's going on is that the read tag command in the function is trying to read a value from standard input (by default that's the terminal), and pausing until you type something in. I'm not sure what it's intended to do, but as written I'd expect it to pause indefinitely until something's typed in.
Solution: either type something in, or use something other than read. You could also add a prompt (read -p "Enter a tag: " tag) to make it more clear what's going on.
BTW, I have a couple of objections to the linux journal article you linked. These aren't relevant to your script, but things you should be aware of.
First, the function keyword is a nonstandard bashism, and I recommend against using it. myfunc() ... is sufficient to introduce a function definition.
Second, and more serious, the article recommends using eval in an unsafe way. Actually, it's really hard to use eval safely (see BashFAQ #48). You can improve it a great deal just by changing the quoting, and even more by not using eval at all:
eval $__resultvar="'$myresult'" # BAD, can evaluate parts of $myresult as executable code
eval $__resultvar='"$myresult"' # better, is only vulnerable to executing $__resultvar
declare $__resultvar="$myresult" # better still
See BashFAQ #6 for more options and discussion.

How to get a variable value if variable name is stored as string?

How can I retrieve a bash variable value if I have the variable name as string? var1="this is the real value"
a="var1"
Do something to get value of var1 just using variable a.
Context:
I have some AMI's (Amazon Machine Image) and I want to fire up a few instances of each AMI. As soon as they finish booting, I want to setup each instance according to its AMI type. I don't want to bake lots of scripts or secret keys inside any AMI so I prepared a generalized startup script and I put it on S3 with a publicly accessible link. In rc.local I put small piece of code which fetches the startup script and executes it. This is all I have in the AMIs. Then each AMI accesses a common configuration script which is applicable to all AMIs and special setup scripts for each. These scripts are private and require a signed URL to access them.
So now, when I fire an instance of an AMI (my_private_ami_1), I pass a signed URL for one more file presented on S3 which contains signed URL for all private scripts in terms of key/value pair.config_url="http://s3.amazo.../config?signature"
my_private_ami_1="http://s3.amazo.../ami_1?signature"
...
When the startup script runs, it downloads the above file and source's it. Then it checks for its AMI type and picks the correct setup script for itself.
ami\_type=GET AMI TYPE #ex: sets ami\_type to my\_private\_ami\_1
setup\_url=GET THE SETUP FILE URL BASED ON AMI\_TYPE # this is where this problem arises
So now I can have a generic code which can fire instances irrespective of their AMI types and instances can take care of themselves.
You can use ${!a}:
var1="this is the real value"
a="var1"
echo "${!a}" # outputs 'this is the real value'
This is an example of indirect parameter expansion:
The basic form of parameter expansion is ${parameter}. The value of
parameter is substituted.
If the first character of parameter is an exclamation point (!), it
introduces a level of variable indirection. Bash uses the value of the
variable formed from the rest of parameter as the name of the
variable; this variable is then expanded and that value is used in the
rest of the substitution, rather than the value of parameter itself.
X=foo
Y=X
eval "Z=\$$Y"
sets Z to foo.
For my fellow zsh users, the way to accomplish the same thing as the accepted answer is to use:
echo ${(P)a} # outputs 'this is the real value'
It is appropriately called Parameter name replacement
This forces the value of the parameter name to be interpreted as a
further parameter name, whose value will be used where appropriate.
Note that flags set with one of the typeset family of commands (in
particular case transformations) are not applied to the value of name
used in this fashion.
If used with a nested parameter or command substitution, the result of
that will be taken as a parameter name in the same way. For example,
if you have ‘foo=bar’ and ‘bar=baz’, the strings ${(P)foo},
${(P)${foo}}, and ${(P)$(echo bar)} will be expanded to ‘baz’.
Likewise, if the reference is itself nested, the expression with the
flag is treated as if it were directly replaced by the parameter name.
It is an error if this nested substitution produces an array with more
than one word. For example, if ‘name=assoc’ where the parameter assoc
is an associative array, then ‘${${(P)name}[elt]}’ refers to the
element of the associative subscripted ‘elt’.
Modified my search keywords and Got it :). eval a=\$$a Thanks for your time.
In bash 4.3+, you can use declare -n:
#!/usr/bin/env bash
var="this is the real value"
var_name="var"
declare -n var_ref=$var_name
echo "${var_ref}"
Had the same issue with arrays, here is how to do it if you're manipulating arrays too :
array_name="ARRAY_NAME"
ARRAY_NAME=("Val0" "Val1" "Val2")
ARRAY=$array_name[#]
echo "ARRAY=${ARRAY}"
ARRAY=("${!ARRAY}")
echo "ARRAY=${ARRAY[#]}"
echo "ARRAY[0]=${ARRAY[0]}"
echo "ARRAY[1]=${ARRAY[1]}"
echo "ARRAY[2]=${ARRAY[2]}"
This will output :
ARRAY=ARRAY_NAME[#]
ARRAY=Val0 Val1 Val2
ARRAY[0]=Val0
ARRAY[1]=Val1
ARRAY[2]=Val2
In bash 4.3, the '-v' test for set variables was introduced. At the same time, 'nameref' declaration was added. These two features together with the indirection operator (!) enable a simplified version of the previous example:
get_value()
{
declare -n var_name=$1
if [[ -v var_name ]]
then
echo "${var_name}"
else
echo "variable with name <${!var_name}> is not set"
fi
}
test=123
get_value test
123
test="\$(echo \"something nasty\")"
get_value test
$(echo "something nasty")
unset test
get_value test
variable with name <test> is not set
As this approach eliminates the need for 'eval', it is safer.
This code checked under bash 5.0.3(1).
modern shells already support arrays( and even associative arrays). So please do use them, and use less of eval.
var1="this is the real value"
array=("$var1")
# or array[0]="$var1"
then when you want to call it , echo ${array[0]}
Based on the answer: https://unix.stackexchange.com/a/111627
###############################################################################
# Summary: Returns the value of a variable given it's name as a string.
# Required Positional Argument:
# variable_name - The name of the variable to return the value of
# Returns: The value if variable exists; otherwise, empty string ("").
###############################################################################
get_value_of()
{
variable_name=$1
variable_value=""
if set | grep -q "^$variable_name="; then
eval variable_value="\$$variable_name"
fi
echo "$variable_value"
}
test=123
get_value_of test
# 123
test="\$(echo \"something nasty\")"
get_value_of test
# $(echo "something nasty")
VALUE=$(eval "echo \$$SOME_VAR_NAME")
Example
SSH_KEY_FILE_PATH_FOO="/tmp/key"
SSH_KEY_FILE_PATH_VAR_NAME_PREFIX="SSH_KEY_FILE_PATH"
SSH_KEY_FILE_PATH_VAR_NAME_SUFFIX="FOO"
SSH_KEY_FILE_PATH=$(eval "echo \$${SSH_KEY_FILE_PATH_VAR_NAME_PREFIX}_${SSH_KEY_FILE_PATH_VAR_NAME_SUFFIX}")
echo "$SSH_KEY_FILE_PATH"
/tmp/key

Resources