├── .gitignore ├── cpanfile ├── lib └── AWS │ └── Lambda │ ├── Toolkit.pm │ └── Toolkit │ └── Config.pm ├── bin ├── build-lambda-function ├── init-lambda-perl ├── install-lambda-deps ├── install-lambda-perl-runtime ├── build-lambda-deps-layer ├── install-lambda-function └── build-lambda-perl-runtime ├── Dockerfile ├── Makefile ├── hello ├── dist.ini ├── share └── bootstrap └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | local/ 2 | dzil-local/ 3 | cpanfile.snapshot 4 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Moo'; 2 | requires 'Path::Tiny'; 3 | requires 'YAML::PP'; 4 | requires 'File::ShareDir'; 5 | requires 'AWS::CLIWrapper'; 6 | -------------------------------------------------------------------------------- /lib/AWS/Lambda/Toolkit.pm: -------------------------------------------------------------------------------- 1 | package AWS::Lambda::Toolkit; 2 | our $VERSION = '0.01'; 3 | #ABSTRACT: A toolkit to build AWS Lambda functions in Perl 4 | 5 | 1; 6 | -------------------------------------------------------------------------------- /bin/build-lambda-function: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use AWS::Lambda::Toolkit::Config; 7 | 8 | my $config = AWS::Lambda::Toolkit::Config->new; 9 | 10 | system "rm layers/" . $config->lambda_name . '.zip'; 11 | system "zip -r layers/" . $config->lambda_name . '.zip lambda lib/'; 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM lambda-perl-runtime:5.28 2 | 3 | COPY cpanfile . 4 | 5 | RUN /opt/runtime/bin/cpanm -n --no-man-pages -l /opt/layer1-lib --installdeps . 6 | 7 | # The layer.zip file from the runtime is still present, so we delete it, or else 8 | # the zip command will add the files to the existing file 9 | RUN rm /layer.zip ; \ 10 | cd /opt ; \ 11 | zip -r /layer.zip layer1-lib 12 | -------------------------------------------------------------------------------- /bin/init-lambda-perl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use AWS::Lambda::Toolkit::Config; 4 | 5 | # load lambda-perl.config in the current dir 6 | 7 | my $config = AWS::Lambda::Toolkit::Config->new( 8 | contents => { 9 | lambda_name => 'test1', 10 | perl_version => '5.28', 11 | region => 'us-west-2', 12 | handler => 'lambda.main' 13 | } 14 | ); 15 | 16 | $config->persist; 17 | 18 | -------------------------------------------------------------------------------- /bin/install-lambda-deps: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use AWS::CLIWrapper; 6 | use AWS::Lambda::Toolkit::Config; 7 | 8 | my $config = AWS::Lambda::Toolkit::Config->new; 9 | 10 | my $cli = AWS::CLIWrapper->new( 11 | region => $config->region, 12 | ); 13 | 14 | my $ret = $cli->lambda('publish-layer-version', { 15 | 'layer-name' => $config->deps_layer_name, 16 | 'zip-file' => 'fileb://' . $config->deps_layer_file, 17 | }); 18 | 19 | my $new_layer_arn = $ret->{ LayerVersionArn }; 20 | 21 | $config->contents->{ deps_layer } = $new_layer_arn; 22 | 23 | $config->persist; 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | dist: 2 | cpanm -n -l dzil-local Dist::Zilla 3 | PATH=$(PATH):dzil-local/bin PERL5LIB=dzil-local/lib/perl5 dzil authordeps --missing | cpanm -n -l dzil-local/ 4 | PATH=$(PATH):dzil-local/bin PERL5LIB=dzil-local/lib/perl5 dzil build 5 | 6 | upload-runtimes: 7 | aws lambda --region us-west-2 publish-layer-version --layer-name Perl516 --zip-file fileb://runtimes/5.16/layer.zip 8 | aws lambda --region us-west-2 publish-layer-version --layer-name Perl528 --zip-file fileb://runtimes/5.28/layer.zip 9 | 10 | upload-code: 11 | aws lambda --region us-west-2 update-function-code --function-name Perl528Lambda --zip-file fileb://./layers/lambda.zip 12 | 13 | explore-lambda-environment: 14 | docker run --rm -ti --entrypoint /bin/bash lambci/lambda:latest 15 | -------------------------------------------------------------------------------- /bin/install-lambda-perl-runtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use AWS::CLIWrapper; 6 | use AWS::Lambda::Toolkit::Config; 7 | 8 | my $config = AWS::Lambda::Toolkit::Config->new; 9 | 10 | my $cli = AWS::CLIWrapper->new( 11 | region => $config->region, 12 | ); 13 | 14 | my $ret = $cli->lambda('publish-layer-version', { 15 | 'layer-name' => 'Perl528', 16 | 'zip-file' => 'fileb://./layers/perl_5.28.zip' 17 | }); 18 | 19 | if (not $ret) { 20 | print "Something went wrong uploading the layer to Lambda. Please verify you have a correctly configured 'aws' CLI utility\n"; 21 | exit 1; 22 | } 23 | 24 | my $new_layer_arn = $ret->{ LayerVersionArn }; 25 | 26 | $config->contents->{ perl_runtime_layer } = $new_layer_arn; 27 | 28 | $config->persist; 29 | -------------------------------------------------------------------------------- /hello: -------------------------------------------------------------------------------- 1 | 2 | use v5.10; 3 | use Data::Dumper; 4 | use HTTP::Tiny; 5 | 6 | sub handler { 7 | my $event = shift; 8 | 9 | # this logs to the Cloudwatch log of the Lambda function 10 | say STDERR Dumper($event); 11 | 12 | say STDERR Dumper(\@INC, \%ENV); 13 | 14 | # this is the stuff that gets returned to the invoker 15 | return 40+2 . " is the answer. Running Perl $^V from $^X"; 16 | } 17 | 18 | sub error { 19 | die "I've died"; 20 | } 21 | 22 | sub https_request { 23 | my $ua = HTTP::Tiny->new; 24 | my $response = $ua->get('https://metacpan.org/recent'); 25 | die $response->{ content } if (not $response->{ success }); 26 | 27 | my ($results) = ($response->{ content } =~ m/(\d+,\d+)/); 28 | return "There are currently $results packages on CPAN"; 29 | } 30 | 31 | sub ls_opt { 32 | print STDERR `ls -la /opt/layer1-lib/lib/perl5`; 33 | } 34 | 35 | 1; 36 | -------------------------------------------------------------------------------- /dist.ini: -------------------------------------------------------------------------------- 1 | name = AWS-Lambda-Toolkit 2 | author = Jose Luis Martinez 3 | license = Apache_2_0 4 | copyright_holder = CAPSiDE 5 | copyright_year = 2019 6 | main_module = lib/AWS/Lambda/Toolkit.pm 7 | 8 | [VersionFromMainModule] 9 | 10 | [Git::GatherDir] 11 | ; LICENSE gets generated by Dist::Zilla, but we want it in 12 | ; the source repo so github can detect it 13 | exclude_match=LICENSE 14 | exclude_match=layers/ 15 | 16 | [MakeMaker] 17 | 18 | [@Git] 19 | allow_dirty = dist.ini 20 | allow_dirty = Changes 21 | allow_dirty = README.md 22 | 23 | [ExecDir] 24 | 25 | [ShareDir] 26 | dir = share 27 | 28 | [Prereqs::FromCPANfile] 29 | 30 | [MetaYAML] 31 | [MetaJSON] 32 | 33 | [MetaResources] 34 | repository.web = https://github.com/pplu/perl-lambda-byor 35 | repository.url = https://github.com/pplu/perl-lambda-byor.git 36 | repository.type = git 37 | bugtracker.web = https://github.com/pplu/perl-lambda-byor/issues 38 | 39 | [License] 40 | 41 | [RunExtraTests] 42 | [TestRelease] 43 | 44 | [Manifest] 45 | 46 | [Git::Tag] 47 | tag_format = release-%v 48 | 49 | [Git::Push] 50 | 51 | [ConfirmRelease] 52 | [UploadToCPAN] 53 | -------------------------------------------------------------------------------- /bin/build-lambda-deps-layer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Path::Tiny; 7 | use AWS::Lambda::Toolkit::Config; 8 | 9 | my $config = AWS::Lambda::Toolkit::Config->new; 10 | my $perl_version = $config->perl_version or die "perl_version expected in config file"; 11 | my $lambda_name = $config->lambda_name or die "lambda_name expected in config file"; 12 | 13 | my $build_deps_dockerfile = <tempdir("lambdadeps_XXXXXX", CLEANUP => 0); 30 | 31 | path("$tmp_dir/Dockerfile")->spew($build_deps_dockerfile); 32 | path("./cpanfile")->copy($tmp_dir); 33 | 34 | system "docker build -t lambda-deps-$lambda_name:latest $tmp_dir"; 35 | system "docker run --name get_runtime lambda-deps-$lambda_name:latest echo ''"; 36 | system "docker cp get_runtime:/layer.zip $tmp_dir"; 37 | system "docker rm get_runtime"; 38 | 39 | path("$tmp_dir/layer.zip")->copy($config->deps_layer_file); 40 | -------------------------------------------------------------------------------- /bin/install-lambda-function: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use AWS::CLIWrapper; 7 | use AWS::Lambda::Toolkit::Config; 8 | use JSON::MaybeXS qw/encode_json/; 9 | 10 | my $config = AWS::Lambda::Toolkit::Config->new; 11 | 12 | 13 | my $cli = AWS::CLIWrapper->new( 14 | region => $config->region, 15 | ); 16 | 17 | my $ret = $cli->lambda('get-function', { 18 | 'function-name' => $config->lambda_name, 19 | }); 20 | 21 | sub env_to_cli_param { 22 | my $env = shift; 23 | return encode_json({ Variables => $env }); 24 | } 25 | 26 | my $role = $config->role or die "Please configure a role in the config file"; 27 | my $handler = $config->handler or die "Please configure a handler in the config file"; 28 | 29 | if (not defined $ret) { 30 | my $creation = $cli->lambda('create-function', { 31 | 'function-name' => $config->lambda_name, 32 | 'runtime' => 'provided', 33 | 'role' => $role, 34 | 'handler' => $handler, 35 | 'zip-file' => 'fileb://' . $config->function_layer_file, 36 | 'layers' => [ 37 | $config->perl_runtime_layer, 38 | $config->deps_layer, 39 | ], 40 | 'environment' => env_to_cli_param($config->env), 41 | 'timeout' => $config->timeout, 42 | 'memory-size' => $config->memory, 43 | }); 44 | } else { 45 | my $update_code = $cli->lambda('update-function-code', { 46 | 'function-name' => $config->lambda_name, 47 | 'zip-file' => 'fileb://' . $config->function_layer_file, 48 | }); 49 | 50 | my $update_layers = $cli->lambda('update-function-configuration', { 51 | 'role' => $role, 52 | 'handler' => $handler, 53 | 'function-name' => $config->lambda_name, 54 | 'layers' => [ 55 | $config->perl_runtime_layer, 56 | $config->deps_layer, 57 | ], 58 | 'environment' => env_to_cli_param($config->env), 59 | 'timeout' => $config->timeout, 60 | 'memory-size' => $config->memory, 61 | }); 62 | } 63 | 64 | -------------------------------------------------------------------------------- /lib/AWS/Lambda/Toolkit/Config.pm: -------------------------------------------------------------------------------- 1 | package AWS::Lambda::Toolkit::Config; 2 | use Moo; 3 | use YAML::PP; 4 | use Path::Tiny 5 | 6 | has yaml => (is => 'ro', default => sub { YAML::PP->new }); 7 | 8 | has project_dir => (is => 'ro', default => sub { 9 | Path::Tiny->cwd 10 | }); 11 | 12 | has layers_dir => (is => 'ro', lazy => 1, default => sub { 13 | my $self = shift; 14 | path($self->project_dir, 'layers'); 15 | }); 16 | 17 | has config_file => (is => 'ro', lazy => 1, default => sub { 18 | my $self = shift; 19 | path($self->project_dir, 'lambda-perl.config'); 20 | }); 21 | 22 | has contents => (is => 'ro', lazy => 1, default => sub { 23 | my $self = shift; 24 | $self->yaml->load_file($self->config_file); 25 | }); 26 | 27 | sub function_layer_file { 28 | my $self = shift; 29 | path($self->layers_dir, $self->lambda_name . '.zip'); 30 | } 31 | 32 | sub deps_layer_name { 33 | my $self = shift; 34 | return 'deps_' . $self->lambda_name; 35 | } 36 | 37 | sub deps_layer_file { 38 | my $self = shift; 39 | 40 | path($self->layers_dir, $self->deps_layer_name . '.zip'); 41 | } 42 | 43 | sub lambda_name { 44 | my $self = shift; 45 | return $self->contents->{ lambda_name }; 46 | } 47 | 48 | sub perl_version { 49 | my $self = shift; 50 | return $self->contents->{ perl_version }; 51 | } 52 | 53 | sub region { 54 | my $self = shift; 55 | return $self->contents->{ region }; 56 | } 57 | 58 | sub perl_runtime_layer { 59 | my $self = shift; 60 | return $self->contents->{ perl_runtime_layer }; 61 | } 62 | 63 | sub deps_layer { 64 | my $self = shift; 65 | return $self->contents->{ deps_layer }; 66 | } 67 | 68 | sub role { 69 | my $self = shift; 70 | return $self->contents->{ role }; 71 | } 72 | 73 | sub handler { 74 | my $self = shift; 75 | return $self->contents->{ handler }; 76 | } 77 | 78 | sub env { 79 | my $self = shift; 80 | return $self->contents->{ env } // {}; 81 | } 82 | 83 | sub timeout { 84 | my $self = shift; 85 | return $self->contents->{ timeout } // 30; 86 | } 87 | 88 | sub memory { 89 | my $self = shift; 90 | return $self->contents->{ memory } // 128; 91 | } 92 | 93 | sub persist { 94 | my $self = shift; 95 | $self->yaml->dump_file( 96 | $self->config_file, 97 | $self->contents 98 | ); 99 | } 100 | 1; 101 | -------------------------------------------------------------------------------- /bin/build-lambda-perl-runtime: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Path::Tiny; 7 | use File::ShareDir qw/dist_file/; 8 | use AWS::Lambda::Toolkit::Config; 9 | 10 | my $config = AWS::Lambda::Toolkit::Config->new; 11 | 12 | my $version = $config->perl_version or die "perl_version expected in config file"; 13 | my $bootstrap_file = dist_file('AWS-Lambda-Toolkit', 'bootstrap'); 14 | 15 | # List from https://www.cpan.org/src/ 16 | my $versions = { 17 | '5.28' => '5.28.1', 18 | '5.26' => '5.26.3', 19 | '5.24' => '5.24.4', 20 | '5.22' => '5.22.4', 21 | '5.20' => '5.20.3', 22 | '5.18' => '5.18.4', 23 | '5.16' => '5.16.3', 24 | '5.14' => '5.14.4', 25 | '5.12' => '5.12.5', 26 | '5.10' => '5.10.1', 27 | }; 28 | 29 | my $cpanfile = <{ $version }; 34 | 35 | die "I don't know the minor version for Perl $version" if (not defined $minor_perl_version); 36 | 37 | my $build_runtime_dockerfile = <tempdir("lambdaruntime_XXXXXX", CLEANUP => 0); 67 | 68 | path("$tmp_dir/Dockerfile")->spew($build_runtime_dockerfile); 69 | path("$tmp_dir/cpanfile")->spew($cpanfile); 70 | path($bootstrap_file)->copy("$tmp_dir/bootstrap"); 71 | 72 | system "docker build -t lambda-perl-runtime:$version $tmp_dir"; 73 | system "docker run --name get_runtime lambda-perl-runtime:$version echo ''"; 74 | system "docker cp get_runtime:/layer.zip $tmp_dir"; 75 | system "docker rm get_runtime"; 76 | 77 | $config->layers_dir->mkpath; 78 | 79 | path("$tmp_dir/layer.zip")->copy($config->layers_dir . "/perl_$version.zip"); 80 | -------------------------------------------------------------------------------- /share/bootstrap: -------------------------------------------------------------------------------- 1 | #!/opt/runtime/bin/perl 2 | 3 | # (c) 2018 CAPSiDE 4 | # Jose Luis Martinez Torres 5 | # Licensed under Apache 2.0 6 | # Implements a custom runtime handler for Perl. See: 7 | # https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html 8 | # https://docs.aws.amazon.com/lambda/latest/dg/runtimes-walkthrough.html 9 | # for more information 10 | 11 | use strict; 12 | use warnings; 13 | 14 | use lib '/var/task', '/opt/layer1-lib/lib/perl5'; 15 | 16 | use JSON::MaybeXS; 17 | my $json_parser = JSON::MaybeXS->new; 18 | 19 | use HTTP::Tiny; 20 | my $ua = HTTP::Tiny->new; 21 | 22 | my $aws_lambda_runtime_api = $ENV{ AWS_LAMBDA_RUNTIME_API } or die "No AWS_LAMBDA_RUNTIME_API env var"; 23 | my $api_base = "http://$aws_lambda_runtime_api/2018-06-01"; 24 | my $handler = $ENV{ _HANDLER } or die "No _HANDLER env var"; 25 | 26 | my ($file, $entrypoint) = split /\./, $handler, 2; 27 | die "Can't derive the file to require from the handler" if (not defined $file); 28 | die "Can't find the file to require from the handler" if (not -e $file); 29 | die "Can't derive the function to call from the handler" if (not defined $entrypoint); 30 | 31 | eval { 32 | require $file; 33 | }; 34 | # TODO: call the init error URL 35 | die "Compilation error: $@" if ($@); 36 | 37 | while (1) { 38 | my $request = $ua->get("$api_base/runtime/invocation/next"); 39 | die "Error when getting request: " . $request->{ content } if (not $request->{ success }); 40 | 41 | my $request_id = $request->{ headers }->{'lambda-runtime-aws-request-id'}; 42 | die "Didn't get the request-id in the expected header" if (not defined $request_id); 43 | 44 | my $event = $json_parser->decode($request->{ content }); 45 | 46 | my $result = eval { 47 | # no strict refs is so we can call a subroutine whose name is stored in variable 48 | # entrypoint while 'use strict' is on 49 | no strict 'refs'; 50 | &$entrypoint($event); 51 | }; 52 | if ($@) { 53 | my $error_message = $@; 54 | $error_message =~ s/"/\\\"/g; 55 | my $error_body = sprintf '{"errorMessage":"%s","errorType":"UnhandledException"}', $error_message; 56 | 57 | my $error_url = "$api_base/runtime/invocation/$request_id/error"; 58 | my $result = $ua->post($error_url, { 59 | content => $error_body, 60 | }); 61 | } else { 62 | my $success_url = "$api_base/runtime/invocation/$request_id/response"; 63 | my $result = $ua->post($success_url, { 64 | content => $result, 65 | }); 66 | die "Error when reporting response: " . $result->{ content } if (not $result->{ success }); 67 | } 68 | } 69 | 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Custom Lambda Runtime for Perl 2 | ============================== 3 | 4 | This is an experiment to get Perl support in Lambda. 5 | 6 | It consists of an implementation of the `bootstrap` script as [AWS documentation suggests](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). 7 | 8 | The implementation is not complete yet, though it does work to some extent :) 9 | 10 | Install AWS::Lambda::Toolkit 11 | ============================ 12 | 13 | ``` 14 | cpanm AWS::Lambda::Toolkit 15 | ``` 16 | 17 | (Note: you might need to install the `cpanminus` system package to get access to the cpanm command). 18 | 19 | This installs the utilities that help us bootstrap the Lambda environment. 20 | 21 | Note2: While the distribution is not uploaded to CPAN, you can install this way: 22 | 23 | ``` 24 | git clone git@github.com:pplu/perl-lambda-byor.git 25 | make dist 26 | cpanm AWS-Lambda-Toolkit-0.01.tar.gz 27 | ``` 28 | 29 | Create your Lambda function 30 | =========================== 31 | 32 | - Create a directory for your Lambda project: 33 | 34 | ``` 35 | mkdir my-lambda-project 36 | cd my-lambda-project 37 | ``` 38 | 39 | - Initialize the directory as a lambda project: 40 | Create a config file named `lambda-perl.config` for your project 41 | ``` 42 | init-lambda-perl 43 | ``` 44 | - Create a runtime layer 45 | 46 | Although Perl is installed in the Lambda environment, since AWS bases the OS on CentOS, the Perl runtime is quite old (5.16, where at the time of this 47 | writing Perl has gone through 5.18, 5.20, 5.22, 5.24, 5.26 and 5.28 releases). Added to that, the Perl installation is lacking the 'perl-core' yum 48 | package, which makes that Perl very hard to deal with, since it's lacking almost all of the "core" modules that you would normally find installed 49 | on a system. Because of that, we're going to use custom compiled, modern versions of Perl as the runtime. 50 | 51 | ``` 52 | build-lambda-perl-runtime 5.28 53 | ``` 54 | 55 | will generate a `layers/perl_5.28.zip`. This can be uploaded to Lambda as a layer with `install-lambda-perl-runtime`. 56 | 57 | - Create a cpanfile with the dependencies of your project 58 | 59 | ``` 60 | requires 'Moo'; 61 | ``` 62 | 63 | Generate the dependencies layer with: 64 | 65 | ``` 66 | build-lambda-deps-layer 67 | ``` 68 | 69 | this will generate another zip file in the layers directory that can be uploaded with `install-lambda-deps` 70 | 71 | - Write code. Start a file called `lambda`. Write a Perl subroutine: 72 | 73 | ``` 74 | sub main { 75 | return "I'm alive!" 76 | } 77 | ``` 78 | 79 | Create a zip with with the `build-lambda-function`. This creates a zip with the code in the layers directory 80 | 81 | Configure the `lambda-perl.config` file: 82 | 83 | Add the config keys: 84 | 85 | ``` 86 | handler: lambda.main 87 | role: arn:aws:iam::XXXXX:role/XXXXX 88 | ``` 89 | 90 | handler is formed by the name of the file that contains the lambda function (`lambda` in this example), followed 91 | by `.` and the subroutine to invoke in that file. 92 | 93 | Invoke `install-lambda-function` to up create the lambda function in AWS. 94 | 95 | - Invoke the function 96 | 97 | You can invoke the function with the AWS CLI, or with Paws: `paws Lambda --region x Invoke FunctionName TheNameOfYourLambda Payload ''` 98 | 99 | The bootstrap script will require the `lambda` file (or whatever the handler of the Lambda function tells it to), and will invoke the `main` sub in 100 | the lambda file with the parsed JSON payload passed to the function as a hashref as it's first argument. 101 | 102 | TODO 103 | ==== 104 | 105 | Make bootstrap include '/var/task/lib' so the libs in your project directory are usable from the lambda function 106 | 107 | automatically encode the return value from the function to JSON? (should it be overridable?) 108 | 109 | Adaptor for ALB incoming message format to Plack environment 110 | 111 | Follow all the Processing Tasks in [custom runtimes section](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) 112 | 113 | Handle errors in compilation phase of the Lambda function 114 | 115 | See if Dist::Zilla can build the zip for distributing to Lambda 116 | 117 | Notes 118 | ===== 119 | 120 | Finding out what you can or can't do inside the Lambda runtime can be exhausting if you do it directly inside Lambda. The `make explore-lambda-environment` target 121 | runs a shell inside a Docker container that resembles the Lambda runtime environment. 122 | 123 | License 124 | ======= 125 | 126 | This software is released under an Apache 2.0 127 | 128 | Copyright 129 | ========= 130 | 131 | (c) 2018 CAPSiDE 132 | 133 | Author 134 | ====== 135 | 136 | Jose Luis Martinez Torres 137 | 138 | 139 | 140 | --------------------------------------------------------------------------------