mrsk 0.10.1 → 0.12.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: d53c75ef2fad59a884ae2e8a04fff57a68330faf7522ef7bb620affc48a803d6
4
- data.tar.gz: b91edccb441e562d24680034df00fbcdf3518aadd570784fbf31d9f2c07a9ebd
3
+ metadata.gz: 8a09720b016119167213b3c1aab02e56c5e3de0eaa09dc39996d55931048e3e5
4
+ data.tar.gz: 7b435e7ff711c7267cb3f1771d56791fe76c1fb62950daeeacfe923288c314e8
5
5
  SHA512:
6
- metadata.gz: d7037c8e7a41acd4cb199b29251245f3e99f2c9cf62ca99c392ba7dfb77defde655f616161e6ef82319ea14438fb27992a8a88861bb49a2a1efe1771f92d60e0
7
- data.tar.gz: a0810f91aeb9332ea8401d2a7f8ca847f78a37a85f67b71753734fe2f0ef298435f813fc07fa156bb755e05170afd09f2bdc384504041276ca2502b19ab1d7a6
6
+ metadata.gz: de31f5f7e47e1f93446603e48a9a1760fca96bc41ee6dfec0660030fb0fbcdcb3fb4145d4b81fda9c3f4eef5de0a205f54e67e7bb27c91d7ebd0c93c3f5d0c57
7
+ data.tar.gz: 48d36ec263b4ccfd3729059eb85f340717b17406f88e8281955e948fcfe9c30ffe081bad01977e6f9836ecd480361cf94f15cdfe79aa7477c18a6fb6ad9b97bb
data/README.md CHANGED
@@ -6,6 +6,8 @@ Watch the screencast: https://www.youtube.com/watch?v=LL1cV2FXZ5I
6
6
 
7
7
  Join us on Discord: https://discord.gg/YgHVT7GCXS
8
8
 
9
+ Ask questions: https://github.com/mrsked/mrsk/discussions
10
+
9
11
  ## Installation
10
12
 
11
13
  If you have a Ruby environment available, you can install MRSK globally with:
@@ -14,13 +16,13 @@ If you have a Ruby environment available, you can install MRSK globally with:
14
16
  gem install mrsk
15
17
  ```
16
18
 
17
- ...otherwise, you can run a dockerized version via an alias (add this to your ${SHELL}rc to simplify re-use):
19
+ ...otherwise, you can run a dockerized version via an alias (add this to your .bashrc or similar to simplify re-use):
18
20
 
19
21
  ```sh
20
22
  alias mrsk='docker run --rm -it -v $HOME/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock -v ${PWD}/:/workdir ghcr.io/mrsked/mrsk'
21
23
  ```
22
24
 
23
- Then, inside your app directory, run `mrsk init` (or `mrsk init --bundle` within Rails apps where you want a bin/mrsk binstub). Now edit the new file `config/deploy.yml`. It could look as simple as this:
25
+ Then, inside your app directory, run `mrsk init` (or `mrsk init --bundle` within Rails 7+ apps where you want a bin/mrsk binstub). Now edit the new file `config/deploy.yml`. It could look as simple as this:
24
26
 
25
27
  ```yaml
26
28
  service: hey
@@ -191,6 +193,15 @@ ssh:
191
193
  user: app
192
194
  ```
193
195
 
196
+ If you are using non-root user, you need to bootstrap your servers manually, before using them with MRSK. On Ubuntu, you'd do:
197
+
198
+ ```bash
199
+ sudo apt update
200
+ sudo apt upgrade -y
201
+ sudo apt install -y docker.io curl git
202
+ sudo usermod -a -G docker ubuntu
203
+ ```
204
+
194
205
  ### Using a proxy SSH host
195
206
 
196
207
  If you need to connect to server through a proxy host, you can use `ssh/proxy`:
@@ -207,6 +218,13 @@ ssh:
207
218
  proxy: "app@192.168.0.1"
208
219
  ```
