mrsk 0.10.1 → 0.11.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: 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.