lambda_wrap 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a768d452eb54ba635015184a2aca9df160b8e3e7
4
- data.tar.gz: bf2f039cdedc097c13dc99088081ccdafcf186d8
3
+ metadata.gz: 79ae4cfdc2ed44b009d04a29372cb322ed040130
4
+ data.tar.gz: ebda47ad69d8a654f692889ef1244e97322edd96
5
5
  SHA512:
6
- metadata.gz: f7ada31848484a06eac240266a0f43e27f218556ff25648f3b00c1c160d92f60cde8725c2f7dd95dc033b74efe90552f161cdb5f15b63007e941ccf852959e7d
7
- data.tar.gz: ee600ffb27b596f93b799ce68d1ccc53d2b4068cb2d94c20c0b7460d2ef911ae97ec653da81bbd54c54b514a20c15a1d3c1c9e0d3b142667ba243f4892a50adc
6
+ metadata.gz: 5be6e9c59c7d93797b868e12dba43aa2f44d2e4e000c8da59edb222c36e17b1ece6cd830b0a03bf5a36962ee0eeea63cd31c72f25da1bcc6c3380ebba284a31b
7
+ data.tar.gz: b36aeade9223a9dac371dfc544a998dbee00a1c5ef0456aa51a84bc9b39e1704b970aef166ac9a645d4d9e03cf0304094697b6ea2e469b3b57da15979e09d50d
data/lib/lambda_wrap.rb CHANGED
@@ -1,5 +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
1
+ # :nodoc:
2
+ Dir["#{File.expand_path(File.dirname(__FILE__))}/**/*.rb"].each { |f| require f }
3
+
4
+ STDOUT.sync = true
5
+ STDERR.sync = true
@@ -1,155 +1,158 @@
1
- require 'aws-sdk'
2
-
3
- module LambdaWrap
4
- # The ApiGatewayManager simplifies downloading the aws-apigateway-importer binary,
5
- # importing a {swagger configuration}[http://swagger.io], and managing API Gateway stages.
6
-
7
- # Note: The concept of an environment of the LambdaWrap gem matches a stage in AWS ApiGateway terms.
8
- class ApiGatewayManager
9
- #
10
- # The constructor does some basic setup
11
- # * Validating basic AWS configuration
12
- # * Creating the underlying client to interact with the AWS SDK.
13
- # * Defining the temporary path of the api-gateway-importer jar file
14
- def initialize
15
- # AWS api gateway client
16
- @client = Aws::APIGateway::Client.new
17
- # path to apigateway-importer jar
18
- @jarpath = File.join(Dir.tmpdir, 'aws-apigateway-importer-1.0.3-SNAPSHOT-jar-with-dependencies.jar')
19
- @versionpath = File.join(Dir.tmpdir, 'aws-apigateway-importer-1.0.3-SNAPSHOT-jar-with-dependencies.s3version')
20
- end
21
-
22
- ##
23
- # Downloads the aws-apigateway-importer jar from an S3 bucket.
24
- # This is a workaround since aws-apigateway-importer does not provide a binary.
25
- # Once a binary is available on the public internet, we'll start using this instead
26
- # of requiring users of this gem to upload their custom binary to an S3 bucket.
27
- #
28
- # *Arguments*
29
- # [s3_bucket] An S3 bucket from where the aws-apigateway-importer binary can be downloaded.
30
- # [s3_key] The path (key) to the aws-apigateay-importer binary on the s3 bucket.
31
- def download_apigateway_importer(s3_bucket, s3_key)
32
- s3 = Aws::S3::Client.new
33
-
34
- # current version
35
- current_s3_version = File.open(@versionpath, 'rb').read if File.exist?(@versionpath)
36
-
37
- # online s3 version
38
- desired_s3_version = s3.head_object(bucket: s3_bucket, key: s3_key).version_id
39
-
40
- # compare local with remote version
41
- if current_s3_version != desired_s3_version || !File.exist?(@jarpath)
42
- puts "Downloading aws-apigateway-importer jar with S3 version #{desired_s3_version}"
43
- s3.get_object(response_target: @jarpath, bucket: s3_bucket, key: s3_key)
44
- File.write(@versionpath, desired_s3_version)
45
- end
46
- end
47
-
48
- ##
49
- # Sets up the API gateway by searching whether the API Gateway already exists
50
- # and updates it with the latest information from the swagger file.
51
- #
52
- # *Arguments*
53
- # [api_name] The name of the API to which the swagger file should be applied to.
54
- # [env] The environment where it should be published (which is matching an API gateway stage)
55
- # [swagger_file] A handle to a swagger file that should be used by aws-apigateway-importer
56
- # [api_description] The description of the API to be displayed.
57
- # [stage_variables] A Hash of stage variables to be deployed with the stage. Adds an 'environment' by default.
58
- # [region] The region to deploy the API. Defaults to what is set as an environment variable.
59
- def setup_apigateway(api_name, env, swagger_file, api_description = 'Deployed with LambdaWrap',
60
- stage_variables = {}, region = ENV['AWS_REGION'])
61
- # ensure API is created
62
- api_id = get_existing_rest_api(api_name)
63
- api_id = setup_apigateway_create_rest_api(api_name, api_description) unless api_id
64
-
65
- # create resources
66
- setup_apigateway_create_resources(api_id, swagger_file, region)
67
-
68
- # create stages
69
- stage_variables.store('environment', env)
70
- create_stages(api_id, env, stage_variables)
71
-
72
- # return URI of created stage
73
- "https://#{api_id}.execute-api.#{region}.amazonaws.com/#{env}/"
74
- end
75
-
76
- ##
77
- # Shuts down an environment from the API Gateway. This basically deletes the stage
78
- # from the API Gateway, but does not delete the API Gateway itself.
79
- #
80
- # *Argument*
81
- # [api_name] The name of the API where the environment should be shut down.
82
- # [env] The environment (matching an API Gateway stage) to shutdown.
83
- def shutdown_apigateway(api_name, env)
84
- api_id = get_existing_rest_api(api_name)
85
- delete_stage(api_id, env)
86
- end
87
-
88
- ##
89
- # Gets the ID of an existing API Gateway api, or nil if it doesn't exist
90
- #
91
- # *Arguments*
92
- # [api_name] The name of the API to be checked for existance
93
- def get_existing_rest_api(api_name)
94
- apis = @client.get_rest_apis(limit: 500).data
95
- api = apis.items.select { |a| a.name == api_name }.first
96
-
97
- return api.id if api
98
- # nil is returned otherwise
99
- end
100
-
101
- ##
102
- # Creates the API with a given name and returns the id
103
- #
104
- # *Arguments*
105
- # [api_name] The name of the API to be created
106
- def setup_apigateway_create_rest_api(api_name, api_description)
107
- puts 'Creating API with name ' + api_name
108
- api = @client.create_rest_api(name: api_name, description: api_description)
109
- api.id
110
- end
111
-
112
- ##
113
- # Invokes the aws-apigateway-importer jar with the required parameter
114
- #
115
- # *Arguments*
116
- # [api_id] The AWS ApiGateway id where the swagger file should be applied to.
117
- # [swagger_file] The handle to a swagger definition file that should be imported into API Gateway
118
- def setup_apigateway_create_resources(api_id, swagger_file, region)
119
- raise 'API ID not provided' unless api_id
120
-
121
- cmd = "java -jar #{@jarpath} --update #{api_id} --region #{region} #{swagger_file}"
122
- raise 'API gateway not created' unless system(cmd)
123
- end
124
-
125
- ##
126
- # Creates a stage of the currently set resources
127
- #
128
- # *Arguments*
129
- # [api_id] The AWS ApiGateway id where the stage should be created at.
130
- # [env] The environment (which matches the stage in API Gateway) to create.
131
- def create_stages(api_id, env, stage_variables)
132
- deployment_description = 'Deployment of service to ' + env
133
- deployment = @client.create_deployment(
134
- rest_api_id: api_id, stage_name: env, cache_cluster_enabled: false, description: deployment_description,
135
- variables: stage_variables
136
- ).data
137
- puts deployment
138
- end
139
-
140
- ##
141
- # Deletes a stage of the API Gateway
142
- #
143
- # *Arguments*
144
- # [api_id] The AWS ApiGateway id from which the stage should be deleted from.
145
- # [env] The environment (which matches the stage in API Gateway) to delete.
146
- def delete_stage(_, env)
147
- puts 'Deleted API gateway stage ' + env
148
- rescue Aws::APIGateway::Errors::NotFoundException
149
- puts 'API Gateway stage ' + env + ' does not exist. Nothing to delete.'
150
- end
151
-
152
- private :get_existing_rest_api, :setup_apigateway_create_rest_api, :setup_apigateway_create_resources,
153
- :create_stages, :delete_stage
154
- end
155
- end
1
+ require 'aws-sdk'
2
+
3
+ module LambdaWrap
4
+ # The ApiGatewayManager simplifies downloading the aws-apigateway-importer binary,
5
+ # importing a {swagger configuration}[http://swagger.io], and managing API Gateway stages.
6
+
7
+ # Note: The concept of an environment of the LambdaWrap gem matches a stage in AWS ApiGateway terms.
8
+ class ApiGatewayManager
9
+ #
10
+ # The constructor does some basic setup
11
+ # * Validating basic AWS configuration
12
+ # * Creating the underlying client to interact with the AWS SDK.
13
+ # * Defining the temporary path of the api-gateway-importer jar file
14
+ def initialize
15
+ # AWS api gateway client
16
+ @client = Aws::APIGateway::Client.new
17
+ # path to apigateway-importer jar
18
+ @jarpath = File.join(Dir.tmpdir, 'aws-apigateway-importer-1.0.3-SNAPSHOT-jar-with-dependencies.jar')
19
+ @versionpath = File.join(Dir.tmpdir, 'aws-apigateway-importer-1.0.3-SNAPSHOT-jar-with-dependencies.s3version')
20
+ end
21
+
22
+ ##
23
+ # Downloads the aws-apigateway-importer jar from an S3 bucket.
24
+ # This is a workaround since aws-apigateway-importer does not provide a binary.
25
+ # Once a binary is available on the public internet, we'll start using this instead
26
+ # of requiring users of this gem to upload their custom binary to an S3 bucket.
27
+ #
28
+ # *Arguments*
29
+ # [s3_bucket] An S3 bucket from where the aws-apigateway-importer binary can be downloaded.
30
+ # [s3_key] The path (key) to the aws-apigateay-importer binary on the s3 bucket.
31
+ def download_apigateway_importer(s3_bucket, s3_key)
32
+ s3 = Aws::S3::Client.new
33
+
34
+ # current version
35
+ current_s3_version = File.open(@versionpath, 'rb').read if File.exist?(@versionpath)
36
+
37
+ # online s3 version
38
+ desired_s3_version = s3.head_object(bucket: s3_bucket, key: s3_key).version_id
39
+
40
+ # compare local with remote version
41
+ if current_s3_version != desired_s3_version || !File.exist?(@jarpath)
42
+ puts "Downloading aws-apigateway-importer jar with S3 version #{desired_s3_version}"
43
+ s3.get_object(response_target: @jarpath, bucket: s3_bucket, key: s3_key)
44
+ File.write(@versionpath, desired_s3_version)
45
+ end
46
+ end
47
+
48
+ ##
49
+ # Sets up the API gateway by searching whether the API Gateway already exists
50
+ # and updates it with the latest information from the swagger file.
51
+ #
52
+ # *Arguments*
53
+ # [api_name] The name of the API to which the swagger file should be applied to.
54
+ # [env] The environment where it should be published (which is matching an API gateway stage)
55
+ # [swagger_file] A handle to a swagger file that should be used by aws-apigateway-importer
56
+ # [api_description] The description of the API to be displayed.
57
+ # [stage_variables] A Hash of stage variables to be deployed with the stage. Adds an 'environment' by default.
58
+ # [region] The region to deploy the API. Defaults to what is set as an environment variable.
59
+ def setup_apigateway(api_name, env, swagger_file, api_description = 'Deployed with LambdaWrap',
60
+ stage_variables = {}, region = ENV['AWS_REGION'])
61
+ # ensure API is created
62
+ api_id = get_existing_rest_api(api_name)
63
+ api_id = setup_apigateway_create_rest_api(api_name, api_description) unless api_id
64
+
65
+ # create resources
66
+ setup_apigateway_create_resources(api_id, swagger_file, region)
67
+
68
+ # create stages
69
+ stage_variables.store('environment', env)
70
+ create_stages(api_id, env, stage_variables)
71
+
72
+ # return URI of created stage
73
+ "https://#{api_id}.execute-api.#{region}.amazonaws.com/#{env}/"
74
+ end
75
+
76
+ ##
77
+ # Shuts down an environment from the API Gateway. This basically deletes the stage
78
+ # from the API Gateway, but does not delete the API Gateway itself.
79
+ #
80
+ # *Argument*
81
+ # [api_name] The name of the API where the environment should be shut down.
82
+ # [env] The environment (matching an API Gateway stage) to shutdown.
83
+ def shutdown_apigateway(api_name, env)
84
+ api_id = get_existing_rest_api(api_name)
85
+ delete_stage(api_id, env)
86
+ end
87
+
88
+ ##
89
+ # Gets the ID of an existing API Gateway api, or nil if it doesn't exist
90
+ #
91
+ # *Arguments*
92
+ # [api_name] The name of the API to be checked for existance
93
+ def get_existing_rest_api(api_name)
94
+ apis = @client.get_rest_apis(limit: 500).data
95
+ api = apis.items.select { |a| a.name == api_name }.first
96
+
97
+ return api.id if api
98
+ # nil is returned otherwise
99
+ end
100
+
101
+ ##
102
+ # Creates the API with a given name and returns the id
103
+ #
104
+ # *Arguments*
105
+ # [api_name] The name of the API to be created
106
+ def setup_apigateway_create_rest_api(api_name, api_description)
107
+ puts 'Creating API with name ' + api_name
108
+ api = @client.create_rest_api(name: api_name, description: api_description)
109
+ api.id
110
+ end
111
+
112
+ ##
113
+ # Invokes the aws-apigateway-importer jar with the required parameter
114
+ #
115
+ # *Arguments*
116
+ # [api_id] The AWS ApiGateway id where the swagger file should be applied to.
117
+ # [swagger_file] The handle to a swagger definition file that should be imported into API Gateway
118
+ def setup_apigateway_create_resources(api_id, swagger_file, region)
119
+ raise 'API ID not provided' unless api_id
120
+
121
+ cmd = "java -jar #{@jarpath} --update #{api_id} --region #{region} #{swagger_file}"
122
+ raise 'API gateway not created' unless system(cmd)
123
+ end
124
+
125
+ ##
126
+ # Creates a stage of the currently set resources
127
+ #
128
+ # *Arguments*
129
+ # [api_id] The AWS ApiGateway id where the stage should be created at.
130
+ # [env] The environment (which matches the stage in API Gateway) to create.
131
+ def create_stages(api_id, env, stage_variables)
132
+ deployment_description = 'Deployment of service to ' + env
133
+ deployment = @client.create_deployment(
134
+ rest_api_id: api_id, stage_name: env, cache_cluster_enabled: false, description: deployment_description,
135
+ variables: stage_variables
136
+ ).data
137
+ puts deployment
138
+ end
139
+
140
+ ##
141
+ # Deletes a stage of the API Gateway
142
+ #
143
+ # *Arguments*
144
+ # [api_id] The AWS ApiGateway id from which the stage should be deleted from.
145
+ # [env] The environment (which matches the stage in API Gateway) to delete.
146
+ def delete_stage(api_id, env)
147
+ begin
148
+ @client.delete_stage({rest_api_id: api_id, stage_name: env})
149
+ puts 'Deleted API gateway stage ' + env
150
+ rescue Aws::APIGateway::Errors::NotFoundException
151
+ puts 'API Gateway stage ' + env + ' does not exist. Nothing to delete.'
152
+ end
153
+ end
154
+
155
+ private :get_existing_rest_api, :setup_apigateway_create_rest_api, :setup_apigateway_create_resources,
156
+ :create_stages, :delete_stage
157
+ end
158
+ end
@@ -1,121 +1,121 @@
1
- require 'aws-sdk'
2
-
3
- module LambdaWrap
4
- # The DynamoDBManager simplifies setting up and destroying a DynamoDB database.
5
- #
6
- # Note: In case an environment specific DynamoDB tablename such as +<baseTableName>-production+ should be used, then
7
- # it has to be injected directly to the methods since not all environments necessarily need separated databases.
8
- class DynamoDbManager
9
- ##
10
- # The constructor does some basic setup
11
- # * Validating basic AWS configuration
12
- # * Creating the underlying client to interact with the AWS SDK.
13
- def initialize
14
- # AWS dynamodb client
15
- @client = Aws::DynamoDB::Client.new
16
- end
17
-
18
- def set_table_capacity(table_name, read_capacity, write_capacity)
19
- puts "Updating new read/write capacity for table #{table_name}.
20
- Read #{table_details.provisioned_throughput.read_capacity_units} ==> #{read_capacity}.
21
- Write #{table_details.provisioned_throughput.write_capacity_units} ==> #{write_capacity}."
22
- @client.update_table(
23
- table_name: table_name,
24
- provisioned_throughput: { read_capacity_units: read_capacity, write_capacity_units: write_capacity }
25
- )
26
- end
27
-
28
- ##
29
- # Publishes the database and awaits until it is fully available. If the table already exists,
30
- # it only adjusts the read and write
31
- # capacities upwards (it doesn't downgrade them to avoid a production environment being impacted with
32
- # a default setting of an automated script).
33
- #
34
- # *Arguments*
35
- # [table_name] The table name of the dynamoDB to be created.
36
- # [attribute_definitions] The dynamoDB attribute definitions to be used when the table is created.
37
- # [key_schema] The dynamoDB key definitions to be used when the table is created.
38
- # [read_capacity] The read capacity to configure for the dynamoDB table.
39
- # [write_capacity] The write capacity to configure for the dynamoDB table.
40
- def publish_database(table_name, attribute_definitions, key_schema, read_capacity, write_capacity)
41
- has_updates = false
42
-
43
- # figure out whether the table exists
44
- begin
45
- table_details = @client.describe_table(table_name: table_name).table
46
- rescue Aws::DynamoDB::Errors::ResourceNotFoundException
47
- # skip this exception because we are using it for control flow.
48
- table_details = nil
49
- end
50
-
51
- if table_details
52
- wait_until_table_available(table_name) if table_details.table_status != 'ACTIVE'
53
-
54
- if read_capacity > table_details.provisioned_throughput.read_capacity_units ||
55
- write_capacity > table_details.provisioned_throughput.write_capacity_units
56
-
57
- set_table_capacity read_capacity, write_capacity
58
- has_updates = true
59
- else
60
- puts "Table #{table_name} already exists and the desired read capacity of #{read_capacity} and " \
61
- "write capacity of #{write_capacity} has at least been configured. Downgrading capacity units is not " \
62
- 'supported. No changes were applied.'
63
- end
64
- else
65
- puts "Creating table #{table_name}."
66
- ad = attribute_definitions || [{ attribute_name: 'Id', attribute_type: 'S' }]
67
- ks = key_schema || [{ attribute_name: 'Id', key_type: 'HASH' }]
68
- @client.create_table(table_name: table_name, key_schema: ks, attribute_definitions: ad,
69
- provisioned_throughput:
70
- { read_capacity_units: read_capacity, write_capacity_units: write_capacity })
71
- has_updates = true
72
- end
73
-
74
- if has_updates
75
- wait_until_table_available(table_name)
76
- puts "DynamoDB table #{table_name} is now fully available."
77
- end
78
- end
79
-
80
- ##
81
- # Deletes a DynamoDB table. It does not wait until the table has been deleted.
82
- #
83
- # *Arguments*
84
- # [table_name] The dynamoDB table name to delete.
85
- def delete_database(table_name)
86
- table_details = @client.describe_table(table_name: table_name).table
87
- wait_until_table_available(table_name) if table_details.table_status != 'ACTIVE'
88
- @client.delete_table(table_name: table_name)
89
- rescue Aws::DynamoDB::Errors::ResourceNotFoundException
90
- puts 'Table did not exist. Nothing to delete.'
91
- end
92
-
93
- ##
94
- # Awaits a given status of a table.
95
- #
96
- # *Arguments*
97
- # [table_name] The dynamoDB table name to watch until it reaches an active status.
98
- def wait_until_table_available(table_name)
99
- max_attempts = 24
100
- delay_between_attempts = 5
101
-
102
- # wait until the table has updated to being fully available
103
- # waiting for ~2min at most; an error will be thrown afterwards
104
- begin
105
- @client.wait_until(:table_exists, table_name: table_name) do |w|
106
- w.max_attempts = max_attempts
107
- w.delay = delay_between_attempts
108
- w.before_wait do |attempts, _|
109
- puts "Waiting until table becomes available. Attempt #{attempts}/#{max_attempts} " \
110
- "with polling interval #{delay_between_attempts}."
111
- end
112
- end
113
- rescue Aws::Waiters::Errors::TooManyAttemptsError => e
114
- puts "Table #{table_name} did not become available after #{e.attempts} attempts. " \
115
- 'Try again later or inspect the AWS console.'
116
- end
117
- end
118
-
119
- private :wait_until_table_available
120
- end
121
- end
1
+ require 'aws-sdk'
2
+
3
+ module LambdaWrap
4
+ # The DynamoDBManager simplifies setting up and destroying a DynamoDB database.
5
+ #
6
+ # Note: In case an environment specific DynamoDB tablename such as +<baseTableName>-production+ should be used, then
7
+ # it has to be injected directly to the methods since not all environments necessarily need separated databases.
8
+ class DynamoDbManager
9
+ ##
10
+ # The constructor does some basic setup
11
+ # * Validating basic AWS configuration
12
+ # * Creating the underlying client to interact with the AWS SDK.
13
+ def initialize
14
+ # AWS dynamodb client
15
+ @client = Aws::DynamoDB::Client.new
16
+ end
17
+
18
+ def set_table_capacity(table_name, read_capacity, write_capacity)
19
+ puts "Updating new read/write capacity for table #{table_name}.
20
+ Read #{table_details.provisioned_throughput.read_capacity_units} ==> #{read_capacity}.
21
+ Write #{table_details.provisioned_throughput.write_capacity_units} ==> #{write_capacity}."
22
+ @client.update_table(
23
+ table_name: table_name,
24
+ provisioned_throughput: { read_capacity_units: read_capacity, write_capacity_units: write_capacity }
25
+ )
26
+ end
27
+
28
+ ##
29
+ # Publishes the database and awaits until it is fully available. If the table already exists,
30
+ # it only adjusts the read and write
31
+ # capacities upwards (it doesn't downgrade them to avoid a production environment being impacted with
32
+ # a default setting of an automated script).
33
+ #
34
+ # *Arguments*
35
+ # [table_name] The table name of the dynamoDB to be created.
36
+ # [attribute_definitions] The dynamoDB attribute definitions to be used when the table is created.
37
+ # [key_schema] The dynamoDB key definitions to be used when the table is created.
38
+ # [read_capacity] The read capacity to configure for the dynamoDB table.
39
+ # [write_capacity] The write capacity to configure for the dynamoDB table.
40
+ def publish_database(table_name, attribute_definitions, key_schema, read_capacity, write_capacity)
41
+ has_updates = false
42
+
43
+ # figure out whether the table exists
44
+ begin
45
+ table_details = @client.describe_table(table_name: table_name).table
46
+ rescue Aws::DynamoDB::Errors::ResourceNotFoundException
47
+ # skip this exception because we are using it for control flow.
48
+ table_details = nil
49
+ end
50
+
51
+ if table_details
52
+ wait_until_table_available(table_name) if table_details.table_status != 'ACTIVE'
53
+
54
+ if read_capacity > table_details.provisioned_throughput.read_capacity_units ||
55
+ write_capacity > table_details.provisioned_throughput.write_capacity_units
56
+
57
+ set_table_capacity read_capacity, write_capacity
58
+ has_updates = true
59
+ else
60
+ puts "Table #{table_name} already exists and the desired read capacity of #{read_capacity} and " \
61
+ "write capacity of #{write_capacity} has at least been configured. Downgrading capacity units is not " \
62
+ 'supported. No changes were applied.'
63
+ end
64
+ else
65
+ puts "Creating table #{table_name}."
66
+ ad = attribute_definitions || [{ attribute_name: 'Id', attribute_type: 'S' }]
67
+ ks = key_schema || [{ attribute_name: 'Id', key_type: 'HASH' }]
68
+ @client.create_table(table_name: table_name, key_schema: ks, attribute_definitions: ad,
69
+ provisioned_throughput:
70
+ { read_capacity_units: read_capacity, write_capacity_units: write_capacity })
71
+ has_updates = true
72
+ end
73
+
74
+ if has_updates
75
+ wait_until_table_available(table_name)
76
+ puts "DynamoDB table #{table_name} is now fully available."
77
+ end
78
+ end
79
+
80
+ ##
81
+ # Deletes a DynamoDB table. It does not wait until the table has been deleted.
82
+ #
83
+ # *Arguments*
84
+ # [table_name] The dynamoDB table name to delete.
85
+ def delete_database(table_name)
86
+ table_details = @client.describe_table(table_name: table_name).table
87
+ wait_until_table_available(table_name) if table_details.table_status != 'ACTIVE'
88
+ @client.delete_table(table_name: table_name)
89
+ rescue Aws::DynamoDB::Errors::ResourceNotFoundException
90
+ puts 'Table did not exist. Nothing to delete.'
91
+ end
92
+
93
+ ##
94
+ # Awaits a given status of a table.
95
+ #
96
+ # *Arguments*
97
+ # [table_name] The dynamoDB table name to watch until it reaches an active status.
98
+ def wait_until_table_available(table_name)
99
+ max_attempts = 24
100
+ delay_between_attempts = 5
101
+
102
+ # wait until the table has updated to being fully available
103
+ # waiting for ~2min at most; an error will be thrown afterwards
104
+ begin
105
+ @client.wait_until(:table_exists, table_name: table_name) do |w|
106
+ w.max_attempts = max_attempts
107
+ w.delay = delay_between_attempts
108
+ w.before_wait do |attempts, _|
109
+ puts "Waiting until table becomes available. Attempt #{attempts}/#{max_attempts} " \
110
+ "with polling interval #{delay_between_attempts}."
111
+ end
112
+ end
113
+ rescue Aws::Waiters::Errors::TooManyAttemptsError => e
114
+ puts "Table #{table_name} did not become available after #{e.attempts} attempts. " \
115
+ 'Try again later or inspect the AWS console.'
116
+ end
117
+ end
118
+
119
+ private :wait_until_table_available
120
+ end
121
+ end
@@ -1,181 +1,181 @@
1
- require 'aws-sdk'
2
-
3
- module LambdaWrap
4
- ##
5
- # The LambdaManager simplifies creating a package, publishing to S3, deploying a new version, & setting permissions.
6
- #
7
- # Note: The concept of an environment of the LambdaWrap gem matches an alias of AWS Lambda.
8
- class LambdaManager
9
- ##
10
- # The constructor does some basic setup
11
- # * Validating basic AWS configuration
12
- # * Creating the underlying client to interace with the AWS SDK
13
- def initialize
14
- # AWS lambda client
15
- @client = Aws::Lambda::Client.new
16
- end
17
-
18
- ##
19
- # Packages a set of files and node modules into a deployable package.
20
- #
21
- # *Arguments*
22
- # [directory] A temporary directory to copy all related files before they are packages into a single zip file.
23
- # [zipfile] A path where the deployable package, a zip file, should be stored.
24
- # [input_filenames] A list of file names that contain the source code.
25
- # [node_modules] A list of node modules that need to be included in the package.
26
- def package(directory, zipfile, input_filenames, node_modules)
27
- FileUtils.mkdir_p directory
28
- FileUtils.mkdir_p File.join(directory, 'node_modules')
29
-
30
- input_filenames.each do |filename|
31
- FileUtils.copy_file(File.join(filename), File.join(directory, File.basename(filename)))
32
- end
33
-
34
- node_modules.each do |dir|
35
- FileUtils.cp_r(File.join('node_modules', dir), File.join(directory, 'node_modules'))
36
- end
37
-
38
- ZipFileGenerator.new(directory, zipfile).write
39
- end
40
-
41
- ##
42
- # Publishes a package to S3 so it can be deployed as a lambda function.
43
- #
44
- # *Arguments*
45
- # [local_lambda_file] The location of the package that needs to be deployed.
46
- # [bucket] The s3 bucket where the file needs to be uploaded to.
47
- # [key] The S3 path (key) where the package should be stored.
48
- def publish_lambda_to_s3(local_lambda_file, bucket, key)
49
- # get s3 object
50
- s3 = Aws::S3::Resource.new
51
- obj = s3.bucket(bucket).object(key)
52
-
53
- # upload
54
- version_id = nil
55
- File.open(local_lambda_file, 'rb') do |file|
56
- version_id = obj.put(body: file).version_id
57
- end
58
- raise 'Upload to S3 failed' unless version_id
59
-
60
- puts 'Uploaded object to S3 with version ' + version_id
61
- version_id
62
- end
63
-
64
- ##
65
- # Deploys a package that has been uploaded to S3.
66
- #
67
- # *Arguments*
68
- # [bucket] The S3 bucket where the package can be retrieved from.
69
- # [key] The S3 path (key) where the package can be retrieved from.
70
- # [version_id] The version of the file on S3 to retrieve.
71
- # [function_name] The name of the lambda function.
72
- # [handler] The handler that should be executed for this lambda function.
73
- # [lambda_role] The arn of the IAM role that should be used when executing the lambda function.
74
- # [lambda_description] The description of the lambda function.
75
- # [vpc_subnet_ids] A list of subnet ids for the lambda's VPC configuration. All subnets must be on the same VPC.
76
- # [vpc_security_group_ids] A list of security group ids for the lambda's VPC configuration. All of the
77
- # security_group_ids must be on the same VPC.
78
- def deploy_lambda(
79
- bucket, key, version_id, function_name, handler, lambda_role,
80
- lambda_description = 'Deployed with LambdaWrap', vpc_subnet_ids = [], vpc_security_group_ids = []
81
- )
82
- # create or update function
83
-
84
- begin
85
- @client.get_function(function_name: function_name)
86
- func_config = @client.update_function_code(function_name: function_name, s3_bucket: bucket, s3_key: key,
87
- s3_object_version: version_id, publish: true).data
88
- puts func_config
89
- func_version = func_config.version
90
- raise 'Error while publishing existing lambda function ' + function_name unless func_version
91
- rescue Aws::Lambda::Errors::ResourceNotFoundException
92
- # check if vpc_subnet_ids and vpc_security_group_ids are empty or not and set the vpc_config accordingly.
93
- vpc_Configuration = nil
94
- vpc_Configuration = { subnet_ids: vpc_subnet_ids, security_group_ids: vpc_security_group_ids } unless (vpc_subnet_ids.empty? && vpc_security_group_ids.empty?)
95
-
96
- # if we cannot find it, we have to create it instead of updating it
97
- func_config = @client.create_function(
98
- function_name: function_name, runtime: 'nodejs4.3', role: lambda_role,
99
- handler: handler, code: { s3_bucket: bucket, s3_key: key }, timeout: 5, memory_size: 128, publish: true,
100
- description: lambda_description,
101
- vpc_config: vpc_Configuration
102
- ).data
103
- puts func_config
104
- func_version = func_config.version
105
- raise "Error while publishing new lambda function #{function_name}" unless func_version
106
- end
107
-
108
- add_api_gateway_permissions(function_name, nil)
109
-
110
- func_version
111
- end
112
-
113
- ##
114
- # Creates an alias for a given lambda function version.
115
- #
116
- # *Arguments*
117
- # [function_name] The lambda function name for which the alias should be created.
118
- # [func_version] The lambda function versino to which the alias should point.
119
- # [alias_name] The name of the alias, matching the LambdaWrap environment concept.
120
- def create_alias(function_name, func_version, alias_name)
121
- # create or update alias
122
- func_alias = @client.list_aliases(function_name: function_name).aliases.select { |a| a.name == alias_name }.first
123
- if !func_alias
124
- a = @client.create_alias(
125
- function_name: function_name, name: alias_name, function_version: func_version,
126
- description: 'created by an automated script'
127
- ).data
128
- else
129
- a = @client.update_alias(
130
- function_name: function_name, name: alias_name, function_version: func_version,
131
- description: 'updated by an automated script'
132
- ).data
133
- end
134
- puts a
135
-
136
- add_api_gateway_permissions(function_name, alias_name)
137
- end
138
-
139
- ##
140
- # Removes an alias for a function.
141
- #
142
- # *Arguments*
143
- # [function_name] The lambda function name for which the alias should be removed.
144
- # [alias_name] The alias to remove.
145
- def remove_alias(function_name, alias_name)
146
- @client.delete_alias(function_name: function_name, name: alias_name)
147
- end
148
-
149
- ##
150
- # Adds permissions for API gateway to execute this function.
151
- #
152
- # *Arguments*
153
- # [function_name] The function name which needs to be executed from API Gateway.
154
- # [env] The environment (matching the function's alias) which needs to be executed from API Gateway.
155
- # => If nil, the permissions are set of the $LATEST version.
156
- def add_api_gateway_permissions(function_name, env)
157
- # permissions to execute lambda
158
- suffix = (':' + env if env) || ''
159
- func = @client.get_function(function_name: function_name + suffix).data.configuration
160
- statement_id = func.function_name + (('-' + env if env) || '')
161
- begin
162
- existing_policies = @client.get_policy(function_name: func.function_arn).data
163
- existing_policy = JSON.parse(existing_policies.policy)
164
- policy_exists = existing_policy['Statement'].select { |s| s['Sid'] == statement_id }.any?
165
- rescue Aws::Lambda::Errors::ResourceNotFoundException
166
- # policy does not exist, and that is ok
167
- policy_exists = false
168
- end
169
-
170
- unless policy_exists
171
- perm_add = @client.add_permission(
172
- function_name: func.function_arn, statement_id: statement_id,
173
- action: 'lambda:*', principal: 'apigateway.amazonaws.com'
174
- )
175
- puts perm_add.data
176
- end
177
- end
178
-
179
- private :add_api_gateway_permissions
180
- end
181
- end
1
+ require 'aws-sdk'
2
+
3
+ module LambdaWrap
4
+ ##
5
+ # The LambdaManager simplifies creating a package, publishing to S3, deploying a new version, & setting permissions.
6
+ #
7
+ # Note: The concept of an environment of the LambdaWrap gem matches an alias of AWS Lambda.
8
+ class LambdaManager
9
+ ##
10
+ # The constructor does some basic setup
11
+ # * Validating basic AWS configuration
12
+ # * Creating the underlying client to interace with the AWS SDK
13
+ def initialize
14
+ # AWS lambda client
15
+ @client = Aws::Lambda::Client.new
16
+ end
17
+
18
+ ##
19
+ # Packages a set of files and node modules into a deployable package.
20
+ #
21
+ # *Arguments*
22
+ # [directory] A temporary directory to copy all related files before they are packages into a single zip file.
23
+ # [zipfile] A path where the deployable package, a zip file, should be stored.
24
+ # [input_filenames] A list of file names that contain the source code.
25
+ # [node_modules] A list of node modules that need to be included in the package.
26
+ def package(directory, zipfile, input_filenames, node_modules)
27
+ FileUtils.mkdir_p directory
28
+ FileUtils.mkdir_p File.join(directory, 'node_modules')
29
+
30
+ input_filenames.each do |filename|
31
+ FileUtils.copy_file(File.join(filename), File.join(directory, File.basename(filename)))
32
+ end
33
+
34
+ node_modules.each do |dir|
35
+ FileUtils.cp_r(File.join('node_modules', dir), File.join(directory, 'node_modules'))
36
+ end
37
+
38
+ ZipFileGenerator.new(directory, zipfile).write
39
+ end
40
+
41
+ ##
42
+ # Publishes a package to S3 so it can be deployed as a lambda function.
43
+ #
44
+ # *Arguments*
45
+ # [local_lambda_file] The location of the package that needs to be deployed.
46
+ # [bucket] The s3 bucket where the file needs to be uploaded to.
47
+ # [key] The S3 path (key) where the package should be stored.
48
+ def publish_lambda_to_s3(local_lambda_file, bucket, key)
49
+ # get s3 object
50
+ s3 = Aws::S3::Resource.new
51
+ obj = s3.bucket(bucket).object(key)
52
+
53
+ # upload
54
+ version_id = nil
55
+ File.open(local_lambda_file, 'rb') do |file|
56
+ version_id = obj.put(body: file).version_id
57
+ end
58
+ raise 'Upload to S3 failed' unless version_id
59
+
60
+ puts 'Uploaded object to S3 with version ' + version_id
61
+ version_id
62
+ end
63
+
64
+ ##
65
+ # Deploys a package that has been uploaded to S3.
66
+ #
67
+ # *Arguments*
68
+ # [bucket] The S3 bucket where the package can be retrieved from.
69
+ # [key] The S3 path (key) where the package can be retrieved from.
70
+ # [version_id] The version of the file on S3 to retrieve.
71
+ # [function_name] The name of the lambda function.
72
+ # [handler] The handler that should be executed for this lambda function.
73
+ # [lambda_role] The arn of the IAM role that should be used when executing the lambda function.
74
+ # [lambda_description] The description of the lambda function.
75
+ # [vpc_subnet_ids] A list of subnet ids for the lambda's VPC configuration. All subnets must be on the same VPC.
76
+ # [vpc_security_group_ids] A list of security group ids for the lambda's VPC configuration. All of the
77
+ # security_group_ids must be on the same VPC.
78
+ def deploy_lambda(
79
+ bucket, key, version_id, function_name, handler, lambda_role,
80
+ lambda_description = 'Deployed with LambdaWrap', vpc_subnet_ids = [], vpc_security_group_ids = []
81
+ )
82
+ # create or update function
83
+
84
+ begin
85
+ @client.get_function(function_name: function_name)
86
+ func_config = @client.update_function_code(function_name: function_name, s3_bucket: bucket, s3_key: key,
87
+ s3_object_version: version_id, publish: true).data
88
+ puts func_config
89
+ func_version = func_config.version
90
+ raise 'Error while publishing existing lambda function ' + function_name unless func_version
91
+ rescue Aws::Lambda::Errors::ResourceNotFoundException
92
+ # check if vpc_subnet_ids and vpc_security_group_ids are empty or not and set the vpc_config accordingly.
93
+ vpc_Configuration = nil
94
+ vpc_Configuration = { subnet_ids: vpc_subnet_ids, security_group_ids: vpc_security_group_ids } unless (vpc_subnet_ids.empty? && vpc_security_group_ids.empty?)
95
+
96
+ # if we cannot find it, we have to create it instead of updating it
97
+ func_config = @client.create_function(
98
+ function_name: function_name, runtime: 'nodejs4.3', role: lambda_role,
99
+ handler: handler, code: { s3_bucket: bucket, s3_key: key }, timeout: 5, memory_size: 128, publish: true,
100
+ description: lambda_description,
101
+ vpc_config: vpc_Configuration
102
+ ).data
103
+ puts func_config
104
+ func_version = func_config.version
105
+ raise "Error while publishing new lambda function #{function_name}" unless func_version
106
+ end
107
+
108
+ add_api_gateway_permissions(function_name, nil)
109
+
110
+ func_version
111
+ end
112
+
113
+ ##
114
+ # Creates an alias for a given lambda function version.
115
+ #
116
+ # *Arguments*
117
+ # [function_name] The lambda function name for which the alias should be created.
118
+ # [func_version] The lambda function versino to which the alias should point.
119
+ # [alias_name] The name of the alias, matching the LambdaWrap environment concept.
120
+ def create_alias(function_name, func_version, alias_name)
121
+ # create or update alias
122
+ func_alias = @client.list_aliases(function_name: function_name).aliases.select { |a| a.name == alias_name }.first
123
+ if !func_alias
124
+ a = @client.create_alias(
125
+ function_name: function_name, name: alias_name, function_version: func_version,
126
+ description: 'created by an automated script'
127
+ ).data
128
+ else
129
+ a = @client.update_alias(
130
+ function_name: function_name, name: alias_name, function_version: func_version,
131
+ description: 'updated by an automated script'
132
+ ).data
133
+ end
134
+ puts a
135
+
136
+ add_api_gateway_permissions(function_name, alias_name)
137
+ end
138
+
139
+ ##
140
+ # Removes an alias for a function.
141
+ #
142
+ # *Arguments*
143
+ # [function_name] The lambda function name for which the alias should be removed.
144
+ # [alias_name] The alias to remove.
145
+ def remove_alias(function_name, alias_name)
146
+ @client.delete_alias(function_name: function_name, name: alias_name)
147
+ end
148
+
149
+ ##
150
+ # Adds permissions for API gateway to execute this function.
151
+ #
152
+ # *Arguments*
153
+ # [function_name] The function name which needs to be executed from API Gateway.
154
+ # [env] The environment (matching the function's alias) which needs to be executed from API Gateway.
155
+ # => If nil, the permissions are set of the $LATEST version.
156
+ def add_api_gateway_permissions(function_name, env)
157
+ # permissions to execute lambda
158
+ suffix = (':' + env if env) || ''
159
+ func = @client.get_function(function_name: function_name + suffix).data.configuration
160
+ statement_id = func.function_name + (('-' + env if env) || '')
161
+ begin
162
+ existing_policies = @client.get_policy(function_name: func.function_arn).data
163
+ existing_policy = JSON.parse(existing_policies.policy)
164
+ policy_exists = existing_policy['Statement'].select { |s| s['Sid'] == statement_id }.any?
165
+ rescue Aws::Lambda::Errors::ResourceNotFoundException
166
+ # policy does not exist, and that is ok
167
+ policy_exists = false
168
+ end
169
+
170
+ unless policy_exists
171
+ perm_add = @client.add_permission(
172
+ function_name: func.function_arn, statement_id: statement_id,
173
+ action: 'lambda:*', principal: 'apigateway.amazonaws.com'
174
+ )
175
+ puts perm_add.data
176
+ end
177
+ end
178
+
179
+ private :add_api_gateway_permissions
180
+ end
181
+ end
@@ -2,7 +2,7 @@ require 'aws-sdk'
2
2
 
