mrsk 0.4.0 → 0.5.1
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 +4 -4
- data/README.md +17 -5
- data/lib/mrsk/cli/accessory.rb +50 -34
- data/lib/mrsk/cli/app.rb +129 -61
- data/lib/mrsk/cli/build.rb +4 -1
- data/lib/mrsk/cli/main.rb +26 -4
- data/lib/mrsk/cli/prune.rb +8 -2
- data/lib/mrsk/cli/registry.rb +1 -1
- data/lib/mrsk/cli/traefik.rb +16 -4
- data/lib/mrsk/commander.rb +12 -0
- data/lib/mrsk/commands/accessory.rb +11 -8
- data/lib/mrsk/commands/app.rb +59 -22
- data/lib/mrsk/commands/auditor.rb +31 -0
- data/lib/mrsk/commands/base.rb +8 -0
- data/lib/mrsk/configuration/accessory.rb +1 -1
- data/lib/mrsk/configuration.rb +2 -5
- data/lib/mrsk/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d76643b4635191a3301d23e41f5f4c18ca0db674c44d66b98610df0279c85c66
|
4
|
+
data.tar.gz: cd19d1ed503529b6876232d88f5133f782ddfca7f16e13d781120ed269e155d4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3fc1e47cc35077797dbcc366924c470d877f3f7980ba3b0c87c1dcd4230eb563f4b478a6ee1dfe48cf07c1ae1373d2f746dbd9433c494fd20bfd64a6096058b
|
7
|
+
data.tar.gz: 4c6c4875c4f91712b6dd2fbeeb0c662c45aaf373508a9696a2cf47cd7d9a2fe486774f9b2897c9416dc9bb7f1dcb6890a5be3ef781654e264792c0858beb9f6c
|
data/README.md
CHANGED
@@ -302,9 +302,9 @@ Now run `mrsk accessory start mysql` to start the MySQL server on 1.1.1.3. See `
|
|
302
302
|
|
303
303
|
## Commands
|
304
304
|
|
305
|
-
### Running
|
305
|
+
### Running commands on servers
|
306
306
|
|
307
|
-
|
307
|
+
You can execute one-off commands on the servers:
|
308
308
|
|
309
309
|
```bash
|
310
310
|
# Runs command on all servers
|
@@ -347,13 +347,25 @@ Database adapter sqlite3
|
|
347
347
|
Database schema version 20221231233303
|
348
348
|
|
349
349
|
# Run Rails runner on primary server
|
350
|
-
mrsk app
|
350
|
+
mrsk app exec -p 'bin/rails runner "puts Rails.application.config.time_zone"'
|
351
351
|
UTC
|
352
352
|
```
|
353
353
|
|
354
|
-
### Running
|
354
|
+
### Running interactive commands over SSH
|
355
|
+
|
356
|
+
You can run interactive commands, like a Rails console or a bash session, on a server (default is primary, use `--hosts` to connect to another):
|
357
|
+
|
358
|
+
```bash
|
359
|
+
# Starts a bash session in a new container made from the most recent app image
|
360
|
+
mrsk app exec -i bash
|
361
|
+
|
362
|
+
# Starts a bash session in the currently running container for the app
|
363
|
+
mrsk app exec -i --reuse bash
|
364
|
+
|
365
|
+
# Starts a Rails console in a new container made from the most recent app image
|
366
|
+
mrsk app exec -i 'bin/rails console'
|
367
|
+
```
|
355
368
|
|
356
|
-
If you need to interact with the production console for the app, you can use `mrsk app console`, which will start a Rails console session on the primary host. You can start the console on a different host using `mrsk app console --host 192.168.0.2`. Be mindful that this is a live wire! Any changes made to the production database will take effect immeditately.
|
357
369
|
|
358
370
|
### Running details to see state of containers
|
359
371
|
|
data/lib/mrsk/cli/accessory.rb
CHANGED
@@ -9,7 +9,10 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
9
9
|
with_accessory(name) do |accessory|
|
10
10
|
directories(name)
|
11
11
|
upload(name)
|
12
|
-
on(accessory.host)
|
12
|
+
on(accessory.host) do
|
13
|
+
execute *MRSK.auditor.record("accessory #{name} boot"), verbosity: :debug
|
14
|
+
execute *accessory.run
|
15
|
+
end
|
13
16
|
end
|
14
17
|
end
|
15
18
|
end
|
@@ -18,6 +21,8 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
18
21
|
def upload(name)
|
19
22
|
with_accessory(name) do |accessory|
|
20
23
|
on(accessory.host) do
|
24
|
+
execute *MRSK.auditor.record("accessory #{name} upload files"), verbosity: :debug
|
25
|
+
|
21
26
|
accessory.files.each do |(local, remote)|
|
22
27
|
accessory.ensure_local_file_present(local)
|
23
28
|
|
@@ -33,6 +38,8 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
33
38
|
def directories(name)
|
34
39
|
with_accessory(name) do |accessory|
|
35
40
|
on(accessory.host) do
|
41
|
+
execute *MRSK.auditor.record("accessory #{name} create directories"), verbosity: :debug
|
42
|
+
|
36
43
|
accessory.directories.keys.each do |host_path|
|
37
44
|
execute *accessory.make_directory(host_path)
|
38
45
|
end
|
@@ -52,14 +59,20 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
52
59
|
desc "start [NAME]", "Start existing accessory on host"
|
53
60
|
def start(name)
|
54
61
|
with_accessory(name) do |accessory|
|
55
|
-
on(accessory.host)
|
62
|
+
on(accessory.host) do
|
63
|
+
execute *MRSK.auditor.record("accessory #{name} start"), verbosity: :debug
|
64
|
+
execute *accessory.start
|
65
|
+
end
|
56
66
|
end
|
57
67
|
end
|
58
68
|
|
59
69
|
desc "stop [NAME]", "Stop accessory on host"
|
60
70
|
def stop(name)
|
61
71
|
with_accessory(name) do |accessory|
|
62
|
-
on(accessory.host)
|
72
|
+
on(accessory.host) do
|
73
|
+
execute *MRSK.auditor.record("accessory #{name} stop"), verbosity: :debug
|
74
|
+
execute *accessory.stop, raise_on_non_zero_exit: false
|
75
|
+
end
|
63
76
|
end
|
64
77
|
end
|
65
78
|
|
@@ -82,43 +95,37 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
82
95
|
end
|
83
96
|
end
|
84
97
|
|
85
|
-
desc "exec [NAME] [CMD]", "Execute a custom command on
|
86
|
-
option :
|
98
|
+
desc "exec [NAME] [CMD]", "Execute a custom command on servers"
|
99
|
+
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
100
|
+
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
87
101
|
def exec(name, cmd)
|
88
|
-
runner = \
|
89
|
-
case options[:method]
|
90
|
-
when "exec" then "exec"
|
91
|
-
when "run" then "run_exec"
|
92
|
-
when "ssh_exec" then "exec_over_ssh"
|
93
|
-
when "ssh_run" then "run_over_ssh"
|
94
|
-
else raise "Unknown method: #{options[:method]}"
|
95
|
-
end.inquiry
|
96
|
-
|
97
102
|
with_accessory(name) do |accessory|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
103
|
+
case
|
104
|
+
when options[:interactive] && options[:reuse]
|
105
|
+
say "Launching interactive command with via SSH from existing container...", :magenta
|
106
|
+
run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
|
107
|
+
|
108
|
+
when options[:interactive]
|
109
|
+
say "Launching interactive command via SSH from new container...", :magenta
|
110
|
+
run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
|
111
|
+
|
112
|
+
when options[:reuse]
|
113
|
+
say "Launching command from existing container...", :magenta
|
114
|
+
on(accessory.host) do
|
115
|
+
execute *MRSK.auditor.record("accessory #{name} cmd '#{cmd}'"), verbosity: :debug
|
116
|
+
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
102
117
|
end
|
118
|
+
|
103
119
|
else
|
120
|
+
say "Launching command from new container...", :magenta
|
104
121
|
on(accessory.host) do
|
105
|
-
|
106
|
-
|
122
|
+
execute *MRSK.auditor.record("accessory #{name} cmd '#{cmd}'"), verbosity: :debug
|
123
|
+
capture_with_info(*accessory.execute_in_new_container(cmd))
|
107
124
|
end
|
108
125
|
end
|
109
126
|
end
|
110
127
|
end
|
111
128
|
|
112
|
-
desc "bash [NAME]", "Start a bash session on primary host (or specific host set by --hosts)"
|
113
|
-
def bash(name)
|
114
|
-
with_accessory(name) do |accessory|
|
115
|
-
run_locally do
|
116
|
-
info "Launching bash session on #{accessory.host}"
|
117
|
-
exec accessory.bash
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
129
|
desc "logs [NAME]", "Show log lines from accessory on host"
|
123
130
|
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
124
131
|
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
@@ -162,21 +169,30 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
|
|
162
169
|
desc "remove_container [NAME]", "Remove accessory container from host"
|
163
170
|
def remove_container(name)
|
164
171
|
with_accessory(name) do |accessory|
|
165
|
-
on(accessory.host)
|
172
|
+
on(accessory.host) do
|
173
|
+
execute *MRSK.auditor.record("accessory #{name} remove container"), verbosity: :debug
|
174
|
+
execute *accessory.remove_container
|
175
|
+
end
|
166
176
|
end
|
167
177
|
end
|
168
178
|
|
169
|
-
desc "
|
179
|
+
desc "remove_image [NAME]", "Remove accessory image from host"
|
170
180
|
def remove_image(name)
|
171
181
|
with_accessory(name) do |accessory|
|
172
|
-
on(accessory.host)
|
182
|
+
on(accessory.host) do
|
183
|
+
execute *MRSK.auditor.record("accessory #{name} remove image"), verbosity: :debug
|
184
|
+
execute *accessory.remove_image
|
185
|
+
end
|
173
186
|
end
|
174
187
|
end
|
175
188
|
|
176
189
|
desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host"
|
177
190
|
def remove_service_directory(name)
|
178
191
|
with_accessory(name) do |accessory|
|
179
|
-
on(accessory.host)
|
192
|
+
on(accessory.host) do
|
193
|
+
execute *MRSK.auditor.record("accessory #{name} remove service directory"), verbosity: :debug
|
194
|
+
execute *accessory.remove_service_directory
|
195
|
+
end
|
180
196
|
end
|
181
197
|
end
|
182
198
|
|
data/lib/mrsk/cli/app.rb
CHANGED
@@ -1,37 +1,52 @@
|
|
1
1
|
require "mrsk/cli/base"
|
2
2
|
|
3
3
|
class Mrsk::Cli::App < Mrsk::Cli::Base
|
4
|
-
desc "boot", "Boot app on servers (or
|
4
|
+
desc "boot", "Boot app on servers (or reboot app if already running)"
|
5
5
|
def boot
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
6
|
+
cli = self
|
7
|
+
|
8
|
+
say "Ensure no other version of the app is running...", :magenta
|
9
|
+
stop
|
10
|
+
|
11
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
12
|
+
using_version(options[:version] || most_recent_version_available) do |version|
|
13
|
+
say "Start container with version #{version} (or reboot if already running)...", :magenta
|
14
|
+
|
15
|
+
MRSK.config.roles.each do |role|
|
16
|
+
on(role.hosts) do |host|
|
17
|
+
execute *MRSK.auditor.record("app boot version #{version}"), verbosity: :debug
|
18
|
+
|
19
|
+
begin
|
20
|
+
execute *MRSK.app.run(role: role.name)
|
21
|
+
rescue SSHKit::Command::Failed => e
|
22
|
+
if e.message =~ /already in use/
|
23
|
+
error "Rebooting container with same version already deployed on #{host}"
|
24
|
+
|
25
|
+
cli.remove_container version
|
26
|
+
execute *MRSK.app.run(role: role.name)
|
27
|
+
else
|
28
|
+
raise
|
29
|
+
end
|
16
30
|
end
|
17
31
|
end
|
18
32
|
end
|
19
33
|
end
|
20
34
|
end
|
21
|
-
|
35
|
+
|
22
36
|
desc "start", "Start existing app on servers (use --version=<git-hash> to designate specific version)"
|
23
|
-
option :version, desc: "Defaults to the most recent git-hash in local repository"
|
24
37
|
def start
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
on(MRSK.hosts) { execute *MRSK.app.start, raise_on_non_zero_exit: false }
|
38
|
+
on(MRSK.hosts) do
|
39
|
+
execute *MRSK.auditor.record("app start version #{MRSK.version}"), verbosity: :debug
|
40
|
+
execute *MRSK.app.start, raise_on_non_zero_exit: false
|
29
41
|
end
|
30
42
|
end
|
31
43
|
|
32
44
|
desc "stop", "Stop app on servers"
|
33
45
|
def stop
|
34
|
-
on(MRSK.hosts)
|
46
|
+
on(MRSK.hosts) do
|
47
|
+
execute *MRSK.auditor.record("app stop"), verbosity: :debug
|
48
|
+
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
49
|
+
end
|
35
50
|
end
|
36
51
|
|
37
52
|
desc "details", "Display details about app containers"
|
@@ -40,45 +55,45 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
40
55
|
end
|
41
56
|
|
42
57
|
desc "exec [CMD]", "Execute a custom command on servers"
|
43
|
-
option :
|
58
|
+
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
59
|
+
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
44
60
|
def exec(cmd)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end.inquiry
|
52
|
-
|
53
|
-
if runner.exec_over_ssh?
|
54
|
-
run_locally do
|
55
|
-
info "Launching command on #{MRSK.primary_host}"
|
56
|
-
exec MRSK.app.exec_over_ssh(cmd, host: MRSK.primary_host)
|
61
|
+
case
|
62
|
+
when options[:interactive] && options[:reuse]
|
63
|
+
say "Get current version of running container...", :magenta unless options[:version]
|
64
|
+
using_version(options[:version] || current_running_version) do |version|
|
65
|
+
say "Launching interactive command with version #{version} via SSH from existing container on #{MRSK.primary_host}...", :magenta
|
66
|
+
run_locally { exec MRSK.app.execute_in_existing_container_over_ssh(cmd, host: MRSK.primary_host) }
|
57
67
|
end
|
58
|
-
else
|
59
|
-
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.send(runner, cmd)) }
|
60
|
-
end
|
61
|
-
end
|
62
68
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
end
|
69
|
+
when options[:interactive]
|
70
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
71
|
+
using_version(options[:version] || most_recent_version_available) do |version|
|
72
|
+
say "Launching interactive command with version #{version} via SSH from new container on #{MRSK.primary_host}...", :magenta
|
73
|
+
run_locally { exec MRSK.app.execute_in_new_container_over_ssh(cmd, host: MRSK.primary_host) }
|
74
|
+
end
|
70
75
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
76
|
+
when options[:reuse]
|
77
|
+
say "Get current version of running container...", :magenta unless options[:version]
|
78
|
+
using_version(options[:version] || current_running_version) do |version|
|
79
|
+
say "Launching command with version #{version} from existing container...", :magenta
|
80
|
+
|
81
|
+
on(MRSK.hosts) do |host|
|
82
|
+
execute *MRSK.auditor.record("app cmd '#{cmd}' with version #{version}"), verbosity: :debug
|
83
|
+
puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd))
|
84
|
+
end
|
85
|
+
end
|
78
86
|
|
79
|
-
|
80
|
-
|
81
|
-
|
87
|
+
else
|
88
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
89
|
+
using_version(options[:version] || most_recent_version_available) do |version|
|
90
|
+
say "Launching command with version #{version} from new container...", :magenta
|
91
|
+
on(MRSK.hosts) do |host|
|
92
|
+
execute *MRSK.auditor.record("app cmd '#{cmd}' with version #{version}"), verbosity: :debug
|
93
|
+
puts_by_host host, capture_with_info(*MRSK.app.execute_in_new_container(cmd))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
82
97
|
end
|
83
98
|
|
84
99
|
desc "containers", "List all the app containers currently on servers"
|
@@ -86,6 +101,11 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
86
101
|
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) }
|
87
102
|
end
|
88
103
|
|
104
|
+
desc "images", "List all the app images currently on servers"
|
105
|
+
def images
|
106
|
+
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_images) }
|
107
|
+
end
|
108
|
+
|
89
109
|
desc "current", "Return the current running container ID"
|
90
110
|
def current
|
91
111
|
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_container_id) }
|
@@ -122,16 +142,64 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
122
142
|
end
|
123
143
|
|
124
144
|
desc "remove", "Remove app containers and images from servers"
|
125
|
-
option :only, default: "", desc: "Use 'containers' or 'images'"
|
126
145
|
def remove
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
146
|
+
remove_containers
|
147
|
+
remove_images
|
148
|
+
end
|
149
|
+
|
150
|
+
desc "remove_container [VERSION]", "Remove app container with given version from servers"
|
151
|
+
def remove_container(version)
|
152
|
+
on(MRSK.hosts) do
|
153
|
+
execute *MRSK.auditor.record("app remove container #{version}"), verbosity: :debug
|
154
|
+
execute *MRSK.app.remove_container(version: version)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
desc "remove_containers", "Remove all app containers from servers"
|
159
|
+
def remove_containers
|
160
|
+
on(MRSK.hosts) do
|
161
|
+
execute *MRSK.auditor.record("app remove containers"), verbosity: :debug
|
162
|
+
execute *MRSK.app.remove_containers
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
desc "remove_images", "Remove all app images from servers"
|
167
|
+
def remove_images
|
168
|
+
on(MRSK.hosts) do
|
169
|
+
execute *MRSK.auditor.record("app remove images"), verbosity: :debug
|
170
|
+
execute *MRSK.app.remove_images
|
135
171
|
end
|
136
172
|
end
|
173
|
+
|
174
|
+
desc "current_version", "Shows the version currently running"
|
175
|
+
def current_version
|
176
|
+
on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.current_running_version).strip }
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
def using_version(new_version)
|
181
|
+
if new_version
|
182
|
+
begin
|
183
|
+
old_version = MRSK.config.version
|
184
|
+
MRSK.config.version = new_version
|
185
|
+
yield new_version
|
186
|
+
ensure
|
187
|
+
MRSK.config.version = old_version
|
188
|
+
end
|
189
|
+
else
|
190
|
+
yield MRSK.config.version
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def most_recent_version_available(host: MRSK.primary_host)
|
195
|
+
version = nil
|
196
|
+
on(host) { version = capture_with_info(*MRSK.app.most_recent_version_from_available_images).strip }
|
197
|
+
version.presence
|
198
|
+
end
|
199
|
+
|
200
|
+
def current_running_version(host: MRSK.primary_host)
|
201
|
+
version = nil
|
202
|
+
on(host) { version = capture_with_info(*MRSK.app.current_running_version).strip }
|
203
|
+
version.presence
|
204
|
+
end
|
137
205
|
end
|
data/lib/mrsk/cli/build.rb
CHANGED
@@ -30,7 +30,10 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
|
|
30
30
|
|
31
31
|
desc "pull", "Pull app image from the registry onto servers"
|
32
32
|
def pull
|
33
|
-
on(MRSK.hosts)
|
33
|
+
on(MRSK.hosts) do
|
34
|
+
execute *MRSK.auditor.record("build pull image #{MRSK.version}"), verbosity: :debug
|
35
|
+
execute *MRSK.builder.pull
|
36
|
+
end
|
34
37
|
end
|
35
38
|
|
36
39
|
desc "create", "Create a local build setup"
|
data/lib/mrsk/cli/main.rb
CHANGED
@@ -21,12 +21,21 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
21
21
|
desc "deploy", "Deploy the app to servers"
|
22
22
|
def deploy
|
23
23
|
print_runtime do
|
24
|
+
say "Ensure Docker is installed...", :magenta
|
24
25
|
invoke "mrsk:cli:server:bootstrap"
|
26
|
+
|
27
|
+
say "Log into image registry...", :magenta
|
25
28
|
invoke "mrsk:cli:registry:login"
|
29
|
+
|
30
|
+
say "Build and push app image...", :magenta
|
26
31
|
invoke "mrsk:cli:build:deliver"
|
32
|
+
|
33
|
+
say "Ensure Traefik is running...", :magenta
|
27
34
|
invoke "mrsk:cli:traefik:boot"
|
28
|
-
|
35
|
+
|
29
36
|
invoke "mrsk:cli:app:boot"
|
37
|
+
|
38
|
+
say "Prune old containers and images...", :magenta
|
30
39
|
invoke "mrsk:cli:prune:all"
|
31
40
|
end
|
32
41
|
end
|
@@ -34,17 +43,23 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
34
43
|
desc "redeploy", "Deploy new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)"
|
35
44
|
def redeploy
|
36
45
|
print_runtime do
|
46
|
+
say "Build and push app image...", :magenta
|
37
47
|
invoke "mrsk:cli:build:deliver"
|
38
|
-
|
48
|
+
|
39
49
|
invoke "mrsk:cli:app:boot"
|
40
50
|
end
|
41
51
|
end
|
42
52
|
|
43
|
-
desc "rollback [VERSION]", "Rollback the app to VERSION
|
53
|
+
desc "rollback [VERSION]", "Rollback the app to VERSION"
|
44
54
|
def rollback(version)
|
55
|
+
MRSK.version = version
|
56
|
+
|
57
|
+
cli = self
|
58
|
+
|
59
|
+
cli.say "Stop current version, then start version #{version}...", :magenta
|
45
60
|
on(MRSK.hosts) do
|
46
61
|
execute *MRSK.app.stop, raise_on_non_zero_exit: false
|
47
|
-
execute *MRSK.app.start
|
62
|
+
execute *MRSK.app.start
|
48
63
|
end
|
49
64
|
end
|
50
65
|
|
@@ -55,6 +70,13 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
55
70
|
invoke "mrsk:cli:accessory:details", [ "all" ]
|
56
71
|
end
|
57
72
|
|
73
|
+
desc "audit", "Show audit log from servers"
|
74
|
+
def audit
|
75
|
+
on(MRSK.hosts) do |host|
|
76
|
+
puts_by_host host, capture_with_info(*MRSK.auditor.reveal)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
58
80
|
desc "config", "Show combined config"
|
59
81
|
def config
|
60
82
|
run_locally do
|
data/lib/mrsk/cli/prune.rb
CHANGED
@@ -9,11 +9,17 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
|
|
9
9
|
|
10
10
|
desc "images", "Prune unused images older than 30 days"
|
11
11
|
def images
|
12
|
-
on(MRSK.hosts)
|
12
|
+
on(MRSK.hosts) do
|
13
|
+
execute *MRSK.auditor.record("prune images"), verbosity: :debug
|
14
|
+
execute *MRSK.prune.images
|
15
|
+
end
|
13
16
|
end
|
14
17
|
|
15
18
|
desc "containers", "Prune stopped containers for the service older than 3 days"
|
16
19
|
def containers
|
17
|
-
on(MRSK.hosts)
|
20
|
+
on(MRSK.hosts) do
|
21
|
+
execute *MRSK.auditor.record("prune containers"), verbosity: :debug
|
22
|
+
execute *MRSK.prune.containers
|
23
|
+
end
|
18
24
|
end
|
19
25
|
end
|
data/lib/mrsk/cli/registry.rb
CHANGED
@@ -3,7 +3,7 @@ require "mrsk/cli/base"
|
|
3
3
|
class Mrsk::Cli::Registry < Mrsk::Cli::Base
|
4
4
|
desc "login", "Login to the registry locally and remotely"
|
5
5
|
def login
|
6
|
-
run_locally
|
6
|
+
run_locally { execute *MRSK.registry.login }
|
7
7
|
on(MRSK.hosts) { execute *MRSK.registry.login }
|
8
8
|
rescue ArgumentError => e
|
9
9
|
puts e.message
|
data/lib/mrsk/cli/traefik.rb
CHANGED
@@ -15,12 +15,18 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
|
15
15
|
|
16
16
|
desc "start", "Start existing Traefik on servers"
|
17
17
|
def start
|
18
|
-
on(MRSK.traefik_hosts)
|
18
|
+
on(MRSK.traefik_hosts) do
|
19
|
+
execute *MRSK.auditor.record("traefik start"), verbosity: :debug
|
20
|
+
execute *MRSK.traefik.start, raise_on_non_zero_exit: false
|
21
|
+
end
|
19
22
|
end
|
20
23
|
|
21
24
|
desc "stop", "Stop Traefik on servers"
|
22
25
|
def stop
|
23
|
-
on(MRSK.traefik_hosts)
|
26
|
+
on(MRSK.traefik_hosts) do
|
27
|
+
execute *MRSK.auditor.record("traefik stop"), verbosity: :debug
|
28
|
+
execute *MRSK.traefik.stop, raise_on_non_zero_exit: false
|
29
|
+
end
|
24
30
|
end
|
25
31
|
|
26
32
|
desc "restart", "Restart Traefik on servers"
|
@@ -67,11 +73,17 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
|
|
67
73
|
|
68
74
|
desc "remove_container", "Remove Traefik container from servers"
|
69
75
|
def remove_container
|
70
|
-
on(MRSK.traefik_hosts)
|
76
|
+
on(MRSK.traefik_hosts) do
|
77
|
+
execute *MRSK.auditor.record("traefik remove container"), verbosity: :debug
|
78
|
+
execute *MRSK.traefik.remove_container
|
79
|
+
end
|
71
80
|
end
|
72
81
|
|
73
82
|
desc "remove_container", "Remove Traefik image from servers"
|
74
83
|
def remove_image
|
75
|
-
on(MRSK.traefik_hosts)
|
84
|
+
on(MRSK.traefik_hosts) do
|
85
|
+
execute *MRSK.auditor.record("traefik remove image"), verbosity: :debug
|
86
|
+
execute *MRSK.traefik.remove_image
|
87
|
+
end
|
76
88
|
end
|
77
89
|
end
|
data/lib/mrsk/commander.rb
CHANGED
@@ -3,6 +3,7 @@ require "active_support/core_ext/enumerable"
|
|
3
3
|
require "mrsk/configuration"
|
4
4
|
require "mrsk/commands/accessory"
|
5
5
|
require "mrsk/commands/app"
|
6
|
+
require "mrsk/commands/auditor"
|
6
7
|
require "mrsk/commands/builder"
|
7
8
|
require "mrsk/commands/prune"
|
8
9
|
require "mrsk/commands/traefik"
|
@@ -77,6 +78,10 @@ class Mrsk::Commander
|
|
77
78
|
Mrsk::Commands::Accessory.new(config, name: name)
|
78
79
|
end
|
79
80
|
|
81
|
+
def auditor
|
82
|
+
@auditor ||= Mrsk::Commands::Auditor.new(config)
|
83
|
+
end
|
84
|
+
|
80
85
|
|
81
86
|
def with_verbosity(level)
|
82
87
|
old_level = SSHKit.config.output_verbosity
|
@@ -86,6 +91,13 @@ class Mrsk::Commander
|
|
86
91
|
SSHKit.config.output_verbosity = old_level
|
87
92
|
end
|
88
93
|
|
94
|
+
# Test-induced damage!
|
95
|
+
def reset
|
96
|
+
@config = @config_file = @destination = @version = nil
|
97
|
+
@app = @builder = @traefik = @registry = @prune = @auditor = nil
|
98
|
+
@verbosity = :info
|
99
|
+
end
|
100
|
+
|
89
101
|
private
|
90
102
|
def cascading_version
|
91
103
|
version.presence || ENV["VERSION"] || `git rev-parse HEAD`.strip
|
@@ -33,6 +33,7 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
33
33
|
docker :ps, *service_filter
|
34
34
|
end
|
35
35
|
|
36
|
+
|
36
37
|
def logs(since: nil, lines: nil, grep: nil)
|
37
38
|
pipe \
|
38
39
|
docker(:logs, service_name, (" --since #{since}" if since), (" -n #{lines}" if lines), "-t", "2>&1"),
|
@@ -46,14 +47,15 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
46
47
|
).join(" ")
|
47
48
|
end
|
48
49
|
|
49
|
-
|
50
|
+
|
51
|
+
def execute_in_existing_container(*command, interactive: false)
|
50
52
|
docker :exec,
|
51
53
|
("-it" if interactive),
|
52
54
|
service_name,
|
53
55
|
*command
|
54
56
|
end
|
55
57
|
|
56
|
-
def
|
58
|
+
def execute_in_new_container(*command, interactive: false)
|
57
59
|
docker :run,
|
58
60
|
("-it" if interactive),
|
59
61
|
"--rm",
|
@@ -63,18 +65,19 @@ class Mrsk::Commands::Accessory < Mrsk::Commands::Base
|
|
63
65
|
*command
|
64
66
|
end
|
65
67
|
|
66
|
-
def
|
67
|
-
|
68
|
+
def execute_in_existing_container_over_ssh(*command)
|
69
|
+
run_over_ssh execute_in_existing_container(*command, interactive: true).join(" ")
|
68
70
|
end
|
69
71
|
|
70
|
-
def
|
71
|
-
run_over_ssh
|
72
|
+
def execute_in_new_container_over_ssh(*command)
|
73
|
+
run_over_ssh execute_in_new_container(*command, interactive: true).join(" ")
|
72
74
|
end
|
73
75
|
|
74
|
-
def
|
75
|
-
|
76
|
+
def run_over_ssh(command)
|
77
|
+
super command, host: host
|
76
78
|
end
|
77
79
|
|
80
|
+
|
78
81
|
def ensure_local_file_present(local_file)
|
79
82
|
if !local_file.is_a?(StringIO) && !Pathname.new(local_file).exist?
|
80
83
|
raise "Missing file: #{local_file}"
|
data/lib/mrsk/commands/app.rb
CHANGED
@@ -7,7 +7,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
7
7
|
docker :run,
|
8
8
|
"-d",
|
9
9
|
"--restart unless-stopped",
|
10
|
-
"--name",
|
10
|
+
"--name", service_with_version,
|
11
11
|
*rails_master_key_arg,
|
12
12
|
*role.env_args,
|
13
13
|
*config.volume_args,
|
@@ -16,22 +16,19 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
16
16
|
role.cmd
|
17
17
|
end
|
18
18
|
|
19
|
-
def start
|
20
|
-
docker :start,
|
21
|
-
end
|
22
|
-
|
23
|
-
def current_container_id
|
24
|
-
docker :ps, "-q", *service_filter
|
19
|
+
def start
|
20
|
+
docker :start, service_with_version
|
25
21
|
end
|
26
22
|
|
27
23
|
def stop
|
28
|
-
pipe current_container_id,
|
24
|
+
pipe current_container_id, xargs(docker(:stop))
|
29
25
|
end
|
30
26
|
|
31
27
|
def info
|
32
28
|
docker :ps, *service_filter
|
33
29
|
end
|
34
30
|
|
31
|
+
|
35
32
|
def logs(since: nil, lines: nil, grep: nil)
|
36
33
|
pipe \
|
37
34
|
current_container_id,
|
@@ -39,14 +36,23 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
39
36
|
("grep '#{grep}'" if grep)
|
40
37
|
end
|
41
38
|
|
42
|
-
def
|
39
|
+
def follow_logs(host:, grep: nil)
|
40
|
+
run_over_ssh pipe(
|
41
|
+
current_container_id,
|
42
|
+
"xargs docker logs -t -n 10 -f 2>&1",
|
43
|
+
(%(grep "#{grep}") if grep)
|
44
|
+
).join(" "), host: host
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def execute_in_existing_container(*command, interactive: false)
|
43
49
|
docker :exec,
|
44
50
|
("-it" if interactive),
|
45
51
|
config.service_with_version,
|
46
52
|
*command
|
47
53
|
end
|
48
54
|
|
49
|
-
def
|
55
|
+
def execute_in_new_container(*command, interactive: false)
|
50
56
|
docker :run,
|
51
57
|
("-it" if interactive),
|
52
58
|
"--rm",
|
@@ -57,39 +63,70 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
57
63
|
*command
|
58
64
|
end
|
59
65
|
|
60
|
-
def
|
61
|
-
run_over_ssh
|
66
|
+
def execute_in_existing_container_over_ssh(*command, host:)
|
67
|
+
run_over_ssh execute_in_existing_container(*command, interactive: true).join(" "), host: host
|
62
68
|
end
|
63
69
|
|
64
|
-
def
|
65
|
-
run_over_ssh
|
66
|
-
current_container_id,
|
67
|
-
"xargs docker logs -t -n 10 -f 2>&1",
|
68
|
-
(%(grep "#{grep}") if grep)
|
69
|
-
).join(" "), host: host
|
70
|
+
def execute_in_new_container_over_ssh(*command, host:)
|
71
|
+
run_over_ssh execute_in_new_container(*command, interactive: true).join(" "), host: host
|
70
72
|
end
|
71
73
|
|
72
|
-
|
73
|
-
|
74
|
+
|
75
|
+
def current_container_id
|
76
|
+
docker :ps, "-q", *service_filter
|
77
|
+
end
|
78
|
+
|
79
|
+
def container_id_for(container_name:)
|
80
|
+
docker :container, :ls, "-a", "-f", "name=#{container_name}", "-q"
|
74
81
|
end
|
75
82
|
|
76
|
-
def
|
77
|
-
|
83
|
+
def current_running_version
|
84
|
+
# FIXME: Find more graceful way to extract the version from "app-version" than using sed and tail!
|
85
|
+
pipe \
|
86
|
+
docker(:ps, "--filter", "label=service=#{config.service}", "--format", '"{{.Names}}"'),
|
87
|
+
%(sed 's/-/\\n/g'),
|
88
|
+
"tail -n 1"
|
78
89
|
end
|
79
90
|
|
91
|
+
def most_recent_version_from_available_images
|
92
|
+
pipe \
|
93
|
+
docker(:image, :ls, "--format", '"{{.Tag}}"', config.repository),
|
94
|
+
"head -n 1"
|
95
|
+
end
|
96
|
+
|
97
|
+
|
80
98
|
def list_containers
|
81
99
|
docker :container, :ls, "-a", *service_filter
|
82
100
|
end
|
83
101
|
|
102
|
+
def remove_container(version:)
|
103
|
+
pipe \
|
104
|
+
container_id_for(container_name: service_with_version(version)),
|
105
|
+
xargs(docker(:container, :rm))
|
106
|
+
end
|
107
|
+
|
84
108
|
def remove_containers
|
85
109
|
docker :container, :prune, "-f", *service_filter
|
86
110
|
end
|
87
111
|
|
112
|
+
def list_images
|
113
|
+
docker :image, :ls, config.repository
|
114
|
+
end
|
115
|
+
|
88
116
|
def remove_images
|
89
117
|
docker :image, :prune, "-a", "-f", *service_filter
|
90
118
|
end
|
91
119
|
|
120
|
+
|
92
121
|
private
|
122
|
+
def service_with_version(version = nil)
|
123
|
+
if version
|
124
|
+
"#{config.service}-#{version}"
|
125
|
+
else
|
126
|
+
config.service_with_version
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
93
130
|
def service_filter
|
94
131
|
[ "--filter", "label=service=#{config.service}" ]
|
95
132
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "active_support/core_ext/time/conversions"
|
2
|
+
require "mrsk/commands/base"
|
3
|
+
|
4
|
+
class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
5
|
+
def record(line)
|
6
|
+
append \
|
7
|
+
[ :echo, "'#{tags} #{line}'" ],
|
8
|
+
audit_log_file
|
9
|
+
end
|
10
|
+
|
11
|
+
def reveal
|
12
|
+
[ :tail, "-n", 50, audit_log_file ]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def audit_log_file
|
17
|
+
"mrsk-#{config.service}-audit.log"
|
18
|
+
end
|
19
|
+
|
20
|
+
def tags
|
21
|
+
"[#{timestamp}] [#{performer}]"
|
22
|
+
end
|
23
|
+
|
24
|
+
def performer
|
25
|
+
`whoami`.strip
|
26
|
+
end
|
27
|
+
|
28
|
+
def timestamp
|
29
|
+
Time.now.to_fs(:db)
|
30
|
+
end
|
31
|
+
end
|
data/lib/mrsk/commands/base.rb
CHANGED
@@ -28,6 +28,14 @@ module Mrsk::Commands
|
|
28
28
|
combine *commands, by: "|"
|
29
29
|
end
|
30
30
|
|
31
|
+
def append(*commands)
|
32
|
+
combine *commands, by: ">>"
|
33
|
+
end
|
34
|
+
|
35
|
+
def xargs(command)
|
36
|
+
[ :xargs, command ].flatten
|
37
|
+
end
|
38
|
+
|
31
39
|
def docker(*args)
|
32
40
|
args.compact.unshift :docker
|
33
41
|
end
|
data/lib/mrsk/configuration.rb
CHANGED
@@ -9,6 +9,7 @@ class Mrsk::Configuration
|
|
9
9
|
delegate :service, :image, :servers, :env, :labels, :registry, :builder, to: :raw_config, allow_nil: true
|
10
10
|
delegate :argumentize, :argumentize_env_with_secrets, to: Mrsk::Utils
|
11
11
|
|
12
|
+
attr_accessor :version
|
12
13
|
attr_accessor :raw_config
|
13
14
|
|
14
15
|
class << self
|
@@ -52,7 +53,7 @@ class Mrsk::Configuration
|
|
52
53
|
end
|
53
54
|
|
54
55
|
def accessories
|
55
|
-
@accessories ||= raw_config.accessories&.keys&.collect { |name| Mrsk::Configuration::
|
56
|
+
@accessories ||= raw_config.accessories&.keys&.collect { |name| Mrsk::Configuration::Accessory.new(name, config: self) } || []
|
56
57
|
end
|
57
58
|
|
58
59
|
def accessory(name)
|
@@ -73,10 +74,6 @@ class Mrsk::Configuration
|
|
73
74
|
end
|
74
75
|
|
75
76
|
|
76
|
-
def version
|
77
|
-
@version
|
78
|
-
end
|
79
|
-
|
80
77
|
def repository
|
81
78
|
[ raw_config.registry["server"], image ].compact.join("/")
|
82
79
|
end
|
data/lib/mrsk/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mrsk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-02-
|
11
|
+
date: 2023-02-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -92,6 +92,7 @@ files:
|
|
92
92
|
- lib/mrsk/commands.rb
|
93
93
|
- lib/mrsk/commands/accessory.rb
|
94
94
|
- lib/mrsk/commands/app.rb
|
95
|
+
- lib/mrsk/commands/auditor.rb
|
95
96
|
- lib/mrsk/commands/base.rb
|
96
97
|
- lib/mrsk/commands/builder.rb
|
97
98
|
- lib/mrsk/commands/builder/base.rb
|
@@ -127,7 +128,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
127
128
|
- !ruby/object:Gem::Version
|
128
129
|
version: '0'
|
129
130
|
requirements: []
|
130
|
-
rubygems_version: 3.4.
|
131
|
+
rubygems_version: 3.4.6
|
131
132
|
signing_key:
|
132
133
|
specification_version: 4
|
133
134
|
summary: Deploy Rails apps in containers to servers running Docker with zero downtime.
|