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 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
@@ -0,0 +1,5 @@
1
+ # :nodoc:
2
+ Dir["#{File.expand_path(File.dirname(__FILE__))}/**/*.rb"].each{|f| require f}
3
+
4
+ STDOUT.sync = true
5
+ STDERR.sync = true
@@ -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: []