AWS Developer Tools Blog

AWS re:Invent 2015 and more

re:Invent has come and gone, and once again, we all had a blast. It’s always
great to meet with customers and discuss how they’re using the AWS SDKs and
CLIs. Keep the feedback coming.

At this year’s AWS CLI session at re:Invent, I had the opportunity to
address one of the topics that we previously hadn’t talked about much,
which is using the AWS CLI as a toolkit to create
shell scripts. As a CLI user, you may initially start off running a few
commands interactively in your terminal, but eventually you may want to combine
several AWS CLI commands to create some higher level of abstraction that’s
meaningful to you. This could include:

  • Combining a sequence of commands together: perform operation A, then B, then C.
  • Taking the output of one command and using it as input to another command.
  • Modifying the output to work well with other text processing tools such as
    grep, sed, and awk.

This talk discussed tips, techniques, and tools you can use to help you write shell scripts with the AWS CLI.

The video for the sesssion is now available online. The slides are also
available.

During the talk, I also mentioned that all the code I was showing would be
available on github. You can check out those code samples, along with some I
did not have time to go over, at the awscli-reinvent2015-samples repo.

In this post, and the next couple of posts, I’ll be digging into this
topic of shell scripting in more depth and covering things that I did
not have time to discuss in my re:Invent session.

Resource exists

I’d like to go over one of the examples that I didn’t have time to demo during
the re:Invent talk. During the breakout session, I showed how you can launch an
Amazon EC2 instance, wait until it’s running, and then automatically SSH to
the instance.

To do this, I made some assumptions that simplified the script,
particularly:

  • You have imported your id_rsa SSH key
  • You have a security group tagged with “dev-ec2-instance=linux”
  • You have an instance profile called “dev-ec2-instance”

What if these assumptions aren’t true?

Let’s work on a script called setup-dev-ec2-instance that checks for these
resources, and creates them for you, if necessary.

We’re going to use the resource exists pattern I talked about in this
part of the session. In pseudocode, here’s what this script will do:

if security group tagged with dev-ec2-instance=linux does not exist:
  show a list of security groups and ask which one to tag

if keypair does not exist in ec2
  offer to import the local ~/.ssh/id_rsa key for the user

if instance profile named "dev-ec2-instance" does not exist
  offer to create instance profile for user

Here’s what this script looks like the first time it’s run:

$ ./setup-dev-ec2-instance
Checking for required resources...

Security group not found.

default  sg-87190abc  None
ssh      sg-616a6abc  None
sshNAT   sg-91befabc  vpc-9ecd7abc
default  sg-f987fabc  vpc-17a20abc
default  sg-16befabc  vpc-9ecd7abc

Enter the security group ID to tag: sg-616a6abc
Tagging security group

~/.ssh/id_rsa key pair does not appear to be imported.
Would you like to import ~/.ssh/id_rsa.pub? [y/N]: y
{
    "KeyName": "id_rsa",
    "KeyFingerprint": "bb:3e:a9:82:50:32:6f:45:8a:f8:d4:24:0e:aa:aa:aa"
}

Missing IAM instance profile 'dev-ec2-instance'
Would you like to create an IAM instance profile? [y/N]: y
{
    "Role": {
        "AssumeRolePolicyDocument": {
             ...
        },
        "RoleId": "...",
        "CreateDate": "2015-10-23T21:24:57.160Z",
        "RoleName": "dev-ec2-instance",
        "Path": "/",
        "Arn": "arn:aws:iam::12345:role/dev-ec2-instance"
    }
}
{
    "InstanceProfile": {
        "InstanceProfileId": "...",
        "Roles": [],
        "CreateDate": "2015-10-23T21:25:02.077Z",
        "InstanceProfileName": "dev-ec2-instance",
        "Path": "/",
        "Arn": "arn:aws:iam::12345:instance-profile/dev-ec2-instance"
    }
}

Here’s what this script looks like if all of your resources are configured:

