broadside 1.4.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -1
- data/README.md +13 -9
- data/bin/broadside +9 -6
- data/broadside.gemspec +2 -2
- data/lib/broadside.rb +6 -6
- data/lib/broadside/configuration.rb +37 -11
- data/lib/broadside/configuration/aws_config.rb +7 -7
- data/lib/broadside/configuration/ecs_config.rb +8 -7
- data/lib/broadside/configuration/verify_instance_variables.rb +11 -0
- data/lib/broadside/deploy.rb +41 -37
- data/lib/broadside/{deploy → ecs}/ecs_deploy.rb +57 -79
- data/lib/broadside/{deploy → ecs}/ecs_manager.rb +0 -0
- data/lib/broadside/predeploy_commands.rb +7 -0
- data/lib/broadside/target.rb +110 -0
- data/lib/broadside/utils.rb +4 -4
- data/lib/broadside/version.rb +1 -1
- metadata +13 -13
- data/lib/broadside/configuration/base_config.rb +0 -19
- data/lib/broadside/configuration/config_struct.rb +0 -24
- data/lib/broadside/configuration/deploy_config.rb +0 -148
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc1d69217accd3c37cde3f4a27955021c62b7b75
|
4
|
+
data.tar.gz: 4806175370a4a5254a3da35d33bcd6cad2b7e504
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b123680b578397bc830e20099d47c364071ad2810f71594433fea4eef52280e23baa144c186cc166f9e13eff01799a3e0bc7efbaf39ccef9545efa65747c810f
|
7
|
+
data.tar.gz: fa3b8ee635ca551c74ee41e2f0f57198b8a97469f1c1298c2a2af827fdb4adaa06085f274faf8e9a82037930cacd97ef00b97223c9c8938f9953dfc83aad6ac6
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
|
+
# 2.0.0
|
2
|
+
- **BREAKING CHANGE** `rake db:migrate` is no longer the default `predeploy_command`
|
3
|
+
- `Target` is a first class object
|
4
|
+
- `Deploy` is composed of a `Target` plus command line options
|
5
|
+
- There is no more `base` configuration - the main `Configuration` object holds all the `base` config. You can still call `Broadside.config.base` though you will get a deprecation warning.
|
6
|
+
- There is no more `deploy` configuration - most of that is handled in the main `Configuration` object and in `targets=`. You can still call `Broadside.config.deploy` though you will get a deprecation warning.
|
7
|
+
|
1
8
|
# 1.4.0
|
2
|
-
|
9
|
+
- [#42](https://github.com/lumoslabs/broadside/pull/42/files): Update the task definition when running bootstrap
|
3
10
|
|
4
11
|
# 1.3.0
|
5
12
|
- [#41](https://github.com/lumoslabs/broadside/pull/41/files): Introduce the concept of bootstrap commands, which are designed to be run when setting up a new server or environment.
|
data/README.md
CHANGED
@@ -11,9 +11,9 @@ Broadside offers a simple command-line interface to perform deployments on ECS.
|
|
11
11
|
|
12
12
|
```ruby
|
13
13
|
Broadside.configure do |config|
|
14
|
-
config.
|
15
|
-
config.
|
16
|
-
config.
|
14
|
+
config.application = 'hello_world'
|
15
|
+
config.docker_image = 'lumoslabs/hello_world'
|
16
|
+
config.type = 'ecs'
|
17
17
|
config.ecs.cluster = 'micro-cluster'
|
18
18
|
config.deploy.targets = {
|
19
19
|
production_web: {
|
@@ -21,7 +21,7 @@ Broadside.configure do |config|
|
|
21
21
|
command: ['bundle', 'exec', 'unicorn', '-c', 'config/unicorn.conf.rb'],
|
22
22
|
env_file: '../.env.production'
|
23
23
|
predeploy_commands: [
|
24
|
-
|
24
|
+
Broadside::Predeploy::RAKE_DB_MIGRATE, # RAKE_DB_MIGRATE is just a constant for your convenience
|
25
25
|
['bundle', 'exec', 'rake', 'data:migrate']
|
26
26
|
]
|
27
27
|
},
|
@@ -31,6 +31,7 @@ Broadside.configure do |config|
|
|
31
31
|
env_file: '../.env.production'
|
32
32
|
},
|
33
33
|
staging_web: {
|
34
|
+
cluster: 'staging-cluster', # Overrides config.ecs.cluster
|
34
35
|
scale: 1,
|
35
36
|
command: ['bundle', 'exec', 'puma'],
|
36
37
|
env_file: '../.env.staging'
|
@@ -68,13 +69,16 @@ end
|
|
68
69
|
```
|
69
70
|
|
70
71
|
From here, developers can use broadside's command-line interface to initiate a basic deployment:
|
71
|
-
|
72
|
+
|
73
|
+
```bash
|
72
74
|
broadside deploy short --target production_web --tag $GIT_SHA
|
73
75
|
```
|
74
76
|
or run
|
75
|
-
|
77
|
+
|
78
|
+
```bash
|
76
79
|
broadside deploy full --target production_web --tag $GIT_SHA
|
77
80
|
```
|
81
|
+
|
78
82
|
which will run the listed `predeploy_commands` listed in the config above prior to the deployment.
|
79
83
|
|
80
84
|
In the case of an error or timeout during a deploy, broadside will automatically rollback to the latest stable version. You can perform manual rollbacks as well through the command-line.
|
@@ -84,12 +88,12 @@ See the complete command-line reference in the wiki.
|
|
84
88
|
|
85
89
|
## Setup
|
86
90
|
First, install broadside by adding it to your application gemfile:
|
87
|
-
```
|
91
|
+
```ruby
|
88
92
|
gem 'broadside'
|
89
93
|
```
|
90
94
|
|
91
95
|
Then run
|
92
|
-
```
|
96
|
+
```bash
|
93
97
|
bundle install
|
94
98
|
bundle binstubs broadside
|
95
99
|
```
|
@@ -97,7 +101,7 @@ bundle binstubs broadside
|
|
97
101
|
It's recommended that you specify broadside as a development gem so it doesn't inflate your production image.
|
98
102
|
|
99
103
|
You can now run the executable in your app directory:
|
100
|
-
```
|
104
|
+
```bash
|
101
105
|
bin/broadside --help
|
102
106
|
```
|
103
107
|
|
data/bin/broadside
CHANGED
@@ -26,8 +26,10 @@ def add_shared_deploy_configs(subcmd)
|
|
26
26
|
subcmd.flag [:t, :target], type: Symbol
|
27
27
|
|
28
28
|
subcmd.action do |global_options, options, args|
|
29
|
-
_DeployObj = Kernel.const_get("Broadside::#{Broadside.config.
|
30
|
-
|
29
|
+
_DeployObj = Kernel.const_get("Broadside::#{Broadside.config.type.capitalize}Deploy")
|
30
|
+
_target = Broadside.config.targets.select { |t| t.name == options[:target] }.first
|
31
|
+
raise "Bad target: #{options[:target]}" unless _target
|
32
|
+
_DeployObj.new(_target, options).public_send(subcmd.name)
|
31
33
|
end
|
32
34
|
end
|
33
35
|
|
@@ -44,9 +46,9 @@ end
|
|
44
46
|
|
45
47
|
desc 'Bootstrap your service and task definition from the configured definition.'
|
46
48
|
command :bootstrap do |b|
|
47
|
-
|
48
|
-
|
49
|
-
|
49
|
+
b.desc 'Docker tag for application container'
|
50
|
+
b.arg_name 'TAG'
|
51
|
+
b.flag [:tag]
|
50
52
|
|
51
53
|
add_shared_deploy_configs(b)
|
52
54
|
end
|
@@ -144,7 +146,8 @@ command :deploy do |d|
|
|
144
146
|
end
|
145
147
|
|
146
148
|
def call_hook(type, command)
|
147
|
-
hook = Broadside.config.
|
149
|
+
hook = Broadside.config.send(type)
|
150
|
+
|
148
151
|
if hook.is_a?(Proc)
|
149
152
|
hook_args =
|
150
153
|
if command.parent.is_a?(GLI::Command)
|
data/broadside.gemspec
CHANGED
@@ -6,7 +6,7 @@ require 'broadside/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = 'broadside'
|
8
8
|
spec.version = Broadside::VERSION
|
9
|
-
spec.authors = ['Matthew Leung']
|
9
|
+
spec.authors = ['Matthew Leung', 'Lumos Labs, Inc.']
|
10
10
|
spec.email = ['leung.mattp@gmail.com']
|
11
11
|
|
12
12
|
spec.summary = 'A command-line tool for EC2 Container Service deployment.'
|
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_dependency 'gli', '~> 2.13'
|
24
24
|
spec.add_dependency 'rainbow', '~> 2.1'
|
25
25
|
|
26
|
-
spec.add_development_dependency 'rspec'
|
26
|
+
spec.add_development_dependency 'rspec', '~> 3.4.0'
|
27
27
|
spec.add_development_dependency 'bundler', '~> 1.9'
|
28
28
|
spec.add_development_dependency 'fakefs'
|
29
29
|
end
|
data/lib/broadside.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'broadside/error'
|
2
2
|
require 'broadside/utils'
|
3
|
+
require 'broadside/configuration/verify_instance_variables'
|
3
4
|
require 'broadside/configuration'
|
4
|
-
require 'broadside/configuration/config_struct'
|
5
5
|
require 'broadside/configuration/aws_config'
|
6
|
-
require 'broadside/configuration/base_config'
|
7
|
-
require 'broadside/configuration/deploy_config'
|
8
6
|
require 'broadside/configuration/ecs_config'
|
7
|
+
require 'broadside/target'
|
9
8
|
require 'broadside/deploy'
|
10
|
-
require 'broadside/
|
11
|
-
require 'broadside/
|
9
|
+
require 'broadside/predeploy_commands'
|
10
|
+
require 'broadside/ecs/ecs_deploy'
|
11
|
+
require 'broadside/ecs/ecs_manager'
|
12
12
|
require 'broadside/version'
|
13
13
|
|
14
14
|
module Broadside
|
@@ -29,8 +29,8 @@ module Broadside
|
|
29
29
|
end
|
30
30
|
|
31
31
|
begin
|
32
|
-
load config_file
|
33
32
|
config.file = config_file
|
33
|
+
load config_file
|
34
34
|
rescue LoadError => e
|
35
35
|
error "Encountered an error loading required configuration file '#{config_file}' !"
|
36
36
|
raise e
|
@@ -1,15 +1,30 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
1
3
|
module Broadside
|
2
4
|
class Configuration
|
5
|
+
extend Gem::Deprecate
|
6
|
+
include VerifyInstanceVariables
|
3
7
|
include Utils
|
4
8
|
|
5
|
-
attr_accessor
|
9
|
+
attr_accessor(
|
10
|
+
:application,
|
11
|
+
:docker_image,
|
12
|
+
:file,
|
13
|
+
:git_repo,
|
14
|
+
:logger,
|
15
|
+
:prehook,
|
16
|
+
:posthook,
|
17
|
+
:ssh,
|
18
|
+
:timeout,
|
19
|
+
:type
|
20
|
+
)
|
21
|
+
attr_reader :targets
|
6
22
|
|
7
23
|
def initialize
|
8
|
-
@
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@deploy ||= DeployConfig.new
|
24
|
+
@logger = ::Logger.new(STDOUT)
|
25
|
+
@logger.level = ::Logger::DEBUG
|
26
|
+
@logger.datetime_format = '%Y-%m-%d_%H:%M:%S'
|
27
|
+
@timeout = 600
|
13
28
|
end
|
14
29
|
|
15
30
|
def aws
|
@@ -20,13 +35,24 @@ module Broadside
|
|
20
35
|
@ecs ||= EcsConfig.new
|
21
36
|
end
|
22
37
|
|
23
|
-
def
|
24
|
-
|
38
|
+
def targets=(_targets)
|
39
|
+
raise ArgumentError, "Targets must be a hash" unless _targets.is_a?(Hash)
|
40
|
+
@targets = _targets.map { |name, config| Target.new(name, config) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def verify(*args)
|
44
|
+
super(*([:application, :docker_image] + args))
|
45
|
+
end
|
46
|
+
|
47
|
+
# Maintain backward compatibility
|
48
|
+
def deploy
|
49
|
+
self
|
25
50
|
end
|
51
|
+
deprecate :deploy, 'config.deploy.option should be configured directly as config.option', 2017, 4
|
26
52
|
|
27
|
-
def
|
28
|
-
|
29
|
-
ConfigStruct.new
|
53
|
+
def base
|
54
|
+
self
|
30
55
|
end
|
56
|
+
deprecate :base, 'config.base.option should be configured directly as config.option', 2017, 4
|
31
57
|
end
|
32
58
|
end
|
@@ -1,14 +1,14 @@
|
|
1
1
|
require 'aws-sdk'
|
2
2
|
|
3
3
|
module Broadside
|
4
|
-
class
|
5
|
-
|
6
|
-
attr_accessor :region, :credentials
|
4
|
+
class AwsConfig
|
5
|
+
include VerifyInstanceVariables
|
7
6
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
7
|
+
attr_accessor :region, :credentials
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@region = 'us-east-1'
|
11
|
+
@credentials = Aws::SharedCredentials.new.credentials
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module Broadside
|
2
|
-
class
|
3
|
-
|
4
|
-
attr_accessor :cluster, :poll_frequency
|
2
|
+
class EcsConfig
|
3
|
+
include VerifyInstanceVariables
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
# Cluster can be overridden in a Target
|
6
|
+
attr_accessor :cluster, :poll_frequency
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@cluster = nil
|
10
|
+
@poll_frequency = 2
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Broadside
|
2
|
+
module VerifyInstanceVariables
|
3
|
+
def verify(*args)
|
4
|
+
args.each do |var|
|
5
|
+
if self.send(var).nil?
|
6
|
+
raise Broadside::MissingVariableError, "Missing required #{self.class.to_s.split("::").last} variable '#{var}' !"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/broadside/deploy.rb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
1
|
module Broadside
|
2
2
|
class Deploy
|
3
3
|
include Utils
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@
|
18
|
-
@
|
4
|
+
include VerifyInstanceVariables
|
5
|
+
|
6
|
+
attr_reader(
|
7
|
+
:command,
|
8
|
+
:instance,
|
9
|
+
:lines,
|
10
|
+
:tag,
|
11
|
+
:target
|
12
|
+
)
|
13
|
+
|
14
|
+
def initialize(target, opts = {})
|
15
|
+
@target = target
|
16
|
+
@command = opts[:command] || @target.command
|
17
|
+
@instance = opts[:instance] || @target.instance
|
18
|
+
@lines = opts[:lines] || 10
|
19
|
+
@rollback = opts[:rollback] || 1
|
20
|
+
@scale = opts[:scale] || @target.scale
|
21
|
+
@tag = opts[:tag]
|
19
22
|
end
|
20
23
|
|
21
24
|
def short
|
@@ -23,44 +26,44 @@ module Broadside
|
|
23
26
|
end
|
24
27
|
|
25
28
|
def full
|
26
|
-
|
29
|
+
config.verify(:ssh)
|
30
|
+
verify(:tag)
|
31
|
+
|
32
|
+
info "Running predeploy commands for #{family}..."
|
33
|
+
run_commands(@target.predeploy_commands)
|
34
|
+
info 'Predeploy complete.'
|
35
|
+
|
27
36
|
deploy
|
28
37
|
end
|
29
38
|
|
30
39
|
def deploy
|
31
|
-
|
40
|
+
verify(:tag)
|
41
|
+
|
32
42
|
info "Deploying #{image_tag} to #{family}..."
|
33
43
|
yield
|
34
44
|
info 'Deployment complete.'
|
35
45
|
end
|
36
46
|
|
37
|
-
def rollback(count = @
|
38
|
-
|
39
|
-
info "Rolling back #{@deploy_config.rollback} release for #{family}..."
|
47
|
+
def rollback(count = @rollback)
|
48
|
+
info "Rolling back #{count} release for #{family}..."
|
40
49
|
yield
|
41
50
|
info 'Rollback complete.'
|
42
51
|
end
|
43
52
|
|
44
53
|
def scale
|
45
|
-
info "Rescaling #{family} with scale=#{@
|
54
|
+
info "Rescaling #{family} with scale=#{@scale}"
|
46
55
|
yield
|
47
56
|
info 'Rescaling complete.'
|
48
57
|
end
|
49
58
|
|
50
59
|
def run
|
51
|
-
|
52
|
-
|
60
|
+
config.verify(:ssh)
|
61
|
+
verify(:tag, :command)
|
62
|
+
info "Running command [#{@command}] for #{family}..."
|
53
63
|
yield
|
54
64
|
info 'Complete.'
|
55
65
|
end
|
56
66
|
|
57
|
-
def run_predeploy
|
58
|
-
@deploy_config.verify(:tag, :ssh)
|
59
|
-
info "Running predeploy commands for #{family}..."
|
60
|
-
yield
|
61
|
-
info 'Predeploy complete.'
|
62
|
-
end
|
63
|
-
|
64
67
|
def status
|
65
68
|
info "Getting status information about #{family}"
|
66
69
|
yield
|
@@ -68,32 +71,33 @@ module Broadside
|
|
68
71
|
end
|
69
72
|
|
70
73
|
def logtail
|
71
|
-
|
74
|
+
verify(:instance)
|
72
75
|
yield
|
73
76
|
end
|
74
77
|
|
75
78
|
def ssh
|
76
|
-
|
79
|
+
verify(:instance)
|
77
80
|
yield
|
78
81
|
end
|
79
82
|
|
80
83
|
def bash
|
81
|
-
|
84
|
+
verify(:instance)
|
82
85
|
yield
|
83
86
|
end
|
84
87
|
|
85
|
-
|
88
|
+
private
|
86
89
|
|
87
90
|
def family
|
88
|
-
"#{config.
|
91
|
+
"#{config.application}_#{@target.name}"
|
89
92
|
end
|
90
93
|
|
91
94
|
def image_tag
|
92
|
-
"
|
95
|
+
raise ArgumentError, "Missing tag" unless @tag
|
96
|
+
"#{config.docker_image}:#{@tag}"
|
93
97
|
end
|
94
98
|
|
95
99
|
def gen_ssh_cmd(ip, options = { tty: false })
|
96
|
-
opts =
|
100
|
+
opts = config.ssh || {}
|
97
101
|
cmd = 'ssh -o StrictHostKeyChecking=no'
|
98
102
|
cmd << ' -t -t' if options[:tty]
|
99
103
|
cmd << " -i #{opts[:keyfile]}" if opts[:keyfile]
|
@@ -12,14 +12,14 @@ module Broadside
|
|
12
12
|
memory: 1000
|
13
13
|
}
|
14
14
|
|
15
|
-
def initialize(opts)
|
16
|
-
super
|
15
|
+
def initialize(target, opts = {})
|
16
|
+
super
|
17
17
|
config.ecs.verify(:cluster, :poll_frequency)
|
18
18
|
end
|
19
19
|
|
20
20
|
def deploy
|
21
21
|
super do
|
22
|
-
unless EcsManager.service_exists?(
|
22
|
+
unless EcsManager.service_exists?(@target.cluster, family)
|
23
23
|
exception "No service for #{family}! Please bootstrap or manually configure the service."
|
24
24
|
end
|
25
25
|
unless EcsManager.get_latest_task_definition_arn(family)
|
@@ -46,49 +46,36 @@ module Broadside
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def bootstrap
|
49
|
-
|
50
|
-
|
51
|
-
run_bootstrap_commands
|
52
|
-
else
|
53
|
-
unless @deploy_config.task_definition_config
|
49
|
+
unless EcsManager.get_latest_task_definition_arn(family)
|
50
|
+
unless @target.task_definition_config
|
54
51
|
raise ArgumentError, "No first task definition and no :task_definition_config in '#{family}' configuration"
|
55
52
|
end
|
56
53
|
|
57
54
|
info "Creating an initial task definition for '#{family}' from the config..."
|
58
55
|
|
59
56
|
EcsManager.ecs.register_task_definition(
|
60
|
-
@
|
57
|
+
@target.task_definition_config.merge(
|
61
58
|
family: family,
|
62
59
|
container_definitions: [DEFAULT_CONTAINER_DEFINITION.merge(container_definition)]
|
63
60
|
)
|
64
61
|
)
|
65
|
-
|
66
|
-
run_bootstrap_commands
|
67
62
|
end
|
68
63
|
|
69
|
-
|
64
|
+
run_commands(@target.bootstrap_commands)
|
65
|
+
|
66
|
+
if EcsManager.service_exists?(@target.cluster, family)
|
70
67
|
info("Service for #{family} already exists.")
|
71
68
|
else
|
72
|
-
unless @
|
69
|
+
unless @target.service_config
|
73
70
|
raise ArgumentError, "Service doesn't exist and no :service_config in '#{family}' configuration"
|
74
71
|
end
|
75
72
|
|
76
73
|
info "Service '#{family}' doesn't exist, creating..."
|
77
|
-
EcsManager.create_service(
|
74
|
+
EcsManager.create_service(@target.cluster, family, @target.service_config)
|
78
75
|
end
|
79
76
|
end
|
80
77
|
|
81
|
-
def
|
82
|
-
update_task_revision
|
83
|
-
|
84
|
-
begin
|
85
|
-
@deploy_config.bootstrap_commands.each { |command| run_command(command) }
|
86
|
-
ensure
|
87
|
-
EcsManager.deregister_last_n_tasks_definitions(family, 1)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
def rollback(count = @deploy_config.rollback)
|
78
|
+
def rollback(count = @rollback)
|
92
79
|
super do
|
93
80
|
begin
|
94
81
|
EcsManager.deregister_last_n_tasks_definitions(family, count)
|
@@ -108,32 +95,13 @@ module Broadside
|
|
108
95
|
|
109
96
|
def run
|
110
97
|
super do
|
111
|
-
|
112
|
-
|
113
|
-
begin
|
114
|
-
run_command(@deploy_config.command)
|
115
|
-
ensure
|
116
|
-
EcsManager.deregister_last_n_tasks_definitions(family, 1)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
# runs before deploy commands using the latest task definition
|
122
|
-
def run_predeploy
|
123
|
-
super do
|
124
|
-
update_task_revision
|
125
|
-
|
126
|
-
begin
|
127
|
-
@deploy_config.predeploy_commands.each { |command| run_command(command) }
|
128
|
-
ensure
|
129
|
-
EcsManager.deregister_last_n_tasks_definitions(family, 1)
|
130
|
-
end
|
98
|
+
run_commands(@command)
|
131
99
|
end
|
132
100
|
end
|
133
101
|
|
134
102
|
def status
|
135
103
|
super do
|
136
|
-
ips = EcsManager.get_running_instance_ips(
|
104
|
+
ips = EcsManager.get_running_instance_ips(@target.cluster, family)
|
137
105
|
info "\n---------------",
|
138
106
|
"\nDeployed task definition information:\n",
|
139
107
|
Rainbow(PP.pp(EcsManager.get_latest_task_definition(family), '')).blue,
|
@@ -149,7 +117,7 @@ module Broadside
|
|
149
117
|
ip = get_running_instance_ip
|
150
118
|
debug "Tailing logs for running container at ip #{ip}..."
|
151
119
|
search_pattern = Shellwords.shellescape(family)
|
152
|
-
cmd = "docker logs -f --tail=#{@
|
120
|
+
cmd = "docker logs -f --tail=#{@lines} `docker ps -n 1 --quiet --filter name=#{search_pattern}`"
|
153
121
|
tail_cmd = gen_ssh_cmd(ip) + " '#{cmd}'"
|
154
122
|
exec tail_cmd
|
155
123
|
end
|
@@ -177,7 +145,7 @@ module Broadside
|
|
177
145
|
private
|
178
146
|
|
179
147
|
def get_running_instance_ip
|
180
|
-
EcsManager.get_running_instance_ips(
|
148
|
+
EcsManager.get_running_instance_ips(@target.cluster, family).fetch(@target.instance)
|
181
149
|
end
|
182
150
|
|
183
151
|
# Creates a new task revision using current directory's env vars, provided tag, and configured options.
|
@@ -194,7 +162,7 @@ module Broadside
|
|
194
162
|
|
195
163
|
# Deep merge doesn't work well with arrays (e.g. :container_definitions), so build the container first.
|
196
164
|
updatable_container_definitions.first.merge!(container_definition)
|
197
|
-
revision.deep_merge!((@
|
165
|
+
revision.deep_merge!((@target.task_definition_config || {}).except(:container_definitions))
|
198
166
|
|
199
167
|
task_definition = EcsManager.ecs.register_task_definition(revision).task_definition
|
200
168
|
debug "Successfully created #{task_definition.task_definition_arn}"
|
@@ -203,21 +171,21 @@ module Broadside
|
|
203
171
|
# reloads the service using the latest task definition
|
204
172
|
def update_service
|
205
173
|
task_definition_arn = EcsManager.get_latest_task_definition_arn(family)
|
206
|
-
debug "Updating #{family} with scale=#{@
|
174
|
+
debug "Updating #{family} with scale=#{@target.scale} using task #{task_definition_arn}..."
|
207
175
|
|
208
176
|
update_service_response = EcsManager.ecs.update_service({
|
209
|
-
cluster:
|
210
|
-
desired_count: @
|
177
|
+
cluster: @target.cluster,
|
178
|
+
desired_count: @target.scale,
|
211
179
|
service: family,
|
212
180
|
task_definition: task_definition_arn
|
213
|
-
}.deep_merge(@
|
181
|
+
}.deep_merge(@target.service_config || {}))
|
214
182
|
|
215
183
|
unless update_service_response.successful?
|
216
184
|
exception('Failed to update service during deploy.', update_service_response.pretty_inspect)
|
217
185
|
end
|
218
186
|
|
219
|
-
EcsManager.ecs.wait_until(:services_stable, { cluster:
|
220
|
-
w.max_attempts =
|
187
|
+
EcsManager.ecs.wait_until(:services_stable, { cluster: @target.cluster, services: [family] }) do |w|
|
188
|
+
w.max_attempts = config.timeout ? config.timeout / config.ecs.poll_frequency : nil
|
221
189
|
w.delay = config.ecs.poll_frequency
|
222
190
|
seen_event = nil
|
223
191
|
|
@@ -232,36 +200,46 @@ module Broadside
|
|
232
200
|
end
|
233
201
|
end
|
234
202
|
|
235
|
-
def
|
236
|
-
|
237
|
-
run_task_response = EcsManager.run_task(config.ecs.cluster, family, command)
|
203
|
+
def run_commands(commands)
|
204
|
+
return if commands.nil? || commands.empty?
|
238
205
|
|
239
|
-
|
240
|
-
exception("Failed to run #{command_name} task.", run_task_response.pretty_inspect)
|
241
|
-
end
|
206
|
+
update_task_revision
|
242
207
|
|
243
|
-
|
244
|
-
|
208
|
+
begin
|
209
|
+
Array.wrap(commands).each do |command|
|
210
|
+
command_name = command.join(' ')
|
211
|
+
run_task_response = EcsManager.run_task(@target.cluster, family, command)
|
245
212
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
w.before_attempt do |attempt|
|
250
|
-
debug "Attempt #{attempt}: waiting for #{command_name} to complete..."
|
251
|
-
end
|
252
|
-
end
|
213
|
+
unless run_task_response.successful? && run_task_response.tasks.try(:[], 0)
|
214
|
+
exception("Failed to run #{command_name} task.", run_task_response.pretty_inspect)
|
215
|
+
end
|
253
216
|
|
254
|
-
|
217
|
+
task_arn = run_task_response.tasks[0].task_arn
|
218
|
+
debug "Launched #{command_name} task #{task_arn}, waiting for completion..."
|
255
219
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
220
|
+
EcsManager.ecs.wait_until(:tasks_stopped, { cluster: @target.cluster, tasks: [task_arn] }) do |w|
|
221
|
+
w.max_attempts = nil
|
222
|
+
w.delay = config.ecs.poll_frequency
|
223
|
+
w.before_attempt do |attempt|
|
224
|
+
debug "Attempt #{attempt}: waiting for #{command_name} to complete..."
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
info "#{command_name} task container logs:\n#{get_container_logs(task_arn)}"
|
229
|
+
|
230
|
+
if (code = EcsManager.get_task_exit_code(@target.cluster, task_arn, family)) == 0
|
231
|
+
debug "#{command_name} task #{task_arn} exited with status code 0"
|
232
|
+
else
|
233
|
+
exception "#{command_name} task #{task_arn} exited with a non-zero status code #{code}!"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
ensure
|
237
|
+
EcsManager.deregister_last_n_tasks_definitions(family, 1)
|
260
238
|
end
|
261
239
|
end
|
262
240
|
|
263
241
|
def get_container_logs(task_arn)
|
264
|
-
ip = EcsManager.get_running_instance_ips(
|
242
|
+
ip = EcsManager.get_running_instance_ips(@target.cluster, family, task_arn).first
|
265
243
|
debug "Found ip of container instance: #{ip}"
|
266
244
|
|
267
245
|
find_container_id_cmd = "#{gen_ssh_cmd(ip)} \"docker ps -aqf 'label=com.amazonaws.ecs.task-arn=#{task_arn}'\""
|
@@ -279,15 +257,15 @@ module Broadside
|
|
279
257
|
end
|
280
258
|
|
281
259
|
def container_definition
|
282
|
-
configured_containers = (@
|
260
|
+
configured_containers = (@target.task_definition_config || {})[:container_definitions]
|
283
261
|
if configured_containers && configured_containers.size > 1
|
284
262
|
raise ArgumentError, 'Creating > 1 container definition not supported yet'
|
285
263
|
end
|
286
264
|
|
287
265
|
(configured_containers.try(:first) || {}).merge(
|
288
266
|
name: family,
|
289
|
-
command: @
|
290
|
-
environment: @
|
267
|
+
command: @command,
|
268
|
+
environment: @target.env_vars,
|
291
269
|
image: image_tag
|
292
270
|
)
|
293
271
|
end
|
File without changes
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# Here rest some commonly used predeploy commands, so they can be included by constant name instead of
|
2
|
+
# having to retype them in every config file.
|
3
|
+
module Broadside
|
4
|
+
module PredeployCommands
|
5
|
+
RAKE_DB_MIGRATE = ['bundle', 'exec', 'rake', '--trace', 'db:migrate']
|
6
|
+
end
|
7
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'dotenv'
|
2
|
+
require 'pathname'
|
3
|
+
|
4
|
+
module Broadside
|
5
|
+
class Target
|
6
|
+
include VerifyInstanceVariables
|
7
|
+
include Utils
|
8
|
+
|
9
|
+
attr_accessor(
|
10
|
+
:bootstrap_commands,
|
11
|
+
:command,
|
12
|
+
:env_files,
|
13
|
+
:env_vars,
|
14
|
+
:instance,
|
15
|
+
:name,
|
16
|
+
:predeploy_commands,
|
17
|
+
:scale,
|
18
|
+
:service_config,
|
19
|
+
:tag,
|
20
|
+
:task_definition_config
|
21
|
+
)
|
22
|
+
|
23
|
+
DEFAULT_INSTANCE = 0
|
24
|
+
|
25
|
+
TARGET_ATTRIBUTE_VALIDATIONS = {
|
26
|
+
bootstrap_commands: ->(target_attribute) { validate_commands(target_attribute) },
|
27
|
+
command: ->(target_attribute) { validate_types([Array, NilClass], target_attribute) },
|
28
|
+
env_files: ->(target_attribute) { validate_types([String, Array], target_attribute) },
|
29
|
+
predeploy_commands: ->(target_attribute) { validate_commands(target_attribute) },
|
30
|
+
scale: ->(target_attribute) { validate_types([Integer], target_attribute) },
|
31
|
+
service_config: ->(target_attribute) { validate_types([Hash, NilClass], target_attribute) },
|
32
|
+
task_definition_config: ->(target_attribute) { validate_types([Hash, NilClass], target_attribute) }
|
33
|
+
}
|
34
|
+
|
35
|
+
def initialize(name, options = {})
|
36
|
+
@name = name
|
37
|
+
@config = options
|
38
|
+
|
39
|
+
@bootstrap_commands = @config[:bootstrap_commands] || []
|
40
|
+
@cluster = @config[:cluster]
|
41
|
+
@command = @config[:command]
|
42
|
+
_env_files = @config[:env_files] || @config[:env_file]
|
43
|
+
@env_files = _env_files ? [*_env_files] : nil
|
44
|
+
@env_vars = {}
|
45
|
+
@instance = DEFAULT_INSTANCE || @config[:instance]
|
46
|
+
@predeploy_commands = @config[:predeploy_commands]
|
47
|
+
@scale = @config[:scale]
|
48
|
+
@service_config = @config[:service_config]
|
49
|
+
@tag = @config[:tag]
|
50
|
+
@task_definition_config = @config[:task_definition_config]
|
51
|
+
|
52
|
+
validate!
|
53
|
+
load_env_vars!
|
54
|
+
end
|
55
|
+
|
56
|
+
def cluster
|
57
|
+
@cluster || config.ecs.cluster
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def validate!
|
63
|
+
invalid_messages = TARGET_ATTRIBUTE_VALIDATIONS.map do |var, validation|
|
64
|
+
message = validation.call(instance_variable_get('@' + var.to_s))
|
65
|
+
message.nil? ? nil : "Deploy target '#{@name}' parameter '#{var}' is invalid: #{message}"
|
66
|
+
end.compact
|
67
|
+
|
68
|
+
unless invalid_messages.empty?
|
69
|
+
raise ArgumentError, invalid_messages.join("\n")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def load_env_vars!
|
74
|
+
@env_files.flatten.each do |env_path|
|
75
|
+
env_file = Pathname.new(env_path)
|
76
|
+
|
77
|
+
unless env_file.absolute?
|
78
|
+
dir = config.file.nil? ? Dir.pwd : Pathname.new(config.file).dirname
|
79
|
+
env_file = env_file.expand_path(dir)
|
80
|
+
end
|
81
|
+
|
82
|
+
if env_file.exist?
|
83
|
+
vars = Dotenv.load(env_file)
|
84
|
+
@env_vars.merge!(vars)
|
85
|
+
else
|
86
|
+
raise ArgumentError, "Could not find file '#{env_file}' for loading environment variables !"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# convert env vars to format ecs expects
|
91
|
+
@env_vars = @env_vars.map { |k, v| { 'name' => k, 'value' => v } }
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.validate_types(types, target_attribute)
|
95
|
+
return nil if types.any? { |type| target_attribute.is_a?(type) }
|
96
|
+
|
97
|
+
"'#{target_attribute}' must be of type [#{types.join('|')}], got '#{target_attribute.class}' !"
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.validate_commands(commands)
|
101
|
+
return nil if commands.nil?
|
102
|
+
return 'predeploy_commands must be an array' unless commands.is_a?(Array)
|
103
|
+
|
104
|
+
messages = commands.reject { |cmd| cmd.is_a?(Array) }.map do |command|
|
105
|
+
"predeploy_command '#{command}' must be an array" unless command.is_a?(Array)
|
106
|
+
end
|
107
|
+
messages.empty? ? nil : messages.join(', ')
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/broadside/utils.rb
CHANGED
@@ -1,19 +1,19 @@
|
|
1
1
|
module Broadside
|
2
2
|
module Utils
|
3
3
|
def debug(*args)
|
4
|
-
config.
|
4
|
+
config.logger.debug(args.join(' '))
|
5
5
|
end
|
6
6
|
|
7
7
|
def info(*args)
|
8
|
-
config.
|
8
|
+
config.logger.info(args.join(' '))
|
9
9
|
end
|
10
10
|
|
11
11
|
def warn(*args)
|
12
|
-
config.
|
12
|
+
config.logger.warn(args.join(' '))
|
13
13
|
end
|
14
14
|
|
15
15
|
def error(*args)
|
16
|
-
config.
|
16
|
+
config.logger.error(args.join(' '))
|
17
17
|
end
|
18
18
|
|
19
19
|
def exception(*args)
|
data/lib/broadside/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: broadside
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Leung
|
8
|
+
- Lumos Labs, Inc.
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date: 2017-01-
|
12
|
+
date: 2017-01-13 00:00:00.000000000 Z
|
12
13
|
dependencies:
|
13
14
|
- !ruby/object:Gem::Dependency
|
14
15
|
name: activesupport
|
@@ -90,16 +91,16 @@ dependencies:
|
|
90
91
|
name: rspec
|
91
92
|
requirement: !ruby/object:Gem::Requirement
|
92
93
|
requirements:
|
93
|
-
- - "
|
94
|
+
- - "~>"
|
94
95
|
- !ruby/object:Gem::Version
|
95
|
-
version:
|
96
|
+
version: 3.4.0
|
96
97
|
type: :development
|
97
98
|
prerelease: false
|
98
99
|
version_requirements: !ruby/object:Gem::Requirement
|
99
100
|
requirements:
|
100
|
-
- - "
|
101
|
+
- - "~>"
|
101
102
|
- !ruby/object:Gem::Version
|
102
|
-
version:
|
103
|
+
version: 3.4.0
|
103
104
|
- !ruby/object:Gem::Dependency
|
104
105
|
name: bundler
|
105
106
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,14 +151,14 @@ files:
|
|
150
151
|
- lib/broadside.rb
|
151
152
|
- lib/broadside/configuration.rb
|
152
153
|
- lib/broadside/configuration/aws_config.rb
|
153
|
-
- lib/broadside/configuration/base_config.rb
|
154
|
-
- lib/broadside/configuration/config_struct.rb
|
155
|
-
- lib/broadside/configuration/deploy_config.rb
|
156
154
|
- lib/broadside/configuration/ecs_config.rb
|
155
|
+
- lib/broadside/configuration/verify_instance_variables.rb
|
157
156
|
- lib/broadside/deploy.rb
|
158
|
-
- lib/broadside/
|
159
|
-
- lib/broadside/
|
157
|
+
- lib/broadside/ecs/ecs_deploy.rb
|
158
|
+
- lib/broadside/ecs/ecs_manager.rb
|
160
159
|
- lib/broadside/error.rb
|
160
|
+
- lib/broadside/predeploy_commands.rb
|
161
|
+
- lib/broadside/target.rb
|
161
162
|
- lib/broadside/utils.rb
|
162
163
|
- lib/broadside/version.rb
|
163
164
|
homepage: https://github.com/lumoslabs/broadside
|
@@ -180,9 +181,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
180
181
|
version: '0'
|
181
182
|
requirements: []
|
182
183
|
rubyforge_project:
|
183
|
-
rubygems_version: 2.2.
|
184
|
+
rubygems_version: 2.2.5
|
184
185
|
signing_key:
|
185
186
|
specification_version: 4
|
186
187
|
summary: A command-line tool for EC2 Container Service deployment.
|
187
188
|
test_files: []
|
188
|
-
has_rdoc:
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'logger'
|
2
|
-
|
3
|
-
module Broadside
|
4
|
-
class Configuration
|
5
|
-
class BaseConfig < ConfigStruct
|
6
|
-
attr_accessor :application, :git_repo, :docker_image, :logger, :loglevel, :prehook, :posthook
|
7
|
-
|
8
|
-
def initialize
|
9
|
-
@application = nil
|
10
|
-
@docker_image = nil
|
11
|
-
@logger = Logger.new(STDOUT)
|
12
|
-
@logger.level = Logger::DEBUG
|
13
|
-
@logger.datetime_format = '%Y-%m-%d_%H:%M:%S'
|
14
|
-
@prehook = nil
|
15
|
-
@posthook = nil
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module Broadside
|
2
|
-
class Configuration
|
3
|
-
class ConfigStruct
|
4
|
-
def verify(*args)
|
5
|
-
args.each do |var|
|
6
|
-
if self.send(var).nil?
|
7
|
-
raise Broadside::MissingVariableError, "Missing required #{self.class.to_s.split("::").last} variable '#{var}' !"
|
8
|
-
end
|
9
|
-
end
|
10
|
-
end
|
11
|
-
|
12
|
-
def to_h
|
13
|
-
self.instance_variables.inject({}) do |h, var|
|
14
|
-
h[var] = self.instance_variable_get(var)
|
15
|
-
h
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def method_missing(m, *args, &block)
|
20
|
-
warn "Unknown configuration '#{m}' provided, ignoring. Check your version of broadside?"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
@@ -1,148 +0,0 @@
|
|
1
|
-
require 'dotenv'
|
2
|
-
require 'pathname'
|
3
|
-
|
4
|
-
module Broadside
|
5
|
-
class Configuration
|
6
|
-
class DeployConfig < ConfigStruct
|
7
|
-
include Utils
|
8
|
-
|
9
|
-
DEFAULT_PREDEPLOY_COMMANDS = [
|
10
|
-
['bundle', 'exec', 'rake', '--trace', 'db:migrate']
|
11
|
-
]
|
12
|
-
|
13
|
-
attr_accessor(
|
14
|
-
:type,
|
15
|
-
:tag,
|
16
|
-
:ssh,
|
17
|
-
:rollback,
|
18
|
-
:timeout,
|
19
|
-
:target,
|
20
|
-
:targets,
|
21
|
-
:scale,
|
22
|
-
:env_vars,
|
23
|
-
:command,
|
24
|
-
:instance,
|
25
|
-
:lines,
|
26
|
-
:predeploy_commands,
|
27
|
-
:bootstrap_commands,
|
28
|
-
:service_config,
|
29
|
-
:task_definition_config
|
30
|
-
)
|
31
|
-
|
32
|
-
TARGET_ATTRIBUTE_VALIDATIONS = {
|
33
|
-
scale: ->(target_attribute) { validate_types([Fixnum], target_attribute) },
|
34
|
-
env_file: ->(target_attribute) { validate_types([String, Array], target_attribute) },
|
35
|
-
command: ->(target_attribute) { validate_types([Array, NilClass], target_attribute) },
|
36
|
-
predeploy_commands: ->(target_attribute) { validate_predeploy_commands(target_attribute) },
|
37
|
-
bootstrap_commands: ->(target_attribute) { validate_bootstrap_commands(target_attribute) },
|
38
|
-
service_config: ->(target_attribute) { validate_types([Hash, NilClass], target_attribute) },
|
39
|
-
task_definition_config: ->(target_attribute) { validate_types([Hash, NilClass], target_attribute) }
|
40
|
-
}
|
41
|
-
|
42
|
-
def initialize
|
43
|
-
@type = 'ecs'
|
44
|
-
@ssh = nil
|
45
|
-
@tag = nil
|
46
|
-
@rollback = 1
|
47
|
-
@timeout = 600
|
48
|
-
@target = nil
|
49
|
-
@targets = nil
|
50
|
-
@scale = nil
|
51
|
-
@env_vars = nil
|
52
|
-
@command = nil
|
53
|
-
@predeploy_commands = DEFAULT_PREDEPLOY_COMMANDS
|
54
|
-
@bootstrap_commands = []
|
55
|
-
@instance = 0
|
56
|
-
@service_config = nil
|
57
|
-
@task_definition_config = nil
|
58
|
-
@lines = 10
|
59
|
-
end
|
60
|
-
|
61
|
-
# Validates format of deploy targets
|
62
|
-
# Checks existence of provided target
|
63
|
-
def validate_targets!
|
64
|
-
@targets.each do |target, configuration|
|
65
|
-
invalid_messages = TARGET_ATTRIBUTE_VALIDATIONS.map do |var, validation|
|
66
|
-
message = validation.call(configuration[var])
|
67
|
-
message.nil? ? nil : "Deploy target '#{@target}' parameter '#{var}' is invalid: #{message}"
|
68
|
-
end.compact
|
69
|
-
|
70
|
-
unless invalid_messages.empty?
|
71
|
-
raise ArgumentError, invalid_messages.join("\n")
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
unless @targets.has_key?(@target)
|
76
|
-
raise ArgumentError, "Could not find deploy target #{@target} in configuration !"
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# Loads deploy target data using provided target
|
81
|
-
def load_target!
|
82
|
-
validate_targets!
|
83
|
-
load_env_vars!
|
84
|
-
|
85
|
-
@scale ||= @targets[@target][:scale]
|
86
|
-
@command = @targets[@target][:command]
|
87
|
-
@predeploy_commands = @targets[@target][:predeploy_commands] if @targets[@target][:predeploy_commands]
|
88
|
-
@bootstrap_commands = @targets[@target][:bootstrap_commands] if @targets[@target][:bootstrap_commands]
|
89
|
-
@service_config = @targets[@target][:service_config]
|
90
|
-
@task_definition_config = @targets[@target][:task_definition_config]
|
91
|
-
end
|
92
|
-
|
93
|
-
def load_env_vars!
|
94
|
-
@env_vars ||= {}
|
95
|
-
|
96
|
-
[@targets[@target][:env_file]].flatten.each do |env_path|
|
97
|
-
env_file = Pathname.new(env_path)
|
98
|
-
|
99
|
-
unless env_file.absolute?
|
100
|
-
dir = config.file.nil? ? Dir.pwd : Pathname.new(config.file).dirname
|
101
|
-
env_file = env_file.expand_path(dir)
|
102
|
-
end
|
103
|
-
|
104
|
-
if env_file.exist?
|
105
|
-
vars = Dotenv.load(env_file)
|
106
|
-
@env_vars.merge!(vars)
|
107
|
-
else
|
108
|
-
raise ArgumentError, "Could not find file '#{env_file}' for loading environment variables !"
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# convert env vars to format ecs expects
|
113
|
-
@env_vars = @env_vars.map { |k, v| { 'name' => k, 'value' => v } }
|
114
|
-
end
|
115
|
-
|
116
|
-
private
|
117
|
-
|
118
|
-
class << self
|
119
|
-
def validate_types(types, target_attribute)
|
120
|
-
if types.include?(target_attribute.class)
|
121
|
-
nil
|
122
|
-
else
|
123
|
-
"'#{target_attribute}' must be of type [#{types.join('|')}], got '#{target_attribute.class}' !"
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def validate_predeploy_commands(commands)
|
128
|
-
validate_commands(commands, 'predeploy_commands')
|
129
|
-
end
|
130
|
-
|
131
|
-
def validate_bootstrap_commands(commands)
|
132
|
-
validate_commands(commands, 'bootstrap_commands')
|
133
|
-
end
|
134
|
-
|
135
|
-
def validate_commands(commands, attribute_name)
|
136
|
-
return nil if commands.nil?
|
137
|
-
return "#{attribute_name} must be an array" unless commands.is_a?(Array)
|
138
|
-
|
139
|
-
messages = commands.reject { |cmd| cmd.is_a?(Array) }.map do |command|
|
140
|
-
"#{attribute_name} '#{command}' must be an array" unless command.is_a?(Array)
|
141
|
-
end
|
142
|
-
|
143
|
-
messages.empty? ? nil : messages.join(', ')
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
end
|
148
|
-
end
|