mrsk 0.10.1 → 0.12.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.
data/lib/mrsk/cli/main.rb CHANGED
@@ -17,9 +17,6 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
17
17
  invoke_options = deploy_options
18
18
 
19
19
  runtime = print_runtime do
20
- say "Ensure curl and Docker are installed...", :magenta
21
- invoke "mrsk:cli:server:bootstrap", [], invoke_options
22
-
23
20
  say "Log into image registry...", :magenta
24
21
  invoke "mrsk:cli:registry:login", [], invoke_options
25
22
 
@@ -37,7 +34,12 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
37
34
  say "Ensure app can pass healthcheck...", :magenta
38
35
  invoke "mrsk:cli:healthcheck:perform", [], invoke_options
39
36
 
40
- invoke "mrsk:cli:app:boot", [], invoke_options
37
+ say "Detect stale containers...", :magenta
38
+ invoke "mrsk:cli:app:stale_containers", [], invoke_options
39
+
40
+ hold_lock_on_error do
41
+ invoke "mrsk:cli:app:boot", [], invoke_options
42
+ end
41
43
 
42
44
  say "Prune old containers and images...", :magenta
43
45
  invoke "mrsk:cli:prune:all", [], invoke_options
@@ -65,7 +67,12 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
65
67
  say "Ensure app can pass healthcheck...", :magenta
66
68
  invoke "mrsk:cli:healthcheck:perform", [], invoke_options
67
69
 
68
- invoke "mrsk:cli:app:boot", [], invoke_options
70
+ say "Detect stale containers...", :magenta
71
+ invoke "mrsk:cli:app:stale_containers", [], invoke_options
72
+
73
+ hold_lock_on_error do
74
+ invoke "mrsk:cli:app:boot", [], invoke_options
75
+ end
69
76
  end
70
77
 
71
78
  audit_broadcast "Redeployed #{service_version} in #{runtime.round} seconds" unless options[:skip_broadcast]
@@ -75,29 +82,41 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
75
82
  desc "rollback [VERSION]", "Rollback app to VERSION"
76
83
  def rollback(version)
77
84
  with_lock do
78
- MRSK.config.version = version
79
-
80
- if container_name_available?(MRSK.config.service_with_version)
81
- say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
85
+ invoke_options = deploy_options
82
86
 
83
- cli = self
87
+ hold_lock_on_error do
88
+ MRSK.config.version = version
84
89
  old_version = nil
85
90
 
86
- on(MRSK.hosts) do |host|
87
- old_version = capture_with_info(*MRSK.app.current_running_version).strip.presence
91
+ if container_available?(version)
92
+ say "Start version #{version}, then wait #{MRSK.config.readiness_delay}s for app to boot before stopping the old version...", :magenta
93
+
94
+ on(MRSK.hosts) do
95
+ execute *MRSK.auditor.record("Tagging #{MRSK.config.absolute_image} as the latest image"), verbosity: :debug
96
+ execute *MRSK.app.tag_current_as_latest
97
+ end
98
+
99
+ on(MRSK.hosts) do |host|
100
+ roles = MRSK.roles_on(host)
101
+
102
+ roles.each do |role|
103
+ app = MRSK.app(role: role)
104
+ old_version = capture_with_info(*app.current_running_version).strip.presence
88
105
 
89
- execute *MRSK.app.start
106
+ execute *app.start
90
107
 
91
- if old_version
92
- sleep MRSK.config.readiness_delay
108
+ if old_version
109
+ sleep MRSK.config.readiness_delay
93
110
 
94
- execute *MRSK.app.stop(version: old_version), raise_on_non_zero_exit: false
111
+ execute *app.stop(version: old_version), raise_on_non_zero_exit: false
112
+ end
113
+ end
95
114
  end
96
- end
97
115
 
98
- audit_broadcast "Rolled back #{service_version(Mrsk::Utils.abbreviate_version(old_version))} to #{service_version}" unless options[:skip_broadcast]
99
- else
100
- say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
116
+ audit_broadcast "Rolled back #{service_version(Mrsk::Utils.abbreviate_version(old_version))} to #{service_version}" unless options[:skip_broadcast]
117
+ else
118
+ say "The app version '#{version}' is not available as a container (use 'mrsk app containers' for available versions)", :red
119
+ end
101
120
  end
102
121
  end
103
122
  end
@@ -119,7 +138,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
119
138
  desc "config", "Show combined config (including secrets!)"
120
139
  def config
121
140
  run_locally do
122
- puts MRSK.config.to_h.to_yaml
141
+ puts Mrsk::Utils.redacted(MRSK.config.to_h).to_yaml
123
142
  end
124
143
  end
125
144
 
@@ -214,10 +233,24 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
214
233
  subcommand "lock", Mrsk::Cli::Lock
215
234
 
216
235
  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)
236
+ def container_available?(version)
237
+ begin
238
+ on(MRSK.hosts) do
239
+ MRSK.roles_on(host).each do |role|
240
+ container_id = capture_with_info(*MRSK.app(role: role).container_id_for_version(version))
241
+ raise "Container not found" unless container_id.present?
242
+ end
243
+ end
244
+ rescue SSHKit::Runner::ExecuteError => e
245
+ if e.message =~ /Container not found/
246
+ say "Error looking for container version #{version}: #{e.message}"
247
+ return false
248
+ else
249
+ raise
250
+ end
251
+ end
252
+
253
+ true
221
254
  end
222
255
 
223
256
  def deploy_options
@@ -7,7 +7,7 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
7
7
  end
8
8
  end
9
9
 
10
- desc "images", "Prune unused images older than 7 days"
10
+ desc "images", "Prune dangling images"
11
11
  def images
