DEV Community

loading...
Cover image for Working with the AWS SDK for Ruby - Part I
Clouderize

Working with the AWS SDK for Ruby - Part I

pabloxio profile image Pablo Jaramillo ・Updated on ・6 min read

In this post, I'll explain some of the concepts around the AWS SDK for Ruby that I found hard to understand at the beginning like majors SDK versions, what's the difference between the Client and Resource objects or how to "stub" requests for the Client so we can add tests to our code using the AWS SDK for Ruby.

The AWS SDK for Ruby is an alternative to manage resources and interact with your AWS account using the popular - or maybe not so much nowadays πŸ™ƒ - Ruby programming language. It offers an abstraction for different AWS services like S3, EC2, DynamoDB, etc, through a Ruby idiomatic wrapper using classic Object-Oriented Programming (OOP) paradigm. There are other AWS SDK implementations for different programming languages like C++, Go, Java, JavaScript, .NET, Node.js, PHP, and Python.

Configuration

The AWS CLI and SDK uses the same credentials mechanism, environment variables, name profile, etc. More info at AWS documentation. If you already have configured the AWS CLI on your local environment, the SDK will reuse that config.

You just need to install the EC2 aws-sdk-ec2 specific gem that we're going to use in the examples below. table_print will be used as an auxiliary gem to easily print Ruby objects:

gem install aws-sdk-ec2 table_print
Enter fullscreen mode Exit fullscreen mode

aws-sdk-ruby

The aws-sdk-ruby got split into several ruby gems at version 3, each gem matches specific AWS services: aws-sdk-s3 for S3, aws-sdk-ec2 for EC2, and so on. The following code snippet prints the ids, instance_types and the state for EC2 instances in the us-west-2 region:

require 'aws-sdk-ec2'
require 'table_print'

ec2 = Aws::EC2::Resource.new(region: 'us-west-2')

tp ec2.instances.map{|i|{id: i.instance_id, type: i.instance_type, state: i.state.name}}
Enter fullscreen mode Exit fullscreen mode
ID                  | TYPE       | STATE  
--------------------|------------|--------
i-0c2d688b44c5a6324 | t2.micro   | stopped
i-04e764ff7cc2f9f33 | t2.micro   | running
i-0fe8144847b3c49ac | c5.2xlarge | running
Enter fullscreen mode Exit fullscreen mode

Versions

This was really misleading at the beginning for me because I found a lot of examples on the Internet that didn't use version 3, or didn't explain the differences between major versions.

There are three major versions, our main focus today is version 3, which is the latest. How do we know if we're using the SDK version 3? Well, If you're using a specific AWS service require as I did in the previous code snippet for EC2 (aws-sdk-ec2) then you're using the SDK version 3:

require 'aws-sdk-ec2'
require 'aws-sdk-s3'
require 'aws-sdk-sqs'
Enter fullscreen mode Exit fullscreen mode

Requiring specific gems for AWS services wasn't possible before version 3, this version modularizes the monolithic SDK into multiple specific gems, and each gem contains both client and resource interfaces. There is a really interesting post about it in the Ruby AWS blog that explains all the motivations for this change:

alt AWS SDK for Ruby Modularization (Version 3)

REPL

The AWS SDK has an interactive command line (REPL) aws-v3.rb. AWS documentation mentions that the aws-sdk-core gem contains the REPL, but actually it's inside the aws-sdk-resources gem, which actually installs all the aws-sdk-* gems πŸ‘€

gem install aws-sdk-resources
Fetching aws-sdk-acmpca-1.26.0.gem                                                                                                                          Fetching aws-sdk-acm-1.34.0.gem                                                                                                                             Fetching aws-sdk-amplify-1.21.0.gem                                                                                                                         Fetching aws-sdk-apigatewaymanagementapi-1.16.0.gem                                                                                                         Fetching aws-sdk-accessanalyzer-1.9.0.gem
...
233 gems installed
Enter fullscreen mode Exit fullscreen mode

aws-v3.rb enables us to quickly test and use the AWS SDK in an interactive console. We can use the SDK REPL to execute the same previous example:

aws-v3.rb
Pry not available, falling back to irb
2.7.1 :001 > require 'table_print'
 => true 
2.7.1 :002 > ec2 = Aws::EC2::Resource.new
2.7.1 :003 > tp ec2.instances.map{|i|{id: i.instance_id, type: i.instance_type, state: i.sta
te.name}}
[Aws::EC2::Client 200 0.857933 0 retries] describe_instances()  
ID                  | TYPE       | STATE  
--------------------|------------|--------
i-0c2d688b44c5a6324 | t2.micro   | stopped
i-04e764ff7cc2f9f33 | t2.micro   | running
i-0fe8144847b3c49ac | c5.2xlarge | running
Enter fullscreen mode Exit fullscreen mode

In this case, we don't need to require the aws-sdk-ec2 because the SDK REPL requires the whole SDK inside the irb session (or Pry).

