File Coverage

blib/lib/Net/EC2/Tiny.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
1             package Net::EC2::Tiny;
2             {
3             $Net::EC2::Tiny::VERSION = '0.03';
4             }
5              
6 1     1   21355 use 5.014;
  1         4  
  1         41  
7              
8 1     1   845 use POSIX qw(strftime);
  1         7180  
  1         7  
9 1     1   46599 use Digest::SHA qw(hmac_sha256);
  1         5896  
  1         82  
10 1     1   775 use MIME::Base64 qw(encode_base64);
  1         778  
  1         59  
11 1     1   1216 use HTTP::Tiny;
  1         46777  
  1         39  
12 1     1   10 use Carp qw(croak);
  1         2  
  1         46  
13              
14 1     1   390 use XML::Simple qw(XMLin);
  0            
  0            
15             use Moo;
16              
17             # ABSTRACT: Basic EC2 client
18              
19              
20              
21             has 'AWSAccessKey' => ( is => 'ro', required => 1 );
22              
23              
24             has 'AWSSecretKey' => ( is => 'ro', required => 1 );
25              
26              
27             has 'debug' => ( is => 'ro', required => 0, default => sub { 0 } );
28              
29              
30             has 'version' => ( is => 'ro', required => 1, default => sub { '2012-07-20' } );
31              
32              
33             has 'region' => ( is => 'ro', required => 1, default => sub { 'us-east-1' } );
34              
35              
36             has 'base_url' => (
37             is => 'ro',
38             required => 1,
39             lazy => 1,
40             default => sub {
41             'https://ec2.' . $_[0]->region . '.amazonaws.com';
42             }
43             );
44              
45              
46             has 'ua' => (
47             is => 'ro',
48             required => 1,
49             lazy => 1,
50             default => sub {
51             HTTP::Tiny->new(
52             'agent' => 'Net::EC2::Tiny ',
53             );
54             }
55             );
56              
57             has '_base_url_host' => (
58             is => 'ro',
59             required => 1,
60             lazy => 1,
61             default => sub {
62             ($_[0]->ua->_split_url($_[0]->base_url))[1]
63             }
64             );
65              
66             sub _timestamp {
67             return strftime("%Y-%m-%dT%H:%M:%SZ",gmtime);
68             }
69            
70             sub _sign {
71             my $self = shift;
72             my %args = @_;
73             my $action = delete $args{Action};
74            
75             croak "Action must be defined!\n" if not defined $action;
76              
77             my %sign_hash = %args;
78             my $timestamp = $self->_timestamp;
79              
80             $sign_hash{AWSAccessKeyId} = $self->AWSAccessKey;
81             $sign_hash{Action} = $action;
82             $sign_hash{Timestamp} = $timestamp;
83             $sign_hash{Version} = $self->version;
84             $sign_hash{SignatureVersion} = "2";
85             $sign_hash{SignatureMethod} = "HmacSHA256";
86              
87             my $sign_this = "POST\n";
88             $sign_this .= $self->_base_url_host . "\n";
89             $sign_this .= "/\n";
90              
91              
92             $sign_this .= $self->ua->www_form_urlencode(\%sign_hash);
93              
94             warn "QUERY TO SIGN: $sign_this" if $self->debug;
95             my $encoded = encode_base64(hmac_sha256($sign_this, $self->AWSSecretKey), '');
96              
97             my %params = (
98             Action => $action,
99             SignatureVersion => "2",
100             SignatureMethod => "HmacSHA256",
101             AWSAccessKeyId => $self->AWSAccessKey,
102             Timestamp => $timestamp,
103             Version => $self->version,
104             Signature => $encoded,
105             %args
106             );
107              
108             return \%params;
109             }
110              
111             sub _request {
112             my $self = shift;
113             my $params = shift;
114              
115             return $self->ua->post_form( $self->base_url, $params );
116             }
117              
118             sub _process {
119             my $self = shift;
120             my $data = shift;
121              
122             my $xml = XMLin( $data,
123             ForceArray => qr/(?:item|Errors)/i,
124             KeyAttr => '',
125             SuppressEmpty => undef,
126             );
127              
128             return $xml;
129             }
130              
131              
132             sub send {
133             my $self = shift;
134             my $request = $self->_sign(@_);
135             my $response = $self->_request($request);
136              
137             if ( $response->{success} ) {
138             my $xml = $self->_process( $response->{content} );
139             if ( defined $xml->{Errors} ) {
140             croak "Error: $response->{content}\n";
141             }
142             return $xml;
143             }
144              
145             croak "POST Request failed: $response->{status} $response->{reason} $response->{content}\n";
146             }
147              
148              
149             1;
150              
151             __END__