rebi 0.1.6

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
+ SHA1:
3
+ metadata.gz: e86f234a1a4dcdb347b576c07f449d68f4ef0918
4
+ data.tar.gz: 26fc0b37540c17cc4008f3bafb278db933591e01
5
+ SHA512:
6
+ metadata.gz: 0b3818bc2410ee9b0bfb567b8ee76f6c98ffd312f11b806837dc21c4766c9428f5fb7c6c6cb4cbe7d8a4f49641aeada918c43c9557112c791676f1aecf9accf1
7
+ data.tar.gz: 14f13a7ced8fcec217bbff5149f86b18914a5b63b3a27444253631b57174a45dedd13c04d6ca897eb9b60f197074968276cc2bc36db00d7cfc4b8e22e139bc80
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # Rebi
2
+ Deployment tool for Elasticbeanstalk
3
+
4
+ # Features
5
+ - Switchable + multiple ebextensions folder
6
+ - Support erb in ebextension config files
7
+ - Support env_file for environment variables
8
+ - Multiple deployment
9
+ - Deploy source code along with updating beanstalk options
10
+ - Simple config
11
+
12
+ ## Installation
13
+ Or install it yourself as:
14
+ ```bash
15
+ $ gem install rebi
16
+ ```
17
+
18
+ ## Usage
19
+ How to use my plugin.
20
+
21
+ ### AWS authentication
22
+ Rebi uses environment variables to get aws credentials
23
+ To set environment variables use `export` or `.env` file
24
+ ```bash
25
+ # Use access key
26
+ export AWS_ACCESS_KEY_ID=xxxxx
27
+ export AWS_SECRET_ACCESS_KEY=xxxxxx
28
+ ```
29
+ Or
30
+ ```bash
31
+ # Use profile
32
+ export AWS_PROFILE=xxxxx
33
+ ```
34
+
35
+ Refer http://docs.aws.amazon.com/sdkforruby/api/Aws/ElasticBeanstalk/Client.html for other settings
36
+
37
+ ### Yaml config
38
+ Default config file is `config/rebi.yml` use `-c` to switch
39
+ ```yaml
40
+ app_name: app-name
41
+ stages:
42
+ development:
43
+ web:
44
+ name: web01
45
+ env_file: .env.development
46
+ ebextensions: "web-ebextensions"
47
+ ```
48
+
49
+ For other configs( key_name, instance_type, instance_num, service_role,...), please refer sample config
50
+ ```bash
51
+ $ bundle exec rebi sample > rebi.yml
52
+ ```
53
+
54
+ ### Deploy
55
+ ```bash
56
+ # Single deploy
57
+ $ bundle exec rebi deploy development web
58
+ ```
59
+
60
+ ```bash
61
+ # Multiple deploy (if development has more than one environments)
62
+ $ bundle exec rebi deploy development
63
+ ```
64
+
65
+ ### Get envronment variables and status
66
+ ```bash
67
+ # Running envronment variables
68
+ $ bundle exec rebi get_env development
69
+ # envronment variables for config
70
+ $ bundle exec rebi get_env development --from-config
71
+ # Status
72
+ $ bundle exec rebi status development
73
+ ```
74
+
75
+ ###For more help
76
+ ```bash
77
+ $ bundle exec rebi --help
78
+ ```
79
+
80
+ ### ERB in ebextensions config
81
+ Use `rebi_env` to get environment variables config in .ebextensions
82
+ ```yaml
83
+ # Ex
84
+ # .ebextensions/00-envrionments.config
85
+ option_settings:
86
+ - option_name: KEY
87
+ value: <%= rebi_env[KEY] %>
88
+ ```
89
+
90
+ ## Contributing
91
+ Feel free to fork and request a pull, or submit a ticket
92
+ https://github.com/khiemns54/rebi/issues
93
+
94
+ ## License
95
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Rebi'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
data/bin/rebi ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'commander/import'
5
+ require 'rebi'
6
+
7
+ REBI_PATH = File.join(File.dirname(File.expand_path(__FILE__)), "..")
8
+
9
+ program :name, 'Rebi'
10
+ program :version, Rebi::VERSION
11
+ program :description, 'Elasticbeanstalk deployment tool'
12
+
13
+ global_option('-c', '--config FILE', 'Load config data for your commands to use') do |file|
14
+ Rebi.config.config_file = file
15
+ Rebi.config.reload!
16
+ end
17
+
18
+ command :deploy do |c|
19
+ c.syntax = 'rebi deploy stage [env_name] [--options]'
20
+ c.description = 'Deploy single or multiple ElasticBeanstalk environments'
21
+ c.example 'Deploy only web environment in development', 'rebi deploy development web'
22
+ c.example 'Deploy all environments in development', 'rebi deploy development'
23
+ c.option '--source-only', 'Deploy source only'
24
+ c.option '--settings-only', 'Deploy option_settings and environment variables only'
25
+ c.action do |args, options|
26
+ stage, env_name = args
27
+ raise Rebi::Error.new("Stage cannot be nil") if stage.blank?
28
+ opts = options.__hash__
29
+ if env_name.present?
30
+ Rebi.app.deploy stage, env_name, opts
31
+ else
32
+ if agree("Do you want to deploy all environments in #{stage} stage?(Y/n)")
33
+ Rebi.log("Preparing for deployment")
34
+ Rebi.app.deploy stage, nil, opts
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ command :get_env do |c|
41
+ c.syntax = 'rebi get_env stage [env_name] [--options]'
42
+ c.description = 'Get using environment variables or environment variables from config file'
43
+ c.example 'Get environment variables of web in development', 'rebi get_env development web'
44
+ c.example 'Get environment variables of development stages', 'rebi get_env development'
45
+ c.option '--from-config', 'Get environment variables from config'
46
+ c.action do |args, options|
47
+ stage, env_name = args
48
+ raise Rebi::Error.new("Stage cannot be nil") if stage.blank?
49
+ Rebi.app.print_environment_variables stage, env_name, options.from_config
50
+ end
51
+ end
52
+
53
+ command :status do |c|
54
+ c.syntax = 'rebi status stage [env_name]'
55
+ c.description = 'Get current status'
56
+ c.action do |args, options|
57
+ stage, env_name = args
58
+ raise Rebi::Error.new("Stage cannot be nil") if stage.blank?
59
+ Rebi.app.print_environment_status stage, env_name
60
+ end
61
+ end
62
+
63
+ command :sample do |c|
64
+ c.syntax = 'rebi sample '
65
+ c.description = 'Create sample config yaml'
66
+ c.example 'Create sample', 'rebi sample > rebi.yml '
67
+ c.action do |args, options|
68
+ puts File.read("#{REBI_PATH}/sample/rebi.yml")
69
+ end
70
+ end
71
+
72
+ command :terminate do |c|
73
+ c.syntax = 'rebi terminate stage env_name '
74
+ c.description = 'Terminate environment'
75
+ c.action do |args, options|
76
+ stage, env_name = args
77
+ raise Rebi::Error.new("Stage cannot be nil") if stage.blank?
78
+ raise Rebi::Error.new("Env name cannot be nil") if env_name.blank?
79
+ env_conf = Rebi.config.environment(stage, env_name)
80
+ if ask("Type '#{env_conf.name}' to confirm termination") == env_conf.name
81
+ Rebi.app.terminate! stage, env_name
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,152 @@
1
+ module Rebi
2
+ class Application
3
+ attr_reader :app, :app_name, :client, :s3_client
4
+ def initialize app, client
5
+ @app = app
6
+ @app_name = app.application_name
7
+ @client = client
8
+ @s3_client = Aws::S3::Client.new
9
+ end
10
+
11
+ def bucket_name
12
+ @bucket_name ||= client.create_storage_location.s3_bucket
13
+ end
14
+
15
+ def environments
16
+ Rebi::Environment.all app_name
17
+ end
18
+
19
+ def deploy stage_name, env_name=nil, opts={}
20
+ return deploy_stage(stage_name, opts) if env_name.blank?
21
+ env = Rebi::Environment.new stage_name, env_name, client
22
+ app_version = create_app_version env
23
+ begin
24
+ req_id = env.deploy app_version, opts
25
+ env.watch_request req_id if req_id
26
+ rescue Rebi::Error::EnvironmentInUpdating => e
27
+ Rebi.log("Environment in updating", env.name)
28
+ raise e
29
+ end
30
+ req_id
31
+ end
32
+
33
+ def deploy_stage stage_name, opts={}
34
+ threads = []
35
+ Rebi.config.stage(stage_name).each do |env_name, conf|
36
+ next if conf.blank?
37
+ threads << Thread.new do
38
+ begin
39
+ deploy stage_name, env_name, opts
40
+ rescue Exception => e
41
+ Rebi.log(e.message, "ERROR")
42
+ e.backtrace.each do |m|
43
+ Rebi.log(m, "ERROR")
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ ThreadsWait.all_waits(*threads)
50
+ end
51
+
52
+ def print_environment_variables stage_name, env_name, from_config=false
53
+ if env_name.blank?
54
+ Rebi.config.stage(stage_name).each do |e_name, conf|
55
+ next if conf.blank?
56
+ print_environment_variables stage_name, e_name, from_config
57
+ end
58
+ return
59
+ end
60
+
61
+ env = Rebi::Environment.new stage_name, env_name, client
62
+ env_vars = from_config ? env.config.environment_variables : env.environment_variables
63
+
64
+ Rebi.log("#{from_config ? "Config" : "Current"} environment variables", env.name)
65
+ env_vars.each do |k,v|
66
+ Rebi.log("#{k}=#{v}")
67
+ end
68
+ Rebi.log("--------------------------------------", env.name)
69
+ end
70
+
71
+ def print_environment_status stage_name, env_name
72
+ if env_name.blank?
73
+ Rebi.config.stage(stage_name).each do |e_name, conf|
74
+ next if conf.blank?
75
+ print_environment_status stage_name, e_name
76
+ end
77
+ return
78
+ end
79
+
80
+ env = Rebi::Environment.new stage_name, env_name, client
81
+ env.check_created!
82
+ Rebi.log("--------- CURRENT STATUS -------------", env.name)
83
+ Rebi.log("id: #{env.id}", env.name)
84
+ Rebi.log("Status: #{env.status}", env.name)
85
+ Rebi.log("Health: #{env.health}", env.name)
86
+ Rebi.log("--------------------------------------", env.name)
87
+ end
88
+
89
+ def terminate! stage_name, env_name
90
+ env = Rebi::Environment.new stage_name, env_name, client
91
+ begin
92
+ req_id = env.terminate!
93
+ ThreadsWait.all_waits(env.watch_request req_id) if req_id
94
+ rescue Rebi::Error::EnvironmentInUpdating => e
95
+ Rebi.log("Environment in updating", env.name)
96
+ raise e
97
+ end
98
+ end
99
+
100
+ def create_app_version env
101
+ start = Time.now.utc
102
+ source_bundle = Rebi::ZipHelper.new.gen(env.config)
103
+ version_label = source_bundle[:label]
104
+ key = "#{app_name}/#{version_label}.zip"
105
+ Rebi.log("Uploading source bundle: #{version_label}.zip", env.config.name)
106
+ s3_client.put_object(
107
+ bucket: bucket_name,
108
+ key: key,
109
+ body: source_bundle[:file].read
110
+ )
111
+ Rebi.log("Creating app version: #{version_label}", env.config.name)
112
+ client.create_application_version({
113
+ application_name: app_name,
114
+ description: source_bundle[:message],
115
+ version_label: version_label,
116
+ source_bundle: {
117
+ s3_bucket: bucket_name,
118
+ s3_key: key
119
+ }
120
+ })
121
+ Rebi.log("App version was created in: #{Time.now.utc - start}s", env.config.name)
122
+ return version_label
123
+ end
124
+
125
+
126
+ def self.client
127
+ Rebi.client || Aws::ElasticBeanstalk::Client.new
128
+ end
129
+
130
+ def self.get_application app_name
131
+ raise Error::ApplicationNotFound.new unless app = client.describe_applications(application_names: [app_name]).applications.first
132
+ return Rebi::Application.new(app, client)
133
+ end
134
+
135
+ def self.create_application app_name, description=nil
136
+ res = client.create_application(
137
+ application_name: app_name,
138
+ description: description,
139
+ )
140
+ return Rebi::Application.new(res.application, client)
141
+ end
142
+
143
+ def self.get_or_create_application app_name
144
+ begin
145
+ return get_application app_name
146
+ rescue Error::ApplicationNotFound
147
+ return create_application app_name, Rebi.config.app_description
148
+ end
149
+ end
150
+
151
+ end
152
+ end
@@ -0,0 +1,48 @@
1
+ module Rebi
2
+ class Config
3
+ include Singleton
4
+
5
+ def config_file
6
+ @config_file ||= "#{Rebi.root}/config/rebi.yml"
7
+ end
8
+
9
+ def config_file=path
10
+ @config_file = Pathname.new(path).realpath.to_s
11
+ end
12
+
13
+ def reload!
14
+ @data = nil
15
+ return data
16
+ end
17
+
18
+ def app_name
19
+ data[:app_name]
20
+ end
21
+
22
+ def app_description
23
+ data[:app_description] || "Created via rebi"
24
+ end
25
+
26
+ def stage stage
27
+ data[:stages][stage] || raise(Rebi::Error::ConfigNotFound.new("Stage: #{stage}"))
28
+ end
29
+
30
+ def timeout
31
+ (data[:timeout] || 60*10).second
32
+ end
33
+
34
+ def environment stg_name, env_name
35
+ stg = stage stg_name
36
+ raise(Rebi::Error::ConfigNotFound.new("Environment config: #{env_name}")) unless stg.key?(env_name)
37
+ return Rebi::ConfigEnvironment.new(stg_name, env_name, stg[env_name] || {})
38
+ end
39
+
40
+ private
41
+ def data
42
+ @data ||= YAML::load(ERB.new(IO.read(config_file)).result).with_indifferent_access
43
+ raise Rebi::Error::ConfigInvalid.new("app_name cannot be nil") if @data[:app_name].blank?
44
+ raise Rebi::Error::ConfigInvalid.new("stages cannot be nil") if @data[:stages].blank?
45
+ return @data
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,317 @@
1
+ require 'pathname'
2
+
3
+ module Rebi
4
+ class ConfigEnvironment
5
+
6
+ attr_reader :stage,
7
+ :env_name,
8
+ :name,
9
+ :description,
10
+ :cname_prefix,
11
+ :tier,
12
+ :instance_type,
13
+ :instance_num,
14
+ :key_name,
15
+ :service_role,
16
+ :ebextensions,
17
+ :solution_stack_name,
18
+ :cfg_file,
19
+ :env_file,
20
+ :environment_variables,
21
+ :option_settings,
22
+ :raw_conf
23
+
24
+ NAMESPACE ={
25
+ app_env: "aws:elasticbeanstalk:application:environment",
26
+ eb_env: "aws:elasticbeanstalk:environment",
27
+ autoscaling_launch: "aws:autoscaling:launchconfiguration",
28
+ autoscaling_asg: "aws:autoscaling:asg",
29
+ elb_policies: "aws:elb:policies",
30
+ elb_health: "aws:elb:healthcheck",
31
+ elb_loadbalancer: "aws:elb:loadbalancer",
32
+ healthreporting: "aws:elasticbeanstalk:healthreporting:system",
33
+ eb_command: "aws:elasticbeanstalk:command",
34
+ }
35
+
36
+ UPDATEABLE_NS = {
37
+ autoscaling_asg: [:MaxSize, :MinSize],
38
+ autoscaling_launch: [:InstanceType, :EC2KeyName],
39
+ eb_env: [:ServiceRole],
40
+ }
41
+
42
+ def initialize stage, env_name, env_conf={}
43
+ @raw_conf = env_conf.with_indifferent_access
44
+ @stage = stage.to_sym
45
+ @env_name = env_name.to_sym
46
+ end
47
+
48
+ def name
49
+ @name ||= raw_conf[:name] || "#{env_name}-#{stage}"
50
+ end
51
+
52
+ def app_name
53
+ Rebi.config.app_name
54
+ end
55
+
56
+ def description
57
+ @description ||= raw_conf[:description] || "Created via rebi"
58
+ end
59
+
60
+ def cname_prefix
61
+ @cname_prefix ||= raw_conf[:cname_prefix] || "#{name}-#{stage}"
62
+ end
63
+
64
+ def tier
65
+ return @tier if @tier
66
+ t = if raw_conf[:tier]
67
+ raw_conf[:tier].to_sym
68
+ elsif cfg.present? && cfg[:EnvironmentTier].present? && cfg[:EnvironmentTier][:Name].present?
69
+ cfg[:EnvironmentTier][:Name] == "Worker" ? :worker : :web
70
+ else
71
+ :web
72
+ end
73
+
74
+ @tier = if t == :web
75
+ {
76
+ name: "WebServer",
77
+ type: "Standard",
78
+ }
79
+ elsif t == :worker
80
+ {
81
+ name: "Worker",
82
+ type: "SQS/HTTP",
83
+ }
84
+ else
85
+ Rebi::Error::ConfigInvalid.new("Invalid tier")
86
+ end
87
+
88
+ return @tier = @tier.with_indifferent_access
89
+ end
90
+
91
+ def worker?
92
+ tier[:name] == "Worker" ? true : false
93
+ end
94
+
95
+ def instance_type
96
+ get_opt ns[:autoscaling_launch], :InstanceType
97
+ end
98
+
99
+ def instance_num
100
+ {
101
+ min: get_opt(ns[:autoscaling_asg], :MinSize),
102
+ max: get_opt(ns[:autoscaling_asg], :MaxSize)
103
+ }
104
+ end
105
+
106
+ def key_name
107
+ get_opt(ns[:autoscaling_launch], :EC2KeyName)
108
+ end
109
+
110
+ def service_role
111
+ get_opt(ns[:eb_env], :ServiceRole)
112
+ end
113
+
114
+ def cfg_file
115
+ @cfg_file ||= raw_conf[:cfg_file]
116
+ return @cfg_file if @cfg_file.blank?
117
+ return @cfg_file if Pathname.new(@cfg_file).absolute?
118
+ @cfg_file = ".elasticbeanstalk/saved_configs/#{@cfg_file}.cfg.yml" unless @cfg_file.match(".yml$")
119
+ return (@cfg_file = "#{Rebi.root}/#{@cfg_file}")
120
+ end
121
+
122
+ def env_file
123
+ @env_file ||= raw_conf[:env_file]
124
+ end
125
+
126
+ def cfg
127
+ begin
128
+ return nil if cfg_file.blank?
129
+ return (@cfg ||= YAML.load(ERB.new(IO.read(cfg_file)).result).with_indifferent_access)
130
+ rescue Errno::ENOENT
131
+ raise Rebi::Error::ConfigInvalid.new("cfg_file: #{cfg_file}")
132
+ end
133
+ end
134
+
135
+ def solution_stack_name
136
+ @solution_stack_name ||= raw_conf[:solution_stack_name] || "64bit Amazon Linux 2017.03 v2.4.1 running Ruby 2.3 (Puma)"
137
+ end
138
+
139
+ def platform_arn
140
+ cfg && cfg[:Platform] && cfg[:Platform][:PlatformArn]
141
+ end
142
+
143
+ def ebextensions
144
+ return @ebextensions ||= if (ebx = raw_conf[:ebextensions])
145
+ ebx = [ebx] if ebx.is_a?(String)
146
+ ebx.prepend(".ebextensions") unless ebx.include?(".ebextensions")
147
+ ebx
148
+ else
149
+ [".ebextensions"]
150
+ end
151
+ end
152
+
153
+ def raw_environment_variables
154
+ raw_conf[:environment_variables] || {}
155
+ end
156
+
157
+ def dotenv
158
+ env_file.present? ? Dotenv.load(env_file).with_indifferent_access : {}
159
+ end
160
+
161
+ def environment_variables
162
+ get_opt(ns(:app_env))
163
+ end
164
+
165
+ def get_opt namespace, opt_name=nil
166
+ has_value_by_keys(option_settings, namespace, opt_name)
167
+ end
168
+
169
+ def get_raw_opt namespace, opt_name=nil
170
+ has_value_by_keys(raw_conf[:option_settings], namespace, opt_name)
171
+ end
172
+
173
+ def opts_array opts=option_settings
174
+ res = []
175
+ opts.each do |namespace, v|
176
+ namespace, resource_name = namespace.split(".").reverse
177
+ v.each do |option_name, value|
178
+ res << {
179
+ resource_name: resource_name,
180
+ namespace: namespace,
181
+ option_name: option_name,
182
+ value: value.to_s,
183
+ }.with_indifferent_access
184
+ end
185
+ end
186
+ return res
187
+ end
188
+
189
+ def option_settings
190
+ return @opt if @opt.present?
191
+ opt = (cfg && cfg[:OptionSettings]) || {}.with_indifferent_access
192
+
193
+ opt = opt.deep_merge(raw_conf[:option_settings] || {})
194
+
195
+ ns.values.each do |ns|
196
+ opt[ns] = {}.with_indifferent_access if opt[ns].blank?
197
+ end
198
+
199
+ opt = set_opt_default opt
200
+ opt = set_opt_env_var opt
201
+ opt = set_opt_keyname opt
202
+ opt = set_opt_instance_type opt
203
+ opt = set_opt_instance_num opt
204
+ opt = set_opt_service_role opt
205
+
206
+ return @opt = opt
207
+ end
208
+
209
+ def ns key=nil
210
+ key.present? ? NAMESPACE[key.to_sym] : NAMESPACE
211
+ end
212
+
213
+ private
214
+
215
+ def has_value_by_keys(hash, *keys)
216
+ if keys.empty? || hash.blank?
217
+ return hash
218
+ else
219
+ return hash unless k = keys.shift
220
+ return hash[k] && has_value_by_keys(hash[k], *keys)
221
+ end
222
+ end
223
+
224
+ def set_opt_keyname opt
225
+ k = if raw_conf.key?(:key_name)
226
+ raw_conf[:key_name]
227
+ elsif get_raw_opt(ns[:autoscaling_launch], :EC2KeyName)
228
+ get_raw_opt(ns[:autoscaling_launch], :EC2KeyName)
229
+ else
230
+ nil
231
+ end
232
+ if k.present?
233
+ opt[ns[:autoscaling_launch]].merge!({
234
+ EC2KeyName: k
235
+ }.with_indifferent_access)
236
+ end
237
+ return opt
238
+ end
239
+
240
+ def set_opt_service_role opt
241
+ s_role = if raw_conf.key?(:service_role)
242
+ raw_conf[:service_role]
243
+ elsif role = get_raw_opt(ns[:eb_env], :ServiceRole)
244
+ role
245
+ else
246
+ 'aws-elasticbeanstalk-service-role'
247
+ end
248
+ if s_role.present?
249
+ opt[ns[:eb_env]].merge!({
250
+ ServiceRole: s_role,
251
+ }.with_indifferent_access)
252
+ end
253
+ return opt
254
+ end
255
+
256
+ def set_opt_instance_type opt
257
+ itype = raw_conf[:instance_type] \
258
+ || get_raw_opt(ns[:autoscaling_launch], :InstanceType) \
259
+ || "t2.small"
260
+ if itype.present?
261
+ opt[ns[:autoscaling_launch]].merge!({
262
+ InstanceType: itype
263
+ }.with_indifferent_access)
264
+ end
265
+ return opt
266
+ end
267
+
268
+ def set_opt_instance_num opt
269
+ max = min = 1
270
+ if raw_conf[:instance_num].present?
271
+ min = raw_conf[:instance_num][:min]
272
+ max = raw_conf[:instance_num][:min]
273
+ elsif mi = get_raw_opt(ns[:autoscaling_asg], :MinSize) \
274
+ || ma = get_raw_opt(ns[:autoscaling_asg], :MaxSize)
275
+ min = mi if mi
276
+ max = ma if ma
277
+ end
278
+ opt[ns[:autoscaling_asg]].merge!({
279
+ MaxSize: max,
280
+ MinSize: min,
281
+ }.with_indifferent_access)
282
+ return opt
283
+ end
284
+
285
+ def set_opt_env_var opt
286
+ opt[ns[:app_env]].merge!(dotenv.merge(raw_environment_variables))
287
+ return opt
288
+ end
289
+
290
+ def set_opt_default opt
291
+
292
+ opt[ns[:healthreporting]].reverse_merge!({
293
+ SystemType: 'enhanced',
294
+ }.with_indifferent_access)
295
+
296
+ opt[ns[:eb_command]].reverse_merge!({
297
+ BatchSize: "50",
298
+ BatchSizeType: "Percentage",
299
+ }.with_indifferent_access)
300
+
301
+ unless worker?
302
+ opt[ns[:elb_policies]].reverse_merge!({
303
+ ConnectionDrainingEnabled: true
304
+ }.with_indifferent_access)
305
+
306
+ opt[NAMESPACE[:elb_health]].reverse_merge!({
307
+ Interval: 30
308
+ }.with_indifferent_access)
309
+
310
+ opt[NAMESPACE[:elb_loadbalancer]].reverse_merge!({
311
+ CrossZone: true
312
+ }.with_indifferent_access)
313
+ end
314
+ return opt
315
+ end
316
+ end
317
+ end
@@ -0,0 +1,305 @@
1
+ module Rebi
2
+ class Environment
3
+
4
+ attr_reader :stage_name,
5
+ :env_name,
6
+ :app_name,
7
+ :name,
8
+ :config
9
+
10
+ attr_accessor :client,
11
+ :api_data
12
+
13
+
14
+ RESPONSES = {
15
+ 'event.createstarting': 'createEnvironment is starting.',
16
+ 'event.terminatestarting': 'terminateEnvironment is starting.',
17
+ 'event.updatestarting': 'Environment update is starting.',
18
+ 'event.redmessage': 'Environment health has been set to RED',
19
+ 'event.commandfailed': 'Command failed on instance',
20
+ 'event.launchfailed': 'Failed to launch',
21
+ 'event.deployfailed': 'Failed to deploy application.',
22
+ 'event.redtoyellowmessage': 'Environment health has transitioned from YELLOW to RED',
23
+ 'event.yellowmessage': 'Environment health has been set to YELLOW',
24
+ 'event.greenmessage': 'Environment health has been set to GREEN',
25
+ 'event.launchsuccess': 'Successfully launched environment:',
26
+ 'event.launchbad': 'Create environment operation is complete, but with errors',
27
+ 'event.updatebad': 'Update environment operation is complete, but with errors.',
28
+ 'git.norepository': 'Error: Not a git repository (or any of the parent directories): .git',
29
+ 'env.updatesuccess': 'Environment update completed successfully.',
30
+ 'env.cnamenotavailable': 'DNS name \([^ ]+\) is not available.',
31
+ 'env.nameexists': 'Environment [^ ]+ already exists.',
32
+ 'app.deletesuccess': 'The application has been deleted successfully.',
33
+ 'app.exists': 'Application {app-name} already exists.',
34
+ 'app.notexists': 'No Application named {app-name} found.',
35
+ 'logs.pulled': 'Pulled logs for environment instances.',
36
+ 'logs.successtail': 'Successfully finished tailing',
37
+ 'logs.successbundle': 'Successfully finished bundling',
38
+ 'env.terminated': 'terminateEnvironment completed successfully.',
39
+ 'env.invalidstate': 'Environment named {env-name} is in an invalid state for this operation. Must be Ready.',
40
+ 'loadbalancer.notfound': 'There is no ACTIVE Load Balancer named',
41
+ 'ec2.sshalreadyopen': 'the specified rule "peer: 0.0.0.0/0, TCP, from port: 22, to port: 22,',
42
+ }
43
+
44
+ def initialize stage_name, env_name, client=Rebi.client
45
+ @stage_name = stage_name
46
+ @env_name = env_name
47
+ @client = client
48
+ @config = Rebi.config.environment(stage_name, env_name)
49
+ @app_name = @config.app_name
50
+ @api_data
51
+ end
52
+
53
+ def name
54
+ created? ? api_data.environment_name : config.name
55
+ end
56
+
57
+ def cname
58
+ created? ? api_data.cname : nil
59
+ end
60
+
61
+ def version_label
62
+ created? ? api_data.version_label : nil
63
+ end
64
+
65
+ def id
66
+ created? ? api_data.environment_id : nil
67
+ end
68
+
69
+ def status
70
+ check_created! && api_data.status
71
+ end
72
+
73
+ def in_updating?
74
+ !!status.match("ing$")
75
+ end
76
+
77
+ def health
78
+ check_created! && api_data.health
79
+ end
80
+
81
+ def option_settings
82
+ check_created!
83
+ client.describe_configuration_settings({
84
+ application_name: app_name,
85
+ environment_name: name
86
+ }).configuration_settings.first.option_settings.map do |o|
87
+ {
88
+ namespace: o.namespace,
89
+ value: o.value,
90
+ resource_name: o.resource_name,
91
+ option_name: o.option_name,
92
+ }.with_indifferent_access
93
+ end
94
+ end
95
+
96
+ def environment_variables
97
+ option_settings.select do |o|
98
+ o[:namespace] == config.ns(:app_env)
99
+ end.map do |o|
100
+ [o[:option_name], o[:value]]
101
+ end.to_h.with_indifferent_access
102
+ end
103
+
104
+ def check_created
105
+ raise Rebi::Error::EnvironmentNotExisted.new("#{name} not exists") unless created?
106
+ return created?
107
+ end
108
+
109
+ # refresh data
110
+ def check_created!
111
+ refresh
112
+ check_created
113
+ end
114
+
115
+ def created?
116
+ !!api_data
117
+ end
118
+
119
+ def api_data
120
+ @api_data ||= client.describe_environments(application_name: config.app_name,
121
+ environment_names:[config.name],
122
+ include_deleted: false).environments.first
123
+ end
124
+
125
+ def response_msgs key=nil
126
+ @response_msgs ||= RESPONSES.with_indifferent_access
127
+ return key ? @response_msgs[key] : @response_msgs
128
+ end
129
+
130
+ def watch_request request_id
131
+ check_created!
132
+ start = Time.now
133
+ finished = false
134
+ last_time = Time.now - 30.minute
135
+ thread = Thread.new do
136
+ while (start + Rebi.config.timeout) > Time.now && !finished
137
+ events(last_time, request_id).reverse.each do |e|
138
+ finished ||= success_message?(e.message)
139
+ last_time = [last_time + 1.second, e.event_date + 1.second].max
140
+ log(e.message)
141
+ end
142
+ sleep(5) unless finished
143
+ end
144
+ log ("Timeout") unless finished
145
+ end
146
+ thread.join
147
+ return thread
148
+ end
149
+
150
+ def events start_time=Time.now, request_id=nil
151
+ client.describe_events({
152
+ application_name: app_name,
153
+ environment_name: name,
154
+ start_time: start_time,
155
+ }.merge( request_id ? {request_id: request_id} : {})).events
156
+ end
157
+
158
+ def refresh
159
+ self.api_data = nil
160
+ return self
161
+ end
162
+
163
+ def init version_label, opts={}
164
+ log("Creating new environment")
165
+ start_time = Time.now
166
+ self.api_data = client.create_environment({
167
+ application_name: config.app_name,
168
+ cname_prefix: config.cname_prefix,
169
+ environment_name: config.name,
170
+ version_label: version_label,
171
+ tier: config.tier,
172
+ description: config.description,
173
+ option_settings: config.opts_array,
174
+ }.merge(if(config.platform_arn)
175
+ { platform_arn: config.platform_arn}
176
+ else
177
+ { solution_stack_name: config.solution_stack_name}
178
+ end))
179
+ request_id = events(start_time).select do |e|
180
+ e.message.match(response_msgs('event.createstarting'))
181
+ end.map(&:request_id).first
182
+ return request_id
183
+ end
184
+
185
+ def update version_label, opts={}
186
+ raise Rebi::Error::EnvironmentInUpdating.new(name) if in_updating?
187
+ log("Start updating")
188
+ start_time = Time.now
189
+ deploy_opts = gen_deploy_opts
190
+ deploy_args = {
191
+ application_name: config.app_name,
192
+ environment_name: config.name,
193
+ version_label: version_label,
194
+ description: config.description,
195
+ option_settings: deploy_opts[:option_settings],
196
+ options_to_remove: deploy_opts[:options_to_remove],
197
+ }
198
+
199
+ if opts[:source_only]
200
+ deploy_args.delete(:option_settings)
201
+ deploy_args.delete(:options_to_remove)
202
+ elsif opts[:settings_only]
203
+ deploy_args.delete(:version_label)
204
+ end
205
+
206
+ self.api_data = client.update_environment(deploy_args)
207
+
208
+ request_id = events(start_time).select do |e|
209
+ e.message.match(response_msgs('event.updatestarting'))
210
+ end.map(&:request_id).first
211
+
212
+ return request_id
213
+ end
214
+
215
+ def deploy version_label, opts={}
216
+ request_id = if created?
217
+ update version_label, opts
218
+ else
219
+ init version_label, opts
220
+ end
221
+ return request_id
222
+ end
223
+
224
+ def terminate!
225
+ check_created
226
+ log("Start terminating")
227
+ client.terminate_environment({
228
+ environment_name: name,
229
+ environment_id: id,
230
+ })
231
+ start_time = Time.now
232
+
233
+ request_id = events(start_time).select do |e|
234
+ e.message.match(response_msgs('event.updatestarting'))
235
+ end.map(&:request_id).first
236
+ return request_id
237
+ end
238
+
239
+ def log mes
240
+ Rebi.log(mes, name)
241
+ end
242
+
243
+ def success_message? mes
244
+ return true if [
245
+ 'event.greenmessage',
246
+ 'event.launchsuccess',
247
+ 'logs.pulled',
248
+ 'env.terminated',
249
+ 'env.updatesuccess',
250
+ 'app.deletesuccess',
251
+ ].map{|k| response_msgs(k)}.any?{|s| mes.match(s)}
252
+
253
+ if [
254
+ 'event.redmessage',
255
+ 'event.launchbad',
256
+ 'event.updatebad',
257
+ 'event.commandfailed',
258
+ 'event.launchfailed',
259
+ 'event.deployfailed',
260
+ ].map {|k| response_msgs(k)}.any? {|s| mes.match(s)}
261
+ raise Rebi::Error::ServiceError.new(mes)
262
+ end
263
+
264
+ return false
265
+ end
266
+
267
+ def gen_deploy_opts
268
+ to_deploy = []
269
+ to_remove = []
270
+ config.opts_array.each do |o|
271
+ o = o.deep_dup
272
+ if o[:namespace] == config.ns(:app_env) && o[:value].blank?
273
+ o.delete(:value)
274
+ to_remove << o
275
+ next
276
+ end
277
+ to_deploy << o
278
+ end
279
+ return {
280
+ option_settings: to_deploy,
281
+ options_to_remove: to_remove,
282
+ }
283
+ end
284
+
285
+ def self.create stage_name, env_name, version_label, client
286
+ env = new stage_name, env_name, client
287
+ raise Rebi::Error::EnvironmentExisted.new if env.created?
288
+
289
+ env.init version_label
290
+ return env
291
+ end
292
+
293
+ # TODO
294
+ def self.all client=Rebi.client
295
+ client.describe_environments(application_name: app_name,
296
+ include_deleted: false).environments
297
+ end
298
+
299
+ def self.get stage_name, env_name, client=Rebi.client
300
+ env = new stage_name, env_name, client
301
+ return env.created? ? env : nil
302
+ end
303
+
304
+ end
305
+ end
@@ -0,0 +1,16 @@
1
+ module Rebi
2
+ class ErbHelper
3
+ def initialize input, env_vars
4
+ @input = input
5
+ @env = env_vars || {}
6
+ end
7
+
8
+ def rebi_env k=nil
9
+ k.present? ? @env[k] : @env
10
+ end
11
+
12
+ def result
13
+ ERB.new(@input).result(binding)
14
+ end
15
+ end
16
+ end
data/lib/rebi/error.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Rebi
2
+ class Error < StandardError
3
+ ApplicationNotFound = Class.new(self)
4
+ EnvironmentExisted = Class.new(self)
5
+ EnvironmentNotExisted = Class.new(self)
6
+ ConfigFileNotFound = Class.new(self)
7
+ ConfigNotFound = Class.new(self)
8
+ ConfigInvalid = Class.new(self)
9
+ NoGit = Class.new(self)
10
+ ServiceError = Class.new(self)
11
+ EnvironmentInUpdating = Class.new(self)
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module Rebi
2
+ VERSION = '0.1.6'
3
+ end
@@ -0,0 +1,61 @@
1
+ module Rebi
2
+ class ZipHelper
3
+
4
+ def initialize
5
+ `git status`
6
+ raise Rebi::Error::NoGit.new("Not a git repository") unless $?.success?
7
+ end
8
+
9
+ def ls_files
10
+ `git ls-files`.split("\n")
11
+ end
12
+
13
+ def raw_zip_archive opts={}
14
+ tmp_file = Tempfile.new("git_archive")
15
+ system "git archive --format=zip HEAD > #{tmp_file.path}"
16
+ return tmp_file
17
+ end
18
+
19
+ def version_label
20
+ `git describe --always --abbrev=8`.chomp
21
+ end
22
+
23
+ def message
24
+ `git log --oneline -1`.chomp.split(" ")[1..-1].join(" ")[0..190]
25
+ end
26
+
27
+ # Create zip archivement
28
+ def gen env_conf
29
+ Rebi.log("Creating zip archivement", env_conf.name)
30
+ start = Time.now
31
+ ebextensions = env_conf.ebextensions
32
+ files = ls_files
33
+ tmp_file = raw_zip_archive
34
+ tmp_folder = Dir.mktmpdir
35
+ Zip::File.open(tmp_file) do |z|
36
+ ebextensions.each do |ex_folder|
37
+ Dir.glob("#{ex_folder}/*.config") do |fname|
38
+ next unless (File.file?(fname) && files.include?(fname))
39
+ next unless y = YAML::load(ErbHelper.new(File.read(fname), env_conf.environment_variables).result)
40
+ basename = File.basename(fname)
41
+ target = ".ebextensions/#{basename}"
42
+ tmp_yaml = "#{tmp_folder}/#{basename}"
43
+ File.open(tmp_yaml, 'w') do |f|
44
+ f.write y.to_yaml
45
+ end
46
+ z.remove target if z.find_entry target
47
+ z.add target, tmp_yaml
48
+ end
49
+ end
50
+ end
51
+ FileUtils.rm_rf tmp_folder
52
+ Rebi.log("Zip was created in: #{Time.now - start}s", env_conf.name)
53
+ return {
54
+ label: Time.now.strftime("app_#{env_conf.name}_#{version_label}_%Y%m%d_%H%M%S"),
55
+ file: File.open(tmp_file.path),
56
+ message: message,
57
+ }
58
+ end
59
+
60
+ end
61
+ end
data/lib/rebi.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'active_support/all'
2
+ require 'aws-sdk'
3
+ require 'colorized_string'
4
+ require 'singleton'
5
+ require 'yaml'
6
+ require 'dotenv'
7
+ require 'tempfile'
8
+ require 'pathname'
9
+ require 'zip'
10
+ require 'fileutils'
11
+ require 'erb'
12
+ require 'ostruct'
13
+ require 'thread'
14
+ require 'thwait'
15
+
16
+ require 'rebi/erb_helper'
17
+ require 'rebi/zip_helper'
18
+ require 'rebi/application'
19
+ require 'rebi/environment'
20
+ require 'rebi/config'
21
+ require 'rebi/config_environment'
22
+ require 'rebi/error'
23
+ require 'rebi/version'
24
+
25
+ Dotenv.load
26
+
27
+ module Rebi
28
+ extend self
29
+
30
+ attr_accessor :config_file
31
+ @config_file = "config/rebi.yml"
32
+
33
+ def root
34
+ Dir.pwd
35
+ end
36
+
37
+ def client c=nil
38
+ @@client = c || Aws::ElasticBeanstalk::Client.new
39
+ end
40
+
41
+ def app
42
+ return Rebi::Application.get_or_create_application(config.app_name)
43
+ end
44
+
45
+ def config
46
+ yield Rebi::Config.instance if block_given?
47
+ return Rebi::Config.instance
48
+ end
49
+
50
+ def reload!
51
+ config.reload!
52
+ end
53
+
54
+ def log mes, prefix=nil
55
+ puts "#{prefix ? "#{colorize_prefix(prefix)}: " : ""}#{mes}"
56
+ end
57
+
58
+ COLORS = [:red, :green, :yellow, :blue, :magenta, :cyan, :white]
59
+ def colorize_prefix(prefix)
60
+ h = prefix.chars.inject(0) do |m, c|
61
+ m + c.ord
62
+ end
63
+ return ColorizedString[prefix].colorize(COLORS[h % COLORS.count])
64
+ end
65
+
66
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rebi do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rebi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - KhiemNS
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-07-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubyzip
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.10'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dotenv
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: colorize
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.8'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: commander
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '4.4'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '4.4'
97
+ description: Deploy ElasticBeanstalk with multiple deploy, switchable and dynamic
98
+ generated ebextensions with erb
99
+ email:
100
+ - khiemns.k54@gmail.com
101
+ executables:
102
+ - rebi
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - MIT-LICENSE
107
+ - README.md
108
+ - Rakefile
109
+ - bin/rebi
110
+ - lib/rebi.rb
111
+ - lib/rebi/application.rb
112
+ - lib/rebi/config.rb
113
+ - lib/rebi/config_environment.rb
114
+ - lib/rebi/environment.rb
115
+ - lib/rebi/erb_helper.rb
116
+ - lib/rebi/error.rb
117
+ - lib/rebi/version.rb
118
+ - lib/rebi/zip_helper.rb
119
+ - lib/tasks/rebi.rake
120
+ homepage: https://github.com/khiemns54/rebi
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubyforge_project:
140
+ rubygems_version: 2.5.1
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: ElasticBeanstalk Deployment Tool
144
+ test_files: []