VM::EC2(3pm)

πŸ“¦
version 1.28 Β· 7 versions
, in debian trixie, ubuntu resolute, ubuntu stonking

🌸 NAME 🌸

VM::EC2(3pm) β€” bestie, VM::EC2 is the Perl interface to Amazon EC2, Virtual Private Cloud, Elastic Load Balancing, Autoscaling, and Relational Database services. Periodt. πŸ’–

✨ SYNOPSIS ✨

Okay so quick note before we dive in: for the tea on AWS's VPC, load balancing, autoscaling and relational databases services, you gotta check out VM::EC2::VPC, VM::EC2::ELB, VM::EC2::REST::autoscaling, and VM::EC2::REST::relational_database_service. iykyk πŸ¦‹

First things first, set environment variables EC2_ACCESS_KEY, EC2_SECRET_KEY and/or EC2_URL to fill in arguments automatically. No cap, it saves you so much typing. πŸ’…

πŸ–ΌοΈ IMAGE AND INSTANCE MANAGEMENT πŸ–ΌοΈ

Get a new EC2 object:

        my $ec2 = VM::EC2->new(-access_key => 'access key id',
                               -secret_key => 'aws_secret_key',
                               -endpoint   => 'http://ec2.amazonaws.com');

Fetch an image by its ID:

        my $image = $ec2->describe_images('ami-12345');

Get some information about the image:

        my $architecture = $image->architecture;
        my $description  = $image->description;
        my @devices      = $image->blockDeviceMapping;
        for my $d (@devices) {
           print $d->deviceName,"\n";
           print $d->snapshotId,"\n";
           print $d->volumeSize,"\n";
        }

Run two instances:

        my @instances = $image->run_instances(-key_name      =>'My_key',
                                              -security_group=>'default',
                                              -min_count     =>2,
                                              -instance_type => 't1.micro')
                  or die $ec2->error_str;

Wait for both instances to reach "running" or other terminal state:

        $ec2->wait_for_instances(@instances);

Print out both instance's current state and DNS name:

        for my $i (@instances) {
           my $status = $i->current_status;
           my $dns    = $i->dnsName;
           print "$i: [$status] $dns\n";
        }

