Add a pre-connect hook
This can be used for hooks that should run before connecting to remote hosts. An example use case is pre-warming DNS.
This commit is contained in:
14
README.md
14
README.md
@@ -880,17 +880,21 @@ fine-grained audit reporting, e.g. for triggering deployment reports or
|
|||||||
firing a JSON webhook. These variables include:
|
firing a JSON webhook. These variables include:
|
||||||
- `MRSK_RECORDED_AT` - UTC timestamp in ISO 8601 format, e.g. `2023-04-14T17:07:31Z`
|
- `MRSK_RECORDED_AT` - UTC timestamp in ISO 8601 format, e.g. `2023-04-14T17:07:31Z`
|
||||||
- `MRSK_PERFORMER` - the local user performing the command (from `whoami`)
|
- `MRSK_PERFORMER` - the local user performing the command (from `whoami`)
|
||||||
- `MRSK_MESSAGE` - the full audit message, e.g. "Deployed app@150b24f"
|
- `MRSK_SERVICE_VERSION` - an abbreviated service and version for use in messages, e.g. app@150b24f
|
||||||
- `MRSK_SERVICE_VERSION` - an abbreviated version (for use in messages)
|
- `MRSK_VERSION` - an full version being deployed
|
||||||
- `MRSK_DESTINATION` - optional: destination, e.g. "staging"
|
- `MRSK_DESTINATION` - optional: destination, e.g. "staging"
|
||||||
|
- `MRSK_HOSTS` - a comma separated list of the hosts targeted by the command
|
||||||
- `MRSK_ROLE` - optional: role targeted, e.g. "web"
|
- `MRSK_ROLE` - optional: role targeted, e.g. "web"
|
||||||
|
|
||||||
There are two hooks:
|
There are three hooks:
|
||||||
|
|
||||||
1. pre-build
|
1. pre-connect
|
||||||
|
Called before taking the deploy lock. For checks that need to run before connecting to remote hosts - e.g. DNS warming.
|
||||||
|
|
||||||
|
2. pre-build
|
||||||
Used for pre-build checks - e.g. there are no uncommitted changes or that CI has passed.
|
Used for pre-build checks - e.g. there are no uncommitted changes or that CI has passed.
|
||||||
|
|
||||||
2. post-deploy - run after a deploy, redeploy or rollback
|
3. post-deploy - run after a deploy, redeploy or rollback
|
||||||
|
|
||||||
This hook is also passed a `MRSK_RUNTIME` env variable.
|
This hook is also passed a `MRSK_RUNTIME` env variable.
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,8 @@ module Mrsk::Cli
|
|||||||
if MRSK.holding_lock?
|
if MRSK.holding_lock?
|
||||||
yield
|
yield
|
||||||
else
|
else
|
||||||
|
run_hook "pre-connect"
|
||||||
|
|
||||||
acquire_lock
|
acquire_lock
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@@ -135,7 +137,7 @@ module Mrsk::Cli
|
|||||||
if !options[:skip_hooks] && MRSK.hook.hook_exists?(hook)
|
if !options[:skip_hooks] && MRSK.hook.hook_exists?(hook)
|
||||||
say "Running the #{hook} hook...", :magenta
|
say "Running the #{hook} hook...", :magenta
|
||||||
run_locally do
|
run_locally do
|
||||||
MRSK.with_verbosity(:debug) { execute *MRSK.hook.run(hook, **details) }
|
MRSK.with_verbosity(:debug) { execute *MRSK.hook.run(hook, **details, hosts: MRSK.hosts.join(",")) }
|
||||||
rescue SSHKit::Command::Failed
|
rescue SSHKit::Command::Failed
|
||||||
raise HookError.new("Hook `#{hook}` failed")
|
raise HookError.new("Hook `#{hook}` failed")
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
# MRSK_RECORDED_AT
|
# MRSK_RECORDED_AT
|
||||||
# MRSK_PERFORMER
|
# MRSK_PERFORMER
|
||||||
# MRSK_VERSION
|
# MRSK_VERSION
|
||||||
|
# MRSK_HOSTS
|
||||||
|
# MRSK_ROLE (if set)
|
||||||
# MRSK_DESTINATION (if set)
|
# MRSK_DESTINATION (if set)
|
||||||
# MRSK_RUNTIME
|
# MRSK_RUNTIME
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
# MRSK_RECORDED_AT
|
# MRSK_RECORDED_AT
|
||||||
# MRSK_PERFORMER
|
# MRSK_PERFORMER
|
||||||
# MRSK_VERSION
|
# MRSK_VERSION
|
||||||
|
# MRSK_HOSTS
|
||||||
|
# MRSK_ROLE (if set)
|
||||||
# MRSK_DESTINATION (if set)
|
# MRSK_DESTINATION (if set)
|
||||||
|
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
|||||||
47
lib/mrsk/cli/templates/sample_hooks/pre-connect.sample
Executable file
47
lib/mrsk/cli/templates/sample_hooks/pre-connect.sample
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# A sample pre-connect check
|
||||||
|
#
|
||||||
|
# Warms DNS before connecting to hosts in parallel
|
||||||
|
#
|
||||||
|
# These environment variables are available:
|
||||||
|
# MRSK_RECORDED_AT
|
||||||
|
# MRSK_PERFORMER
|
||||||
|
# MRSK_VERSION
|
||||||
|
# MRSK_HOSTS
|
||||||
|
# MRSK_ROLE (if set)
|
||||||
|
# MRSK_DESTINATION (if set)
|
||||||
|
# MRSK_RUNTIME
|
||||||
|
|
||||||
|
hosts = ENV["MRSK_HOSTS"].split(",")
|
||||||
|
results = nil
|
||||||
|
max = 3
|
||||||
|
|
||||||
|
elapsed = Benchmark.realtime do
|
||||||
|
results = hosts.map do |host|
|
||||||
|
Thread.new do
|
||||||
|
tries = 1
|
||||||
|
|
||||||
|
begin
|
||||||
|
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
|
||||||
|
rescue SocketError
|
||||||
|
if tries < max
|
||||||
|
puts "Retrying DNS warmup: #{host}"
|
||||||
|
tries += 1
|
||||||
|
sleep rand
|
||||||
|
retry
|
||||||
|
else
|
||||||
|
puts "DNS warmup failed: #{host}"
|
||||||
|
host
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tries
|
||||||
|
end
|
||||||
|
end.map(&:value)
|
||||||
|
end
|
||||||
|
|
||||||
|
retries = results.sum - hosts.size
|
||||||
|
nopes = results.count { |r| r == max }
|
||||||
|
|
||||||
|
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]
|
||||||
@@ -23,6 +23,7 @@ class CliMainTest < CliTestCase
|
|||||||
Mrsk::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
Mrsk::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||||
|
|
||||||
run_command("deploy").tap do |output|
|
run_command("deploy").tap do |output|
|
||||||
|
assert_match /Running the pre-connect hook.../, output
|
||||||
assert_match /Log into image registry/, output
|
assert_match /Log into image registry/, output
|
||||||
assert_match /Build and push app image/, output
|
assert_match /Build and push app image/, output
|
||||||
assert_match /Ensure Traefik is running/, output
|
assert_match /Ensure Traefik is running/, output
|
||||||
|
|||||||
8
test/integration/docker/deployer/app/.mrsk/hooks/pre-connect
Executable file
8
test/integration/docker/deployer/app/.mrsk/hooks/pre-connect
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "About to lock..."
|
||||||
|
if [ "$MRSK_HOSTS" != "vm1,vm2" ]; then
|
||||||
|
echo "Expected hosts to be 'vm1,vm2', got $MRSK_HOSTS"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/pre-connect
|
||||||
@@ -8,16 +8,16 @@ class MainTest < IntegrationTest
|
|||||||
|
|
||||||
mrsk :deploy
|
mrsk :deploy
|
||||||
assert_app_is_up version: first_version
|
assert_app_is_up version: first_version
|
||||||
assert_hooks_ran "pre-build", "post-deploy"
|
assert_hooks_ran "pre-connect", "pre-build", "post-deploy"
|
||||||
|
|
||||||
second_version = update_app_rev
|
second_version = update_app_rev
|
||||||
|
|
||||||
mrsk :redeploy
|
mrsk :redeploy
|
||||||
assert_app_is_up version: second_version
|
assert_app_is_up version: second_version
|
||||||
assert_hooks_ran "pre-build", "post-deploy"
|
assert_hooks_ran "pre-connect", "pre-build", "post-deploy"
|
||||||
|
|
||||||
mrsk :rollback, first_version
|
mrsk :rollback, first_version
|
||||||
assert_hooks_ran "post-deploy"
|
assert_hooks_ran "pre-connect", "post-deploy"
|
||||||
assert_app_is_up version: first_version
|
assert_app_is_up version: first_version
|
||||||
|
|
||||||
details = mrsk :details, capture: true
|
details = mrsk :details, capture: true
|
||||||
|
|||||||
Reference in New Issue
Block a user