Tag: Resources


Preview the AWS Resource APIs for PHP

by Jeremy Lindblom | on | in PHP | Permalink | Comments |  Share

This year is just about over, but we are too excited to wait until the new year to share with you a feature we are developing for the AWS SDK for PHP. We are calling it the AWS Resource APIs for PHP. This feature is maintained as a separate package, but it acts as an extension to Version 3 of the AWS SDK for PHP.

As you know, the core SDK is composed of service client objects that have methods corresponding 1-to-1 with operations in the service’s API (e.g., Ec2Client::runInstances() method maps to the EC2 service’s RunInstances operation). The resource APIs build upon the SDK to add new types of objects that allow you to interact with the AWS service APIs in a more resource-oriented way. This allows you to use a more expressive syntax when working with AWS services, because you are acting on objects that understand their relationships with other resources and that encapsulate their identifying information.

Resource Objects

Resource objects each represent a single, identifiable AWS resource (e.g., an Amazon S3 bucket or an Amazon SQS queue). They contain information about how to identify the resource and load its data, the actions that can be performed on it, and the other resources to which it is related. Let’s take a look at a few examples showing how to interact with these resource objects.

First, let’s set up the Aws object, which acts as the starting point into the resource APIs.

<?php

require 'vendor/autoload.php';

use AwsResourceAws;

$aws = new Aws([
    'region'  => 'us-west-2',
    'version' => 'latest',
    'profile' => 'your-credential-profile',
]);

(Note: The array of configuration options provided in the preceding example is the same as what you would provide when instantiating the AwsSdk object in the core SDK.)

You can access related resources by calling the related resource’s name as a method and passing in its identity.

$bucket = $aws->s3->bucket('your-bucket');
$object = $bucket->object('image/bird.jpg');

Accessing resources this way is evaluated lazily, so the preceding example does not actually make any API calls.

Once you access the data of a resource, an API call will be triggered to "load" the resource and fetch its data. To access a resource object’s data, you can access it like an array.

echo $object['LastModified'];

Performing Actions

You can perform actions on a resource by calling verb-like methods on the object.

// Create a bucket and object.
$bucket = $aws->s3->createBucket([
    'Bucket' => 'my-new-bucket'
]);
$object = $bucket->putObject([
    'Key'  => 'images/image001.jpg',
    'Body' => fopen('/path/to/image.jpg', 'r'),
]);

// Delete the bucket and object.
$object->delete();
$bucket->delete();

Because the resource’s identity is encapsulated within the resource object, you never have to specify it again once the object is created. This way, actions like $object->delete() do not need to require arguments.

Collections

Some resources have a "has many" type relationship with other resources. For example, an S3 bucket has many S3 objects. The AWS Resource APIs also allow you to work with resource collections.

foreach ($bucket->objects() as $object) {
    echo $object->delete();
}

Using the Resource APIs

We are currently working on providing API documentation for the AWS Resource APIs. Even without documentation, you can programmatically determine what methods are available on a resource object by calling the respondsTo method.

print_r($bucket->respondsTo());
// Array
// (
//     [0] => create
//     [1] => delete
//     [2] => deleteObjects
//     [3] => putObject
//     [4] => multipartUploads
//     [5] => objectVersions
//     [6] => objects
//     [7] => bucketAcl
//     [8] => bucketCors
//     [9] => bucketLifecycle
//     [10] => bucketLogging
//     [11] => bucketPolicy
//     [12] => bucketNotification
//     [13] => bucketRequestPayment
//     [14] => bucketTagging
//     [15] => bucketVersioning
//     [16] => bucketWebsite
//     [17] => object
// )

var_dump($bucket->respondsTo('putObject'));
// bool(true)

Check it Out!

To get started, you can install the AWS Resource APIs for PHP using Composer, by requiring the aws/aws-sdk-php-resources package in your project. The source code and README, are located in the awslabs/aws-sdk-php-resources repo on GitHub.

The initial preview release of the AWS Resource APIs supports the following services: Amazon EC2, Amazon Glacier, Amazon S3, Amazon SNS, Amazon SQS, AWS CloudFormation, and AWS Identity and Access Management (IAM). We will continue to add support for more APIs over this next year.

We’re eager to hear your feedback about this new feature! Please use the issue tracker to ask questions, provide feedback, or submit any issues or feature requests.

Using Resources

by Trevor Rowe | on | in Ruby | Permalink | Comments |  Share

With the recent 2.0 stable release of the aws-sdk-core gem, we started publishing preview releases of aws-sdk-resources. Until the preview status is released, you will need to use the –pre flag to install this gem:

gem install aws-sdk-resources --pre

In bundler, you should give the full version:

# update the version as needed
gem 'aws-sdk-resources', version: '2.0.1.pre'

Usage

Each service module has a Client class that provides a 1-to-1 mapping of the service API. Each service module now also has a Resource class that provides an object-oriented interface to work with.

Each resource object wraps a service client.

s3 = Aws::S3::Resource.new
s3.client
#=> #<Aws::S3::Client>

Given a service resource object you can start exploring related resources. Lets start with buckets in Amazon S3:

# enumerate all of my buckets
s3.buckets.map(&:name)
#=> ['aws-sdk', ...]

# get one bucket
bucket = s3.buckets.first
#=> #<Aws::S3::Bucket name="aws-sdk">

If you know the name of a bucket, you can construct a bucket resource without making an API request.

bucket = s3.bucket('aws-sdk')

