hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io/
Other
42.52k stars 9.52k forks source link

template_file cannot render bash arrays variables #27706

Closed cd-bustamante closed 3 years ago

cd-bustamante commented 3 years ago

Terraform Version

Terraform v0.14.6
+ provider registry.terraform.io/hashicorp/google v3.5.0
+ provider registry.terraform.io/hashicorp/template v2.2.0

Terraform Configuration Files

main.tf

resource "google_compute_instance" "vm_instance" {
  name         = var.proxy_vm_name
  machine_type = var.proxy_vm_machine_type
  allow_stopping_for_update = var.allow_stopping_for_update
  deletion_protection = var.deletion_protection

  boot_disk {
    initialize_params {
      image = var.proxy_vm_boot_disk
    }
  }

  network_interface {
    network = var.proxy_vm_network
    subnetwork = var.proxy_vm_subnetwork
  }

  metadata_startup_script = data.template_file.init.rendered

  service_account {
    # Google recommends custom service accounts that have cloud-platform scope and permissions granted via IAM Roles.
    scopes = var.service_account_scope
  }

  labels = var.labels
}

provider.tf

terraform {
  required_providers {
    google = {
      source = "hashicorp/google"
      version = "3.5.0"
    }
  }
}
provider "google" {

  project = var.project
  region  = "northamerica-northeast1"
  zone    = "northamerica-northeast1-c"
}

datasource.tf

locals {

  count = length(var.iptables_in)
  in = join(",",var.iptables_in)
  out = join(",",var.iptables_out)

}

data "template_file" "init" {
  template = file("${path.module}/templates/startup_script.sh")
  vars = {
    counter = local.count
    incoming = local.in
    outgoing = local.out
  }
}

variables.tf

variable "project" {
  description = "(Required) GCP Project where the proxy Virtual machine will be created. The project VPC must have network access against the end point"
}

variable "proxy_vm_name" {
  description = "(Required) A unique name for the resource, required by GCE. Changing this forces a new resource to be created."
}

variable "proxy_vm_machine_type" {
  description = "(Required) The machine type to create."
  default = "e2-medium"
}

variable "allow_stopping_for_update" {
  description = "(Optional) If true, allows Terraform to stop the instance to update its properties. If you try to update a property that requires stopping the instance without setting this field, the update will fail."
  default = true
}

variable "deletion_protection" {
  description = "(Optional) Enable deletion protection on this instance. Defaults to false. Note: you must disable deletion protection before removing the resource (e.g., via terraform destroy), or the instance cannot be deleted and the Terraform run will not complete successfully."
  default = true
}

variable "proxy_vm_boot_disk" {
  description = "(Required) Boot disk image for the proxy vm"
  default = "debian-cloud/debian-10"
}

variable "proxy_vm_network" {
  description = "(Optional) The name or self_link of the network to attach this interface to. Either network or subnetwork must be provided."
}

variable "proxy_vm_subnetwork" {
  description = "(Optional) The name or self_link of the subnetwork to attach this interface to. The subnetwork must exist in the same region this instance will be created in. If network isn't provided it will be inferred from the subnetwork. Either network or subnetwork must be provided."
}

variable "service_account_scope" {
  description = "(Required) A list of service scopes. Both OAuth2 URLs and gcloud short names are supported. To allow full access to all Cloud APIs, use the cloud-platform scope."
  type        = list
  default = ["cloud-platform"]
}

variable "labels" {
  description = "A map of the tags to use on the resources that are deployed with this module."
  type        = map
  default     = {
    type = "proxy"
  }
}

variable "iptables_in" {
  description = "(Required) iptables configuration parameters list of listening ports"
  type = list
  default = ["6443","6444"]
}

variable "iptables_out" {
  description = "(Required) iptables configuration parameters list of destination_ips:destination_ports"
  type = list
  default = ["172.217.13.174:80","74.6.231.20:80"]
}

startup_script.sh

#!/bin/bash

#
# Echo commands as they are run, to make debugging easier.
# GCE startup script output shows up in "/var/log/syslog" .
#
set -x

#
# Stop apt-get calls from trying to bring up UI.
#
export DEBIAN_FRONTEND=noninteractive