$ ./setup-dev-ec2-instance
Checking for required resources...

Security groups exists.
Key pair exists.
Instance profile exists.

The full script is available on github, but I’d like to highlight the use of
the resource_exists function, which is used for each of the three
resources:

echo "Checking for required resources..."
echo ""
# 1. Check if a security group is found.
if resource_exists "aws ec2 describe-security-groups 
  --filter Name=tag:dev-ec2-instance,Values=linux"; then
  echo "Security groups exists."
else
  echo "Security group not found."
  tag_security_group
fi

# 2. Make sure the keypair is imported.
if [ ! -f ~/.ssh/id_rsa ]; then
  echo "Missing ~/.ssh/id_rsa key pair."
elif has_new_enough_openssl; then
  fingerprint=$(compute_key_fingerprint ~/.ssh/id_rsa)
  if resource_exists "aws ec2 describe-key-pairs 
    --filter Name=fingerprint,Values=$fingerprint"; then
    echo "Key pair exists."
  else
    echo "~/.ssh/id_rsa key pair does not appear to be imported."
    import_key_pair
  fi
else
  echo "Can't check if SSH key has been imported."
  echo "You need at least openssl 1.0.0 that has a "pkey" command."
  echo "Please upgrade your version of openssl."
fi

# 3. Check that they have an IAM role called dev-ec2-instance.
#    We're using a local --query expression here.
if resource_exists "aws iam list-instance-profiles" 
  "InstanceProfiles[?InstanceProfileName=='dev-ec2-instance']"; then
  echo "Instance profile exists."
else
  echo "Missing IAM instance profile 'dev-ec2-instance'"
  create_instance_profile
fi

By using the resource_exists function, the bash script is fairly close to the
original pseudocode. In the third step, we’re using a local
–query expression
to check if we have an instance profile with the name dev-ec2-instance.

Creating an instance profile

In the preceding script, each of the three conditionals has a clause where we
offer to create the resource that does not exist. They all follow a similar
pattern, so we’ll take a look at just one for creating an instance profile.

The create_instance_profile has this high level logic:

  • Ask the user if they’d like us to create an instance profile.
  • If they don’t say yes, then exit.
  • First create an IAM role with a trust policy that allows the
    ec2.amazonaws.com service to assume the role
  • Next find the ARN for the “AdministratorAccess” managed policy.
  • Attach the role policy to the role we’ve created
  • Create an instance profile with the same name as the role
  • Finally, add the role to the instance profile we’ve created.

Here’s the code for that function:

create_instance_profile() {
  echo -n "Would you like to create an IAM instance profile? [y/N]: "
  read confirmation
  if [[ "$confirmation" != "y" ]]; then
    return
  fi
  aws iam create-role --role-name dev-ec2-instance 
    --assume-role-policy-document "$TRUST_POLICY" || errexit "Could not create Role"

  # Use a managed policy
  admin_policy_arn=$(aws iam list-policies --scope AWS 
      --query "Policies[?PolicyName=='AdministratorAccess'].Arn | [0]" 
      --output text | head -n 1)
  aws iam attach-role-policy 
    --role-name dev-ec2-instance 
    --policy-arn "$admin_policy_arn" || errexit "Could not attach role policy"

  # Then we need to create an instance profile from the role.
  aws iam create-instance-profile 
    --instance-profile-name dev-ec2-instance || 
    errexit "Could not create instance profile."
  # And add it to the role
  aws iam add-role-to-instance-profile 
    --role-name dev-ec2-instance 
    --instance-profile-name dev-ec2-instance || 
    errexit "Could not add role to instance profile."
}

In this function, we’re using –output text combined with –query
to retrieve the admin_policy_arn as a string that we can pass to
the subsequent attach-role-policy command. Using –output text
along with –query is one of the most powerful patterns you can use
when shell scripting with the AWS CLI.

See you at re:Invent 2016

I hope everyone enjoyed re:Invent 2015. We look forward to seeing you again in
2016!