209
220
 
221
+ Also if you need specific proxy command to connect to the server:
222
+
223
+ ```yaml
224
+ ssh:
225
+ proxy_command: aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p' --region=us-east-1 ## ssh via aws ssm
226
+ ```
227
+
210
228
  ### Using env variables
211
229
 
212
230
  You can inject env variables into the app containers using `env`:
@@ -288,8 +306,9 @@ You can specialize the default Traefik rules by setting labels on the containers
288
306
 
289
307
  ```yaml
290
308
  labels:
291
- traefik.http.routers.hey.rule: Host(`app.hey.com`)
309
+ traefik.http.routers.hey-web.rule: Host(`app.hey.com`)
292
310
  ```
311
+ Traefik rules are in the "service-role-destination" format. The default role will be `web` if no rule is specified. If the destination is not specified, it is not included. To give an example, the above rule would become "traefik.http.routers.hey-web.rule" if it was for the "staging" destination.
293
312
 
294
313
  Note: The backticks are needed to ensure the rule is passed in correctly and not treated as command substitution by Bash!
295
314
 
@@ -312,6 +331,21 @@ servers:
312
331
  my-label: "50"
313
332
  ```
314
333
 
334
+ ### Using shell expansion
335
+
336
+ You can use shell expansion to interpolate values from the host machine into labels and env variables with the `${}` syntax.
337
+ Anything within the curly braces will be executed on the host machine and the result will be interpolated into the label or env variable.
338
+
339
+ ```yaml
340
+ labels:
341
+ host-machine: "${cat /etc/hostname}"
342
+
343
+ env:
344
+ HOST_DEPLOYMENT_DIR: "${PWD}"
345
+ ```
346
+
347
+ Note: Any other occurrence of `$` will be escaped to prevent unwanted shell expansion!
348
+
315
349
  ### Using container options
316
350
 
317
351
  You can specialize the options used to start containers using the `options` definitions:
@@ -439,9 +473,9 @@ RUN --mount=type=secret,id=GITHUB_TOKEN \
439
473
  rm -rf /usr/local/bundle/cache
440
474
  ```
441
475
 
442
- ### Using command arguments for Traefik
476
+ ### Traefik command arguments
443
477
 
444
- You can customize the traefik command line:
478
+ Customize the Traefik command line using `args`:
445
479
 
446
480
  ```yaml
447
481
  traefik:
@@ -450,37 +484,70 @@ traefik:
450
484
  accesslog.format: json
451
485
  ```
452
486
 
453
- This will start the traefik container with `--accesslog=true accesslog.format=json`.
487
+ This starts the Traefik container with `--accesslog=true --accesslog.format=json` arguments.
454
488
 
455
- ### Traefik's host port binding
489
+ ### Traefik host port binding
456
490
 
457
- By default Traefik binds to port 80 of the host machine, it can be configured to use an alternative port:
491
+ Traefik binds to port 80 by default. Specify an alternative port using `host_port`:
458
492
 
459
493
  ```yaml
460
494
  traefik:
461
495
  host_port: 8080
462
496
  ```
463
497
 
464
- ### Configure docker options for traefik
498
+ ### Traefik version, upgrades, and custom images
465
499
 
466
- We allow users to pass additional docker options to the trafik container like
500
+ MRSK runs the traefik:v2.9 image to track Traefik 2.9.x releases.
501
+
502
+ To pin Traefik to a specific version or an image published to your registry,
503
+ specify `image`:
504
+
505
+ ```yaml
506
+ traefik:
507
+ image: traefik:v2.10.0-rc1
508
+ ```
509
+
510
+ This is useful for downgrading Traefik if there's an unexpected breaking
511
+ change in a minor version release, upgrading Traefik to test forthcoming
512
+ releases, or running your own Traefik-derived image.
513
+
514
+ MRSK has not been tested for compatibility with Traefik 3 betas. Please do!
515
+
516
+ ### Traefik container configuration
517
+
518
+ Pass additional Docker configuration for the Traefik container using `options`:
467
519
 
