Jenkins Parameterised Job with Terraform map variable - groovy

I'm trying to feed user-defined parameters from Jenkins into a job which runs terraform. I have a set of simple key/value vars, which I'm passing into the Terraform like this:
sh "terraform plan -var 'var1=${params.Var1}' -var 'var2=${params.Var2}' ...."
However, I also have a map var:
tags = {
Cluster = "mycluster"
Environment = "myenv"
Owner = "owner"
Product = "myproduct"
}
and I would like to feed some String Parameters into this map var. It looks like I need to define a groovy map and then render the map as JSON, so that I can have e.g. terraform -var 'tags={"Cluster":"mycluster","Environment":"dev"...}
My job is currently of the form:
pipeline {
agent {
...
}
options {
...
}
environment {
...
}
stages {
stage('MyStage') {
steps {
script {
sh "terraform ..."
}
}
}
}
}
I'me very new to Jenkinsfiles, so where would I define a map, if that's what was needed..?

Your attempt in the question is close, but Maps in Groovy are constructed with [:]. Therefore, you can construct a Jenkins Pipeline Map type output in JSON to be passed to a Terraform Map type like the following:
terraform -var "tags=${JsonOutput.toJson(['Cluster':'mycluster', 'Environment':'dev'...])}"
Given your use of the sh step method above, you will also need to escape your quotes to properly cast your strings appropriately as well.

Related

Trouble passing variable in terraform apply

I am using Terraform Cloud for my Backend with version 1.1
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
cloud {
hostname = "app.terraform.io"
organization = "MyOrg"
workspaces {
name = "MyWorkspace"
}
}
}
I have a variable in my HCL
variable "app_version" {
description = "The application version to deploy"
type = string
}
I am attempting to set it when I call terraform apply like so:-
terraform apply -var="app_version=v0.0.1"
I get the following error though.
1 error occurred:
* Invalid HCL in variable "app_version": At 1:15: Unknown token: 1:15 IDENT v0.0.1
What does this mean?
I think the Terraform docs for command line variables do a poor job of explaining that, but you basically need to wrap the value in double quotes. As the value passed after = is interpreted as Terraform expression, not constant (which opens up for other possibilities).
So, try:
terraform apply -var 'app_version="v0.0.1"'
Also notice there should be no = after -var
Grzegorz was correct but if you are on a windows machine you will need to escape the double quotes but adding a \ before each double quote.

How do I set a computed local variable

I have a tf.json file that declares a bunch of local variables. One of the variables is a array of complex objects like so:
{
"locals": [
{
"ordered_cache_behaviors": [
{
"path_pattern": "/poc-app-angular*",
"s3_target": "dev-ui-static",
"ingress": "external"
}
]
}
]
}
Here is what I want to do... instead of declaring the variable ordered_cache_behaviors statically in my file I want this to be a computed value. I will get this configuration from a S3 bucket and set the value here. So the value statically will only be an empty array [] that I will append to with a script after getting the data from S3.
This logic needs to execute each time before a terraform plan or terraform apply. What is the best way to do this? I am assuming I need to use a Provisioner to fire off a script? If so how do I then set the local variable here?
If the cache configuration data can be JSON-formatted, you may be able to use the s3_bucket_object datasource plus the jsondecode function as an alternative approach:
Upload your cache configuration data to the poc-app-cache-config bucket as cache-config.json, and then use the following to have Terraform download that file from S3 and parse it into your local ordered_cache_behaviors variable:
data "aws_s3_bucket_object" "cache_configuration" {
bucket = "poc-app-cache-config"
key = "cache-config.json" # JSON-formatted cache configuration map
}
...
locals {
ordered_cache_behaviors = jsondecode(aws_s3_bucket_object.cache_configuration.body)
}

terraform variable default value interpolation from locals