3
3
  module LambdaWrap
4
4
  ##
5
- # The S3BucketManager simplifies would have functions to help add policies etcc to S3 bucket.
5
+ # The S3BucketManager would have functions to help add policies, CORS etc to S3 bucket.
6
6
  class S3BucketManager
7
7
  #
8
8
  # The constructor creates an instance of s3 bucket
@@ -1,67 +1,67 @@
1
- require 'rubygems'
2
- require 'zip'
3
-
4
- module LambdaWrap
5
- ##
6
- # Allows to easily zip a directory recursively. It's intended for gem internal use only.
7
- #
8
- # From the original example:
9
- # This is a simple example which uses rubyzip to
10
- # recursively generate a zip file from the contents of
11
- # a specified directory. The directory itself is not
12
- # included in the archive, rather just its contents.
13
- #
14
- # Usage:
15
- # require /path/to/the/ZipFileGenerator/Class
16
- # directoryToZip = "/tmp/input"
17
- # outputFile = "/tmp/out.zip"
18
- # zf = ZipFileGenerator.new(directoryToZip, outputFile)
19
- # zf.write()
20
- class ZipFileGenerator
21
- ##
22
- # Initialize with the directory to zip and the location of the output archive.
23
- def initialize(input_dir, output_file)
24
- @input_dir = input_dir
25
- @output_file = output_file
26
- end
27
-
28
- ##
29
- # Zip the input directory.
30
- def write
31
- entries = Dir.entries(@input_dir) - %w(. ..)
32
-
33
- Zip::File.open(@output_file, Zip::File::CREATE) do |io|
34
- write_entries entries, '', io
35
- end
36
- end
37
-
38
- private
39
-
40
- # A helper method to make the recursion work.
41
- def write_entries(entries, path, io)
42
- entries.each do |e|
43
- zip_file_path = path == '' ? e : File.join(path, e)
44
- disk_file_path = File.join(@input_dir, zip_file_path)
45
- puts "Deflating #{disk_file_path}"
46
-
47
- if File.directory? disk_file_path
48
- recursively_deflate_directory(disk_file_path, io, zip_file_path)
49
- else
50
- put_into_archive(disk_file_path, io, zip_file_path)
51
- end
52
- end
53
- end
54
-
55
- def recursively_deflate_directory(disk_file_path, io, zip_file_path)
56
- io.mkdir zip_file_path
57
- subdir = Dir.entries(disk_file_path) - %w(. ..)
58
- write_entries subdir, zip_file_path, io
59
- end
60
-
61
- def put_into_archive(disk_file_path, io, zip_file_path)
62
- io.get_output_stream(zip_file_path) do |f|
63
- f.puts(File.open(disk_file_path, 'rb').read)
64
- end
65
- end
66
- end
67
- end
1
+ require 'rubygems'
2
+ require 'zip'
3
+
4
+ module LambdaWrap
5
+ ##
6
+ # Allows to easily zip a directory recursively. It's intended for gem internal use only.
7
+ #
8
+ # From the original example:
9
+ # This is a simple example which uses rubyzip to
10
+ # recursively generate a zip file from the contents of
11
+ # a specified directory. The directory itself is not
12
+ # included in the archive, rather just its contents.
13
+ #
14
+ # Usage:
15
+ # require /path/to/the/ZipFileGenerator/Class
16
+ # directoryToZip = "/tmp/input"
17
+ # outputFile = "/tmp/out.zip"
18
+ # zf = ZipFileGenerator.new(directoryToZip, outputFile)
19
+ # zf.write()
20
+ class ZipFileGenerator
21
+ ##
22
+ # Initialize with the directory to zip and the location of the output archive.
23
+ def initialize(input_dir, output_file)
24
+ @input_dir = input_dir
25
+ @output_file = output_file
26
+ end
27
+
28
+ ##
29
+ # Zip the input directory.
30
+ def write
31
+ entries = Dir.entries(@input_dir) - %w(. ..)
32
+
33
+ Zip::File.open(@output_file, Zip::File::CREATE) do |io|
34
+ write_entries entries, '', io
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ # A helper method to make the recursion work.
41
+ def write_entries(entries, path, io)
42
+ entries.each do |e|
43
+ zip_file_path = path == '' ? e : File.join(path, e)
44
+ disk_file_path = File.join(@input_dir, zip_file_path)
45
+ puts "Deflating #{disk_file_path}"
46
+
47
+ if File.directory? disk_file_path
48
+ recursively_deflate_directory(disk_file_path, io, zip_file_path)
49
+ else
50
+ put_into_archive(disk_file_path, io, zip_file_path)
51
+ end
52
+ end
53
+ end
54
+
55
+ def recursively_deflate_directory(disk_file_path, io, zip_file_path)
56
+ io.mkdir zip_file_path
57
+ subdir = Dir.entries(disk_file_path) - %w(. ..)
58
+ write_entries subdir, zip_file_path, io
59
+ end
60
+
61
+ def put_into_archive(disk_file_path, io, zip_file_path)
62
+ io.get_output_stream(zip_file_path) do |f|
63
+ f.puts(File.open(disk_file_path, 'rb').read)
64
+ end
65
+ end
66
+ end
67
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lambda_wrap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Markus Thurner
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2016-07-18 00:00:00.000000000 Z
13
+ date: 2016-07-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws-sdk
@@ -73,7 +73,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
73
  version: '0'
74
74
  requirements: []
75
75
  rubyforge_project:
76
- rubygems_version: 2.4.7
76
+ rubygems_version: 2.2.5
77
77
  signing_key:
78
78
  specification_version: 4
79
79
  summary: Easy deployment of AWS Lambda functions and dependencies.