mrsk 0.12.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 +17 -2
- data/lib/mrsk/cli/app.rb +4 -4
- data/lib/mrsk/cli/healthcheck.rb +1 -0
- data/lib/mrsk/cli/main.rb +7 -0
- data/lib/mrsk/commander.rb +2 -2
- data/lib/mrsk/commands/auditor.rb +22 -20
- data/lib/mrsk/commands/base.rb +1 -0
- data/lib/mrsk/commands/healthcheck.rb +4 -0
- data/lib/mrsk/commands/lock.rb +2 -2
- data/lib/mrsk/commands/prune.rb +1 -1
- data/lib/mrsk/sshkit_with_ext.rb +40 -0
- data/lib/mrsk/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '099a4dc2dc59df4e0c5301b85a579970dbc6c46a3c1e2a634f4461c5cff1f241'
|
4
|
+
data.tar.gz: 0a0356837992a9847b7b6bc51956c26a4fb0e8fbb9809753a5bdb373bacd3aee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 06f1365b5f8a7cc2064f2bd184224dba12b1c0ebf57cc502c9ac423a9d0ae96a601161fe681764f9a660b8a6214f2a86b79d1c4d46d9ff13819c52159db14318
|
7
|
+
data.tar.gz: 7acf9c5d2e26709b391a2c9915300c569fd6cc7d841b55cd3a497122d2d2317b8d2b3df4e634f14c38acd571fbc8aac51096a221f10097f8ce68d6c07dbce038
|
data/README.md
CHANGED
@@ -308,7 +308,7 @@ You can specialize the default Traefik rules by setting labels on the containers
|
|
308
308
|
labels:
|
309
309
|
traefik.http.routers.hey-web.rule: Host(`app.hey.com`)
|
310
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.
|
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-staging.rule" if it was for the "staging" destination.
|
312
312
|
|
313
313
|
Note: The backticks are needed to ensure the rule is passed in correctly and not treated as command substitution by Bash!
|
314
314
|
|
@@ -677,6 +677,21 @@ That'll post a line like follows to a preconfigured chatbot in Basecamp:
|
|
677
677
|
[My App] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
|
678
678
|
```
|
679
679
|
|
680
|
+
`MRSK_*` environment variables are available to the broadcast command for
|
681
|
+
fine-grained audit reporting, e.g. for triggering deployment reports or
|
682
|
+
firing a JSON webhook. These variables include:
|
683
|
+
- `MRSK_RECORDED_AT` - UTC timestamp in ISO 8601 format, e.g. `2023-04-14T17:07:31Z`
|
684
|
+
- `MRSK_PERFORMER` - the local user performing the command (from `whoami`)
|
685
|
+
- `MRSK_MESSAGE` - the full audit message, e.g. "Deployed app@150b24f"
|
686
|
+
- `MRSK_DESTINATION` - optional: destination, e.g. "staging"
|
687
|
+
- `MRSK_ROLE` - optional: role targeted, e.g. "web"
|
688
|
+
|
689
|
+
Use `mrsk broadcast` to test and troubleshoot your broadcast command:
|
690
|
+
|
691
|
+
```bash
|
692
|
+
mrsk broadcast -m "test audit message"
|
693
|
+
```
|
694
|
+
|
680
695
|
### Healthcheck
|
681
696
|
|
682
697
|
MRSK uses Docker healtchecks to check the health of your application during deployment. Traefik uses this same healthcheck status to determine when a container is ready to receive traffic.
|
@@ -714,7 +729,7 @@ servers:
|
|
714
729
|
|
715
730
|
The healthcheck 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.
|
716
731
|
|
717
|
-
Note
|
732
|
+
Note: The HTTP health checks assume that the `curl` command is available inside the container. If that's not the case, use the healthcheck's `cmd` option to specify an alternative check that the container supports.
|
718
733
|
|
719
734
|
## Commands
|
720
735
|
|
data/lib/mrsk/cli/app.rb
CHANGED
@@ -60,7 +60,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
60
60
|
roles = MRSK.roles_on(host)
|
61
61
|
|
62
62
|
roles.each do |role|
|
63
|
-
execute *MRSK.auditor
|
63
|
+
execute *MRSK.auditor.record("Stopped app", role: role), verbosity: :debug
|
64
64
|
execute *MRSK.app(role: role).stop, raise_on_non_zero_exit: false
|
65
65
|
end
|
66
66
|
end
|
@@ -107,7 +107,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
107
107
|
roles = MRSK.roles_on(host)
|
108
108
|
|
109
109
|
roles.each do |role|
|
110
|
-
execute *MRSK.auditor
|
110
|
+
execute *MRSK.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
|
111
111
|
puts_by_host host, capture_with_info(*MRSK.app(role: role).execute_in_existing_container(cmd))
|
112
112
|
end
|
113
113
|
end
|
@@ -214,7 +214,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
214
214
|
roles = MRSK.roles_on(host)
|
215
215
|
|
216
216
|
roles.each do |role|
|
217
|
-
execute *MRSK.auditor
|
217
|
+
execute *MRSK.auditor.record("Removed app container with version #{version}", role: role), verbosity: :debug
|
218
218
|
execute *MRSK.app(role: role).remove_container(version: version)
|
219
219
|
end
|
220
220
|
end
|
@@ -228,7 +228,7 @@ class Mrsk::Cli::App < Mrsk::Cli::Base
|
|
228
228
|
roles = MRSK.roles_on(host)
|
229
229
|
|
230
230
|
roles.each do |role|
|
231
|
-
execute *MRSK.auditor
|
231
|
+
execute *MRSK.auditor.record("Removed all app containers", role: role), verbosity: :debug
|
232
232
|
execute *MRSK.app(role: role).remove_containers
|
233
233
|
end
|
234
234
|
end
|
data/lib/mrsk/cli/healthcheck.rb
CHANGED
@@ -9,6 +9,7 @@ class Mrsk::Cli::Healthcheck < Mrsk::Cli::Base
|
|
9
9
|
Mrsk::Utils::HealthcheckPoller.wait_for_healthy { capture_with_info(*MRSK.healthcheck.status) }
|
10
10
|
rescue Mrsk::Utils::HealthcheckPoller::HealthcheckError => e
|
11
11
|
error capture_with_info(*MRSK.healthcheck.logs)
|
12
|
+
error capture_with_pretty_json(*MRSK.healthcheck.container_health_log)
|
12
13
|
raise
|
13
14
|
ensure
|
14
15
|
execute *MRSK.healthcheck.stop, raise_on_non_zero_exit: false
|
data/lib/mrsk/cli/main.rb
CHANGED
@@ -200,6 +200,13 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
|
|
200
200
|
end
|
201
201
|
end
|
202
202
|
|
203
|
+
desc "broadcast", "Broadcast an audit message"
|
204
|
+
option :message, aliases: "-m", type: :string, desc: "Audit mesasge", required: true
|
205
|
+
def broadcast
|
206
|
+
say "Broadcast: #{options[:message]}", :magenta
|
207
|
+
audit_broadcast options[:message]
|
208
|
+
end
|
209
|
+
|
203
210
|
desc "version", "Show MRSK version"
|
204
211
|
def version
|
205
212
|
puts Mrsk::VERSION
|
data/lib/mrsk/commander.rb
CHANGED
@@ -84,8 +84,8 @@ class Mrsk::Commander
|
|
84
84
|
Mrsk::Commands::Accessory.new(config, name: name)
|
85
85
|
end
|
86
86
|
|
87
|
-
def auditor(
|
88
|
-
Mrsk::Commands::Auditor.new(config,
|
87
|
+
def auditor(**details)
|
88
|
+
Mrsk::Commands::Auditor.new(config, **details)
|
89
89
|
end
|
90
90
|
|
91
91
|
def builder
|
@@ -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
@@ -3,6 +3,7 @@ module Mrsk::Commands
|
|
3
3
|
delegate :sensitive, :argumentize, to: Mrsk::Utils
|
4
4
|
|
5
5
|
DOCKER_HEALTH_STATUS_FORMAT = "'{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}'"
|
6
|
+
DOCKER_HEALTH_LOG_FORMAT = "'{{json .State.Health}}'"
|
6
7
|
|
7
8
|
attr_accessor :config
|
8
9
|
|
@@ -22,6 +22,10 @@ class Mrsk::Commands::Healthcheck < Mrsk::Commands::Base
|
|
22
22
|
pipe container_id, xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
|
23
23
|
end
|
24
24
|
|
25
|
+
def container_health_log
|
26
|
+
pipe container_id, xargs(docker(:inspect, "--format", DOCKER_HEALTH_LOG_FORMAT))
|
27
|
+
end
|
28
|
+
|
25
29
|
def logs
|
26
30
|
pipe container_id, xargs(docker(:logs, "--tail", 50, "2>&1"))
|
27
31
|
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
@@ -3,7 +3,7 @@ require "active_support/core_ext/numeric/time"
|
|
3
3
|
|
4
4
|
class Mrsk::Commands::Prune < Mrsk::Commands::Base
|
5
5
|
def images
|
6
|
-
docker :image, :prune, "--
|
6
|
+
docker :image, :prune, "--force", "--filter", "label=service=#{config.service}", "--filter", "dangling=true"
|
7
7
|
end
|
8
8
|
|
9
9
|
def containers(keep_last: 5)
|
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
7
|
def capture_with_info(*args, **kwargs)
|
6
8
|
capture(*args, **kwargs, verbosity: Logger::INFO)
|
7
9
|
end
|
8
10
|
|
11
|
+
def capture_with_pretty_json(*args, **kwargs)
|
12
|
+
JSON.pretty_generate(JSON.parse(capture(*args, **kwargs)))
|
13
|
+
end
|
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
|
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.12.
|
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-05-
|
11
|
+
date: 2023-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|