12
12
  with_lock do
13
13
  on(MRSK.hosts) do
@@ -17,7 +17,7 @@ class Mrsk::Cli::Prune < Mrsk::Cli::Base
17
17
  end
18
18
  end
19
19
 
20
- desc "containers", "Prune stopped containers older than 3 days"
20
+ desc "containers", "Prune all stopped containers, except the last 5"
21
21
  def containers
22
22
  with_lock do
23
23
  on(MRSK.hosts) do
@@ -1,17 +1,21 @@
1
1
  class Mrsk::Cli::Server < Mrsk::Cli::Base
2
- desc "bootstrap", "Ensure curl and Docker are installed on servers"
2
+ desc "bootstrap", "Set up Docker to run MRSK apps"
3
3
  def bootstrap
4
- with_lock do
5
- on(MRSK.hosts + MRSK.accessory_hosts) do
6
- dependencies_to_install = Array.new.tap do |dependencies|
7
- dependencies << "curl" unless execute "which curl", raise_on_non_zero_exit: false
8
- dependencies << "docker.io" unless execute "which docker", raise_on_non_zero_exit: false
9
- end
4
+ missing = []
10
5
 
11
- if dependencies_to_install.any?
12
- execute "apt-get update -y && apt-get install #{dependencies_to_install.join(" ")} -y"
6
+ on(MRSK.hosts | MRSK.accessory_hosts) do |host|
7
+ unless execute(*MRSK.docker.installed?, raise_on_non_zero_exit: false)
8
+ if execute(*MRSK.docker.superuser?, raise_on_non_zero_exit: false)
9
+ info "Missing Docker on #{host}. Installing…"
10
+ execute *MRSK.docker.install
11
+ else
12
+ missing << host
13
13
  end
14
14
  end
15
15
  end
16
+
17
+ if missing.any?
18
+ raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/"
19
+ end
16
20
  end
17
21
  end
@@ -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
 
@@ -91,7 +94,7 @@ class Mrsk::Cli::Traefik < Mrsk::Cli::Base
91
94
  end
92
95
  end
93
96
 
94
- desc "remove_container", "Remove Traefik image from servers", hide: true
97
+ desc "remove_image", "Remove Traefik image from servers", hide: true
95
98
  def remove_image
96
99
  with_lock do
97
100
  on(MRSK.traefik_hosts) do
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
@@ -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, :lock_count
5
+ attr_accessor :verbosity, :holding_lock, :hold_lock_on_error
6
6
 
7
7
  def initialize
8
8
  self.verbosity = :info
9
- self.lock_count = 0
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
@@ -83,6 +92,10 @@ class Mrsk::Commander
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)
@@ -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) : current_container_id,
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
- current_container_id,
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
- current_container_id,
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 current_container_id
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
- # FIXME: Find more graceful way to extract the version from "app-version" than using sed and tail!
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
- %(sed 's/-/\\n/g'),
98
- "tail -n 1"
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,6 +1,8 @@
1
1
  module Mrsk::Commands
2
2
  class Base
3
- delegate :redact, :argumentize, to: Mrsk::Utils
3
+ delegate :sensitive, :argumentize, to: Mrsk::Utils
4
+
5
+ DOCKER_HEALTH_STATUS_FORMAT = "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'"
4
6
 
5
7
  attr_accessor :config
6
8
 
@@ -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 ]
@@ -28,7 +31,7 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
28
31
  end
29
32
 
30
33
  def build_args
31
- argumentize "--build-arg", args, redacted: true
34
+ argumentize "--build-arg", args, sensitive: true
32
35
  end
33
36
 
34
37
  def build_secrets
@@ -36,7 +39,11 @@ class Mrsk::Commands::Builder::Base < Mrsk::Commands::Base
36
39
  end
37
40
 
38
41
  def build_dockerfile
39
- argumentize "--file", dockerfile
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,15 @@ 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 curl
21
- [ :curl, "--silent", "--output", "/dev/null", "--write-out", "'%{http_code}'", "--max-time", "2", health_url ]
21
+ def status
22
+ pipe container_id, xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
22
23
  end
23
24
 
24
25
  def logs
@@ -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(until_hours: 7.days.in_hours.to_i)
6
- docker :image, :prune, "--all", "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{until_hours}h"
5
+ def images
6
+ docker :image, :prune, "--all", "--force", "--filter", "label=service=#{config.service}", "--filter", "dangling=true"
7
7
  end
8
8
 
9
- def containers(until_hours: 3.days.in_hours.to_i)
10
- docker :container, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "until=#{until_hours}h"
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
@@ -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
@@ -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,18 +89,23 @@ class Mrsk::Configuration::Role
74
89
  def traefik_labels
75
90
  if running_traefik?
76
91
  {
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"
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
+
95
+ "traefik.http.routers.#{traefik_service}.rule" => "PathPrefix(`/`)",
96
+ "traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
97
+ "traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
98
+ "traefik.http.routers.#{traefik_service}.middlewares" => "#{traefik_service}-retry@docker"
83
99
  }
84
100
  else
85
101
  {}
86
102
  end
87
103
  end
88
104
 
105
+ def traefik_service
106
+ [ config.service, name, config.destination ].compact.join("-")
107
+ end
108
+
89
109
  def custom_labels
90
110
  Hash.new.tap do |labels|
91
111
  labels.merge!(config.labels) if config.labels.present?
@@ -121,4 +141,8 @@ class Mrsk::Configuration::Role
121
141
  new_env["clear"] = (clear_app_env + clear_role_env).uniq
122
142
  end
123
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
124
148
  end