rebi 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 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: []