As I mentioned before, each gem contains client and resource interfaces. We're only going to talk about the aws-sdk-ec2 today but the concepts apply to other services/gems.

Client

The Client is the basic object and represents an API client for EC2. It has a 1-to-1 relationship with actual AWS API calls. If you have ever use the AWS CLI, you can found some similarities. Let's see for example the EC2 DescribeInstances API, using the AWS CLI you'll make something like this:

aws ec2 describe-instances
Enter fullscreen mode Exit fullscreen mode

And you'll get an output like this:

{
    "Reservations": [
        {
            "Groups": [],
            "Instances": [
                {
                    "AmiLaunchIndex": 0,
...
Enter fullscreen mode Exit fullscreen mode

If you want to make the same API Call using the SDK for Ruby you'll be doing something like this using the REPL:

[1] pry(Aws)> aws_ec2 = Aws::EC2::Client.new                                         
=> #<Aws::EC2::Client>
[2] pry(Aws)> aws_ec2.describe_instances
[3] pry(Aws)> aws_ec2.describe_instances                                             
[Aws::EC2::Client 200 0.886631 0 retries] describe_instances()  
=> #<struct Aws::EC2::Types::DescribeInstancesResult
 reservations=
  [#<struct Aws::EC2::Types::Reservation
    groups=[],
    instances=
     [#<struct Aws::EC2::Types::Instance
       ami_launch_index=0,
Enter fullscreen mode Exit fullscreen mode

As you can see, in practice, the returning info is the same. JSON for the CLI and a Ruby object representation for the same info in the SDK. So, you can consider the Client (Aws::EC2::Client) object as the Ruby implementation for the aws ec2 option in the AWS CLI.

But if we're using the AWS SDK, what we're actually looking for, are higher levels of abstractions for AWS Services.

Resources

Resources (e.g. Aws::EC2::Vpc, Aws::EC2::Instance, etc) are Ruby OOP representations for AWS Services and each of those resources implements internally the Client (Aws::EC2::Client), this is really important to understand, the resources use the Client in the background to make AWS API calls. If we keep this in mind, it will make our life easier in the next post of this series when we talk about how to use waiters and how to stub the Client API calls.

The Aws::EC2::Resource class is built on top of other Resource definitions like Aws::EC2::Instance, Aws::EC2::InternetGateway, etc. It doesn't act as a base class for the other more specific resources, it provides a resource-oriented interface for EC2.

But that's enough talking, let's see an example to fully understand the difference between these concepts inside the AWS SDK for Ruby.

Examples

So, What if we want to list EC2 instances? Let's see which options do we have inside the aws-sdk-ec2 gem. The output for all examples is the same:

ID                  | TYPE       | STATE  
-------------------------|------------|--------
i-0c2d688b44c5a6324 | t2.micro   | stopped
i-04e764ff7cc2f9f33 | t2.micro   | running
i-0fe8144847b3c49ac | c5.2xlarge | running
Enter fullscreen mode Exit fullscreen mode

Client Aws::EC2::Client

require 'aws-sdk-ec2'
require 'table_print'

client = Aws::EC2::Client.new

tp client.describe_instances.reservations.map{|r| r.instances.first}.map{|i|{id: i.instance_id, type: i.instance_type, state: i.state.name}}
Enter fullscreen mode Exit fullscreen mode

Resource Aws::EC2::Resource

require 'aws-sdk-ec2'
require 'table_print'

ec2 = Aws::EC2::Resource.new

tp ec2.instances.map{|i|{id: i.instance_id, type: i.instance_type, state: i.state.name}}
Enter fullscreen mode Exit fullscreen mode

Resource VPC Aws::EC2::Vpc

require 'aws-sdk-ec2'
require 'table_print'

vpc = Aws::EC2::Vpc.new(id: "vpc-12345678")

tp vpc.instances.map{|i|{id: i.instance_id, type: i.instance_type, state: i.state.name}}
Enter fullscreen mode Exit fullscreen mode

The important thing here is that even the Aws::EC2::Resource and Aws::EC2::Vpc options are using the Client Aws::EC2::Client in the background to trigger the EC2 DescribeInstances API call.

For example, this is the actual code for AWS::EC2::Vpc.instances method:

def instances(options = {})
  batches = Enumerator.new do |y|
    options = Aws::Util.deep_merge(options, filters: [{
      name: "vpc-id",
      values: [@id]
     }])
     resp = @client.describe_instances(options)
     resp.each_page do |page|
     batch = []
     page.data.reservations.each do |r|
       r.instances.each do |i|
          batch << Instance.new(
            id: i.instance_id,
            data: i,
            client: @client
          )
        end
      end
      y.yield(batch)
    end
  end
  Instance::Collection.new(batches)
end
Enter fullscreen mode Exit fullscreen mode

As we can see at this line:

resp = @client.describe_instances(options)
Enter fullscreen mode Exit fullscreen mode

The #instances method from Aws::EC2::Vpc class it's just an abstraction for the #describe_instances method at Aws::EC2::Client.

Discussion (0)

pic
Editor guide