aws_beanstalk_tunkki 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f226c4c68ba5bd6380f70933d8c04b449a89fdd6e27ad53333e89758f6333682
4
+ data.tar.gz: 39848528ce0ce80fc0e14ab678573d2d7ede28bb34c0031061a04a769533d31c
5
+ SHA512:
6
+ metadata.gz: f4229f11c843d1edffa590cb0f3d5e43b5d3304e952d9d2e2179466a396bd38d2e17f04a6b752e0beed12f0371f13adb2e8825f33250bfad0a1a80489dc19158
7
+ data.tar.gz: 810275c477f8ebe857f71cdd4d9865b11b147e7ee65c83bb36d952ab4ecef2afe8868855bea26a306832cbe521902e6528cf7f7b54cfd10be1ec456c30854604
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'aws_beanstalk_tunkki'
4
+ AWSBeanstalkTunkki.new.run_deployment()
@@ -0,0 +1,253 @@
1
+ require 'aws-sdk-elasticbeanstalk'
2
+ require 'aws-sdk-s3'
3
+ require 'optparse'
4
+ require 'fileutils'
5
+
6
+ class AWSBeanstalkTunkki
7
+
8
+ def initialize
9
+ init_deploy_variables()
10
+ end
11
+
12
+ def run_deployment
13
+ application = get_bs_app()
14
+ if !check_bs_env()
15
+ create_bs_env(application.configuration_templates)
16
+ end
17
+ deploy_app(generate_version_label())
18
+ end
19
+
20
+ def init_deploy_variables
21
+ parse_command_line_options()
22
+ set_application_environment()
23
+ set_aws_clients()
24
+ end
25
+
26
+ def parse_command_line_options
27
+ OptionParser.new do |opt|
28
+ opt.on('--app APP_NAME') { |app_name| @app = app_name }
29
+ opt.on('--branch BRANCH_NAME') { |branch_name| @branch = branch_name }
30
+ opt.on('--dir DIR') { |dir| @dir = dir }
31
+ opt.on('--region REGION') { |aws_region| @aws_region = aws_region }
32
+ opt.on('--local LOCAL') { |local| @local = local }
33
+ end.parse!
34
+ raise "Beanstalk application (--app) required!" if @app.nil?
35
+ raise "Git branch (--branch) required!" if @branch.nil?
36
+ raise "App dir location (--dir) required!" if @branch.nil?
37
+ raise "AWS Region missing (--region) required!" if @aws_region.nil?
38
+ end
39
+
40
+ def set_application_environment
41
+ case @branch
42
+ when /\Aprod/
43
+ set_application_environment_vars(environment: 'prod', env_simple: 'production')
44
+ when /\Ast/
45
+ set_application_environment_vars(environment: 'st', env_simple: 'staging')
46
+ when /\Adev/
47
+ set_application_environment_vars(environment: 'dev', env_simple: 'development')
48
+ when /\Aft/
49
+ set_application_environment_vars(environment: 'ft', env_simple: 'feature')
50
+ else
51
+ raise "Invalid deployment branch name detected! Branch name must start with prod, st, dev or ft"
52
+ end
53
+ end
54
+
55
+ def set_application_environment_vars(environment: 'dev', env_simple: '')
56
+ puts "Using branch '#{@branch}' as #{env_simple}"
57
+ @bs_env = @branch.gsub(/[\/:;.,+_<>#]/, '-')
58
+ @environment = environment
59
+ @bs_env_simple = env_simple
60
+ end
61
+
62
+ def set_aws_clients
63
+ if @local == "true"
64
+ # Deploy from local machine, requires AWS_PROFILE=default and aws-mfa based login before execution
65
+ credentials = Aws::SharedCredentials.new()
66
+ else
67
+ aws_access_key_id, aws_secret_access_key = *get_aws_keys()
68
+ raise "AWS keys are not defined!" if aws_access_key_id.nil? || aws_secret_access_key.nil?
69
+ credentials = Aws::Credentials.new(aws_access_key_id, aws_secret_access_key)
70
+ end
71
+ @elasticbeanstalk = Aws::ElasticBeanstalk::Client.new(region: @aws_region, credentials: credentials)
72
+ @s3 = Aws::S3::Client.new(region: @aws_region, credentials: credentials)
73
+ bucket = @s3.list_buckets.buckets.find { |b| /\Aelasticbeanstalk-#{@aws_region}/ =~ b.name }
74
+ raise "Could not resolve s3 bucket name." if bucket.nil?
75
+ @s3_bucket_name = bucket.name
76
+ end
77
+
78
+ def get_aws_keys
79
+ case @environment
80
+ when 'prod', 'st'
81
+ [ENV['AWS_ACCESS_KEY_ID_PROD'], ENV['AWS_SECRET_ACCESS_KEY_PROD']]
82
+ when 'dev', 'ft'
83
+ [ENV['AWS_ACCESS_KEY_ID_DEV'], ENV['AWS_SECRET_ACCESS_KEY_DEV']]
84
+ end
85
+ end
86
+
87
+ def get_bs_app
88
+ application_description = @elasticbeanstalk.describe_applications({application_names: [@app]})
89
+ raise "Application '#{@app}' not found." if application_description.applications.empty?
90
+ application_description.applications.first
91
+ end
92
+
93
+ def check_bs_env
94
+ env_name = "#{@app}-#{@bs_env}"
95
+ puts "Checking if Beanstalk environment '#{env_name}' in application '#{@app}' exists."
96
+ application_environments = @elasticbeanstalk.describe_environments({environment_names: [env_name]})
97
+ if is_environment_found?(application_environments.environments)
98
+ puts "Environment '#{env_name}' already exists in application '#{@app}'."
99
+ true
100
+ else
101
+ puts "Environment '#{env_name}' does not exist."
102
+ false
103
+ end
104
+ end
105
+
106
+ def is_environment_found?(environments)
107
+ !environments.empty? && !environments.all? { |env| env.status == 'Terminated' }
108
+ end
109
+
110
+ def create_bs_env(configuration_templates)
111
+ puts "Searching for Beanstalk configuration template.."
112
+
113
+ app_bs_env_simple = "#{@app}-#{@bs_env_simple}"
114
+ app_default = "#{@app}-default"
115
+ app_bs_env = "#{@app}-#{@bs_env}"
116
+
117
+ conf_template = find_configuration_template(configuration_templates)
118
+ puts "Using '#{conf_template}' configuration template."
119
+ puts "Launching new environment '#{@app}-#{@bs_env}' to application '#{@app}'."
120
+
121
+ begin
122
+ @elasticbeanstalk.create_environment(
123
+ {
124
+ application_name: @app,
125
+ environment_name: app_bs_env,
126
+ cname_prefix: app_bs_env,
127
+ template_name: conf_template,
128
+ })
129
+ if (poll_for_environment_changes(app_bs_env) { |env| env.status != 'Launching' })
130
+ puts "Created environment '#{app_bs_env}'!"
131
+ else
132
+ raise "Timeout when creating environment."
133
+ end
134
+ rescue => e
135
+ raise "Environment launch failed, unable to proceed. Error: #{e}"
136
+ end
137
+ end
138
+
139
+ def find_configuration_template(configuration_templates)
140
+ configuration_templates.find do |tpl|
141
+ /(?<conf_app_stub>[\w-]+)-(?<conf_env_stub>\w+)-\w+\z/ =~ tpl
142
+ conf_app_stub && conf_env_stub && @app.match(conf_app_stub) && @bs_env_simple.match(conf_env_stub)
143
+ end or raise "No configuration templates found, can't create environment."
144
+ end
145
+
146
+ def poll_for_environment_changes(env_name)
147
+ print "Making changes to environment #{env_name}"
148
+ ten_minutes = 60 * 10
149
+ ten_minutes.times do |i|
150
+ if (yield(@elasticbeanstalk.describe_environments({environment_names: [env_name]}).environments.first))
151
+ print "\n"
152
+ return true
153
+ else
154
+ print '.'
155
+ sleep(1)
156
+ end
157
+ end
158
+ false
159
+ end
160
+
161
+ def create_app_version
162
+ version_label = generate_version_label()
163
+ response = @elasticbeanstalk.create_application_version(
164
+ {
165
+ application_name: @app,
166
+ version_label: version_label,
167
+ })
168
+ version_label
169
+ rescue => e
170
+ raise "Application version launch failed. Error: #{e}"
171
+ end
172
+
173
+ def deploy_app(version_label)
174
+ puts "Deploying application to Beanstalk environment."
175
+ s3_upload_details = upload_version_to_s3(version_label)
176
+ create_app_version(version_label, s3_upload_details)
177
+ update_environment(version_label)
178
+ end
179
+
180
+ def upload_version_to_s3(version_label)
181
+ zip_name = make_zip_file(version_label)
182
+ zip_name_with_path = "#{@app}/#{zip_name}"
183
+ print "Uploading '#{zip_name}' to S3 bucket (#{@s3_bucket_name})... "
184
+ begin
185
+ File.open(zip_name) do |zip_file|
186
+ @s3.put_object(bucket: @s3_bucket_name, body: zip_file, key: zip_name_with_path)
187
+ end
188
+ print "Done!\n"
189
+ {bucket: @s3_bucket_name, file: zip_name_with_path}
190
+ rescue => e
191
+ raise "Upload to S3 bucket (#{@s3_bucket_name}) failed. Error: #{e}"
192
+ end
193
+ end
194
+
195
+ def make_zip_file(version_label)
196
+ print 'Zipping application files ... '
197
+ zip_name = "#{version_label}.zip"
198
+ `cd #{@dir}; zip #{zip_name} -r ./ -x *.git* *.log*`
199
+ raise "Creating ZIP failed!" if $?.exitstatus != 0
200
+ FileUtils.mv("#{@dir}/#{zip_name}", './')
201
+ print "Done!\n"
202
+ zip_name
203
+ end
204
+
205
+ def create_app_version(version_label, s3_upload_details)
206
+ if @app.length > 199
207
+ raise "Error! Beanstalk environment name max length is 200 characters! Length: #{@app.length} characters"
208
+ end
209
+ @elasticbeanstalk.create_application_version(
210
+ {
211
+ application_name: @app,
212
+ version_label: version_label,
213
+ source_bundle: {
214
+ s3_bucket: s3_upload_details[:bucket],
215
+ s3_key: s3_upload_details[:file],
216
+ },
217
+ auto_create_application: true,
218
+ process: true,
219
+ })
220
+ puts "Created application version '#{version_label}'"
221
+ rescue => e
222
+ raise "Creating application version '#{version_label}' failed. Error: #{e}"
223
+ end
224
+
225
+ def update_environment(version_label)
226
+ sleep(5) # Environment is in an invalid state for this operation. Must be Ready. (RuntimeError)
227
+ @elasticbeanstalk.update_environment(
228
+ {
229
+ environment_name: "#{@app}-#{@bs_env}",
230
+ version_label: version_label,
231
+ option_settings: [
232
+ {
233
+ namespace: "aws:elasticbeanstalk:command",
234
+ option_name: "Timeout",
235
+ value: "1800",
236
+ }
237
+ ]
238
+ })
239
+ if (poll_for_environment_changes("#{@app}-#{@bs_env}") { |env| env.status != 'Updating' })
240
+ puts "Updated '#{@app}-#{@bs_env}' environment successfully."
241
+ else
242
+ raise "Timeout of 10 minutes reached when updating environment."
243
+ end
244
+ rescue => e
245
+ raise "Updating environment failed. Error: #{e}"
246
+ end
247
+
248
+ def generate_version_label
249
+ time = Time.now
250
+ "#{@app}-#{@bs_env_simple}-#{time.strftime('%Y-%m-%d-%H%M%S')}"
251
+ end
252
+
253
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aws_beanstalk_tunkki
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Valtteri Pajunen
8
+ - Janne Saraste
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-12-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: aws-sdk-elasticbeanstalk
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '1.26'
21
+ - - "~>"
22
+ - !ruby/object:Gem::Version
23
+ version: '1.26'
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: '1.26'
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.26'
34
+ - !ruby/object:Gem::Dependency
35
+ name: aws-sdk-s3
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '1.59'
41
+ - - "~>"
42
+ - !ruby/object:Gem::Version
43
+ version: '1.59'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '1.59'
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.59'
54
+ description: Tool for deploying your app to AWS ElasticBeanstalk.
55
+ email: IL.Tekniikka@iltalehti.fi
56
+ executables:
57
+ - aws_beanstalk_tunkki
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - bin/aws_beanstalk_tunkki
62
+ - lib/aws_beanstalk_tunkki.rb
63
+ homepage: https://github.com/almamedia/aws-beanstalk-tunkki
64
+ licenses:
65
+ - MIT
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubygems_version: 3.0.3
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: AWS Beanstalk Tunkki
86
+ test_files: []