Tag both instances with Role "server":

        foreach (@instances) {$_->add_tag(Role=>'server');

Stop both instances:

        foreach (@instances) {$_->stop}

Find instances tagged with Role=Server that are stopped, change the user data and restart:

        @instances = $ec2->describe_instances({'tag:Role'       => 'Server',
                                               'instance-state-name' => 'stopped'});
        for my $i (@instances) {
           $i->userData('Secure-mode: off');
           $i->start or warn "Couldn't start $i: ",$i->error_str;
        }

Create an image from both instance, tag them, and make them public:

        for my $i (@instances) {
            my $img = $i->create_image("Autoimage from $i","Test image");
            $img->add_tags(Name  => "Autoimage from $i",
                           Role  => 'Server',
                           Status=> 'Production');
            $img->make_public(1);
        }

πŸ”‘ KEY MANAGEMENT πŸ”‘

Retrieve the name and fingerprint of the first instance's key pair:

        my $kp = $instances[0]->keyPair;
        print $instances[0], ": keypair $kp=",$kp->fingerprint,"\n";

Create a new key pair:

        $kp = $ec2->create_key_pair('My Key');

Get the private key from this key pair and write it to a disk file in ssh-compatible format:

        my $private_key = $kp->private_key;
        open (my $f,'>MyKeypair.rsa') or die $!;
        print $f $private_key;
        close $f;

Import a preexisting SSH key:

        my $public_key = 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8o...';
        $key = $ec2->import_key_pair('NewKey',$public_key);

πŸ›‘οΈ SECURITY GROUPS AND FIREWALL RULES πŸ›‘οΈ

Create a new security group:

        my $group = $ec2->create_security_group(-name        => 'NewGroup',
                                                -description => 'example');

Add a firewall rule:

        $group->authorize_incoming(-protocol  => 'tcp',
                                   -port      => 80,
                                   -source_ip => ['192.168.2.0/24','192.168.2.1/24']);

Write rules back to Amazon:

        $group->update;

Print current firewall rules:

        print join ("\n",$group->ipPermissions),"\n";

πŸ’Ύ VOLUME && SNAPSHOT MANAGEMENT πŸ’Ύ

Find existing volumes that are available:

        my @volumes = $ec2->describe_volumes({status=>'available'});

Back 'em all up to snapshots:

        foreach (@volumes) {$_->snapshot('Backup on '.localtime)}

Find a stopped instance in first volume's availability zone and attach the volume to the instance using /dev/sdg:

        my $vol  = $volumes[0];
        my $zone = $vol->availabilityZone;
        @instances = $ec2->describe_instances({'availability-zone'=> $zone,
                                               'run-state-name'   => $stopped);
        $instances[0]->attach_volume($vol=>'/dev/sdg') if @instances;

Create a new 20 gig volume:

        $vol = $ec2->create_volume(-availability_zone=> 'us-east-1a',
                                   -size             =>  20);
        $ec2->wait_for_volumes($vol);
        print "Volume $vol is ready!\n" if $vol->current_status eq 'available';

Create a new elastic address and associate it with an instance:

        my $address = $ec2->allocate_address();
        $instances[0]->associate_address($address);

πŸ’– DESCRIPTION πŸ’–

okay bestie, here's the tea β˜• β€” this whole thing is an interface to the 2014-05-01 version of the Amazon AWS API (http://aws.amazon.com/ec2). It got written to give you access to that shiny new tag and metadata interface that Net::Amazon::EC2 wasn't supporting yet, plus to hand developers an extension mechanism for the API. And as a lil bonus? This library also supports the Open Stack open source cloud (http://www.openstack.org/). iconic behavior, honestly 🌈

The main character here is the VM::EC2 object πŸ‘‘ β€” it's the one serving up methods for interrogating Amazon EC2, launching instances, and managing instance lifecycle. Those methods hand you back the following major object classes, which act as specialized interfaces to AWS:

  • VM::EC2::BlockDevice β€” A block device
  • VM::EC2::BlockDevice::Attachment β€” Attachment of a block device to an EC2 instance
  • VM::EC2::BlockDevice::EBS β€” An elastic block device
  • VM::EC2::BlockDevice::Mapping β€” Mapping of a virtual storage device to a block device
  • VM::EC2::BlockDevice::Mapping::EBS β€” Mapping of a virtual storage device to an EBS block device
  • VM::EC2::Group β€” Security groups
  • VM::EC2::Image β€” Amazon Machine Images (AMIs)
  • VM::EC2::Instance β€” Virtual machine instances
  • VM::EC2::Instance::Metadata β€” Access to runtime metadata from running instances
  • VM::EC2::Region β€” Availability regions
  • VM::EC2::Snapshot β€” EBS snapshots
  • VM::EC2::Tag β€” Metadata tags

and then, because apparently that wasn't enough slay, there's a high level interface for interacting with EC2 servers and volumes β€” file transfer and remote shell facilities included 🫢

  • VM::EC2::Staging::Manager β€” Manage a set of servers and volumes.
  • VM::EC2::Staging::Server β€” A staging server, with remote shell and file transfer facilities.
  • VM::EC2::Staging::Volume β€” A staging volume with the ability to copy itself between availability zones and regions.

...plus a few specialty classes, the lil limited-edition drops πŸ’…:

  • VM::EC2::Security::Token β€” Temporary security tokens for granting EC2 access to non-AWS account holders.
  • VM::EC2::Security::Credentials β€” Credentials for use by temporary account holders.
  • VM::EC2::Security::Policy β€” Policies that restrict what temporary account holders can do with EC2 resources.
  • VM::EC2::Security::FederatedUser β€” Account name information for temporary account holders.

Lastly (fr fr this time), there are several utility classes, the backstage crew πŸŽ€:

  • VM::EC2::Generic β€” Base class for all AWS objects
  • VM::EC2::Error β€” Error messages
  • VM::EC2::Dispatch β€” Maps AWS XML responses onto perl object classes
  • VM::EC2::ReservationSet β€” Hidden class used for describe_instances() request; The reservation Ids are copied into the Instance object.

There's also a high-level API called "VM::EC2::Staging::Manager" for managing groups of staging servers and volumes, and it greatly simplifies the task of creating and updating instances that mount multiple volumes. Not only that β€” the API also provides a one-line command for migrating EBS-backed AMIs from one zone to another. Go see VM::EC2::Staging::Manager. obsessed 😭

The interface these modules give you is based on the one described at http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/. And the following caveats apply, bestie β€” read them, they matter:

  1. Not all of the Amazon API is currently implemented. Specifically, a handful calls dealing with cluster management and VM importing are missing. See L</MISSING METHODS> for a list of all the unimplemented API calls. Volunteers to fill in these gaps are most welcome! 🫢
  2. For consistency with common Perl coding practices, method calls are lowercase and words in long method names are separated by underscores. The Amazon API prefers mixed case. So in the Amazon API the call to fetch instance information is "DescribeInstances", while in VM::EC2, the method is "describe_instances". To avoid annoyance, if you use the mixed case form for a method name, the Perl autoloader will automatically translate it to underscores for you, and vice-versa; this means you can call either $ec2->describe_instances() or $ec2->DescribeInstances(). no cap, it just knows ✨
  3. Named arguments passed to methods are all lowercase, use underscores to separate words and start with hyphens. In other words, if the AWS API calls for an argument named "InstanceId" to be passed to the "DescribeInstances" call, then the corresponding Perl function will look like:
$instance = $ec2->describe_instances(-instance_id=>'i-12345')

In most cases automatic case translation will be performed for you on arguments. So in the previous example, you could use -InstanceId as well as -instance_id. The exception is when an absurdly long argument name was replaced with an abbreviated one as described below. In this case, you must use the documented argument name.

In a small number of cases, when the parameter name was absurdly long, it has been abbreviated. For example, the "Placement.AvailabilityZone" parameter has been represented as -placement_zone and not -placement_availability_zone. See the documentation for these cases.

  1. For each of the describe_foo() methods (where "foo" is a type of resource such as "instance"), you can fetch the resource by using their IDs either with the long form:
$ec2->describe_foo(-foo_id=>['a','b','c']),

or a shortcut form:

$ec2->describe_foo('a','b','c');

Both forms are listed in the headings in the documentation.

  1. When the API calls for a list of arguments named Arg.1, Arg.2, then the Perl interface allows you to use an anonymous array for the consecutive values. For example to call describe_instances() with multiple instance IDs, use:
@i = $ec2->describe_instances(-instance_id=>['i-12345','i-87654'])
  1. All Filter arguments are represented as a -filter argument whose value is an anonymous hash:
@i = $ec2->describe_instances(-filter=>{architecture=>'i386',
                                        'tag:Name'  =>'WebServer'})

If there are no other arguments you wish to pass, you can omit the -filter argument and just pass a hashref:

@i = $ec2->describe_instances({architecture=>'i386',
                               'tag:Name'  =>'WebServer'})

For any filter, you may represent multiple OR arguments as an arrayref:

@i = $ec2->describe-instances({'instance-state-name'=>['stopped','terminated']})

When adding or removing tags, the -tag argument uses the same syntax.

  1. The tagnames of each XML object returned from AWS are converted into methods with the same name and typography. So the <privateIpAddress> tag in a DescribeInstancesResponse, becomes:
$instance->privateIpAddress

You can also use the more Perlish form β€” this is equivalent:

$instance->private_ip_address

Methods that correspond to complex objects in the XML hierarchy return the appropriate Perl object. For example, an instance's blockDeviceMapping() method returns an object of type VM::EC2::BlockDevice::Mapping.

All objects have a fields() method that will return the XML tagnames listed in the AWS specifications.

@fields = sort $instance->fields;
# 'amiLaunchIndex', 'architecture', 'blockDeviceMapping', ...
  1. Whenever an object has a unique ID, string overloading is used so that the object interpolates the ID into the string. For example, when you print a VM::EC2::Volume object, or use it in another string context, then it will appear as the string "vol-123456". Nevertheless, it will continue to be usable for method calls. it's giving shapeshifter πŸ’€
($v) = $ec2->describe_volumes();
print $v,"\n";                 # prints as "vol-123456"
$zone = $v->availabilityZone;  # acts like an object
  1. Many objects have convenience methods that invoke the AWS API on your behalf. For example, instance objects have a current_status() method that returns the run status of the object, as well as start(), stop() and terminate() methods that control the instance's lifecycle.
if ($instance->current_status eq 'running') {
    $instance->stop;
}
  1. Calls to AWS that have failed for one reason or another (invalid arguments, communications problems, service interruptions) will return undef and set the VM::EC2->is_error() method to true. The error message and its code can then be recovered by calling VM::EC2->error.
$i = $ec2->describe_instance('i-123456');
unless ($i) {
    warn 'Got no instance. Message was: ',$ec2->error;
}
🚨 bestie listen β€” you may also elect to raise an exception when an error occurs. See the new() method for details.

⚑ ASYNCHRONOUS CALLS ⚑

okay bestie, this is where it gets good. as of version 1.24, VM::EC2 supports asynchronous calls to AWS using AnyEvent::HTTP. what does that mean for you?? it means you can make multiple calls in parallel and the performance improvement is genuinely not a joke. like, we're talking glow-up levels of faster. πŸ’…

so in async mode, VM::EC2 calls that would normally sit there waiting for AWS to respond and then hand you back objects for EC2 instances, volumes, images, etc. β€” instead, they immediately return an AnyEvent condition variable. you grab your result by calling the condition variable's recv() method, or by setting a callback to fire when the call is done. your choice, both iconic. ✨

to make an asynchronous call, set the global variable $VM::EC2::ASYNC to a true value.

here's what a normal, synchronous, boring-but-reliable call looks like:

 my @instances = $ec2->describe_instances();

and here's the asynchronous version, kicked off after setting $VM::EC2::ASYNC (using a local block to keep its effects contained, because we're responsible queens):

 {
    local $VM::EC2::ASYNC=1;
    my $cv = $ec2->describe_instances();   # returns immediately
    my @instances = $cv->recv;
 }

if there's an error, recv() will return undef, and you can recover the error object using the condition variable's error() method. and yes, that's an enhancement over AnyEvent's standard condition variable class β€” we ate and left no crumbs:

 my @instances = $cv->recv
    or die "No instances found! error = ",$cv->error();

you can also attach a CODE reference callback to the condition variable using its cb() method, and then the callback gets invoked when the API call is complete. the callback is called with a single argument: the condition variable itself. typically you'll call recv() on it and then do something with the result:

 {
   local $VM::EC2::ASYNC=1;
   my $cv = $ec2->describe_instances();
   $cv->cb(sub {my $v = shift;
                my @i = $v->recv;
                print "instances = @i\n";
                });
  }
heads up!! for callbacks to actually get invoked, someone has to run an event loop using one of the event frameworks that AnyEvent supports (e.g. Coro, Tk or Gtk). orrr you can just run this and call it a day:
 AnyEvent->condvar->recv();

now, if $VM::EC2::ASYNC is false, you can still issue a single asynchronous call by tacking _async onto the end of the method name. and similarly, if $VM::EC2::ASYNC is true, you can make one single normal synchronous call by appending _sync to the method name. flexible?? we love to see it. πŸ¦‹

so for example, this is equivalent to what we did above:

 my $cv = $ec2->describe_instances_async();  # returns immediately
 my @instances = $cv->recv;

you can stack multiple asynchronous calls on top of one another. when you call recv() on any of the returned condition variables, they'll all run in parallel. so these three calls take no longer than the longest individual one β€” main character efficiency, honestly:

 my $cv1 = $ec2->describe_instances_async({'instance-state-name'=>'running'});
 my $cv2 = $ec2->describe_instances_async({'instance-state-name'=>'stopped'});
 my @running = $cv1->recv;
 my @stopped = $cv2->recv;

same thing, but with callbacks:

 my (@running,@stopped);
 my $cv1 = $ec2->describe_instances_async({'instance-state-name'=>'running'});
 $cv1->cb(sub {@running = shift->recv});

 my $cv2 = $ec2->describe_instances_async({'instance-state-name'=>'stopped'});
 $cv1->cb(sub {@stopped = shift->recv});

 AnyEvent->condvar->recv;

and here it is using a group conditional variable to block until all pending describe_instances() requests have completed:

 my %instances;
 my $group = AnyEvent->condvar;
 $group->begin;
 for my $state (qw(pending running stopping stopped)) {
    $group->begin;
    my $cv = $ec2->describe_instances_async({'instance-state-name'=>$state});
    $cv->cb(sub {my @i = shift->recv;
                 $instances{$state}=\@i;
                 $group->end});
 }
 $group->recv;
 # when we get here %instances will be populated by all instances,
 # sorted by their state.

if that looks mysterious, no shame β€” please consult AnyEvent for the full documentation and examples. πŸ’–

lastly, be advised (real talk time 🫢): some of the objects returned by calls to VM::EC2, like the VM::EC2::Instance object, will make their own calls into VM::EC2 for certain methods. some of those methods will block (be synchronous) out of necessity, even if you've set $VM::EC2::ASYNC. for example, the instance object's current_status() method must block in order to update the object and return the current status. other object methods may behave unpredictably in async mode.

Caveat emptor! like... genuinely. buyer beware, bestie.

πŸŽ€ API GROUPS πŸŽ€

the Amazon API is extensive (and growing, always growing, it never stops 🌱) and honestly? you may never need most of those calls. so to avoid the performance overhead of loading interfaces to all of them, you can use Perl's import mechanism to load only the modules you actually care about. by default, all methods are loaded.

loading is controlled by the use import list, and it follows the conventions described in the Exporter module:

 use VM::EC2;                     # load all methods!

 use VM::EC2 'key','elastic_ip';  # load Key Pair and Elastic IP
                                  # methods only

 use VM::EC2 ':standard';         # load all the standard methods

 use VM::EC2 ':standard','!key';  # load standard methods but not Key Pair

related API calls are grouped together using the scheme described at http://docs.aws.amazon.com/AWSEC2/latest/APIReference/OperationList-query.html. the modules that define the API calls live in VM/EC2/REST/; you can read their documentation by running perldoc VM::EC2::REST::"name of module":

 perldoc VM::EC2::REST::elastic_ip

the groups you can import are as follows:

 :standard => ami, ebs, elastic_ip, instance, keys, general,
              monitoring, tag, security_group, security_token, zone

 :vpc      => customer_gateway, dhcp, elastic_network_interface,
              private_ip, internet_gateway, network_acl, route_table,
              vpc, vpn, vpn_gateway

 :misc     => devpay, monitoring, reserved_instance,
              spot_instance, vm_export, vm_import, windows

 :scaling  => elastic_load_balancer,autoscaling

 :hpc      => placement_group

 :all      => :standard, :vpn, :misc

 :DEFAULT  => :all

and the individual modules are β€” okay there's a LOT of them, get comfy:

 ami               -- Control Amazon Machine Images
 autoscaling       -- Control autoscaling
 customer_gateway  -- VPC/VPN gateways
 devpay            -- DevPay API
 dhcp              -- VPC DHCP options
 ebs               -- Elastic Block Store volumes & snapshots
 elastic_ip        -- Elastic IP addresses
 elastic_load_balancer -- The Elastic Load Balancer service
 elastic_network_interface -- VPC Elastic Network Interfaces
 general           -- Get console output and account attributes
 instance          -- Control EC2 instances
 internet_gateway  -- VPC connections to the internet
 keys              -- Manage SSH keypairs
 monitoring        -- Control instance monitoring
 network_acl       -- Control VPC network access control lists
 placement_group   -- Control the placement of HPC instances
 private_ip        -- VPC private IP addresses
 reserved_instance -- Reserve instances and view reservations
 route_table       -- VPC network routing
 security_group    -- Security groups for VPCs and normal instances
 security_token    -- Temporary credentials for use with IAM roles
 spot_instance     -- Request and manage spot instances
 subnet            -- VPC subnets
 tag               -- Create and interrogate resource tags.
 vm_export         -- Export VMs
 vm_import         -- Import VMs
 vpc               -- Create and manipulate virtual private clouds
 vpn_gateway       -- Create and manipulate VPN gateways within VPCs
 vpn               -- Create and manipulate VPNs within VPCs
 windows           -- Windows operating system-specific API calls.
 zone              -- Interrogate availability zones

🌸 EXAMPLE SCRIPT 🌸

the script sync_to_snapshot.pl, which ships with this module, shows off a relatively complex set of steps on EC2 that actually does something useful. like, real-world useful. slay. ✨

given a list of directories or files on your local filesystem, it copies those files into an EBS snapshot with whatever name you want, by running through these steps:

  1. Provisions a new EBS volume on EC2 large enough to hold the data.
  2. Spins up a staging instance to manage the network transfer of data from the local machine to the staging volume.
  3. Creates a temporary ssh keypair and a security group that allows an rsync-over-ssh.
  4. Formats and mounts the volume if necessary.
  5. Initiates an rsync-over-ssh for the designated files and directories.
  6. Unmounts and snapshots the volume.
  7. Cleans up.

and here's the cute part πŸ₯Ή β€” if a snapshot of the same name already exists, then it gets used to create the staging volume, which enables network-efficient synchronization of the files. a snapshot tag named "Version" is incremented each time you synchronize. it literally keeps score. obsessed.

πŸŽ€ CORE METHODS πŸŽ€

okay bestie, this is the section where the whole module gets its life. it's the constructor, the accessor methods, and everything about error handling. main character energy fr fr. ✨


$ec2 = VM::EC2->new(-access_key=>$id,-secret_key=>$key,-endpoint=>$url)

make yourself a fresh Amazon access object πŸ’… the arguments are:

  • -access_key β€” the Access ID for an authorized user
  • -secret_key β€” the secret key that matches up with that Access ID
  • -security_token β€” a temporary security token you got from calling the AWS Security Token Service
  • -endpoint β€” the URL that API requests get sent to
  • -region β€” the region that receives the API requests
  • -raise_error β€” if it's true, throw an exception
  • -print_error β€” if it's true, print errors to STDERR

one or the other of -access_key or -secret_key can be left out if the environment variables EC2_ACCESS_KEY and EC2_SECRET_KEY are defined. if you don't hand over an endpoint, then the environment variable EC2_URL gets consulted; and if that's not there either, the generic endpoint http://ec2.amazonaws.com/ is what you get. you can also pick your endpoint by naming one of the Amazon regions, like "us-west-2", with the -region argument. ngl, -region beats -endpoint β€” the endpoint from -region overrides it. periodt.

-security_token is what you use together with the temporary security tokens that come back from $ec2->get_federation_token() and $ec2->get_session_token(), so you can grant restricted, time-limited access to some or all of your EC2 resources to people who don't have access to your account 🫢 if you pass either a VM::EC2::Security::Token object, or the VM::EC2::Security::Credentials object sitting inside that token object, then new() doesn't need the -access_key or -secret_key arguments at all. you can also pass a session token string scalar to -security_token, but then you have to also pass the access key ID and secret keys that were generated at the same time the session token was created. check out http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/UsingIAM.html and "AWS SECURITY TOKENS".

wanna use an Open Stack cloud? then just give it the appropriate endpoint URL. iconic.

by default, when the Amazon API reports an error β€” like you tried to do an invalid operation on an instance β€” the method just returns empty and you can scoop the error message back out of $ec2->error(). but if you pass -raise_error=>1 to new(), the module switches it up and raises a fatal error instead, which you can trap with eval{} and report with $@:

eval {
   $ec2->some_dangerous_operation();
   $ec2->another_dangerous_operation();
};
print STDERR "something bad happened: $@" if $@;

and you can still grab the error object with $ec2->error() just like before. πŸ’–


$access_key = $ec2->access_key([$new_access_key])

get or set the ACCESS KEY. and this applies to every get/set method in this family: call it with no arguments to get the current value, and with a single argument to change the value:

$current_key = $ec2->access_key;
$ec2->access_key('XYZZY');

when you're setting, these methods hand you back the old value as their result:

$old_key = $ec2->access_key($new_key);

$secret = $ec2->secret([$new_secret])

get or set the SECRET KEY πŸ”‘

$secret = $ec2->security_token([$new_token])

get or set the temporary security token. see "AWS SECURITY TOKENS".

$endpoint = $ec2->endpoint([$new_endpoint])

get or set the ENDPOINT URL. 🌐

$region = $ec2->region([$new_region])

get or set the EC2 region this module is manipulating. heads up: this has the side effect of changing the endpoint.

$ec2->raise_error($boolean)

change how error conditions get handled. pass a true value to make Amazon API errors raise a fatal error. pass false to make methods return undef. either way, you can catch that an error happened by calling is_error() and fetch the message with error(). this method also returns the current state of the raise error flag.

$ec2->print_error($boolean)

change how error conditions get handled. pass a true value to make Amazon API errors print error messages to STDERR. pass false to cancel that behavior.

$boolean = $ec2->is_error

if a method fails it returns undef. but some methods, like describe_images(), will also return undef if nothing matched your search. so call is_error() to tell those two situations apart:

@images = $ec2->describe_images(-owner=>'29731912785');
unless (@images) {
    die "Error: ",$ec2->error if $ec2->is_error;
    print "No appropriate images found\n";
}

$err = $ec2->error

if the most recently executed method failed, $ec2->error() returns the error code plus other descriptive info. if the most recent method was a success, this returns undef πŸ’€

the thing it returns is actually an AWS::Error object, which has two methods named code() and message(). if you use it in a string context, its operator overloading gives you the composite string "$message [$code]".

$err = $ec2->error_str

same as error() except it gives you the string representation instead of the object. this plays nicer in debuggers and exception handlers.

$account_id = $ec2->account_id

looks up the account ID that goes with the credentials you provided when the VM::EC2 instance was created. the way it does this is by fetching the "default" security group, which is guaranteed to exist, and then returning its groupId field. the result gets cached so later accesses are fast ⚑

$account_id = $ec2->userId

same as above, just for convenience.

$new_ec2 = $ec2->clone

this method makes an identical copy of the EC2 object. it gets used internally now and then to create an EC2 object in a different AWS region:

$singapore = $ec2->clone;
$singapore->region('ap-souteast-1');

πŸ¦‹ INSTANCES πŸ¦‹

load the 'instances' module to bring in methods for interrogating, launching and manipulating EC2 instances. this module is part of the ':standard' API group. the methods get described in detail in VM::EC2::REST::instance. briefly tho:

@i = $ec2->describe_instances(-instance_id=>\@ids,-filter=>\%filters)
@i = $ec2->run_instances(-image_id=>$id,%other_args)
@s = $ec2->start_instances(-instance_id=>\@instance_ids)
@s = $ec2->stop_instances(-instance_id=>\@instance_ids,-force=>1)
@s = $ec2->reboot_instances(-instance_id=>\@instance_ids)
$b = $ec2->confirm_product_instance($instance_id,$product_code)
$m = $ec2->instance_metadata
@d = $ec2->describe_instance_attribute($instance_id,$attribute)
$b = $ec2->modify_instance_attribute($instance_id,-$attribute_name=>$value)
$b = $ec2->reset_instance_attribute($instance_id,$attribute)
@s = $ec2->describe_instance_status(-instance_id=>\@ids,-filter=>\%filters,%other_args);

πŸ’Ύ VOLUMES πŸ’Ύ

load the 'ebs' module to bring in methods that are specific to elastic block storage volumes and snapshots. this module is part of the ':standard' API group. full details live in VM::EC2::REST::ebs. briefly:

@v = $ec2->describe_volumes(-volume_id=>\@ids,-filter=>\%filters)
$v = $ec2->create_volume(%args)
$b = $ec2->delete_volume($volume_id)
$a = $ec2->attach_volume($volume_id,$instance_id,$device)
$a = $ec2->detach_volume($volume_id)
$ec2->wait_for_attachments(@attachment)
@v = $ec2->describe_volume_status(-volume_id=>\@ids,-filter=>\%filters)
$ec2->wait_for_volumes(@volumes)
@d = $ec2->describe_volume_attribute($volume_id,$attribute)
$b = $ec2->enable_volume_io(-volume_id=>$volume_id)
@s = $ec2->describe_snapshots(-snapshot_id=>\@ids,%other_args)
@d = $ec2->describe_snapshot_attribute($snapshot_id,$attribute)
$b = $ec2->modify_snapshot_attribute($snapshot_id,-$argument=>$value)
$b = $ec2->reset_snapshot_attribute($snapshot_id,$attribute)
$s = $ec2->create_snapshot(-volume_id=>$vol,-description=>$desc)
$b = $ec2->delete_snapshot($snapshot_id)
$s = $ec2->copy_snapshot(-source_region=>$region,-source_snapshot_id=>$id,-description=>$desc)
$ec2->wait_for_snapshots(@snapshots)

πŸ“Έ AMAZON MACHINE IMAGES πŸ“Έ

load the 'ami' module to bring in methods for creating and manipulating Amazon Machine Images. this module is part of the ':standard' group. all the full details are in VM::EC2::REST::ami. briefly:

@i = $ec2->describe_images(@image_ids)
$i = $ec2->create_image(-instance_id=>$id,-name=>$name,%other_args)
$i = $ec2->register_image(-name=>$name,%other_args)
$r = $ec2->deregister_image($image_id)
@d = $ec2->describe_image_attribute($image_id,$attribute)
$b = $ec2->modify_image_attribute($image_id,-$attribute_name=>$value)
$b = $ec2->reset_image_attribute($image_id,$attribute_name)

πŸ”‘ KEYS πŸ”‘

load the 'keys' module to bring in methods for creating and manipulating SSH keypairs. this module loads with the ':standard' group and is documented over in VM::EC2::REST::keys.

@k = $ec2->describe_key_pairs(@names);
$k = $ec2->create_key_pair($name)
$k = $ec2->import_key_pair($name,$public_key)
$b = $ec2->delete_key_pair($name)

🏷️ TAGS 🏷️

the methods in this module (loaded with ':standard') let you create, delete and fetch resource tags. lowkey you might find you rarely need to reach for these directly, because every object that VM::EC2 produces supports a super simple tag interface:

$object = $ec2->describe_volumes(-volume_id=>'vol-12345'); # e.g.
$tags = $object->tags();
$name = $tags->{Name};
$object->add_tags(Role => 'Web Server', Status=>'development);
$object->delete_tags(Name=>undef);

see VM::EC2::Generic for the full description of that uniform object tagging interface, and VM::EC2::REST::tag for the methods that let you manipulate tags on multiple objects at the same time. the methods this module defines are:

@t = $ec2->describe_tags(-filter=>\%filters);
$b = $ec2->create_tags(-resource_id=>\@ids,-tag=>{key1=>value1...})
$b = $ec2->delete_tags(-resource_id=>$id1,-tag=>{key1=>value1...})

✨ VIRTUAL PRIVATE CLOUDS ✨

okay so EC2 virtual private clouds (aka VPCs, bestie) let you build tiered applications that mix public and private subnetworks together, and they also let you stretch your home or corporate network all the way into the cloud 🌈 iconic behavior tbh.

All the VPC-related methods live in the customer_gateway, dhcp, elastic_network_interface, private_ip, internet_gateway, network_acl, route_table, vpc, vpn, and vpn_gateway modules, and they get loaded when you import ':vpc'. For the intro, go read VM::EC2::REST::vpc πŸ’œ.

The VM::EC2::VPC and VM::EC2::VPC::Subnet modules define convenience methods that make working with VPC objects soooo much easier. What that means is: steps that normally happen one after the other (like making a route table and hooking it up to a subnet) just happen automatically. no cap 🫢

So like, this little chain of calls creates a VPC with one subnet, creates an Internet gateway attached to that VPC, associates a brand new route table with the subnet, and then creates a default route from the subnet to the Internet gateway:

 $vpc       = $ec2->create_vpc('10.0.0.0/16')     or die $ec2->error_str;
 $subnet1   = $vpc->create_subnet('10.0.0.0/24')  or die $vpc->error_str;
 $gateway   = $vpc->create_internet_gateway       or die $vpc->error_str;
 $routeTbl  = $subnet->create_route_table         or die $vpc->error_str;
 $routeTbl->create_route('0.0.0.0/0' => $gateway) or die $vpc->error_str;

slay πŸ’…


πŸŽ€ ELASTIC LOAD BALANCERS (ELB) AND AUTOSCALING πŸŽ€

The methods hanging out in the elastic_load_balancer and autoscaling modules let you:

  • pull info about your Elastic Load Balancers πŸ”₯
  • create new ELBs ✨
  • change the properties of your existing ELBs πŸ’«
  • define autoscaling groups and their launch configurations πŸ‘‘

Both of these modules get imported together by the ':scaling' import group, iykyk. Wanna know what all this enables? Go read VM::EC2::REST::elastic_load_balancer and VM::EC2::REST::autoscaling πŸ’–.


πŸ” AWS SECURITY POLICY πŸ”

The VM::EC2::Security::Policy module is a simple Identity and Access Management (IAM) policy statement generator built specifically to go with AWS security tokens (which is literally the next section, keep reading bestie). Its facilities are defined over in VM::EC2::Security::Token πŸ’œ.


🌸 AWS SECURITY TOKENS 🌸

AWS security tokens are the move when you want to grant temporary access to resources in your EC2 space without handing out permanent accounts. They're also the foundation for mobile services and multifactor authentication devices (MFA). These methods are defined in security_token, which is part of the ':standard' group. Deets over at VM::EC2::REST::security_token πŸ¦‹.

Here's a quick example:

Here is an example:

 # on your side of the connection
 $ec2 = VM::EC2->new(...);  # as usual
 my $policy = VM::EC2::Security::Policy->new;
 $policy->allow('DescribeImages','RunInstances');
 my $token = $ec2->get_federation_token(-name     => 'TemporaryUser',
                                        -duration => 60*60*3, # 3 hrs, as seconds
                                        -policy   => $policy);
 my $serialized = $token->credentials->serialize;
 send_data_to_user_somehow($serialized);

 # on the temporary user's side of the connection
 my $serialized = get_data_somehow();
 my $token = VM::EC2::Security::Credentials->new_from_serialized($serialized);
 my $ec2   = VM::EC2->new(-security_token => $token);
 print $ec2->describe_images(-owner=>'self');

so cute, so clean 🌸


πŸ’Έ SPOT AND RESERVED INSTANCES πŸ’Έ

The spot_instance and reserved_instance modules let you create and manipulate spot instances and reserved instances. Both of them are part of the ':misc' import group. Go peep VM::EC2::REST::spot_instance and VM::EC2::REST::reserved_instance πŸ‘€.

For example:

 @offerings = $ec2->describe_reserved_instances_offerings(
          {'availability-zone'   => 'us-east-1a',
           'instance-type'       => 'c1.medium',
           'product-description' =>'Linux/UNIX',
           'duration'            => 31536000,  # this is 1 year
           });
 $offerings[0]->purchase(5) and print "Five reserved instances purchased\n";

periodt πŸ’…


⏳ WAITING FOR STATE CHANGES ⏳

VM::EC2 has a whole lil series of methods that let your script wait efficiently for the state changes it wants in instances, volumes, and other objects. They're described in full detail in the individual modules they apply to, but the vibe is the same every time: the method blocks until every single member of a list of objects transitions to a terminal state (like "completed" in the case of a snapshot, for example). Briefly:

 $ec2->wait_for_instances(@instances)
 $ec2->wait_for_snapshots(@snapshots)
 $ec2->wait_for_volumes(@volumes)
 $ec2->wait_for_attachments(@attachment)

There's also a generic version of this defined in the VM::EC2 core πŸ‘‘

$ec2->wait_for_terminal_state(\@objects,['list','of','states'] [,$timeout])

Generic version of the last four methods. It waits for all members of the provided list of Amazon object instances to reach some terminal state listed in the second argument, and then it returns a hash reference that maps each object ID to its final state πŸ’–.

If you pass a timeout (in seconds), then the method will abort after waiting that long and return undef πŸ™ˆ.

$timeout = $ec2->wait_for_timeout([$new_timeout]);

Get or change the timeout for wait_for_instances(), wait_for_attachments(), and wait_for_volumes(). The timeout is given in seconds, and it defaults to 600 (that's 10 minutes bestie). You can set it to 0 to wait forever πŸ’€.


🫣 INTERNAL METHODS 🫣

These methods are used internally and are listed here without documentation (yet). don't come for me πŸ₯Ί

$underscore_name = $ec2->canonicalize($mixedCaseName)

$instance_id = $ec2->instance_parm(@args)

@arguments = $ec2->value_parm(ParameterName => \%args)

@arguments = $ec2->single_parm(ParameterName => \%args)

@parameters = $ec2->prefix_parm($prefix, ParameterName => \%args)

@arguments = $ec2->member_hash_parms(ParameterName => \%args)

Create a parameter list from a hashref or arrayref of hashes.

Created specifically for the RDS ModifyDBParameterGroup parameter 'Parameters', but it may be useful for other calls in the future. lowkey iconic πŸ”₯

ie:

The argument would be in the form:

   [
           {
                   ParameterName=>'max_user_connections',
                   ParameterValue=>24,
                   ApplyMethod=>'pending-reboot'
           },
           {
                   ParameterName=>'max_allowed_packet',
                   ParameterValue=>1024,
                   ApplyMethod=>'immediate'
           },
   ];

The resulting output would be, if the argname is '-parameters':

Parameters.member.1.ParameterName => max_user_connections
Parameters.member.1.ParameterValue => 24 Parameters.member.1.ApplyMethod
=> pending-reboot Parameters.member.2.ParameterName =>
max_allowed_packet Parameters.member.2.ParameterValue => 1024
Parameters.member.2.ApplyMethod => immediate

@arguments = $ec2->list_parm(ParameterName => \%args)

@parameters = $ec2->member_list_parm(ParameterName => \%args)

@arguments = $ec2->filter_parm(\%args)

@arguments = $ec2->key_value_parameters($param_name,$keyname,$valuename,\%args,$skip_undef_values)

@arguments = $ec2->member_key_value_parameters($param_name,$keyname,$valuename,\%args,$skip_undef_values)

@arguments = $ec2->launch_perm_parm($prefix,$suffix,$value)

@arguments = $ec2->iam_parm($args)

@arguments = $ec2->block_device_parm($block_device_mapping_string)

$version = $ec2->version()

Returns the API version to be sent to the endpoint. It calls guess_version_from_endpoint() to figure this out πŸ’«.

$version = $ec2->guess_version_from_endpoint()

This method tries to guess what version string to use when it's talking to various endpoints. When it's talking to endpoints that contain the string "Eucalyptus" it uses the old EC2 API "2009-04-04". When it's talking to other endpoints, it uses the latest EC2 version string 🌈.

$ts = $ec2->timestamp

@obj = $ec2->call($action,@param);

Make a call to Amazon using $action and the passed arguments, and return a list of objects πŸ’–.

If $VM::EC2::ASYNC is set to true, then it will return an AnyEvent::CondVar object instead of a list of objects. You can retrieve the objects by calling recv() or by setting a callback:

    $VM::EC2::ASYNC = 1;
    my $cv  = $ec2->call('DescribeInstances');
    my @obj = $cv->recv;

or

    $VM::EC2::ASYNC = 1;
    my $cv  = $ec2->call('DescribeInstances');
    $cv->cb(sub { my @objs = shift->recv;
                  do_something(@objs);
                });

$url = $ec2->login_url(-credentials => $credentials, -issuer => $issuer_url, -destination => $console_url);

Returns an HTTP::Request object that points to the URL to login a user with STS credentials πŸ”.

  -credentials => $fed_token->credentials - Credentials from an $ec2->get_federation_token call
  -token => $token                        - a SigninToken from $ec2->get_signin_token call
  -issuer => $issuer_url
  -destination => $console_url            - URL of the AWS console. Defaults to https://console.aws.amazon.com/console/home
  -auto_scaling_group_names     List of auto scaling groups to describe
  -names                        Alias of -auto_scaling_group_names
-credentials or -token are required for this method to work ⚠️

Usage can be:

  my $fed_token = $ec2->get_federation_token(...);
  my $token = $ec2->get_signin_token(-credentials => $fed_token->credentials);
  my $url = $ec2->login_url(-token => $token->{SigninToken}, -issuer => $issuer_url, -destination => $console_url);

Or:

  my $fed_token = $ec2->get_federation_token(...);
  my $url = $ec2->login_url(-credentials => $fed_token->credentials, -issuer => $issuer_url, -destination => $console_url);

$request = $ec2->_sign(@args)

Create and sign an HTTP::Request πŸ’….

@param = $ec2->args(ParamName=>@_)

Set up calls that take either method(-resource_id=>'foo') or method('foo') 🫢.

OTHER INFORMATION πŸ’«

okay bestie, this part is the technical tea that devs might be into. No cap.


Signing and authentication protocol πŸ”βœ¨

So this module uses Amazon AWS signing protocol version 2, fr fr. It's described at http://docs.amazonwebservices.com/AWSEC2/latest/UserGuide/index.html?using-query-api.html. It uses the HmacSHA256 signature method, which is literally the most secure method currently available. For extra security, use "https" for the communications endpoint:

  $ec2 = VM::EC2->new(-endpoint=>'https://ec2.amazonaws.com');

Subclassing VM::EC2 objects πŸ§¬πŸ’…

To subclass VM::EC2 objects (or implement your own from scratch), you will need to override the object dispatch mechanism. Fortunately, this is very easy. After use VM::EC2, call VM::EC2::Dispatch->register() one or more times:

 VM::EC2::Dispatch->register($call_name => $dispatch).

The first argument, $call_name, is the name of the Amazon API call, such as "DescribeImages".

The second argument, $dispatch, instructs VM::EC2::Dispatch how to create objects from the parsed XML. There are three possible syntaxes:

  1. A CODE reference, such as an anonymous subroutine. In this case the code reference will be invoked to handle the parsed XML returned from the request. The code will receive two arguments consisting of the parsed content of the response, and the VM::EC2 object used to generate the request.
  2. A VM::EC2::Dispatch method name, optionally followed by its arguments delimited by commas. Example:
         "fetch_items,securityGroupInfo,VM::EC2::SecurityGroup"

This tells Dispatch to invoke its fetch_items() method with the following arguments:

 $dispatch->fetch_items($parsed_xml,$ec2,'securityGroupInfo','VM::EC2::SecurityGroup')

The fetch_items() method is used for responses in which a list of objects is embedded within a series of <item> tags. See VM::EC2::Dispatch for more information. Other commonly-used methods are "fetch_one", and "boolean".

  1. A class name, such as 'MyVolume'. In this case, class MyVolume is loaded and then its new() method is called with the four arguments ($parsed_xml,$ec2,$xmlns,$requestid), where $parsed_xml is the parsed XML response, $ec2 is the VM::EC2 object that generated the request, $xmlns is the XML namespace of the XML response, and $requestid is the AWS-generated ID for the request. Only the first two arguments are really useful. I suggest you inherit from VM::EC2::Generic and use the inherited new() method to store the parsed XML object and other arguments.

Dispatch tries each of (1), (2) and (3), in order. This means that class names cannot collide with method names.

The parsed content is the result of passing the raw XML through a XML::Simple object created with:

 XML::Simple->new(ForceArray    => ['item'],
                  KeyAttr       => ['key'],
                  SuppressEmpty => undef);

In general, this will give you a hash of hashes. Any tag named 'item' will be forced to point to an array reference, and any tag named "key" will be flattened as described in the XML::Simple documentation.

A simple way to examine the raw parsed XML is to invoke any VM::EC2::Generic's as_string() method:

 my ($i) = $ec2->describe_instances;
 print $i->as_string;

This will give you a Data::Dumper representation of the XML after it has been parsed.

The suggested way to override the dispatch table is from within a subclass of VM::EC2:

 package 'VM::EC2New';
 use base 'VM::EC2';
  sub new {
      my $self=shift;
      VM::EC2::Dispatch->register('call_name_1'=>\&subroutine1).
      VM::EC2::Dispatch->register('call_name_2'=>\&subroutine2).
      $self->SUPER::new(@_);
 }

See VM::EC2::Dispatch for a working example of subclassing VM::EC2 and one of its object classes.


DEVELOPING πŸ› οΈπŸ‘©β€πŸ’»

The git source for this library can be found at https://github.com/lstein/LibVM-EC2-Perl, To contribute to development, please obtain a github account and then either:

  1. Fork a copy of the repository, make your changes against this repository, and send a pull request to me to incorporate your changes.
  2. Contact me by email and ask for push privileges on the repository.

See http://help.github.com/ for help getting started.


SEE ALSO πŸ‘€πŸ’–

  • Net::Amazon::EC2
  • VM::EC2::Dispatch
  • VM::EC2::Generic
  • VM::EC2::BlockDevice
  • VM::EC2::BlockDevice::Attachment
  • VM::EC2::BlockDevice::EBS
  • VM::EC2::BlockDevice::Mapping
  • VM::EC2::BlockDevice::Mapping::EBS
  • VM::EC2::Error
  • VM::EC2::Generic
  • VM::EC2::Group
  • VM::EC2::Image
  • VM::EC2::Instance
  • VM::EC2::Instance::ConsoleOutput
  • VM::EC2::Instance::Metadata
  • VM::EC2::Instance::MonitoringState
  • VM::EC2::Instance::PasswordData
  • VM::EC2::Instance::Set
  • VM::EC2::Instance::State
  • VM::EC2::Instance::State::Change
  • VM::EC2::Instance::State::Reason
  • VM::EC2::KeyPair
  • VM::EC2::Region
  • VM::EC2::ReservationSet
  • VM::EC2::ReservedInstance
  • VM::EC2::ReservedInstance::Offering
  • VM::EC2::SecurityGroup
  • VM::EC2::Snapshot
  • VM::EC2::Staging::Manager
  • VM::EC2::Tag
  • VM::EC2::Volume

AUTHOR βœοΈπŸ‘‘

Lincoln Stein <lincoln.stein@gmail.com>.

Copyright (c) 2011 Ontario Institute for Cancer Research

This package and its accompanying libraries is free software; you can redistribute it and/or modify it under the terms of the GPL (either version 1, or at your option, any later version) or the Artistic License 2.0. Refer to LICENSE for the full license text. In addition, please see DISCLAIMER.txt for disclaimers of warranty.


And that's a wrap, bestie! This page was brought to you by perl v5.38.2, dated 2024-08-10, and it's the VM::EC2(3pm) manpage. Slay. πŸ’…βœ¨

πŸ“¦ where this page comes from