Home Tags Anciens articles Mon CV

Interact with AWS using Perl

AWS is mostly about APIs (and a little bit of cloud). In fact, WS is… Yeah, you got it. Most of popular programming languages support these API. You can find on the web SDKs for Go, NodeJS, Python, C++…

But have you ever considered perl (as in Perl 5)? Yes, the old brother as we call it in my office. It’s still alive and so much fun to code !

Perl, again ?!

It’s good to know that this language is still out there, not dead, and far from it either. So let’s go for a little introduction.

You may have heard of Perl 6, and thought something like:

Hey, this is a new version of Perl, finally ! We can let go the old one and start to learn the future ! Best day of my life !

But that’s the thing, Perl 6 isn’t going to kill Perl 5. In fact, the syntax and the spirit are very different. Beside, tons of new modules are uploaded to the CPAN (which is Comprehensive Perl Archive Network, the perl modules index) every day, and as many are updated.

Remember:

Perl = perl 5 and perl 6, the Perl family
perl = perl 5

So you can check the MetaCPAN if you don’t belive me. I can just say, I’m a perl believer for over a decade and I’ll still be.

For the record, it’s not an article where I detail why perl over Python. It’s more like a tutorial on Paws. It assumes that you have a reasonable knowledge of perl.

So… Now let’s talk about Paws !

What’s Paws ?

Python has boto, C++ has aws-sdk-cpp, NodeJS has aws-sdk, the list goes on. But there’s no official SDK built for perl to interact with AWS services.

So, a well-known Perl developer (Jose Luis Martinez Torres, for the record) has started few years ago a project : Paws.

Basically, Paws is an attempt to develop an always up-to-date SDK that covers all possible AWS services. It’s based on botocore, which is low-level, core functionality of boto3.

The logic behind the code is actually pretty smart. Using a builtin builder, it converts every piece of botocore code into perl code : modules, methods, array, hashes, etc. The project is actually generating all of it’s classes from botocore. It’s called data driven development.

So, in fine, Paws aims to provide a full featured SDK to let Perl and AWS love each over !

Installation

The easiest way to install Paws is with cpanm. You can use perlbrew if you want to.

$ cpanm Paws
--> Working on Paws
Fetching http://www.cpan.org/authors/id/J/JL/JLMARTIN/Paws-0.40.tar.gz ... OK
Configuring Paws-0.40 ... OK
Building and testing Paws-0.40 ... OK
Successfully installed Paws-0.40
1 distribution installed
$

Good to go.

Firsts things first

Primo, we need to tell to Paws what’s our AWS credentials to use. To do so, you have to edit ~/.aws/credentials.

It must looks like this:

$ cat ~/.aws/credentials
[the_profile]
aws_access_key_id=XXXXXXX3X022X0X
aws_secret_access_key=XxXxx+XxxxXXXxXx5435xXXx+Xxx32

The profile is a couple of access_key/secret_access_key that you got when you created the IAM user on AWS. This user must have the necessary rights for the APIs that you will use. You can refer to IAM documentation to create the right user to use CLI/API.

Now, Paws can join your AWS account, and you can use the services via it.

Two ways

You have to know that Paws provides, on top of all its classes/methods/objects, a perl script that uses all of it. A little bit like awscli.

Now, the purpose of this article is to learn how to use perl code to interact with AWS and not to use an umpteenth tool, so I’ll just comment it a bit and show some examples before we tackle the bulk of the job.

Paws cli tool

The cli tool coming with Paws is… paws. Its syntax usually is:

$ paws <service> --region <region> <action>

Let’s give it a try:

$ paws EC2 --region=eu-west-1 DescribeRegions 
Regions.0.Endpoint:    ec2.eu-north-1.amazonaws.com
Regions.0.RegionName:  eu-north-1
Regions.1.Endpoint:    ec2.ap-south-1.amazonaws.com
Regions.1.RegionName:  ap-south-1
Regions.10.Endpoint:   ec2.ap-southeast-2.amazonaws.com
Regions.10.RegionName: ap-southeast-2
Regions.11.Endpoint:   ec2.eu-central-1.amazonaws.com
Regions.11.RegionName: eu-central-1
Regions.12.Endpoint:   ec2.us-east-1.amazonaws.com
Regions.12.RegionName: us-east-1
Regions.13.Endpoint:   ec2.us-east-2.amazonaws.com
Regions.13.RegionName: us-east-2
Regions.14.Endpoint:   ec2.us-west-1.amazonaws.com
Regions.14.RegionName: us-west-1
Regions.15.Endpoint:   ec2.us-west-2.amazonaws.com
Regions.15.RegionName: us-west-2
Regions.2.Endpoint:    ec2.eu-west-3.amazonaws.com
Regions.2.RegionName:  eu-west-3
Regions.3.Endpoint:    ec2.eu-west-2.amazonaws.com
Regions.3.RegionName:  eu-west-2
Regions.4.Endpoint:    ec2.eu-west-1.amazonaws.com
Regions.4.RegionName:  eu-west-1
Regions.5.Endpoint:    ec2.ap-northeast-2.amazonaws.com
Regions.5.RegionName:  ap-northeast-2
Regions.6.Endpoint:    ec2.ap-northeast-1.amazonaws.com
Regions.6.RegionName:  ap-northeast-1
Regions.7.Endpoint:    ec2.sa-east-1.amazonaws.com
Regions.7.RegionName:  sa-east-1
Regions.8.Endpoint:    ec2.ca-central-1.amazonaws.com
Regions.8.RegionName:  ca-central-1
Regions.9.Endpoint:    ec2.ap-southeast-1.amazonaws.com
Regions.9.RegionName:  ap-southeast-1
$

