mrsk 0.10.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d53c75ef2fad59a884ae2e8a04fff57a68330faf7522ef7bb620affc48a803d6
4
- data.tar.gz: b91edccb441e562d24680034df00fbcdf3518aadd570784fbf31d9f2c07a9ebd
3
+ metadata.gz: 3a8c9aac5626518667d963bd8c0b8e757be3dfe2cfed585f1e7d0783e2e017a4
4
+ data.tar.gz: 9e7c336b776e518d98a3a25a7a3c47858cf40f2d8398c881d8c3ec3f224d1222
5
5
  SHA512:
6
- metadata.gz: d7037c8e7a41acd4cb199b29251245f3e99f2c9cf62ca99c392ba7dfb77defde655f616161e6ef82319ea14438fb27992a8a88861bb49a2a1efe1771f92d60e0
7
- data.tar.gz: a0810f91aeb9332ea8401d2a7f8ca847f78a37a85f67b71753734fe2f0ef298435f813fc07fa156bb755e05170afd09f2bdc384504041276ca2502b19ab1d7a6
6
+ metadata.gz: 3889961797501b12da9ca958021b98a5a2b1167943813156241e5da44cc35b3d6ace84b56f4850b50e351cd995be099821d791e2136001f37ba700f8310e5128
7
+ data.tar.gz: a15e1ad08dfc1c8471f0fa341d865ffc237d88aae229cd250b49feb1d4ca10e750ddc2dec85c392974017c13c6d7b2f583fb3af85e840d5dddcbed19c8f90712
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
 
@@ -439,9 +458,9 @@ RUN --mount=type=secret,id=GITHUB_TOKEN \
439
458
  rm -rf /usr/local/bundle/cache
440
459
  ```
441
460
 
442
- ### Using command arguments for Traefik
461
+ ### Traefik command arguments
443
462
 
444
- You can customize the traefik command line:
463
+ Customize the Traefik command line using `args`:
445
464
 
446
465
  ```yaml
447
466
  traefik:
@@ -450,37 +469,70 @@ traefik:
450
469
  accesslog.format: json
451
470
  ```
452
471
 
453
- This will start the traefik container with `--accesslog=true accesslog.format=json`.
472
+ This starts the Traefik container with `--accesslog=true --accesslog.format=json` arguments.
454
473
 
455
- ### Traefik's host port binding
474
+ ### Traefik host port binding
456
475
 
457
- By default Traefik binds to port 80 of the host machine, it can be configured to use an alternative port:
476
+ Traefik binds to port 80 by default. Specify an alternative port using `host_port`:
458
477
 
459
478
  ```yaml
460
479
  traefik:
461
480
  host_port: 8080
462
481
  ```
463
482
 
464
- ### Configure docker options for traefik
483
+ ### Traefik version, upgrades, and custom images
484
+
485
+ MRSK runs the traefik:v2.9 image to track Traefik 2.9.x releases.
465
486
 
466
- We allow users to pass additional docker options to the trafik container like
487
+ To pin Traefik to a specific version or an image published to your registry,
488
+ specify `image`:
489
+
490
+ ```yaml
491
+ traefik:
492
+ image: traefik:v2.10.0-rc1
493
+ ```
494
+
495
+ This is useful for downgrading Traefik if there's an unexpected breaking
496
+ change in a minor version release, upgrading Traefik to test forthcoming
497
+ releases, or running your own Traefik-derived image.
498
+
499
+ MRSK has not been tested for compatibility with Traefik 3 betas. Please do!
500
+
501
+ ### Traefik container configuration
502
+
503
+ Pass additional Docker configuration for the Traefik container using `options`:
467
504
 
468
505
  ```yaml
469
506
  traefik:
470
507
  options:
471
508
  publish:
472
- - 8080:8080
509
+ - 8080:8080
473
510
  volumes:
474
- - /tmp/example.json:/tmp/example.json
511
+ - /tmp/example.json:/tmp/example.json
475
512
  memory: 512m
