mrsk 0.4.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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.