diff --git a/README.md b/README.md index ca550b15..dda74054 100644 --- a/README.md +++ b/README.md @@ -880,17 +880,21 @@ fine-grained audit reporting, e.g. for triggering deployment reports or firing a JSON webhook. These variables include: - `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_MESSAGE` - the full audit message, e.g. "Deployed app@150b24f" -- `MRSK_SERVICE_VERSION` - an abbreviated version (for use in messages) +- `MRSK_SERVICE_VERSION` - an abbreviated service and version for use in messages, e.g. app@150b24f +- `MRSK_VERSION` - an full version being deployed - `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" -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. -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. diff --git a/lib/mrsk/cli/base.rb b/lib/mrsk/cli/base.rb index 395df4cc..439223c4 100644 --- a/lib/mrsk/cli/base.rb +++ b/lib/mrsk/cli/base.rb @@ -76,6 +76,8 @@ module Mrsk::Cli if MRSK.holding_lock? yield else + run_hook "pre-connect" + acquire_lock begin @@ -135,7 +137,7 @@ module Mrsk::Cli if !options[:skip_hooks] && MRSK.hook.hook_exists?(hook) say "Running the #{hook} hook...", :magenta 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 raise HookError.new("Hook `#{hook}` failed") end diff --git a/lib/mrsk/cli/templates/sample_hooks/post-deploy.sample b/lib/mrsk/cli/templates/sample_hooks/post-deploy.sample index abc18ccb..42681cc2 100755 --- a/lib/mrsk/cli/templates/sample_hooks/post-deploy.sample +++ b/lib/mrsk/cli/templates/sample_hooks/post-deploy.sample @@ -12,6 +12,8 @@ # MRSK_RECORDED_AT # MRSK_PERFORMER # MRSK_VERSION +# MRSK_HOSTS +# MRSK_ROLE (if set) # MRSK_DESTINATION (if set) # MRSK_RUNTIME diff --git a/lib/mrsk/cli/templates/sample_hooks/pre-build.sample b/lib/mrsk/cli/templates/sample_hooks/pre-build.sample index 0d12be7e..2902ea10 100755 --- a/lib/mrsk/cli/templates/sample_hooks/pre-build.sample +++ b/lib/mrsk/cli/templates/sample_hooks/pre-build.sample @@ -12,6 +12,8 @@ # MRSK_RECORDED_AT # MRSK_PERFORMER # MRSK_VERSION +# MRSK_HOSTS +# MRSK_ROLE (if set) # MRSK_DESTINATION (if set) if [ -n "$(git status --porcelain)" ]; then diff --git a/lib/mrsk/cli/templates/sample_hooks/pre-connect.sample b/lib/mrsk/cli/templates/sample_hooks/pre-connect.sample new file mode 100755 index 00000000..557ecccb --- /dev/null +++ b/lib/mrsk/cli/templates/sample_hooks/pre-connect.sample @@ -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 ] diff --git a/test/cli/main_test.rb b/test/cli/main_test.rb index b2884b4e..d4e779f3 100644 --- a/test/cli/main_test.rb +++ b/test/cli/main_test.rb @@ -23,6 +23,7 @@ class CliMainTest < CliTestCase Mrsk::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true) run_command("deploy").tap do |output| + assert_match /Running the pre-connect hook.../, output assert_match /Log into image registry/, output assert_match /Build and push app image/, output assert_match /Ensure Traefik is running/, output diff --git a/test/integration/docker/deployer/app/.mrsk/hooks/pre-connect b/test/integration/docker/deployer/app/.mrsk/hooks/pre-connect new file mode 100755 index 00000000..51585d1e --- /dev/null +++ b/test/integration/docker/deployer/app/.mrsk/hooks/pre-connect @@ -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 diff --git a/test/integration/main_test.rb b/test/integration/main_test.rb index 9fecf6b1..341af5eb 100644 --- a/test/integration/main_test.rb +++ b/test/integration/main_test.rb @@ -8,16 +8,16 @@ class MainTest < IntegrationTest mrsk :deploy 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 mrsk :redeploy 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 - assert_hooks_ran "post-deploy" + assert_hooks_ran "pre-connect", "post-deploy" assert_app_is_up version: first_version details = mrsk :details, capture: true