mrsk 0.13.1 → 0.14.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: 4f798abc56a655b00c23b0967edc93b3c420b71b50c92e28b986b35c6934ad91
4
- data.tar.gz: b0606bf3e0874b765fbe6c5b17d941b326fc2da802a5fdf04f456547a2542fc2
3
+ metadata.gz: 39d645ed2ca51ebf7edeaca7502b9b9d83456ad4fd11d5b905903de2ee76f189
4
+ data.tar.gz: c8fe80b16c14e89fd333224b887968620a88741c32a8b7cb4ee1cf9c37c1dcd5
5
5
  SHA512:
6
- metadata.gz: 8528839c8f62dbfb766c670d30915f7ea8680a8f4c4b4bfc7bfffb3227a017c856b3dfbfb4df6fe6ab2c78371cd73e6d53f51ba01cdf998aeacbbf64cca73fdc
7
- data.tar.gz: 47fd8f783ad74b2c4c518854f91a9b3ffb8db9d3f7fb929ee05e4e79f8995f3c831077dcbda116ea179201cbf8e8180af3b5cab8c5970acc9a4e81c31b188baa
6
+ metadata.gz: fa4264e442bc470b02ebd946b4667535b9e5e8795cf9cb22c311693f381a0a9671c67d16dd6f1ae6391e48b8e7b2be51fc49671bccd2610e3ec98afd75002a12
7
+ data.tar.gz: 408f3500ff87847217b08f21c86a189b77e73f54d3b1a407a7e16a9f73fea20a883233cf8970f9f5fb6b6ebdcd69d3a47ec5006e42b7190cffd41c1c5b9bfb60
data/README.md CHANGED
@@ -67,7 +67,7 @@ Voila! All the servers are now serving the app on port 80. If you're just runnin
67
67
 
68
68
  In the past decade+, there's been an explosion in commercial offerings that make deploying web apps easier. Heroku kicked it off with an incredible offering that stayed ahead of the competition seemingly forever. These days we have excellent alternatives like Fly.io and Render. And hosted Kubernetes is making things easier too on AWS, GCP, Digital Ocean, and elsewhere. But these are all offerings that have you renting computers in the cloud at a premium. If you want to run on your own hardware, or even just have a clear migration path to do so in the future, you need to carefully consider how locked in you get to these commercial platforms. Preferably before the bills swallow your business whole!
69
69
 
70
- MRSK seeks to bring the advance in ergonomics pioneered by these commercial offerings to deploying web apps anywhere. Whether that's low-cost cloud options without the managed-service markup from the likes of Digital Ocean, Hetzner, OVH, etc, or it's your own colocated bare metal. To MRSK, it's all the same. Feed the config file a list of IP addresses with vanilla Ubuntu servers that have seen no prep beyond an added SSH key, and you'll be running in literally minutes.
70
+ MRSK seeks to bring the advance in ergonomics pioneered by these commercial offerings to deploying web apps anywhere. Whether that's low-cost cloud options without the managed-service markup from the likes of Digital Ocean, Hetzner, OVH, etc., or it's your own colocated bare metal. To MRSK, it's all the same. Feed the config file a list of IP addresses with vanilla Ubuntu servers that have seen no prep beyond an added SSH key, and you'll be running in literally minutes.
71
71
 
72
72
  This approach gives you enormous portability. You can have your web app deployed on several clouds at ease like this. Or you can buy the baseline with your own hardware, then deploy to a cloud before a big seasonal spike to get more capacity. When you're not locked into a single provider from a tooling perspective, there are a lot of compelling options available.
73
73
 
@@ -380,6 +380,16 @@ servers:
380
380
 
381
381
  That'll start the job containers with `docker run ... --cap-add --cpu-count 4 ...`.
382
382
 
383
+ ### Setting a minimum version
384
+
385
+ You can set the minimum MRSK version with:
386
+
387
+ ```yaml
388
+ minimum_version: 0.13.3
389
+ ```
390
+
391
+ Note: versions <= 0.13.2 will ignore this setting.
392
+
383
393
  ### Configuring logging
384
394
 
385
395
  You can configure the logging driver and options passed to Docker using `logging`:
@@ -463,6 +473,37 @@ builder:
463
473
  context: ".."
464
474
  ```
465
475
 
476
+ ### Using multistage builder cache
477
+
478
+ Docker multistage build cache can singlehandedly speed up your builds by a lot. Currently MRSK only supports using the GHA cache or the Registry cache:
479
+
480
+ ```yaml
481
+ # Using GHA cache
482
+ builder:
483
+ cache:
484
+ type: gha
485
+
486
+ # Using Registry cache
487
+ builder:
488
+ cache:
489
+ type: registry
490
+
491
+ # Using Registry cache with different cache image
492
+ builder:
493
+ cache:
494
+ type: registry
495
+ # default image name is <image>-build-cache
496
+ image: application-cache-image
497
+
498
+ # Using Registry cache with additinonal cache-to options
499
+ builder:
500
+ cache:
501
+ type: registry
502
+ options: mode=max,image-manifest=true,oci-mediatypes=true
503
+ ```
504
+
505
+ For further insights into build cache optimization, check out documentation on Docker's official website: https://docs.docker.com/build/cache/.
506
+
466
507
  ### Using build secrets for new images
467
508
 
468
509
  Some images need a secret passed in during build time, like a GITHUB_TOKEN, to give access to private gem repositories. This can be done by having the secret in ENV, then referencing it in the builder configuration:
@@ -670,7 +711,7 @@ This assumes the Cron settings are stored in `config/crontab`.
670
711
 
671
712
  ### Healthcheck
672
713
 
673
- MRSK uses Docker healtchecks to check the health of your application during deployment. Traefik uses this same healthcheck status to determine when a container is ready to receive traffic.
714
+ MRSK uses Docker healthchecks to check the health of your application during deployment. Traefik uses this same healthcheck status to determine when a container is ready to receive traffic.
674
715
 
675
716
  The healthcheck defaults to testing the HTTP response to the path `/up` on port 3000, up to 7 times. You can tailor this behaviour with the `healthcheck` setting:
676
717
 
@@ -840,7 +881,7 @@ Message: Automatic deploy lock
840
881
  You can also manually acquire and release the lock
841
882
 
842
883
  ```
