Using the SDK for Ruby on a Vagrant Instance - AWS OpsWorks

Using the SDK for Ruby on a Vagrant Instance

Important

The AWS OpsWorks Stacks service reached end of life on May 26, 2024 and has been disabled for both new and existing customers. We strongly recommend customers migrate their workloads to other solutions as soon as possible. If you have questions about migration, reach out to the AWS Support Team on AWS re:Post or through AWS Premium Support.

This topic describes how a recipe running on a Vagrant instance can use the AWS SDK for Ruby to download a file from Amazon S3. Before starting, you must first have a set of AWS credentials—an access key and a secret access key—that allow the recipe to access Amazon S3.

Important

We strongly recommend that you do not use root account credentials for this purpose. Instead, create a user with an appropriate policy and provide those credentials to the recipe.

Be careful not to put credentials—even IAM user credentials—in a publicly accessible location, such as by uploading a file containing the credentials to a public GitHub or Bitbucket repository. Doing so exposes your credentials and could compromise your account's security.

Recipes running on an EC2Amazon EC2 instance can use an even better approach, an IAM role, as described in Using the SDK for Ruby on an AWS OpsWorks Stacks Linux Instance.

Content delivered to Amazon S3 buckets might contain customer content. For more information about removing sensitive data, see How Do I Empty an S3 Bucket? or How Do I Delete an S3 Bucket?.

If you don't already have an appropriate user, you can create one as follows. For more information, see What is IAM.

Warning

IAM users have long-term credentials, which presents a security risk. To help mitigate this risk, we recommend that you provide these users with only the permissions they require to perform the task and that you remove these users when they are no longer needed.

To create an IAM user
  1. Sign in to the AWS Management Console and open the IAM console at https://console.aws.amazon.com/iam/.

  2. In the navigation pane, choose Users and, if necessary, choose Add users to create a new administrative user.

  3. On the Set permissions page, choose Attach policies directly.

  4. Type S3 in the Permissions policies search box to display the Amazon S3 policies.

    Choose AmazonS3ReadOnlyAccess. If you prefer, you can specify a policy that grants broader permissions, such as AmazonS3FullAccess, but standard practice is to grant only those permissions that are required. In this case, the recipe will only be downloading a file, so read-only access is sufficient.

  5. Choose Next.

  6. Choose Create user

  7. Next create access keys for your user. For more information about creating access keys, see Managing access keys for IAM users in the IAM User Guide.

You must next provide a file to be downloaded. This example assumes that you will put a file named myfile.txt in a newly created S3 bucket named cookbook_bucket.

To provide a file for downloading
  1. Create a file named myfile.txt with the following text and save it in a convenient location on your workstation.

    This is the file that you just downloaded from Amazon S3.
  2. On the Amazon S3 console, create a bucket named cookbook_bucket in the Standard region and upload myfile.txt to the bucket.

Set the cookbook up as follows.

To set up the cookbook
  1. Create a directory within opsworks_cookbooks named s3bucket and navigate to it.

  2. Initialize and configure Test Kitchen, as described in Example 1: Installing Packages.

  3. Replace the text in .kitchen.yml with the following.

    --- driver: name: vagrant provisioner: name: chef_solo environments_path: ./environments platforms: - name: ubuntu-14.04 suites: - name: s3bucket provisioner: solo_rb: environment: test run_list: - recipe[s3bucket::default] attributes:
  4. Add two directories to s3bucket: recipes and environments.

  5. Create an environment file named test.json with the following default_attributes section, replacing the access_key and secret_key values with the corresponding keys for your user. Save the file to the cookbook's environments folder.

    { "default_attributes" : { "cookbooks_101" : { "access_key": "AKIAIOSFODNN7EXAMPLE", "secret_key" : "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" } }, "chef_type" : "environment", "json_class" : "Chef::Environment" }

You have a variety of ways to provide credentials to a recipe running on an instance. The key consideration is limiting the chances of accidentally exposing the keys and compromising your account security. For that reason, using explicit key values in your code is not recommended. The example instead puts the key values in the node object, which allows the recipe to reference them by using node syntax instead of exposing literal values. You must have root privileges to access the node object, which limits the possibility that the keys might be exposed. For more information, see Best Practices for Managing AWS Access Keys.

Note

Notice that the example uses nested attributes, with cookbooks_101 as the first element. This practice limits the chance of a name collision if there are other access_key or secret_key attributes in the node object.

The following recipe downloads myfile.text from the cookbook_bucket bucket.

gem_package "aws-sdk ~> 3" do action :install end ruby_block "download-object" do block do require 'aws-sdk' s3 = Aws::S3::Client.new( :access_key_id => "#{node['cookbooks_101']['access_key']}", :secret_access_key => "#{node['cookbooks_101']['secret_key']}") myfile = s3.bucket['cookbook_bucket'].objects['myfile.txt'] Dir.chdir("/tmp") File.open("myfile.txt", "w") do |f| f.write(myfile.read) f.close end end action :run end

The first part of the recipe installs the SDK for Ruby, which is a gem package. The gem_package resource installs gems that will be used by recipes or other applications.

Note

Your instance usually has two Ruby instances, which are typically different versions. One is a dedicated instance that is used by the Chef client. The other is used by applications and recipes running on the instance. It's important to understand this distinction when installing gem packages, because there are two resources for installing gems, gem_package and chef_gem. If applications or recipes use the gem package, install it with gem_package. chef_gem is only for gem packages used by Chef client.

The remainder of the recipe is a ruby_block resource, which contains the Ruby code that downloads the file. You might think that because a recipe is a Ruby application, you could put the code in the recipe directly. However, a Chef run compiles all of that code before executing any resources. If you put the example code directly in the recipe, Ruby will attempt to resolve the require 'aws-sdk' statement before it executes the gem_package resource. Because the SDK for Ruby hasn't been installed yet, compilation will fail.

Code in a ruby_block resource isn't compiled until that resource is executed. In this example, the ruby_block resource is executed after the gem_package resource has finished installing the SDK for Ruby, so the code will run successfully.

The code in the ruby_block works as follows.

  1. Creates a new Aws::S3 object, which provides the service interface.

    The access and secret keys are specified by referencing the values stored in the node object.

  2. Calls the S3 object's bucket.objects association, which returns an Aws::S3::Object object named myfile that represents myfile.txt.

  3. Uses Dir.chdir to set the working directory to /tmp.

  4. Opens a file named myfile.txt, writes the contents of myfile to the file, and closes the file.

To run the recipe
  1. Create a file named default.rb with the example recipe and save it to the recipes directory.

  2. Run kitchen converge.

  3. Run kitchen login to log in to the instance, and then run ls /tmp. You should see the myfile.txt, along with several Test Kitchen files and directories.

    vagrant@s3bucket-ubuntu-1204:~$ ls /tmp install.sh kitchen myfile.txt stderr

    You can also run cat /tmp/myfile.txt to verify that the file's content is correct.

When you are finished, run kitchen destroy to terminate the instance.