mrsk 0.11.0 → 0.12.1
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 +4 -4
- data/README.md +83 -11
- data/lib/mrsk/cli/app.rb +62 -20
- data/lib/mrsk/cli/base.rb +36 -17
- data/lib/mrsk/cli/build.rb +23 -1
- data/lib/mrsk/cli/healthcheck.rb +4 -34
- data/lib/mrsk/cli/main.rb +60 -30
- data/lib/mrsk/cli/prune.rb +2 -2
- data/lib/mrsk/cli/server.rb +13 -9
- data/lib/mrsk/cli/traefik.rb +1 -1
- data/lib/mrsk/commander.rb +26 -5
- data/lib/mrsk/commands/app.rb +25 -12
- data/lib/mrsk/commands/auditor.rb +22 -20
- data/lib/mrsk/commands/base.rb +3 -0
- data/lib/mrsk/commands/builder/base.rb +9 -2
- data/lib/mrsk/commands/builder.rb +21 -1
- data/lib/mrsk/commands/docker.rb +21 -0
- data/lib/mrsk/commands/healthcheck.rb +7 -2
- data/lib/mrsk/commands/lock.rb +2 -2
- data/lib/mrsk/commands/prune.rb +12 -4
- data/lib/mrsk/configuration/boot.rb +20 -0
- data/lib/mrsk/configuration/role.rb +22 -2
- data/lib/mrsk/configuration.rb +4 -0
- data/lib/mrsk/sshkit_with_ext.rb +42 -2
- data/lib/mrsk/utils/healthcheck_poller.rb +39 -0
- data/lib/mrsk/utils.rb +5 -1
- data/lib/mrsk/version.rb +1 -1
- metadata +5 -2
data/lib/mrsk/commander.rb
CHANGED
@@ -2,11 +2,12 @@ require "active_support/core_ext/enumerable"
|
|
2
2
|
require "active_support/core_ext/module/delegation"
|
3
3
|
|
4
4
|
class Mrsk::Commander
|
5
|
-
attr_accessor :verbosity, :
|
5
|
+
attr_accessor :verbosity, :holding_lock, :hold_lock_on_error
|
6
6
|
|
7
7
|
def initialize
|
8
8
|
self.verbosity = :info
|
9
|
-
self.
|
9
|
+
self.holding_lock = false
|
10
|
+
self.hold_lock_on_error = false
|
10
11
|
end
|
11
12
|
|
12
13
|
def config
|
@@ -35,7 +36,7 @@ class Mrsk::Commander
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def primary_host
|
38
|
-
specific_hosts&.first || config.primary_web_host
|
39
|
+
specific_hosts&.first || specific_roles&.first&.primary_host || config.primary_web_host
|
39
40
|
end
|
40
41
|
|
41
42
|
def roles
|
@@ -50,6 +51,14 @@ class Mrsk::Commander
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
54
|
+
def boot_strategy
|
55
|
+
if config.boot.limit.present?
|
56
|
+
{ in: :groups, limit: config.boot.limit, wait: config.boot.wait }
|
57
|
+
else
|
58
|
+
{}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
53
62
|
def roles_on(host)
|
54
63
|
roles.select { |role| role.hosts.include?(host.to_s) }.map(&:name)
|
55
64
|
end
|
@@ -75,14 +84,18 @@ class Mrsk::Commander
|
|
75
84
|
Mrsk::Commands::Accessory.new(config, name: name)
|
76
85
|
end
|
77
86
|
|
78
|
-
def auditor(
|
79
|
-
Mrsk::Commands::Auditor.new(config,
|
87
|
+
def auditor(**details)
|
88
|
+
Mrsk::Commands::Auditor.new(config, **details)
|
80
89
|
end
|
81
90
|
|
82
91
|
def builder
|
83
92
|
@builder ||= Mrsk::Commands::Builder.new(config)
|
84
93
|
end
|
85
94
|
|
95
|
+
def docker
|
96
|
+
@docker ||= Mrsk::Commands::Docker.new(config)
|
97
|
+
end
|
98
|
+
|
86
99
|
def healthcheck
|
87
100
|
@healthcheck ||= Mrsk::Commands::Healthcheck.new(config)
|
88
101
|
end
|
@@ -115,6 +128,14 @@ class Mrsk::Commander
|
|
115
128
|
SSHKit.config.output_verbosity = old_level
|
116
129
|
end
|
117
130
|
|
131
|
+
def holding_lock?
|
132
|
+
self.holding_lock
|
133
|
+
end
|
134
|
+
|
135
|
+
def hold_lock_on_error?
|
136
|
+
self.hold_lock_on_error
|
137
|
+
end
|
138
|
+
|
118
139
|
private
|
119
140
|
# Lazy setup of SSHKit
|
120
141
|
def configure_sshkit_with(config)
|
data/lib/mrsk/commands/app.rb
CHANGED
@@ -15,6 +15,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
15
15
|
"--name", container_name,
|
16
16
|
"-e", "MRSK_CONTAINER_NAME=\"#{container_name}\"",
|
17
17
|
*role.env_args,
|
18
|
+
*role.health_check_args,
|
18
19
|
*config.logging_args,
|
19
20
|
*config.volume_args,
|
20
21
|
*role.label_args,
|
@@ -27,9 +28,13 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
27
28
|
docker :start, container_name
|
28
29
|
end
|
29
30
|
|
31
|
+
def status(version:)
|
32
|
+
pipe container_id_for_version(version), xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
|
33
|
+
end
|
34
|
+
|
30
35
|
def stop(version: nil)
|
31
36
|
pipe \
|
32
|
-
version ? container_id_for_version(version) :
|
37
|
+
version ? container_id_for_version(version) : current_running_container_id,
|
33
38
|
xargs(config.stop_wait_time ? docker(:stop, "-t", config.stop_wait_time) : docker(:stop))
|
34
39
|
end
|
35
40
|
|
@@ -40,7 +45,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
40
45
|
|
41
46
|
def logs(since: nil, lines: nil, grep: nil)
|
42
47
|
pipe \
|
43
|
-
|
48
|
+
current_running_container_id,
|
44
49
|
"xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
|
45
50
|
("grep '#{grep}'" if grep)
|
46
51
|
end
|
@@ -48,7 +53,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
48
53
|
def follow_logs(host:, grep: nil)
|
49
54
|
run_over_ssh \
|
50
55
|
pipe(
|
51
|
-
|
56
|
+
current_running_container_id,
|
52
57
|
"xargs docker logs --timestamps --tail 10 --follow 2>&1",
|
53
58
|
(%(grep "#{grep}") if grep)
|
54
59
|
),
|
@@ -82,8 +87,8 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
82
87
|
end
|
83
88
|
|
84
89
|
|
85
|
-
def
|
86
|
-
docker :ps, "--quiet", *filter_args
|
90
|
+
def current_running_container_id
|
91
|
+
docker :ps, "--quiet", *filter_args(status: :running), "--latest"
|
87
92
|
end
|
88
93
|
|
89
94
|
def container_id_for_version(version)
|
@@ -91,11 +96,14 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
91
96
|
end
|
92
97
|
|
93
98
|
def current_running_version
|
94
|
-
|
99
|
+
list_versions("--latest", status: :running)
|
100
|
+
end
|
101
|
+
|
102
|
+
def list_versions(*docker_args, status: nil)
|
95
103
|
pipe \
|
96
|
-
docker(:ps, *filter_args, "--format", '"{{.Names}}"'),
|
97
|
-
%(
|
98
|
-
|
104
|
+
docker(:ps, *filter_args(status: status), *docker_args, "--format", '"{{.Names}}"'),
|
105
|
+
%(grep -oE "\\-[^-]+$"), # Extract SHA from "service-role-dest-SHA"
|
106
|
+
%(cut -c 2-)
|
99
107
|
end
|
100
108
|
|
101
109
|
def list_containers
|
@@ -128,20 +136,25 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
128
136
|
docker :image, :prune, "--all", "--force", *filter_args
|
129
137
|
end
|
130
138
|
|
139
|
+
def tag_current_as_latest
|
140
|
+
docker :tag, config.absolute_image, config.latest_image
|
141
|
+
end
|
142
|
+
|
131
143
|
|
132
144
|
private
|
133
145
|
def container_name(version = nil)
|
134
146
|
[ config.service, role, config.destination, version || config.version ].compact.join("-")
|
135
147
|
end
|
136
148
|
|
137
|
-
def filter_args
|
138
|
-
argumentize "--filter", filters
|
149
|
+
def filter_args(status: nil)
|
150
|
+
argumentize "--filter", filters(status: status)
|
139
151
|
end
|
140
152
|
|
141
|
-
def filters
|
153
|
+
def filters(status: nil)
|
142
154
|
[ "label=service=#{config.service}" ].tap do |filters|
|
143
155
|
filters << "label=destination=#{config.destination}" if config.destination
|
144
156
|
filters << "label=role=#{role}" if role
|
157
|
+
filters << "status=#{status}" if status
|
145
158
|
end
|
146
159
|
end
|
147
160
|
end
|
@@ -1,24 +1,24 @@
|
|
1
|
-
require "
|
1
|
+
require "time"
|
2
2
|
|
3
3
|
class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :details
|
5
5
|
|
6
|
-
def initialize(config,
|
6
|
+
def initialize(config, **details)
|
7
7
|
super(config)
|
8
|
-
@
|
8
|
+
@details = default_details.merge(details)
|
9
9
|
end
|
10
10
|
|
11
11
|
# Runs remotely
|
12
|
-
def record(line)
|
12
|
+
def record(line, **details)
|
13
13
|
append \
|
14
|
-
[ :echo,
|
14
|
+
[ :echo, *audit_tags(**details), line ],
|
15
15
|
audit_log_file
|
16
16
|
end
|
17
17
|
|
18
18
|
# Runs locally
|
19
|
-
def broadcast(line)
|
19
|
+
def broadcast(line, **details)
|
20
20
|
if broadcast_cmd = config.audit_broadcast_cmd
|
21
|
-
[ broadcast_cmd,
|
21
|
+
[ broadcast_cmd, *broadcast_args(line, **details), env: env_for(event: line, **details) ]
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -31,27 +31,29 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
|
31
31
|
[ "mrsk", config.service, config.destination, "audit.log" ].compact.join("-")
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
35
|
-
|
34
|
+
def default_details
|
35
|
+
{ recorded_at: Time.now.utc.iso8601,
|
36
|
+
performer: `whoami`.chomp,
|
37
|
+
destination: config.destination }
|
36
38
|
end
|
37
39
|
|
38
|
-
def
|
39
|
-
|
40
|
+
def audit_tags(**details)
|
41
|
+
tags_for **self.details.merge(details)
|
40
42
|
end
|
41
43
|
|
42
|
-
def
|
43
|
-
"'#{
|
44
|
+
def broadcast_args(line, **details)
|
45
|
+
"'#{broadcast_tags(**details).join(" ")} #{line}'"
|
44
46
|
end
|
45
47
|
|
46
|
-
def
|
47
|
-
|
48
|
+
def broadcast_tags(**details)
|
49
|
+
tags_for **self.details.merge(details).except(:recorded_at)
|
48
50
|
end
|
49
51
|
|
50
|
-
def
|
51
|
-
"[#{
|
52
|
+
def tags_for(**details)
|
53
|
+
details.compact.values.map { |value| "[#{value}]" }
|
52
54
|
end
|
53
55
|
|
54
|
-
def
|
55
|
-
"
|
56
|
+
def env_for(**details)
|
57
|
+
self.details.merge(details).compact.transform_keys { |detail| "MRSK_#{detail.upcase}" }
|
56
58
|
end
|
57
59
|
end
|
data/lib/mrsk/commands/base.rb
CHANGED
@@ -2,6 +2,9 @@ module Mrsk::Commands
|
|
2
2
|
class Base
|
3
3
|
delegate :sensitive, :argumentize, to: Mrsk::Utils
|
4
4
|
|
5
|
+
DOCKER_HEALTH_STATUS_FORMAT = "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'"
|
6
|
+
DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
|
7
|
+
|
5
8
|
attr_accessor :config
|
6
9
|
|
7
10
|
def initialize(config)
|
@@ -1,4 +1,7 @@
|
|
1
|
+
|
1
2
|
class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
|
3
|
+
class BuilderError < StandardError; end
|
4
|
+
|
2
5
|
delegate :argumentize, to: Mrsk::Utils
|
3
6
|
|
4
7
|
def clean
|
@@ -7,7 +10,6 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
|
|
7
10
|
|
8
11
|
def pull
|
9
12
|
docker :pull, config.absolute_image
|
10
|
-
docker :pull, config.latest_image
|
11
13
|
end
|
12
14
|
|
13
15
|
def build_options
|
@@ -18,6 +20,7 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
|
|
18
20
|
context
|
19
21
|
end
|
20
22
|
|
23
|
+
|
21
24
|
private
|
22
25
|
def build_tags
|
23
26
|
[ "-t", config.absolute_image, "-t", config.latest_image ]
|
@@ -36,7 +39,11 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
|
|
36
39
|
end
|
37
40
|
|
38
41
|
def build_dockerfile
|
39
|
-
|
42
|
+
if Pathname.new(File.expand_path(dockerfile)).exist?
|
43
|
+
argumentize "--file", dockerfile
|
44
|
+
else
|
45
|
+
raise BuilderError, "Missing #{dockerfile}"
|
46
|
+
end
|
40
47
|
end
|
41
48
|
|
42
49
|
def args
|
@@ -2,7 +2,7 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
|
|
2
2
|
delegate :create, :remove, :push, :clean, :pull, :info, to: :target
|
3
3
|
|
4
4
|
def name
|
5
|
-
target.class.to_s.remove("Mrsk::Commands::Builder::").underscore
|
5
|
+
target.class.to_s.remove("Mrsk::Commands::Builder::").underscore.inquiry
|
6
6
|
end
|
7
7
|
|
8
8
|
def target
|
@@ -33,4 +33,24 @@ class Mrsk::Commands::Builder < Mrsk::Commands::Base
|
|
33
33
|
def multiarch_remote
|
34
34
|
@multiarch_remote ||= Mrsk::Commands::Builder::Multiarch::Remote.new(config)
|
35
35
|
end
|
36
|
+
|
37
|
+
|
38
|
+
def ensure_local_dependencies_installed
|
39
|
+
if name.native?
|
40
|
+
ensure_local_docker_installed
|
41
|
+
else
|
42
|
+
combine \
|
43
|
+
ensure_local_docker_installed,
|
44
|
+
ensure_local_buildx_installed
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
def ensure_local_docker_installed
|
50
|
+
docker "--version"
|
51
|
+
end
|
52
|
+
|
53
|
+
def ensure_local_buildx_installed
|
54
|
+
docker :buildx, "version"
|
55
|
+
end
|
36
56
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Mrsk::Commands::Docker < Mrsk::Commands::Base
|
2
|
+
# Install Docker using the https://github.com/docker/docker-install convenience script.
|
3
|
+
def install
|
4
|
+
pipe [ :curl, "-fsSL", "https://get.docker.com" ], :sh
|
5
|
+
end
|
6
|
+
|
7
|
+
# Checks the Docker client version. Fails if Docker is not installed.
|
8
|
+
def installed?
|
9
|
+
docker "-v"
|
10
|
+
end
|
11
|
+
|
12
|
+
# Checks the Docker server version. Fails if Docker is not running.
|
13
|
+
def running?
|
14
|
+
docker :version
|
15
|
+
end
|
16
|
+
|
17
|
+
# Do we have superuser access to install Docker and start system services?
|
18
|
+
def superuser?
|
19
|
+
[ '[ "${EUID:-$(id -u)}" -eq 0 ]' ]
|
20
|
+
end
|
21
|
+
end
|
@@ -11,14 +11,19 @@ class Mrsk::Commands::Healthcheck < Mrsk::Commands::Base
|
|
11
11
|
"--label", "service=#{container_name}",
|
12
12
|
"-e", "MRSK_CONTAINER_NAME=\"#{container_name}\"",
|
13
13
|
*web.env_args,
|
14
|
+
*web.health_check_args,
|
14
15
|
*config.volume_args,
|
15
16
|
*web.option_args,
|
16
17
|
config.absolute_image,
|
17
18
|
web.cmd
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
|
21
|
+
def status
|
22
|
+
pipe container_id, xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
|
23
|
+
end
|
24
|
+
|
25
|
+
def container_health_log
|
26
|
+
pipe container_id, xargs(docker(:inspect, "--format", DOCKER_HEALTH_LOG_FORMAT))
|
22
27
|
end
|
23
28
|
|
24
29
|
def logs
|
data/lib/mrsk/commands/lock.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
require "active_support/duration"
|
2
|
-
require "
|
2
|
+
require "time"
|
3
3
|
|
4
4
|
class Mrsk::Commands::Lock < Mrsk::Commands::Base
|
5
5
|
def acquire(message, version)
|
@@ -49,7 +49,7 @@ class Mrsk::Commands::Lock < Mrsk::Commands::Base
|
|
49
49
|
|
50
50
|
def lock_details(message, version)
|
51
51
|
<<~DETAILS.strip
|
52
|
-
Locked by: #{locked_by} at #{Time.now.
|
52
|
+
Locked by: #{locked_by} at #{Time.now.utc.iso8601}
|
53
53
|
Version: #{version}
|
54
54
|
Message: #{message}
|
55
55
|
DETAILS
|
data/lib/mrsk/commands/prune.rb
CHANGED
@@ -2,11 +2,19 @@ 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
|
6
|
-
docker :image, :prune, "--
|
5
|
+
def images
|
6
|
+
docker :image, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "dangling=true"
|
7
7
|
end
|
8
8
|
|
9
|
-
def containers(
|
10
|
-
|
9
|
+
def containers(keep_last: 5)
|
10
|
+
pipe \
|
11
|
+
docker(:ps, "-q", "-a", "--filter", "label=service=#{config.service}", *stopped_containers_filters),
|
12
|
+
"tail -n +#{keep_last + 1}",
|
13
|
+
"while read container_id; do docker rm $container_id; done"
|
11
14
|
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def stopped_containers_filters
|
18
|
+
[ "created", "exited", "dead" ].flat_map { |status| ["--filter", "status=#{status}"] }
|
19
|
+
end
|
12
20
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Mrsk::Configuration::Boot
|
2
|
+
def initialize(config:)
|
3
|
+
@options = config.raw_config.boot || {}
|
4
|
+
@host_count = config.all_hosts.count
|
5
|
+
end
|
6
|
+
|
7
|
+
def limit
|
8
|
+
limit = @options["limit"]
|
9
|
+
|
10
|
+
if limit.to_s.end_with?("%")
|
11
|
+
@host_count * limit.to_i / 100
|
12
|
+
else
|
13
|
+
limit
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def wait
|
18
|
+
@options["wait"]
|
19
|
+
end
|
20
|
+
end
|
@@ -35,6 +35,21 @@ class Mrsk::Configuration::Role
|
|
35
35
|
argumentize_env_with_secrets env
|
36
36
|
end
|
37
37
|
|
38
|
+
def health_check_args
|
39
|
+
if health_check_cmd.present?
|
40
|
+
optionize({ "health-cmd" => health_check_cmd, "health-interval" => "1s" })
|
41
|
+
else
|
42
|
+
[]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def health_check_cmd
|
47
|
+
options = specializations["healthcheck"] || {}
|
48
|
+
options = config.healthcheck.merge(options) if running_traefik?
|
49
|
+
|
50
|
+
options["cmd"] || http_health_check(port: options["port"], path: options["path"])
|
51
|
+
end
|
52
|
+
|
38
53
|
def cmd
|
39
54
|
specializations["cmd"]
|
40
55
|
end
|
@@ -74,9 +89,10 @@ class Mrsk::Configuration::Role
|
|
74
89
|
def traefik_labels
|
75
90
|
if running_traefik?
|
76
91
|
{
|
92
|
+
# Setting a service property ensures that the generated service name will be consistent between versions
|
93
|
+
"traefik.http.services.#{traefik_service}.loadbalancer.server.scheme" => "http",
|
94
|
+
|
77
95
|
"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
96
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
|
81
97
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
|
82
98
|
"traefik.http.routers.#{traefik_service}.middlewares" => "#{traefik_service}-retry@docker"
|
@@ -125,4 +141,8 @@ class Mrsk::Configuration::Role
|
|
125
141
|
new_env["clear"] = (clear_app_env + clear_role_env).uniq
|
126
142
|
end
|
127
143
|
end
|
144
|
+
|
145
|
+
def http_health_check(port:, path:)
|
146
|
+
"curl -f #{URI.join("http://localhost:#{port}", path)} || exit 1" if path.present? || port.present?
|
147
|
+
end
|
128
148
|
end
|
data/lib/mrsk/configuration.rb
CHANGED
data/lib/mrsk/sshkit_with_ext.rb
CHANGED
@@ -1,12 +1,52 @@
|
|
1
1
|
require "sshkit"
|
2
2
|
require "sshkit/dsl"
|
3
|
+
require "active_support/core_ext/hash/deep_merge"
|
4
|
+
require "json"
|
3
5
|
|
4
6
|
class SSHKit::Backend::Abstract
|
5
|
-
def capture_with_info(*args)
|
6
|
-
capture(*args, verbosity: Logger::INFO)
|
7
|
+
def capture_with_info(*args, **kwargs)
|
8
|
+
capture(*args, **kwargs, verbosity: Logger::INFO)
|
9
|
+
end
|
10
|
+
|
11
|
+
def capture_with_pretty_json(*args, **kwargs)
|
12
|
+
JSON.pretty_generate(JSON.parse(capture(*args, **kwargs)))
|
7
13
|
end
|
8
14
|
|
9
15
|
def puts_by_host(host, output, type: "App")
|
10
16
|
puts "#{type} Host: #{host}\n#{output}\n\n"
|
11
17
|
end
|
18
|
+
|
19
|
+
# Our execution pattern is for the CLI execute args lists returned
|
20
|
+
# from commands, but this doesn't support returning execution options
|
21
|
+
# from the command.
|
22
|
+
#
|
23
|
+
# Support this by using kwargs for CLI options and merging with the
|
24
|
+
# args-extracted options.
|
25
|
+
module CommandEnvMerge
|
26
|
+
private
|
27
|
+
|
28
|
+
# Override to merge options returned by commands in the args list with
|
29
|
+
# options passed by the CLI and pass them along as kwargs.
|
30
|
+
def command(args, options)
|
31
|
+
more_options, args = args.partition { |a| a.is_a? Hash }
|
32
|
+
more_options << options
|
33
|
+
|
34
|
+
build_command(args, **more_options.reduce(:deep_merge))
|
35
|
+
end
|
36
|
+
|
37
|
+
# Destructure options to pluck out env for merge
|
38
|
+
def build_command(args, env: nil, **options)
|
39
|
+
# Rely on native Ruby kwargs precedence rather than explicit Hash merges
|
40
|
+
SSHKit::Command.new(*args, **default_command_options, **options, env: env_for(env))
|
41
|
+
end
|
42
|
+
|
43
|
+
def default_command_options
|
44
|
+
{ in: pwd_path, host: @host, user: @user, group: @group }
|
45
|
+
end
|
46
|
+
|
47
|
+
def env_for(env)
|
48
|
+
@env.to_h.merge(env.to_h)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
prepend CommandEnvMerge
|
12
52
|
end
|
@@ -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
|
data/lib/mrsk/utils.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
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
7
|
def argumentize(argument, attributes, sensitive: false)
|
6
8
|
Array(attributes).flat_map do |key, value|
|
@@ -75,7 +77,9 @@ module Mrsk::Utils
|
|
75
77
|
|
76
78
|
# Escape a value to make it safe for shell use.
|
77
79
|
def escape_shell_value(value)
|
78
|
-
value.to_s.dump
|
80
|
+
value.to_s.dump
|
81
|
+
.gsub(/`/, '\\\\`')
|
82
|
+
.gsub(DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX, '\$')
|
79
83
|
end
|
80
84
|
|
81
85
|
# Abbreviate a git revhash for concise display
|
data/lib/mrsk/version.rb
CHANGED
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.
|
4
|
+
version: 0.12.1
|
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-
|
11
|
+
date: 2023-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -201,6 +201,7 @@ files:
|
|
201
201
|
- lib/mrsk/commands/builder/multiarch/remote.rb
|
202
202
|
- lib/mrsk/commands/builder/native.rb
|
203
203
|
- lib/mrsk/commands/builder/native/remote.rb
|
204
|
+
- lib/mrsk/commands/docker.rb
|
204
205
|
- lib/mrsk/commands/healthcheck.rb
|
205
206
|
- lib/mrsk/commands/lock.rb
|
206
207
|
- lib/mrsk/commands/prune.rb
|
@@ -208,9 +209,11 @@ files:
|
|
208
209
|
- lib/mrsk/commands/traefik.rb
|
209
210
|
- lib/mrsk/configuration.rb
|
210
211
|
- lib/mrsk/configuration/accessory.rb
|
212
|
+
- lib/mrsk/configuration/boot.rb
|
211
213
|
- lib/mrsk/configuration/role.rb
|
212
214
|
- lib/mrsk/sshkit_with_ext.rb
|
213
215
|
- lib/mrsk/utils.rb
|
216
|
+
- lib/mrsk/utils/healthcheck_poller.rb
|
214
217
|
- lib/mrsk/utils/sensitive.rb
|
215
218
|
- lib/mrsk/version.rb
|
216
219
|
homepage: https://github.com/rails/mrsk
|