843
- mrsk lock acquire -m "Doing maintanence"
884
+ mrsk lock acquire -m "Doing maintenance"
844
885
  ```
845
886
 
846
887
  ```
@@ -882,11 +923,13 @@ firing a JSON webhook. These variables include:
882
923
  - `MRSK_PERFORMER` - the local user performing the command (from `whoami`)
883
924
  - `MRSK_SERVICE_VERSION` - an abbreviated service and version for use in messages, e.g. app@150b24f
884
925
  - `MRSK_VERSION` - an full version being deployed
885
- - `MRSK_DESTINATION` - optional: destination, e.g. "staging"
886
926
  - `MRSK_HOSTS` - a comma separated list of the hosts targeted by the command
927
+ - `MRSK_COMMAND` - The command we are running
928
+ - `MRSK_SUBCOMMAND` - optional: The subcommand we are running
929
+ - `MRSK_DESTINATION` - optional: destination, e.g. "staging"
887
930
  - `MRSK_ROLE` - optional: role targeted, e.g. "web"
888
931
 
889
- There are three hooks:
932
+ There are four hooks:
890
933
 
891
934
  1. pre-connect
892
935
  Called before taking the deploy lock. For checks that need to run before connecting to remote hosts - e.g. DNS warming.
@@ -894,6 +937,9 @@ Called before taking the deploy lock. For checks that need to run before connect
894
937
  2. pre-build
895
938
  Used for pre-build checks - e.g. there are no uncommitted changes or that CI has passed.
896
939
 
940
+ 3. pre-deploy
941
+ For final checks before deploying, e.g. checking CI completed
942
+
897
943
  3. post-deploy - run after a deploy, redeploy or rollback
898
944
 
899
945
  This hook is also passed a `MRSK_RUNTIME` env variable.
@@ -1,7 +1,7 @@
1
1
  class Mrsk::Cli::Accessory < Mrsk::Cli::Base
2
2
  desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
3
3
  def boot(name)
4
- with_lock do
4
+ mutating do
5
5
  if name == "all"
6
6
  MRSK.accessory_names.each { |accessory_name| boot(accessory_name) }
7
7
  else
@@ -21,7 +21,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
21
21
 
22
22
  desc "upload [NAME]", "Upload accessory files to host", hide: true
23
23
  def upload(name)
24
- with_lock do
24
+ mutating do
25
25
  with_accessory(name) do |accessory|
26
26
  on(accessory.hosts) do
27
27
  accessory.files.each do |(local, remote)|
@@ -38,7 +38,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
38
38
 
39
39
  desc "directories [NAME]", "Create accessory directories on host", hide: true
40
40
  def directories(name)
41
- with_lock do
41
+ mutating do
42
42
  with_accessory(name) do |accessory|
43
43
  on(accessory.hosts) do
44
44
  accessory.directories.keys.each do |host_path|
@@ -51,7 +51,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
51
51
 
52
52
  desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container)"
53
53
  def reboot(name)
54
- with_lock do
54
+ mutating do
55
55
  with_accessory(name) do |accessory|
56
56
  stop(name)
57
57
  remove_container(name)
@@ -62,7 +62,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
62
62
 
63
63
  desc "start [NAME]", "Start existing accessory container on host"
64
64
  def start(name)
65
- with_lock do
65
+ mutating do
66
66
  with_accessory(name) do |accessory|
67
67
  on(accessory.hosts) do
68
68
  execute *MRSK.auditor.record("Started #{name} accessory"), verbosity: :debug
@@ -74,7 +74,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
74
74
 
75
75
  desc "stop [NAME]", "Stop existing accessory container on host"
76
76
  def stop(name)
77
- with_lock do
77
+ mutating do
78
78
  with_accessory(name) do |accessory|
79
79
  on(accessory.hosts) do
80
80
  execute *MRSK.auditor.record("Stopped #{name} accessory"), verbosity: :debug
@@ -86,7 +86,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
86
86
 
87
87
  desc "restart [NAME]", "Restart existing accessory container on host"
88
88
  def restart(name)
89
- with_lock do
89
+ mutating do
90
90
  with_accessory(name) do
91
91
  stop(name)
92
92
  start(name)
@@ -165,7 +165,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
165
165
  desc "remove [NAME]", "Remove accessory container, image and data directory from host (use NAME=all to remove all accessories)"
166
166
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
167
167
  def remove(name)
168
- with_lock do
168
+ mutating do
169
169
  if name == "all"
170
170
  MRSK.accessory_names.each { |accessory_name| remove(accessory_name) }
171
171
  else
@@ -183,7 +183,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
183
183
 
184
184
  desc "remove_container [NAME]", "Remove accessory container from host", hide: true
185
185
  def remove_container(name)
186
- with_lock do
186
+ mutating do
187
187
  with_accessory(name) do |accessory|
188
188
  on(accessory.hosts) do
189
189
  execute *MRSK.auditor.record("Remove #{name} accessory container"), verbosity: :debug
@@ -195,7 +195,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
195
195
 
196
196
  desc "remove_image [NAME]", "Remove accessory image from host", hide: true
197
197
  def remove_image(name)
198
- with_lock do
198
+ mutating do
199
199
  with_accessory(name) do |accessory|
200
200
  on(accessory.hosts) do
201
201
  execute *MRSK.auditor.record("Removed #{name} accessory image"), verbosity: :debug
@@ -207,7 +207,7 @@ class Mrsk::Cli::Accessory < Mrsk::Cli::Base
207
207
 
208
208
  desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
209
209
  def remove_service_directory(name)
210
- with_lock do
210
+ mutating do
211
211
  with_accessory(name) do |accessory|
212
212
  on(accessory.hosts) do
213
213
  execute *accessory.remove_service_directory