468
520
  ```yaml
469
521
  traefik:
470
522
  options:
471
523
  publish:
472
- - 8080:8080
524
+ - 8080:8080
473
525
  volumes:
474
- - /tmp/example.json:/tmp/example.json
526
+ - /tmp/example.json:/tmp/example.json
475
527
  memory: 512m
476
528
  ```
477
529
 
478
- This will start the traefik container with a command like: `docker run ... --volume /tmp/example.json:/tmp/example.json --publish 8080:8080 `
530
+ This starts the Traefik container with `--volume /tmp/example.json:/tmp/example.json --publish 8080:8080 --memory 512m` arguments to `docker run`.
531
+
532
+ ### Traefik container labels
533
+
534
+ Add labels to Traefik Docker container.
479
535
 
536
+ ```yaml
537
+ traefik:
538
+ labels:
539
+ traefik.enable: true
540
+ traefik.http.routers.dashboard.rule: Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))
541
+ traefik.http.routers.dashboard.service: api@internal
542
+ traefik.http.routers.dashboard.middlewares: auth
543
+ traefik.http.middlewares.auth.basicauth.users: test:$2y$05$H2o72tMaO.TwY1wNQUV1K.fhjRgLHRDWohFvUZOJHBEtUXNKrqUKi # test:password
544
+ ```
545
+
546
+ This labels Traefik container with `--label traefik.http.routers.dashboard.middlewares=\"auth\"` and so on.
480
547
 
481
- ### Configure alternate entrypoints for traefik
548
+ ### Traefik alternate entrypoints
482
549
 
483
- You can configure multiple entrypoints for traefik like so:
550
+ You can configure multiple entrypoints for Traefik like so:
484
551
 
485
552
  ```yaml
486
553
  service: myservice
@@ -540,7 +607,7 @@ accessories:
540
607
  memory: "2GB"
541
608
  redis:
542
609
  image: redis:latest
543
- role:
610
+ roles:
544
611
  - web
545
612
  port: "36379:6379"
546
613
  volumes:
@@ -610,18 +677,45 @@ That'll post a line like follows to a preconfigured chatbot in Basecamp:
610
677
  [My App] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
611
678
  ```
612
679
 
613
- ### Using custom healthcheck path or port
680
+ ### Healthcheck
681
+
682
+ 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.
614
683
 
615
- MRSK defaults to checking the health of your application again `/up` on port 3000. You can tailor both with the `healthcheck` setting:
684
+ 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:
616
685
 
617
686
  ```yaml
618
687
  healthcheck:
619
688
  path: /healthz
620
689
  port: 4000
690
+ max_attempts: 7
621
691
  ```
622
692
 
623
693
  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.
624
694
 
695
+ You can also specify a custom healthcheck command, which is useful for non-HTTP services:
696
+
697
+ ```yaml
698
+ healthcheck:
699
+ cmd: /bin/check_health
700
+ ```
701
+
702
+ The top-level healthcheck configuration applies to all services that use
703
+ Traefik, by default. You can also specialize the configuration at the role
704
+ level:
705
+
706
+ ```yaml
707
+ servers:
708
+ job:
709
+ hosts: ...
710
+ cmd: bin/jobs
711
+ healthcheck:
712
+ cmd: bin/check
713
+ ```
714
+
715
+ The healthcheck allows for an optional `max_attempts` setting, which will attempt the healthcheck up to the specified number of times before failing the deploy. This is useful for applications that take a while to start up. The default is 7.
716
+
717
+ Note that the HTTP health checks assume that the `curl` command is avilable inside the container. If that's not the case, use the healthcheck's `cmd` option to specify an alternative check that the container supports.
718
+
625
719
  ## Commands
626
720
 
627
721
  ### Running commands on servers
@@ -761,6 +855,24 @@ mrsk lock acquire -m "Doing maintanence"
761
855
  mrsk lock release