476
513
  ```
477
514
 
478
- This will start the traefik container with a command like: `docker run ... --volume /tmp/example.json:/tmp/example.json --publish 8080:8080 `
515
+ This starts the Traefik container with `--volume /tmp/example.json:/tmp/example.json --publish 8080:8080 --memory 512m` arguments to `docker run`.
479
516
 
517
+ ### Traefik container lables
480
518
 
481
- ### Configure alternate entrypoints for traefik
519
+ Add labels to Traefik Docker container.
482
520
 
483
- You can configure multiple entrypoints for traefik like so:
521
+ ```yaml
522
+ traefik:
523
+ lables:
524
+ - traefik.enable: true
525
+ - traefik.http.routers.dashboard.rule: Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))
526
+ - traefik.http.routers.dashboard.service: api@internal
527
+ - traefik.http.routers.dashboard.middlewares: auth
528
+ - traefik.http.middlewares.auth.basicauth.users: test:$2y$05$H2o72tMaO.TwY1wNQUV1K.fhjRgLHRDWohFvUZOJHBEtUXNKrqUKi # test:password
529
+ ```
530
+
531
+ This labels Traefik container with `--label traefik.http.routers.dashboard.middlewares=\"auth\"` and so on.
532
+
533
+ ### Traefik alternate entrypoints
534
+
535
+ You can configure multiple entrypoints for Traefik like so:
484
536
 
485
537
  ```yaml
486
538
  service: myservice
@@ -540,7 +592,7 @@ accessories:
540
592
  memory: "2GB"
541
593
  redis:
542
594
  image: redis:latest
543
- role:
595
+ roles:
544
596
  - web
545
597
  port: "36379:6379"
546
598
  volumes:
@@ -610,18 +662,21 @@ That'll post a line like follows to a preconfigured chatbot in Basecamp:
610
662
  [My App] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
611
663
  ```
612
664
 
613
- ### Using custom healthcheck path or port
665
+ ### Custom healthcheck
614
666
 
615
- MRSK defaults to checking the health of your application again `/up` on port 3000. You can tailor both with the `healthcheck` setting:
667
+ MRSK defaults to checking the health of your application again `/up` on port 3000 up to 7 times. You can tailor the behaviour with the `healthcheck` setting:
616
668
 
617
669
  ```yaml
618
670
  healthcheck:
619
671
  path: /healthz
620
672
  port: 4000
673
+ max_attempts: 7
621
674
  ```
622
675
 
623
676
  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
677
 
678
+ The healthcheck also 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.
679
+
625
680
  ## Commands
626
681
 
627
682
  ### Running commands on servers
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"
@@ -82,8 +80,11 @@ module Mrsk::Cli
82
80
  acquire_lock
83
81
 
84
82
  yield
85
- ensure
83
+
86
84
  release_lock
85
+ rescue
86
+ error " \e[31mDeploy lock was not released\e[0m" if MRSK.lock_count > 0
87
+ raise
87
88
  end
88
89
 
89
90
  def acquire_lock
@@ -95,9 +96,10 @@ module Mrsk::Cli
95
96
  rescue SSHKit::Runner::ExecuteError => e
96
97
  if e.message =~ /cannot create directory/
97
98
  invoke "mrsk:cli:lock:status", []
99
+ raise LockError, "Deploy lock found"
100
+ else
101
+ raise e
98
102
  end
99
-
100
- raise LockError, "Deploy lock found"
101
103
  end
102
104
 
103
105
  def release_lock
@@ -1,5 +1,4 @@
1
1
  class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
2
- MAX_ATTEMPTS = 7
3
2
 
4
3
  class HealthcheckError < StandardError; end
5
4
 
@@ -13,6 +12,7 @@ class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
13
12
 
14
13
  target = "Health check against #{MRSK.config.healthcheck["path"]}"
15
14
  attempt = 1
15
+ max_attempts = MRSK.config.healthcheck["max_attempts"]
16
16
 
17
17
  begin
18
18
  status = capture_with_info(*MRSK.healthcheck.curl)
@@ -23,8 +23,8 @@ class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
23
23
  raise HealthcheckError, "#{target} failed with status #{status}"
24
24
  end
25
25
  rescue SSHKit::Command::Failed
26
- if attempt <= MAX_ATTEMPTS
27
- info "#{target} failed to respond, retrying in #{attempt}s..."
26
+ if attempt <= max_attempts
27
+ info "#{target} failed to respond, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
28
28
  sleep attempt
29
29
  attempt += 1
30
30
 
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
 
data/lib/mrsk/cli/main.rb CHANGED
@@ -77,21 +77,26 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
77
77
  with_lock do