data/lib/mrsk/cli/app.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  class Mrsk::Cli::App < Mrsk::Cli::Base
2
2
  desc "boot", "Boot app on servers (or reboot app if already running)"
3
3
  def boot
4
- with_lock do
4
+ mutating do
5
5
  hold_lock_on_error do
6
6
  say "Get most recent version available as an image...", :magenta unless options[:version]
7
7
  using_version(version_or_latest) do |version|
@@ -29,7 +29,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
29
29
  execute *auditor.record("Booted app version #{version}"), verbosity: :debug
30
30
 
31
31
  old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
32
- execute *app.start_or_run
32
+ execute *app.start_or_run(hostname: "#{host}-#{SecureRandom.hex(6)}")
33
33
 
34
34
  Mrsk::Utils::HealthcheckPoller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
35
35
 
@@ -43,7 +43,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
43
43
 
44
44
  desc "start", "Start existing app container on servers"
45
45
  def start
46
- with_lock do
46
+ mutating do
47
47
  on(MRSK.hosts) do |host|
48
48
  roles = MRSK.roles_on(host)
49
49
 
@@ -57,7 +57,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
57
57
 
58
58
  desc "stop", "Stop app container on servers"
59
59
  def stop
60
- with_lock do
60
+ mutating do
61
61
  on(MRSK.hosts) do |host|
62
62
  roles = MRSK.roles_on(host)
63
63
 
@@ -135,7 +135,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
135
135
  desc "stale_containers", "Detect app stale containers"
136
136
  option :stop, aliases: "-s", type: :boolean, default: false, desc: "Stop the stale containers found"
137
137
  def stale_containers
138
- with_lock do
138
+ mutating do
139
139
  stop = options[:stop]
140
140
 
141
141
  cli = self
@@ -202,7 +202,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
202
202
 
203
203
  desc "remove", "Remove app containers and images from servers"
204
204
  def remove
205
- with_lock do
205
+ mutating do
206
206
  stop
207
207
  remove_containers
208
208
  remove_images
@@ -211,7 +211,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
211
211
 
212
212
  desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
213
213
  def remove_container(version)
214
- with_lock do
214
+ mutating do
215
215
  on(MRSK.hosts) do |host|
216
216
  roles = MRSK.roles_on(host)
217
217
 
@@ -225,7 +225,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
225
225
 
226
226
  desc "remove_containers", "Remove all app containers from servers", hide: true
227
227
  def remove_containers
228
- with_lock do
228
+ mutating do
229
229
  on(MRSK.hosts) do |host|
230
230
  roles = MRSK.roles_on(host)
231
231
 
@@ -239,7 +239,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
239
239
 
240
240
  desc "remove_images", "Remove all app images from servers", hide: true
241
241
  def remove_images
242
- with_lock do
242
+ mutating do
243
243
  on(MRSK.hosts) do
244
244
  execute *MRSK.auditor.record("Removed all app images"), verbosity: :debug
245
245
  execute *MRSK.app.remove_images
data/lib/mrsk/cli/base.rb CHANGED
@@ -72,28 +72,28 @@ module Mrsk::Cli
72
72
  puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
73
73
  end
74
74
 
75
- def with_lock
76
- if MRSK.holding_lock?
77
- yield
78
- else
79
- run_hook "pre-connect"
75
+ def mutating
76
+ return yield if MRSK.holding_lock?
80
77
 
81
- acquire_lock
78
+ MRSK.config.ensure_env_available
82
79
 
83
- begin
84
- yield
85
- rescue
86
- if MRSK.hold_lock_on_error?
87
- error " \e[31mDeploy lock was not released\e[0m"
88
- else
89
- release_lock
90
- end
80
+ run_hook "pre-connect"
91
81
 
92
- raise
82
+ acquire_lock
83
+
84
+ begin
85
+ yield
86
+ rescue
87
+ if MRSK.hold_lock_on_error?
88
+ error " \e[31mDeploy lock was not released\e[0m"
89
+ else
90
+ release_lock
93
91
  end
94
92
 
95
- release_lock
93
+ raise
96
94
  end
95
+
96
+ release_lock
97
97
  end
98
98
 
99
99
  def acquire_lock
@@ -133,15 +133,39 @@ module Mrsk::Cli
133
133
  end
134
134
  end
135
135
 
136
- def run_hook(hook, **details)
136
+ def run_hook(hook, **extra_details)
137
137
  if !options[:skip_hooks] && MRSK.hook.hook_exists?(hook)
138
+ details = { hosts: MRSK.hosts.join(","), command: command, subcommand: subcommand }
139
+
138
140
  say "Running the #{hook} hook...", :magenta
139
141
  run_locally do
140
- MRSK.with_verbosity(:debug) { execute *MRSK.hook.run(hook, **details, hosts: MRSK.hosts.join(",")) }
142
+ MRSK.with_verbosity(:debug) { execute *MRSK.hook.run(hook, **details, **extra_details) }
141
143
  rescue SSHKit::Command::Failed
142
144
  raise HookError.new("Hook `#{hook}` failed")
143
145
  end
144
146
  end
145
147
  end
146
- end
148
+
149
+ def command
150
+ @mrsk_command ||= begin
151
+ invocation_class, invocation_commands = *first_invocation
152
+ if invocation_class == Mrsk::Cli::Main
153
+ invocation_commands[0]
154
+ else
155
+ Mrsk::Cli::Main.subcommand_classes.find { |command, clazz| clazz == invocation_class }[0]
156
+ end
157
+ end
158
+ end
159
+
160
+ def subcommand
161
+ @mrsk_subcommand ||= begin
162
+ invocation_class, invocation_commands = *first_invocation
163
+ invocation_commands[0] if invocation_class != Mrsk::Cli::Main
164
+ end
165
+ end
166
+
167
+ def first_invocation
168
+ instance_variable_get("@_invocations").first
169
+ end
170
+ end
147
171
  end
@@ -3,7 +3,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
3
3
 
4
4
  desc "deliver", "Build app and push app image to registry then pull image on servers"
5
5
  def deliver
