mrsk 0.6.4 → 0.7.0

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: 72714468cef4a41762f0da7a7a1af4b63a09181ae8ce73839b642ff0f91dd7ab
4
- data.tar.gz: 147c1b2ca7bca7b0a5e91489b0cfa5d4efec52a9233978c50e5e4c3b6079b1d0
3
+ metadata.gz: de97b438ef507257a74ec7c5143028946072ac152ff631e979f71788786bc2ba
4
+ data.tar.gz: 35112851498a8a99452850077ad1845f403d198d49b39fb769a746690c48909b
5
5
  SHA512:
6
- metadata.gz: e69b26849eedbc5fe5a17870b7184e8f73bfa2ac9b8c802ceedfab9fd688427445422e1709037548b87d06478aae49e681a5250195b111ff4d6f3560c56db02e
7
- data.tar.gz: dbbda93a73d43820ac11c184b5ce838eb95216f54fae27961fbd3e735066cc01906960da9759818a994b6d9629ef538319577e9c48d3cb1925c94cd09c33b1e4
6
+ metadata.gz: f35616494c2fcf90fb00f2cfd108492147e76d5eb12b91356a520dd0c5b7974bb6336feb55fa8239df407566144b1b1e069d1981e6aad8c159068e4bb4a91d5d
7
+ data.tar.gz: 4e9e55f95b32399c465384351d209bc12709158f39ce05f4ea1b569a3e9f1bd7cb725ce8bd795aa18548230812c0309e0d277392bf942adc62e3a6f436d626d4
data/README.md CHANGED
@@ -20,7 +20,7 @@ env:
20
20
  - RAILS_MASTER_KEY
21
21
  ```
22
22
 
23
- Then edit your `.env` file to add your registry password as `MRSK_REGISTRY_PASSWORD` (and your `RAILS_MASTER_KEY` for production with a Rails app).
23
+ Then edit your `.env` file to add your registry password as `MRSK_REGISTRY_PASSWORD` (and your `RAILS_MASTER_KEY` for production with a Rails app).
24
24
 
25
25
  Now you're ready to deploy to the servers:
26
26
 
@@ -37,9 +37,10 @@ This will:
37
37
  5. Push the image to the registry.
38
38
  6. Pull the image from the registry on the servers.
39
39
  7. Ensure Traefik is running and accepting traffic on port 80.
40
- 8. Stop any containers running a previous versions of the app.
41
- 9. Start a new container with the version of the app that matches the current git version hash.
42
- 10. Prune unused images and stopped containers to ensure servers don't fill up.
40
+ 8. Ensure your app responds with `200 OK` to `GET /up`.
41
+ 9. Stop any containers running a previous versions of the app.
42
+ 10. Start a new container with the version of the app that matches the current git version hash.
43
+ 11. Prune unused images and stopped containers to ensure servers don't fill up.
43
44
 
44
45
  Voila! All the servers are now serving the app on port 80. If you're just running a single server, you're ready to go. If you're running multiple servers, you need to put a load balancer in front of them.
45
46
 
@@ -183,10 +184,10 @@ You can specialize the default Traefik rules by setting labels on the containers
183
184
 
184
185
  ```
185
186
  labels:
