In a previous post I wrote about a small script I call ebenv, which makes AWS Elastic Beanstalk environment properties available to shell commands as environment variables. See that post for the rationale behind this script.

Since that time, I came up with a simpler way to obtain the environment properties, and the new script does not depend on jq.

It turns out Elastic Beanstalk on Amazon Linux 2 stores its environment properties in a file at /opt/elasticbeanstalk/deployment/env, readable by root. Each property is defined on its own line, in shell variable syntax, like this:

RACK_ENV=production
MY_PROP=my-val

When I found this file, I realized I could simplify my ebenv script fairly significantly. All it needs to do is read this file and export each line, then run the supplied command.

There’s one caveat though—it needs to handle special shell characters, namely ', ", and $, because the values in the env file can contain these characters, which will cause problems with shell interpolation and expansion.

The first version prepended the environment variables to the command invocation. If our command was rails c, the command sent to the shell was RACK_ENV=production MY_PROP=my-val rails c. The updated version exports each property and then runs the command, which is simpler and easier to reason about.

Here’s the updated script, with a couple of strange line breaks so that it’s easier to read here.

#!/bin/bash

# This script runs the command (and args) passed to it
# as the EB AppUser, after making Elastic Beanstalk
# environment properties available to the command.
#
# Usage:
#
#   ebenv <command> [arguments]
#
# Example:
#
#   ebenv bin/rails c
#
# When using shell variables in a command, be sure to
# escape or properly quote them so they are expanded by
# ebenv rather than by the shell before the arguments
# reach ebenv. For example:
#
#   ebenv echo $RACK_ENV    #=> [blank]
#   ebenv echo "$RACK_ENV"  #=> [blank]
#   ebenv echo '$RACK_ENV'  #=> production
#   ebenv echo \$RACK_ENV   #=> production
#
# In the first two examples, $RACK_ENV is interpreted
# by the shell before calling ebenv. In the second two
# examples, $RACK_ENV is interpolated inside ebenv.

# exit if there's no command to run
if [[ "$#" == "0" ]]; then
  echo 'Usage: ebenv <command> [arguments]'
  exit 1
fi

# get the user to run the command as
EB_APP_USER="$(
  /opt/elasticbeanstalk/bin/get-config \
    platformconfig -k AppUser
)"

# export each key=value after escaping special chars
while read -r line; do
  key="$(cut -d= -f1 <<< "$line")"
  val="$(cut -d= -f2- <<< "$line" \
       | sed -r "/[\\$\"']/s/[\\$\"']/\\\\\\0/g")"
  export "$key=$val"
done < /opt/elasticbeanstalk/deployment/env

# run the command as the EB AppUser
eval "su -s /bin/bash -c \"$*\" \"$EB_APP_USER\""

Feel free to email me if you have suggestions for further improvement.