6
- with_lock do
6
+ mutating do
7
7
  push
8
8
  pull
9
9
  end
@@ -11,7 +11,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
11
11
 
12
12
  desc "push", "Build and push app image to registry"
13
13
  def push
14
- with_lock do
14
+ mutating do
15
15
  cli = self
16
16
 
17
17
  verify_local_dependencies
@@ -37,7 +37,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
37
37
 
38
38
  desc "pull", "Pull app image from registry onto servers"
39
39
  def pull
40
- with_lock do
40
+ mutating do
41
41
  on(MRSK.hosts) do
42
42
  execute *MRSK.auditor.record("Pulled image with version #{MRSK.config.version}"), verbosity: :debug
43
43
  execute *MRSK.builder.clean, raise_on_non_zero_exit: false
@@ -48,7 +48,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
48
48
 
49
49
  desc "create", "Create a build setup"
50
50
  def create
51
- with_lock do
51
+ mutating do
52
52
  run_locally do
53
53
  begin
54
54
  debug "Using builder: #{MRSK.builder.name}"
@@ -67,7 +67,7 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
67
67
 
68
68
  desc "remove", "Remove build setup"
69
69
  def remove
70
- with_lock do
70
+ mutating do
71
71
  run_locally do
72
72
  debug "Using builder: #{MRSK.builder.name}"
73
73
  execute *MRSK.builder.remove
data/lib/mrsk/cli/lock.rb CHANGED
@@ -7,7 +7,7 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
7
7
  end
8
8
 
9
9
  desc "acquire", "Acquire the deploy lock"
10
- option :message, aliases: "-m", type: :string, desc: "A lock mesasge", required: true
10
+ option :message, aliases: "-m", type: :string, desc: "A lock message", required: true
11
11
  def acquire
12
12
  message = options[:message]
13
13
  raise_if_locked do
data/lib/mrsk/cli/main.rb CHANGED
@@ -2,7 +2,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
2
2
  desc "setup", "Setup all accessories and deploy app to servers"
3
3
  def setup
4
4
  print_runtime do
5
- with_lock do
5
+ mutating do
6
6
  invoke "mrsk:cli:server:bootstrap"
7
7
  invoke "mrsk:cli:accessory:boot", [ "all" ]
8
8
  deploy
@@ -14,7 +14,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
14
14
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
15
15
  def deploy
16
16
  runtime = print_runtime do
17
- with_lock do
17
+ mutating do
18
18
  invoke_options = deploy_options
19
19
 
20
20
  say "Log into image registry...", :magenta
@@ -28,6 +28,8 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
28
28
  invoke "mrsk:cli:build:deliver", [], invoke_options
29
29
  end
30
30
 
31
+ run_hook "pre-deploy"
32
+
31
33
  say "Ensure Traefik is running...", :magenta
32
34
  invoke "mrsk:cli:traefik:boot", [], invoke_options
33
35
 
@@ -51,7 +53,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
51
53
  option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
52
54
  def redeploy
53
55
  runtime = print_runtime do
54
- with_lock do
56
+ mutating do
55
57
  invoke_options = deploy_options
56
58
 
57
59
  if options[:skip_push]
@@ -62,6 +64,8 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
62
64
  invoke "mrsk:cli:build:deliver", [], invoke_options
63
65
  end
64
66
 
67
+ run_hook "pre-deploy"
68
+
65
69
  say "Ensure app can pass healthcheck...", :magenta
66
70
  invoke "mrsk:cli:healthcheck:perform", [], invoke_options
67
71
 
@@ -79,13 +83,15 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
79
83
  def rollback(version)
80
84
  rolled_back = false
81
85
  runtime = print_runtime do
82
- with_lock do
86
+ mutating do
83
87
  invoke_options = deploy_options
84
88
 
85
89
  MRSK.config.version = version
86
90
  old_version = nil
87
91
 
88
92
  if container_available?(version)
93
+ run_hook "pre-deploy"
94
+
89
95
  invoke "mrsk:cli:app:boot", [], invoke_options.merge(version: version)
90
96
  rolled_back = true
91
97
  else
@@ -174,7 +180,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
174
180
  desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
175
181
  option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
176
182
  def remove
