Files
ubicloud/routes/webhook/github.rb
Jeremy Evans 4c0bd0295f Use the Roda plain_hash_response_headers plugin
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.
2025-05-13 06:04:31 +09:00

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