78
78
  MRSK.config.version = version
79
79
 
80
- if container_name_available?(MRSK.config.service_with_version)
80
+ if container_available?(version)
81
81
  say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
82
82
 
83
83
  cli = self
84
84
  old_version = nil
85
85
 
86
86
  on(MRSK.hosts) do |host|
87
- old_version = capture_with_info(*MRSK.app.current_running_version).strip.presence
87
+ roles = MRSK.roles_on(host)
88
88
 
89
- execute *MRSK.app.start
89
+ roles.each do |role|
90
+ app = MRSK.app(role: role)
91
+ old_version = capture_with_info(*app.current_running_version).strip.presence
90
92
 
91
- if old_version
92
- sleep MRSK.config.readiness_delay
93
+ execute *app.start
93
94
 
94
- execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false
95
+ if old_version
96
+ sleep MRSK.config.readiness_delay
97
+
98
+ execute *app.stop(version: old_version), raise_on_non_zero_exit: false
99
+ end
95
100
  end
96
101
  end
97
102
 
@@ -119,7 +124,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
119
124
  desc "config", "Show combined config (including secrets!)"
120
125
  def config
121
126
  run_locally do
122
- puts MRSK.config.to_h.to_yaml
127
+ puts Mrsk::Utils.redacted(MRSK.config.to_h).to_yaml
123
128
  end
124
129
  end
125
130
 
@@ -214,10 +219,15 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
214
219
  subcommand "lock", Mrsk::Cli::Lock
215
220
 
216
221
  private
217
- def container_name_available?(container_name, host: MRSK.primary_host)
218
- container_names = nil
219
- on(host) { container_names = capture_with_info(*MRSK.app.list_container_names).split("\n") }
220
- Array(container_names).include?(container_name)
222
+ def container_available?(version, host: MRSK.primary_host)
223
+ available = nil
224
+
225
+ on(host) do
226
+ first_role = MRSK.roles_on(host).first
227
+ available = capture_with_info(*MRSK.app(role: first_role).container_id_for_version(version)).present?
228
+ end
229
+
230
+ available
221
231
  end
222
232
 
223
233
  def deploy_options
@@ -2,7 +2,10 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
2
2
  desc "boot", "Boot Traefik on servers"
3
3
  def boot
4
4
  with_lock do
5
- on(MRSK.traefik_hosts) { execute *MRSK.traefik.run, raise_on_non_zero_exit: false }
5
+ on(MRSK.traefik_hosts) do
6
+ execute *MRSK.registry.login
7
+ execute *MRSK.traefik.run, raise_on_non_zero_exit: false
8
+ end
6
9
  end
7
10
  end
8
11
 
data/lib/mrsk/cli.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module Mrsk::Cli
2
+ class LockError < StandardError; end
2
3
  end
3
4
 
4
5
  # SSHKit uses instance eval, so we need a global const for ergonomics
@@ -1,6 +1,6 @@
1
1
  module Mrsk::Commands
2
2
  class Base
3
- delegate :redact, :argumentize, to: Mrsk::Utils
3
+ delegate :sensitive, :argumentize, to: Mrsk::Utils
4
4
 
5
5
  attr_accessor :config
6
6
 
@@ -28,7 +28,7 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
28
28
  end
29
29
 
30
30
  def build_args
31
- argumentize "--build-arg", args, redacted: true
31
+ argumentize "--build-arg", args, sensitive: true
32
32
  end
33
33
 
34
34
  def build_secrets
@@ -2,7 +2,7 @@ class Mrsk::Commands::Registry < Mrsk::Commands::Base
2
2
  delegate :registry, to: :config
3
3
 
4
4
  def login
5
- docker :login, registry["server"], "-u", redact(lookup("username")), "-p", redact(lookup("password"))
5
+ docker :login, registry["server"], "-u", sensitive(lookup("username")), "-p", sensitive(lookup("password"))
6
6
  end
7
7
 
8
8
  def logout
@@ -1,7 +1,7 @@
1
1
  class Mrsk::Commands::Traefik < Mrsk::Commands::Base
2
- delegate :optionize, to: Mrsk::Utils
2
+ delegate :argumentize, :optionize, to: Mrsk::Utils
3
3
 