177
- with_lock do
183
+ mutating do
178
184
  if options[:confirmed] || ask("This will remove all containers and images. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
179
185
  invoke "mrsk:cli:traefik:remove", [], options.without(:confirmed)
180
186
  invoke "mrsk:cli:app:remove", [], options.without(:confirmed)
@@ -1,7 +1,7 @@
1
1
  class Mrsk::Cli::Prune < Mrsk::Cli::Base
2
2
  desc "all", "Prune unused images and stopped containers"
3
3
  def all
4
- with_lock do
4
+ mutating do
5
5
  containers
6
6
  images
7
7
  end
@@ -9,17 +9,18 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
9
9
 
10
10
  desc "images", "Prune dangling images"
11
11
  def images
12
- with_lock do
12
+ mutating do
13
13
  on(MRSK.hosts) do
14
14
  execute *MRSK.auditor.record("Pruned images"), verbosity: :debug
15
- execute *MRSK.prune.images
15
+ execute *MRSK.prune.dangling_images
16
+ execute *MRSK.prune.tagged_images
16
17
  end
17
18
  end
18
19
  end
19
20
 
20
21
  desc "containers", "Prune all stopped containers, except the last 5"
21
22
  def containers
22
- with_lock do
23
+ mutating do
23
24
  on(MRSK.hosts) do
24
25
  execute *MRSK.auditor.record("Pruned containers"), verbosity: :debug
25
26
  execute *MRSK.prune.containers
@@ -0,0 +1,109 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # A sample pre-deploy hook
4
+ #
5
+ # Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
6
+ #
7
+ # Fails unless the combined status is "success"
8
+ #
9
+ # These environment variables are available:
10
+ # MRSK_RECORDED_AT
11
+ # MRSK_PERFORMER
12
+ # MRSK_VERSION
13
+ # MRSK_HOSTS
14
+ # MRSK_COMMAND
15
+ # MRSK_SUBCOMMAND
16
+ # MRSK_ROLE (if set)
17
+ # MRSK_DESTINATION (if set)
18
+
19
+ # Only check the build status for production deployments
20
+ if ENV["MRSK_COMMAND"] == "rollback" || ENV["MRSK_DESTINATION"] != "production"
21
+ exit 0
22
+ end
23
+
24
+ require "bundler/inline"
25
+
26
+ # true = install gems so this is fast on repeat invocations
27
+ gemfile(true, quiet: true) do
28
+ source "https://rubygems.org"
29
+
30
+ gem "octokit"
31
+ gem "faraday-retry"
32
+ end
33
+
34
+ MAX_ATTEMPTS = 72
35
+ ATTEMPTS_GAP = 10
36
+
37
+ def exit_with_error(message)
38
+ $stderr.puts message
39
+ exit 1
40
+ end
41
+
42
+ class GithubStatusChecks
43
+ attr_reader :remote_url, :git_sha, :github_client, :combined_status
44
+
45
+ def initialize
46
+ @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
47
+ @git_sha = `git rev-parse HEAD`.strip
48
+ @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
49
+ refresh!
50
+ end
51
+
52
+ def refresh!
53
+ @combined_status = github_client.combined_status(remote_url, git_sha)
54
+ end
55
+
56
+ def state
57
+ combined_status[:state]
58
+ end
59
+
60
+ def first_status_url
61
+ first_status = combined_status[:statuses].find { |status| status[:state] == state }
62
+ first_status && first_status[:target_url]
63
+ end
64
+
65
+ def complete_count
66
+ combined_status[:statuses].count { |status| status[:state] != "pending"}
67
+ end
68
+
69
+ def total_count
70
+ combined_status[:statuses].count
71
+ end
72
+
73
+ def current_status
74
+ if total_count > 0
75
+ "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
76
+ else
77
+ "Build not started..."
78
+ end
79
+ end
80
+ end
81
+
82
+
83
+ $stdout.sync = true
84
+
85
+ puts "Checking build status..."
86
+ attempts = 0
87
+ checks = GithubStatusChecks.new
88
+
89
+ begin
90
+ loop do
91
+ case checks.state
92
+ when "success"
93
+ puts "Checks passed, see #{checks.first_status_url}"
94
+ exit 0
95
+ when "failure"
96
+ exit_with_error "Checks failed, see #{checks.first_status_url}"
97
+ when "pending"
98
+ attempts += 1
99
+ end
100
+
101
+ exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
102
+
103
+ puts checks.current_status
104
+ sleep(ATTEMPTS_GAP)
105
+ checks.refresh!
106
+ end
107
+ rescue Octokit::NotFound
108
+ exit_with_error "Build status could not be found"
109
+ end
@@ -1,7 +1,7 @@
1
1
  class Mrsk::Cli::Traefik < Mrsk::Cli::Base
2
2
  desc "boot", "Boot Traefik on servers"
3
3
  def boot
4
- with_lock do
4
+ mutating do
5
5
  on(MRSK.traefik_hosts) do
6
6
  execute *MRSK.registry.login
7
7
  execute *MRSK.traefik.run, raise_on_non_zero_exit: false
@@ -11,7 +11,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
11
11
 
12
12
  desc "reboot", "Reboot Traefik on servers (stop container, remove container, start new container)"
13
13
  def reboot
14
- with_lock do
14
+ mutating do
15
15
  stop
16
16
  remove_container
17
17
  boot
@@ -20,7 +20,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
20
20
 
21
21
  desc "start", "Start existing Traefik container on servers"
22
22
  def start
23
- with_lock do
23
+ mutating do
24
24
  on(MRSK.traefik_hosts) do
25
25
  execute *MRSK.auditor.record("Started traefik"), verbosity: :debug
26
26
  execute *MRSK.traefik.start, raise_on_non_zero_exit: false
@@ -30,7 +30,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
30
30
 
31
31
  desc "stop", "Stop existing Traefik container on servers"
32
32
  def stop
33
- with_lock do
33
+ mutating do
34
34
  on(MRSK.traefik_hosts) do
35
35
  execute *MRSK.auditor.record("Stopped traefik"), verbosity: :debug
36
36
  execute *MRSK.traefik.stop, raise_on_non_zero_exit: false
@@ -40,7 +40,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
40
40
 
41
41
  desc "restart", "Restart existing Traefik container on servers"
42
42
  def restart
43
- with_lock do
43
+ mutating do
44
44
  stop
45
45
  start
46
46
  end
@@ -77,7 +77,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
77
77
 
78
78
  desc "remove", "Remove Traefik container and image from servers"
79
79
  def remove
80
- with_lock do
80
+ mutating do
81
81
  stop
82
82
  remove_container
83
83
  remove_image
@@ -86,7 +86,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
86
86
 
87
87
  desc "remove_container", "Remove Traefik container from servers", hide: true
88
88
  def remove_container
89
- with_lock do
89
+ mutating do
90
90
  on(MRSK.traefik_hosts) do
91
91
  execute *MRSK.auditor.record("Removed traefik container"), verbosity: :debug
92
92
  execute *MRSK.traefik.remove_container
@@ -96,7 +96,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
96
96
 
97
97
  desc "remove_image", "Remove Traefik image from servers", hide: true
98
98
  def remove_image
99
- with_lock do
99
+ mutating do
100
100
  on(MRSK.traefik_hosts) do
101
101
  execute *MRSK.auditor.record("Removed traefik image"), verbosity: :debug
102
102
  execute *MRSK.traefik.remove_image
@@ -8,17 +8,18 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
8
8
  @role = role
9
9
  end
10
10
 
11
- def start_or_run
12
- combine start, run, by: "||"
11
+ def start_or_run(hostname: nil)
12
+ combine start, run(hostname: hostname), by: "||"
13
13
  end
14
14
 
15
- def run
15
+ def run(hostname: nil)
16
16
  role = config.role(self.role)
17
17
 
18
18
  docker :run,
19
19
  "--detach",
20
20
  "--restart unless-stopped",
21
21
  "--name", container_name,
22
+ *(["--hostname", hostname] if hostname),
22
23
  "-e", "MRSK_CONTAINER_NAME=\"#{container_name}\"",
23
24
  *role.env_args,
24
25
  *role.health_check_args,
@@ -13,7 +13,11 @@ module Mrsk::Commands
13
13
 
14
14
  def run_over_ssh(*command, host:)
15
15
  "ssh".tap do |cmd|
16
- cmd << " -J #{config.ssh_proxy.jump_proxies}" if config.ssh_proxy
16
+ if config.ssh_proxy && config.ssh_proxy.is_a?(Net::SSH::Proxy::Jump)
17
+ cmd << " -J #{config.ssh_proxy.jump_proxies}"
18
+ elsif config.ssh_proxy && config.ssh_proxy.is_a?(Net::SSH::Proxy::Command)
19
+ cmd << " -o ProxyCommand='#{config.ssh_proxy.command_line_template}'"
20
+ end
17
21
  cmd << " -t #{config.ssh_user}@#{host} '#{command.join(" ")}'"
18
22
  end
19
23
  end
@@ -3,6 +3,7 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
3
3
  class BuilderError < StandardError; end
4
4
 
5
5
  delegate :argumentize, to: Mrsk::Utils
6
+ delegate :args, :secrets, :dockerfile, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, to: :builder_config
6
7
 
7
8
  def clean
8
9
  docker :image, :rm, "--force", config.absolute_image
@@ -13,11 +14,11 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
13
14
  end
14
15
 
15
16
  def build_options
16
- [ *build_tags, *build_labels, *build_args, *build_secrets, *build_dockerfile ]
17
+ [ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile ]
17
18
  end
18
19
 
19
20
  def build_context
20
- context
21
+ config.builder.context
21
22
  end
22
23
 
23
24
 
@@ -26,6 +27,13 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
26
27
  [ "-t", config.absolute_image, "-t", config.latest_image ]
27
28
  end
28
29
 
30
+ def build_cache
31
+ if cache_to && cache_from
32
+ ["--cache-to", cache_to,
33
+ "--cache-from", cache_from]
34
+ end
35
+ end
36
+
29
37
  def build_labels
30
38
  argumentize "--label", { service: config.service }
31
39
  end
@@ -46,19 +54,7 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
46
54
  end
47
55
  end
48
56
 
49
- def args
50
- (config.builder && config.builder["args"]) || {}
51
- end
52
-
53
- def secrets
54
- (config.builder && config.builder["secrets"]) || []
55
- end
56
-
57
- def dockerfile
58
- (config.builder && config.builder["dockerfile"]) || "Dockerfile"
59
- end
60
-
61
- def context
62
- (config.builder && config.builder["context"]) || "."
57
+ def builder_config
58
+ config.builder
63
59
  end
64
60
  end
@@ -22,17 +22,17 @@ class Mrsk::Commands::Builder::Multiarch::Remote < Mrsk::Commands::Builder::Mult
22
22
  end
23
23
 
24
24
  def create_local_buildx
25
- docker :buildx, :create, "--name", builder_name, builder_name_with_arch(local["arch"]), "--platform", "linux/#{local["arch"]}"
25
+ docker :buildx, :create, "--name", builder_name, builder_name_with_arch(local_arch), "--platform", "linux/#{local_arch}"
26
26
  end
27
27
 
28
28
  def append_remote_buildx
29
- docker :buildx, :create, "--append", "--name", builder_name, builder_name_with_arch(remote["arch"]), "--platform", "linux/#{remote["arch"]}"
29
+ docker :buildx, :create, "--append", "--name", builder_name, builder_name_with_arch(remote_arch), "--platform", "linux/#{remote_arch}"
30
30
  end
31
31
 
32
32
  def create_contexts
33
33
  combine \
34
- create_context(local["arch"], local["host"]),
35
- create_context(remote["arch"], remote["host"])
34
+ create_context(local_arch, local_host),
35
+ create_context(remote_arch, remote_host)
36
36
  end
37
37
 
38
38
  def create_context(arch, host)
@@ -41,19 +41,11 @@ class Mrsk::Commands::Builder::Multiarch::Remote < Mrsk::Commands::Builder::Mult
41
41
 
42
42
  def remove_contexts
43
43
  combine \
44
- remove_context(local["arch"]),
45
- remove_context(remote["arch"])
44
+ remove_context(local_arch),
45
+ remove_context(remote_arch)
46
46
  end
47
47
 
48
48
  def remove_context(arch)
49
49
  docker :context, :rm, builder_name_with_arch(arch)
50
50
  end
51
-
52
- def local
53
- config.builder["local"]
54
- end
55
-
56
- def remote
57
- config.builder["remote"]
58
- end
59
51
  end
@@ -0,0 +1,16 @@
1
+ class Mrsk::Commands::Builder::Native::Cached < Mrsk::Commands::Builder::Native
2
+ def create
3
+ docker :buildx, :create, "--use", "--driver=docker-container"
4
+ end
5
+
6
+ def remove
7
+ docker :buildx, :rm, builder_name
8
+ end
9
+
10
+ def push
11
+ docker :buildx, :build,
12
+ "--push",
13
+ *build_options,
14
+ build_context
15
+ end
16
+ end
@@ -28,29 +28,21 @@ class Mrsk::Commands::Builder::Native::Remote < Mrsk::Commands::Builder::Native
28
28
 
29
29
 
30
30
  private
31
- def arch
32
- config.builder["remote"]["arch"]
33
- end
34
-
35
- def host
36
- config.builder["remote"]["host"]
37
- end
38
-
39
31
  def builder_name
40
32
  "mrsk-#{config.service}-native-remote"
41
33
  end
42
34
 
43
35
  def builder_name_with_arch
44
- "#{builder_name}-#{arch}"
36
+ "#{builder_name}-#{remote_arch}"
45
37
  end
46
38
 
47
39
  def platform
48
- "linux/#{arch}"
40
+ "linux/#{remote_arch}"
49
41
  end
50
42
 
51
43
  def create_context
52
44
  docker :context, :create,
53
- builder_name_with_arch, "--description", "'#{builder_name} #{arch} native host'", "--docker", "'host=#{host}'"
45
+ builder_name_with_arch, "--description", "'#{builder_name} #{remote_arch} native host'", "--docker", "'host=#{remote_host}'"
54
46
  end
55
47
 
56
48
  def remove_context
@@ -1,10 +1,10 @@
1
1
  class Mrsk::Commands::Builder::Native < Mrsk::Commands::Builder::Base
2
2
  def create
3
- # No-op on native
3
+ # No-op on native without cache
4
4
  end
5
5
 
6
6
  def remove
7
- # No-op on native
7
+ # No-op on native without cache
8
8
  end
9
9
 
10
10
  def push
@@ -7,11 +7,13 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
7
7
 
8
8
  def target
9
9
  case
10
- when config.builder && config.builder["multiarch"] == false
10
+ when !config.builder.multiarch? && !config.builder.cached?
11
11
  native
12
- when config.builder && config.builder["local"] && config.builder["remote"]
12
+ when !config.builder.multiarch? && config.builder.cached?
13
+ native_cached
14
+ when config.builder.local? && config.builder.remote?
13
15
  multiarch_remote
14
- when config.builder && config.builder["remote"]
16
+ when config.builder.remote?
15
17
  native_remote
16
18
  else
17
19
  multiarch
@@ -22,6 +24,10 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
22
24
  @native ||= Mrsk::Commands::Builder::Native.new(config)
23
25
  end
24
26
 
27
+ def native_cached
28
+ @native ||= Mrsk::Commands::Builder::Native::Cached.new(config)
29
+ end
30
+
25
31
  def native_remote
26
32
  @native ||= Mrsk::Commands::Builder::Native::Remote.new(config)
27
33
  end
@@ -2,13 +2,20 @@ 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
- def images
5
+ def dangling_images
6
6
  docker :image, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "dangling=true"
7
7
  end
8
8
 
9
+ def tagged_images
10
+ pipe \
11
+ docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"),
12
+ "grep -v -w \"#{active_image_list}\"",
13
+ "while read image tag; do docker rmi $tag; done"
14
+ end
15
+
9
16
  def containers(keep_last: 5)
10
17
  pipe \
11
- docker(:ps, "-q", "-a", "--filter", "label=service=#{config.service}", *stopped_containers_filters),
18
+ docker(:ps, "-q", "-a", *service_filter, *stopped_containers_filters),
12
19
  "tail -n +#{keep_last + 1}",
13
20
  "while read container_id; do docker rm $container_id; done"
14
21
  end
@@ -17,4 +24,15 @@ class Mrsk::Commands::Prune < Mrsk::Commands::Base
17
24
  def stopped_containers_filters
18
25
  [ "created", "exited", "dead" ].flat_map { |status| ["--filter", "status=#{status}"] }
19
26
  end
27
+
28
+ def active_image_list
29
+ # Pull the images that are used by any containers
30
+ # Append repo:latest - to avoid deleting the latest tag
31
+ # Append repo:<none> - to avoid deleting dangling images that are in use. Unused dangling images are deleted separately
32
+ "$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=#{config.service} | tr -d '\\n')#{config.latest_image}\\|#{config.repository}:<none>"
33
+ end
34
+
35
+ def service_filter
36
+ [ "--filter", "label=service=#{config.service}" ]
37
+ end
20
38
  end
@@ -3,6 +3,9 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
3
3
 
4
4
  DEFAULT_IMAGE = "traefik:v2.9"
5
5
  CONTAINER_PORT = 80
6
+ DEFAULT_ARGS = {
7
+ 'log.level' => 'DEBUG'
8
+ }
6
9
 
7
10
  def run
8
11
  docker :run, "--name traefik",
@@ -16,7 +19,6 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
16
19
  *docker_options_args,
17
20
  image,
18
21
  "--providers.docker",
19
- "--log.level=DEBUG",
20
22
  *cmd_option_args
21
23
  end
22
24
 
@@ -86,9 +88,9 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
86
88
 
87
89
  def cmd_option_args
88
90
  if args = config.traefik["args"]
89
- optionize args, with: "="
91
+ optionize DEFAULT_ARGS.merge(args), with: "="
90
92
  else
91
- []
93
+ optionize DEFAULT_ARGS, with: "="
92
94
  end
93
95
  end
94
96
 
@@ -0,0 +1,114 @@
1
+ class Mrsk::Configuration::Builder
2
+ def initialize(config:)
3
+ @options = config.raw_config.builder || {}
4
+ @image = config.image
5
+ @server = config.registry["server"]
6
+
7
+ valid?
8
+ end
9
+
10
+ def to_h
11
+ @options
12
+ end
13
+
14
+ def multiarch?
15
+ @options["multiarch"] != false
16
+ end
17
+
18
+ def local?
19
+ !!@options["local"]
20
+ end
21
+
22
+ def remote?
23
+ !!@options["remote"]
24
+ end
25
+
26
+ def cached?
27
+ !!@options["cache"]
28
+ end
29
+
30
+ def args
31
+ @options["args"] || {}
32
+ end
33
+
34
+ def secrets
35
+ @options["secrets"] || []
36
+ end
37
+
38
+ def dockerfile
39
+ @options["dockerfile"] || "Dockerfile"
40
+ end
41
+
42
+ def context
43
+ @options["context"] || "."
44
+ end
45
+
46
+ def local_arch
47
+ @options["local"]["arch"] if local?
48
+ end
49
+
50
+ def local_host
51
+ @options["local"]["host"] if local?
52
+ end
53
+
54
+ def remote_arch
55
+ @options["remote"]["arch"] if remote?
56
+ end
57
+
58
+ def remote_host
59
+ @options["remote"]["host"] if remote?
60
+ end
61
+
62
+ def cache_from
63
+ if cached?
64
+ case @options["cache"]["type"]
65
+ when "gha"
66
+ cache_from_config_for_gha
67
+ when "registry"
68
+ cache_from_config_for_registry
69
+ end
70
+ end
71
+ end
72
+
73
+ def cache_to
74
+ if cached?
75
+ case @options["cache"]["type"]
76
+ when "gha"
77
+ cache_to_config_for_gha
78
+ when "registry"
79
+ cache_to_config_for_registry
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+ def valid?
86
+ if @options["local"] && !@options["remote"]
87
+ raise ArgumentError, "You must specify both local and remote builder config for remote multiarch builds"
88
+ end
89
+
90
+ if @options["cache"] && @options["cache"]["type"]
91
+ raise ArgumentError, "Invalid cache type: #{@options["cache"]["type"]}" unless ["gha", "registry"].include?(@options["cache"]["type"])
92
+ end
93
+ end
94
+
95
+ def cache_image
96
+ @options["cache"]&.fetch("image", nil) || "#{@image}-build-cache"
97
+ end
98
+
99
+ def cache_from_config_for_gha
100
+ "type=gha"
101
+ end
102
+
103
+ def cache_from_config_for_registry
104
+ [ "type=registry", "ref=#{@server}/#{cache_image}" ].compact.join(",")
105
+ end
106
+
107
+ def cache_to_config_for_gha
108
+ [ "type=gha", @options["cache"]&.fetch("options", nil)].compact.join(",")
109
+ end
110
+
111
+ def cache_to_config_for_registry
112
+ [ "type=registry", @options["cache"]&.fetch("options", nil), "ref=#{@server}/#{cache_image}" ].compact.join(",")
113
+ end
114
+ end
@@ -6,7 +6,7 @@ require "erb"
6
6
  require "net/ssh/proxy/jump"
7
7
 
8
8
  class Mrsk::Configuration
9
- delegate :service, :image, :servers, :env, :labels, :registry, :builder, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
9
+ delegate :service, :image, :servers, :env, :labels, :registry, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
10
10
  delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Mrsk::Utils
11
11
 
12
12
  attr_accessor :destination
@@ -165,8 +165,12 @@ class Mrsk::Configuration
165
165
  raw_config.readiness_delay || 7
166
166
  end
167
167
 
168
+ def minimum_version
169
+ raw_config.minimum_version
170
+ end
171
+
168
172
  def valid?
169
- ensure_required_keys_present && ensure_env_available
173
+ ensure_required_keys_present && ensure_valid_mrsk_version
170
174
  end
171
175
 
172
176
 
@@ -182,7 +186,7 @@ class Mrsk::Configuration
182
186
  env_args: env_args,
183
187
  volume_args: volume_args,
184
188
  ssh_options: ssh_options,
185
- builder: raw_config.builder,
189
+ builder: builder.to_h,
186
190
  accessories: raw_config.accessories,
187
191
  logging: logging_args,
188
192
  healthcheck: healthcheck
@@ -197,6 +201,18 @@ class Mrsk::Configuration
197
201
  raw_config.hooks_path || ".mrsk/hooks"
198
202
  end
199
203
 
204
+ def builder
205
+ Mrsk::Configuration::Builder.new(config: self)
206
+ end
207
+
208
+ # Will raise KeyError if any secret ENVs are missing
209
+ def ensure_env_available
210
+ env_args
211
+ roles.each(&:env_args)
212
+
213
+ true
214
+ end
215
+
200
216
  private
201
217
  # Will raise ArgumentError if any required config keys are missing
202
218
  def ensure_required_keys_present
@@ -221,14 +237,15 @@ class Mrsk::Configuration
221
237
  true
222
238
  end
223
239
 
224
- # Will raise KeyError if any secret ENVs are missing
225
- def ensure_env_available
226
- env_args
227
- roles.each(&:env_args)
240
+ def ensure_valid_mrsk_version
241
+ if minimum_version && Gem::Version.new(minimum_version) > Gem::Version.new(Mrsk::VERSION)
242
+ raise ArgumentError, "Current version is #{Mrsk::VERSION}, minimum required is #{minimum_version}"
243
+ end
228
244
 
229
245
  true
230
246
  end
231
247
 
248
+
232
249
  def role_names
233
250
  raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
234
251
  end
data/lib/mrsk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.13.1"
2
+ VERSION = "0.14.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.13.1
4
+ version: 0.14.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-05-25 00:00:00.000000000 Z
11
+ date: 2023-06-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -190,6 +190,7 @@ files:
190
190
  - lib/mrsk/cli/templates/sample_hooks/post-deploy.sample
191
191
  - lib/mrsk/cli/templates/sample_hooks/pre-build.sample
192
192
  - lib/mrsk/cli/templates/sample_hooks/pre-connect.sample
193
+ - lib/mrsk/cli/templates/sample_hooks/pre-deploy.sample
193
194
  - lib/mrsk/cli/templates/template.env
194
195
  - lib/mrsk/cli/traefik.rb
195
196
  - lib/mrsk/commander.rb
@@ -203,6 +204,7 @@ files:
203
204
  - lib/mrsk/commands/builder/multiarch.rb
204
205
  - lib/mrsk/commands/builder/multiarch/remote.rb
205
206
  - lib/mrsk/commands/builder/native.rb
207
+ - lib/mrsk/commands/builder/native/cached.rb
206
208
  - lib/mrsk/commands/builder/native/remote.rb
207
209
  - lib/mrsk/commands/docker.rb
208
210
  - lib/mrsk/commands/healthcheck.rb
@@ -214,6 +216,7 @@ files:
214
216
  - lib/mrsk/configuration.rb
215
217
  - lib/mrsk/configuration/accessory.rb
216
218
  - lib/mrsk/configuration/boot.rb
219
+ - lib/mrsk/configuration/builder.rb
217
220
  - lib/mrsk/configuration/role.rb
218
221
  - lib/mrsk/sshkit_with_ext.rb
219
222
  - lib/mrsk/tags.rb