lambda_wrap 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/lambda_wrap.rb +5 -0
- data/lib/lambda_wrap/api_gateway_manager.rb +100 -0
- data/lib/lambda_wrap/aws_setup.rb +20 -0
- data/lib/lambda_wrap/dynamo_db_manager.rb +95 -0
- data/lib/lambda_wrap/lambda_manager.rb +115 -0
- data/lib/lambda_wrap/zip_file_generator.rb +63 -0
- metadata +65 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5f48ab03f6ee01f4084324fd582489720820a060
|
4
|
+
data.tar.gz: f7a1b100285e4fd602f654211dc0ac6a34cf1a8c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ed51f65ba43fe439c31319287b33e4f04e5f59f1d4385c05408713354e1529a20f1cf99f83ae1bca4ce7866fb9a7cd9af2ed504064b4e51eb29b5ebd7a77dbf8
|
7
|
+
data.tar.gz: 4fece3ef3a2758a300cb838ab295fdabbf502a3576b30fd307e19666b0061594fc3d50438b4becc1942e7057eb414f2b767fbeec96e0c510fe78ce2b1c5bdde0
|
data/lib/lambda_wrap.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative 'aws_setup'
|
3
|
+
|
4
|
+
module LambdaWrap
|
5
|
+
|
6
|
+
class ApiGatewayManager
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
AwsSetup.new.validate()
|
10
|
+
# AWS api gateway client
|
11
|
+
@client = Aws::APIGateway::Client.new()
|
12
|
+
# path to apigateway-importer jar
|
13
|
+
@jarpath = File.join(Dir.tmpdir, 'aws-apigateway-importer-1.0.3-SNAPSHOT-jar-with-dependencies.jar')
|
14
|
+
end
|
15
|
+
|
16
|
+
def download_apigateway_importer(s3_bucket, s3_key)
|
17
|
+
unless File.exist?(@jarpath)
|
18
|
+
puts 'Downloading aws-apigateway-importer jar'
|
19
|
+
s3 = Aws::S3::Client.new
|
20
|
+
obj = s3.get_object(response_target:@jarpath, bucket:s3_bucket, key:s3_key)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def setup_apigateway(api_name, env, swagger_file)
|
25
|
+
|
26
|
+
# ensure API is created
|
27
|
+
api_id = get_existing_rest_api(api_name)
|
28
|
+
api_id = setup_apigateway_create_rest_api(api_name) if !api_id
|
29
|
+
|
30
|
+
# create resources
|
31
|
+
setup_apigateway_create_resources(api_id, swagger_file)
|
32
|
+
|
33
|
+
# create stages
|
34
|
+
create_stages(api_id, env)
|
35
|
+
|
36
|
+
# return URI of created stage
|
37
|
+
return "https://#{api_id}.execute-api.#{ENV['AWS_REGION']}.amazonaws.com/#{env}/"
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
def shutdown_apigateway(api_name, env)
|
42
|
+
|
43
|
+
api_id = get_existing_rest_api(api_name)
|
44
|
+
delete_stage(api_id, env)
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_existing_rest_api(api_name)
|
49
|
+
|
50
|
+
apis = @client.get_rest_apis({limit: 500}).data
|
51
|
+
api = apis.items.select{ |api| api.name == api_name}.first()
|
52
|
+
|
53
|
+
if (api)
|
54
|
+
return api.id
|
55
|
+
else
|
56
|
+
return nil
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def setup_apigateway_create_rest_api(api_name)
|
62
|
+
|
63
|
+
puts 'Creating API with name ' + api_name
|
64
|
+
api = @client.create_rest_api({name: api_name})
|
65
|
+
|
66
|
+
return api.id
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
def setup_apigateway_create_resources(api_id, swagger_file)
|
71
|
+
|
72
|
+
raise 'API ID not provided' if !api_id
|
73
|
+
|
74
|
+
cmd = "java -jar #{@jarpath} --update #{api_id} --region #{ENV['AWS_REGION']} #{swagger_file}"
|
75
|
+
raise 'API gateway not created' if !system(cmd)
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_stages(api_id, env)
|
80
|
+
|
81
|
+
deployment_description = 'Deployment of service to ' + env
|
82
|
+
deployment = @client.create_deployment({rest_api_id: api_id, stage_name: env, cache_cluster_enabled: false, description: deployment_description, variables: { "environment" => env}}).data
|
83
|
+
puts deployment
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete_stage(api_id, env)
|
88
|
+
|
89
|
+
begin
|
90
|
+
@client.delete_stage({rest_api_id: api_id, stage_name: env})
|
91
|
+
puts 'Deleted API gateway stage ' + env
|
92
|
+
rescue Aws::APIGateway::Errors::NotFoundException
|
93
|
+
puts 'API Gateway stage ' + env + ' does not exist. Nothing to delete.'
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
|
3
|
+
module LambdaWrap
|
4
|
+
|
5
|
+
class AwsSetup
|
6
|
+
|
7
|
+
def validate()
|
8
|
+
|
9
|
+
# validate settings
|
10
|
+
raise 'AWS_ACCESS_KEY_ID not set' if !ENV['AWS_ACCESS_KEY_ID']
|
11
|
+
raise 'AWS_SECRET_ACCESS_KEY not set' if !ENV['AWS_SECRET_ACCESS_KEY']
|
12
|
+
raise 'AWS_REGION not set' if !ENV['AWS_REGION']
|
13
|
+
|
14
|
+
Aws::use_bundled_cert!
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative 'aws_setup'
|
3
|
+
|
4
|
+
module LambdaWrap
|
5
|
+
|
6
|
+
class DynamoDbManager
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
AwsSetup.new.validate()
|
10
|
+
# AWS dynamodb client
|
11
|
+
@client = Aws::DynamoDB::Client.new()
|
12
|
+
end
|
13
|
+
|
14
|
+
def publish_database(table_name, attribute_definitions, key_schema, read_capacity, write_capacity)
|
15
|
+
|
16
|
+
has_updates = false
|
17
|
+
|
18
|
+
# figure out whether the table exists
|
19
|
+
begin
|
20
|
+
table_details = @client.describe_table(table_name: table_name).table
|
21
|
+
rescue Aws::DynamoDB::Errors::ResourceNotFoundException
|
22
|
+
end
|
23
|
+
|
24
|
+
if (table_details)
|
25
|
+
wait_until_table_available(table_name) if (table_details.table_status != 'ACTIVE')
|
26
|
+
|
27
|
+
if (read_capacity > table_details.provisioned_throughput.read_capacity_units ||
|
28
|
+
write_capacity > table_details.provisioned_throughput.write_capacity_units)
|
29
|
+
puts "Updating new read/write capacity for table #{table_name}.
|
30
|
+
Read #{table_details.provisioned_throughput.read_capacity_units} ==> #{read_capacity}.
|
31
|
+
Write #{table_details.provisioned_throughput.write_capacity_units} ==> #{write_capacity}."
|
32
|
+
table = @client.update_table({
|
33
|
+
table_name: table_name,
|
34
|
+
provisioned_throughput: { read_capacity_units: read_capacity, write_capacity_units: write_capacity }
|
35
|
+
})
|
36
|
+
has_updates = true
|
37
|
+
else
|
38
|
+
puts "Table #{table_name} already exists and the desired read capacity of #{read_capacity} and write capacity of #{write_capacity} has at least been configured. Downgrading capacity units is not supported. No changes were applied."
|
39
|
+
end
|
40
|
+
else
|
41
|
+
puts "Creating table #{table_name}."
|
42
|
+
ad = attribute_definitions || [{ attribute_name: "Id", attribute_type: "S" }]
|
43
|
+
ks = key_schema || [{ attribute_name: "Id", key_type: "HASH" }]
|
44
|
+
table = @client.create_table({
|
45
|
+
attribute_definitions: ad,
|
46
|
+
table_name: table_name,
|
47
|
+
key_schema: ks,
|
48
|
+
provisioned_throughput: {read_capacity_units: read_capacity, write_capacity_units: write_capacity}
|
49
|
+
})
|
50
|
+
has_updates = true
|
51
|
+
end
|
52
|
+
|
53
|
+
if (has_updates)
|
54
|
+
wait_until_table_available(table_name)
|
55
|
+
puts "DynamoDB table #{table_name} is now fully available."
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def delete_database(table_name)
|
61
|
+
|
62
|
+
begin
|
63
|
+
table_details = @client.describe_table(table_name: table_name).table
|
64
|
+
wait_until_table_available(table_name) if (table_details.table_status != 'ACTIVE')
|
65
|
+
@client.delete_table({table_name: table_name})
|
66
|
+
rescue Aws::DynamoDB::Errors::ResourceNotFoundException
|
67
|
+
puts 'Table did not exist. Nothing to delete.'
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
def wait_until_table_available(table_name)
|
73
|
+
|
74
|
+
max_attempts = 24
|
75
|
+
delay_between_attempts = 5
|
76
|
+
|
77
|
+
# wait until the table has updated to being fully available
|
78
|
+
# waiting for ~2min at most; an error will be thrown afterwards
|
79
|
+
begin
|
80
|
+
@client.wait_until(:table_exists, table_name: table_name) do |w|
|
81
|
+
w.max_attempts = max_attempts
|
82
|
+
w.delay = delay_between_attempts
|
83
|
+
w.before_wait do |attempts, response|
|
84
|
+
puts "Waiting until table becomes available. Attempt #{attempts}/#{max_attempts} with polling interval #{delay_between_attempts}."
|
85
|
+
end
|
86
|
+
end
|
87
|
+
rescue Aws::Waiters::Errors::TooManyAttemptsError => e
|
88
|
+
puts "Table #{table_name} did not become available after #{e.attempts} attempts. Try again later or inspect the AWS console."
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'aws-sdk'
|
2
|
+
require_relative 'aws_setup'
|
3
|
+
|
4
|
+
module LambdaWrap
|
5
|
+
|
6
|
+
class LambdaManager
|
7
|
+
|
8
|
+
def initialize()
|
9
|
+
AwsSetup.new.validate()
|
10
|
+
# AWS lambda client
|
11
|
+
@client = Aws::Lambda::Client.new()
|
12
|
+
end
|
13
|
+
|
14
|
+
def package(directory, zipfile, input_filenames, node_modules)
|
15
|
+
|
16
|
+
FileUtils::mkdir_p directory
|
17
|
+
FileUtils::mkdir_p File.join(directory, 'node_modules')
|
18
|
+
|
19
|
+
input_filenames.each do |filename|
|
20
|
+
FileUtils::copy_file(File.join(filename), File.join(directory, File.basename(filename)))
|
21
|
+
end
|
22
|
+
|
23
|
+
node_modules.each do |dir|
|
24
|
+
FileUtils::cp_r(File.join('node_modules', dir), File.join(directory, 'node_modules'))
|
25
|
+
end
|
26
|
+
|
27
|
+
ZipFileGenerator.new(directory, zipfile).write
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
def publish_lambda_to_s3(local_lambda_file, bucket, key)
|
32
|
+
|
33
|
+
# get s3 object
|
34
|
+
s3 = Aws::S3::Resource.new()
|
35
|
+
obj = s3.bucket(bucket).object(key)
|
36
|
+
|
37
|
+
# upload
|
38
|
+
version_id = nil
|
39
|
+
File.open(local_lambda_file, 'rb') do |file|
|
40
|
+
version_id = obj.put({body: file}).version_id
|
41
|
+
end
|
42
|
+
raise 'Upload to S3 failed' if !version_id
|
43
|
+
|
44
|
+
puts 'Uploaded object to S3 with version ' + version_id
|
45
|
+
return version_id
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def deploy_lambda(version_id, function_name, handler)
|
50
|
+
|
51
|
+
# create or update function
|
52
|
+
|
53
|
+
begin
|
54
|
+
func = @client.get_function({function_name: function_name})
|
55
|
+
func_config = @client.update_function_code({function_name: function_name, s3_bucket: S3_BUCKET, s3_key: S3_KEY, s3_object_version: version_id, publish: true}).data
|
56
|
+
puts func_config
|
57
|
+
func_version = func_config.version
|
58
|
+
raise 'Error while publishing existing lambda function ' + function_name if !func_config.version
|
59
|
+
rescue Aws::Lambda::Errors::ResourceNotFoundException
|
60
|
+
func_config = @client.create_function({function_name: function_name, runtime: 'nodejs', role: LAMBDA_ROLE_ARN, handler: handler, code: { s3_bucket: S3_BUCKET, s3_key: S3_KEY }, timeout: 5, memory_size: 128, publish: true, description: 'created by an automated script'}).data
|
61
|
+
puts func_config
|
62
|
+
func_version = func_config.version
|
63
|
+
raise 'Error while publishing new lambda function ' + function_name if !func_config.version
|
64
|
+
end
|
65
|
+
|
66
|
+
add_api_gateway_permissions(function_name, nil)
|
67
|
+
|
68
|
+
return func_version
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_alias(function_name, func_version, alias_name)
|
73
|
+
|
74
|
+
# create or update alias
|
75
|
+
func_alias = @client.list_aliases({function_name: function_name}).aliases.select{ |a| a.name == alias_name }.first()
|
76
|
+
if (!func_alias)
|
77
|
+
a = @client.create_alias({function_name: function_name, name: alias_name, function_version: func_version, description: 'created by an automated script'}).data
|
78
|
+
puts a
|
79
|
+
else
|
80
|
+
a = @client.update_alias({function_name: function_name, name: alias_name, function_version: func_version, description: 'updated by an automated script'}).data
|
81
|
+
puts a
|
82
|
+
end
|
83
|
+
|
84
|
+
add_api_gateway_permissions(function_name, alias_name)
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
def remove_alias(function_name, alias_name)
|
89
|
+
|
90
|
+
@client.delete_alias({function_name: function_name, name: alias_name})
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
def add_api_gateway_permissions(function_name, env)
|
95
|
+
# permissions to execute lambda
|
96
|
+
suffix = (':' + env if env) || ''
|
97
|
+
func = @client.get_function({function_name: function_name + suffix}).data.configuration
|
98
|
+
statement_id = func.function_name + (('-' + env if env) || '')
|
99
|
+
policy_exists = false
|
100
|
+
begin
|
101
|
+
existing_policies = @client.get_policy({function_name: func.function_arn}).data
|
102
|
+
existing_policy = JSON.parse(existing_policies.policy)
|
103
|
+
policy_exists = existing_policy['Statement'].select{ |s| s['Sid'] == statement_id}.any?
|
104
|
+
rescue Aws::Lambda::Errors::ResourceNotFoundException
|
105
|
+
end
|
106
|
+
|
107
|
+
if !policy_exists
|
108
|
+
perm_add = @client.add_permission({function_name: func.function_arn, statement_id: statement_id, action: 'lambda:*', principal: 'apigateway.amazonaws.com'})
|
109
|
+
puts perm_add.data
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'zip'
|
3
|
+
# This is a simple example which uses rubyzip to
|
4
|
+
# recursively generate a zip file from the contents of
|
5
|
+
# a specified directory. The directory itself is not
|
6
|
+
# included in the archive, rather just its contents.
|
7
|
+
#
|
8
|
+
# Usage:
|
9
|
+
# require /path/to/the/ZipFileGenerator/Class
|
10
|
+
# directoryToZip = "/tmp/input"
|
11
|
+
# outputFile = "/tmp/out.zip"
|
12
|
+
# zf = ZipFileGenerator.new(directoryToZip, outputFile)
|
13
|
+
# zf.write()
|
14
|
+
|
15
|
+
module LambdaWrap
|
16
|
+
|
17
|
+
class ZipFileGenerator
|
18
|
+
# Initialize with the directory to zip and the location of the output archive.
|
19
|
+
def initialize(input_dir, output_file)
|
20
|
+
@input_dir = input_dir
|
21
|
+
@output_file = output_file
|
22
|
+
end
|
23
|
+
|
24
|
+
# Zip the input directory.
|
25
|
+
def write
|
26
|
+
entries = Dir.entries(@input_dir) - %w(. ..)
|
27
|
+
|
28
|
+
::Zip::File.open(@output_file, ::Zip::File::CREATE) do |io|
|
29
|
+
write_entries entries, '', io
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# A helper method to make the recursion work.
|
36
|
+
def write_entries(entries, path, io)
|
37
|
+
entries.each do |e|
|
38
|
+
zip_file_path = path == '' ? e : File.join(path, e)
|
39
|
+
disk_file_path = File.join(@input_dir, zip_file_path)
|
40
|
+
puts "Deflating #{disk_file_path}"
|
41
|
+
|
42
|
+
if File.directory? disk_file_path
|
43
|
+
recursively_deflate_directory(disk_file_path, io, zip_file_path)
|
44
|
+
else
|
45
|
+
put_into_archive(disk_file_path, io, zip_file_path)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def recursively_deflate_directory(disk_file_path, io, zip_file_path)
|
51
|
+
io.mkdir zip_file_path
|
52
|
+
subdir = Dir.entries(disk_file_path) - %w(. ..)
|
53
|
+
write_entries subdir, zip_file_path, io
|
54
|
+
end
|
55
|
+
|
56
|
+
def put_into_archive(disk_file_path, io, zip_file_path)
|
57
|
+
io.get_output_stream(zip_file_path) do |f|
|
58
|
+
f.puts(File.open(disk_file_path, 'rb').read)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
metadata
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lambda_wrap
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Markus Thurner
|
8
|
+
- Dorota Ruta
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2016-01-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: aws-sdk
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '2'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '2'
|
28
|
+
description: This gem wraps the AWS SDK to simplify deployment of AWS Lambda functions
|
29
|
+
backed by API Gateway and DynamoDB.
|
30
|
+
email:
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- lib/lambda_wrap.rb
|
36
|
+
- lib/lambda_wrap/api_gateway_manager.rb
|
37
|
+
- lib/lambda_wrap/aws_setup.rb
|
38
|
+
- lib/lambda_wrap/dynamo_db_manager.rb
|
39
|
+
- lib/lambda_wrap/lambda_manager.rb
|
40
|
+
- lib/lambda_wrap/zip_file_generator.rb
|
41
|
+
homepage:
|
42
|
+
licenses:
|
43
|
+
- Apache-2.0
|
44
|
+
metadata: {}
|
45
|
+
post_install_message:
|
46
|
+
rdoc_options: []
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
requirements: []
|
60
|
+
rubyforge_project:
|
61
|
+
rubygems_version: 2.4.7
|
62
|
+
signing_key:
|
63
|
+
specification_version: 4
|
64
|
+
summary: Easy deployment of AWS Lambda functions and dependencies.
|
65
|
+
test_files: []
|