Last active
November 24, 2023 16:45
-
-
Save antifuchs/5c2394eb88e0ca436ba9113e2aaeaed7 to your computer and use it in GitHub Desktop.
GithubEvalNotify.pm - a hydra plugin that pushes evaluation status of each commit to your nix flake's github repo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# GithubEvalNotify.pm - a hydra plugin that pushes evaluation status of each commit | |
# to your nix flake's github repo | |
# | |
# Note that this file must live under the path "ci/hydra-plugins/Hydra/Plugin/GithubEvalNotify.pm" | |
# The "ci/hydra-plugins" can be changed, but you have to adjust it in the hydra.nix config below. | |
# The "Hydra/Plugin/" directory must be kept intact though, otherwise perl won't load this module. | |
package Hydra::Plugin::GithubEvalNotify; | |
use strict; | |
use warnings; | |
use parent 'Hydra::Plugin'; | |
use HTTP::Request; | |
use JSON::MaybeXS; | |
use LWP::UserAgent; | |
sub isEnabled { | |
my ($self) = @_; | |
return defined $self->{config}->{githubstatus}; | |
} | |
## Evaluation has begun: | |
## | |
## Sadly, useless - isn't called at the right time & doesn't get the | |
## necessary IDs to say anything about the evaluation. | |
# sub evalStarted { | |
# my ($self, $traceID, $jobset) = @_; | |
# print STDERR "evalStarted was called!\n"; | |
# } | |
## Same for evalFailed, but thankfully evalAdded is able to handle that case. | |
sub common { | |
my ($self, $traceID, $jobset, $eval, $cached) = @_; | |
my $baseurl = $self->{config}->{'base_uri'} || "http://localhost:3000"; | |
my $jsId = $jobset->id; | |
my $jsName = $jobset->name; | |
my $evalId = $eval->id; | |
my $fl = $eval->flake; | |
if (!$fl) { | |
print STDERR "Evaluation finalized, but TODO - determine github ref: $evalId for jobset $jsId\n"; | |
return 0; | |
} | |
my $job_name = $jobset->get_column('project') . ":" . $jobset->get_column('name'); | |
my $github_job_name = $job_name =~ s/-pr-\d+//r; | |
my $context = "ci/hydra-eval:" . $github_job_name; | |
my $error = $eval->evaluationerror; | |
my $body = encode_json( | |
{ | |
state => $error->errormsg ne "" ? "failure" : "success", | |
target_url => "$baseurl/eval/" . $eval->id, | |
description => "Hydra evaluation #" . $eval->id . " of $jsName", | |
context => $context, | |
}); | |
print STDERR "Evaluation finalized (cached: $cached): $evalId, $github_job_name $fl\n"; | |
if ($cached) { | |
return 0; | |
} | |
# Actually post status to github (sendStatus & the repo ref computation taken from GithubStatus.pm): | |
my $ua = LWP::UserAgent->new(); | |
my $sendStatus = sub { | |
my ($input, $owner, $repo, $rev) = @_; | |
my $key = $owner . "-" . $repo . "-" . $rev; | |
my $url = "https://api.github.com/repos/$owner/$repo/statuses/$rev"; | |
my $req = HTTP::Request->new('POST', $url); | |
$req->header('Content-Type' => 'application/json'); | |
$req->header('Accept' => 'application/vnd.github.v3+json'); | |
$req->header('Authorization' => $self->{config}->{github_authorization}->{$owner}); | |
$req->content($body); | |
my $res = $ua->request($req); | |
print STDERR $res->status_line, ": ", $res->decoded_content, "\n" unless $res->is_success; | |
my $limit = $res->header("X-RateLimit-Limit"); | |
my $limitRemaining = $res->header("X-RateLimit-Remaining"); | |
my $limitReset = $res->header("X-RateLimit-Reset"); | |
my $now = time(); | |
my $diff = $limitReset - $now; | |
my $delay = (($limit - $limitRemaining) / $diff) * 5; | |
if ($limitRemaining < 1000) { | |
$delay = max(1, $delay); | |
} | |
if ($limitRemaining < 2000) { | |
print STDERR "GithubEvalNotify ratelimit $limitRemaining/$limit, resets in $diff, sleeping $delay\n"; | |
sleep $delay; | |
} else { | |
print STDERR "GithubEvalNotify ratelimit $limitRemaining/$limit, resets in $diff\n"; | |
} | |
}; | |
if ($eval->flake =~ m!github:([^/]+)/([^/]+)/([[:xdigit:]]{40})$! or $eval->flake =~ m!git\+ssh://git\@github.com/([^/]+)/([^/]+)\?.*rev=([[:xdigit:]]{40})$!) { | |
$sendStatus->("src", $1, $2, $3); | |
} else { | |
print STDERR "Can't parse flake, skipping evaluation GitHub status update for $evalId, js:$jsId ev:$evalId $fl\n"; | |
} | |
} | |
## Evaluation has finished adding builds: | |
sub evalAdded { | |
common(@_, 0); | |
} | |
## Evaluation didn't happen, as it is already cached: | |
sub evalCached { | |
common(@_, 1); | |
} | |
1; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{pkgs, config, lib, ...}: { | |
services.hydra = { | |
# The rest of your hydra config, and then: | |
extraEnv = { | |
PERL5LIB = let | |
hydraPlugins = builtins.path { | |
path = ./ci/hydra-plugins; | |
name = "hydra-plugins"; | |
}; | |
in "${hydraPlugins}"; | |
}; | |
extraConfig = '' | |
# Make sure you configure github authorization under <githubstatus>! | |
''; | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment