AWS-Lambda-Quick
view release on metacpan or search on metacpan
The hard part is configuring AWS to execute the code. Traditionally
you have to complete the following steps.
- Create a zip file containing your code
- Create (or update) an AWS Lambda function with this zip file
- Create a REST API with AWS Gateway API
- Configure a resource for that REST API for this script
- Set up a method and put method response for that resource
- Manage an integration and integration response for that resource
And then debug all the above things, a lot, and google weird error
messages it generates when you inevitably make a mistake.
This module provides a way to do all of this completely transparently
just by executing your script, without having to either interact with
the AWS Management Console nor directly use the awscli utility.
Simply include this module at the top of your script containing the
handler function:
use AWS::Lambda::Quick (
If you've only changed the source code and want to deploy a new version
you can just do that by setting the `AWS_LAMBDA_QUICK_UPDATE_CODE_ONLY`
enviroment variable:
shell$ AWS_LAMBDA_QUICK_UPDATE_CODE_ONLY=1 perl lambda-function.pl
In the interest of being as quick as possible, when this is environment
variable is enabled the URL for the upload is not computed and printed
out.
## Enabling debugging output
To gain a little more insight into what is going on you can set
the `AWS_LAMBDA_QUICK_DEBUG` environment variable to enabled
debugging to STDERR:
shell$ AWS_LAMBDA_QUICK_DEBUG=1 perl lambda-function.pl
updating function code
function code updated
updating function configuration
searching for existing role
found existing role
...
# AUTHOR
lib/AWS/Lambda/Quick.pm view on Meta::CPAN
=item Create a REST API with AWS Gateway API
=item Configure a resource for that REST API for this script
=item Set up a method and put method response for that resource
=item Manage an integration and integration response for that resource
=back
And then debug all the above things, a lot, and google weird error
messages it generates when you inevitably make a mistake.
This module provides a way to do all of this completely transparently
just by executing your script, without having to either interact with
the AWS Management Console nor directly use the awscli utility.
Simply include this module at the top of your script containing the
handler function:
use AWS::Lambda::Quick (
lib/AWS/Lambda/Quick.pm view on Meta::CPAN
If you've only changed the source code and want to deploy a new version
you can just do that by setting the C<AWS_LAMBDA_QUICK_UPDATE_CODE_ONLY>
enviroment variable:
shell$ AWS_LAMBDA_QUICK_UPDATE_CODE_ONLY=1 perl lambda-function.pl
In the interest of being as quick as possible, when this is environment
variable is enabled the URL for the upload is not computed and printed
out.
=head2 Enabling debugging output
To gain a little more insight into what is going on you can set
the C<AWS_LAMBDA_QUICK_DEBUG> environment variable to enabled
debugging to STDERR:
shell$ AWS_LAMBDA_QUICK_DEBUG=1 perl lambda-function.pl
updating function code
function code updated
updating function configuration
searching for existing role
found existing role
...
=head1 AUTHOR
lib/AWS/Lambda/Quick/Upload.pm view on Meta::CPAN
has role => default => 'perl-aws-lambda-quick';
has _role_arn => sub {
my $self = shift;
# if whatever we were passed in role was an actual ARN then we
# can just use that without any further lookups
if ( $self->role
=~ /^arn:(aws[a-zA-Z-]*)?:iam::\d{12}:role\/?[a-zA-Z_0-9+=,.@\-_\/]+$/
) {
$self->debug('using passed role arn');
return $self->role;
}
$self->debug('searching for existing role');
my $aws = $self->aws;
my $result = $aws->iam(
'get-role',
{
'role-name' => $self->role,
}
);
if ($result) {
$self->debug('found existing role');
return $result->{Role}{Arn};
}
$self->debug('creating new role');
$result = $self->aws_do(
'iam',
'create-role',
{
'role-name' => $self->role,
'description' =>
'Role for lambda functions created by AWS::Lambda::Quick. See https://metacpan.org/pod/AWS::Lambda::Quick for more info.',
'assume-role-policy-document' => <<'JSON',
{
"Version": "2012-10-17",
lib/AWS/Lambda/Quick/Upload.pm view on Meta::CPAN
"lambda.amazonaws.com",
"apigateway.amazonaws.com"
]
}
}
]
}
JSON
}
);
$self->debug('new role created');
$self->debug('attaching permissions to role');
$self->aws_do(
'iam',
'attach-role-policy',
{
'policy-arn' =>
'arn:aws:iam::aws:policy/service-role/AWSLambdaRole',
'role-name' => $self->role,
}
);
$self->aws_do(
'iam',
'attach-role-policy',
{
'policy-arn' =>
'arn:aws:iam::aws:policy/CloudWatchLogsFullAccess',
'role-name' => $self->role,
}
);
$self->debug('permissions attached to role');
return $result->{Role}{Arn};
};
### rest api attributes
has rest_api => default => 'perl-aws-lambda-quick';
has rest_api_id => sub {
my $self = shift;
# search existing apis
$self->debug('searching for existing rest api');
my $result = $self->aws_do(
'apigateway',
'get-rest-apis',
);
for ( @{ $result->{items} } ) {
next unless $_->{name} eq $self->rest_api;
$self->debug('found existing existing rest api');
return $_->{id};
}
# couldn't find it. Create a new one
$self->debug('creating new rest api');
$result = $self->aws_do(
'apigateway',
'create-rest-api',
{
name => $self->rest_api,
description =>
'Created by AWS::Lambda::Quick. See https://metacpan.org/pod/AWS::Lambda::Quick for more info.',
},
);
$self->debug('created new rest api');
return $result->{id};
};
has resource_id => sub {
my $self = shift;
# TODO: We shold probably make this configurable, right?
my $path = '/' . $self->name;
# search existing resources
$self->debug('searching of existing resource');
my $result = $self->aws_do(
'apigateway',
'get-resources',
{
'rest-api-id' => $self->rest_api_id,
}
);
for ( @{ $result->{items} } ) {
next unless $_->{path} eq $path;
$self->debug('found exiting resource');
return $_->{id};
}
# couldn't find it. Create a new one
$self->debug('creating new resource');
my $parent_id;
for ( @{ $result->{items} } ) {
if ( $_->{path} eq '/' ) {
$parent_id = $_->{id};
last;
}
}
unless ($parent_id) {
die q{Can't find '/' resource to create a new resource from!};
}
$result = $self->aws_do(
'apigateway',
'create-resource',
{
'rest-api-id' => $self->rest_api_id,
'parent-id' => $parent_id,
'path-part' => $self->name,
},
);
$self->debug('created new resource');
return $result->{id};
};
has greedy_resource_id => sub {
my $self = shift;
my $path = '/' . $self->name . '/{proxy+}';
# search existing resources
$self->debug('searching of existing greedy resource');
my $result = $self->aws_do(
'apigateway',
'get-resources',
{
'rest-api-id' => $self->rest_api_id,
}
);
for ( @{ $result->{items} } ) {
next unless $_->{path} eq $path;
$self->debug('found exiting resource');
return $_->{id};
}
# couldn't find it. Create a new one
$self->debug('creating new greedy resource');
$result = $self->aws_do(
'apigateway',
'create-resource',
{
'rest-api-id' => $self->rest_api_id,
'parent-id' => $self->resource_id,
'path-part' => '{proxy+}',
},
);
$self->debug('created new greedy resource');
return $result->{id};
};
### methods
sub upload {
my $self = shift;
my $function_arn = $self->_upload_function;
lib/AWS/Lambda/Quick/Upload.pm view on Meta::CPAN
sub _create_method {
my $self = shift;
my $resource_id = shift;
my @identifiers = (
'rest-api-id' => $self->rest_api_id,
'resource-id' => $resource_id,
'http-method' => 'ANY',
);
$self->debug('checking for existing method');
# get the current method
my $result = $self->aws->apigateway(
'get-method', {@identifiers},
);
if ($result) {
$self->debug('found existing method');
return ();
}
$self->debug('putting new method');
$self->aws_do(
'apigateway',
'put-method',
{
@identifiers,
'authorization-type' => 'NONE',
},
);
$self->debug('new method put');
return ();
}
sub _create_method_response {
my $self = shift;
my $resource_id = shift;
my $identifiers = {
'rest-api-id' => $self->rest_api_id,
'resource-id' => $resource_id,
'http-method' => 'ANY',
'status-code' => 200,
};
$self->debug('checking for existing method response');
# get the current method response
my $result = $self->aws->apigateway(
'get-method-response', $identifiers,
);
if ($result) {
$self->debug('found existing method response');
return ();
}
$self->debug('putting new method response');
$self->aws_do(
'apigateway',
'put-method-response',
$identifiers,
);
$self->debug('new method response put');
return ();
}
sub _create_integration {
my $self = shift;
my $function_arn = shift;
my $resource_id = shift;
my $identifiers = {
lib/AWS/Lambda/Quick/Upload.pm view on Meta::CPAN
'resource-id' => $resource_id,
'http-method' => 'ANY',
};
# according the the documentation at https://docs.aws.amazon.com/cli/latest/reference/apigateway/put-integration.html
# the uri has the form arn:aws:apigateway:{region}:{subdomain.service|service}:path|action/{service_api}
# "lambda:path/2015-03-31/functions" is the {subdomain.service|service}:path|action for lambda functions
my $uri
= "arn:aws:apigateway:@{[ $self->region ]}:lambda:path/2015-03-31/functions/$function_arn/invocations";
$self->debug('checking for existing integration');
# get the current method response
my $result = $self->aws->apigateway(
'get-integration', $identifiers,
);
if ($result) {
$self->debug('found existing integration');
return ();
}
$self->debug('putting new integration');
$self->aws_do(
'apigateway',
'put-integration',
{
%{$identifiers},
type => 'AWS_PROXY',
'integration-http-method' => 'POST',
'credential' => $self->_role_arn,
uri => $uri,
}
);
$self->debug('new integration put');
return ();
}
sub _create_integration_response {
my $self = shift;
my $resource_id = shift;
my $identifiers = {
'rest-api-id' => $self->rest_api_id,
'resource-id' => $resource_id,
'http-method' => 'ANY',
'status-code' => 200,
};
$self->debug('checking for existing integration response');
# get the current method response
my $result = $self->aws->apigateway(
'get-integration-response', $identifiers,
);
if ($result) {
$self->debug('found existing integration response');
return ();
}
$self->debug('putting new integration');
$self->aws_do(
'apigateway',
'put-integration-response',
{
%{$identifiers},
'selection-pattern' => q{},
}
);
$self->debug('new integration put');
return ();
}
sub _upload_function {
my $self = shift;
my $update_type = $self->update_type;
my $region = $self->region;
lib/AWS/Lambda/Quick/Upload.pm view on Meta::CPAN
my $pv = $region eq 'me-south-1' ? 3 : 4;
push @{$layers},
"arn:aws:lambda:$region:445285296882:layer:perl-5-30-paws:$pv";
next;
}
die "Layer '$layer' is neither a known named layer nor a layer arn";
}
if ( $update_type eq 'create-function' ) {
$self->debug('creating new function');
my $result = $self->aws_do(
'lambda',
'create-function',
{
'function-name' => $self->name,
'role' => $self->_role_arn,
'region' => $region,
'runtime' => 'provided',
'zip-file' => $self->zip_file_blob,
'handler' => 'handler.handler',
'layers' => $layers,
'timeout' => $self->timeout,
'memory-size' => $self->memory_size,
}
);
$self->debug('new function created');
return $result->{FunctionArn};
}
$self->debug('updating function code');
my $result = $self->aws_do(
'lambda',
'update-function-code',
{
'function-name' => $self->name,
'zip-file' => $self->zip_file_blob,
}
);
$self->debug('function code updated');
$self->debug('updating function configuration');
$self->aws_do(
'lambda',
'update-function-configuration',
{
'function-name' => $self->name,
'role' => $self->_role_arn,
'region' => $region,
'runtime' => 'provided',
'handler' => 'handler.handler',
'layers' => $layers,
'timeout' => $self->timeout,
'memory-size' => $self->memory_size,
}
);
$self->debug('function congifuration updated');
return $result->{FunctionArn};
}
# just like $self->aws->$method but throws exception on error
sub aws_do {
my $self = shift;
my $method = shift;
my $aws = $self->aws;
my $result = $aws->$method(@_);
lib/AWS/Lambda/Quick/Upload.pm view on Meta::CPAN
my $code = $AWS::CLIWrapper::Error->{Code};
my $message = $AWS::CLIWrapper::Error->{Message};
die "AWS CLI failure when calling $method $_[0] '$code': $message";
}
sub encode_json($) {
return JSON::PP->new->ascii->canonical(1)->allow_nonref(1)->encode(shift);
}
sub debug {
my $self = shift;
return unless $ENV{AWS_LAMBDA_QUICK_DEBUG};
for (@_) {
print STDERR "$_\n" or die "Can't write to fh: $!";
}
return ();
}
sub just_update_function_code {
my $self = shift;
( run in 1.706 second using v1.01-cache-2.11-cpan-49f99fa48dc )