mrsk 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +158 -13
- data/lib/mrsk/cli/accessory.rb +87 -64
- data/lib/mrsk/cli/app.rb +103 -65
- data/lib/mrsk/cli/base.rb +46 -9
- data/lib/mrsk/cli/build.rb +40 -30
- data/lib/mrsk/cli/lock.rb +37 -0
- data/lib/mrsk/cli/main.rb +91 -49
- data/lib/mrsk/cli/prune.rb +14 -8
- data/lib/mrsk/cli/server.rb +9 -7
- data/lib/mrsk/cli/templates/deploy.yml +2 -0
- data/lib/mrsk/cli/traefik.rb +37 -21
- data/lib/mrsk/commander.rb +40 -34
- data/lib/mrsk/commands/accessory.rb +9 -6
- data/lib/mrsk/commands/app.rb +39 -37
- data/lib/mrsk/commands/auditor.rb +20 -5
- data/lib/mrsk/commands/base.rb +6 -4
- data/lib/mrsk/commands/builder/base.rb +1 -0
- data/lib/mrsk/commands/builder/native.rb +2 -1
- data/lib/mrsk/commands/healthcheck.rb +5 -3
- data/lib/mrsk/commands/lock.rb +63 -0
- data/lib/mrsk/commands/traefik.rb +13 -7
- data/lib/mrsk/configuration/accessory.rb +53 -7
- data/lib/mrsk/configuration/role.rb +13 -4
- data/lib/mrsk/configuration.rb +59 -17
- data/lib/mrsk/utils.rb +18 -2
- data/lib/mrsk/version.rb +1 -1
- data/lib/mrsk.rb +1 -0
- metadata +47 -3
data/lib/mrsk/cli/app.rb
CHANGED
@@ -1,32 +1,31 @@
|
|
1
1
|
class Mrsk::Cli::App < Mrsk::Cli::Base
|
2
2
|
desc "boot", "Boot app on servers (or reboot app if already running)"
|
3
3
|
def boot
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
with_lock do
|
5
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
6
|
+
using_version(version_or_latest) do |version|
|
7
|
+
say "Start container with version #{version} using a #{MRSK.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
MRSK.config.roles.each do |role|
|
11
|
-
on(role.hosts) do |host|
|
12
|
-
execute *MRSK.auditor.record("Booted app version #{version}"), verbosity: :debug
|
9
|
+
cli = self
|
13
10
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
execute *MRSK.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
11
|
+
on(MRSK.hosts) do |host|
|
12
|
+
roles = MRSK.roles_on(host)
|
13
|
+
|
14
|
+
roles.each do |role|
|
15
|
+
execute *MRSK.auditor(role: role).record("Booted app version #{version}"), verbosity: :debug
|
16
|
+
|
17
|
+
begin
|
18
|
+
if capture_with_info(*MRSK.app(role: role).container_id_for_version(version)).present?
|
19
|
+
tmp_version = "#{version}_#{SecureRandom.hex(8)}"
|
20
|
+
info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
|
21
|
+
execute *MRSK.auditor(role: role).record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
|
22
|
+
execute *MRSK.app(role: role).rename_container(version: version, new_version: tmp_version)
|
23
|
+
end
|
24
|
+
|
25
|
+
old_version = capture_with_info(*MRSK.app(role: role).current_running_version).strip
|
26
|
+
execute *MRSK.app(role: role).run
|
27
|
+
sleep MRSK.config.readiness_delay
|
28
|
+
execute *MRSK.app(role: role).stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
|
30
29
|
end
|
31
30
|
end
|
32
31
|
end
|
@@ -36,24 +35,42 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
36
35
|
|
37
36
|
desc "start", "Start existing app container on servers"
|
38
37
|
def start
|
39
|
-
|
40
|
-
|
41
|
-
|
38
|
+
with_lock do
|
39
|
+
on(MRSK.hosts) do |host|
|
40
|
+
roles = MRSK.roles_on(host)
|
41
|
+
|
42
|
+
roles.each do |role|
|
43
|
+
execute *MRSK.auditor.record("Started app version #{MRSK.config.version}"), verbosity: :debug
|
44
|
+
execute *MRSK.app(role: role).start, raise_on_non_zero_exit: false
|
45
|
+
end
|
46
|
+
end
|
42
47
|
end
|
43
48
|
end
|
44
49
|
|
45
50
|
desc "stop", "Stop app container on servers"
|
46
51
|
def stop
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
with_lock do
|
53
|
+
on(MRSK.hosts) do |host|
|
54
|
+
roles = MRSK.roles_on(host)
|
55
|
+
|
56
|
+
roles.each do |role|
|
57
|
+
execute *MRSK.auditor(role: role).record("Stopped app"), verbosity: :debug
|
58
|
+
execute *MRSK.app(role: role).stop, raise_on_non_zero_exit: false
|
59
|
+
end
|
60
|
+
end
|
50
61
|
end
|
51
62
|
end
|
52
63
|
|
53
64
|
# FIXME: Drop in favor of just containers?
|
54
65
|
desc "details", "Show details about app containers"
|
55
66
|
def details
|
56
|
-
on(MRSK.hosts)
|
67
|
+
on(MRSK.hosts) do |host|
|
68
|
+
roles = MRSK.roles_on(host)
|
69
|
+
|
70
|
+
roles.each do |role|
|
71
|
+
puts_by_host host, capture_with_info(*MRSK.app(role: role).info)
|
72
|
+
end
|
73
|
+
end
|
57
74
|
end
|
58
75
|
|
59
76
|
desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
|
@@ -65,12 +82,12 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
65
82
|
say "Get current version of running container...", :magenta unless options[:version]
|
66
83
|
using_version(options[:version] || current_running_version) do |version|
|
67
84
|
say "Launching interactive command with version #{version} via SSH from existing container on #{MRSK.primary_host}...", :magenta
|
68
|
-
run_locally { exec MRSK.app.execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) }
|
85
|
+
run_locally { exec MRSK.app(role: "web").execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) }
|
69
86
|
end
|
70
87
|
|
71
88
|
when options[:interactive]
|
72
89
|
say "Get most recent version available as an image...", :magenta unless options[:version]
|
73
|
-
using_version(
|
90
|
+
using_version(version_or_latest) do |version|
|
74
91
|
say "Launching interactive command with version #{version} via SSH from new container on #{MRSK.primary_host}...", :magenta
|
75
92
|
run_locally { exec MRSK.app.execute_in_new_container_over_ssh(cmd, host: MRSK.primary_host) }
|
76
93
|
end
|
@@ -81,14 +98,18 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
81
98
|
say "Launching command with version #{version} from existing container...", :magenta
|
82
99
|
|
83
100
|
on(MRSK.hosts) do |host|
|
84
|
-
|
85
|
-
|
101
|
+
roles = MRSK.roles_on(host)
|
102
|
+
|
103
|
+
roles.each do |role|
|
104
|
+
execute *MRSK.auditor(role: role).record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
105
|
+
puts_by_host host, capture_with_info(*MRSK.app(role: role).execute_in_existing_container(cmd))
|
106
|
+
end
|
86
107
|
end
|
87
108
|
end
|
88
109
|
|
89
110
|
else
|
90
111
|
say "Get most recent version available as an image...", :magenta unless options[:version]
|
91
|
-
using_version(
|
112
|
+
using_version(version_or_latest) do |version|
|
92
113
|
say "Launching command with version #{version} from new container...", :magenta
|
93
114
|
on(MRSK.hosts) do |host|
|
94
115
|
execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
@@ -121,18 +142,26 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
121
142
|
if options[:follow]
|
122
143
|
run_locally do
|
123
144
|
info "Following logs on #{MRSK.primary_host}..."
|
124
|
-
|
125
|
-
|
145
|
+
|
146
|
+
MRSK.specific_roles ||= ["web"]
|
147
|
+
role = MRSK.roles_on(MRSK.primary_host).first
|
148
|
+
|
149
|
+
info MRSK.app(role: role).follow_logs(host: MRSK.primary_host, grep: grep)
|
150
|
+
exec MRSK.app(role: role).follow_logs(host: MRSK.primary_host, grep: grep)
|
126
151
|
end
|
127
152
|
else
|
128
153
|
since = options[:since]
|
129
154
|
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
130
155
|
|
131
156
|
on(MRSK.hosts) do |host|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
157
|
+
roles = MRSK.roles_on(host)
|
158
|
+
|
159
|
+
roles.each do |role|
|
160
|
+
begin
|
161
|
+
puts_by_host host, capture_with_info(*MRSK.app(role: role).logs(since: since, lines: lines, grep: grep))
|
162
|
+
rescue SSHKit::Command::Failed
|
163
|
+
puts_by_host host, "Nothing found"
|
164
|
+
end
|
136
165
|
end
|
137
166
|
end
|
138
167
|
end
|
@@ -140,32 +169,48 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
140
169
|
|
141
170
|
desc "remove", "Remove app containers and images from servers"
|
142
171
|
def remove
|
143
|
-
|
144
|
-
|
145
|
-
|
172
|
+
with_lock do
|
173
|
+
stop
|
174
|
+
remove_containers
|
175
|
+
remove_images
|
176
|
+
end
|
146
177
|
end
|
147
178
|
|
148
179
|
desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
|
149
180
|
def remove_container(version)
|
150
|
-
|
151
|
-
|
152
|
-
|
181
|
+
with_lock do
|
182
|
+
on(MRSK.hosts) do |host|
|
183
|
+
roles = MRSK.roles_on(host)
|
184
|
+
|
185
|
+
roles.each do |role|
|
186
|
+
execute *MRSK.auditor(role: role).record("Removed app container with version #{version}"), verbosity: :debug
|
187
|
+
execute *MRSK.app(role: role).remove_container(version: version)
|
188
|
+
end
|
189
|
+
end
|
153
190
|
end
|
154
191
|
end
|
155
192
|
|
156
193
|
desc "remove_containers", "Remove all app containers from servers", hide: true
|
157
194
|
def remove_containers
|
158
|
-
|
159
|
-
|
160
|
-
|
195
|
+
with_lock do
|
196
|
+
on(MRSK.hosts) do |host|
|
197
|
+
roles = MRSK.roles_on(host)
|
198
|
+
|
199
|
+
roles.each do |role|
|
200
|
+
execute *MRSK.auditor(role: role).record("Removed all app containers"), verbosity: :debug
|
201
|
+
execute *MRSK.app(role: role).remove_containers
|
202
|
+
end
|
203
|
+
end
|
161
204
|
end
|
162
205
|
end
|
163
206
|
|
164
207
|
desc "remove_images", "Remove all app images from servers", hide: true
|
165
208
|
def remove_images
|
166
|
-
|
167
|
-
|
168
|
-
|
209
|
+
with_lock do
|
210
|
+
on(MRSK.hosts) do
|
211
|
+
execute *MRSK.auditor.record("Removed all app images"), verbosity: :debug
|
212
|
+
execute *MRSK.app.remove_images
|
213
|
+
end
|
169
214
|
end
|
170
215
|
end
|
171
216
|
|
@@ -189,20 +234,13 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
189
234
|
end
|
190
235
|
end
|
191
236
|
|
192
|
-
def most_recent_version_available(host: MRSK.primary_host)
|
193
|
-
version = nil
|
194
|
-
on(host) { version = capture_with_info(*MRSK.app.most_recent_version_from_available_images).strip }
|
195
|
-
|
196
|
-
if version == "<none>"
|
197
|
-
raise "Most recent image available was not tagged with a version (returned <none>)"
|
198
|
-
else
|
199
|
-
version.presence
|
200
|
-
end
|
201
|
-
end
|
202
|
-
|
203
237
|
def current_running_version(host: MRSK.primary_host)
|
204
238
|
version = nil
|
205
239
|
on(host) { version = capture_with_info(*MRSK.app.current_running_version).strip }
|
206
240
|
version.presence
|
207
241
|
end
|
242
|
+
|
243
|
+
def version_or_latest
|
244
|
+
options[:version] || "latest"
|
245
|
+
end
|
208
246
|
end
|
data/lib/mrsk/cli/base.rb
CHANGED
@@ -6,6 +6,8 @@ module Mrsk::Cli
|
|
6
6
|
class Base < Thor
|
7
7
|
include SSHKit::DSL
|
8
8
|
|
9
|
+
class LockError < StandardError; end
|
10
|
+
|
9
11
|
def self.exit_on_failure?() true end
|
10
12
|
|
11
13
|
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
@@ -25,7 +27,7 @@ module Mrsk::Cli
|
|
25
27
|
def initialize(*)
|
26
28
|
super
|
27
29
|
load_envs
|
28
|
-
initialize_commander(
|
30
|
+
initialize_commander(options_with_subcommand_class_options)
|
29
31
|
end
|
30
32
|
|
31
33
|
private
|
@@ -37,16 +39,12 @@ module Mrsk::Cli
|
|
37
39
|
end
|
38
40
|
end
|
39
41
|
|
42
|
+
def options_with_subcommand_class_options
|
43
|
+
options.merge(@_initializer.last[:class_options] || {})
|
44
|
+
end
|
45
|
+
|
40
46
|
def initialize_commander(options)
|
41
47
|
MRSK.tap do |commander|
|
42
|
-
commander.config_file = Pathname.new(File.expand_path(options[:config_file]))
|
43
|
-
commander.destination = options[:destination]
|
44
|
-
commander.version = options[:version]
|
45
|
-
|
46
|
-
commander.specific_hosts = options[:hosts]&.split(",")
|
47
|
-
commander.specific_roles = options[:roles]&.split(",")
|
48
|
-
commander.specific_primary! if options[:primary]
|
49
|
-
|
50
48
|
if options[:verbose]
|
51
49
|
ENV["VERBOSE"] = "1" # For backtraces via cli/start
|
52
50
|
commander.verbosity = :debug
|
@@ -55,6 +53,15 @@ module Mrsk::Cli
|
|
55
53
|
if options[:quiet]
|
56
54
|
commander.verbosity = :error
|
57
55
|
end
|
56
|
+
|
57
|
+
commander.configure \
|
58
|
+
config_file: Pathname.new(File.expand_path(options[:config_file])),
|
59
|
+
destination: options[:destination],
|
60
|
+
version: options[:version]
|
61
|
+
|
62
|
+
commander.specific_hosts = options[:hosts]&.split(",")
|
63
|
+
commander.specific_roles = options[:roles]&.split(",")
|
64
|
+
commander.specific_primary! if options[:primary]
|
58
65
|
end
|
59
66
|
end
|
60
67
|
|
@@ -70,5 +77,35 @@ module Mrsk::Cli
|
|
70
77
|
def audit_broadcast(line)
|
71
78
|
run_locally { execute *MRSK.auditor.broadcast(line), verbosity: :debug }
|
72
79
|
end
|
80
|
+
|
81
|
+
def with_lock
|
82
|
+
acquire_lock
|
83
|
+
|
84
|
+
yield
|
85
|
+
ensure
|
86
|
+
release_lock
|
87
|
+
end
|
88
|
+
|
89
|
+
def acquire_lock
|
90
|
+
if MRSK.lock_count == 0
|
91
|
+
say "Acquiring the deploy lock"
|
92
|
+
on(MRSK.primary_host) { execute *MRSK.lock.acquire("Automatic deploy lock", MRSK.config.version) }
|
93
|
+
end
|
94
|
+
MRSK.lock_count += 1
|
95
|
+
rescue SSHKit::Runner::ExecuteError => e
|
96
|
+
if e.message =~ /cannot create directory/
|
97
|
+
invoke "mrsk:cli:lock:status", []
|
98
|
+
end
|
99
|
+
|
100
|
+
raise LockError, "Deploy lock found"
|
101
|
+
end
|
102
|
+
|
103
|
+
def release_lock
|
104
|
+
MRSK.lock_count -= 1
|
105
|
+
if MRSK.lock_count == 0
|
106
|
+
say "Releasing the deploy lock"
|
107
|
+
on(MRSK.primary_host) { execute *MRSK.lock.release }
|
108
|
+
end
|
109
|
+
end
|
73
110
|
end
|
74
111
|
end
|
data/lib/mrsk/cli/build.rb
CHANGED
@@ -1,26 +1,30 @@
|
|
1
1
|
class Mrsk::Cli::Build < Mrsk::Cli::Base
|
2
2
|
desc "deliver", "Build app and push app image to registry then pull image on servers"
|
3
3
|
def deliver
|
4
|
-
|
5
|
-
|
4
|
+
with_lock do
|
5
|
+
push
|
6
|
+
pull
|
7
|
+
end
|
6
8
|
end
|
7
9
|
|
8
10
|
desc "push", "Build and push app image to registry"
|
9
11
|
def push
|
10
|
-
|
12
|
+
with_lock do
|
13
|
+
cli = self
|
11
14
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
run_locally do
|
16
|
+
begin
|
17
|
+
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
18
|
+
rescue SSHKit::Command::Failed => e
|
19
|
+
if e.message =~ /(no builder)|(no such file or directory)/
|
20
|
+
error "Missing compatible builder, so creating a new one first"
|
18
21
|
|
19
|
-
|
20
|
-
|
22
|
+
if cli.create
|
23
|
+
MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
|
24
|
+
end
|
25
|
+
else
|
26
|
+
raise
|
21
27
|
end
|
22
|
-
else
|
23
|
-
raise
|
24
28
|
end
|
25
29
|
end
|
26
30
|
end
|
@@ -28,25 +32,29 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|
28
32
|
|
29
33
|
desc "pull", "Pull app image from registry onto servers"
|
30
34
|
def pull
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
+
with_lock do
|
36
|
+
on(MRSK.hosts) do
|
37
|
+
execute *MRSK.auditor.record("Pulled image with version #{MRSK.config.version}"), verbosity: :debug
|
38
|
+
execute *MRSK.builder.clean, raise_on_non_zero_exit: false
|
39
|
+
execute *MRSK.builder.pull
|
40
|
+
end
|
35
41
|
end
|
36
42
|
end
|
37
43
|
|
38
44
|
desc "create", "Create a build setup"
|
39
45
|
def create
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
with_lock do
|
47
|
+
run_locally do
|
48
|
+
begin
|
49
|
+
debug "Using builder: #{MRSK.builder.name}"
|
50
|
+
execute *MRSK.builder.create
|
51
|
+
rescue SSHKit::Command::Failed => e
|
52
|
+
if e.message =~ /stderr=(.*)/
|
53
|
+
error "Couldn't create remote builder: #{$1}"
|
54
|
+
false
|
55
|
+
else
|
56
|
+
raise
|
57
|
+
end
|
50
58
|
end
|
51
59
|
end
|
52
60
|
end
|
@@ -54,9 +62,11 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|
54
62
|
|
55
63
|
desc "remove", "Remove build setup"
|
56
64
|
def remove
|
57
|
-
|
58
|
-
|
59
|
-
|
65
|
+
with_lock do
|
66
|
+
run_locally do
|
67
|
+
debug "Using builder: #{MRSK.builder.name}"
|
68
|
+
execute *MRSK.builder.remove
|
69
|
+
end
|
60
70
|
end
|
61
71
|
end
|
62
72
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Mrsk::Cli::Lock < Mrsk::Cli::Base
|
2
|
+
desc "status", "Report lock status"
|
3
|
+
def status
|
4
|
+
handle_missing_lock do
|
5
|
+
on(MRSK.primary_host) { puts capture_with_info(*MRSK.lock.status) }
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
desc "acquire", "Acquire the deploy lock"
|
10
|
+
option :message, aliases: "-m", type: :string, desc: "A lock mesasge", required: true
|
11
|
+
def acquire
|
12
|
+
message = options[:message]
|
13
|
+
handle_missing_lock do
|
14
|
+
on(MRSK.primary_host) { execute *MRSK.lock.acquire(message, MRSK.config.version) }
|
15
|
+
say "Set the deploy lock"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "release", "Release the deploy lock"
|
20
|
+
def release
|
21
|
+
handle_missing_lock do
|
22
|
+
on(MRSK.primary_host) { execute *MRSK.lock.release }
|
23
|
+
say "Removed the deploy lock"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def handle_missing_lock
|
29
|
+
yield
|
30
|
+
rescue SSHKit::Runner::ExecuteError => e
|
31
|
+
if e.message =~ /No such file or directory/
|
32
|
+
say "There is no deploy lock"
|
33
|
+
else
|
34
|
+
raise
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|