762
856
  ```
763
857
 
858
+ ## Rolling deployments
859
+
860
+ When deploying to large numbers of hosts, you might prefer not to restart your services on every host at the same time.
861
+
862
+ MRSK's default is to boot new containers on all hosts in parallel. But you can control this by configuring `boot/limit` and `boot/wait` as options:
863
+
864
+ ```yaml
865
+ service: myservice
866
+
867
+ boot:
868
+ limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
869
+ wait: 2
870
+ ```
871
+
872
+ When `limit` is specified, containers will be booted on, at most, `limit` hosts at once. MRSK will pause for `wait` seconds between batches.
873
+
874
+ These settings only apply when booting containers (using `mrsk deploy`, or `mrsk app boot`). For other commands, MRSK continues to run commands in parallel across all hosts.
875
+
764
876
  ## Stage of development
765
877
 
766
878
  This is beta software. Commands may still move around. But we're live in production at [37signals](https://37signals.com).
data/lib/mrsk/cli/app.rb CHANGED
@@ -6,27 +6,33 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
6
6
  using_version(version_or_latest) do |version|
7
7
  say "Start container with version #{version} using a #{MRSK.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
8
8
 
9
- cli = self
9
+ on(MRSK.hosts) do
10
+ execute *MRSK.auditor.record("Tagging #{MRSK.config.absolute_image} as the latest image"), verbosity: :debug
11
+ execute *MRSK.app.tag_current_as_latest
12
+ end
10
13
 
11
- on(MRSK.hosts) do |host|
14
+ on(MRSK.hosts, **MRSK.boot_strategy) do |host|
12
15
  roles = MRSK.roles_on(host)
13
16
 
14
17
  roles.each do |role|
15
- execute *MRSK.auditor(role: role).record("Booted app version #{version}"), verbosity: :debug
16
-
17
- begin
18
- if capture_with_info(*MRSK.app(role: role).container_id_for_version(version)).present?
19
- tmp_version = "#{version}_#{SecureRandom.hex(8)}"
20
- info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
21
- execute *MRSK.auditor(role: role).record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
22
- execute *MRSK.app(role: role).rename_container(version: version, new_version: tmp_version)
23
- end
24
-
25
- old_version = capture_with_info(*MRSK.app(role: role).current_running_version).strip
26
- execute *MRSK.app(role: role).run
27
- sleep MRSK.config.readiness_delay
28
- execute *MRSK.app(role: role).stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
18
+ app = MRSK.app(role: role)
19
+ auditor = MRSK.auditor(role: role)
20
+
21
+ execute *auditor.record("Booted app version #{version}"), verbosity: :debug
22
+
23
+ if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
24
+ tmp_version = "#{version}_#{SecureRandom.hex(8)}"
25
+ info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
26
+ execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
27
+ execute *app.rename_container(version: version, new_version: tmp_version)
29
28
  end
29
+
30
+ old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
31
+ execute *app.run
32
+
33
+ Mrsk::Utils::HealthcheckPoller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
34
+
35
+ execute *app.stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
30
36
  end
31
37
  end
32
38
  end
@@ -124,6 +130,31 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
124
130
  on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_containers) }
125
131
  end
126
132
 
133
+ desc "stale_containers", "Detect app stale containers"
134
+ option :stop, aliases: "-s", type: :boolean, default: false, desc: "Stop the stale containers found"
135
+ def stale_containers
136
+ with_lock do
137
+ stop = options[:stop]
138
+
139
+ cli = self
140
+
141
+ on(MRSK.hosts) do |host|
142
+ roles = MRSK.roles_on(host)
143
+
144
+ roles.each do |role|
145
+ cli.send(:stale_versions, host: host, role: role).each do |version|
146
+ if stop
147
+ puts_by_host host, "Stopping stale container for role #{role} with version #{version}"
148
+ execute *MRSK.app(role: role).stop(version: version), raise_on_non_zero_exit: false
149
+ else
150
+ puts_by_host host, "Detected stale container for role #{role} with version #{version} (use `mrsk app stale_containers --stop` to stop)"
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+
127
158
  desc "images", "Show app images on servers"
128
159
  def images
129
160
  on(MRSK.hosts) { |host| puts_by_host host, capture_with_info(*MRSK.app.list_images) }
@@ -240,6 +271,17 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
240
271
  version.presence
241
272
  end
242
273
 
274
+ def stale_versions(host:, role:)
275
+ versions = nil
276
+ on(host) do
277
+ versions = \
278
+ capture_with_info(*MRSK.app(role: role).list_versions, raise_on_non_zero_exit: false)
279
+ .split("\n")
280
+ .drop(1)
281
+ end
282
+ versions
283
+ end
284
+
243
285
  def version_or_latest
244
286
  options[:version] || "latest"
245
287
  end
data/lib/mrsk/cli/base.rb CHANGED
@@ -6,8 +6,6 @@ module Mrsk::Cli
6
6
  class Base < Thor
7
7
  include SSHKit::DSL
8
8
 
9
- class LockError < StandardError; end
10
-
11
9
  def self.exit_on_failure?() true end
12
10
 
13
11
  class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
@@ -79,32 +77,55 @@ module Mrsk::Cli
79
77
  end
80
78
 
81
79
  def with_lock
82
- acquire_lock
80
+ if MRSK.holding_lock?
81
+ yield
82
+ else
83
+ acquire_lock
84
+
85
+ begin
86
+ yield
87
+ rescue
88
+ if MRSK.hold_lock_on_error?
89
+ error " \e[31mDeploy lock was not released\e[0m"
90
+ else
91
+ release_lock
92
+ end
93
+
94
+ raise
95
+ end
83
96
 
84
- yield
85
- ensure
86
- release_lock
97
+ release_lock
98
+ end
87
99
  end
88
100
 
89
101
  def acquire_lock
90
- if MRSK.lock_count == 0
91
- say "Acquiring the deploy lock"
92
- on(MRSK.primary_host) { execute *MRSK.lock.acquire("Automatic deploy lock", MRSK.config.version) }
93
- end
94
- MRSK.lock_count += 1
102
+ say "Acquiring the deploy lock"
103
+ on(MRSK.primary_host) { execute *MRSK.lock.acquire("Automatic deploy lock", MRSK.config.version) }
104
+
105
+ MRSK.holding_lock = true
95
106
  rescue SSHKit::Runner::ExecuteError => e
96
107
  if e.message =~ /cannot create directory/
97
- invoke "mrsk:cli:lock:status", []
108
+ on(MRSK.primary_host) { execute *MRSK.lock.status }
109
+ raise LockError, "Deploy lock found"
110
+ else
111
+ raise e
98
112
  end
99
-
100
- raise LockError, "Deploy lock found"
101
113
  end
102
114
 
103
115
  def release_lock
104
- MRSK.lock_count -= 1
105
- if MRSK.lock_count == 0
106
- say "Releasing the deploy lock"
107
- on(MRSK.primary_host) { execute *MRSK.lock.release }
116
+ say "Releasing the deploy lock"
117
+ on(MRSK.primary_host) { execute *MRSK.lock.release }
118
+
119
+ MRSK.holding_lock = false
120
+ end
121
+
122
+ def hold_lock_on_error
123
+ if MRSK.hold_lock_on_error?
124
+ yield
125
+ else
126
+ MRSK.hold_lock_on_error = true
127
+ yield
128
+ MRSK.hold_lock_on_error = false
108
129
  end
109
130
  end
110
131
  end
@@ -1,4 +1,6 @@
1
1
  class Mrsk::Cli::Build < Mrsk::Cli::Base
2
+ class BuildError < StandardError; end
3
+
2
4
  desc "deliver", "Build app and push app image to registry then pull image on servers"
3
5
  def deliver
4
6
  with_lock do
@@ -14,7 +16,9 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
14
16
 
15
17
  run_locally do
16
18
  begin
17
- MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
19
+ if cli.verify_local_dependencies
20
+ MRSK.with_verbosity(:debug) { execute *MRSK.builder.push }
21
+ end
18
22
  rescue SSHKit::Command::Failed => e
19
23
  if e.message =~ /(no builder)|(no such file or directory)/
20
24
  error "Missing compatible builder, so creating a new one first"
@@ -77,4 +81,22 @@ class Mrsk::Cli::Build < Mrsk::Cli::Base
77
81
  puts capture(*MRSK.builder.info)
78
82
  end
79
83
  end
84
+
85
+
86
+ desc "", "" # Really a private method, but needed to be invoked from #push
87
+ def verify_local_dependencies
88
+ run_locally do
89
+ begin
90
+ execute *MRSK.builder.ensure_local_dependencies_installed
91
+ rescue SSHKit::Command::Failed => e
92
+ build_error = e.message =~ /command not found/ ?
93
+ "Docker is not installed locally" :
94
+ "Docker buildx plugin is not installed locally"
95
+
96
+ raise BuildError, build_error
97
+ end
98
+ end
99
+
100
+ true
101
+ end
80
102
  end
@@ -1,8 +1,4 @@
1
1
  class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
2
- MAX_ATTEMPTS = 7
3
-
4
- class HealthcheckError < StandardError; end
5
-
6
2
  default_command :perform
7
3
 
8
4
  desc "perform", "Health check current app version"
@@ -10,37 +6,10 @@ class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
10
6
  on(MRSK.primary_host) do
11
7
  begin
12
8
  execute *MRSK.healthcheck.run
13
-
14
- target = "Health check against #{MRSK.config.healthcheck["path"]}"
15
- attempt = 1
16
-
17
- begin
18
- status = capture_with_info(*MRSK.healthcheck.curl)
19
-
20
- if status == "200"
21
- info "#{target} succeeded with 200 OK!"
22
- else
23
- raise HealthcheckError, "#{target} failed with status #{status}"
24
- end
25
- rescue SSHKit::Command::Failed
26
- if attempt <= MAX_ATTEMPTS
27
- info "#{target} failed to respond, retrying in #{attempt}s..."
28
- sleep attempt
29
- attempt += 1
30
-
31
- retry
32
- else
33
- raise
34
- end
35
- end
36
- rescue SSHKit::Command::Failed, HealthcheckError => e
9
+ Mrsk::Utils::HealthcheckPoller.wait_for_healthy { capture_with_info(*MRSK.healthcheck.status) }
10
+ rescue Mrsk::Utils::HealthcheckPoller::HealthcheckError => e
37
11
  error capture_with_info(*MRSK.healthcheck.logs)
38
-
39
- if e.message =~ /curl/
40
- raise SSHKit::Command::Failed, "#{target} failed to return 200 OK!"
41
- else
42
- raise
43
- end
12
+ raise
44
13
  ensure
45
14
  execute *MRSK.healthcheck.stop, raise_on_non_zero_exit: false
46
15
  execute *MRSK.healthcheck.remove, raise_on_non_zero_exit: false
data/lib/mrsk/cli/lock.rb CHANGED
@@ -12,7 +12,7 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
12
12
  message = options[:message]
13
13
  handle_missing_lock do
14
14
  on(MRSK.primary_host) { execute *MRSK.lock.acquire(message, MRSK.config.version) }
15
- say "Set the deploy lock"
15
+ say "Acquired the deploy lock"
16
16
  end
17
17
  end
18
18
 
@@ -20,7 +20,7 @@ class Mrsk::Cli::Lock < Mrsk::Cli::Base
20
20
  def release
21
21
  handle_missing_lock do
22
22
  on(MRSK.primary_host) { execute *MRSK.lock.release }
23
- say "Removed the deploy lock"
23
+ say "Released the deploy lock"
24
24
  end
25
25
  end
26
26