We have a situation where we need to have two people unblock a step before it actually unblocks it. This is to enforce a policy of code review before pipeline run.
Hey @nathan.pierce! Thanks for the request.
Have you considered adding two block steps? You’d have to verify they’re two different users via the API, comparing the “unblocked” in the jobs payload of the build, but it’s probably possible to orchestrate now if you needed.
Can you show me what you mean by verify they’re two separate users using the API?
Are you saying that a third block step could be triggered with a lambda or something if the first two block steps get triggered by different people?
@nathan.pierce sorry for the late reply on this one!
Here’s an example pipeline:
steps:
- command: "echo test"
- block: "Confirmation #1"
- block: "Confirmation #2"
- command: "ruby check.rb"
- wait
- command: "echo deploy"
Here I’ve added 2 block steps so that it can be unblocked by 2 different folks.
The big issue here is that there’s nothing stopping the same person unblocking both steps, so that’s what the check.rb
script does. I wrote it in Ruby, but you can write it in any language! (or even build it into your deploy scripts as some sort of check)
This is what that looks like:
BUILDKITE_ORGANIZATION_SLUG = ENV.fetch("BUILDKITE_ORGANIZATION_SLUG")
BUILDKITE_PIPELINE_SLUG = ENV.fetch("BUILDKITE_PIPELINE_SLUG")
BUILDKITE_BUILD_NUMBER = ENV.fetch("BUILDKITE_BUILD_NUMBER")
TOKEN = "..."
build_api_url = "https://api.buildkite.com/v2/organizations/#{BUILDKITE_ORGANIZATION_SLUG}/pipelines/#{BUILDKITE_PIPELINE_SLUG}/builds/#{BUILDKITE_BUILD_NUMBER}"
require "net/http"
require "uri"
require "json"
# Setup the HTTP request
uri = URI.parse(build_api_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
request["Authorization"] = "Bearer #{TOKEN}"
# Call out to the Buildkite API
response = http.request(request)
json = JSON.parse(response.body)
# Find the 2 block steps
first_confirmation_step = json["jobs"].find { |j| j["label"] == "Confirmation #1" }
second_confirmation_step = json["jobs"].find { |j| j["label"] == "Confirmation #2" }
if first_confirmation_step.nil? || second_confirmation_step.nil?
puts "Can't find confirmation steps in build payload"
exit 1
end
first_confirmation_unblocked_by = first_confirmation_step["unblocked_by"]
second_confirmation_unblocked_by = second_confirmation_step["unblocked_by"]
if first_confirmation_unblocked_by.nil? || second_confirmation_unblocked_by.nil?
puts "Both steps haven't been unblocked yet"
exit 1
end
if first_confirmation_unblocked_by["id"] == second_confirmation_unblocked_by["id"]
puts "Both steps need to be unblocked by sepereate people"
exit 1
end
puts "All good!"
exit 0
It grabs the current build using the REST API, and looks up both the block steps. It then checks that both unblockers are different. You can do further checks in here (like what teams their in for example), or even have a hard-coded list of folks that you only want to allow.
I hope that helps!
Thanks! I’d love to see this as functionality in the existing block steps though.