186
- traefik.http.routers.hey.rule: '''Host(`app.hey.com`)'''
187
+ traefik.http.routers.hey.rule: Host(\`app.hey.com\`)
187
188
  ```
188
189
 
189
- Note: The extra quotes are needed to ensure the rule is passed in correctly!
190
+ Note: The escaped backticks are needed to ensure the rule is passed in correctly and not treated as command substitution by Bash!
190
191
 
191
192
  This allows you to run multiple applications on the same server sharing the same Traefik instance and port.
192
193
  See https://doc.traefik.io/traefik/routing/routers/#rule for a full list of available routing rules.
@@ -345,6 +346,41 @@ This template can safely be checked into git. Then everyone deploying the app ca
345
346
 
346
347
  If you need separate env variables for different destinations, you can set them with `.env.destination.erb` for the template, which will generate `.env.staging` when run with `mrsk envify -d staging`.
347
348
 
349
+ ### Using audit broadcasts
350
+
351
+ If you'd like to broadcast audits of deploys, rollbacks, etc to a chatroom or elsewhere, you can configure the `audit_broadcast_cmd` setting with the path to a bin file that reads the audit line from STDIN, and then does whatever with it:
352
+
353
+ ```yaml
354
+ audit_broadcast_cmd:
355
+ bin/audit_broadcast
356
+ ```
357
+
358
+ The broadcast command could look something like:
359
+
360
+ ```bash
361
+ #!/usr/bin/env bash
362
+ read
363
+ curl -q -d content="[My app] ${REPLY}" https://3.basecamp.com/XXXXX/integrations/XXXXX/buckets/XXXXX/chats/XXXXX/lines
364
+ ```
365
+
366
+ That'll post a line like follows to a preconfigured chatbot in Basecamp:
367
+
368
+ ```
369
+ [My App] [2023-02-18 11:29:52] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
370
+ ```
371
+
372
+ ### Using custom healthcheck path or port
373
+
374
+ MRSK defaults to checking the health of your application again `/up` on port 3000. You can tailor both with the `healthcheck` setting:
375
+
376
+ ```yaml
377
+ healthcheck:
378
+ path: /healthz
379
+ port: 4000
380
+ ```
381
+
382
+ This will ensure your application is configured with a traefik label for the healthcheck against `/healthz` and that the pre-deploy healthcheck that MRSK performs is done against the same path on port 4000.
383
+
348
384
  ## Commands
349
385
 
350
386
  ### Running commands on servers
@@ -9,9 +9,11 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
9
9
  upload(name)
10
10
 
11
11
  on(accessory.host) do
12
- execute *MRSK.auditor.record("accessory #{name} boot"), verbosity: :debug
12
+ execute *MRSK.auditor.record("Booted #{name} accessory"), verbosity: :debug
13
13
  execute *accessory.run
14
14
  end
15
+
16
+ audit_broadcast "Booted accessory #{name}"
15
17
  end
16
18
  end
17
19
  end
@@ -20,8 +22,6 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
20
22
  def upload(name)
21
23
  with_accessory(name) do |accessory|
22
24
  on(accessory.host) do
23
- execute *MRSK.auditor.record("accessory #{name} upload files"), verbosity: :debug
24
-
25
25
  accessory.files.each do |(local, remote)|
26
26
  accessory.ensure_local_file_present(local)
27
27
 
@@ -37,8 +37,6 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
37
37
  def directories(name)
38
38
  with_accessory(name) do |accessory|
39
39
  on(accessory.host) do
40
- execute *MRSK.auditor.record("accessory #{name} create directories"), verbosity: :debug
41
-
42
40
  accessory.directories.keys.each do |host_path|
43
41
  execute *accessory.make_directory(host_path)
44
42
  end
@@ -59,7 +57,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
59
57
  def start(name)
60
58
  with_accessory(name) do |accessory|
61
59
  on(accessory.host) do
62
- execute *MRSK.auditor.record("accessory #{name} start"), verbosity: :debug
60
+ execute *MRSK.auditor.record("Started #{name} accessory"), verbosity: :debug
63
61
  execute *accessory.start
64
62
  end
65
63
  end
@@ -69,7 +67,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
69
67
  def stop(name)
70
68
  with_accessory(name) do |accessory|
71
69
  on(accessory.host) do
72
- execute *MRSK.auditor.record("accessory #{name} stop"), verbosity: :debug
70
+ execute *MRSK.auditor.record("Stopped #{name} accessory"), verbosity: :debug
73
71
  execute *accessory.stop, raise_on_non_zero_exit: false
74
72
  end
75
73
  end
@@ -111,14 +109,14 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
111
109
  when options[:reuse]
112
110
  say "Launching command from existing container...", :magenta
113
111
  on(accessory.host) do
114
- execute *MRSK.auditor.record("accessory #{name} cmd '#{cmd}'"), verbosity: :debug
112
+ execute *MRSK.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
115
113
  capture_with_info(*accessory.execute_in_existing_container(cmd))
116
114
  end
117
115
 
118
116
  else
119
117
  say "Launching command from new container...", :magenta
120
118
  on(accessory.host) do
121
- execute *MRSK.auditor.record("accessory #{name} cmd '#{cmd}'"), verbosity: :debug
119
+ execute *MRSK.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
122
120
  capture_with_info(*accessory.execute_in_new_container(cmd))
123
121
  end
124
122
  end
@@ -169,7 +167,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
169
167
  def remove_container(name)
170
168
  with_accessory(name) do |accessory|
171
169
  on(accessory.host) do
172
- execute *MRSK.auditor.record("accessory #{name} remove container"), verbosity: :debug
170
+ execute *MRSK.auditor.record("Remove #{name} accessory container"), verbosity: :debug
173
171
  execute *accessory.remove_container
174
172
  end
175
173
  end
@@ -179,7 +177,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
179
177
  def remove_image(name)
180
178
  with_accessory(name) do |accessory|
181
179
  on(accessory.host) do
182
- execute *MRSK.auditor.record("accessory #{name} remove image"), verbosity: :debug
180
+ execute *MRSK.auditor.record("Removed #{name} accessory image"), verbosity: :debug
183
181
  execute *accessory.remove_image
184
182
  end
185
183
  end
@@ -189,7 +187,6 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
189
187
  def remove_service_directory(name)
190
188
  with_accessory(name) do |accessory|
191
189
  on(accessory.host) do
192
- execute *MRSK.auditor.record("accessory #{name} remove service directory"), verbosity: :debug
193
190
  execute *accessory.remove_service_directory
194
191
  end
195
192
  end
data/lib/mrsk/cli/app.rb CHANGED
@@ -7,7 +7,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
7
7
 
8
8
  MRSK.config.roles.each do |role|
9
9
  on(role.hosts) do |host|
10
- execute *MRSK.auditor.record("app boot version #{version}"), verbosity: :debug
10
+ execute *MRSK.auditor.record("Booted app version #{version}"), verbosity: :debug
11
11
 
12
12
  begin
13
13
  execute *MRSK.app.stop, raise_on_non_zero_exit: false
@@ -15,7 +15,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
15
15
  rescue SSHKit::Command::Failed => e
16
16
  if e.message =~ /already in use/
17
17
  error "Rebooting container with same version already deployed on #{host}"
18
- execute *MRSK.auditor.record("app rebooted with version #{version}"), verbosity: :debug
18
+ execute *MRSK.auditor.record("Rebooted app version #{version}"), verbosity: :debug
19
19
 
20
20
  execute *MRSK.app.remove_container(version: version)
21
21
  execute *MRSK.app.run(role: role.name)
@@ -31,7 +31,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
31
31
  desc "start", "Start existing app on servers (use --version=<git-hash> to designate specific version)"
32
32
  def start
33
33
  on(MRSK.hosts) do
34
- execute *MRSK.auditor.record("app start version #{MRSK.version}"), verbosity: :debug
34
+ execute *MRSK.auditor.record("Started app version #{MRSK.version}"), verbosity: :debug
35
35
  execute *MRSK.app.start, raise_on_non_zero_exit: false
36
36
  end
37
37
  end
@@ -39,7 +39,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
39
39
  desc "stop", "Stop app on servers"
40
40
  def stop
41
41
  on(MRSK.hosts) do
42
- execute *MRSK.auditor.record("app stop"), verbosity: :debug
42
+ execute *MRSK.auditor.record("Stopped app"), verbosity: :debug
43
43
  execute *MRSK.app.stop, raise_on_non_zero_exit: false
44
44
  end
45
45
  end
@@ -74,7 +74,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
74
74
  say "Launching command with version #{version} from existing container...", :magenta
75
75
 
76
76
  on(MRSK.hosts) do |host|
77
- execute *MRSK.auditor.record("app cmd '#{cmd}' with version #{version}"), verbosity: :debug
77
+ execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
78
78
  puts_by_host host, capture_with_info(*MRSK.app.execute_in_existing_container(cmd))
79
79
  end
80
80
  end
@@ -84,7 +84,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
84
84
  using_version(options[:version] || most_recent_version_available) do |version|
85
85
  say "Launching command with version #{version} from new container...", :magenta
86
86
  on(MRSK.hosts) do |host|
87
- execute *MRSK.auditor.record("app cmd '#{cmd}' with version #{version}"), verbosity: :debug
87
+ execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
88
88
  puts_by_host host, capture_with_info(*MRSK.app.execute_in_new_container(cmd))
89
89
  end
90
90
  end
@@ -140,7 +140,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
140
140
  desc "remove_container [VERSION]", "Remove app container with given version from servers"
141
141
  def remove_container(version)
142
142
  on(MRSK.hosts) do
143
- execute *MRSK.auditor.record("app remove container #{version}"), verbosity: :debug
143
+ execute *MRSK.auditor.record("Removed app container with version #{version}"), verbosity: :debug
144
144
  execute *MRSK.app.remove_container(version: version)
145
145
  end
146
146
  end
@@ -148,7 +148,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
148
148
  desc "remove_containers", "Remove all app containers from servers"
149
149
  def remove_containers
150
150
  on(MRSK.hosts) do
151
- execute *MRSK.auditor.record("app remove containers"), verbosity: :debug
151
+ execute *MRSK.auditor.record("Removed all app containers"), verbosity: :debug
152
152
  execute *MRSK.app.remove_containers
153
153
  end
154
154
  end
@@ -156,7 +156,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
156
156
  desc "remove_images", "Remove all app images from servers"
157
157
  def remove_images
158
158
  on(MRSK.hosts) do
159
- execute *MRSK.auditor.record("app remove images"), verbosity: :debug
159
+ execute *MRSK.auditor.record("Removed all app images"), verbosity: :debug
160
160
  execute *MRSK.app.remove_images
161
161
  end
162
162
  end
data/lib/mrsk/cli/base.rb CHANGED
@@ -59,9 +59,14 @@ module Mrsk::Cli
59
59
  def print_runtime
60
60
  started_at = Time.now
61
61
  yield
62
+ return Time.now - started_at
62
63
  ensure
63
64
  runtime = Time.now - started_at
64
65
  puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
65
66
  end
67
+
68
+ def audit_broadcast(line)
69
+ run_locally { execute *MRSK.auditor.broadcast(line), verbosity: :debug }
70
+ end
66
71
  end
67
72
  end
@@ -29,7 +29,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
29
29
  desc "pull", "Pull app image from the registry onto servers"
30
30
  def pull
31
31
  on(MRSK.hosts) do
32
- execute *MRSK.auditor.record("build pull image #{MRSK.version}"), verbosity: :debug
32
+ execute *MRSK.auditor.record("Pulled image with version #{MRSK.version}"), verbosity: :debug
33
33
  execute *MRSK.builder.pull
34
34
  end
35
35
  end
@@ -0,0 +1,29 @@
1
+ class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
2
+ desc "perform", "Health check the current version of the app"
3
+ def perform
4
+ on(MRSK.primary_host) do
5
+ begin
6
+ execute *MRSK.healthcheck.run
7
+
8
+ target = "Health check against #{MRSK.config.healthcheck["path"]}"
9
+
10
+ if capture_with_info(*MRSK.healthcheck.curl) == "200"
11
+ info "#{target} succeeded with 200 OK!"
12
+ else
13
+ # Catches 1xx, 2xx, 3xx
14
+ raise SSHKit::Command::Failed, "#{target} failed to return 200 OK!"
15
+ end
16
+ rescue SSHKit::Command::Failed => e
17
+ if e.message =~ /curl/
18
+ # Catches 4xx, 5xx
19
+ raise SSHKit::Command::Failed, "#{target} failed to return 200 OK!"
20
+ else
21
+ raise
22
+ end
23
+ ensure
24
+ execute *MRSK.healthcheck.stop, raise_on_non_zero_exit: false
25
+ execute *MRSK.healthcheck.remove, raise_on_non_zero_exit: false
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/mrsk/cli/main.rb CHANGED
@@ -10,7 +10,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
10
10
 
11
11
  desc "deploy", "Deploy the app to servers"
12
12
  def deploy
13
- print_runtime do
13
+ runtime = print_runtime do
14
14
  say "Ensure Docker is installed...", :magenta
15
15
  invoke "mrsk:cli:server:bootstrap"
16
16
 
@@ -23,33 +23,48 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
23
23
  say "Ensure Traefik is running...", :magenta
24
24
  invoke "mrsk:cli:traefik:boot"
25
25
 
26
+ say "Ensure app can pass healthcheck...", :magenta
27
+ invoke "mrsk:cli:healthcheck:perform"
28
+
26
29
  invoke "mrsk:cli:app:boot"
27
30
 
28
31
  say "Prune old containers and images...", :magenta
29
32
  invoke "mrsk:cli:prune:all"
30
33
  end
34
+
35
+ audit_broadcast "Deployed app in #{runtime.to_i} seconds"
31
36
  end
32
37
 
33
38
  desc "redeploy", "Deploy new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)"
34
39
  def redeploy
35
- print_runtime do
40
+ runtime = print_runtime do
36
41
  say "Build and push app image...", :magenta
37
42
  invoke "mrsk:cli:build:deliver"
38
43
 
44
+ say "Ensure app can pass healthcheck...", :magenta
45
+ invoke "mrsk:cli:healthcheck:perform"
46
+
39
47
  invoke "mrsk:cli:app:boot"
40
48
  end
49
+
50
+ audit_broadcast "Redeployed app in #{runtime.to_i} seconds"
41
51
  end
42
52
 
43
53
  desc "rollback [VERSION]", "Rollback the app to VERSION"
44
54
  def rollback(version)
45
55
  MRSK.version = version
46
56
 
47
- cli = self
57
+ if container_name_available?(MRSK.config.service_with_version)
58
+ say "Stop current version, then start version #{version}...", :magenta
48
59
 
49
- cli.say "Stop current version, then start version #{version}...", :magenta
50
- on(MRSK.hosts) do
51
- execute *MRSK.app.stop, raise_on_non_zero_exit: false
52
- execute *MRSK.app.start
60
+ on(MRSK.hosts) do |host|
61
+ execute *MRSK.app.stop, raise_on_non_zero_exit: false
62
+ execute *MRSK.app.start
63
+ end
64
+
65
+ audit_broadcast "Rolled back app to version #{version}"
66
+ else
67
+ say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
53
68
  end
54
69
  end
55
70
 
@@ -138,6 +153,9 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
138
153
  desc "build", "Build the application image"
139
154
  subcommand "build", Mrsk::Cli::Build
140
155
 
156
+ desc "healthcheck", "Healthcheck the application"
157
+ subcommand "healthcheck", Mrsk::Cli::Healthcheck
158
+
141
159
  desc "prune", "Prune old application images and containers"
142
160
  subcommand "prune", Mrsk::Cli::Prune
143
161
 
@@ -149,4 +167,11 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
149
167
 
150
168
  desc "traefik", "Manage the Traefik load balancer"
151
169
  subcommand "traefik", Mrsk::Cli::Traefik
170
+
171
+ private
172
+ def container_name_available?(container_name, host: MRSK.primary_host)
173
+ container_names = nil
174
+ on(host) { container_names = capture_with_info(*MRSK.app.list_container_names).split("\n") }
175
+ Array(container_names).include?(container_name)
176
+ end
152
177
  end
@@ -5,10 +5,10 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
5
5
  invoke :images
6
6
  end
7
7
 
8
- desc "images", "Prune unused images older than 30 days"
8
+ desc "images", "Prune unused images older than 7 days"
9
9
  def images
10
10
  on(MRSK.hosts) do
11
- execute *MRSK.auditor.record("prune images"), verbosity: :debug
11
+ execute *MRSK.auditor.record("Pruned images"), verbosity: :debug
12
12
  execute *MRSK.prune.images
13
13
  end
14
14
  end
@@ -16,7 +16,7 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
16
16
  desc "containers", "Prune stopped containers for the service older than 3 days"
17
17
  def containers
18
18
  on(MRSK.hosts) do
19
- execute *MRSK.auditor.record("prune containers"), verbosity: :debug
19
+ execute *MRSK.auditor.record("Pruned containers"), verbosity: :debug
20
20
  execute *MRSK.prune.containers
21
21
  end
22
22
  end
@@ -14,7 +14,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
14
14
  desc "start", "Start existing Traefik on servers"
15
15
  def start
16
16
  on(MRSK.traefik_hosts) do
17
- execute *MRSK.auditor.record("traefik start"), verbosity: :debug
17
+ execute *MRSK.auditor.record("Started traefik"), verbosity: :debug
18
18
  execute *MRSK.traefik.start, raise_on_non_zero_exit: false
19
19
  end
20
20
  end
@@ -22,7 +22,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
22
22
  desc "stop", "Stop Traefik on servers"
23
23
  def stop
24
24
  on(MRSK.traefik_hosts) do
25
- execute *MRSK.auditor.record("traefik stop"), verbosity: :debug
25
+ execute *MRSK.auditor.record("Stopped traefik"), verbosity: :debug
26
26
  execute *MRSK.traefik.stop, raise_on_non_zero_exit: false
27
27
  end
28
28
  end
@@ -72,7 +72,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
72
72
  desc "remove_container", "Remove Traefik container from servers"
73
73
  def remove_container
74
74
  on(MRSK.traefik_hosts) do
75
- execute *MRSK.auditor.record("traefik remove container"), verbosity: :debug
75
+ execute *MRSK.auditor.record("Removed traefik container"), verbosity: :debug
76
76
  execute *MRSK.traefik.remove_container
77
77
  end
78
78
  end
@@ -80,7 +80,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
80
80
  desc "remove_container", "Remove Traefik image from servers"
81
81
  def remove_image
82
82
  on(MRSK.traefik_hosts) do
83
- execute *MRSK.auditor.record("traefik remove image"), verbosity: :debug
83
+ execute *MRSK.auditor.record("Removed traefik image"), verbosity: :debug
84
84
  execute *MRSK.traefik.remove_image
85
85
  end
86
86
  end
@@ -73,6 +73,10 @@ class Mrsk::Commander
73
73
  @auditor ||= Mrsk::Commands::Auditor.new(config)
74
74
  end
75
75
 
76
+ def healthcheck
77
+ @healthcheck ||= Mrsk::Commands::Healthcheck.new(config)
78
+ end
79
+
76
80
 
77
81
  def with_verbosity(level)
78
82
  old_level = self.verbosity
@@ -75,10 +75,6 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
75
75
  docker :ps, "-q", *service_filter
76
76
  end
77
77
 
78
- def container_id_for(container_name:)
79
- docker :container, :ls, "-a", "-f", "name=#{container_name}", "-q"
80
- end
81
-
82
78
  def current_running_version
83
79
  # FIXME: Find more graceful way to extract the version from "app-version" than using sed and tail!
84
80
  pipe \
@@ -93,11 +89,21 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
93
89
  "head -n 1"
94
90
  end
95
91
 
92
+ def all_versions_from_available_containers
93
+ pipe \
94
+ docker(:image, :ls, "--format", '"{{.Tag}}"', config.repository),
95
+ "head -n 1"
96
+ end
97
+
96
98
 
97
99
  def list_containers
98
100
  docker :container, :ls, "-a", *service_filter
99
101
  end
100
102
 
103
+ def list_container_names
104
+ [ *list_containers, "--format", "'{{ .Names }}'" ]
105
+ end
106
+
101
107
  def remove_container(version:)
102
108
  pipe \
103
109
  container_id_for(container_name: service_with_version(version)),
@@ -1,12 +1,22 @@
1
1
  require "active_support/core_ext/time/conversions"
2
2
 
3
3
  class Mrsk::Commands::Auditor < Mrsk::Commands::Base
4
+ # Runs remotely
4
5
  def record(line)
5
6
  append \
6
7
  [ :echo, tagged_line(line) ],
7
8
  audit_log_file
8
9
  end
9
10
 
11
+ # Runs locally
12
+ def broadcast(line)
13
+ if broadcast_cmd = config.audit_broadcast_cmd
14
+ pipe \
15
+ [ :echo, tagged_line(line) ],
16
+ broadcast_cmd
17
+ end
18
+ end
19
+
10
20
  def reveal
11
21
  [ :tail, "-n", 50, audit_log_file ]
12
22
  end
@@ -21,14 +31,14 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
21
31
  end
22
32
 
23
33
  def tags
24
- "[#{timestamp}] [#{performer}]"
34
+ "[#{recorded_at}] [#{performer}]"
25
35
  end
26
36
 
27
37
  def performer
28
- `whoami`.strip
38
+ @performer ||= `whoami`.strip
29
39
  end
30
40
 
31
- def timestamp
41
+ def recorded_at
32
42
  Time.now.to_fs(:db)
33
43
  end
34
44
  end
@@ -17,6 +17,10 @@ module Mrsk::Commands
17
17
  end
18
18
  end
19
19
 
20
+ def container_id_for(container_name:)
21
+ docker :container, :ls, "-a", "-f", "name=#{container_name}", "-q"
22
+ end
23
+
20
24
  private
21
25
  def combine(*commands, by: "&&")
22
26
  commands
@@ -0,0 +1,46 @@
1
+ class Mrsk::Commands::Healthcheck < Mrsk::Commands::Base
2
+ EXPOSED_PORT = 3999
3
+
4
+ def run
5
+ web = config.role(:web)
6
+
7
+ docker :run,
8
+ "-d",
9
+ "--name", container_name_with_version,
10
+ "-p", "#{EXPOSED_PORT}:#{config.healthcheck["port"]}",
11
+ "--label", "service=#{container_name}",
12
+ *web.env_args,
13
+ *config.volume_args,
14
+ config.absolute_image,
15
+ web.cmd
16
+ end
17
+
18
+ def curl
19
+ [ :curl, "--silent", "--output", "/dev/null", "--write-out", "'%{http_code}'", health_url ]
20
+ end
21
+
22
+ def stop
23
+ pipe \
24
+ container_id_for(container_name: container_name),
25
+ xargs(docker(:stop))
26
+ end
27
+
28
+ def remove
29
+ pipe \
30
+ container_id_for(container_name: container_name),
31
+ xargs(docker(:container, :rm))
32
+ end
33
+
34
+ private
35
+ def container_name
36
+ "healthcheck-#{config.service}"
37
+ end
38
+
39
+ def container_name_with_version
40
+ "healthcheck-#{config.service_with_version}"
41
+ end
42
+
43
+ def health_url
44
+ "http://localhost:#{EXPOSED_PORT}#{config.healthcheck["path"]}"
45
+ end
46
+ end
@@ -2,14 +2,11 @@ require "active_support/duration"
2
2
  require "active_support/core_ext/numeric/time"
3
3
 
4
4
  class Mrsk::Commands::Prune < Mrsk::Commands::Base
5
- PRUNE_IMAGES_AFTER = 7.days.in_hours.to_i
6
- PRUNE_CONTAINERS_AFTER = 3.days.in_hours.to_i
7
-
8
- def images
9
- docker :image, :prune, "--all", "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{PRUNE_IMAGES_AFTER}h"
5
+ def images(until_hours: 7.days.in_hours.to_i)
6
+ docker :image, :prune, "--all", "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{until_hours}h"
10
7
  end
11
8
 
12
- def containers
13
- docker :container, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{PRUNE_CONTAINERS_AFTER}h"
9
+ def containers(until_hours: 3.days.in_hours.to_i)
10
+ docker :container, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{until_hours}h"
14
11
  end
15
12
  end
@@ -59,7 +59,7 @@ class Mrsk::Configuration::Role
59
59
  if running_traefik?
60
60
  {
61
61
  "traefik.http.routers.#{config.service}.rule" => "'PathPrefix(`/`)'",
62
- "traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => "/up",
62
+ "traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
63
63
  "traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
64
64
  "traefik.http.middlewares.#{config.service}.retry.attempts" => "3",
65
65
  "traefik.http.middlewares.#{config.service}.retry.initialinterval" => "500ms"
@@ -107,6 +107,7 @@ class Mrsk::Configuration
107
107
  end
108
108
  end
109
109
 
110
+
110
111
  def ssh_user
111
112
  if raw_config.ssh.present?
112
113
  raw_config.ssh["user"] || "root"
@@ -127,6 +128,15 @@ class Mrsk::Configuration
127
128
  end
128
129
 
129
130
 
131
+ def audit_broadcast_cmd
132
+ raw_config.audit_broadcast_cmd
133
+ end
134
+
135
+ def healthcheck
136
+ { "path" => "/up", "port" => 3000 }.merge(raw_config.healthcheck || {})
137
+ end
138
+
139
+
130
140
  def valid?
131
141
  ensure_required_keys_present && ensure_env_available
132
142
  end
@@ -145,7 +155,8 @@ class Mrsk::Configuration
145
155
  volume_args: volume_args,
146
156
  ssh_options: ssh_options,
147
157
  builder: raw_config.builder,
148
- accessories: raw_config.accessories
158
+ accessories: raw_config.accessories,
159
+ healthcheck: healthcheck
149
160
  }.compact
150
161
  end
151
162
 
data/lib/mrsk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.6.4"
2
+ VERSION = "0.7.0"
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.6.4
4
+ version: 0.7.0
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-15 00:00:00.000000000 Z
11
+ date: 2023-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -96,6 +96,7 @@ files:
96
96
  - lib/mrsk/cli/app.rb
97
97
  - lib/mrsk/cli/base.rb
98
98
  - lib/mrsk/cli/build.rb
99
+ - lib/mrsk/cli/healthcheck.rb
99
100
  - lib/mrsk/cli/main.rb
100
101
  - lib/mrsk/cli/prune.rb
101
102
  - lib/mrsk/cli/registry.rb
@@ -115,6 +116,7 @@ files:
115
116
  - lib/mrsk/commands/builder/multiarch/remote.rb
116
117
  - lib/mrsk/commands/builder/native.rb
117
118
  - lib/mrsk/commands/builder/native/remote.rb
119
+ - lib/mrsk/commands/healthcheck.rb
118
120
  - lib/mrsk/commands/prune.rb
119
121
  - lib/mrsk/commands/registry.rb
120
122
  - lib/mrsk/commands/traefik.rb