Awesome, isn’t it ?

Note: unlike awscli, in paws usage the services/actions use upper cases and no dash, and you have to specify the region on the command line.

Using Paws in perl scripts

Then comes Paws as a set of perl modules which you can use to build awesome apps and workflows. You can use, for example, a small part of Paws::S3 to upload a file from your app.

To use Paws, there’s one thing you have to remember, it’s the code logic. Basically, you can use any module of Paws using this syntax :

use Paws;

my $service = Paws->service('...', region => 'my-region-1');
my $res = $service->MethodCall(Arg1 => $val1, Arg2 => $val2);
print $res->AttributeFromResult;

All of Paws submodules works as in this example. Let’s try some examples to get you started !

First example : Paws::EC2

We are going to use this class to get our instance’s informations. First, let’s build our object:

use Paws;

my $ec2 = Paws->service('EC2', region => 'eu-west-1');
my $describeResult = $ec2->DescribeInstances(
    InstanceIds => ['i-xxxxxx'],
);
my $data = $describeResult->Reservations;

The Reservations method contains all of our instance’s data. You can see it via the following code:

use Data::Dumper;
print(Dumper($data));

There’s a lot of informations. So, $data is an ARRAYREF. You can access its data with an index like :

$data->[0]

And this $data->[0] is a HASHREF, so you can access its attributes with a key. For example:

$data->[0]->{'Instances'}

Finally, this is also an ARRAYREF. I know it’s a bit complicated but if you follow the dump of $data, you’ll get the logic.

Let’s get a real information like the public IP of the instance, via a full example.

#!/usr/bin/env perl
use strict;
use warnings;

use Paws;

my $ec2 = Paws->service('EC2', region => 'eu-west-1');
my $describeResult = $ec2->DescribeInstances(
    InstanceIds => ['i-xxxxxxxxx'],
);

my $data = $describeResult->Reservations;

print($data->[0]->{'Instances'}->[0]->{'PublicIpAddress'}."\n");

exit(0);

Here we go!

Second example : Paws::CostExplorer

This module implements classes to browse the Cost Explorer service. We’re going to use it to browse our costs and bills.

First as usual, let’s build our object:

use Paws;

my $ce = Paws->service('CostExplorer', region => 'us-east-1');

Note that we use a different region. It’s because, according to the API Documentation:

The Cost Explorer API provides the following endpoint: https://ce.us-east-1.amazonaws.com

Now, we want to know the amount of our current bill. We have to get all the datas we need by indicating some pointers.

my $data = $ce->GetCostAndUsage(
    Granularity => 'MONTHLY',
    Metrics       => ['UNBLENDED_COST'],
    TimePeriod    => {
        End   => '2019-05-30',
        Start => '2019-05-01',
    },
);

So today is May, 30th. I want the cost for the month, this explain the HASH TimePeriod and the granularity. The metric which I’m intererested in is UNBLENDED_COST.

As in the previous example, you can browse the data with the following code:

use Paws;

use Data::Dumper;

my $ce = Paws->service('CostExplorer', region => 'us-east-1');

my $data = $ce->GetCostAndUsage(
    Granularity => 'MONTHLY',
    Metrics       => ['UNBLENDED_COST'],
    TimePeriod    => {
        End   => '2019-05-30',
        Start => '2019-05-01',
    },
);

print(Dumper($data));

With this data, let’s get the accurate member in the object. We can see that $data is a HASHREF, and gives us an ARRAYREF, $data->{‘ResultsByTime’}.

In this ARRAYREF, we have, on the first index, another HASHREF. The key we’re looking to is Total.

$data->{'ResultsByTime'}->[0]->{'Total'}

This key is an HASHREF as well, which contains the HASHREF Map, and this Map contains the finally HASHREF named UnblendedCost.

Again, you can get it by only looking at the full dump of $data.

This last HASHREF contains three keys which are the data. So here’s the code to get your bill:

#!/usr/bin/env perl
use strict;
use warnings;

use Paws;

my $ce = Paws->service('CostExplorer', region => 'us-east-1');

my $data = $ce->GetCostAndUsage(
    Granularity => 'MONTHLY',
    Metrics       => ['UNBLENDED_COST'],
    TimePeriod    => {
        End   => '2019-05-30',
        Start => '2019-05-01',
    },
);

my $amount = $data->{'ResultsByTime'}->[0]->{'Total'}->{'Map'}->{'UnblendedCost'}->{'Amount'};
my $currency = $data->{'ResultsByTime'}->[0]->{'Total'}->{'Map'}->{'UnblendedCost'}->{'Unit'};

print("This month so far, your bill is $amount $currency. \n");

exit(0);

How cool is that ?

Conclusion

We can write as many examples like those two above as we want. As I said, Paws contains as many classes and method as botocore.

You juste have to keep in mind the logic to call a method:

my $service = Paws->service('...', region => 'my-region-1');
my $res = $service->MethodCall(Arg1 => $val1, Arg2 => $val2);
print $res->AttributeFromResult;

And most important, the reflex to dump any variable to get the correct combination of hash keys and array indexes to get you to your precious data.

use Data::Dumper;
print(Dumper($data));

I hope you enjoyed this article. If you want to know more, you can join me on the social networks or by email.