hydra: add Coverity Scan plugin

This adds a Hydra plugin for users to submit their open source projects
to the Coverity Scan system for analysis.

First, add a <coverityscan> section to your Hydra config, including the
access token, project name, and email, and a regex specifying jobs to
upload:

    <coverityscan>
      project = testrix
      jobs    = foobar:.*:coverity.*
      email   = aseipp@pobox.com
      token   = ${builtins.readFile ./coverity-token}
    </coverityscan>

This will upload the scan results for any job whose name matches
'coverity.*' in any jobset in the Hydra 'foobar' project, for the
Coverity Scan project named 'testrix'.

Note that one upload will occur per job matched by the regular
expression - so be careful with how many builds you upload.

The jobs which are matched by the jobs specification must have a file in
their output path of the form:

  $out/tarballs/...-cov-int.(xz|lzma|zip|bz2|tgz)

The file must have the 'cov-int' directory produced by `cov-build` in
the root.

(You can also output something into
$out/nix-support/hydra-build-products for the Hydra UI.)

This file will be found in the store, and uploaded to the service
directly using your access credentials. Note the exact extension: don't
use .tar.xz, only use .xz specifically.

Signed-off-by: Austin Seipp <aseipp@pobox.com>
This commit is contained in:
Austin Seipp 2014-04-30 18:50:43 -05:00
parent 6c6ce7a781
commit 4166974657

View file

@ -0,0 +1,122 @@
package Hydra::Plugin::CoverityScan;
use strict;
use feature 'switch';
use parent 'Hydra::Plugin';
use File::Basename;
use LWP::UserAgent;
use Hydra::Helper::CatalystUtils;
sub buildFinished {
my ($self, $b, $dependents) = @_;
my $cfg = $self->{config}->{coverityscan};
my @config = defined $cfg ? ref $cfg eq "ARRAY" ? @$cfg : ($cfg) : ();
# Scan the job and see if it matches any of the Coverity Scan projects
my $proj;
my $jobName = showJobName $b;
foreach my $p (@config) {
next unless $jobName =~ /^$p->{jobs}$/;
# If build is cancelled or aborted, do not upload build
next if $b->buildstatus == 4 || $b->buildstatus == 3;
# Otherwise, select this Coverity project
$proj = $p; last;
}
# Bail if there's no matching project
return unless defined $proj;
# Compile submission information
my $project = $proj->{project};
my $email = $proj->{email};
my $token = $proj->{token};
my $scanurl = $proj->{scanurl} || "http://scan5.coverity.com/cgi-bin/upload.py";
# Sanity checks
die "coverity project name not configured" unless defined $project;
die "email must be specified for Coverity project '".$project."'"
unless defined $email;
die "access token must be specified for Coverity project '".$project."'"
unless defined $token;
# Get tarball locations
my $storePath = ($b->buildoutputs)[0]->path;
my $tarballs = "$storePath/tarballs";
my $covTarball;
opendir TARBALLS, $tarballs or die;
while (readdir TARBALLS) {
next unless $_ =~ /.*-coverity-int\.(tgz|lzma|xz|bz2|zip)$/;
$covTarball = "$tarballs/$_"; last;
}
closedir TARBALLS;
unless (defined $covTarball) {
print STDERR "CoverityScan.pm: Coverity tarball not found in $tarballs; skipping upload...\n";
return;
}
# Find the file mimetype
my @exts = qw(.xz .bz2 .lzma .zip .tgz);
my ($dir, $file, $ext) = fileparse($covTarball, @exts);
my $mimetype;
given ($ext) {
when ('.xz') { $mimetype = "application/x-xz"; }
when ('.lzma') { $mimetype = "application/x-xz"; }
when ('.zip') { $mimetype = "application/zip"; }
when ('.bz2') { $mimetype = "application/x-bzip2"; }
when ('.tgz') { $mimetype = "application/x-gzip"; }
default { die "couldn't parse extension of $covTarball"; }
}
die "couldn't detect mimetype of $covTarball" unless defined $mimetype;
# Parse version number from tarball
my $pkgNameRE = "(?:(?:[A-Za-z0-9]|(?:-[^0-9]))+)";
my $versionRE = "(?:[A-Za-z0-9\.\-]+)";
my $shortName = basename($covTarball);
my $version = $2 if $shortName =~ /^($pkgNameRE)-($versionRE)-coverity-int.*$/;
die "CoverityScan.pm: Couldn't parse build version for upload! ($shortName)"
unless defined $version;
# Submit build
my $jobid = $b->id;
my $desc = "Hydra Coverity Build ($jobName) - $jobid:$version";
print STDERR "uploading $desc ($shortName) to Coverity Scan\n";
my $ua = LWP::UserAgent->new();
my $resp = $ua->post($scanurl,
Content_Type => 'form-data',
Content => [
project => $project,
email => $email,
token => $token,
version => $version,
description => $desc,
file => [ $covTarball, $shortName,
Content_Type => $mimetype,
],
],
);
# The Coverity HTTP endpoint doesn't handle errors very well, and always
# returns a 200 :(
my $results = $resp->decoded_content;
if ($results =~ /ERROR!/) {
print STDERR "CoverityScan.pm: upload error - ", $resp->decoded_content, "\n";
return;
}
# Just for sanity, in case things change later
unless ($results =~ /Your request has been submitted/) {
print STDERR "CoverityScan.pm: upload error, didn't find expected response - ", $resp->decoded_content, "\n";
}
}
1;