# constructors are also available
bucket = Aws::S3::Bucket.new('aws-sdk')
bucket = Aws::S3::Bucket.new(name: 'aws-sdk')

In each of the three previous examples, an instance of Aws::S3::Bucket is returned. This is a lightweight reference to an actual bucket that might exist in Amazon S3. When you reference a resource, no API calls are made until you operate on the resource.

Here I will use the bucket reference to delete the bucket.

bucket.delete

You can use a resource to reference other resources. In the next exmple, I use the bucket object to reference an object in the bucket by its key.
Again, no API calls are made until I invoke an operation such as #put or #delete.

obj = bucket.object('hello.txt')
obj.put(body:'Hello World!')
obj.delete

Resource Data

Resources have one or more identifiers, and data. To construct a resource, you only need the identifiers. A resource can load itself using its identifiers.

Constructing a resource object from its identifiers will never make an API call.

obj = s3.bucket('aws-sdk').object('key') # no API call made

# calling #data loads an object, returning a structure
obj.data.etag
#=> "ed076287532e86365e841e92bfc50d8c"

# same as obj.data.etag
obj.etag
#=> "ed076287532e86365e841e92bfc50d8c"

Resources will never update internal data until you call #reload. Use #reload if you need to poll a resource attribute for a change.

# force the resource to refresh data, returning self
obj.reload.last_updated_at

Resource Associations

Most resources types are associated with one or more different resources. For example, an Aws::S3::Bucket object bucket has many objects, a website configuration, an ACL, etc.

Each association is documented on the resource class. The API documentation will specify what API call is being made. If the association is plural, it will document when multiple calls are made.

When working with plural associations, such as bucket that has many objects, resources are automatically paginated. This makes it simple to lazily enumerate all objects.

bucket = s3.bucket('aws-sdk')

# enumerate **all** objects in a bucket, objects are fetched
# in batches of 1K until every object has been yielded
bucket.objects.each do |obj|
  puts "#{obj.key} => #{obj.etag}"
end

# filter objects with a prefix
bucket.objects(prefix:'/tmp/').map(&:key)

Some APIs support operating on resources in batches. When possible,
the SDK will provide batch actions.

# gets and deletes objects in batches of 1K, sweet!
bucket.objects(prefix:'/tmp/').delete

Resource Waiters

Some resources have associated waiters. These allow you to poll until the resource enters a desired state.

instance = Aws::EC2::Instance.new('i-12345678')
instance.stop
instance.wait_until_stopped
puts instance.id + ' is stopped'

Whats Next?

The resource interface has a lot of unfinished features. Some of the things we are working on include:

  • Adding #exists? methods to all resource objects
  • Consistent tagging interfaces
  • Batch waiters
  • More service coverage with resource definitions

We would love to hear your feedback. Resources are available now in the preview release of the aws-sdk-resources gem and in the master branch of GitHub.

Happy coding!

Version 2 Resource Interfaces

by Trevor Rowe | on | in Ruby | Permalink | Comments |  Share

In version 1 of the AWS SDK for Ruby provides a 1-to-1 client class for each AWS service. For many services it also provides a resource-oriented interface. These resource objects use the client to provide a more natural object-oriented experience when working with AWS APIs.

We are busy working resource interfaces for the v2 Ruby SDK.

Resource Interfaces

The following examples use version 1 of the aws-sdk gem. This first example uses the 1-to-1 client to terminate running instances:

ec2 = AWS::EC2::Client.new
resp = ec2.describe_instances
resp[:reservations].each do |reservation|
  reservation[:instances].each do |instance|
    if instance[:state][:name] == 'running'
      ec2.terminate_instances(instance_ids:[instance[:instance_id]])
    end
  end
end

This example uses the resource abstraction to start instances in the stopped state:

ec2 = AWS::EC2.new
ec2.instances.each do |instance|
  instance.start if instance.status == :stopped
end

Resources for Version 2

We have a lot of lessons learned from our v1 resource interfaces. We are busy working on the v2 abstraction. Here are some of the major changes from v1 to v2.

Memoization Interfaces Removed

The version 1 resource abstraction was very chatty by default. It did not memoize any resource attributes and a user could unknowingly trigger a large number of API requests. As a workaround, users could use memoization blocks around sections of their code.

In version 2, all resources objects will hold onto their data/state until you explicitly call a method to reload the resource. We are working hard to make it very obvious when calling a method on a resource object will generate an API request over the network.

Less Hand-Written Code and More API Coverage

The version 1 SDK has hand-coded resource and collection classes. In version 2, our goal is to extend the service API descriptions that power our clients with resource definitions. These definitions will be consumed to generate our resource classes.

Using resource definitions helps eliminate a significant amount of hand written code, ensures interfaces are consistent, and makes it easier for users to contribute resource abstractions.

We also plan to provide extension points to resources to allow for custom logic and more powerful helpers.

Resource Waiters

It is a common pattern to operate on a resource and then wait for the change to take effect. Waiting typically requires making an API request, asserting some value has changed and optionally waiting and trying again. Waiting for a resource to enter a certain state can be tricky. You need to deal with terminal cases, failures, transient errors, etc.

Our goal is to provide waiter definitions and attach them to our resource interfaces. For example:

# create a new table in Amazon DynamoDB
table = dynamodb.table('my-table')
table.update(provisioned_throughput: { 
  read_capcity_units: 1000
})

# wait for the table to be ready
table.wait_for(:status, 'ACTIVE')

In a follow up blog post, I will be introducing the resources branch of the SDK that is available today on GitHub. Please take a look and feedback is always welcome!