123 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			123 lines
		
	
	
		
			2.8 KiB
		
	
	
	
		
			Ruby
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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_ROLES (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 = github_repo_from_remote_url
 | |
|     @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
 | |
| 
 | |
|   private
 | |
|     def github_repo_from_remote_url
 | |
|       url = `git config --get remote.origin.url`.strip.delete_suffix(".git")
 | |
|       if url.start_with?("https://github.com/")
 | |
|         url.delete_prefix("https://github.com/")
 | |
|       elsif url.start_with?("git@github.com:")
 | |
|         url.delete_prefix("git@github.com:")
 | |
|       else
 | |
|         url
 | |
|       end
 | |
|     end
 | |
| end
 | |
| 
 | |
| 
 | |
| $stdout.sync = true
 | |
| 
 | |
| begin
 | |
|   puts "Checking build status..."
 | |
| 
 | |
|   attempts = 0
 | |
|   checks = GithubStatusChecks.new
 | |
| 
 | |
|   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
 | 
