From 4cb5e6cd9414ed24e3f046c31a84aec3f50269be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sun, 26 Dec 2021 16:14:10 +0100 Subject: [PATCH 01/36] RunCommand: Capture the output of the commands --- hydra-module.nix | 7 ++++++- src/lib/Hydra/Plugin/RunCommand.pm | 15 ++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/hydra-module.nix b/hydra-module.nix index 03265c66..e3d7e614 100644 --- a/hydra-module.nix +++ b/hydra-module.nix @@ -275,7 +275,11 @@ in mkdir -m 0700 -p ${baseDir}/queue-runner mkdir -m 0750 -p ${baseDir}/build-logs - chown hydra-queue-runner.hydra ${baseDir}/queue-runner ${baseDir}/build-logs + mkdir -m 0750 -p ${baseDir}/runcommand-logs + chown hydra-queue-runner.hydra \ + ${baseDir}/queue-runner \ + ${baseDir}/build-logs \ + ${baseDir}/runcommand-logs ${optionalString haveLocalDB '' if ! [ -e ${baseDir}/.db-created ]; then @@ -457,6 +461,7 @@ in script = '' find /var/lib/hydra/build-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f + find /var/lib/hydra/runcommand-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f ''; startAt = "Sun 01:45"; }; diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 36c77ce4..096bf1c1 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -5,6 +5,8 @@ use warnings; use parent 'Hydra::Plugin'; use experimental 'smartmatch'; use JSON::MaybeXS; +use Digest::SHA1 qw(sha1_hex); +use Hydra::Model::DB; sub isEnabled { my ($self) = @_; @@ -160,7 +162,18 @@ sub buildFinished { $runlog->started(); - system("$command") == 0 + # Prepare log collection + my $filename = sha1_hex($command) . "-" . $build->get_column('id'); + my $dir = Hydra::Model::DB::getHydraPath . "/runcommand-logs/" . substr($filename, 0, 2); + my $logpath = "$dir/$filename"; + mkdir($dir, oct(755)); + # This creates the file with the correct permissions + open(my $f, '>', $logpath); + close($f); + chmod(oct(644), $logpath); + + # Run the command + system("$command 1>$logpath 2>&1") == 0 or warn "notification command '$command' failed with exit status $? ($!)\n"; $runlog->completed_with_child_error($?, $!); From 796ce165d4b16c00f2f7684aafa2a1bbe10bcd02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sun, 26 Dec 2021 16:14:28 +0100 Subject: [PATCH 02/36] RunCommand: Allow displaying command output --- src/lib/Hydra/Controller/Build.pm | 9 ++++++ src/lib/Hydra/Controller/Root.pm | 17 +++++++++++ src/lib/Hydra/Schema/Result/RunCommandLogs.pm | 28 +++++++++++++++++++ src/root/build.tt | 4 +-- src/root/log.tt | 6 ++-- 5 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index bde1030e..f00eecf8 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -137,6 +137,15 @@ sub view_log : Chained('buildChain') PathPart('log') { } +sub view_runcommand_log : Chained('buildChain') PathPart('runcommand-log') { + my ($self, $c, $sha) = @_; + + $c->stash->{is_runcommand} = 1; + $c->stash->{log_uri} = $c->uri_for($c->controller('Root')->action_for("runcommandlog"), $sha . "-" . $c->stash->{build}->id); + $c->stash->{template} = 'log.tt'; +} + + sub showLog { my ($c, $mode, $finished, $drvPath) = @_; $mode //= "pretty"; diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 2d8224a5..abb95c7c 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -7,6 +7,7 @@ use base 'Hydra::Base::Controller::ListBuilds'; use Hydra::Helper::Nix; use Hydra::Helper::CatalystUtils; use Hydra::View::TT; +use Hydra::Model::DB; use Nix::Store; use Nix::Config; use Encode; @@ -530,4 +531,20 @@ sub log :Local :Args(1) { } } +sub runcommandlog :Local :Args(1) { + my ($self, $c, $filename) = @_; + + my $tail = $c->request->params->{"tail"}; + + die if defined $tail && $tail !~ /^[0-9]+$/; + + my $logFile = Hydra::Model::DB::getHydraPath . "/runcommand-logs/" . substr($filename, 0, 2) . "/$filename"; + if (-f $logFile) { + serveLogFile($c, $logFile, $tail); + return; + } else { + notFound($c, "The RunCommand log is not available."); + } +} + 1; diff --git a/src/lib/Hydra/Schema/Result/RunCommandLogs.pm b/src/lib/Hydra/Schema/Result/RunCommandLogs.pm index b74416e8..66fa9c56 100644 --- a/src/lib/Hydra/Schema/Result/RunCommandLogs.pm +++ b/src/lib/Hydra/Schema/Result/RunCommandLogs.pm @@ -152,6 +152,8 @@ __PACKAGE__->belongs_to( # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9AIzlQl1RjRXrs9gQCZKVw use POSIX qw(WEXITSTATUS WIFEXITED WIFSIGNALED WTERMSIG); +use Digest::SHA1 qw(sha1_hex); +use Hydra::Model::DB; =head2 started @@ -321,3 +323,29 @@ sub did_fail_with_exec_error { } 1; + +=head2 log_relative_url + +Returns the URL to the log file relative to the build it belongs to. + +Return: + +* The relative URL if a log file exists +* An empty string otherwise +=cut +sub log_relative_url() { + my ($self) = @_; + + # Do not return a URL when there is no build yet + if (not defined($self->start_time)) { + return ""; + } + + my $sha = sha1_hex($self->command); + # Do not return a URL when there is no log file yet + if (not -f Hydra::Model::DB::getHydraPath . "/runcommand-logs/" . substr($sha, 0, 2) . "/$sha-" . $self->build_id) { + return ""; + } + + return "runcommand-log/$sha"; +} diff --git a/src/root/build.tt b/src/root/build.tt index dc58f191..ba122667 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -525,9 +525,9 @@ END; [% IF runcommandlog.start_time != undef %]
Started at [% INCLUDE renderDateTime timestamp = runcommandlog.start_time; %]
[% IF runcommandlog.end_time != undef %] -
Ran for [% INCLUDE renderDuration duration = runcommandlog.end_time - runcommandlog.start_time %]
+
Ran for [% INCLUDE renderDuration duration = runcommandlog.end_time - runcommandlog.start_time %][% IF runcommandlog.log_relative_url() %] — Logs[% END %]
[% ELSE %] -
Running for [% INCLUDE renderDuration duration = curTime - runcommandlog.start_time %]
+
Running for [% INCLUDE renderDuration duration = curTime - runcommandlog.start_time %][% IF runcommandlog.log_relative_url() %] — Logs[% END %]
[% END %] [% ELSE %]
Pending
diff --git a/src/root/log.tt b/src/root/log.tt index 8bb4954d..4874fa77 100644 --- a/src/root/log.tt +++ b/src/root/log.tt @@ -1,6 +1,6 @@ [% WRAPPER layout.tt - titleHTML="Log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ linkToJob(build.jobset, job) - title="Log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ makeNameTextForJob(build.jobset, job) + titleHTML=(is_runcommand ? "RunCommand log of " : "Log of ") _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ linkToJob(build.jobset, job) + title=(is_runcommand ? "RunCommand log of " : "Log of ") _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ makeNameTextForJob(build.jobset, job) %] [% PROCESS common.tt %] @@ -11,7 +11,7 @@ [% ELSE %] is [% END %] - the build log of derivation [% IF step; step.drvpath; ELSE; build.drvpath; END %]. + [% IF is_runcommand %] the output of a RunCommand execution of[% ELSE %] the build log of[% END %] derivation [% IF step; step.drvpath; ELSE; build.drvpath; END %]. [% IF step && step.machine %] It was built on [% step.machine %]. [% END %] From 14090fbb866b14561614061759bfab721e275bfd Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 24 Jan 2022 11:05:49 -0800 Subject: [PATCH 03/36] runcommand-log.tt: init --- src/lib/Hydra/Controller/Build.pm | 3 +- src/root/log.tt | 6 ++-- src/root/runcommand-log.tt | 49 +++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 src/root/runcommand-log.tt diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index f00eecf8..a833ae04 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -140,9 +140,8 @@ sub view_log : Chained('buildChain') PathPart('log') { sub view_runcommand_log : Chained('buildChain') PathPart('runcommand-log') { my ($self, $c, $sha) = @_; - $c->stash->{is_runcommand} = 1; $c->stash->{log_uri} = $c->uri_for($c->controller('Root')->action_for("runcommandlog"), $sha . "-" . $c->stash->{build}->id); - $c->stash->{template} = 'log.tt'; + $c->stash->{template} = 'runcommand-log.tt'; } diff --git a/src/root/log.tt b/src/root/log.tt index 4874fa77..8bb4954d 100644 --- a/src/root/log.tt +++ b/src/root/log.tt @@ -1,6 +1,6 @@ [% WRAPPER layout.tt - titleHTML=(is_runcommand ? "RunCommand log of " : "Log of ") _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ linkToJob(build.jobset, job) - title=(is_runcommand ? "RunCommand log of " : "Log of ") _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ makeNameTextForJob(build.jobset, job) + titleHTML="Log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ linkToJob(build.jobset, job) + title="Log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ makeNameTextForJob(build.jobset, job) %] [% PROCESS common.tt %] @@ -11,7 +11,7 @@ [% ELSE %] is [% END %] - [% IF is_runcommand %] the output of a RunCommand execution of[% ELSE %] the build log of[% END %] derivation [% IF step; step.drvpath; ELSE; build.drvpath; END %]. + the build log of derivation [% IF step; step.drvpath; ELSE; build.drvpath; END %]. [% IF step && step.machine %] It was built on [% step.machine %]. [% END %] diff --git a/src/root/runcommand-log.tt b/src/root/runcommand-log.tt new file mode 100644 index 00000000..d1d4cd75 --- /dev/null +++ b/src/root/runcommand-log.tt @@ -0,0 +1,49 @@ +[% WRAPPER layout.tt + titleHTML="RunCommand log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ linkToJob(build.jobset, job) + title="RunCommand log of " _ (step ? " step $step.stepnr of " : "") _ "build ${build.id} of job " _ makeNameTextForJob(build.jobset, job) +%] +[% PROCESS common.tt %] + +

+ Below + [% IF tail %] + are the last lines of + [% ELSE %] + is + [% END %] + the output of a RunCommand execution of derivation [% IF step; step.drvpath; ELSE; build.drvpath; END %]. + [% IF tail %] + The full log is also available. + [% END %] +

+ +
+Loading...
+
+ + + +[% END %] From 4a441b54ce9ad1722a5ae27664664c9921b83868 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 28 Jan 2022 12:59:11 -0800 Subject: [PATCH 04/36] hydra-module: /var/lib/hydra -> ${baseDir} --- hydra-module.nix | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hydra-module.nix b/hydra-module.nix index e3d7e614..c950f872 100644 --- a/hydra-module.nix +++ b/hydra-module.nix @@ -436,12 +436,12 @@ in if [ $(systemctl is-active $service) == active ]; then echo "stopping $service due to lack of free space..." systemctl stop $service - date > /var/lib/hydra/.$service-stopped-minspace + date > ${baseDir}/.$service-stopped-minspace fi else if [ $spaceleft -gt $(( ($minFreeGB + 10) * 1024**3)) -a \ - -r /var/lib/hydra/.$service-stopped-minspace ] ; then - rm /var/lib/hydra/.$service-stopped-minspace + -r ${baseDir}/.$service-stopped-minspace ] ; then + rm ${baseDir}/.$service-stopped-minspace echo "restarting $service due to newly available free space..." systemctl start $service fi @@ -460,8 +460,8 @@ in { path = [ pkgs.bzip2 ]; script = '' - find /var/lib/hydra/build-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f - find /var/lib/hydra/runcommand-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f + find ${baseDir}/build-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f + find ${baseDir}/runcommand-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f ''; startAt = "Sun 01:45"; }; From 5d3912962b8da28c377eb9dbfac022f1bffc80d7 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 24 Jan 2022 11:25:13 -0800 Subject: [PATCH 05/36] RunCommand: use helper functions to ensure filenames and paths are the same Otherwise, it's possible someone updates the format in one place but not the others, leading to broken or incorrect functionality. --- src/lib/Hydra/Controller/Build.pm | 2 +- src/lib/Hydra/Controller/Root.pm | 3 +-- src/lib/Hydra/Helper/Nix.pm | 18 ++++++++++++++++++ src/lib/Hydra/Plugin/RunCommand.pm | 16 +++++++++------- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index a833ae04..9a2c5607 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -140,7 +140,7 @@ sub view_log : Chained('buildChain') PathPart('log') { sub view_runcommand_log : Chained('buildChain') PathPart('runcommand-log') { my ($self, $c, $sha) = @_; - $c->stash->{log_uri} = $c->uri_for($c->controller('Root')->action_for("runcommandlog"), $sha . "-" . $c->stash->{build}->id); + $c->stash->{log_uri} = $c->uri_for($c->controller('Root')->action_for("runcommandlog"), constructRunCommandLogFilename($sha, $c->stash->{build}->id)); $c->stash->{template} = 'runcommand-log.tt'; } diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index abb95c7c..81f79378 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -7,7 +7,6 @@ use base 'Hydra::Base::Controller::ListBuilds'; use Hydra::Helper::Nix; use Hydra::Helper::CatalystUtils; use Hydra::View::TT; -use Hydra::Model::DB; use Nix::Store; use Nix::Config; use Encode; @@ -538,7 +537,7 @@ sub runcommandlog :Local :Args(1) { die if defined $tail && $tail !~ /^[0-9]+$/; - my $logFile = Hydra::Model::DB::getHydraPath . "/runcommand-logs/" . substr($filename, 0, 2) . "/$filename"; + my $logFile = constructRunCommandLogPath($filename); if (-f $logFile) { serveLogFile($c, $logFile, $tail); return; diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index 55c1f6f3..a58e638d 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -19,6 +19,8 @@ our @EXPORT = qw( cancelBuilds captureStdoutStderr captureStdoutStderrWithStdin + constructRunCommandLogFilename + constructRunCommandLogPath findLog gcRootFor getBaseUrl @@ -589,4 +591,20 @@ sub isLocalStore { } +sub constructRunCommandLogFilename { + my ($sha, $build_id) = @_; + my $filename = "$sha-$build_id"; + return $filename; +} + + +sub constructRunCommandLogPath { + my ($filename) = @_; + + my $hydra_path = Hydra::Model::DB::getHydraPath; + my $bucket = substr($filename, 0, 2); + + return "$hydra_path/runcommand-logs/$bucket/$filename"; +} + 1; diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 096bf1c1..725f7aaa 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -7,6 +7,8 @@ use experimental 'smartmatch'; use JSON::MaybeXS; use Digest::SHA1 qw(sha1_hex); use Hydra::Model::DB; +use Hydra::Helper::Nix; +use File::Basename qw(dirname); sub isEnabled { my ($self) = @_; @@ -162,15 +164,15 @@ sub buildFinished { $runlog->started(); - # Prepare log collection - my $filename = sha1_hex($command) . "-" . $build->get_column('id'); - my $dir = Hydra::Model::DB::getHydraPath . "/runcommand-logs/" . substr($filename, 0, 2); - my $logpath = "$dir/$filename"; + my $filename = constructRunCommandLogFilename(sha1_hex($command), $build->get_column('id')); + my $logPath = constructRunCommandLogPath($filename); + my $dir = dirname($logPath); + mkdir($dir, oct(755)); - # This creates the file with the correct permissions - open(my $f, '>', $logpath); + + open(my $f, '>', $logPath); close($f); - chmod(oct(644), $logpath); + chmod(oct(644), $logPath); # Run the command system("$command 1>$logpath 2>&1") == 0 From bb16f4fb101f73faa3b6c82a9f61da87b1685c8d Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 24 Jan 2022 11:27:09 -0800 Subject: [PATCH 06/36] RunCommand: set umask when creating log paths This uses the somewhat restrictive umask of 0027 so that people outside the user or group cannot read the files. This also helps to inhibit TOCTOU where someone else has a handle to our file before we chmod it and after we close it. --- src/lib/Hydra/Plugin/RunCommand.pm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 725f7aaa..084e0dd8 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -167,12 +167,16 @@ sub buildFinished { my $filename = constructRunCommandLogFilename(sha1_hex($command), $build->get_column('id')); my $logPath = constructRunCommandLogPath($filename); my $dir = dirname($logPath); + my $oldUmask = umask(); - mkdir($dir, oct(755)); + # file: 640, dir: 750 + umask(0027); + mkdir($dir); open(my $f, '>', $logPath); close($f); - chmod(oct(644), $logPath); + + umask($oldUmask); # Run the command system("$command 1>$logpath 2>&1") == 0 From bf3c46ed439abc3008f1889e490c8dd379739e4a Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 25 Jan 2022 10:33:01 -0800 Subject: [PATCH 07/36] RunCommand: use IPC::Run to spawn the command This allows `logPath`s with spaces and other characters that might otherwise cause problems inside a `system()` call. --- src/lib/Hydra/Plugin/RunCommand.pm | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 084e0dd8..93af65dc 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -9,6 +9,7 @@ use Digest::SHA1 qw(sha1_hex); use Hydra::Model::DB; use Hydra::Helper::Nix; use File::Basename qw(dirname); +use IPC::Run; sub isEnabled { my ($self) = @_; @@ -174,14 +175,15 @@ sub buildFinished { mkdir($dir); open(my $f, '>', $logPath); - close($f); - umask($oldUmask); - # Run the command - system("$command 1>$logpath 2>&1") == 0 + my $stdin = ""; + my @cmd = ["sh", "-c", $command]; + IPC::Run::run(@cmd, \$stdin, $f, '2>&1') == 1 or warn "notification command '$command' failed with exit status $? ($!)\n"; + close($f); + $runlog->completed_with_child_error($?, $!); } } From 1554750acc37f00f255b4ec70e0c9b41f7ff8694 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 25 Jan 2022 10:35:33 -0800 Subject: [PATCH 08/36] RunCommand: use make_path over mkdir This will make all necessary parent directories a la `mkdir -p`. --- src/lib/Hydra/Plugin/RunCommand.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 93af65dc..eea4d2e1 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -9,6 +9,7 @@ use Digest::SHA1 qw(sha1_hex); use Hydra::Model::DB; use Hydra::Helper::Nix; use File::Basename qw(dirname); +use File::Path qw(make_path); use IPC::Run; sub isEnabled { @@ -172,7 +173,7 @@ sub buildFinished { # file: 640, dir: 750 umask(0027); - mkdir($dir); + make_path($dir); open(my $f, '>', $logPath); umask($oldUmask); From 988e79c6e57f0e7ab746d3180eadf15aa65775ee Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 25 Jan 2022 10:55:40 -0800 Subject: [PATCH 09/36] t/RunCommand: test that the log file exists on the filesystem --- t/Hydra/Plugin/RunCommand/basic.t | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/t/Hydra/Plugin/RunCommand/basic.t b/t/Hydra/Plugin/RunCommand/basic.t index e0cbd1bb..9410d348 100644 --- a/t/Hydra/Plugin/RunCommand/basic.t +++ b/t/Hydra/Plugin/RunCommand/basic.t @@ -58,6 +58,13 @@ subtest "Validate a run log was created" => sub { is($runlog->start_time, within(time() - 1, 2), "The start time is recent."); is($runlog->end_time, within(time() - 1, 2), "The end time is also recent."); is($runlog->exit_code, 0, "This command should have succeeded."); + + subtest "Validate the run log file exists" => sub { + my $filename = Hydra::Helper::Nix::constructRunCommandLogFilename(sha1_hex($runlog->command), $build->get_column('id')); + my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($filename); + ok(-f $logPath, "The run log was saved to a file."); + ok(-z $logPath, "The run log was empty."); + }; }; done_testing; From 3432cd76364bd602fbbd770165ace50df6a07d14 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 25 Jan 2022 10:58:25 -0800 Subject: [PATCH 10/36] build.tt: split runcommand logic across multiple lines Helps with readability. --- src/root/build.tt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index ba122667..8a3a921f 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -525,9 +525,17 @@ END; [% IF runcommandlog.start_time != undef %]
Started at [% INCLUDE renderDateTime timestamp = runcommandlog.start_time; %]
[% IF runcommandlog.end_time != undef %] -
Ran for [% INCLUDE renderDuration duration = runcommandlog.end_time - runcommandlog.start_time %][% IF runcommandlog.log_relative_url() %] — Logs[% END %]
+
Ran for [% INCLUDE renderDuration duration = runcommandlog.end_time - runcommandlog.start_time %] + [% IF runcommandlog.log_relative_url() %] +  — Logs + [% END %] +
[% ELSE %] -
Running for [% INCLUDE renderDuration duration = curTime - runcommandlog.start_time %][% IF runcommandlog.log_relative_url() %] — Logs[% END %]
+
Running for [% INCLUDE renderDuration duration = curTime - runcommandlog.start_time %] + [% IF runcommandlog.log_relative_url() %] +  — Logs + [% END %] +
[% END %] [% ELSE %]
Pending
From c8f3943d037f3dcab867b2be90c66cb44e53a10e Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 25 Jan 2022 11:04:18 -0800 Subject: [PATCH 11/36] hydra-module: log files don't have a `.drv` extension --- hydra-module.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hydra-module.nix b/hydra-module.nix index c950f872..617b0752 100644 --- a/hydra-module.nix +++ b/hydra-module.nix @@ -461,7 +461,7 @@ in script = '' find ${baseDir}/build-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f - find ${baseDir}/runcommand-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f + find ${baseDir}/runcommand-logs -type f -mtime +3 -size +0c | xargs -r bzip2 -v -f ''; startAt = "Sun 01:45"; }; From 3e722f1d0a3d889ddd750f3139d90aa736bb81ee Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 25 Jan 2022 12:08:20 -0800 Subject: [PATCH 12/36] t/RunCommand: remove duplicate use --- t/Hydra/Plugin/RunCommand/json.t | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/t/Hydra/Plugin/RunCommand/json.t b/t/Hydra/Plugin/RunCommand/json.t index e0221b24..7bd8484d 100644 --- a/t/Hydra/Plugin/RunCommand/json.t +++ b/t/Hydra/Plugin/RunCommand/json.t @@ -10,13 +10,11 @@ my %ctx = test_init( |); -use Test2::V0; -use Hydra::Plugin::RunCommand; - require Hydra::Schema; require Hydra::Model::DB; use Test2::V0; +use Hydra::Plugin::RunCommand; my $db = Hydra::Model::DB->new; hydra_setup($db); From fdf6f4d3dad0967238b40621186826457224de5b Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 25 Jan 2022 12:09:05 -0800 Subject: [PATCH 13/36] RunCommand: use IPC::Run3::run3 instead run3 just seems to do better handling for what we want to do, and requires less deep-reaching changes to this plugin to get it to play nice, as IPC::Run::run would. --- flake.nix | 1 + src/lib/Hydra/Plugin/RunCommand.pm | 6 ++---- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 3e382d9f..3b69d0bb 100644 --- a/flake.nix +++ b/flake.nix @@ -474,6 +474,7 @@ git IOCompress IPCRun + IPCRun3 JSON JSONMaybeXS JSONXS diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index eea4d2e1..d1ce8f55 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -10,7 +10,7 @@ use Hydra::Model::DB; use Hydra::Helper::Nix; use File::Basename qw(dirname); use File::Path qw(make_path); -use IPC::Run; +use IPC::Run3; sub isEnabled { my ($self) = @_; @@ -178,9 +178,7 @@ sub buildFinished { open(my $f, '>', $logPath); umask($oldUmask); - my $stdin = ""; - my @cmd = ["sh", "-c", $command]; - IPC::Run::run(@cmd, \$stdin, $f, '2>&1') == 1 + run3($command, \undef, $f, $f, { return_if_system_error => 1 }) == 1 or warn "notification command '$command' failed with exit status $? ($!)\n"; close($f); From 244300c1ade190b1b08799ed43354b479d4e54c9 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Tue, 25 Jan 2022 12:10:43 -0800 Subject: [PATCH 14/36] RunCommand: remove unused and problematic imports Since breaking the filename construction out to a helper function, Hydra::Model::DB is no longer used. Importing Hydra::Helper::Nix, however, has the potential to break tests, so just use the functions we need without importing the entire module. --- src/lib/Hydra/Plugin/RunCommand.pm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index d1ce8f55..94ed5a25 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -5,9 +5,6 @@ use warnings; use parent 'Hydra::Plugin'; use experimental 'smartmatch'; use JSON::MaybeXS; -use Digest::SHA1 qw(sha1_hex); -use Hydra::Model::DB; -use Hydra::Helper::Nix; use File::Basename qw(dirname); use File::Path qw(make_path); use IPC::Run3; @@ -166,8 +163,8 @@ sub buildFinished { $runlog->started(); - my $filename = constructRunCommandLogFilename(sha1_hex($command), $build->get_column('id')); - my $logPath = constructRunCommandLogPath($filename); + my $filename = Hydra::Helper::Nix::constructRunCommandLogFilename(sha1_hex($command), $build->get_column('id')); + my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($filename); my $dir = dirname($logPath); my $oldUmask = umask(); From 3ae4b19d12797b5330e31732255f22e5281bb5d8 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Mon, 24 Jan 2022 10:44:27 -0500 Subject: [PATCH 15/36] flake: update to postgres 13 for UUID commands --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 3b69d0bb..0bd54597 100644 --- a/flake.nix +++ b/flake.nix @@ -518,7 +518,7 @@ gitAndTools.topGit mercurial darcs subversion breezy openssl bzip2 libxslt final.nix perlDeps perl mdbook pixz boost - postgresql_11 + postgresql_13 (if lib.versionAtLeast lib.version "20.03pre" then nlohmann_json else nlohmann_json.override { multipleHeaders = true; }) From cf49a05ff50bffa7d91826b4b9ddb4797335c319 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Mon, 24 Jan 2022 11:37:42 -0500 Subject: [PATCH 16/36] RunCommandLogs: add a uuid to each log entry --- src/lib/Hydra/Schema/Result/RunCommandLogs.pm | 26 +++++++++++++++++-- src/sql/hydra.sql | 5 +++- src/sql/upgrade-81.sql | 6 +++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 src/sql/upgrade-81.sql diff --git a/src/lib/Hydra/Schema/Result/RunCommandLogs.pm b/src/lib/Hydra/Schema/Result/RunCommandLogs.pm index 66fa9c56..0bc144d5 100644 --- a/src/lib/Hydra/Schema/Result/RunCommandLogs.pm +++ b/src/lib/Hydra/Schema/Result/RunCommandLogs.pm @@ -42,6 +42,12 @@ __PACKAGE__->table("runcommandlogs"); is_nullable: 0 sequence: 'runcommandlogs_id_seq' +=head2 uuid + + data_type: 'uuid' + is_nullable: 0 + size: 16 + =head2 job_matcher data_type: 'text' @@ -98,6 +104,8 @@ __PACKAGE__->add_columns( is_nullable => 0, sequence => "runcommandlogs_id_seq", }, + "uuid", + { data_type => "uuid", is_nullable => 0, size => 16 }, "job_matcher", { data_type => "text", is_nullable => 0 }, "build_id", @@ -130,6 +138,20 @@ __PACKAGE__->add_columns( __PACKAGE__->set_primary_key("id"); +=head1 UNIQUE CONSTRAINTS + +=head2 C + +=over 4 + +=item * L + +=back + +=cut + +__PACKAGE__->add_unique_constraint("runcommandlogs_uuid_unique", ["uuid"]); + =head1 RELATIONS =head2 build @@ -148,8 +170,8 @@ __PACKAGE__->belongs_to( ); -# Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-11-19 15:15:36 -# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:9AIzlQl1RjRXrs9gQCZKVw +# Created by DBIx::Class::Schema::Loader v0.07049 @ 2022-01-24 10:24:52 +# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZVpYU6k3d/k/nitjpdgf/A use POSIX qw(WEXITSTATUS WIFEXITED WIFSIGNALED WTERMSIG); use Digest::SHA1 qw(sha1_hex); diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 7762e133..459305df 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -568,6 +568,7 @@ create index IndexTaskRetriesOrdered on TaskRetries(retry_at asc); -- 3. Update the end_time and exit_code when it completes create table RunCommandLogs ( id serial primary key not null, + uuid uuid not null, job_matcher text not null, build_id integer not null, -- TODO: evaluation_id integer not null, @@ -599,7 +600,9 @@ create table RunCommandLogs ( constraint RunCommandLogs_end_time_has_start_time check ( -- If end time is not null, then end_time, exit_code, and core_dumped should not be null (end_time is null) or (start_time is not null) - ) + ), + -- The uuid should be actually unique. + constraint RunCommandLogs_uuid_unique unique(uuid) -- Note: if exit_code is not null then signal and core_dumped must be null. -- Similarly, if signal is not null then exit_code must be null and diff --git a/src/sql/upgrade-81.sql b/src/sql/upgrade-81.sql new file mode 100644 index 00000000..cfbb2239 --- /dev/null +++ b/src/sql/upgrade-81.sql @@ -0,0 +1,6 @@ +alter table runcommandlogs add column uuid uuid; +update runcommandlogs set uuid = gen_random_uuid() where uuid is null; +alter table runcommandlogs alter column uuid set not null; +alter table runcommandlogs add constraint RunCommandLogs_uuid_unique unique(uuid); +# the unique uuid constraint adds a unique, btree index, so we don't need to create out own: +# "runcommandlogs_uuid_unique" UNIQUE CONSTRAINT, btree (uuid) From 89e8676d8012670893d6580a1031c7ce434e2e4f Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Mon, 24 Jan 2022 11:52:32 -0500 Subject: [PATCH 17/36] UUID4Tiny: init --- flake.nix | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/flake.nix b/flake.nix index 0bd54597..1b29fa72 100644 --- a/flake.nix +++ b/flake.nix @@ -432,6 +432,20 @@ license = with final.lib.licenses; [ artistic1 gpl1Plus ]; }; }; + + UUID4Tiny = final.buildPerlPackage { + pname = "UUID4-Tiny"; + version = "0.002"; + src = final.fetchurl { + url = "mirror://cpan/authors/id/C/CV/CVLIBRARY/UUID4-Tiny-0.002.tar.gz"; + sha256 = "e7535b31e386d432dec7adde214348389e1d5cf753e7ed07f1ae04c4360840cf"; + }; + meta = { + description = "Cryptographically secure v4 UUIDs for Linux x64"; + license = with final.lib.licenses; [ artistic1 gpl1Plus ]; + }; + }; + }; hydra = with final; let @@ -502,6 +516,7 @@ TestPostgreSQL TextDiff TextTable + UUID4Tiny XMLSimple YAML ]; From dcb0c1425c0533e531c9bdaaffcc6f7691b16643 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Mon, 24 Jan 2022 11:56:34 -0500 Subject: [PATCH 18/36] RunCommandLogs: set a UUID automatically --- src/lib/Hydra/Schema/Result/RunCommandLogs.pm | 21 +++++++++++++++++++ t/Hydra/Schema/Result/RunCommandLogs.t | 6 ++++++ 2 files changed, 27 insertions(+) diff --git a/src/lib/Hydra/Schema/Result/RunCommandLogs.pm b/src/lib/Hydra/Schema/Result/RunCommandLogs.pm index 0bc144d5..54ab7a2f 100644 --- a/src/lib/Hydra/Schema/Result/RunCommandLogs.pm +++ b/src/lib/Hydra/Schema/Result/RunCommandLogs.pm @@ -174,9 +174,30 @@ __PACKAGE__->belongs_to( # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:ZVpYU6k3d/k/nitjpdgf/A use POSIX qw(WEXITSTATUS WIFEXITED WIFSIGNALED WTERMSIG); +use UUID4::Tiny qw(create_uuid_string); use Digest::SHA1 qw(sha1_hex); use Hydra::Model::DB; + +=head2 new + +Initialize a new row object. + +Sets the UUID automatically unless a UUID is specified in the attributes. + +=cut +sub new { + my ($class, $attrs) = @_; + + if (!defined $attrs->{uuid}) { + $attrs->{uuid} = create_uuid_string(); + } + + my $new = $class->next::method($attrs); + + return $new; +} + =head2 started Update the row with the current timestamp as the start time. diff --git a/t/Hydra/Schema/Result/RunCommandLogs.t b/t/Hydra/Schema/Result/RunCommandLogs.t index f93f92b6..80589549 100644 --- a/t/Hydra/Schema/Result/RunCommandLogs.t +++ b/t/Hydra/Schema/Result/RunCommandLogs.t @@ -20,6 +20,12 @@ sub new_run_log { }); } +subtest "A new record has a UUID" => sub { + my $runlog = new_run_log(); + is(length($runlog->uuid), 36, "The UUID attribute is sufficiently UUID-like."); + like($runlog->uuid, qr/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/, "The UUID attribute is sufficiently UUID-like."); +}; + subtest "Not yet started" => sub { my $runlog = new_run_log(); From fc3cf4ecb2ba2af8267bb2283e9cc069f01a4714 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Wed, 26 Jan 2022 10:26:28 -0800 Subject: [PATCH 19/36] RunCommandLogs: identify and access via uuid Using a sha1 of the command combined with the build ID is not a particularly good or unique identifier: * A build could fail, be restarted, and then succeed -- assuming no configuration changes, the sha1 hash of the command as well as the build ID will be the same. This would lead to an overwritten log file. * Allowing user input to influence filenames is not the best of ideas. --- src/lib/Hydra/Controller/Build.pm | 6 ++-- src/lib/Hydra/Controller/Root.pm | 4 +-- src/lib/Hydra/Helper/Nix.pm | 8 ------ src/lib/Hydra/Plugin/RunCommand.pm | 3 +- src/lib/Hydra/Schema/Result/RunCommandLogs.pm | 28 ------------------- src/root/build.tt | 8 +++--- 6 files changed, 10 insertions(+), 47 deletions(-) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index 9a2c5607..d02c66a4 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -137,10 +137,10 @@ sub view_log : Chained('buildChain') PathPart('log') { } -sub view_runcommand_log : Chained('buildChain') PathPart('runcommand-log') { - my ($self, $c, $sha) = @_; +sub view_runcommandlog : Chained('buildChain') PathPart('runcommandlog') { + my ($self, $c, $uuid) = @_; - $c->stash->{log_uri} = $c->uri_for($c->controller('Root')->action_for("runcommandlog"), constructRunCommandLogFilename($sha, $c->stash->{build}->id)); + $c->stash->{log_uri} = $c->uri_for($c->controller('Root')->action_for("runcommandlog"), $uuid); $c->stash->{template} = 'runcommand-log.tt'; } diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 81f79378..2683d6c9 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -531,13 +531,13 @@ sub log :Local :Args(1) { } sub runcommandlog :Local :Args(1) { - my ($self, $c, $filename) = @_; + my ($self, $c, $uuid) = @_; my $tail = $c->request->params->{"tail"}; die if defined $tail && $tail !~ /^[0-9]+$/; - my $logFile = constructRunCommandLogPath($filename); + my $logFile = constructRunCommandLogPath($uuid); if (-f $logFile) { serveLogFile($c, $logFile, $tail); return; diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index a58e638d..d2c4908c 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -19,7 +19,6 @@ our @EXPORT = qw( cancelBuilds captureStdoutStderr captureStdoutStderrWithStdin - constructRunCommandLogFilename constructRunCommandLogPath findLog gcRootFor @@ -591,13 +590,6 @@ sub isLocalStore { } -sub constructRunCommandLogFilename { - my ($sha, $build_id) = @_; - my $filename = "$sha-$build_id"; - return $filename; -} - - sub constructRunCommandLogPath { my ($filename) = @_; diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 94ed5a25..9c13e4b7 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -163,8 +163,7 @@ sub buildFinished { $runlog->started(); - my $filename = Hydra::Helper::Nix::constructRunCommandLogFilename(sha1_hex($command), $build->get_column('id')); - my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($filename); + my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($runlog->uuid); my $dir = dirname($logPath); my $oldUmask = umask(); diff --git a/src/lib/Hydra/Schema/Result/RunCommandLogs.pm b/src/lib/Hydra/Schema/Result/RunCommandLogs.pm index 54ab7a2f..6dd4d297 100644 --- a/src/lib/Hydra/Schema/Result/RunCommandLogs.pm +++ b/src/lib/Hydra/Schema/Result/RunCommandLogs.pm @@ -175,8 +175,6 @@ __PACKAGE__->belongs_to( use POSIX qw(WEXITSTATUS WIFEXITED WIFSIGNALED WTERMSIG); use UUID4::Tiny qw(create_uuid_string); -use Digest::SHA1 qw(sha1_hex); -use Hydra::Model::DB; =head2 new @@ -366,29 +364,3 @@ sub did_fail_with_exec_error { } 1; - -=head2 log_relative_url - -Returns the URL to the log file relative to the build it belongs to. - -Return: - -* The relative URL if a log file exists -* An empty string otherwise -=cut -sub log_relative_url() { - my ($self) = @_; - - # Do not return a URL when there is no build yet - if (not defined($self->start_time)) { - return ""; - } - - my $sha = sha1_hex($self->command); - # Do not return a URL when there is no log file yet - if (not -f Hydra::Model::DB::getHydraPath . "/runcommand-logs/" . substr($sha, 0, 2) . "/$sha-" . $self->build_id) { - return ""; - } - - return "runcommand-log/$sha"; -} diff --git a/src/root/build.tt b/src/root/build.tt index 8a3a921f..61a846bb 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -526,14 +526,14 @@ END;
Started at [% INCLUDE renderDateTime timestamp = runcommandlog.start_time; %]
[% IF runcommandlog.end_time != undef %]
Ran for [% INCLUDE renderDuration duration = runcommandlog.end_time - runcommandlog.start_time %] - [% IF runcommandlog.log_relative_url() %] -  — Logs + [% IF runcommandlog.uuid != undef %] +  — Logs [% END %]
[% ELSE %]
Running for [% INCLUDE renderDuration duration = curTime - runcommandlog.start_time %] - [% IF runcommandlog.log_relative_url() %] -  — Logs + [% IF runcommandlog.uuid != undef %] +  — Logs [% END %]
[% END %] From 47c1f89d5a7cfa60576ee2f22c3d993763fcd8c9 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Wed, 26 Jan 2022 11:49:39 -0800 Subject: [PATCH 20/36] t/RunCommand: fixup basic.t to use uuid --- t/Hydra/Plugin/RunCommand/basic.t | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/t/Hydra/Plugin/RunCommand/basic.t b/t/Hydra/Plugin/RunCommand/basic.t index 9410d348..7148adaa 100644 --- a/t/Hydra/Plugin/RunCommand/basic.t +++ b/t/Hydra/Plugin/RunCommand/basic.t @@ -60,8 +60,7 @@ subtest "Validate a run log was created" => sub { is($runlog->exit_code, 0, "This command should have succeeded."); subtest "Validate the run log file exists" => sub { - my $filename = Hydra::Helper::Nix::constructRunCommandLogFilename(sha1_hex($runlog->command), $build->get_column('id')); - my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($filename); + my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($runlog->uuid); ok(-f $logPath, "The run log was saved to a file."); ok(-z $logPath, "The run log was empty."); }; From 38896db6b63c917289c5d31549e969f1a0e7515e Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Wed, 26 Jan 2022 11:50:16 -0800 Subject: [PATCH 21/36] t/RunCommand: init http.t Test that we can indeed visit the pages of a valid runcommand log and not of an invalid one. --- t/Hydra/Plugin/RunCommand/http.t | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 t/Hydra/Plugin/RunCommand/http.t diff --git a/t/Hydra/Plugin/RunCommand/http.t b/t/Hydra/Plugin/RunCommand/http.t new file mode 100644 index 00000000..1ee4b2dd --- /dev/null +++ b/t/Hydra/Plugin/RunCommand/http.t @@ -0,0 +1,50 @@ +use feature 'unicode_strings'; +use strict; +use warnings; +use Setup; +use Test2::V0; + +my $ctx = test_context( + hydra_config => q| + + command = cp "$HYDRA_JSON" "$HYDRA_DATA/joboutput.json" + +|); + +require Catalyst::Test; +Catalyst::Test->import('Hydra'); +use HTTP::Request::Common qw(GET); + +my $db = $ctx->db(); + +my $builds = $ctx->makeAndEvaluateJobset( + expression => "runcommand.nix", + build => 1, +); +my $build = $builds->{"metrics"}; + +ok(sendNotifications(), "Notifications execute successfully."); + +my $runlog = $build->runcommandlogs->find({}); +ok($runlog->uuid, "The log's uuid is saved."); + +my $get1 = request(GET '/build/' . $build->id . '/runcommandlog/' . $runlog->uuid); +ok($get1->is_success, "GET /build/{id}/runcommandlog/{uuid} succeeded."); + +my $get2 = request(GET '/runcommandlog/' . $runlog->uuid); +ok($get2->is_success, "GET /runcommandlog/{uuid} succeeded."); + +my $get3 = request(GET '/runcommandlog/some-invalid-or-nonexistent-uuid'); +ok(!$get3->is_success, "GET'ing invalid uuid failed."); + +my $get4a = request(GET '/build/' . $build->id . '/runcommandlog/' . $runlog->uuid . '/raw'); +is($get4a->code, 302, "GET /build/{id}/runcommandlog/{uuid}/raw is a redirect."); +my $get4b = request(GET $get4a->header('location')); +ok($get4b->is_success, "GET /build/{id}/runcommandlog/{uuid}/raw succeeded."); + +my $get5a = request(GET '/build/' . $build->id . '/runcommandlog/some-invalid-or-nonexistent-uuid/raw'); +is($get5a->code, 302, "GET /raw of invalid uuid is a redirect."); +my $get5b = request(GET $get5a->header("location")); +ok(!$get5b->is_success, "GET /raw of invalid uuid failed."); + +done_testing; From ff390e89a6817e9db6e7246f5587a69c7c565ba7 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Wed, 26 Jan 2022 11:58:02 -0800 Subject: [PATCH 22/36] Controller/Build: remove unused parameter from showLog --- src/lib/Hydra/Controller/Build.pm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index d02c66a4..b2303e08 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -126,14 +126,13 @@ sub view_nixlog : Chained('buildChain') PathPart('nixlog') { $c->stash->{step} = $step; - showLog($c, $mode, $step->busy == 0, $step->drvpath); + showLog($c, $mode, $step->drvpath); } sub view_log : Chained('buildChain') PathPart('log') { my ($self, $c, $mode) = @_; - showLog($c, $mode, $c->stash->{build}->finished, - $c->stash->{build}->drvpath); + showLog($c, $mode, $c->stash->{build}->drvpath); } @@ -146,7 +145,7 @@ sub view_runcommandlog : Chained('buildChain') PathPart('runcommandlog') { sub showLog { - my ($c, $mode, $finished, $drvPath) = @_; + my ($c, $mode, $drvPath) = @_; $mode //= "pretty"; my $log_uri = $c->uri_for($c->controller('Root')->action_for("log"), [basename($drvPath)]); From 1d0076408b5df483367e5fe80a8fbf8c45907e62 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Wed, 26 Jan 2022 12:36:25 -0800 Subject: [PATCH 23/36] Controller/Build: pass log_uri to showLog in place of drvPath This way, we can reuse the `showLog` sub for other things, such as `view_runcommandlog` (which doesn't have a drvPath attached). --- src/lib/Hydra/Controller/Build.pm | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index b2303e08..1b2be1eb 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -126,13 +126,18 @@ sub view_nixlog : Chained('buildChain') PathPart('nixlog') { $c->stash->{step} = $step; - showLog($c, $mode, $step->drvpath); + my $drvPath = $step->drvpath; + my $log_uri = $c->uri_for($c->controller('Root')->action_for("log"), [basename($drvPath)]); + showLog($c, $mode, $log_uri); } sub view_log : Chained('buildChain') PathPart('log') { my ($self, $c, $mode) = @_; - showLog($c, $mode, $c->stash->{build}->drvpath); + + my $drvPath = $c->stash->{build}->drvpath; + my $log_uri = $c->uri_for($c->controller('Root')->action_for("log"), [basename($drvPath)]); + showLog($c, $mode, $log_uri); } @@ -145,11 +150,9 @@ sub view_runcommandlog : Chained('buildChain') PathPart('runcommandlog') { sub showLog { - my ($c, $mode, $drvPath) = @_; + my ($c, $mode, $log_uri) = @_; $mode //= "pretty"; - my $log_uri = $c->uri_for($c->controller('Root')->action_for("log"), [basename($drvPath)]); - if ($mode eq "pretty") { $c->stash->{log_uri} = $log_uri; $c->stash->{template} = 'log.tt'; From 3594ba942a3a9fcb4741dfd414e61dd0c807ae2a Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Wed, 26 Jan 2022 12:38:06 -0800 Subject: [PATCH 24/36] Controller/Build: use showLog in view_runcommandlog This also adds the `runcommandlog` object to the stash so that we can access its uuid as well as command run in order to display more useful and specific information on the webpage. --- src/lib/Hydra/Controller/Build.pm | 6 ++++-- src/root/runcommand-log.tt | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/lib/Hydra/Controller/Build.pm b/src/lib/Hydra/Controller/Build.pm index 1b2be1eb..af648109 100644 --- a/src/lib/Hydra/Controller/Build.pm +++ b/src/lib/Hydra/Controller/Build.pm @@ -142,10 +142,12 @@ sub view_log : Chained('buildChain') PathPart('log') { sub view_runcommandlog : Chained('buildChain') PathPart('runcommandlog') { - my ($self, $c, $uuid) = @_; + my ($self, $c, $uuid, $mode) = @_; - $c->stash->{log_uri} = $c->uri_for($c->controller('Root')->action_for("runcommandlog"), $uuid); + my $log_uri = $c->uri_for($c->controller('Root')->action_for("runcommandlog"), $uuid); + showLog($c, $mode, $log_uri); $c->stash->{template} = 'runcommand-log.tt'; + $c->stash->{runcommandlog} = $c->stash->{build}->runcommandlogs->find({ uuid => $uuid }); } diff --git a/src/root/runcommand-log.tt b/src/root/runcommand-log.tt index d1d4cd75..7d346823 100644 --- a/src/root/runcommand-log.tt +++ b/src/root/runcommand-log.tt @@ -11,10 +11,10 @@ [% ELSE %] is [% END %] - the output of a RunCommand execution of derivation [% IF step; step.drvpath; ELSE; build.drvpath; END %]. + the output of a RunCommand execution of the command [% runcommandlog.command %] + on Build [% build.id %]. [% IF tail %] - The full log is also available. + The full log is also available. [% END %]

From 71bbb042dbc4e4b55f1de613aa842d3b753452a6 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Thu, 27 Jan 2022 09:14:01 -0800 Subject: [PATCH 25/36] build.tt: link to the pretty, raw, and tail versions of the log Also split it out to a new div -- there are now 3 lines per RunCommandLog -- the first saying when it started, the second saying how long it ran for (or has been running), and the third with the buttons for the pretty, raw, and tail versions of the log. --- src/root/build.tt | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index 61a846bb..66587eca 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -524,19 +524,29 @@ END;
[% IF runcommandlog.start_time != undef %]
Started at [% INCLUDE renderDateTime timestamp = runcommandlog.start_time; %]
- [% IF runcommandlog.end_time != undef %] -
Ran for [% INCLUDE renderDuration duration = runcommandlog.end_time - runcommandlog.start_time %] - [% IF runcommandlog.uuid != undef %] -  — Logs - [% END %] -
- [% ELSE %] -
Running for [% INCLUDE renderDuration duration = curTime - runcommandlog.start_time %] - [% IF runcommandlog.uuid != undef %] -  — Logs - [% END %] -
- [% END %] +
+ [% IF runcommandlog.end_time != undef %] + Ran for [% INCLUDE renderDuration duration = runcommandlog.end_time - runcommandlog.start_time %] + [% IF runcommandlog.uuid != undef %] + [% runLog = c.uri_for('/build', build.id, 'runcommandlog', runcommandlog.uuid) %] +
+ pretty + raw + tail +
+ [% END %] + [% ELSE %] + Running for [% INCLUDE renderDuration duration = curTime - runcommandlog.start_time %] + [% IF runcommandlog.uuid != undef %] + [% runLog = c.uri_for('/build', build.id, 'runcommandlog', runcommandlog.uuid) %] +
+ pretty + raw + tail +
+ [% END %] + [% END %] +
[% ELSE %]
Pending
[% END %] From 61914d56c64761beb770eb25d3e3d3f1db07f138 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 28 Jan 2022 09:39:51 -0800 Subject: [PATCH 26/36] runcommand-log.tt: escape the command --- src/root/runcommand-log.tt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/root/runcommand-log.tt b/src/root/runcommand-log.tt index 7d346823..b937a373 100644 --- a/src/root/runcommand-log.tt +++ b/src/root/runcommand-log.tt @@ -11,7 +11,7 @@ [% ELSE %] is [% END %] - the output of a RunCommand execution of the command [% runcommandlog.command %] + the output of a RunCommand execution of the command [% HTML.escape(runcommandlog.command) %] on Build [% build.id %]. [% IF tail %] The full log is also available. From 8eab7b8543ecf623bd10b288ed01a3b20b12109e Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 28 Jan 2022 10:02:31 -0800 Subject: [PATCH 27/36] Helper/Nix: constructRunCommandLogPath: take RunCommandLog as input This way we ensure that it actually exists in the database, rather than blindly trusting user-generated input. --- src/lib/Hydra/Controller/Root.pm | 3 ++- src/lib/Hydra/Helper/Nix.pm | 7 ++++--- src/lib/Hydra/Plugin/RunCommand.pm | 2 +- t/Hydra/Plugin/RunCommand/basic.t | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 2683d6c9..3ce3609b 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -537,7 +537,8 @@ sub runcommandlog :Local :Args(1) { die if defined $tail && $tail !~ /^[0-9]+$/; - my $logFile = constructRunCommandLogPath($uuid); + my $runlog = $c->model('DB')->resultset('RunCommandLogs')->find({ uuid => $uuid }); + my $logFile = constructRunCommandLogPath($runlog); if (-f $logFile) { serveLogFile($c, $logFile, $tail); return; diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index d2c4908c..6aa754f7 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -591,12 +591,13 @@ sub isLocalStore { sub constructRunCommandLogPath { - my ($filename) = @_; + my ($runlog) = @_; + my $uuid = $runlog->uuid; my $hydra_path = Hydra::Model::DB::getHydraPath; - my $bucket = substr($filename, 0, 2); + my $bucket = substr($uuid, 0, 2); - return "$hydra_path/runcommand-logs/$bucket/$filename"; + return "$hydra_path/runcommand-logs/$bucket/$uuid"; } 1; diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 9c13e4b7..75e6c2c9 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -163,7 +163,7 @@ sub buildFinished { $runlog->started(); - my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($runlog->uuid); + my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($runlog); my $dir = dirname($logPath); my $oldUmask = umask(); diff --git a/t/Hydra/Plugin/RunCommand/basic.t b/t/Hydra/Plugin/RunCommand/basic.t index 7148adaa..6e31aed3 100644 --- a/t/Hydra/Plugin/RunCommand/basic.t +++ b/t/Hydra/Plugin/RunCommand/basic.t @@ -60,7 +60,7 @@ subtest "Validate a run log was created" => sub { is($runlog->exit_code, 0, "This command should have succeeded."); subtest "Validate the run log file exists" => sub { - my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($runlog->uuid); + my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($runlog); ok(-f $logPath, "The run log was saved to a file."); ok(-z $logPath, "The run log was empty."); }; From 8bf3cdbc6745eff1b78ed0516f7fd6bb9dcc3a13 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 28 Jan 2022 10:25:59 -0800 Subject: [PATCH 28/36] t/Helper: switch to using test_context() --- t/Hydra/Helper/Nix.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/Hydra/Helper/Nix.t b/t/Hydra/Helper/Nix.t index 788ae685..eec77551 100644 --- a/t/Hydra/Helper/Nix.t +++ b/t/Hydra/Helper/Nix.t @@ -3,7 +3,7 @@ use warnings; use Setup; use File::Temp; -my %ctx = test_init(); +my $ctx = test_context(); require Hydra::Helper::Nix; From 2c6487b8d7fc5355a03066d3d669cdc7a0b249f3 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 28 Jan 2022 10:27:34 -0800 Subject: [PATCH 29/36] t/Helper: test constructRunCommandLogPath --- t/Hydra/Helper/Nix.t | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/t/Hydra/Helper/Nix.t b/t/Hydra/Helper/Nix.t index eec77551..8abb60ea 100644 --- a/t/Hydra/Helper/Nix.t +++ b/t/Hydra/Helper/Nix.t @@ -4,6 +4,7 @@ use Setup; use File::Temp; my $ctx = test_context(); +my $db = $ctx->db(); require Hydra::Helper::Nix; @@ -65,4 +66,22 @@ is(Hydra::Helper::Nix::getMachines(), { }, ":)"); +subtest "constructRunCommandLogPath" => sub { + my $builds = $ctx->makeAndEvaluateJobset( + expression => "basic.nix", + ); + my $build = $builds->{"empty_dir"}; + my $runlog = $db->resultset('RunCommandLogs')->create({ + job_matcher => "*:*:*", + build_id => $build->get_column('id'), + command => "bogus", + }); + + like( + Hydra::Helper::Nix::constructRunCommandLogPath($runlog), + qr@/runcommand-logs/[0-9a-f]{2}/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}@, + "The constructed RunCommandLog path is sufficiently bucketed and UUID-like." + ); +}; + done_testing; From e38175156483dcb8815a0ba40d940c6edcbe262c Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 28 Jan 2022 11:33:29 -0800 Subject: [PATCH 30/36] Helper/Nix: constructRunCommandLogPath: return undef in case of an error This allows us to give a web request to an invalid UUID a 404. --- src/lib/Hydra/Controller/Root.pm | 2 +- src/lib/Hydra/Helper/Nix.pm | 13 +++++++++---- src/lib/Hydra/Plugin/RunCommand.pm | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 3ce3609b..247a6caf 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -538,7 +538,7 @@ sub runcommandlog :Local :Args(1) { die if defined $tail && $tail !~ /^[0-9]+$/; my $runlog = $c->model('DB')->resultset('RunCommandLogs')->find({ uuid => $uuid }); - my $logFile = constructRunCommandLogPath($runlog); + my $logFile = constructRunCommandLogPath($runlog) or notFound($c, "RunCommandLog not found."); if (-f $logFile) { serveLogFile($c, $logFile, $tail); return; diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index 6aa754f7..9c804011 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -592,12 +592,17 @@ sub isLocalStore { sub constructRunCommandLogPath { my ($runlog) = @_; + my $path = undef; - my $uuid = $runlog->uuid; - my $hydra_path = Hydra::Model::DB::getHydraPath; - my $bucket = substr($uuid, 0, 2); + eval { + my $uuid = $runlog->uuid; + my $hydra_path = Hydra::Model::DB::getHydraPath; + my $bucket = substr($uuid, 0, 2); - return "$hydra_path/runcommand-logs/$bucket/$uuid"; + $path = "$hydra_path/runcommand-logs/$bucket/$uuid"; + }; + + return $path; } 1; diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 75e6c2c9..3d35e5a8 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -163,7 +163,7 @@ sub buildFinished { $runlog->started(); - my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($runlog); + my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($runlog) or die "RunCommandLog not found."; my $dir = dirname($logPath); my $oldUmask = umask(); From 61189ecca9947870c1d6e7186197b3776170224f Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Fri, 28 Jan 2022 12:45:12 -0800 Subject: [PATCH 31/36] Helper/Nix: constructRunCommandLogPath: verify uuid is valid This shouldn't be possible normally, but it is possible to: $db->resultset('RunCommandLogs')->new({ uuid => "../etc/passwd" }); if you have access to the `$db`. --- src/lib/Hydra/Controller/Root.pm | 6 ++++-- src/lib/Hydra/Helper/Nix.pm | 16 ++++++++-------- t/Hydra/Helper/Nix.t | 6 ++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 247a6caf..c6843d29 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -537,8 +537,10 @@ sub runcommandlog :Local :Args(1) { die if defined $tail && $tail !~ /^[0-9]+$/; - my $runlog = $c->model('DB')->resultset('RunCommandLogs')->find({ uuid => $uuid }); - my $logFile = constructRunCommandLogPath($runlog) or notFound($c, "RunCommandLog not found."); + my $runlog = $c->model('DB')->resultset('RunCommandLogs')->find({ uuid => $uuid }) + or notFound($c, "The RunCommand log is not available."); + + my $logFile = constructRunCommandLogPath($runlog); if (-f $logFile) { serveLogFile($c, $logFile, $tail); return; diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index 9c804011..6f7852af 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -13,6 +13,7 @@ use Nix::Store; use Encode; use Sys::Hostname::Long; use IPC::Run; +use UUID4::Tiny qw(is_uuid4_string); our @ISA = qw(Exporter); our @EXPORT = qw( @@ -592,17 +593,16 @@ sub isLocalStore { sub constructRunCommandLogPath { my ($runlog) = @_; - my $path = undef; + my $uuid = $runlog->uuid; - eval { - my $uuid = $runlog->uuid; - my $hydra_path = Hydra::Model::DB::getHydraPath; - my $bucket = substr($uuid, 0, 2); + if (!is_uuid4_string($uuid)) { + die "UUID was invalid." + } - $path = "$hydra_path/runcommand-logs/$bucket/$uuid"; - }; + my $hydra_path = Hydra::Model::DB::getHydraPath; + my $bucket = substr($uuid, 0, 2); - return $path; + return "$hydra_path/runcommand-logs/$bucket/$uuid"; } 1; diff --git a/t/Hydra/Helper/Nix.t b/t/Hydra/Helper/Nix.t index 8abb60ea..a34cfe02 100644 --- a/t/Hydra/Helper/Nix.t +++ b/t/Hydra/Helper/Nix.t @@ -82,6 +82,12 @@ subtest "constructRunCommandLogPath" => sub { qr@/runcommand-logs/[0-9a-f]{2}/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}@, "The constructed RunCommandLog path is sufficiently bucketed and UUID-like." ); + + my $badlog = $db->resultset('RunCommandLogs')->new({ uuid => "../../../etc/passwd" }); + ok( + dies { Hydra::Helper::Nix::constructRunCommandLogPath($badlog) }, + "Expected invalid UUID to be rejected and not have a path constructed for it.", + ); }; done_testing; From 34e4c119f49bcafaa69d13e2127f6c756b20ecb8 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 31 Jan 2022 11:35:33 -0800 Subject: [PATCH 32/36] build.tt: don't duplicate RunCommandLog buttons --- src/root/build.tt | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index 66587eca..0848da4a 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -527,24 +527,16 @@ END;
[% IF runcommandlog.end_time != undef %] Ran for [% INCLUDE renderDuration duration = runcommandlog.end_time - runcommandlog.start_time %] - [% IF runcommandlog.uuid != undef %] - [% runLog = c.uri_for('/build', build.id, 'runcommandlog', runcommandlog.uuid) %] -
- pretty - raw - tail -
- [% END %] [% ELSE %] Running for [% INCLUDE renderDuration duration = curTime - runcommandlog.start_time %] - [% IF runcommandlog.uuid != undef %] - [% runLog = c.uri_for('/build', build.id, 'runcommandlog', runcommandlog.uuid) %] -
- pretty - raw - tail -
- [% END %] + [% END %] + [% IF runcommandlog.uuid != undef %] + [% runLog = c.uri_for('/build', build.id, 'runcommandlog', runcommandlog.uuid) %] +
+ pretty + raw + tail +
[% END %]
[% ELSE %] From 8c67e32480f25f052fc8947715acc34ec39a9040 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 31 Jan 2022 11:39:16 -0800 Subject: [PATCH 33/36] RunCommand: ensure we reset the umask --- src/lib/Hydra/Plugin/RunCommand.pm | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 3d35e5a8..401942c7 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -8,6 +8,7 @@ use JSON::MaybeXS; use File::Basename qw(dirname); use File::Path qw(make_path); use IPC::Run3; +use Try::Tiny; sub isEnabled { my ($self) = @_; @@ -166,20 +167,28 @@ sub buildFinished { my $logPath = Hydra::Helper::Nix::constructRunCommandLogPath($runlog) or die "RunCommandLog not found."; my $dir = dirname($logPath); my $oldUmask = umask(); + my $f; - # file: 640, dir: 750 - umask(0027); - make_path($dir); + try { + # file: 640, dir: 750 + umask(0027); + make_path($dir); - open(my $f, '>', $logPath); - umask($oldUmask); + open($f, '>', $logPath); + umask($oldUmask); - run3($command, \undef, $f, $f, { return_if_system_error => 1 }) == 1 - or warn "notification command '$command' failed with exit status $? ($!)\n"; + run3($command, \undef, $f, $f, { return_if_system_error => 1 }) == 1 + or warn "notification command '$command' failed with exit status $? ($!)\n"; - close($f); + close($f); - $runlog->completed_with_child_error($?, $!); + $runlog->completed_with_child_error($?, $!); + 1; + } catch { + die "Died while trying to process RunCommand (${\$runlog->uuid}): $_"; + } finally { + umask($oldUmask); + }; } } From 9c4e6f78e744e9330a619116f42033f720e866bc Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 31 Jan 2022 11:43:25 -0800 Subject: [PATCH 34/36] hydra-module: don't bzip2 runcommand-logs --- hydra-module.nix | 1 - 1 file changed, 1 deletion(-) diff --git a/hydra-module.nix b/hydra-module.nix index 617b0752..6cfa6aa3 100644 --- a/hydra-module.nix +++ b/hydra-module.nix @@ -461,7 +461,6 @@ in script = '' find ${baseDir}/build-logs -type f -name "*.drv" -mtime +3 -size +0c | xargs -r bzip2 -v -f - find ${baseDir}/runcommand-logs -type f -mtime +3 -size +0c | xargs -r bzip2 -v -f ''; startAt = "Sun 01:45"; }; From d0b6329aa88c10434fbf39bebf747c2e25125906 Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 31 Jan 2022 11:44:00 -0800 Subject: [PATCH 35/36] sql/upgrade-81: remove unnecessary comment --- src/sql/upgrade-81.sql | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sql/upgrade-81.sql b/src/sql/upgrade-81.sql index cfbb2239..8e6adc43 100644 --- a/src/sql/upgrade-81.sql +++ b/src/sql/upgrade-81.sql @@ -2,5 +2,3 @@ alter table runcommandlogs add column uuid uuid; update runcommandlogs set uuid = gen_random_uuid() where uuid is null; alter table runcommandlogs alter column uuid set not null; alter table runcommandlogs add constraint RunCommandLogs_uuid_unique unique(uuid); -# the unique uuid constraint adds a unique, btree index, so we don't need to create out own: -# "runcommandlogs_uuid_unique" UNIQUE CONSTRAINT, btree (uuid) From b57345ba1f74f66b2dc46a8251ea442377b3a03a Mon Sep 17 00:00:00 2001 From: Cole Helbling Date: Mon, 31 Jan 2022 12:56:34 -0800 Subject: [PATCH 36/36] hydra.sql: add IndexRunCommandLogsOnBuildID index --- src/sql/hydra.sql | 1 + src/sql/upgrade-81.sql | 1 + 2 files changed, 2 insertions(+) diff --git a/src/sql/hydra.sql b/src/sql/hydra.sql index 459305df..26617789 100644 --- a/src/sql/hydra.sql +++ b/src/sql/hydra.sql @@ -609,6 +609,7 @@ create table RunCommandLogs ( -- core_dumped must not be null. However, these semantics are tricky -- to encode as constraints and probably provide limited actual value. ); +create index IndexRunCommandLogsOnBuildID on RunCommandLogs(build_id); -- The output paths that have permanently failed. create table FailedPaths ( diff --git a/src/sql/upgrade-81.sql b/src/sql/upgrade-81.sql index 8e6adc43..d7dcb519 100644 --- a/src/sql/upgrade-81.sql +++ b/src/sql/upgrade-81.sql @@ -2,3 +2,4 @@ alter table runcommandlogs add column uuid uuid; update runcommandlogs set uuid = gen_random_uuid() where uuid is null; alter table runcommandlogs alter column uuid set not null; alter table runcommandlogs add constraint RunCommandLogs_uuid_unique unique(uuid); +create index IndexRunCommandLogsOnBuildID on RunCommandLogs(build_id);