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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b7f4333081763cf001d3891931c1615eeb46bc29c2a8754ec863db67259f542
4
- data.tar.gz: 809cc3d35b9c9f445299f3e188cd74b32e504dd7747d7dfcd0b11a698b34ebf9
3
+ metadata.gz: d76643b4635191a3301d23e41f5f4c18ca0db674c44d66b98610df0279c85c66
4
+ data.tar.gz: cd19d1ed503529b6876232d88f5133f782ddfca7f16e13d781120ed269e155d4
5
5
  SHA512:
6
- metadata.gz: 675b6e985621e236c9a3cde0b19d245bb8ac68ce9ae0ceef68c574c3427b1d8255bcd84f4547f9329eca535994a66b6cc6e88470181ca3ef74ea4e06e1a88244
7
- data.tar.gz: 701df3deb670cfcd323a57d0baa5ded62e267657a7ad2198d226c809b89ace6eda46a282146d18123b569db4ef72d996ea487fd157ec8f0f7b639dbb56bf1fa4
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 remote execution and runners
305
+ ### Running commands on servers
306
306
 
307
- If you need to execute commands inside the Rails containers, you can use `mrsk app exec` and `mrsk app runner`. Examples:
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 runner -p 'puts Rails.application.config.time_zone'
350
+ mrsk app exec -p 'bin/rails runner "puts Rails.application.config.time_zone"'
351
351
  UTC
352
352
  ```
353
353
 
354
- ### Running a Rails console
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
 
@@ -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) { execute *accessory.run }
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) { execute *accessory.start }
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) { execute *accessory.stop, raise_on_non_zero_exit: false }
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 accessory host"
86
- option :method, aliases: "-m", default: "exec", desc: "Execution method: [exec] perform inside container / [run] perform in new container / [ssh] perform over ssh"
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
- if runner.exec_over_ssh? || runner.run_over_ssh?
99
- run_locally do
100
- info "Launching command on #{accessory.host}"
101
- exec accessory.send(runner, cmd)
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
- info "Launching command on #{accessory.host}"
106
- execute *accessory.send(runner, cmd)
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) { execute *accessory.remove_container }
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 "remove_container [NAME]", "Remove accessory image from host"
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) { execute *accessory.remove_image }
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) { execute *accessory.remove_service_directory }
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 start them if they've already been booted)"
4
+ desc "boot", "Boot app on servers (or reboot app if already running)"
5
5
  def boot
6
- MRSK.config.roles.each do |role|
7
- on(role.hosts) do |host|
8
- begin
9
- execute *MRSK.app.run(role: role.name)
10
- rescue SSHKit::Command::Failed => e
11
- if e.message =~ /already in use/
12
- error "Container with same version already deployed on #{host}, starting that instead"
13
- execute *MRSK.app.start, host: host
14
- else
15
- raise
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
- if (version = options[:version]).present?
26
- on(MRSK.hosts) { execute *MRSK.app.start(version: version) }
27
- else
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) { execute *MRSK.app.stop, raise_on_non_zero_exit: false }
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 :method, aliases: "-m", default: "exec", desc: "Execution method: [exec] perform inside app container / [run] perform in new container / [ssh] perform over ssh"
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
- runner = \
46
- case options[:method]
47
- when "exec" then "exec"
48
- when "run" then "run_exec"
49
- when "ssh" then "exec_over_ssh"
50
- else raise "Unknown method: #{options[:method]}"
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
- desc "console", "Start Rails Console on primary host (or specific host set by --hosts)"
64
- def console
65
- run_locally do
66
- info "Launching Rails console on #{MRSK.primary_host}"
67
- exec MRSK.app.console(host: MRSK.primary_host)
68
- end
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
- desc "bash", "Start a bash session on primary host (or specific host set by --hosts)"
72
- def bash
73
- run_locally do
74
- info "Launching bash session on #{MRSK.primary_host}"
75
- exec MRSK.app.bash(host: MRSK.primary_host)
76
- end
77
- end
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
- desc "runner [EXPRESSION]", "Execute Rails runner with given expression"
80
- def runner(expression)
81
- on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.exec("bin/rails", "runner", "'#{expression}'")) }
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
- case options[:only]
128
- when "containers"
129
- on(MRSK.hosts) { execute *MRSK.app.remove_containers }
130
- when "images"
131
- on(MRSK.hosts) { execute *MRSK.app.remove_images }
132
- else
133
- on(MRSK.hosts) { execute *MRSK.app.remove_containers }
134
- on(MRSK.hosts) { execute *MRSK.app.remove_images }
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
@@ -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) { execute *MRSK.builder.pull }
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
- invoke "mrsk:cli:app:stop"
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
- invoke "mrsk:cli:app:stop"
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 (that must already be on servers)"
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(version: version)
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
@@ -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) { execute *MRSK.prune.images }
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) { execute *MRSK.prune.containers }
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
@@ -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 { execute *MRSK.registry.login }
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
@@ -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) { execute *MRSK.traefik.start, raise_on_non_zero_exit: false }
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) { execute *MRSK.traefik.stop, raise_on_non_zero_exit: false }
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) { execute *MRSK.traefik.remove_container }
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) { execute *MRSK.traefik.remove_image }
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
@@ -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
- def exec(*command, interactive: false)
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 run_exec(*command, interactive: false)
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 run_over_ssh(command)
67
- super command, host: host
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 exec_over_ssh(*command)
71
- run_over_ssh run_exec(*command, interactive: true).join(" ")
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 bash
75
- exec_over_ssh "bash"
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}"
@@ -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", config.service_with_version,
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(version: config.version)
20
- docker :start, "#{config.service}-#{version}"
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, "xargs docker stop"
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 exec(*command, interactive: false)
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 run_exec(*command, interactive: false)
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 exec_over_ssh(*command, host:)
61
- run_over_ssh run_exec(*command, interactive: true).join(" "), host: host
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 follow_logs(host:, grep: nil)
65
- run_over_ssh pipe(
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
- def console(host:)
73
- exec_over_ssh "bin/rails", "c", host: host
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 bash(host:)
77
- exec_over_ssh "bash", host: host
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
@@ -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
@@ -1,4 +1,4 @@
1
- class Mrsk::Configuration::Assessory
1
+ class Mrsk::Configuration::Accessory
2
2
  delegate :argumentize, :argumentize_env_with_secrets, to: Mrsk::Utils
3
3
 
4
4
  attr_accessor :name, :specifics
@@ -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::Assessory.new(name, config: self) } || []
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
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.1"
3
3
  end
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.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-01 00:00:00.000000000 Z
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.5
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.