mrsk 0.10.1 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -87,6 +87,10 @@ class Mrsk::Configuration
87
87
  roles.select(&:running_traefik?).flat_map(&:hosts).uniq
88
88
  end
89
89
 
90
+ def boot
91
+ Mrsk::Configuration::Boot.new(config: self)
92
+ end
93
+
90
94
 
91
95
  def repository
92
96
  [ raw_config.registry["server"], image ].compact.join("/")
@@ -143,6 +147,8 @@ class Mrsk::Configuration
143
147
  if raw_config.ssh.present? && raw_config.ssh["proxy"]
144
148
  Net::SSH::Proxy::Jump.new \
145
149
  raw_config.ssh["proxy"].include?("@") ? raw_config.ssh["proxy"] : "root@#{raw_config.ssh["proxy"]}"
150
+ elsif raw_config.ssh.present? && raw_config.ssh["proxy_command"]
151
+ Net::SSH::Proxy::Command.new(raw_config.ssh["proxy_command"])
146
152
  end
147
153
  end
148
154
 
@@ -156,7 +162,7 @@ class Mrsk::Configuration
156
162
  end
157
163
 
158
164
  def healthcheck
159
- { "path" => "/up", "port" => 3000 }.merge(raw_config.healthcheck || {})
165
+ { "path" => "/up", "port" => 3000, "max_attempts" => 7 }.merge(raw_config.healthcheck || {})
160
166
  end
161
167
 
162
168
  def readiness_delay
@@ -2,8 +2,8 @@ require "sshkit"
2
2
  require "sshkit/dsl"
3
3
 
4
4
  class SSHKit::Backend::Abstract
5
- def capture_with_info(*args)
6
- capture(*args, verbosity: Logger::INFO)
5
+ def capture_with_info(*args, **kwargs)
6
+ capture(*args, **kwargs, verbosity: Logger::INFO)
7
7
  end
8
8
 
9
9
  def puts_by_host(host, output, type: "App")
@@ -0,0 +1,39 @@
1
+ class Mrsk::Utils::HealthcheckPoller
2
+ TRAEFIK_HEALTHY_DELAY = 2
3
+
4
+ class HealthcheckError < StandardError; end
5
+
6
+ class << self
7
+ def wait_for_healthy(pause_after_ready: false, &block)
8
+ attempt = 1
9
+ max_attempts = MRSK.config.healthcheck["max_attempts"]
10
+
11
+ begin
12
+ case status = block.call
13
+ when "healthy"
14
+ sleep TRAEFIK_HEALTHY_DELAY if pause_after_ready
15
+ when "running" # No health check configured
16
+ sleep MRSK.config.readiness_delay if pause_after_ready
17
+ else
18
+ raise HealthcheckError, "container not ready (#{status})"
19
+ end
20
+ rescue HealthcheckError => e
21
+ if attempt <= max_attempts
22
+ info "#{e.message}, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
23
+ sleep attempt
24
+ attempt += 1
25
+ retry
26
+ else
27
+ raise
28
+ end
29
+ end
30
+
31
+ info "Container is healthy!"
32
+ end
33
+
34
+ private
35
+ def info(message)
36
+ SSHKit.config.output.info(message)
37
+ end
38
+ end
39
+ end
@@ -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
@@ -1,12 +1,15 @@
1
1
  module Mrsk::Utils
2
2
  extend self
3
3
 
4
+ DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX = /\$(?!{[^\}]*\})/
5
+
4
6
  # 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)
7
+ def argumentize(argument, attributes, sensitive: false)
6
8
  Array(attributes).flat_map do |key, value|
7
9
  if value.present?
8
- escaped_pair = [ key, escape_shell_value(value) ].join("=")
9
- [ argument, redacted ? redact(escaped_pair) : escaped_pair ]
10
+ attr = "#{key}=#{escape_shell_value(value)}"
11
+ attr = self.sensitive(attr, redaction: "#{key}=[REDACTED]") if sensitive
12
+ [ argument, attr]
10
13
  else