#
# Make sure installed packages are up to date with all security patches.
#
apt-get -yq update
apt-get -yq upgrade
#
# Install Google's Stackdriver logging agent, as per
# https://cloud.google.com/logging/docs/agent/installation
#
curl -sSO https://dl.google.com/cloudagents/install-logging-agent.sh
bash install-logging-agent.sh
#
# Install Google's Stackdriver monitoring agent, as per
# https://cloud.google.com/monitoring/agent/installation
curl -sSO https://dl.google.com/cloudagents/add-monitoring-agent-repo.sh
bash add-monitoring-agent-repo.sh
apt-get -yq update
apt-get install -y stackdriver-agent

# IP tables configuration section
#

# Converting string into array for the proxy incoming and outgoing variables
echo "IP TABLES RESET"
my_inco="${incoming}"
declare -a iarray
iarray=($(echo $my_inco | tr "," " "))

my_outg="${outgoing}"
declare -a oarray
oarray=($(echo $my_outg | tr "," " "))

# Retrieving IP address of the VM proxy
IP="$(/usr/sbin/ifconfig | grep -A 1 'ens4' | tail -1 | awk '{print $2}')"
PRE="$(hostname)"

# Delete previous IP tables configuration
iptables -F
iptables -F -t nat
echo 1 >| /proc/sys/net/ipv4/ip_forward

c=0
max=$((${counter} - 1))
until [ $c -gt $max ]
do
  echo 'iptables -t nat -A PREROUTING -p tcp -d $IP --dport "${iarray[$c]}" -j DNAT --to "${oarray[$c]}"'
  echo "iptables -t nat -A POSTROUTING -j MASQUERADE"
  echo 'iptables -A FORWARD -p tcp -m tcp --dport "${iarray[$c]}" -j LOG --log-prefix "$PRE-${iarray[$c]}"'
  iptables -t nat -A PREROUTING -p tcp -d $IP --dport "${iarray[$c]}" -j DNAT --to "${oarray[$c]}"
  iptables -t nat -A POSTROUTING -j MASQUERADE
  iptables -A FORWARD -p tcp -m tcp --dport "${iarray[$c]}" -j LOG --log-prefix "$PRE-${iarray[$c]}"
  c=$((c+1))
done
echo "Finished the loop"

Debug Output

https://gist.github.com/cd-bustamante/c86f186df5c8cf310f65c2d5d6f7765c

Crash Output

N/A

Expected Behavior

When running the code, it should be able to create a new VM using template_file, hence the VM should be able to execute the starting up script.

Actual Behavior

Instead of creating the VM I am receiving the following error: Error: failed to render : :61,71-72: Invalid character; This character is not used within the language., and 8 other diagnostic(s)

on datasource.tf line 9, in data "template_file" "init": 9: data "template_file" "init" {

Steps to Reproduce

  1. terraform init
  2. terraform apply

    Additional Context

References

First I opened an issue against terraform-provider-google, reference below, they mentioned to follow up on this project with the following comment "This is about the script rendering via data source & template_file." https://github.com/hashicorp/terraform-provider-google/issues/8379

pselle commented 3 years ago

Hey there! the template_file data source is part of the archived template provider: https://registry.terraform.io/providers/hashicorp/template/latest.

I tried to pull out simplified reproduction case in this repo, to try and help out: https://github.com/danieldreier/terraform-issue-reproductions/tree/master/27706 but was unable to run into the error you have.

Ultimately though, this doesn't appear to be an error in Terraform, but this data source in an archived provider, and we recommend using the templatefile() function instead, generally. https://www.terraform.io/docs/language/functions/templatefile.html If you run into issues there, when using it as documented, please open another issue.

If you want to continue to debug this issue, I'd recommend using the Community Forums for prior art and assistance.

cd-bustamante commented 3 years ago

Thanks for your feedback @pselle , I'll review your input and if required will use the Community Forums. Have a great weekend!

cd-bustamante commented 3 years ago

@pselle also for your info the reproduction case you use it is missing a crucial step to reproduce the issue, the call of the variable, example below:

This is the string that triggers the error ==> "${iarray[$c]}"

 echo 'iptables -t nat -A PREROUTING -p tcp -d $IP --dport "${iarray[$c]}" -j DNAT --to "${oarray[$c]}"'
  echo "iptables -t nat -A POSTROUTING -j MASQUERADE"
  echo 'iptables -A FORWARD -p tcp -m tcp --dport "${iarray[$c]}" -j LOG --log-prefix "$PRE-${iarray[$c]}"'
ghost commented 3 years ago

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.