4
- IMAGE = "traefik:v2.9.9"
4
+ DEFAULT_IMAGE = "traefik:v2.9"
5
5
  CONTAINER_PORT = 80
6
6
 
7
7
  def run
@@ -11,8 +11,9 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
11
11
  "--publish", port,
12
12
  "--volume", "/var/run/docker.sock:/var/run/docker.sock",
13
13
  *config.logging_args,
14
+ *label_args,
14
15
  *docker_options_args,
15
- IMAGE,
16
+ image,
16
17
  "--providers.docker",
17
18
  "--log.level=DEBUG",
18
19
  *cmd_option_args
@@ -56,6 +57,18 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
56
57
  end
57
58
 
58
59
  private
60
+ def label_args
61
+ argumentize "--label", labels
62
+ end
63
+
64
+ def labels
65
+ config.traefik["labels"] || []
66
+ end
67
+
68
+ def image
69
+ config.traefik.fetch("image") { DEFAULT_IMAGE }
70
+ end
71
+
59
72
  def docker_options_args
60
73
  optionize(config.traefik["options"] || {})
61
74
  end
@@ -74,18 +74,22 @@ class Mrsk::Configuration::Role
74
74
  def traefik_labels
75
75
  if running_traefik?
76
76
  {
77
- "traefik.http.routers.#{config.service}.rule" => "PathPrefix(`/`)",
78
- "traefik.http.services.#{config.service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
79
- "traefik.http.services.#{config.service}.loadbalancer.healthcheck.interval" => "1s",
80
- "traefik.http.middlewares.#{config.service}-retry.retry.attempts" => "5",
81
- "traefik.http.middlewares.#{config.service}-retry.retry.initialinterval" => "500ms",
82
- "traefik.http.routers.#{config.service}.middlewares" => "#{config.service}-retry@docker"
77
+ "traefik.http.routers.#{traefik_service}.rule" => "PathPrefix(`/`)",
78
+ "traefik.http.services.#{traefik_service}.loadbalancer.healthcheck.path" => config.healthcheck["path"],
79
+ "traefik.http.services.#{traefik_service}.loadbalancer.healthcheck.interval" => "1s",
80
+ "traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
81
+ "traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
82
+ "traefik.http.routers.#{traefik_service}.middlewares" => "#{traefik_service}-retry@docker"
83
83
  }
84
84
  else
85
85
  {}
86
86
  end
87
87
  end
88
88
 
89
+ def traefik_service
90
+ [ config.service, name, config.destination ].compact.join("-")
91
+ end
92
+
89
93
  def custom_labels
90
94
  Hash.new.tap do |labels|
91
95
  labels.merge!(config.labels) if config.labels.present?
@@ -143,6 +143,8 @@ class Mrsk::Configuration
143
143
  if raw_config.ssh.present? && raw_config.ssh["proxy"]
144
144
  Net::SSH::Proxy::Jump.new \
145
145
  raw_config.ssh["proxy"].include?("@") ? raw_config.ssh["proxy"] : "root@#{raw_config.ssh["proxy"]}"
146
+ elsif raw_config.ssh.present? && raw_config.ssh["proxy_command"]
147
+ Net::SSH::Proxy::Command.new(raw_config.ssh["proxy_command"])
146
148
  end
147
149
  end
148
150
 
@@ -156,7 +158,7 @@ class Mrsk::Configuration
156
158
  end
157
159
 
158
160
  def healthcheck
159
- { "path" => "/up", "port" => 3000 }.merge(raw_config.healthcheck || {})
161
+ { "path" => "/up", "port" => 3000, "max_attempts" => 7 }.merge(raw_config.healthcheck || {})
160
162
  end
161
163
 
162
164
  def readiness_delay
@@ -0,0 +1,19 @@
1
+ require "active_support/core_ext/module/delegation"
2
+
3
+ class Mrsk::Utils::Sensitive
4
+ # So SSHKit knows to redact these values.
5
+ include SSHKit::Redaction
6
+
7
+ attr_reader :unredacted, :redaction
8
+ delegate :to_s, to: :unredacted
9
+ delegate :inspect, to: :redaction
10
+
11
+ def initialize(value, redaction: "[REDACTED]")
12
+ @unredacted, @redaction = value, redaction
13
+ end
14
+
15
+ # Sensitive values won't leak into YAML output.
16
+ def encode_with(coder)
17
+ coder.represent_scalar nil, redaction
18
+ end
19
+ end
data/lib/mrsk/utils.rb CHANGED
@@ -2,11 +2,12 @@ module Mrsk::Utils
2
2
  extend self
