mrsk 0.11.0 → 0.13.2
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 +141 -37
- data/bin/mrsk +1 -1
- data/lib/mrsk/cli/accessory.rb +0 -2
- data/lib/mrsk/cli/app.rb +66 -22
- data/lib/mrsk/cli/base.rb +80 -22
- data/lib/mrsk/cli/build.rb +20 -0
- data/lib/mrsk/cli/healthcheck.rb +4 -34
- data/lib/mrsk/cli/lock.rb +5 -5
- data/lib/mrsk/cli/main.rb +62 -53
- data/lib/mrsk/cli/prune.rb +4 -3
- data/lib/mrsk/cli/server.rb +13 -9
- data/lib/mrsk/cli/templates/deploy.yml +0 -4
- data/lib/mrsk/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/mrsk/cli/templates/sample_hooks/pre-build.sample +51 -0
- data/lib/mrsk/cli/templates/sample_hooks/pre-connect.sample +47 -0
- data/lib/mrsk/cli/templates/sample_hooks/pre-deploy.sample +82 -0
- data/lib/mrsk/cli/traefik.rb +1 -1
- data/lib/mrsk/cli.rb +1 -0
- data/lib/mrsk/commander.rb +34 -9
- data/lib/mrsk/commands/app.rb +37 -15
- data/lib/mrsk/commands/auditor.rb +7 -36
- data/lib/mrsk/commands/base.rb +9 -2
- 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/hook.rb +14 -0
- data/lib/mrsk/commands/lock.rb +2 -2
- data/lib/mrsk/commands/prune.rb +30 -4
- data/lib/mrsk/commands/traefik.rb +12 -1
- data/lib/mrsk/configuration/boot.rb +20 -0
- data/lib/mrsk/configuration/role.rb +29 -2
- data/lib/mrsk/configuration.rb +15 -9
- data/lib/mrsk/sshkit_with_ext.rb +46 -2
- data/lib/mrsk/tags.rb +39 -0
- data/lib/mrsk/utils/healthcheck_poller.rb +39 -0
- data/lib/mrsk/utils.rb +13 -2
- data/lib/mrsk/version.rb +1 -1
- metadata +11 -2
data/lib/mrsk/commands/app.rb
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
2
|
+
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
|
3
|
+
|
|
2
4
|
attr_reader :role
|
|
3
5
|
|
|
4
6
|
def initialize(config, role: nil)
|
|
@@ -6,15 +8,21 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
|
6
8
|
@role = role
|
|
7
9
|
end
|
|
8
10
|
|
|
9
|
-
def
|
|
11
|
+
def start_or_run(hostname: nil)
|
|
12
|
+
combine start, run(hostname: hostname), by: "||"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def run(hostname: nil)
|
|
10
16
|
role = config.role(self.role)
|
|
11
17
|
|
|
12
18
|
docker :run,
|
|
13
19
|
"--detach",
|
|
14
20
|
"--restart unless-stopped",
|
|
15
21
|
"--name", container_name,
|
|
22
|
+
*(["--hostname", hostname] if hostname),
|
|
16
23
|
"-e", "MRSK_CONTAINER_NAME=\"#{container_name}\"",
|
|
17
24
|
*role.env_args,
|
|
25
|
+
*role.health_check_args,
|
|
18
26
|
*config.logging_args,
|
|
19
27
|
*config.volume_args,
|
|
20
28
|
*role.label_args,
|
|
@@ -27,9 +35,13 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
|
27
35
|
docker :start, container_name
|
|
28
36
|
end
|
|
29
37
|
|
|
38
|
+
def status(version:)
|
|
39
|
+
pipe container_id_for_version(version), xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
|
|
40
|
+
end
|
|
41
|
+
|
|
30
42
|
def stop(version: nil)
|
|
31
43
|
pipe \
|
|
32
|
-
version ? container_id_for_version(version) :
|
|
44
|
+
version ? container_id_for_version(version) : current_running_container_id,
|
|
33
45
|
xargs(config.stop_wait_time ? docker(:stop, "-t", config.stop_wait_time) : docker(:stop))
|
|
34
46
|
end
|
|
35
47
|
|
|
@@ -40,7 +52,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
|
40
52
|
|
|
41
53
|
def logs(since: nil, lines: nil, grep: nil)
|
|
42
54
|
pipe \
|
|
43
|
-
|
|
55
|
+
current_running_container_id,
|
|
44
56
|
"xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1",
|
|
45
57
|
("grep '#{grep}'" if grep)
|
|
46
58
|
end
|
|
@@ -48,7 +60,7 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
|
48
60
|
def follow_logs(host:, grep: nil)
|
|
49
61
|
run_over_ssh \
|
|
50
62
|
pipe(
|
|
51
|
-
|
|
63
|
+
current_running_container_id,
|
|
52
64
|
"xargs docker logs --timestamps --tail 10 --follow 2>&1",
|
|
53
65
|
(%(grep "#{grep}") if grep)
|
|
54
66
|
),
|
|
@@ -82,20 +94,23 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
|
82
94
|
end
|
|
83
95
|
|
|
84
96
|
|
|
85
|
-
def
|
|
86
|
-
docker :ps, "--quiet", *filter_args
|
|
97
|
+
def current_running_container_id
|
|
98
|
+
docker :ps, "--quiet", *filter_args(statuses: ACTIVE_DOCKER_STATUSES), "--latest"
|
|
87
99
|
end
|
|
88
100
|
|
|
89
|
-
def container_id_for_version(version)
|
|
90
|
-
container_id_for(container_name: container_name(version))
|
|
101
|
+
def container_id_for_version(version, only_running: false)
|
|
102
|
+
container_id_for(container_name: container_name(version), only_running: only_running)
|
|
91
103
|
end
|
|
92
104
|
|
|
93
105
|
def current_running_version
|
|
94
|
-
|
|
106
|
+
list_versions("--latest", statuses: ACTIVE_DOCKER_STATUSES)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def list_versions(*docker_args, statuses: nil)
|
|
95
110
|
pipe \
|
|
96
|
-
docker(:ps, *filter_args, "--format", '"{{.Names}}"'),
|
|
97
|
-
%(
|
|
98
|
-
|
|
111
|
+
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
|
112
|
+
%(grep -oE "\\-[^-]+$"), # Extract SHA from "service-role-dest-SHA"
|
|
113
|
+
%(cut -c 2-)
|
|
99
114
|
end
|
|
100
115
|
|
|
101
116
|
def list_containers
|
|
@@ -128,20 +143,27 @@ class Mrsk::Commands::App < Mrsk::Commands::Base
|
|
|
128
143
|
docker :image, :prune, "--all", "--force", *filter_args
|
|
129
144
|
end
|
|
130
145
|
|
|
146
|
+
def tag_current_as_latest
|
|
147
|
+
docker :tag, config.absolute_image, config.latest_image
|
|
148
|
+
end
|
|
149
|
+
|
|
131
150
|
|
|
132
151
|
private
|
|
133
152
|
def container_name(version = nil)
|
|
134
153
|
[ config.service, role, config.destination, version || config.version ].compact.join("-")
|
|
135
154
|
end
|
|
136
155
|
|
|
137
|
-
def filter_args
|
|
138
|
-
argumentize "--filter", filters
|
|
156
|
+
def filter_args(statuses: nil)
|
|
157
|
+
argumentize "--filter", filters(statuses: statuses)
|
|
139
158
|
end
|
|
140
159
|
|
|
141
|
-
def filters
|
|
160
|
+
def filters(statuses: nil)
|
|
142
161
|
[ "label=service=#{config.service}" ].tap do |filters|
|
|
143
162
|
filters << "label=destination=#{config.destination}" if config.destination
|
|
144
163
|
filters << "label=role=#{role}" if role
|
|
164
|
+
statuses&.each do |status|
|
|
165
|
+
filters << "status=#{status}"
|
|
166
|
+
end
|
|
145
167
|
end
|
|
146
168
|
end
|
|
147
169
|
end
|
|
@@ -1,27 +1,18 @@
|
|
|
1
|
-
require "active_support/core_ext/time/conversions"
|
|
2
|
-
|
|
3
1
|
class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
|
4
|
-
attr_reader :
|
|
2
|
+
attr_reader :details
|
|
5
3
|
|
|
6
|
-
def initialize(config,
|
|
4
|
+
def initialize(config, **details)
|
|
7
5
|
super(config)
|
|
8
|
-
@
|
|
6
|
+
@details = details
|
|
9
7
|
end
|
|
10
8
|
|
|
11
9
|
# Runs remotely
|
|
12
|
-
def record(line)
|
|
10
|
+
def record(line, **details)
|
|
13
11
|
append \
|
|
14
|
-
[ :echo,
|
|
12
|
+
[ :echo, audit_tags(**details).except(:version, :service_version).to_s, line ],
|
|
15
13
|
audit_log_file
|
|
16
14
|
end
|
|
17
15
|
|
|
18
|
-
# Runs locally
|
|
19
|
-
def broadcast(line)
|
|
20
|
-
if broadcast_cmd = config.audit_broadcast_cmd
|
|
21
|
-
[ broadcast_cmd, tagged_broadcast_line(line) ]
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
16
|
def reveal
|
|
26
17
|
[ :tail, "-n", 50, audit_log_file ]
|
|
27
18
|
end
|
|
@@ -31,27 +22,7 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
|
|
|
31
22
|
[ "mrsk", config.service, config.destination, "audit.log" ].compact.join("-")
|
|
32
23
|
end
|
|
33
24
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def tagged_broadcast_line(line)
|
|
39
|
-
tagged_line performer_tag, role_tag, line
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def tagged_line(*tags_and_line)
|
|
43
|
-
"'#{tags_and_line.compact.join(" ")}'"
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def recorded_at_tag
|
|
47
|
-
"[#{Time.now.to_fs(:db)}]"
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def performer_tag
|
|
51
|
-
"[#{`whoami`.strip}]"
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def role_tag
|
|
55
|
-
"[#{role}]" if role
|
|
25
|
+
def audit_tags(**details)
|
|
26
|
+
tags(**self.details, **details)
|
|
56
27
|
end
|
|
57
28
|
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)
|
|
@@ -15,8 +18,8 @@ module Mrsk::Commands
|
|
|
15
18
|
end
|
|
16
19
|
end
|
|
17
20
|
|
|
18
|
-
def container_id_for(container_name:)
|
|
19
|
-
docker :container, :ls, "--all", "--filter", "name=^#{container_name}$", "--quiet"
|
|
21
|
+
def container_id_for(container_name:, only_running: false)
|
|
22
|
+
docker :container, :ls, *("--all" unless only_running), "--filter", "name=^#{container_name}$", "--quiet"
|
|
20
23
|
end
|
|
21
24
|
|
|
22
25
|
private
|
|
@@ -50,5 +53,9 @@ module Mrsk::Commands
|
|
|
50
53
|
def docker(*args)
|
|
51
54
|
args.compact.unshift :docker
|
|
52
55
|
end
|
|
56
|
+
|
|
57
|
+
def tags(**details)
|
|
58
|
+
Mrsk::Tags.from_config(config, **details)
|
|
59
|
+
end
|
|
53
60
|
end
|
|
54
61
|
end
|
|
@@ -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
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class Mrsk::Commands::Hook < Mrsk::Commands::Base
|
|
2
|
+
def run(hook, **details)
|
|
3
|
+
[ hook_file(hook), env: tags(**details).env ]
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def hook_exists?(hook)
|
|
7
|
+
Pathname.new(hook_file(hook)).exist?
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
def hook_file(hook)
|
|
12
|
+
"#{config.hooks_path}/#{hook}"
|
|
13
|
+
end
|
|
14
|
+
end
|
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,37 @@ 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
|
|
6
|
-
docker :image, :prune, "--
|
|
5
|
+
def dangling_images
|
|
6
|
+
docker :image, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "dangling=true"
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def
|
|
10
|
-
|
|
9
|
+
def tagged_images
|
|
10
|
+
pipe \
|
|
11
|
+
docker(:image, :ls, *service_filter, "--format", "'{{.ID}} {{.Repository}}:{{.Tag}}'"),
|
|
12
|
+
"grep -v -w \"#{active_image_list}\"",
|
|
13
|
+
"while read image tag; do docker rmi $tag; done"
|
|
11
14
|
end
|
|
15
|
+
|
|
16
|
+
def containers(keep_last: 5)
|
|
17
|
+
pipe \
|
|
18
|
+
docker(:ps, "-q", "-a", *service_filter, *stopped_containers_filters),
|
|
19
|
+
"tail -n +#{keep_last + 1}",
|
|
20
|
+
"while read container_id; do docker rm $container_id; done"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def stopped_containers_filters
|
|
25
|
+
[ "created", "exited", "dead" ].flat_map { |status| ["--filter", "status=#{status}"] }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def active_image_list
|
|
29
|
+
# Pull the images that are used by any containers
|
|
30
|
+
# Append repo:latest - to avoid deleting the latest tag
|
|
31
|
+
# Append repo:<none> - to avoid deleting dangling images that are in use. Unused dangling images are deleted separately
|
|
32
|
+
"$(docker container ls -a --format '{{.Image}}\\|' --filter label=service=#{config.service} | tr -d '\\n')#{config.latest_image}\\|#{config.repository}:<none>"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def service_filter
|
|
36
|
+
[ "--filter", "label=service=#{config.service}" ]
|
|
37
|
+
end
|
|
12
38
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
2
|
-
delegate :argumentize, :optionize, to: Mrsk::Utils
|
|
2
|
+
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Mrsk::Utils
|
|
3
3
|
|
|
4
4
|
DEFAULT_IMAGE = "traefik:v2.9"
|
|
5
5
|
CONTAINER_PORT = 80
|
|
@@ -10,6 +10,7 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
|
10
10
|
"--restart", "unless-stopped",
|
|
11
11
|
"--publish", port,
|
|
12
12
|
"--volume", "/var/run/docker.sock:/var/run/docker.sock",
|
|
13
|
+
*env_args,
|
|
13
14
|
*config.logging_args,
|
|
14
15
|
*label_args,
|
|
15
16
|
*docker_options_args,
|
|
@@ -61,6 +62,16 @@ class Mrsk::Commands::Traefik < Mrsk::Commands::Base
|
|
|
61
62
|
argumentize "--label", labels
|
|
62
63
|
end
|
|
63
64
|
|
|
65
|
+
def env_args
|
|
66
|
+
env_config = config.traefik["env"] || {}
|
|
67
|
+
|
|
68
|
+
if env_config.present?
|
|
69
|
+
argumentize_env_with_secrets(env_config)
|
|
70
|
+
else
|
|
71
|
+
[]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
64
75
|
def labels
|
|
65
76
|
config.traefik["labels"] || []
|
|
66
77
|
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,28 @@ 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" => health_check_interval })
|
|
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
|
+
|
|
53
|
+
def health_check_interval
|
|
54
|
+
options = specializations["healthcheck"] || {}
|
|
55
|
+
options = config.healthcheck.merge(options) if running_traefik?
|
|
56
|
+
|
|
57
|
+
options["interval"] || "1s"
|
|
58
|
+
end
|
|
59
|
+
|
|
38
60
|
def cmd
|
|
39
61
|
specializations["cmd"]
|
|
40
62
|
end
|
|
@@ -74,9 +96,10 @@ class Mrsk::Configuration::Role
|
|
|
74
96
|
def traefik_labels
|
|
75
97
|
if running_traefik?
|
|
76
98
|
{
|
|
99
|
+
# Setting a service property ensures that the generated service name will be consistent between versions
|
|
100
|
+
"traefik.http.services.#{traefik_service}.loadbalancer.server.scheme" => "http",
|
|
101
|
+
|
|
77
102
|
"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
103
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
|
|
81
104
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
|
|
82
105
|
"traefik.http.routers.#{traefik_service}.middlewares" => "#{traefik_service}-retry@docker"
|
|
@@ -125,4 +148,8 @@ class Mrsk::Configuration::Role
|
|
|
125
148
|
new_env["clear"] = (clear_app_env + clear_role_env).uniq
|
|
126
149
|
end
|
|
127
150
|
end
|
|
151
|
+
|
|
152
|
+
def http_health_check(port:, path:)
|
|
153
|
+
"curl -f #{URI.join("http://localhost:#{port}", path)} || exit 1" if path.present? || port.present?
|
|
154
|
+
end
|
|
128
155
|
end
|
data/lib/mrsk/configuration.rb
CHANGED
|
@@ -6,7 +6,7 @@ require "erb"
|
|
|
6
6
|
require "net/ssh/proxy/jump"
|
|
7
7
|
|
|
8
8
|
class Mrsk::Configuration
|
|
9
|
-
delegate :service, :image, :servers, :env, :labels, :registry, :builder, :stop_wait_time, to: :raw_config, allow_nil: true
|
|
9
|
+
delegate :service, :image, :servers, :env, :labels, :registry, :builder, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
|
|
10
10
|
delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Mrsk::Utils
|
|
11
11
|
|
|
12
12
|
attr_accessor :destination
|
|
@@ -50,7 +50,7 @@ class Mrsk::Configuration
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def version
|
|
53
|
-
@declared_version.presence || ENV["VERSION"] ||
|
|
53
|
+
@declared_version.presence || ENV["VERSION"] || git_version
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
def abbreviated_version
|
|
@@ -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("/")
|
|
@@ -153,10 +157,6 @@ class Mrsk::Configuration
|
|
|
153
157
|
end
|
|
154
158
|
|
|
155
159
|
|
|
156
|
-
def audit_broadcast_cmd
|
|
157
|
-
raw_config.audit_broadcast_cmd
|
|
158
|
-
end
|
|
159
|
-
|
|
160
160
|
def healthcheck
|
|
161
161
|
{ "path" => "/up", "port" => 3000, "max_attempts" => 7 }.merge(raw_config.healthcheck || {})
|
|
162
162
|
end
|
|
@@ -193,6 +193,10 @@ class Mrsk::Configuration
|
|
|
193
193
|
raw_config.traefik || {}
|
|
194
194
|
end
|
|
195
195
|
|
|
196
|
+
def hooks_path
|
|
197
|
+
raw_config.hooks_path || ".mrsk/hooks"
|
|
198
|
+
end
|
|
199
|
+
|
|
196
200
|
private
|
|
197
201
|
# Will raise ArgumentError if any required config keys are missing
|
|
198
202
|
def ensure_required_keys_present
|
|
@@ -229,10 +233,12 @@ class Mrsk::Configuration
|
|
|
229
233
|
raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
|
|
230
234
|
end
|
|
231
235
|
|
|
232
|
-
def
|
|
233
|
-
@
|
|
236
|
+
def git_version
|
|
237
|
+
@git_version ||=
|
|
234
238
|
if system("git rev-parse")
|
|
235
|
-
`git
|
|
239
|
+
uncommitted_suffix = `git status --porcelain`.strip.present? ? "_uncommitted_#{SecureRandom.hex(8)}" : ""
|
|
240
|
+
|
|
241
|
+
"#{`git rev-parse HEAD`.strip}#{uncommitted_suffix}"
|
|
236
242
|
else
|
|
237
243
|
raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
|
|
238
244
|
end
|
data/lib/mrsk/sshkit_with_ext.rb
CHANGED
|
@@ -1,12 +1,56 @@
|
|
|
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_debug(*args, **kwargs)
|
|
12
|
+
capture(*args, **kwargs, verbosity: Logger::DEBUG)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def capture_with_pretty_json(*args, **kwargs)
|
|
16
|
+
JSON.pretty_generate(JSON.parse(capture(*args, **kwargs)))
|
|
7
17
|
end
|
|
8
18
|
|
|
9
19
|
def puts_by_host(host, output, type: "App")
|
|
10
20
|
puts "#{type} Host: #{host}\n#{output}\n\n"
|
|
11
21
|
end
|
|
22
|
+
|
|
23
|
+
# Our execution pattern is for the CLI execute args lists returned
|
|
24
|
+
# from commands, but this doesn't support returning execution options
|
|
25
|
+
# from the command.
|
|
26
|
+
#
|
|
27
|
+
# Support this by using kwargs for CLI options and merging with the
|
|
28
|
+
# args-extracted options.
|
|
29
|
+
module CommandEnvMerge
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
# Override to merge options returned by commands in the args list with
|
|
33
|
+
# options passed by the CLI and pass them along as kwargs.
|
|
34
|
+
def command(args, options)
|
|
35
|
+
more_options, args = args.partition { |a| a.is_a? Hash }
|
|
36
|
+
more_options << options
|
|
37
|
+
|
|
38
|
+
build_command(args, **more_options.reduce(:deep_merge))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Destructure options to pluck out env for merge
|
|
42
|
+
def build_command(args, env: nil, **options)
|
|
43
|
+
# Rely on native Ruby kwargs precedence rather than explicit Hash merges
|
|
44
|
+
SSHKit::Command.new(*args, **default_command_options, **options, env: env_for(env))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def default_command_options
|
|
48
|
+
{ in: pwd_path, host: @host, user: @user, group: @group }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def env_for(env)
|
|
52
|
+
@env.to_h.merge(env.to_h)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
prepend CommandEnvMerge
|
|
12
56
|
end
|
data/lib/mrsk/tags.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require "time"
|
|
2
|
+
|
|
3
|
+
class Mrsk::Tags
|
|
4
|
+
attr_reader :config, :tags
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
def from_config(config, **extra)
|
|
8
|
+
new(**default_tags(config), **extra)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def default_tags(config)
|
|
12
|
+
{ recorded_at: Time.now.utc.iso8601,
|
|
13
|
+
performer: `whoami`.chomp,
|
|
14
|
+
destination: config.destination,
|
|
15
|
+
version: config.version,
|
|
16
|
+
service_version: service_version(config) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def service_version(config)
|
|
20
|
+
[ config.service, config.abbreviated_version ].compact.join("@")
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def initialize(**tags)
|
|
25
|
+
@tags = tags.compact
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def env
|
|
29
|
+
tags.transform_keys { |detail| "MRSK_#{detail.upcase}" }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_s
|
|
33
|
+
tags.values.map { |value| "[#{value}]" }.join(" ")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def except(*tags)
|
|
37
|
+
self.class.new(**self.tags.except(*tags))
|
|
38
|
+
end
|
|
39
|
+
end
|