I have a use case where I need two AWS providers for different resources. The default aws provider is configured in the main module which uses another module that defines the additional aws provider.
By default, I'd like both providers to use the same AWS credentials unless explicitly overridden.
I figured I could do something like this. In the main module:
locals {
foo_cloud_access_key = aws.access_key
foo_cloud_secret_key = aws.secret_key
}
variable "foo_cloud_access_key" {
type = string
default = local.foo_cloud_access_key
}
variable "foo_cloud_secret_key" {
type = string
default = local.foo_cloud_secret_key
}
where variables foo_cloud_secret_key and foo_cloud_access_key would then be passed down to the child module like this:
module foobar {
...
foobar_access_key = var.foo_cloud_access_key
foobar_secret_key = var.foo_cloud_secret_key
...
}
Where module foobar would then configure its additional was provide with these variables:
provider "aws" {
alias = "foobar_aws"
access_key = var.foobar_access_key
secret_key = var.foobar_secret_key
}
When I run the init terraform spits out this error (for both variables):
Error: Variables not allowed
on variables.tf line 66, in variable "foo_cloud_access_key":
66: default = local.foo_cloud_access_key
Variables may not be used here.
Is it possible to achieve something like this in terraform or is there any other way to go about this?
Having complex, computed default values of variables is possible, but only with a workaround:
define a dummy default value for the variable, e.g. null
define a local variable, its value is either the value of the variable or the actual default value
variable "something" {
default = null
}
locals {
some_computation = ... # based on whatever data you want
something = var.something == null ? local.some_computation : var.something
}
And then only only use local.something instead of var.something in the rest of the terraform files.

Terraform empty and non-empty block map variables

I want to make backend-service by using Terraform. I use resource_type google_compute_backend_service
Now, i have 2 backend-services created by gcloud command. The one is using cdn_policy block and another one doesn't use cdn_policy.
The first backend-services tfstate is like
...
"cdn_policy": [
{
"cache_key_policy": [],
"signed_url_cache_max_age_sec": 3600
}
]
...
And the second backend-services is like
"cdn_policy": []
How to create the terraform script works for both of them ? So, terraform script can run for backend-services who has cdn_policy include with its block map and can also run for backend-services without cdn_policy.
In my idea, i can create 2 terraform scripts. First for cdn_policy and second without cdn_policy. But, i think this is not best practice.
If i put cdn_policy = [], it would result error An argument named "cdn_policy" is not expected here
You can use dynamic blocks to create a set of blocks based on a list of objects in an input variable: Dynamic Blocks
resource "google_compute_backend_service" "service" {
...
dynamic "cdn_policy" {
for_each = var.cdn_policy
content {
cache_key_policy = cdn_policy.value.cache_key_policy
signed_url_cache_max_age_sec = cdn_policy.value.signed_url_cache_max_age_sec
}
}
}

Terraform > Unescaped interpolations

What does this mean:
Note: Inline templates must escape their interpolations (as seen by the double
$ above). Unescaped interpolations will be processed before the template.
from https://www.terraform.io/docs/providers/template/index.html
The specific example is:
# Template for initial configuration bash script
data "template_file" "init" {
template = "$${consul_address}:1234"
vars {
consul_address = "${aws_instance.consul.private_ip}"
}
}
The ${} syntax is used by HCL for interpolation before the template rendering happens so if you were to just use:
# Template for initial configuration bash script
data "template_file" "init" {
template = "${consul_address}:1234"
vars {
consul_address = "${aws_instance.consul.private_ip}"
}
}
Terraform will attempt to find consul_address to template into the output instead of using the template variable of consul_address (which in turn is resolved to the private_ip output of the aws_instance.consul resource.
This is only an issue for inline templates and you don't need to do this for file based templates. For example this would be fine:
int.tpl
#!/bin/bash
echo ${consul_address}
template.tf
# Template for initial configuration bash script
data "template_file" "init" {
template = "${file("init.tpl")}"
vars {
consul_address = "${aws_instance.consul.private_ip}"
}
}
Of course if you then also needed to use the ${} syntax literally in your output template then you would need to double escape with something like this:
#!/bin/bash
CONSUL_ADDRESS_VAR=${consul_address}
echo $${CONSUL_ADDRESS_VAR}
This would then be rendered as:
#!/bin/bash
CONSUL_ADDRESS_VAR=1.2.3.4
echo ${CONSUL_ADDRESS_VAR}

Resources