aboutsummaryrefslogtreecommitdiffstats
path: root/.kamal/hooks/pre-deploy.sample
blob: 1b280c719e5defecc6dda512ebebc129fff2274d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/usr/bin/env ruby

# A sample pre-deploy hook
#
# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
#
# Fails unless the combined status is "success"
#
# These environment variables are available:
# KAMAL_RECORDED_AT
# KAMAL_PERFORMER
# KAMAL_VERSION
# KAMAL_HOSTS
# KAMAL_COMMAND
# KAMAL_SUBCOMMAND
# KAMAL_ROLE (if set)
# KAMAL_DESTINATION (if set)

# Only check the build status for production deployments
if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
  exit 0
end

require "bundler/inline"

# true = install gems so this is fast on repeat invocations
gemfile(true, quiet: true) do
  source "https://rubygems.org"

  gem "octokit"
  gem "faraday-retry"
end

MAX_ATTEMPTS = 72
ATTEMPTS_GAP = 10

def exit_with_error(message)
  $stderr.puts message
  exit 1
end

class GithubStatusChecks
  attr_reader :remote_url, :git_sha, :github_client, :combined_status

  def initialize
    @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
    @git_sha = `git rev-parse HEAD`.strip
    @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
    refresh!
  end

  def refresh!
    @combined_status = github_client.combined_status(remote_url, git_sha)
  end

  def state
    combined_status[:state]
  end

  def first_status_url
    first_status = combined_status[:statuses].find { |status| status[:state] == state }
    first_status && first_status[:target_url]
  end

  def complete_count
    combined_status[:statuses].count { |status| status[:state] != "pending"}
  end

  def total_count
    combined_status[:statuses].count
  end

  def current_status
    if total_count > 0
      "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
    else
      "Build not started..."
    end
  end
end


$stdout.sync = true

puts "Checking build status..."
attempts = 0
checks = GithubStatusChecks.new

begin
  loop do
    case checks.state
    when "success"
      puts "Checks passed, see #{checks.first_status_url}"
      exit 0
    when "failure"
      exit_with_error "Checks failed, see #{checks.first_status_url}"
    when "pending"
      attempts += 1
    end

    exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS

    puts checks.current_status
    sleep(ATTEMPTS_GAP)
    checks.refresh!
  end
rescue Octokit::NotFound
  exit_with_error "Build status could not be found"
end