Rack 3 requires that response headers use lower case keys. To support applications designed for Rack < 3, Roda by default uses Rack::Headers for response headers, which will automatically lower case keys. This is suboptimal for performance. The plain_hash_response_headers plugin allows using a plain hash for response headers, but in order to use it, you must ensure that all response header keys are already lower case. So change all response header keys to lower case. Unfortunately, committee is not rack 3 compliant and does not support lower case response header keys. It indexes into response headers with Content-Type instead of content-type, which breaks on rack 3 unless the headers hash is automatically lower casing keys. Work around this issue by setting the Content-Type header before calling committee's response validation, and deleting it afterward. This can be removed after committee is fixed.
105 lines
3.0 KiB
Ruby
105 lines
3.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class Clover
|
|
hash_branch(:webhook_prefix, "github") do |r|
|
|
r.post true do
|
|
body = r.body.read
|
|
next 401 unless check_signature(r.headers["x-hub-signature-256"], body)
|
|
|
|
response.headers["content-type"] = "application/json"
|
|
|
|
data = JSON.parse(body)
|
|
case r.headers["x-github-event"]
|
|
when "installation"
|
|
handle_installation(data)
|
|
when "workflow_job"
|
|
handle_workflow_job(data)
|
|
else
|
|
error("Unhandled event")
|
|
end
|
|
end
|
|
end
|
|
|
|
def error(msg)
|
|
{error: {message: msg}}
|
|
end
|
|
|
|
def success(msg)
|
|
{message: msg}
|
|
end
|
|
|
|
def check_signature(signature, body)
|
|
return false unless signature
|
|
method, actual_digest = signature.split("=")
|
|
expected_digest = OpenSSL::HMAC.hexdigest(method, Config.github_app_webhook_secret, body)
|
|
Rack::Utils.secure_compare(actual_digest, expected_digest)
|
|
end
|
|
|
|
def handle_installation(data)
|
|
installation = GithubInstallation[installation_id: data["installation"]["id"]]
|
|
case data["action"]
|
|
when "deleted"
|
|
unless installation
|
|
return error("Unregistered installation")
|
|
end
|
|
Prog::Github::DestroyGithubInstallation.assemble(installation)
|
|
return success("GithubInstallation[#{installation.ubid}] deleted")
|
|
end
|
|
|
|
error("Unhandled installation action")
|
|
end
|
|
|
|
def handle_workflow_job(data)
|
|
unless (installation = GithubInstallation[installation_id: data["installation"]["id"]])
|
|
return error("Unregistered installation")
|
|
end
|
|
|
|
unless (job = data["workflow_job"])
|
|
Clog.emit("No workflow_job in the payload") { {workflow_job_missing: {installation_id: installation.id, action: data["action"]}} }
|
|
return error("No workflow_job in the payload")
|
|
end
|
|
|
|
unless (label = job.fetch("labels").find { Github.runner_labels.key?(it) })
|
|
return error("Unmatched label")
|
|
end
|
|
|
|
if data["action"] == "queued"
|
|
st = Prog::Vm::GithubRunner.assemble(
|
|
installation,
|
|
repository_name: data["repository"]["full_name"],
|
|
label: label,
|
|
default_branch: data["repository"]["default_branch"]
|
|
)
|
|
runner = GithubRunner[st.id]
|
|
|
|
return success("GithubRunner[#{runner.ubid}] created")
|
|
end
|
|
|
|
unless (runner_id = job.fetch("runner_id"))
|
|
return error("A workflow_job without runner_id")
|
|
end
|
|
|
|
runner = GithubRunner.first(
|
|
installation_id: installation.id,
|
|
repository_name: data["repository"]["full_name"],
|
|
runner_id:
|
|
)
|
|
|
|
return error("Unregistered runner") unless runner
|
|
|
|
runner.this.update(workflow_job: Sequel.pg_jsonb(job.except("steps")))
|
|
|
|
case data["action"]
|
|
when "in_progress"
|
|
runner.log_duration("runner_started", Time.parse(job["started_at"]) - Time.parse(job["created_at"]))
|
|
success("GithubRunner[#{runner.ubid}] picked job #{job.fetch("id")}")
|
|
when "completed"
|
|
runner.incr_destroy
|
|
|
|
success("GithubRunner[#{runner.ubid}] deleted")
|
|
else
|
|
error("Unhandled workflow_job action")
|
|
end
|
|
end
|
|
end
|