We used to go through so much trouble to manipulate Amazon Web Services resources from the command-line. There were separate AWS command-line tools for each service, and each needed to be downloaded and had unique configuration, and each had its own unique output format. Eric Hammond wrote in detail about this. With the new awscli tool by Mitch Garnaat (of python boto fame) and his team at AWS, we have a single tool that does it all, uniformly, and can be simply configured. Thanks, Amazon!
But what if you need to use the awscli from within a Chef recipe? How do you install and configure the awscli with Chef and OpsWorks? I had this very same question on some recent projects. Here’s how to do this easily.
The awscli cookbook
I created the awscli cookbook to install and configure the awscli command line tools. Add this cookbook to your deployment and include the awscli::default
recipe in your run list, and watch it work.
This cookbook supports some common deployment scenarios:
- Installing the awscli “plain vanilla”, during the Chef “execute” stage.
- Installing the awscli during the Chef “compile” phase, so it can be called from within Chef.
- Configuring the awscli’s AWS access credentials.
- Configuring multiple configuration profiles.
It does not need to be run on an AWS instance – you can use this cookbook to install the awscli anywhere that you can run Chef.
See the cookbook’s README for more details.
Using awscli with IAM Roles
It’s not obvious from the awscli documentation, but when an instance has an associated IAM Role, the awscli will automatically read its credentials from the instance’s IAM metadata. This will often be the case when you are using OpsWorks to launch your instances. In these circumstances, you don’t need to configure any awscli access credentials. But, make sure you use IAM to grant the IAM Role permissions for the AWS API calls you’ll be making with the awscli.
Why use the awscli from within Chef?
Chef recipes are written in Ruby, and it’s easy to parse and manipulate JSON in Ruby. The awscli outputs its responses in JSON format – so it’s easy to parse those responses into your ruby code. The two make a very handy combination. For example, here is how to wait for a newly-created EBS volume to be available (so you can attach it to an instance without an error):
require 'json'
volume_id = "vol-12345678"
region = "us-east-1"
command = ""
case node[:platform]
when 'debian','ubuntu'
command << "/usr/local/bin/"
when 'redhat','centos','fedora','amazon','scientific'
command << "/usr/bin/"
end
command << "aws --region #{region} ec2 describe-volumes --volume-ids #{volume_id} --output json"
Chef::Log.info("Waiting for volume #{volume_id} to be available")
loop do
state=""
shell = Mixlib::ShellOut.new("#{command} 2>&1")
shell.run_command
if !shell.exitstatus
Chef::Log.fatal(output)
raise "#{command} failed:" + shell.stdout
end
jdoc = JSON.parse(shell.stdout)
vol_state = jdoc["Volumes"].first["State"]
Chef::Log.debug("#{volume_id} is #{vol_state}")
if vol_state=="available"
break
else
Chef::Log.debug("Waiting 5 more seconds for volume #{volume_id} to be ready...")
sleep 5
end
end
The last ten lines show how easy it is to access the awscli output within your ruby code (in your chef recipe).