11
14
  [ argument, key ]
12
15
  end
@@ -17,7 +20,7 @@ module Mrsk::Utils
17
20
  # but redacts and expands secrets.
18
21
  def argumentize_env_with_secrets(env)
19
22
  if (secrets = env["secret"]).present?
20
- argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, redacted: true) + argumentize("-e", env["clear"])
23
+ argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, sensitive: true) + argumentize("-e", env["clear"])
21
24
  else
22
25
  argumentize "-e", env.fetch("clear", env)
23
26
  end
@@ -39,14 +42,44 @@ module Mrsk::Utils
39
42
  args.flat_map { |key, value| value.try(:map) { |entry| [key, entry] } || [ [ key, value ] ] }
40
43
  end
41
44
 
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
45
+ # Marks sensitive values for redaction in logs and human-visible output.
46
+ # Pass `redaction:` to change the default `"[REDACTED]"` redaction, e.g.
47
+ # `sensitive "#{arg}=#{secret}", redaction: "#{arg}=xxxx"
48
+ def sensitive(...)
49
+ Mrsk::Utils::Sensitive.new(...)
50
+ end
51
+
52
+ def redacted(value)
53
+ case
54
+ when value.respond_to?(:redaction)
55
+ value.redaction
56
+ when value.respond_to?(:transform_values)
57
+ value.transform_values { |value| redacted value }
58
+ when value.respond_to?(:map)
59
+ value.map { |element| redacted element }
60
+ else
61
+ value
62
+ end
63
+ end
64
+
65
+ def unredacted(value)
66
+ case
67
+ when value.respond_to?(:unredacted)
68
+ value.unredacted
69
+ when value.respond_to?(:transform_values)
70
+ value.transform_values { |value| unredacted value }
71
+ when value.respond_to?(:map)
72
+ value.map { |element| unredacted element }
73
+ else
74
+ value
75
+ end
45
76
  end
46
77
 
47
78
  # Escape a value to make it safe for shell use.
48
79
  def escape_shell_value(value)
49
- value.to_s.dump.gsub(/`/, '\\\\`')
80
+ value.to_s.dump
81
+ .gsub(/`/, '\\\\`')
82
+ .gsub(DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX, '\$')
50
83
  end
51
84
 
52
85
  # Abbreviate a git revhash for concise display
data/lib/mrsk/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.10.1"
2
+ VERSION = "0.12.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.12.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-05-02 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
@@ -187,6 +201,7 @@ files:
187
201
  - lib/mrsk/commands/builder/multiarch/remote.rb
188
202
  - lib/mrsk/commands/builder/native.rb
189
203
  - lib/mrsk/commands/builder/native/remote.rb
204
+ - lib/mrsk/commands/docker.rb
190
205
  - lib/mrsk/commands/healthcheck.rb
191
206
  - lib/mrsk/commands/lock.rb
192
207
  - lib/mrsk/commands/prune.rb
@@ -194,9 +209,12 @@ files:
194
209
  - lib/mrsk/commands/traefik.rb
195
210
  - lib/mrsk/configuration.rb
196
211
  - lib/mrsk/configuration/accessory.rb
212
+ - lib/mrsk/configuration/boot.rb
197
213
  - lib/mrsk/configuration/role.rb
198
214
  - lib/mrsk/sshkit_with_ext.rb
199
215
  - lib/mrsk/utils.rb
216
+ - lib/mrsk/utils/healthcheck_poller.rb
217
+ - lib/mrsk/utils/sensitive.rb
200
218
  - lib/mrsk/version.rb
201
219
  homepage: https://github.com/rails/mrsk
202
220
  licenses:
@@ -217,7 +235,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
217
235
  - !ruby/object:Gem::Version
218
236
  version: '0'
219
237
  requirements: []
220
- rubygems_version: 3.4.8
238
+ rubygems_version: 3.4.10
221
239
  signing_key:
222
240
  specification_version: 4
223
241
  summary: Deploy web apps in containers to servers running Docker with zero downtime.