Add audit broadcasts

This commit is contained in:
David Heinemeier Hansson
2023-02-18 11:36:30 +01:00
parent 5c93642f2a
commit fb9357b5ba
6 changed files with 80 additions and 5 deletions

View File

@@ -347,6 +347,29 @@ This template can safely be checked into git. Then everyone deploying the app ca
If you need separate env variables for different destinations, you can set them with `.env.destination.erb` for the template, which will generate `.env.staging` when run with `mrsk envify -d staging`.
### Using audit broadcasts
If you'd like to broadcast audits of deploys, rollbacks, etc to a chatroom or elsewhere, you can configure the `audit_broadcast_cmd` setting with the path to a bin file that reads the audit line from STDIN, and then does whatever with it:
```yaml
audit_broadcast_cmd:
bin/audit_broadcast
```
The broadcast command could look something like:
```bash
#!/usr/bin/env bash
read
curl -q -d content="[My app] ${REPLY}" https://3.basecamp.com/XXXXX/integrations/XXXXX/buckets/XXXXX/chats/XXXXX/lines
```
That'll post a line like follows to a preconfigured chatbot in Basecamp:
```
[My App] [2023-02-18 11:29:52] [dhh] Rolled back to version d264c4e92470ad1bd18590f04466787262f605de
```
## Commands
### Running commands on servers

View File

@@ -59,9 +59,14 @@ module Mrsk::Cli
def print_runtime
started_at = Time.now
yield
return Time.now - started_at
ensure
runtime = Time.now - started_at
puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
end
def audit_broadcast(line)
run_locally { execute *MRSK.auditor.broadcast(line), verbosity: :debug }
end
end
end

View File

@@ -10,7 +10,7 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
desc "deploy", "Deploy the app to servers"
def deploy
print_runtime do
runtime = print_runtime do
say "Ensure Docker is installed...", :magenta
invoke "mrsk:cli:server:bootstrap"
@@ -28,16 +28,20 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
say "Prune old containers and images...", :magenta
invoke "mrsk:cli:prune:all"
end
audit_broadcast "Deployed in #{runtime.to_i} seconds"
end
desc "redeploy", "Deploy new version of the app to servers (without bootstrapping servers, starting Traefik, pruning, and registry login)"
def redeploy
print_runtime do
runtime = print_runtime do
say "Build and push app image...", :magenta
invoke "mrsk:cli:build:deliver"
invoke "mrsk:cli:app:boot"
end
audit_broadcast "Redeployed in #{runtime.to_i} seconds"
end
desc "rollback [VERSION]", "Rollback the app to VERSION"
@@ -51,6 +55,8 @@ class Mrsk::Cli::Main < Mrsk::Cli::Base
execute *MRSK.app.stop, raise_on_non_zero_exit: false
execute *MRSK.app.start
end
audit_broadcast "Rolled back to version #{version}"
end
desc "details", "Display details about Traefik and app containers"

View File

@@ -1,12 +1,22 @@
require "active_support/core_ext/time/conversions"
class Mrsk::Commands::Auditor < Mrsk::Commands::Base
# Runs remotely
def record(line)
append \
[ :echo, tagged_line(line) ],
audit_log_file
end
# Runs locally
def broadcast(line)
if broadcast_cmd = config.audit_broadcast_cmd
pipe \
[ :echo, tagged_line(line) ],
broadcast_cmd
end
end
def reveal
[ :tail, "-n", 50, audit_log_file ]
end
@@ -21,14 +31,14 @@ class Mrsk::Commands::Auditor < Mrsk::Commands::Base
end
def tags
"[#{timestamp}] [#{performer}]"
"[#{recorded_at}] [#{performer}]"
end
def performer
`whoami`.strip
@performer ||= `whoami`.strip
end
def timestamp
def recorded_at
Time.now.to_fs(:db)
end
end

View File

@@ -126,6 +126,10 @@ class Mrsk::Configuration
{ user: ssh_user, proxy: ssh_proxy, auth_methods: [ "publickey" ] }.compact
end
def audit_broadcast_cmd
raw_config.audit_broadcast_cmd
end
def valid?
ensure_required_keys_present && ensure_env_available

View File

@@ -0,0 +1,27 @@
require "test_helper"
class CommandsAuditorTest < ActiveSupport::TestCase
setup do
@config = {
service: "app", image: "dhh/app", registry: { "username" => "dhh", "password" => "secret" }, servers: [ "1.1.1.1" ],
audit_broadcast_cmd: "bin/audit_broadcast"
}
end
test "record" do
assert_match \
/echo '.* app removed container' >> mrsk-app-audit.log/,
new_command.record("app removed container").join(" ")
end
test "broadcast" do
assert_match \
/echo '.* app removed container' \| bin\/audit_broadcast/,
new_command.broadcast("app removed container").join(" ")
end
private
def new_command
Mrsk::Commands::Auditor.new(Mrsk::Configuration.new(@config, version: "123"))
end
end