3
3
 
4
4
  # Return a list of escaped shell arguments using the same named argument against the passed attributes (hash or array).
5
- def argumentize(argument, attributes, redacted: false)
5
+ def argumentize(argument, attributes, sensitive: false)
6
6
  Array(attributes).flat_map do |key, value|
7
7
  if value.present?
8
- escaped_pair = [ key, escape_shell_value(value) ].join("=")
9
- [ argument, redacted ? redact(escaped_pair) : escaped_pair ]
8
+ attr = "#{key}=#{escape_shell_value(value)}"
9
+ attr = self.sensitive(attr, redaction: "#{key}=[REDACTED]") if sensitive
10
+ [ argument, attr]
10
11
  else
11
12
  [ argument, key ]
12
13
  end
@@ -17,7 +18,7 @@ module Mrsk::Utils
17
18
  # but redacts and expands secrets.
18
19
  def argumentize_env_with_secrets(env)
19
20
  if (secrets = env["secret"]).present?
20
- argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, redacted: true) + argumentize("-e", env["clear"])
21
+ argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, sensitive: true) + argumentize("-e", env["clear"])
21
22
  else
22
23
  argumentize "-e", env.fetch("clear", env)
23
24
  end
@@ -39,9 +40,37 @@ module Mrsk::Utils
39
40
  args.flat_map { |key, value| value.try(:map) { |entry| [key, entry] } || [ [ key, value ] ] }
40
41
  end
41
42
 
42
- # Copied from SSHKit::Backend::Abstract#redact to be available inside Commands classes
43
- def redact(arg) # Used in execute_command to hide redact() args a user passes in
44
- arg.to_s.extend(SSHKit::Redaction) # to_s due to our inability to extend Integer, etc
43
+ # Marks sensitive values for redaction in logs and human-visible output.
44
+ # Pass `redaction:` to change the default `"[REDACTED]"` redaction, e.g.
45
+ # `sensitive "#{arg}=#{secret}", redaction: "#{arg}=xxxx"
46
+ def sensitive(...)
47
+ Mrsk::Utils::Sensitive.new(...)
48
+ end
49
+
50
+ def redacted(value)
51
+ case
52
+ when value.respond_to?(:redaction)
53
+ value.redaction
54
+ when value.respond_to?(:transform_values)
55
+ value.transform_values { |value| redacted value }
56
+ when value.respond_to?(:map)
57
+ value.map { |element| redacted element }
58
+ else
59
+ value
60
+ end
61
+ end
62
+
63
+ def unredacted(value)
64
+ case
65
+ when value.respond_to?(:unredacted)
66
+ value.unredacted
67
+ when value.respond_to?(:transform_values)
68
+ value.transform_values { |value| unredacted value }
69
+ when value.respond_to?(:map)
70
+ value.map { |element| unredacted element }
71
+ else
72
+ value
73
+ end
45
74
  end
46
75
 
47
76
  # Escape a value to make it safe for shell use.
data/lib/mrsk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.10.1"
2
+ VERSION = "0.11.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.10.1
4
+ version: 0.11.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-03-29 00:00:00.000000000 Z
11
+ date: 2023-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.21'
41
+ - !ruby/object:Gem::Dependency
42
+ name: net-ssh
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '7.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '7.0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: thor
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -197,6 +211,7 @@ files:
197
211
  - lib/mrsk/configuration/role.rb
198
212
  - lib/mrsk/sshkit_with_ext.rb
199
213
  - lib/mrsk/utils.rb
214
+ - lib/mrsk/utils/sensitive.rb
200
215
  - lib/mrsk/version.rb
201
216
  homepage: https://github.com/rails/mrsk
202
217
  licenses:
@@ -217,7 +232,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
232
  - !ruby/object:Gem::Version
218
233
  version: '0'
219
234
  requirements: []
220
- rubygems_version: 3.4.8
235
+ rubygems_version: 3.4.10
221
236
  signing_key:
222
237
  specification_version: 4
223
238
  summary: Deploy web apps in containers to servers running Docker with zero downtime.