From 1a30a0c2f13ad9cc7a52480f918111f6278503cc Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Tue, 14 Dec 2021 22:07:15 -0500 Subject: [PATCH] Dynamic RunCommand: validate that the job's out exists, is a file (or points to a file) which is executable. --- src/lib/Hydra/Plugin/RunCommand.pm | 30 +++++- t/Hydra/Plugin/RunCommand/fanout.t | 88 +++++++++++------ t/jobs/runcommand-dynamic.nix | 145 ++++++++++++++++++++++++----- 3 files changed, 213 insertions(+), 50 deletions(-) diff --git a/src/lib/Hydra/Plugin/RunCommand.pm b/src/lib/Hydra/Plugin/RunCommand.pm index 8d3cf35a..8998fc39 100644 --- a/src/lib/Hydra/Plugin/RunCommand.pm +++ b/src/lib/Hydra/Plugin/RunCommand.pm @@ -41,6 +41,32 @@ sub isBuildEligibleForDynamicRunCommand { my ($build) = @_; if ($build->get_column("job") =~ "^runCommandHook\..+") { + my $out = $build->buildoutputs->find({name => "out"}); + if (!defined $out) { + warn "DynamicRunCommand hook on " . $build->job . " (" . $build->id . ") rejected: no output named 'out'."; + return 0; + } + + my $path = $out->path; + if (-l $path) { + $path = readlink($path); + } + + if (! -e $path) { + warn "DynamicRunCommand hook on " . $build->job . " (" . $build->id . ") rejected: The 'out' output doesn't exist locally. This is a bug."; + return 0; + } + + if (! -x $path) { + warn "DynamicRunCommand hook on " . $build->job . " (" . $build->id . ") rejected: The 'out' output is not executable."; + return 0; + } + + if (! -f $path) { + warn "DynamicRunCommand hook on " . $build->job . " (" . $build->id . ") rejected: The 'out' output is not a regular file or symlink."; + return 0; + } + return 1; } @@ -109,9 +135,7 @@ sub fanoutToCommands { # missing test cases: # # 1. is it enabled on the jobset? - # 2. what if the result is a directory? - # 3. what if the job doens't have an out? - # 4. what if the build failed? + # 2. what if the build failed? if (isBuildEligibleForDynamicRunCommand($build)) { my $job = $build->get_column('job'); my $out = $build->buildoutputs->find({name => "out"}); diff --git a/t/Hydra/Plugin/RunCommand/fanout.t b/t/Hydra/Plugin/RunCommand/fanout.t index d9f67f14..41236456 100644 --- a/t/Hydra/Plugin/RunCommand/fanout.t +++ b/t/Hydra/Plugin/RunCommand/fanout.t @@ -93,36 +93,72 @@ subtest "fanoutToCommandsWithDynamicRunCommandSupport" => sub { }; subtest "isBuildEligibleForDynamicRunCommand" => sub { - my $build = Hydra::Schema::Result::Builds->new({ - "job" => "foo bar baz" - }); + subtest "Non-matches based on name alone ..." => sub { + my $build = $builds->{"foo-bar-baz"}; + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build), + 0, + "The job name does not match" + ); - is( - Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build), - 0, - "The job name does not match" - ); + $build->set_column("job", "runCommandHook"); + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build), + 0, + "The job name does not match" + ); - $build->set_column("job", "runCommandHook"); - is( - Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build), - 0, - "The job name does not match" - ); + $build->set_column("job", "runCommandHook."); + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build), + 0, + "The job name does not match" + ); + }; - $build->set_column("job", "runCommandHook."); - is( - Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build), - 0, - "The job name does not match" - ); + subtest "On outputs ..." => sub { + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.example"}), + 1, + "out is an executable file" + ); - $build->set_column("job", "runCommandHook.a"); - is( - Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($build), - 1, - "The job name does match" - ); + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.symlink"}), + 1, + "out is a symlink to an executable file" + ); + + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.no-out"}), + 0, + "No output named out" + ); + + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.out-is-directory"}), + 0, + "out is a directory" + ); + + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.out-is-not-executable-file"}), + 0, + "out is a file which is not not executable" + ); + + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.symlink-non-executable"}), + 0, + "out is a symlink to a non-executable file" + ); + + is( + Hydra::Plugin::RunCommand::isBuildEligibleForDynamicRunCommand($builds->{"runCommandHook.symlink-directory"}), + 0, + "out is a symlink to a directory" + ); + }; }; diff --git a/t/jobs/runcommand-dynamic.nix b/t/jobs/runcommand-dynamic.nix index cf231f8f..c0b005b7 100644 --- a/t/jobs/runcommand-dynamic.nix +++ b/t/jobs/runcommand-dynamic.nix @@ -1,27 +1,130 @@ with import ./config.nix; -{ - runCommandHook.example = mkDerivation - { - name = "my-build-product"; - builder = "/bin/sh"; - outputs = [ "out" "bin" ]; - args = [ - ( - builtins.toFile "builder.sh" '' - #! /bin/sh +rec { + foo-bar-baz = mkDerivation { + name = "foo-bar-baz"; + builder = "/bin/sh"; + outputs = [ "out" ]; + args = [ + ( + builtins.toFile "builder.sh" '' + #! /bin/sh - echo "$PATH" + touch $out + '' + ) + ]; + }; - mkdir $bin - echo "foo" > $bin/bar + runCommandHook.example = mkDerivation { + name = "my-build-product"; + builder = "/bin/sh"; + outputs = [ "out" ]; + args = [ + ( + builtins.toFile "builder.sh" '' + #! /bin/sh - metrics=$out/nix-support/hydra-metrics - mkdir -p "$(dirname "$metrics")" - echo "lineCoverage 18 %" >> "$metrics" - echo "maxResident 27 KiB" >> "$metrics" - '' - ) - ]; - }; + touch $out + chmod +x $out + # ... dunno ... + '' + ) + ]; + }; + + runCommandHook.symlink = mkDerivation { + name = "symlink-out"; + builder = "/bin/sh"; + outputs = [ "out" ]; + args = [ + ( + builtins.toFile "builder.sh" '' + #! /bin/sh + + ln -s $1 $out + '' + ) + + runCommandHook.example + ]; + }; + + runCommandHook.no-out = mkDerivation { + name = "no-out"; + builder = "/bin/sh"; + outputs = [ "bin" ]; + args = [ + ( + builtins.toFile "builder.sh" '' + #! /bin/sh + mkdir $bin + '' + ) + ]; + }; + + runCommandHook.out-is-directory = mkDerivation { + name = "out-is-directory"; + builder = "/bin/sh"; + outputs = [ "out" ]; + args = [ + ( + builtins.toFile "builder.sh" '' + #! /bin/sh + + mkdir $out + '' + ) + ]; + }; + + runCommandHook.out-is-not-executable-file = mkDerivation { + name = "out-is-directory"; + builder = "/bin/sh"; + outputs = [ "out" ]; + args = [ + ( + builtins.toFile "builder.sh" '' + #! /bin/sh + + touch $out + '' + ) + ]; + }; + + runCommandHook.symlink-non-executable = mkDerivation { + name = "symlink-out"; + builder = "/bin/sh"; + outputs = [ "out" ]; + args = [ + ( + builtins.toFile "builder.sh" '' + #! /bin/sh + + ln -s $1 $out + '' + ) + + runCommandHook.out-is-not-executable-file + ]; + }; + + runCommandHook.symlink-directory = mkDerivation { + name = "symlink-directory"; + builder = "/bin/sh"; + outputs = [ "out" ]; + args = [ + ( + builtins.toFile "builder.sh" '' + #! /bin/sh + + ln -s $1 $out + '' + ) + + runCommandHook.out-is-directory + ]; + }; }