Search code examples
amazon-web-servicesterraforminstance

Error using user_data when creating instance in terrafom


Terraform version = 0.12

resource "aws_instance" "bespin-ec2-web" {
  ami = "ami-0bea7fd38fabe821a"
  instance_type = "t2.micro"
  vpc_security_group_ids = [aws_security_group.bespin-sg.id]
  subnet_id = aws_subnet.bespin-subnet-private-a.id
  associate_public_ip_address = true
  tags = {
    Name = "bespin-ec2-web-a"
  }
  user_data = <<EOF
   #!/bin/bash
   USERS="bespin"
   GROUP="bespin"
   for i in $USERS; do
   adduser ${i} -g ${GROUP};
   echo ${i}:${i}1! | chpasswd;

   cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config_old
   sed -i 's/PasswordAuthentication no/#PasswordAuthentication no/' /etc/ssh/sshd_config
   sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
   systemctl restart sshd
EOF
}

Why do I get an error when running terraform plan?

Error: Invalid reference

on instance.tf line 15, in resource "aws_instance" "bespin-ec2-web": 15: adduser ${i} -g ${GROUP};

A reference to a resource type must be followed by at least one attribute access, specifying the resource name.

Error: Invalid reference

on instance.tf line 15, in resource "aws_instance" "bespin-ec2-web": 15: adduser ${i} -g ${GROUP};

A reference to a resource type must be followed by at least one attribute access, specifying the resource name.


Solution

  • The direct reason for the error here is that ${ ... } is Terraform's string template interpolation syntax, so Terraform is understanding ${GROUP} as an attempt to interpolate the expression GROUP, which is not a valid Terraform expression. As the error message implies, Terraform is understanding GROUP as a resource type, as if this were the first part of a reference like aws_instance.foo.

    The smallest change to fix this is to escape the ${ ... } sequence by adding an additional $ in front, so that the literal ${GROUP} can pass through to the user_data value:

      user_data = <<-EOF
       #!/bin/bash
       USERS="bespin"
       GROUP="bespin"
       for i in $USERS; do
       adduser ${i} -g $${GROUP};
       echo $${i}:$${i}1! | chpasswd;
    
       cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config_old
       sed -i 's/PasswordAuthentication no/#PasswordAuthentication no/' /etc/ssh/sshd_config
       sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config
       systemctl restart sshd
      EOF
    

    Notice that the escaping is needed only for ${ ... }, not for sequences like $USERS: Terraform does not interpret a single dollar sign as a special character, so that will always pass through literally.


    This particular user_data expression is totally static and doesn't include any intended Terraform template syntax, so it's possible to avoid the escaping altogether by moving the user_data value into a separate file and use the file function to take the content of that file literally (no template interpretation at all):

      user_data = file("${path.module}/user_data.sh")
    

    Sometimes the user_data does need to include data interpolated from elsewhere in the Terraform configuration, mixed with shell syntax. It's possible to use escaping as described above to do that, but as the needed string gets more complicated it can be useful to separate the part containing the interpolations from the literal part in order to get the best of both worlds and avoid the escaping within the main body of the script:

      user_data = <<-EOT
        EXAMPLE_VARIABLE='${var.example}'
        ${file("${path.module}/user_data.sh")}
      EOT