Files
ubicloud/helpers/runtime.rb
Enes Cakir c77912a8e9 Check the job scope from the GitHub API if the webhook is delayed
We prioritize both cache security and privacy along with the
performance. We adhere to the same cache restrictions as the default
GitHub Actions Cache [^1].

The endpoint needs to verify the head branch of the workflow job to
allow cache access [^2] . The `runner.workflow_job&.dig("head_branch")`
property, filled via the webhook payload delivered by GitHub when the
job started to run our runners. However, occasional delays in GitHub's
delivery of this webhook can cause the job to receive a 'not found'
response from the endpoint, as it fails to verify the branch.

Without the webhook details, we only have the self-hosted runner's name.
However, the GitHub API doesn’t provide a way to get job details using
just the runner name, so we need additional information.

If we have the job ID, we can retrieve job details from the GitHub API.
We verify the runner name against the job's runner name; if they match,
we can obtain the head branch from the job details.

Unfortunately, the cache client doesn’t have the job ID, but it does
have the run ID. This means we can retrieve all jobs from the workflow
run instead of just the details for a single job. While it’s still a
single API request, it’s slower than getting the details for a single
job.

Next, we try to find the correct job details from the list of jobs using
the runner name.

This approach adds some latency to the runtime cache endpoints, but it's
only for cases where the webhook is delayed, which is rare.

[^1]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache
[^2]: https://github.com/ubicloud/ubicloud/blob/main/routes/runtime/github.rb#L24-L28
2024-12-03 11:42:09 +03:00

37 lines
1.3 KiB
Ruby

# frozen_string_literal: true
class Clover < Roda
def get_runtime_jwt_payload
return unless (v = request.env["HTTP_AUTHORIZATION"])
jwt_token = v.sub(%r{\ABearer:?\s+}, "")
begin
JWT.decode(jwt_token, Config.clover_runtime_token_secret, true, {algorithm: "HS256"})[0]
rescue JWT::DecodeError
end
end
def get_scope_from_github(runner, run_id)
log_context = {runner_ubid: runner.ubid, repository_ubid: runner.repository.ubid, run_id: run_id}
if run_id.nil? || run_id.empty?
Clog.emit("The run_id is blank") { {runner_scope_failure: log_context} }
return
end
Clog.emit("Get runner scope from GitHub API") { {get_runner_scope: log_context} }
client = Github.installation_client(runner.installation.installation_id)
begin
jobs = client.workflow_run_jobs(runner.repository_name, run_id)[:jobs]
rescue Octokit::ClientError => ex
log_context[:expection] = Util.exception_to_hash(ex)
Clog.emit("Could not list the jobs of the workflow run ") { {runner_scope_failure: log_context} }
return
end
if (job = jobs.find { _1[:runner_name] == runner.ubid })
job[:head_branch]
else
Clog.emit("The workflow run does not have given runner") { {runner_scope_failure: log_context} }
nil
end
end
end