mrsk 0.12.0 → 0.12.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a09720b016119167213b3c1aab02e56c5e3de0eaa09dc39996d55931048e3e5
4
- data.tar.gz: 7b435e7ff711c7267cb3f1771d56791fe76c1fb62950daeeacfe923288c314e8
3
+ metadata.gz: '099a4dc2dc59df4e0c5301b85a579970dbc6c46a3c1e2a634f4461c5cff1f241'
4
+ data.tar.gz: 0a0356837992a9847b7b6bc51956c26a4fb0e8fbb9809753a5bdb373bacd3aee
5
5
  SHA512:
6
- metadata.gz: de31f5f7e47e1f93446603e48a9a1760fca96bc41ee6dfec0660030fb0fbcdcb3fb4145d4b81fda9c3f4eef5de0a205f54e67e7bb27c91d7ebd0c93c3f5d0c57
7
- data.tar.gz: 48d36ec263b4ccfd3729059eb85f340717b17406f88e8281955e948fcfe9c30ffe081bad01977e6f9836ecd480361cf94f15cdfe79aa7477c18a6fb6ad9b97bb
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 that the HTTP health checks assume that the `curl` command is avilable inside the container. If that's not the case, use the healthcheck's `cmd` option to specify an alternative check that the container supports.
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(role: role).record("Stopped app"), verbosity: :debug
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(role: role).record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
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(role: role).record("Removed app container with version #{version}"), verbosity: :debug
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(role: role).record("Removed all app containers"), verbosity: :debug
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
@@ -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
@@ -84,8 +84,8 @@ class Mrsk::Commander
84
84
  Mrsk::Commands::Accessory.new(config, name: name)
85
85
  end
86
86
 
87
- def auditor(role: nil)
88
- Mrsk::Commands::Auditor.new(config, role: role)
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 "active_support/core_ext/time/conversions"
1
+ require "time"
2
2
 
3
3
  class Mrsk::Commands::Auditor < Mrsk::Commands::Base
4
- attr_reader :role
4
+ attr_reader :details
5
5
 
6
- def initialize(config, role: nil)
6
+ def initialize(config, **details)
7
7
  super(config)
8
- @role = role
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, tagged_record_line(line) ],
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, tagged_broadcast_line(line) ]
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 tagged_record_line(line)
35
- tagged_line recorded_at_tag, performer_tag, role_tag, line
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 tagged_broadcast_line(line)
39
- tagged_line performer_tag, role_tag, line
40
+ def audit_tags(**details)
41
+ tags_for **self.details.merge(details)
40
42
  end
41
43
 
42
- def tagged_line(*tags_and_line)
43
- "'#{tags_and_line.compact.join(" ")}'"
44
+ def broadcast_args(line, **details)
45
+ "'#{broadcast_tags(**details).join(" ")} #{line}'"
44
46
  end
45
47
 
46
- def recorded_at_tag
47
- "[#{Time.now.to_fs(:db)}]"
48
+ def broadcast_tags(**details)
49
+ tags_for **self.details.merge(details).except(:recorded_at)
48
50
  end
49
51
 
50
- def performer_tag
51
- "[#{`whoami`.strip}]"
52
+ def tags_for(**details)
53
+ details.compact.values.map { |value| "[#{value}]" }
52
54
  end
53
55
 
54
- def role_tag
55
- "[#{role}]" if role
56
+ def env_for(**details)
57
+ self.details.merge(details).compact.transform_keys { |detail| "MRSK_#{detail.upcase}" }
56
58
  end
57
59
  end
@@ -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
@@ -1,5 +1,5 @@
1
1
  require "active_support/duration"
2
- require "active_support/core_ext/numeric/time"
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.gmtime}
52
+ Locked by: #{locked_by} at #{Time.now.utc.iso8601}
53
53
  Version: #{version}
54
54
  Message: #{message}
55
55
  DETAILS
@@ -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, "--all", "--force", "--filter", "label=service=#{config.service}", "--filter", "dangling=true"
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)
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Mrsk
2
- VERSION = "0.12.0"
2
+ VERSION = "0.12.1"
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.12.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-05-02 00:00:00.000000000 Z
11
+ date: 2023-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport