From c15b1601c65ef3185fb45b3eb26166c525b82867 Mon Sep 17 00:00:00 2001 From: Julien Marquet Date: Sat, 23 Apr 2022 23:17:49 +0200 Subject: [PATCH 01/57] docs: refine instructions for proxy setting --- doc/manual/src/configuration.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/manual/src/configuration.md b/doc/manual/src/configuration.md index 856d314c..bd8141a3 100644 --- a/doc/manual/src/configuration.md +++ b/doc/manual/src/configuration.md @@ -51,10 +51,12 @@ base_uri example.com `base_uri` should be your hydra servers proxied URL. If you are using Hydra nixos module then setting `hydraURL` option should be enough. -If you want to serve Hydra with a prefix path, for example -[http://example.com/hydra]() then you need to configure your reverse -proxy to pass `X-Request-Base` to hydra, with prefix path as value. For -example if you are using nginx, then use configuration similar to +You also need to configure your reverse proxy to pass `X-Request-Base` +to hydra, with the same value as `base_uri`. +This also covers the case of serving Hydra with a prefix path, +as in [http://example.com/hydra](). + +For example if you are using nginx, then use configuration similar to following: server { -- 2.50.1 From 154886d1344ac7012af97283393d50a67a34ca0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 24 Jun 2025 18:45:14 +0200 Subject: [PATCH 02/57] test: bump used nix version --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9c05d752..613e3ef9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,6 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: cachix/install-nix-action@v17 + - uses: cachix/install-nix-action@v31 #- run: nix flake check - run: nix-build -A checks.x86_64-linux.build -A checks.x86_64-linux.validate-openapi -- 2.50.1 From 9dcb046bf5ab7feba0a33a76d97f8805eeee5cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 24 Jun 2025 18:45:22 +0200 Subject: [PATCH 03/57] add update-flakes action --- .github/workflows/update-flakes.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/update-flakes.yml diff --git a/.github/workflows/update-flakes.yml b/.github/workflows/update-flakes.yml new file mode 100644 index 00000000..b5c0c2dd --- /dev/null +++ b/.github/workflows/update-flakes.yml @@ -0,0 +1,28 @@ +name: "Update Flakes" +on: + schedule: + # Run weekly on Monday at 00:00 UTC + - cron: '0 0 * * 1' + workflow_dispatch: +jobs: + update-flakes: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v3 + - uses: cachix/install-nix-action@v31 + - name: Update flake inputs + run: nix flake update + - name: Create Pull Request + uses: peter-evans/create-pull-request@v5 + with: + commit-message: "flake.lock: Update" + title: "Update flake inputs" + body: | + Automated flake input updates. + + This PR was automatically created by the update-flakes workflow. + branch: update-flakes + delete-branch: true \ No newline at end of file -- 2.50.1 From 794150319c56b1bab1a41057cffb4fd1e14ffaa1 Mon Sep 17 00:00:00 2001 From: Mic92 Date: Tue, 24 Jun 2025 17:03:10 +0000 Subject: [PATCH 04/57] flake.lock: Update --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index 2679eecb..0ca074f3 100644 --- a/flake.lock +++ b/flake.lock @@ -3,11 +3,11 @@ "nix": { "flake": false, "locked": { - "lastModified": 1748154947, - "narHash": "sha256-rCpANMHFIlafta6J/G0ILRd+WNSnzv/lzi40Y8f1AR8=", + "lastModified": 1750777360, + "narHash": "sha256-nDWFxwhT+fQNgi4rrr55EKjpxDyVKSl1KaNmSXtYj40=", "owner": "NixOS", "repo": "nix", - "rev": "d761dad79c79af17aa476a29749bd9d69747548f", + "rev": "7bb200199705eddd53cb34660a76567c6f1295d9", "type": "github" }, "original": { @@ -20,11 +20,11 @@ "nix-eval-jobs": { "flake": false, "locked": { - "lastModified": 1748211873, - "narHash": "sha256-AJ22q6yWc1hPkqssXMxQqD6QUeJ6hbx52xWHhKsmuP0=", + "lastModified": 1748680938, + "narHash": "sha256-TQk6pEMD0mFw7jZXpg7+2qNKGbAluMQgc55OMgEO8bM=", "owner": "nix-community", "repo": "nix-eval-jobs", - "rev": "d9262e535e35454daebcebd434bdb9c1486bb998", + "rev": "974a4af3d4a8fd242d8d0e2608da4be87a62b83f", "type": "github" }, "original": { @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1748124805, - "narHash": "sha256-8A7HjmnvCpDjmETrZY1QwzKunR63LiP7lHu1eA5q6JI=", + "lastModified": 1750736827, + "narHash": "sha256-UcNP7BR41xMTe0sfHBH8R79+HdCw0OwkC/ZKrQEuMeo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "db1aed32009f408e4048c1dd0beaf714dd34ed93", + "rev": "b4a30b08433ad7b6e1dfba0833fb0fe69d43dfec", "type": "github" }, "original": { -- 2.50.1 From c8549d138d094a7c1610f8a8c92e1f8f382388cc Mon Sep 17 00:00:00 2001 From: Dionysis Grigoropoulos Date: Tue, 15 Jul 2025 19:45:13 +0300 Subject: [PATCH 05/57] fix: Update Nix download url --- doc/manual/src/installation.md | 2 +- src/root/build.tt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/src/installation.md b/doc/manual/src/installation.md index cbf3f907..39a86885 100644 --- a/doc/manual/src/installation.md +++ b/doc/manual/src/installation.md @@ -48,7 +48,7 @@ Getting Nix If your server runs NixOS you are all set to continue with installation of Hydra. Otherwise you first need to install Nix. The latest stable version can be found one [the Nix web -site](http://nixos.org/nix/download.html), along with a manual, which +site](https://nixos.org/download/), along with a manual, which includes installation instructions. Installation diff --git a/src/root/build.tt b/src/root/build.tt index 18ff6f01..93629427 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -563,7 +563,7 @@ END; [% IF eval.flake %] -

If you have Nix +

If you have Nix installed, you can reproduce this build on your own machine by running the following command:

@@ -573,7 +573,7 @@ END; [% ELSE %] -

If you have Nix +

If you have Nix installed, you can reproduce this build on your own machine by downloading url) %]>a script that checks out all inputs of the build and then invokes Nix to -- 2.50.1 From 329816aec418783fea7528bf7e180d45218956bf Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Fri, 4 Jul 2025 06:44:41 +0200 Subject: [PATCH 06/57] Replace nettools with hostname-debian As far as I understand we include nettools for its hostname executable used by the Sys-Hostname-Long perl package. But if we just need that then the hostname-debian package provides a simpler and better maintained version. --- nixos-modules/hydra.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos-modules/hydra.nix b/nixos-modules/hydra.nix index 283a9b8d..83ffeec4 100644 --- a/nixos-modules/hydra.nix +++ b/nixos-modules/hydra.nix @@ -340,7 +340,7 @@ in requires = [ "hydra-init.service" ]; wants = [ "network-online.target" ]; after = [ "hydra-init.service" "network.target" "network-online.target" ]; - path = [ cfg.package pkgs.nettools pkgs.openssh pkgs.bzip2 config.nix.package ]; + path = [ cfg.package pkgs.hostname-debian pkgs.openssh pkgs.bzip2 config.nix.package ]; restartTriggers = [ hydraConf ]; environment = env // { PGPASSFILE = "${baseDir}/pgpass-queue-runner"; # grrr @@ -364,7 +364,7 @@ in requires = [ "hydra-init.service" ]; restartTriggers = [ hydraConf ]; after = [ "hydra-init.service" "network.target" ]; - path = with pkgs; [ nettools cfg.package jq ]; + path = with pkgs; [ hostname-debian cfg.package jq ]; environment = env // { HYDRA_DBI = "${env.HYDRA_DBI};application_name=hydra-evaluator"; }; -- 2.50.1 From fdcb6b4a1bc246d90a3da7a6afb4405fba849a5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sun, 13 Feb 2022 14:24:36 +0100 Subject: [PATCH 07/57] Fix local store detection and related issues - Add localStore into the stash because it's used in templates - Hide the Channels button for non-local stores because the link 404s anyway - Fix a style issue when having popovers in dark mode --- src/lib/Hydra/Controller/Root.pm | 1 + src/root/static/css/hydra.css | 2 +- src/root/topbar.tt | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index adb5ad44..0a5d05e5 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -57,6 +57,7 @@ sub begin :Private { $c->stash->{tracker} = defined $c->config->{tracker} ? $c->config->{tracker} : ""; $c->stash->{flashMsg} = $c->flash->{flashMsg}; $c->stash->{successMsg} = $c->flash->{successMsg}; + $c->stash->{localStore} = isLocalStore; $c->stash->{isPrivateHydra} = $c->config->{private} // "0" ne "0"; diff --git a/src/root/static/css/hydra.css b/src/root/static/css/hydra.css index ec7e4b64..b55f557a 100644 --- a/src/root/static/css/hydra.css +++ b/src/root/static/css/hydra.css @@ -186,7 +186,7 @@ a.squiggle:hover { html { background-color: #1f1f1f; } - body, div.popover { + body, div.popover, div.popover-body { background-color: #1f1f1f; color: #fafafa !important; } diff --git a/src/root/topbar.tt b/src/root/topbar.tt index 58dd94e3..808f308f 100644 --- a/src/root/topbar.tt +++ b/src/root/topbar.tt @@ -42,7 +42,7 @@

[% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('project'), [project.name]) title = "Overview" %] [% INCLUDE menuItem uri = c.uri_for(c.controller('Project').action_for('all'), [project.name]) title = "Latest builds" %] - [% INCLUDE menuItem uri = c.uri_for('/project' project.name 'channel' 'latest') title = "Channel" %] + [% IF localStore %][% INCLUDE menuItem uri = c.uri_for('/project' project.name 'channel' 'latest') title = "Channel" %][% END %] [% END %] [% END %] @@ -59,7 +59,7 @@ [% INCLUDE menuItem uri = c.uri_for(c.controller('Jobset').action_for('all'), [project.name, jobset.name]) title = "Latest builds" %] - [% INCLUDE menuItem uri = c.uri_for('/jobset' project.name jobset.name 'channel' 'latest') title = "Channel" %] + [% IF localStore %][% INCLUDE menuItem uri = c.uri_for('/jobset' project.name jobset.name 'channel' 'latest') title = "Channel" %][% END %] [% END %] [% END %] @@ -73,7 +73,7 @@ [% INCLUDE menuItem uri = c.uri_for(c.controller('Job').action_for('all'), [project.name, jobset.name, job]) title = "Latest builds" %] - [% INCLUDE menuItem uri = c.uri_for('/job' project.name jobset.name job 'channel' 'latest') title = "Channel" %] + [% IF localStore %][% INCLUDE menuItem uri = c.uri_for('/job' project.name jobset.name job 'channel' 'latest') title = "Channel" %][% END %] [% END %] [% END %] -- 2.50.1 From 710092c556384698e07ec220e9ecd7b00a0e3fb7 Mon Sep 17 00:00:00 2001 From: Sandro Date: Fri, 4 Jul 2025 12:01:42 +0200 Subject: [PATCH 08/57] module: sync with nixpkgs --- nixos-modules/hydra.nix | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nixos-modules/hydra.nix b/nixos-modules/hydra.nix index 83ffeec4..544da7eb 100644 --- a/nixos-modules/hydra.nix +++ b/nixos-modules/hydra.nix @@ -463,12 +463,12 @@ in '' set -eou pipefail compression=$(sed -nr 's/compress_build_logs_compression = ()/\1/p' ${baseDir}/hydra.conf) - if [[ $compression == "" ]]; then - compression="bzip2" + if [[ $compression == "" || $compression == bzip2 ]]; then + compressionCmd=(bzip2) elif [[ $compression == zstd ]]; then - compression="zstd --rm" + compressionCmd=(zstd --rm) fi - find ${baseDir}/build-logs -ignore_readdir_race -type f -name "*.drv" -mtime +3 -size +0c | xargs -r "$compression" --force --quiet + find ${baseDir}/build-logs -ignore_readdir_race -type f -name "*.drv" -mtime +3 -size +0c -print0 | xargs -0 -r "''${compressionCmd[@]}" --force --quiet ''; startAt = "Sun 01:45"; }; -- 2.50.1 From 16bb3aad9a4527f4387c06ba85e05552072b1483 Mon Sep 17 00:00:00 2001 From: Ivor Wanders Date: Wed, 4 May 2022 13:31:31 -0400 Subject: [PATCH 09/57] Add a link to the raw log. --- src/root/log.tt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/root/log.tt b/src/root/log.tt index 8bb4954d..1c5fd3b5 100644 --- a/src/root/log.tt +++ b/src/root/log.tt @@ -11,7 +11,8 @@ [% ELSE %] is [% END %] - the build log of derivation [% IF step; step.drvpath; ELSE; build.drvpath; END %]. + the build log (raw) of derivation [% IF step; step.drvpath; ELSE; build.drvpath; END %]. [% IF step && step.machine %] It was built on [% step.machine %]. [% END %] -- 2.50.1 From dc6fd37e02e1bf50deeb72f134d8607a6eabad3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Wed, 16 Jul 2025 17:05:45 +0200 Subject: [PATCH 10/57] Show queue runner v2 status This is guarded behind a setting and will overwrite everything that was learned from the machines file. Also drops `sshKeys` since that wasn't used anyway. --- src/lib/Hydra/Controller/Root.pm | 6 ++- src/lib/Hydra/Helper/Nix.pm | 77 +++++++++++++++++++++----------- t/Hydra/Helper/Nix.t | 4 -- 3 files changed, 55 insertions(+), 32 deletions(-) diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index 0a5d05e5..d78e1b6d 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -189,8 +189,10 @@ sub machines :Local Args(0) { my ($self, $c) = @_; my $machines = getMachines; - # Add entry for localhost. - $machines->{''} //= {}; + # Add entry for localhost. The implicit addition is not needed with queue runner v2 + if (not $c->config->{'queue_runner_endpoint'}) { + $machines->{''} //= {}; + } delete $machines->{'localhost'}; my $status = $c->model('DB::SystemStatus')->find("queue-runner"); diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index 134b8b7e..5150ec47 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -12,6 +12,8 @@ use Nix::Store; use Encode; use Sys::Hostname::Long; use IPC::Run; +use LWP::UserAgent; +use JSON::MaybeXS; use UUID4::Tiny qw(is_uuid4_string); our @ISA = qw(Exporter); @@ -340,37 +342,60 @@ sub getEvals { sub getMachines { my %machines = (); + my $config = getHydraConfig(); - my @machinesFiles = split /:/, ($ENV{"NIX_REMOTE_SYSTEMS"} || "/etc/nix/machines"); + if ($config->{'queue_runner_endpoint'}) { + my $ua = LWP::UserAgent->new(); + my $resp = $ua->get($config->{'queue_runner_endpoint'} . "/status/machines"); + if (not $resp->is_success) { + print STDERR "Unable to ask queue runner for machines\n"; + return \%machines; + } - for my $machinesFile (@machinesFiles) { - next unless -e $machinesFile; - open(my $conf, "<", $machinesFile) or die; - while (my $line = <$conf>) { - chomp($line); - $line =~ s/\#.*$//g; - next if $line =~ /^\s*$/; - my @tokens = split /\s+/, $line; + my $data = decode_json($resp->decoded_content) or return \%machines; + my $machinesData = $data->{machines}; - if (!defined($tokens[5]) || $tokens[5] eq "-") { - $tokens[5] = ""; - } - my @supportedFeatures = split(/,/, $tokens[5] || ""); - - if (!defined($tokens[6]) || $tokens[6] eq "-") { - $tokens[6] = ""; - } - my @mandatoryFeatures = split(/,/, $tokens[6] || ""); - $machines{$tokens[0]} = - { systemTypes => [ split(/,/, $tokens[1]) ] - , sshKeys => $tokens[2] - , maxJobs => int($tokens[3]) - , speedFactor => 1.0 * (defined $tokens[4] ? int($tokens[4]) : 1) - , supportedFeatures => [ @supportedFeatures, @mandatoryFeatures ] - , mandatoryFeatures => [ @mandatoryFeatures ] + foreach my $machineName (keys %$machinesData) { + my $machine = %$machinesData{$machineName}; + $machines{$machineName} = + { systemTypes => $machine->{systems} + , maxJobs => $machine->{maxJobs} + , speedFactor => $machine->{speedFactor} + , supportedFeatures => [ @{$machine->{supportedFeatures}}, @{$machine->{mandatoryFeatures}} ] + , mandatoryFeatures => [ @{$machine->{mandatoryFeatures}} ] }; } - close $conf; + } else { + my @machinesFiles = split /:/, ($ENV{"NIX_REMOTE_SYSTEMS"} || "/etc/nix/machines"); + + for my $machinesFile (@machinesFiles) { + next unless -e $machinesFile; + open(my $conf, "<", $machinesFile) or die; + while (my $line = <$conf>) { + chomp($line); + $line =~ s/\#.*$//g; + next if $line =~ /^\s*$/; + my @tokens = split /\s+/, $line; + + if (!defined($tokens[5]) || $tokens[5] eq "-") { + $tokens[5] = ""; + } + my @supportedFeatures = split(/,/, $tokens[5] || ""); + + if (!defined($tokens[6]) || $tokens[6] eq "-") { + $tokens[6] = ""; + } + my @mandatoryFeatures = split(/,/, $tokens[6] || ""); + $machines{$tokens[0]} = + { systemTypes => [ split(/,/, $tokens[1]) ] + , maxJobs => int($tokens[3]) + , speedFactor => 1.0 * (defined $tokens[4] ? int($tokens[4]) : 1) + , supportedFeatures => [ @supportedFeatures, @mandatoryFeatures ] + , mandatoryFeatures => [ @mandatoryFeatures ] + }; + } + close $conf; + } } return \%machines; diff --git a/t/Hydra/Helper/Nix.t b/t/Hydra/Helper/Nix.t index a34cfe02..98c1f8c0 100644 --- a/t/Hydra/Helper/Nix.t +++ b/t/Hydra/Helper/Nix.t @@ -33,7 +33,6 @@ close $fh; is(Hydra::Helper::Nix::getMachines(), { 'root@ip' => { 'systemTypes' => ["x86_64-darwin"], - 'sshKeys' => '/sshkey', 'maxJobs' => 15, 'speedFactor' => 15, 'supportedFeatures' => ["big-parallel", "kvm", "nixos-test" ], @@ -41,7 +40,6 @@ is(Hydra::Helper::Nix::getMachines(), { }, 'root@baz' => { 'systemTypes' => [ "aarch64-darwin" ], - 'sshKeys' => '/sshkey', 'maxJobs' => 4, 'speedFactor' => 1, 'supportedFeatures' => ["big-parallel"], @@ -49,7 +47,6 @@ is(Hydra::Helper::Nix::getMachines(), { }, 'root@bux' => { 'systemTypes' => [ "i686-linux", "x86_64-linux" ], - 'sshKeys' => '/var/sshkey', 'maxJobs' => 1, 'speedFactor' => 1, 'supportedFeatures' => [ "kvm", "nixos-test", "benchmark" ], @@ -57,7 +54,6 @@ is(Hydra::Helper::Nix::getMachines(), { }, 'root@lotsofspace' => { 'systemTypes' => [ "i686-linux", "x86_64-linux" ], - 'sshKeys' => '/var/sshkey', 'maxJobs' => 1, 'speedFactor' => 1, 'supportedFeatures' => [ "kvm", "nixos-test", "benchmark" ], -- 2.50.1 From 83db317594616808a98cd0197d2655914e599e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Wed, 16 Jul 2025 17:31:01 +0200 Subject: [PATCH 11/57] Fix PATH for the foreman scripts --- foreman/start-hydra.sh | 2 ++ foreman/start-notify.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/foreman/start-hydra.sh b/foreman/start-hydra.sh index bb8cc909..f5af310f 100755 --- a/foreman/start-hydra.sh +++ b/foreman/start-hydra.sh @@ -1,5 +1,7 @@ #!/bin/sh +export PATH=$(pwd)/src/script:$PATH + # wait for postgresql to listen while ! pg_isready -h $(pwd)/.hydra-data/postgres -p 64444; do sleep 1; done diff --git a/foreman/start-notify.sh b/foreman/start-notify.sh index 6a647e54..454f1461 100755 --- a/foreman/start-notify.sh +++ b/foreman/start-notify.sh @@ -1,5 +1,7 @@ #!/bin/sh +export PATH=$(pwd)/src/script:$PATH + # wait for hydra-server to listen while ! nc -z localhost 63333; do sleep 1; done -- 2.50.1 From 60876ef897d0e2cbafe07a9c9d0556370ddeec74 Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Tue, 9 Feb 2021 14:10:08 +0100 Subject: [PATCH 12/57] Add Queue Runner Status to the topbar I've been searching for this waaay too often in the past and I simply do not see a reason not to include it in the topbar by default. --- src/root/topbar.tt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/root/topbar.tt b/src/root/topbar.tt index 808f308f..b19ed741 100644 --- a/src/root/topbar.tt +++ b/src/root/topbar.tt @@ -34,6 +34,9 @@ [% INCLUDE menuItem uri = c.uri_for(c.controller('Root').action_for('steps')) title = "Latest steps" %] + [% INCLUDE menuItem + uri = c.uri_for(c.controller('Root').action_for('queue_runner_status')) + title = "Queue Runner Status" %] [% END %] [% IF project %] -- 2.50.1 From c396bc958f33b2c2b15d786968dce92e4e64527f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Thu, 31 Jul 2025 18:48:47 +0200 Subject: [PATCH 13/57] Document how to connect to postgres --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 54b95549..b0244f60 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Have a look at the [Procfile](./Procfile) if you want to see how the processes a conflicts with services that might be running on your host, hydra and postgress are started on custom ports: - hydra-server: 63333 with the username "alice" and the password "foobar" -- postgresql: 64444 +- postgresql: 64444, can be connected to using `psql -p 64444 -h localhost hydra` Note that this is only ever meant as an ad-hoc way of executing Hydra during development. Please make use of the NixOS module for actually running Hydra in production. -- 2.50.1 From 6eeb08fc0a87bc7d3b356a8c45b2c2523c394a41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 13:35:58 +0200 Subject: [PATCH 14/57] Add nix-direnv --- .envrc | 1 + .gitignore | 1 + README.md | 2 ++ 3 files changed, 4 insertions(+) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..3550a30f --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 12df926f..8158f64b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *~ +/.direnv/ .test_info.* /src/sql/hydra-postgresql.sql /src/sql/hydra-sqlite.sql diff --git a/README.md b/README.md index b0244f60..4cbd13cb 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,8 @@ $ mesonConfigurePhase $ ninja ``` +The development environment can also automatically be established using [nix-direnv](https://github.com/nix-community/nix-direnv). + ### Executing Hydra During Development When working on new features or bug fixes you need to be able to run Hydra from your working copy. This -- 2.50.1 From f9d7629f95c408728d218d0e09916a3163d9b922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 13:41:39 +0200 Subject: [PATCH 15/57] Fix meson and ninja commands and link bootstrap --- .gitignore | 1 + README.md | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 8158f64b..bf385fa3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *~ /.direnv/ .test_info.* +/src/root/static/bootstrap /src/sql/hydra-postgresql.sql /src/sql/hydra-sqlite.sql /src/sql/tmp.sqlite diff --git a/README.md b/README.md index 4cbd13cb..52faffbd 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,9 @@ $ nix build You can use the provided shell.nix to get a working development environment: ``` $ nix develop -$ mesonConfigurePhase -$ ninja +$ ln -svf ../../../build/src/bootstrap src/root/static/bootstrap +$ meson setup build +$ ninja -C build ``` The development environment can also automatically be established using [nix-direnv](https://github.com/nix-community/nix-direnv). -- 2.50.1 From 15e742c8c24b00f40977b2d730cd109faecabbd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 13:52:35 +0200 Subject: [PATCH 16/57] Fixup static libraries in development server --- .gitignore | 2 ++ README.md | 2 ++ src/meson.build | 16 ++++------------ 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index bf385fa3..ea9c2985 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ /.direnv/ .test_info.* /src/root/static/bootstrap +/src/root/static/fontawesome +/src/root/static/js/flot /src/sql/hydra-postgresql.sql /src/sql/hydra-sqlite.sql /src/sql/tmp.sqlite diff --git a/README.md b/README.md index 52faffbd..7c20f9bf 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ You can use the provided shell.nix to get a working development environment: ``` $ nix develop $ ln -svf ../../../build/src/bootstrap src/root/static/bootstrap +$ ln -svf ../../../build/src/fontawesome src/root/static/fontawesome +$ ln -svf ../../../../build/src/flot src/root/static/js/flot $ meson setup build $ ninja -C build ``` diff --git a/src/meson.build b/src/meson.build index 52b821bc..c2ffc075 100644 --- a/src/meson.build +++ b/src/meson.build @@ -57,20 +57,12 @@ fontawesome = custom_target( command: ['unzip', '-u', '-d', '@OUTDIR@', '@INPUT@'], ) custom_target( - 'name-fontawesome-css', + 'name-fontawesome', input: fontawesome, - output: 'css', - command: ['cp', '-r', '@INPUT@/css', '@OUTPUT@'], + output: 'fontawesome', + command: ['cp', '-r', '@INPUT@' , '@OUTPUT@'], install: true, - install_dir: hydra_libexecdir_static / 'fontawesome', -) -custom_target( - 'name-fontawesome-webfonts', - input: fontawesome, - output: 'webfonts', - command: ['cp', '-r', '@INPUT@/webfonts', '@OUTPUT@'], - install: true, - install_dir: hydra_libexecdir_static / 'fontawesome', + install_dir: hydra_libexecdir_static, ) # Scripts -- 2.50.1 From 5bb8da7280bb6e920aba367304056f4c2709b20d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 13:52:49 +0200 Subject: [PATCH 17/57] Fix the evaluator not finding hydra-eval-jobset --- foreman/start-evaluator.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/foreman/start-evaluator.sh b/foreman/start-evaluator.sh index a2858003..73e0fe42 100755 --- a/foreman/start-evaluator.sh +++ b/foreman/start-evaluator.sh @@ -1,5 +1,7 @@ #!/bin/sh +export PATH=$(pwd)/src/script:$PATH + # wait for hydra-server to listen while ! nc -z localhost 63333; do sleep 1; done -- 2.50.1 From 81d278fe5b5682ab0ae6a2c6b42711a789edd5a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 14:20:59 +0200 Subject: [PATCH 18/57] Remove useless previous eval message This message serves no purpose and looks like something went wrong. There is nothing wrong, there is just no previous evaluation. --- src/root/jobset-eval.tt | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt index 12086d85..11965ce2 100644 --- a/src/root/jobset-eval.tt +++ b/src/root/jobset-eval.tt @@ -30,8 +30,6 @@ eval.checkouttime %]s and evaluation took [% eval.evaltime %]s.

project=otherEval.jobset.project.name jobset=otherEval.jobset.name %] evaluation [% otherEval.id %].

-[% ELSE %] -
Couldn't find an evaluation to compare to.
[% END %]
-- 2.50.1 From ea2024a9bc52b42a7b42483bfa14e698bd6b5bd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Thu, 31 Jul 2025 16:26:06 +0200 Subject: [PATCH 19/57] machine-status: Render new queue runner details --- src/lib/Hydra/Helper/Nix.pm | 8 ++++++ src/root/common.tt | 8 ++++++ src/root/machine-status.tt | 47 +++++++++++++++++++++++++++++++++-- src/root/static/css/hydra.css | 8 ++++++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/lib/Hydra/Helper/Nix.pm b/src/lib/Hydra/Helper/Nix.pm index 5150ec47..8d640f93 100644 --- a/src/lib/Hydra/Helper/Nix.pm +++ b/src/lib/Hydra/Helper/Nix.pm @@ -363,6 +363,14 @@ sub getMachines { , speedFactor => $machine->{speedFactor} , supportedFeatures => [ @{$machine->{supportedFeatures}}, @{$machine->{mandatoryFeatures}} ] , mandatoryFeatures => [ @{$machine->{mandatoryFeatures}} ] + # New fields for the machine status + , primarySystemType => $machine->{systems}[0] + , hasCapacity => $machine->{hasCapacity} + , hasDynamicCapacity => $machine->{hasDynamicCapacity} + , hasStaticCapacity => $machine->{hasStaticCapacity} + , score => $machine->{score} + , stats => $machine->{stats} + , memTotal => $machine->{totalMem} }; } } else { diff --git a/src/root/common.tt b/src/root/common.tt index 86335a74..7a93cc95 100644 --- a/src/root/common.tt +++ b/src/root/common.tt @@ -685,6 +685,14 @@ BLOCK includeFlot %] [% END; +BLOCK renderYesNo %] +[% IF value %] +Yes +[% ELSE %] +No +[% END %] +[% END; + BLOCK createChart %]
diff --git a/src/root/machine-status.tt b/src/root/machine-status.tt index 725598eb..924bdb52 100644 --- a/src/root/machine-status.tt +++ b/src/root/machine-status.tt @@ -19,10 +19,53 @@ [% INCLUDE renderMachineName machine=m.key %] - [% IF m.value.systemTypes %] + [% IF m.value.primarySystemType %] - ([% comma=0; FOREACH system IN m.value.systemTypes %][% IF comma; %], [% ELSE; comma = 1; END %][% system %][% END %]) + ([% m.value.primarySystemType %]) +   + [% WRAPPER makePopover title="Details" classes="btn-secondary btn-sm" %] +
    +
  • System types: [% comma=0; FOREACH system IN m.value.systemTypes %][% IF comma; %], [% ELSE; comma = 1; END %][% system %][% END %]
  • +
  • Supported Features: [% comma=0; FOREACH feat IN m.value.supportedFeatures %][% IF comma; %], [% ELSE; comma = 1; END %][% feat %][% END %]
  • +
  • Mandatory Features: [% comma=0; FOREACH feat IN m.value.mandatoryFeatures %][% IF comma; %], [% ELSE; comma = 1; END %][% feat %][% END %]
  • +
  • Capacity: [% INCLUDE renderYesNo value=m.value.hasCapacity %] Static: [% INCLUDE renderYesNo value=m.value.hasStaticCapacity %] Dynamic: [% INCLUDE renderYesNo value=m.value.hasDynamicCapacity %]
  • +
  • Score: [% m.value.score %]
  • +
  • Load: [% m.value.stats.load1 %]  [% m.value.stats.load5 %]  [% m.value.stats.load15 %]
  • +
  • Memory: [% mibs(m.value.stats.memUsage / (1024 * 1024)) %] MiB bytes of [% mibs(m.value.memTotal / (1024 * 1024)) %] MiB bytes used
  • + [% pressure = m.value.stats.pressure %] + [% IF pressure %] +
  • Pressure:  + + [% IF pressure.cpuSome %] +
    Some CPU:[% pressure.cpuSome.avg10 %]%[% pressure.cpuSome.avg60 %]%[% pressure.cpuSome.avg300 %]% + [% END %] + [% IF pressure.ioSome %] +
    Some IO:[% pressure.ioSome.avg10 %]%[% pressure.ioSome.avg60 %]%[% pressure.ioSome.avg300 %]% + [% END %] + [% IF pressure.ioFull %] +
    Full IO:[% pressure.ioFull.avg10 %]%[% pressure.ioFull.avg60 %]%[% pressure.ioFull.avg300 %]% + [% END %] + [% IF pressure.irqFull %] +
    Full IRQ:[% pressure.irqFull.avg10 %]%[% pressure.irqFull.avg60 %]%[% pressure.irqFull.avg300 %]% + [% END %] + [% IF pressure.memSome %] +
    Some Memory:[% pressure.memSome.avg10 %]%[% pressure.memSome.avg60 %]%[% pressure.memSome.avg300 %]% + [% END %] + [% IF pressure.memFull %] +
    Full Memory:[% pressure.memFull.avg10 %]%[% pressure.memFull.avg60 %]%[% pressure.memFull.avg300 %]% + [% END %] +
    +
  • + [% END %] +
+ [% END %] + [% ELSE %] + [% IF m.value.systemTypes %] + + ([% comma=0; FOREACH system IN m.value.systemTypes %][% IF comma; %], [% ELSE; comma = 1; END %][% system %][% END %]) + + [% END %] [% END %] [% IF m.value.nrStepsDone %] diff --git a/src/root/static/css/hydra.css b/src/root/static/css/hydra.css index b55f557a..3899e766 100644 --- a/src/root/static/css/hydra.css +++ b/src/root/static/css/hydra.css @@ -181,6 +181,14 @@ a.squiggle:hover { padding-bottom: 1px; } +table.pressureTable { + margin-left: 2em; +} + +table.pressureTable td { + padding: 0 .4em; +} + @media (prefers-color-scheme: dark) { /* Prevent some flickering */ html { -- 2.50.1 From bfd2a4c4f93c774df73ce0e698e21b6c58c7b14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Fri, 1 Aug 2025 11:25:14 +0200 Subject: [PATCH 20/57] machine-status: Make new runner status prettier - Remove bottom margin - Properly format memory in human format - Calculate free memory - Format the load with 2 digits after comma - Lpad pressure percentages - Use a macro to render pressure - Score -> Scheduling Score - More spacing in the load - Add IRQ pressure --- package.nix | 1 + src/lib/Hydra/Controller/Root.pm | 14 ++++++++++++ src/root/machine-status.tt | 37 +++++++++++++------------------- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/package.nix b/package.nix index 5c1a7860..22caf1da 100644 --- a/package.nix +++ b/package.nix @@ -110,6 +110,7 @@ let NetAmazonS3 NetPrometheus NetStatsd + NumberBytesHuman PadWalker ParallelForkManager PerlCriticCommunity diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index d78e1b6d..c4000d1c 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -9,6 +9,7 @@ use Hydra::Helper::CatalystUtils; use Hydra::View::TT; use Nix::Store; use Nix::Config; +use Number::Bytes::Human qw(format_bytes); use Encode; use File::Basename; use JSON::MaybeXS; @@ -215,6 +216,19 @@ sub machines :Local Args(0) { "where busy != 0 order by machine, stepnr", { Slice => {} }); $c->stash->{template} = 'machine-status.tt'; + $c->stash->{human_bytes} = sub { + my ($bytes) = @_; + return format_bytes($bytes, si => 1); + }; + $c->stash->{pretty_load} = sub { + my ($load) = @_; + return sprintf('%.2f', $load); + }; + $c->stash->{pretty_percent} = sub { + my ($percent) = @_; + my $ret = sprintf('%.2f', $percent); + return (' ' x (6 - length($ret))) . $ret; + }; $self->status_ok($c, entity => $c->stash->{machines}); } diff --git a/src/root/machine-status.tt b/src/root/machine-status.tt index 924bdb52..cf8f960c 100644 --- a/src/root/machine-status.tt +++ b/src/root/machine-status.tt @@ -25,36 +25,29 @@   [% WRAPPER makePopover title="Details" classes="btn-secondary btn-sm" %] -
    +
    • System types: [% comma=0; FOREACH system IN m.value.systemTypes %][% IF comma; %], [% ELSE; comma = 1; END %][% system %][% END %]
    • Supported Features: [% comma=0; FOREACH feat IN m.value.supportedFeatures %][% IF comma; %], [% ELSE; comma = 1; END %][% feat %][% END %]
    • Mandatory Features: [% comma=0; FOREACH feat IN m.value.mandatoryFeatures %][% IF comma; %], [% ELSE; comma = 1; END %][% feat %][% END %]
    • Capacity: [% INCLUDE renderYesNo value=m.value.hasCapacity %] Static: [% INCLUDE renderYesNo value=m.value.hasStaticCapacity %] Dynamic: [% INCLUDE renderYesNo value=m.value.hasDynamicCapacity %]
    • -
    • Score: [% m.value.score %]
    • -
    • Load: [% m.value.stats.load1 %]  [% m.value.stats.load5 %]  [% m.value.stats.load15 %]
    • -
    • Memory: [% mibs(m.value.stats.memUsage / (1024 * 1024)) %] MiB bytes of [% mibs(m.value.memTotal / (1024 * 1024)) %] MiB bytes used
    • +
    • Scheduling Score: [% m.value.score %]
    • +
    • Load: [% pretty_load(m.value.stats.load1) %]   [% pretty_load(m.value.stats.load5) %]   [% pretty_load(m.value.stats.load15) %]
    • +
    • Memory: [% human_bytes(m.value.stats.memUsage) %] of [% human_bytes(m.value.memTotal) %] used ([% human_bytes(m.value.memTotal - m.value.stats.memUsage) %] free)
    • [% pressure = m.value.stats.pressure %] + [% MACRO render_pressure(title, pressure) BLOCK %] + [% IF pressure %] + [% title %]:[% pretty_percent(pressure.avg10) %]%[% pretty_percent(pressure.avg60) %]%[% pretty_percent(pressure.avg300) %]% + [% END %] + [% END %] [% IF pressure %]
    • Pressure:  - [% IF pressure.cpuSome %] -
      Some CPU:[% pressure.cpuSome.avg10 %]%[% pressure.cpuSome.avg60 %]%[% pressure.cpuSome.avg300 %]% - [% END %] - [% IF pressure.ioSome %] -
      Some IO:[% pressure.ioSome.avg10 %]%[% pressure.ioSome.avg60 %]%[% pressure.ioSome.avg300 %]% - [% END %] - [% IF pressure.ioFull %] -
      Full IO:[% pressure.ioFull.avg10 %]%[% pressure.ioFull.avg60 %]%[% pressure.ioFull.avg300 %]% - [% END %] - [% IF pressure.irqFull %] -
      Full IRQ:[% pressure.irqFull.avg10 %]%[% pressure.irqFull.avg60 %]%[% pressure.irqFull.avg300 %]% - [% END %] - [% IF pressure.memSome %] -
      Some Memory:[% pressure.memSome.avg10 %]%[% pressure.memSome.avg60 %]%[% pressure.memSome.avg300 %]% - [% END %] - [% IF pressure.memFull %] -
      Full Memory:[% pressure.memFull.avg10 %]%[% pressure.memFull.avg60 %]%[% pressure.memFull.avg300 %]% - [% END %] + [% render_pressure('Some CPU', pressure.cpuSome) %] + [% render_pressure('Some IO', pressure.ioSome) %] + [% render_pressure('Full IO', pressure.ioFull) %] + [% render_pressure('Full IRQ', pressure.irqFull) %] + [% render_pressure('Some Memory', pressure.memSome) %] + [% render_pressure('Full Memory', pressure.memFull) %]
    • [% END %] -- 2.50.1 From 6ea6d8fc70d2b9c3346a473caa13af3f5fd24604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 14:05:23 +0200 Subject: [PATCH 21/57] machine-status: Fixup double localhost during development --- src/lib/Hydra/Controller/Root.pm | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/lib/Hydra/Controller/Root.pm b/src/lib/Hydra/Controller/Root.pm index c4000d1c..e67d909a 100644 --- a/src/lib/Hydra/Controller/Root.pm +++ b/src/lib/Hydra/Controller/Root.pm @@ -201,9 +201,11 @@ sub machines :Local Args(0) { my $ms = decode_json($status->status)->{"machines"}; foreach my $name (keys %{$ms}) { $name = "" if $name eq "localhost"; - $machines->{$name} //= {disabled => 1}; - $machines->{$name}->{nrStepsDone} = $ms->{$name}->{nrStepsDone}; - $machines->{$name}->{avgStepBuildTime} = $ms->{$name}->{avgStepBuildTime} // 0; + my $outName = $name; + $outName = "" if $name eq "ssh://localhost"; + $machines->{$outName} //= {disabled => 1}; + $machines->{$outName}->{nrStepsDone} = $ms->{$name}->{nrStepsDone}; + $machines->{$outName}->{avgStepBuildTime} = $ms->{$name}->{avgStepBuildTime} // 0; } } -- 2.50.1 From b0ccc5aa49fa52044aa7a2bdd8647656df85415f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 3 Aug 2025 07:20:32 +0200 Subject: [PATCH 22/57] docs/hacking: document how to run single tests --- doc/manual/src/hacking.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/doc/manual/src/hacking.md b/doc/manual/src/hacking.md index 8b2b13ba..6815197a 100644 --- a/doc/manual/src/hacking.md +++ b/doc/manual/src/hacking.md @@ -46,6 +46,16 @@ $ meson test $ YATH_JOB_COUNT=$NIX_BUILD_CORES meson test ``` +To run individual tests: + +```console +# Run a specific test file +$ PERL5LIB=t/lib:$PERL5LIB perl t/test.pl t/Hydra/Controller/API/checks.t + +# Run all tests in a directory +$ PERL5LIB=t/lib:$PERL5LIB perl t/test.pl t/Hydra/Controller/API/ +``` + **Warning**: Currently, the tests can fail if run with high parallelism [due to an issue in `Test::PostgreSQL`](https://github.com/TJC/Test-postgresql/issues/40) -- 2.50.1 From 44b007c16752d110736a9438d58033951875d75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 3 Aug 2025 10:39:12 +0200 Subject: [PATCH 23/57] hydra-eval-jobs: unset NIX_PATH --- src/script/hydra-eval-jobset | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/script/hydra-eval-jobset b/src/script/hydra-eval-jobset index 80f5d79c..56aa1c55 100755 --- a/src/script/hydra-eval-jobset +++ b/src/script/hydra-eval-jobset @@ -396,9 +396,14 @@ sub evalJobs { print STDERR "evaluator: @escaped\n"; } + # Unset NIX_PATH for nix-eval-jobs to ensure reproducible evaluations + my %env = %ENV; + delete $env{'NIX_PATH'}; + my $evalProc = IPC::Run::start \@cmd, '>', IPC::Run::new_chunker, \my $out, - '2>', \my $err; + '2>', \my $err, + init => sub { %ENV = %env; }; return sub { while (1) { -- 2.50.1 From aee4e406e93632c1180379a027aad4fe3c14ba63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 3 Aug 2025 11:25:39 +0200 Subject: [PATCH 24/57] Fix libpqxx 7.10.1 API compatibility - Replace deprecated exec_params/exec_params0 calls with exec() - Wrap all parameterized queries with pqxx::params{} - Add .no_rows()/.one_row() to exec calls that don't return results --- src/hydra-evaluator/hydra-evaluator.cc | 27 ++--- src/hydra-queue-runner/builder.cc | 9 +- src/hydra-queue-runner/hydra-queue-runner.cc | 109 ++++++++----------- src/hydra-queue-runner/queue-monitor.cc | 59 ++++------ 4 files changed, 87 insertions(+), 117 deletions(-) diff --git a/src/hydra-evaluator/hydra-evaluator.cc b/src/hydra-evaluator/hydra-evaluator.cc index 52664188..10cd2233 100644 --- a/src/hydra-evaluator/hydra-evaluator.cc +++ b/src/hydra-evaluator/hydra-evaluator.cc @@ -180,10 +180,8 @@ struct Evaluator { auto conn(dbPool.get()); pqxx::work txn(*conn); - txn.exec_params0 - ("update Jobsets set startTime = $1 where id = $2", - now, - jobset.name.id); + txn.exec("update Jobsets set startTime = $1 where id = $2", + pqxx::params{now, jobset.name.id}).no_rows(); txn.commit(); } @@ -234,7 +232,7 @@ struct Evaluator pqxx::work txn(*conn); if (jobset.evaluation_style == EvaluationStyle::ONE_AT_A_TIME) { - auto evaluation_res = txn.exec_params + auto evaluation_res = txn.exec ("select id from JobsetEvals " "where jobset_id = $1 " "order by id desc limit 1" @@ -250,7 +248,7 @@ struct Evaluator auto evaluation_id = evaluation_res[0][0].as(); - auto unfinished_build_res = txn.exec_params + auto unfinished_build_res = txn.exec ("select id from Builds " "join JobsetEvalMembers " " on (JobsetEvalMembers.build = Builds.id) " @@ -420,21 +418,18 @@ struct Evaluator /* Clear the trigger time to prevent this jobset from getting stuck in an endless failing eval loop. */ - txn.exec_params0 + txn.exec ("update Jobsets set triggerTime = null where id = $1 and startTime is not null and triggerTime <= startTime", - jobset.name.id); + jobset.name.id).no_rows(); /* Clear the start time. */ - txn.exec_params0 + txn.exec ("update Jobsets set startTime = null where id = $1", - jobset.name.id); + jobset.name.id).no_rows(); if (!WIFEXITED(status) || WEXITSTATUS(status) > 1) { - txn.exec_params0 - ("update Jobsets set errorMsg = $1, lastCheckedTime = $2, errorTime = $2, fetchErrorMsg = null where id = $3", - fmt("evaluation %s", statusToString(status)), - now, - jobset.name.id); + txn.exec("update Jobsets set errorMsg = $1, lastCheckedTime = $2, errorTime = $2, fetchErrorMsg = null where id = $3", + pqxx::params{fmt("evaluation %s", statusToString(status)), now, jobset.name.id}).no_rows(); } txn.commit(); @@ -459,7 +454,7 @@ struct Evaluator { auto conn(dbPool.get()); pqxx::work txn(*conn); - txn.exec("update Jobsets set startTime = null"); + txn.exec("update Jobsets set startTime = null").no_rows(); txn.commit(); } diff --git a/src/hydra-queue-runner/builder.cc b/src/hydra-queue-runner/builder.cc index ff0634b1..85f1c8d3 100644 --- a/src/hydra-queue-runner/builder.cc +++ b/src/hydra-queue-runner/builder.cc @@ -458,13 +458,12 @@ void State::failStep( for (auto & build : indirect) { if (build->finishedInDB) continue; printError("marking build %1% as failed", build->id); - txn.exec_params0 - ("update Builds set finished = 1, buildStatus = $2, startTime = $3, stopTime = $4, isCachedBuild = $5, notificationPendingSince = $4 where id = $1 and finished = 0", - build->id, + txn.exec("update Builds set finished = 1, buildStatus = $2, startTime = $3, stopTime = $4, isCachedBuild = $5, notificationPendingSince = $4 where id = $1 and finished = 0", + pqxx::params{build->id, (int) (build->drvPath != step->drvPath && result.buildStatus() == bsFailed ? bsDepFailed : result.buildStatus()), result.startTime, result.stopTime, - result.stepStatus == bsCachedFailure ? 1 : 0); + result.stepStatus == bsCachedFailure ? 1 : 0}).no_rows(); nrBuildsDone++; } @@ -473,7 +472,7 @@ void State::failStep( if (result.stepStatus != bsCachedFailure && result.canCache) for (auto & i : step->drv->outputsAndOptPaths(*localStore)) if (i.second.second) - txn.exec_params0("insert into FailedPaths values ($1)", localStore->printStorePath(*i.second.second)); + txn.exec("insert into FailedPaths values ($1)", pqxx::params{localStore->printStorePath(*i.second.second)}).no_rows(); txn.commit(); } diff --git a/src/hydra-queue-runner/hydra-queue-runner.cc b/src/hydra-queue-runner/hydra-queue-runner.cc index a4a7f0a7..7cdc04b3 100644 --- a/src/hydra-queue-runner/hydra-queue-runner.cc +++ b/src/hydra-queue-runner/hydra-queue-runner.cc @@ -276,17 +276,16 @@ void State::monitorMachinesFile() void State::clearBusy(Connection & conn, time_t stopTime) { pqxx::work txn(conn); - txn.exec_params0 - ("update BuildSteps set busy = 0, status = $1, stopTime = $2 where busy != 0", - (int) bsAborted, - stopTime != 0 ? std::make_optional(stopTime) : std::nullopt); + txn.exec("update BuildSteps set busy = 0, status = $1, stopTime = $2 where busy != 0", + pqxx::params{(int) bsAborted, + stopTime != 0 ? std::make_optional(stopTime) : std::nullopt}).no_rows(); txn.commit(); } unsigned int State::allocBuildStep(pqxx::work & txn, BuildID buildId) { - auto res = txn.exec_params1("select max(stepnr) from BuildSteps where build = $1", buildId); + auto res = txn.exec("select max(stepnr) from BuildSteps where build = $1", buildId).one_row(); return res[0].is_null() ? 1 : res[0].as() + 1; } @@ -297,9 +296,8 @@ unsigned int State::createBuildStep(pqxx::work & txn, time_t startTime, BuildID restart: auto stepNr = allocBuildStep(txn, buildId); - auto r = txn.exec_params - ("insert into BuildSteps (build, stepnr, type, drvPath, busy, startTime, system, status, propagatedFrom, errorMsg, stopTime, machine) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) on conflict do nothing", - buildId, + auto r = txn.exec("insert into BuildSteps (build, stepnr, type, drvPath, busy, startTime, system, status, propagatedFrom, errorMsg, stopTime, machine) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) on conflict do nothing", + pqxx::params{buildId, stepNr, 0, // == build localStore->printStorePath(step->drvPath), @@ -310,17 +308,16 @@ unsigned int State::createBuildStep(pqxx::work & txn, time_t startTime, BuildID propagatedFrom != 0 ? std::make_optional(propagatedFrom) : std::nullopt, // internal::params errorMsg != "" ? std::make_optional(errorMsg) : std::nullopt, startTime != 0 && status != bsBusy ? std::make_optional(startTime) : std::nullopt, - machine); + machine}); if (r.affected_rows() == 0) goto restart; for (auto & [name, output] : getDestStore()->queryPartialDerivationOutputMap(step->drvPath, &*localStore)) - txn.exec_params0 - ("insert into BuildStepOutputs (build, stepnr, name, path) values ($1, $2, $3, $4)", - buildId, stepNr, name, + txn.exec("insert into BuildStepOutputs (build, stepnr, name, path) values ($1, $2, $3, $4)", + pqxx::params{buildId, stepNr, name, output ? std::optional { localStore->printStorePath(*output)} - : std::nullopt); + : std::nullopt}).no_rows(); if (status == bsBusy) txn.exec(fmt("notify step_started, '%d\t%d'", buildId, stepNr)); @@ -331,11 +328,10 @@ unsigned int State::createBuildStep(pqxx::work & txn, time_t startTime, BuildID void State::updateBuildStep(pqxx::work & txn, BuildID buildId, unsigned int stepNr, StepState stepState) { - if (txn.exec_params - ("update BuildSteps set busy = $1 where build = $2 and stepnr = $3 and busy != 0 and status is null", - (int) stepState, + if (txn.exec("update BuildSteps set busy = $1 where build = $2 and stepnr = $3 and busy != 0 and status is null", + pqxx::params{(int) stepState, buildId, - stepNr).affected_rows() != 1) + stepNr}).affected_rows() != 1) throw Error("step %d of build %d is in an unexpected state", stepNr, buildId); } @@ -345,29 +341,27 @@ void State::finishBuildStep(pqxx::work & txn, const RemoteResult & result, { assert(result.startTime); assert(result.stopTime); - txn.exec_params0 - ("update BuildSteps set busy = 0, status = $1, errorMsg = $4, startTime = $5, stopTime = $6, machine = $7, overhead = $8, timesBuilt = $9, isNonDeterministic = $10 where build = $2 and stepnr = $3", - (int) result.stepStatus, buildId, stepNr, + txn.exec("update BuildSteps set busy = 0, status = $1, errorMsg = $4, startTime = $5, stopTime = $6, machine = $7, overhead = $8, timesBuilt = $9, isNonDeterministic = $10 where build = $2 and stepnr = $3", + pqxx::params{(int) result.stepStatus, buildId, stepNr, result.errorMsg != "" ? std::make_optional(result.errorMsg) : std::nullopt, result.startTime, result.stopTime, machine != "" ? std::make_optional(machine) : std::nullopt, result.overhead != 0 ? std::make_optional(result.overhead) : std::nullopt, result.timesBuilt > 0 ? std::make_optional(result.timesBuilt) : std::nullopt, - result.timesBuilt > 1 ? std::make_optional(result.isNonDeterministic) : std::nullopt); + result.timesBuilt > 1 ? std::make_optional(result.isNonDeterministic) : std::nullopt}).no_rows(); assert(result.logFile.find('\t') == std::string::npos); txn.exec(fmt("notify step_finished, '%d\t%d\t%s'", buildId, stepNr, result.logFile)); if (result.stepStatus == bsSuccess) { // Update the corresponding `BuildStepOutputs` row to add the output path - auto res = txn.exec_params1("select drvPath from BuildSteps where build = $1 and stepnr = $2", buildId, stepNr); + auto res = txn.exec("select drvPath from BuildSteps where build = $1 and stepnr = $2", pqxx::params{buildId, stepNr}).one_row(); assert(res.size()); StorePath drvPath = localStore->parseStorePath(res[0].as()); // If we've finished building, all the paths should be known for (auto & [name, output] : getDestStore()->queryDerivationOutputMap(drvPath, &*localStore)) - txn.exec_params0 - ("update BuildStepOutputs set path = $4 where build = $1 and stepnr = $2 and name = $3", - buildId, stepNr, name, localStore->printStorePath(output)); + txn.exec("update BuildStepOutputs set path = $4 where build = $1 and stepnr = $2 and name = $3", + pqxx::params{buildId, stepNr, name, localStore->printStorePath(output)}).no_rows(); } } @@ -378,23 +372,21 @@ int State::createSubstitutionStep(pqxx::work & txn, time_t startTime, time_t sto restart: auto stepNr = allocBuildStep(txn, build->id); - auto r = txn.exec_params - ("insert into BuildSteps (build, stepnr, type, drvPath, busy, status, startTime, stopTime) values ($1, $2, $3, $4, $5, $6, $7, $8) on conflict do nothing", - build->id, + auto r = txn.exec("insert into BuildSteps (build, stepnr, type, drvPath, busy, status, startTime, stopTime) values ($1, $2, $3, $4, $5, $6, $7, $8) on conflict do nothing", + pqxx::params{build->id, stepNr, 1, // == substitution (localStore->printStorePath(drvPath)), 0, 0, startTime, - stopTime); + stopTime}); if (r.affected_rows() == 0) goto restart; - txn.exec_params0 - ("insert into BuildStepOutputs (build, stepnr, name, path) values ($1, $2, $3, $4)", - build->id, stepNr, outputName, - localStore->printStorePath(storePath)); + txn.exec("insert into BuildStepOutputs (build, stepnr, name, path) values ($1, $2, $3, $4)", + pqxx::params{build->id, stepNr, outputName, + localStore->printStorePath(storePath)}).no_rows(); return stepNr; } @@ -461,35 +453,32 @@ void State::markSucceededBuild(pqxx::work & txn, Build::ptr build, { if (build->finishedInDB) return; - if (txn.exec_params("select 1 from Builds where id = $1 and finished = 0", build->id).empty()) return; + if (txn.exec("select 1 from Builds where id = $1 and finished = 0", pqxx::params{build->id}).empty()) return; - txn.exec_params0 - ("update Builds set finished = 1, buildStatus = $2, startTime = $3, stopTime = $4, size = $5, closureSize = $6, releaseName = $7, isCachedBuild = $8, notificationPendingSince = $4 where id = $1", - build->id, + txn.exec("update Builds set finished = 1, buildStatus = $2, startTime = $3, stopTime = $4, size = $5, closureSize = $6, releaseName = $7, isCachedBuild = $8, notificationPendingSince = $4 where id = $1", + pqxx::params{build->id, (int) (res.failed ? bsFailedWithOutput : bsSuccess), startTime, stopTime, res.size, res.closureSize, res.releaseName != "" ? std::make_optional(res.releaseName) : std::nullopt, - isCachedBuild ? 1 : 0); + isCachedBuild ? 1 : 0}).no_rows(); for (auto & [outputName, outputPath] : res.outputs) { - txn.exec_params0 - ("update BuildOutputs set path = $3 where build = $1 and name = $2", - build->id, + txn.exec("update BuildOutputs set path = $3 where build = $1 and name = $2", + pqxx::params{build->id, outputName, - localStore->printStorePath(outputPath) - ); + localStore->printStorePath(outputPath)} + ).no_rows(); } - txn.exec_params0("delete from BuildProducts where build = $1", build->id); + txn.exec("delete from BuildProducts where build = $1", pqxx::params{build->id}).no_rows(); unsigned int productNr = 1; for (auto & product : res.products) { - txn.exec_params0 - ("insert into BuildProducts (build, productnr, type, subtype, fileSize, sha256hash, path, name, defaultPath) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)", - build->id, + txn.exec("insert into BuildProducts (build, productnr, type, subtype, fileSize, sha256hash, path, name, defaultPath) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + pqxx::params{build->id, productNr++, product.type, product.subtype, @@ -497,22 +486,21 @@ void State::markSucceededBuild(pqxx::work & txn, Build::ptr build, product.sha256hash ? std::make_optional(product.sha256hash->to_string(HashFormat::Base16, false)) : std::nullopt, product.path, product.name, - product.defaultPath); + product.defaultPath}).no_rows(); } - txn.exec_params0("delete from BuildMetrics where build = $1", build->id); + txn.exec("delete from BuildMetrics where build = $1", pqxx::params{build->id}).no_rows(); for (auto & metric : res.metrics) { - txn.exec_params0 - ("insert into BuildMetrics (build, name, unit, value, project, jobset, job, timestamp) values ($1, $2, $3, $4, $5, $6, $7, $8)", - build->id, + txn.exec("insert into BuildMetrics (build, name, unit, value, project, jobset, job, timestamp) values ($1, $2, $3, $4, $5, $6, $7, $8)", + pqxx::params{build->id, metric.second.name, metric.second.unit != "" ? std::make_optional(metric.second.unit) : std::nullopt, metric.second.value, build->projectName, build->jobsetName, build->jobName, - build->timestamp); + build->timestamp}).no_rows(); } nrBuildsDone++; @@ -524,7 +512,7 @@ bool State::checkCachedFailure(Step::ptr step, Connection & conn) pqxx::work txn(conn); for (auto & i : step->drv->outputsAndOptPaths(*localStore)) if (i.second.second) - if (!txn.exec_params("select 1 from FailedPaths where path = $1", localStore->printStorePath(*i.second.second)).empty()) + if (!txn.exec("select 1 from FailedPaths where path = $1", pqxx::params{localStore->printStorePath(*i.second.second)}).empty()) return true; return false; } @@ -736,8 +724,8 @@ void State::dumpStatus(Connection & conn) auto mc = startDbUpdate(); pqxx::work txn(conn); // FIXME: use PostgreSQL 9.5 upsert. - txn.exec("delete from SystemStatus where what = 'queue-runner'"); - txn.exec_params0("insert into SystemStatus values ('queue-runner', $1)", statusJson.dump()); + txn.exec("delete from SystemStatus where what = 'queue-runner'").no_rows(); + txn.exec("insert into SystemStatus values ('queue-runner', $1)", pqxx::params{statusJson.dump()}).no_rows(); txn.exec("notify status_dumped"); txn.commit(); } @@ -802,7 +790,7 @@ void State::unlock() { pqxx::work txn(*conn); - txn.exec("delete from SystemStatus where what = 'queue-runner'"); + txn.exec("delete from SystemStatus where what = 'queue-runner'").no_rows(); txn.commit(); } } @@ -880,11 +868,10 @@ void State::run(BuildID buildOne) pqxx::work txn(*conn); for (auto & step : steps) { printMsg(lvlError, "cleaning orphaned step %d of build %d", step.second, step.first); - txn.exec_params0 - ("update BuildSteps set busy = 0, status = $1 where build = $2 and stepnr = $3 and busy != 0", - (int) bsAborted, + txn.exec("update BuildSteps set busy = 0, status = $1 where build = $2 and stepnr = $3 and busy != 0", + pqxx::params{(int) bsAborted, step.first, - step.second); + step.second}).no_rows(); } txn.commit(); } catch (std::exception & e) { diff --git a/src/hydra-queue-runner/queue-monitor.cc b/src/hydra-queue-runner/queue-monitor.cc index 0785be6f..cce60da9 100644 --- a/src/hydra-queue-runner/queue-monitor.cc +++ b/src/hydra-queue-runner/queue-monitor.cc @@ -108,8 +108,7 @@ bool State::getQueuedBuilds(Connection & conn, { pqxx::work txn(conn); - auto res = txn.exec_params - ("select builds.id, builds.jobset_id, jobsets.project as project, " + auto res = txn.exec("select builds.id, builds.jobset_id, jobsets.project as project, " "jobsets.name as jobset, job, drvPath, maxsilent, timeout, timestamp, " "globalPriority, priority from Builds " "inner join jobsets on builds.jobset_id = jobsets.id " @@ -158,11 +157,10 @@ bool State::getQueuedBuilds(Connection & conn, if (!build->finishedInDB) { auto mc = startDbUpdate(); pqxx::work txn(conn); - txn.exec_params0 - ("update Builds set finished = 1, buildStatus = $2, startTime = $3, stopTime = $3 where id = $1 and finished = 0", - build->id, + txn.exec("update Builds set finished = 1, buildStatus = $2, startTime = $3, stopTime = $3 where id = $1 and finished = 0", + pqxx::params{build->id, (int) bsAborted, - time(0)); + time(0)}).no_rows(); txn.commit(); build->finishedInDB = true; nrBuildsDone++; @@ -192,22 +190,20 @@ bool State::getQueuedBuilds(Connection & conn, derivation path, then by output path. */ BuildID propagatedFrom = 0; - auto res = txn.exec_params1 - ("select max(build) from BuildSteps where drvPath = $1 and startTime != 0 and stopTime != 0 and status = 1", - localStore->printStorePath(ex.step->drvPath)); + auto res = txn.exec("select max(build) from BuildSteps where drvPath = $1 and startTime != 0 and stopTime != 0 and status = 1", + pqxx::params{localStore->printStorePath(ex.step->drvPath)}).one_row(); if (!res[0].is_null()) propagatedFrom = res[0].as(); if (!propagatedFrom) { for (auto & [outputName, optOutputPath] : destStore->queryPartialDerivationOutputMap(ex.step->drvPath, &*localStore)) { constexpr std::string_view common = "select max(s.build) from BuildSteps s join BuildStepOutputs o on s.build = o.build where startTime != 0 and stopTime != 0 and status = 1"; auto res = optOutputPath - ? txn.exec_params( + ? txn.exec( std::string { common } + " and path = $1", - localStore->printStorePath(*optOutputPath)) - : txn.exec_params( + pqxx::params{localStore->printStorePath(*optOutputPath)}) + : txn.exec( std::string { common } + " and drvPath = $1 and name = $2", - localStore->printStorePath(ex.step->drvPath), - outputName); + pqxx::params{localStore->printStorePath(ex.step->drvPath), outputName}); if (!res[0][0].is_null()) { propagatedFrom = res[0][0].as(); break; @@ -216,12 +212,11 @@ bool State::getQueuedBuilds(Connection & conn, } createBuildStep(txn, 0, build->id, ex.step, "", bsCachedFailure, "", propagatedFrom); - txn.exec_params - ("update Builds set finished = 1, buildStatus = $2, startTime = $3, stopTime = $3, isCachedBuild = 1, notificationPendingSince = $3 " + txn.exec("update Builds set finished = 1, buildStatus = $2, startTime = $3, stopTime = $3, isCachedBuild = 1, notificationPendingSince = $3 " "where id = $1 and finished = 0", - build->id, + pqxx::params{build->id, (int) (ex.step->drvPath == build->drvPath ? bsFailed : bsDepFailed), - time(0)); + time(0)}).no_rows(); notifyBuildFinished(txn, build->id, {}); txn.commit(); build->finishedInDB = true; @@ -653,10 +648,8 @@ Jobset::ptr State::createJobset(pqxx::work & txn, if (i != jobsets_->end()) return i->second; } - auto res = txn.exec_params1 - ("select schedulingShares from Jobsets where id = $1", - jobsetID); - if (res.empty()) throw Error("missing jobset - can't happen"); + auto res = txn.exec("select schedulingShares from Jobsets where id = $1", + pqxx::params{jobsetID}).one_row(); auto shares = res["schedulingShares"].as(); @@ -664,11 +657,10 @@ Jobset::ptr State::createJobset(pqxx::work & txn, jobset->setShares(shares); /* Load the build steps from the last 24 hours. */ - auto res2 = txn.exec_params - ("select s.startTime, s.stopTime from BuildSteps s join Builds b on build = id " + auto res2 = txn.exec("select s.startTime, s.stopTime from BuildSteps s join Builds b on build = id " "where s.startTime is not null and s.stopTime > $1 and jobset_id = $2", - time(0) - Jobset::schedulingWindow * 10, - jobsetID); + pqxx::params{time(0) - Jobset::schedulingWindow * 10, + jobsetID}); for (auto const & row : res2) { time_t startTime = row["startTime"].as(); time_t stopTime = row["stopTime"].as(); @@ -705,11 +697,10 @@ BuildOutput State::getBuildOutputCached(Connection & conn, nix::ref pqxx::work txn(conn); for (auto & [name, output] : derivationOutputs) { - auto r = txn.exec_params - ("select id, buildStatus, releaseName, closureSize, size from Builds b " + auto r = txn.exec("select id, buildStatus, releaseName, closureSize, size from Builds b " "join BuildOutputs o on b.id = o.build " "where finished = 1 and (buildStatus = 0 or buildStatus = 6) and path = $1", - localStore->printStorePath(output)); + pqxx::params{localStore->printStorePath(output)}); if (r.empty()) continue; BuildID id = r[0][0].as(); @@ -721,9 +712,8 @@ BuildOutput State::getBuildOutputCached(Connection & conn, nix::ref res.closureSize = r[0][3].is_null() ? 0 : r[0][3].as(); res.size = r[0][4].is_null() ? 0 : r[0][4].as(); - auto products = txn.exec_params - ("select type, subtype, fileSize, sha256hash, path, name, defaultPath from BuildProducts where build = $1 order by productnr", - id); + auto products = txn.exec("select type, subtype, fileSize, sha256hash, path, name, defaultPath from BuildProducts where build = $1 order by productnr", + pqxx::params{id}); for (auto row : products) { BuildProduct product; @@ -745,9 +735,8 @@ BuildOutput State::getBuildOutputCached(Connection & conn, nix::ref res.products.emplace_back(product); } - auto metrics = txn.exec_params - ("select name, unit, value from BuildMetrics where build = $1", - id); + auto metrics = txn.exec("select name, unit, value from BuildMetrics where build = $1", + pqxx::params{id}); for (auto row : metrics) { BuildMetric metric; -- 2.50.1 From 5187992c943770f5f62cce81b4176bc8bd32c3be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 3 Aug 2025 12:03:00 +0200 Subject: [PATCH 25/57] Migrate from deprecated notification_receiver to connection::listen() libpqxx 7.10.1 deprecates the notification_receiver class. --- src/libhydra/db.hh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libhydra/db.hh b/src/libhydra/db.hh index c664a01d..acdf3b8c 100644 --- a/src/libhydra/db.hh +++ b/src/libhydra/db.hh @@ -27,19 +27,20 @@ struct Connection : pqxx::connection }; -class receiver : public pqxx::notification_receiver +class receiver { std::optional status; + pqxx::connection & conn; public: receiver(pqxx::connection_base & c, const std::string & channel) - : pqxx::notification_receiver(c, channel) { } - - void operator() (const std::string & payload, int pid) override + : conn(static_cast(c)) { - status = payload; - }; + conn.listen(channel, [this](pqxx::notification n) { + status = std::string(n.payload); + }); + } std::optional get() { auto s = status; -- 2.50.1 From 64e05c24e22c1adc87cfb2dc1a7f391a59f452f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 3 Aug 2025 12:13:56 +0200 Subject: [PATCH 26/57] queue-runner: Add missing signal.h include for SIGINT and kill() --- src/hydra-queue-runner/queue-monitor.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hydra-queue-runner/queue-monitor.cc b/src/hydra-queue-runner/queue-monitor.cc index cce60da9..d6d87e71 100644 --- a/src/hydra-queue-runner/queue-monitor.cc +++ b/src/hydra-queue-runner/queue-monitor.cc @@ -5,6 +5,7 @@ #include #include +#include using namespace nix; -- 2.50.1 From a44d946e090c16afdd735b2f7021f58341b0d172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 3 Aug 2025 12:27:24 +0200 Subject: [PATCH 27/57] cache build with the magic nix cache --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 613e3ef9..3ad0ba6e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,5 +13,6 @@ jobs: with: fetch-depth: 0 - uses: cachix/install-nix-action@v31 + - uses: DeterminateSystems/magic-nix-cache-action@main #- run: nix flake check - run: nix-build -A checks.x86_64-linux.build -A checks.x86_64-linux.validate-openapi -- 2.50.1 From ed939f44f4813f4d7359eebca3c0c437333eefce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 3 Aug 2025 12:37:20 +0200 Subject: [PATCH 28/57] ci: also build on aarch64-linux --- .github/workflows/test.yml | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3ad0ba6e..790e7565 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,12 +7,21 @@ on: - master jobs: tests: - runs-on: ubuntu-latest + strategy: + matrix: + include: + - system: x86_64-linux + runner: ubuntu-latest + - system: aarch64-linux + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: cachix/install-nix-action@v31 + with: + extra_nix_config: | + extra-systems = ${{ matrix.system }} - uses: DeterminateSystems/magic-nix-cache-action@main - #- run: nix flake check - - run: nix-build -A checks.x86_64-linux.build -A checks.x86_64-linux.validate-openapi + - run: nix-build -A checks.${{ matrix.system }}.build -A checks.${{ matrix.system }}.validate-openapi -- 2.50.1 From b295744323184ed970f812bae6a436eb35aff156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 3 Aug 2025 07:12:17 +0200 Subject: [PATCH 29/57] package.nix: fix PATH for devshell We don't install scripts to build so this must point to src --- package.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.nix b/package.nix index 22caf1da..2ea21b1e 100644 --- a/package.nix +++ b/package.nix @@ -239,7 +239,7 @@ stdenv.mkDerivation (finalAttrs: { shellHook = '' pushd $(git rev-parse --show-toplevel) >/dev/null - PATH=$(pwd)/build/src/hydra-evaluator:$(pwd)/build/src/script:$(pwd)/build/src/hydra-queue-runner:$PATH + PATH=$(pwd)/build/src/hydra-evaluator:$(pwd)/src/script:$(pwd)/build/src/hydra-queue-runner:$PATH PERL5LIB=$(pwd)/src/lib:$PERL5LIB export HYDRA_HOME="$(pwd)/src/" mkdir -p .hydra-data -- 2.50.1 From 3eeba86a87efebb06498c87571c7ee0036d5a801 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:01:08 +0200 Subject: [PATCH 30/57] hydra-queue-runner: Fix crash when < > are in hydra-build-products This prevents a forever-hanging build (don't know why) when < or > are in the path of hydra-build-products. This is not to prevent any XSS (see next commits), just to prevent the DOS (if you can even call it that). --- src/hydra-queue-runner/build-result.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index b0695e8b..a3fc3c38 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -51,8 +51,8 @@ BuildOutput getBuildOutput( "[[:space:]]+" "([a-zA-Z0-9_-]+)" // subtype (e.g. "readme") "[[:space:]]+" - "(\"[^\"]+\"|[^[:space:]\"]+)" // path (may be quoted) - "([[:space:]]+([^[:space:]]+))?" // entry point + "(\"[^\"]+\"|[^[:space:]<>\"]+)" // path (may be quoted) + "([[:space:]]+([^[:space:]<>]+))?" // entry point , std::regex::extended); for (auto & output : outputs) { -- 2.50.1 From 21a75982aa1c3c015eb1a70f4cde906f77d3c76a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:02:48 +0200 Subject: [PATCH 31/57] hydra-queue-runner: Fix potential UB Removing two characters from a string when it starts with " can lead to a substring call with -1 --- src/hydra-queue-runner/build-result.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index a3fc3c38..1874b085 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -78,7 +78,7 @@ BuildOutput getBuildOutput( product.type = match[1]; product.subtype = match[2]; std::string s(match[3]); - product.path = s[0] == '"' ? std::string(s, 1, s.size() - 2) : s; + product.path = s[0] == '"' && s.back() == '"' ? std::string(s, 1, s.size() - 2) : s; product.defaultPath = match[5]; /* Ensure that the path exists and points into the Nix -- 2.50.1 From 252801cea8299f8956d005a035362205137e2ca5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:05:33 +0200 Subject: [PATCH 32/57] hydra-queue-runner: Verify product names in hydra-build-products --- src/hydra-queue-runner/build-result.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index 1874b085..f77e7dde 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -93,6 +93,8 @@ BuildOutput getBuildOutput( if (file == narMembers.end()) continue; product.name = product.path == store->printStorePath(output) ? "" : baseNameOf(product.path); + if (!std::regex_match(product.name, std::regex("[a-zA-Z0-9.@:_ -]*"))) + product.name = ""; if (file->second.type == SourceAccessor::Type::tRegular) { product.isRegular = true; -- 2.50.1 From 9396846892e0bb65e1a1d12bf32b16540dc7ae93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:06:13 +0200 Subject: [PATCH 33/57] hydra-queue-runner: Validate release name --- src/hydra-queue-runner/build-result.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index f77e7dde..a5dec9c5 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -129,8 +129,9 @@ BuildOutput getBuildOutput( if (file == narMembers.end() || file->second.type != SourceAccessor::Type::tRegular) continue; - res.releaseName = trim(file->second.contents.value()); - // FIXME: validate release name + auto contents = trim(file->second.contents.value()); + if (std::regex_match(contents, std::regex("[a-zA-Z0-9.@:_-]+"))) + res.releaseName = contents; } /* Get metrics. */ -- 2.50.1 From 74d923441e439542d9a5353226885ddbc53d344a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:07:25 +0200 Subject: [PATCH 34/57] hydra-queue-runner: Validate metric name in hydra-metrics --- src/hydra-queue-runner/build-result.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index a5dec9c5..937cdd94 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -143,8 +143,10 @@ BuildOutput getBuildOutput( for (auto & line : tokenizeString(file->second.contents.value(), "\n")) { auto fields = tokenizeString>(line); if (fields.size() < 2) continue; + if (!std::regex_match(fields[0], std::regex("[a-zA-Z0-9._-]+"))) + continue; BuildMetric metric; - metric.name = fields[0]; // FIXME: validate + metric.name = fields[0]; metric.value = atof(fields[1].c_str()); // FIXME metric.unit = fields.size() >= 3 ? fields[2] : ""; res.metrics[metric.name] = metric; -- 2.50.1 From 8f3fdc14d854a264e4d80c2aaaa4f272e3b64304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:09:02 +0200 Subject: [PATCH 35/57] hydra-queue-runner: Validate hydra-metrics unit --- src/hydra-queue-runner/build-result.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index 937cdd94..73bf79f0 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -149,6 +149,8 @@ BuildOutput getBuildOutput( metric.name = fields[0]; metric.value = atof(fields[1].c_str()); // FIXME metric.unit = fields.size() >= 3 ? fields[2] : ""; + if (!std::regex_match(metric.unit, std::regex("[a-zA-Z0-9._%-]+"))) + metric.unit = ""; res.metrics[metric.name] = metric; } } -- 2.50.1 From 5014274c99abe98bb20be52a40bf4bff8134a7d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:17:35 +0200 Subject: [PATCH 36/57] hydra-queue-runner: Validate metric type --- src/hydra-queue-runner/build-result.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hydra-queue-runner/build-result.cc b/src/hydra-queue-runner/build-result.cc index 73bf79f0..aa98acbb 100644 --- a/src/hydra-queue-runner/build-result.cc +++ b/src/hydra-queue-runner/build-result.cc @@ -147,7 +147,11 @@ BuildOutput getBuildOutput( continue; BuildMetric metric; metric.name = fields[0]; - metric.value = atof(fields[1].c_str()); // FIXME + try { + metric.value = std::stod(fields[1]); + } catch (...) { + continue; // skip this metric + } metric.unit = fields.size() >= 3 ? fields[2] : ""; if (!std::regex_match(metric.unit, std::regex("[a-zA-Z0-9._%-]+"))) metric.unit = ""; -- 2.50.1 From 0764b1f48c59d3f07bdf7610c6be290cc3fa3204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 15:26:34 +0200 Subject: [PATCH 37/57] product-list: Escape untrusted values --- src/root/product-list.tt | 56 ++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/root/product-list.tt b/src/root/product-list.tt index 4d545b3e..97fe6141 100644 --- a/src/root/product-list.tt +++ b/src/root/product-list.tt @@ -1,17 +1,17 @@ [% BLOCK renderProductLinks %] URL: - [% uri %] + uri) %]>[% uri | html %] [% IF latestRoot %] Links to latest: [% uri2 = "${c.uri_for(latestRoot.join('/') 'download-by-type' product.type product.subtype)}" %] - [% uri2 %] + uri2) %]>[% uri2 | html %]
      [% uri2 = "${c.uri_for(latestRoot.join('/') 'download' product.productnr)}" %] - [% uri2 %] + uri2) %]>[% uri2 | html %] [% END %] @@ -49,7 +49,7 @@ Error - + contents) %]> Failed build produced output. Click here to inspect the output. @@ -58,9 +58,9 @@

      If you have Nix installed on your machine, this failed build output and all its dependencies can be unpacked into your local Nix store by doing:

      -
      $ curl [% uri %] | gunzip | nix-store --import
      +
      $ curl [% HTML.escape(uri) %] | gunzip | nix-store --import
      -

      The build output can then be found in the path [% product.path %].

      +

      The build output can then be found in the path [% product.path | html %].

      [% END %] @@ -74,7 +74,7 @@ Nix package - [% HTML.escape(build.nixname) %] + [% build.nixname | html %] [% WRAPPER makePopover title="Help" classes="btn-secondary btn-sm" @@ -84,7 +84,7 @@
      $ nix-env -i [%HTML.escape(product.path)%][% IF binaryCachePublicUri %] --option binary-caches [% HTML.escape(binaryCachePublicUri) %][% END %]
      [% END %] [% IF localStore %] - Contents + contents) %]>Contents [% END %] @@ -100,8 +100,8 @@ [% filename = build.nixname _ (product.subtype ? "-" _ product.subtype : "") _ ".closure.gz" %] [% uri = c.uri_for('/build' build.id 'nix' 'closure' filename ) %] - - [% product.path %] + uri) %]> + [% product.path | html %] @@ -110,16 +110,16 @@ all its dependencies can be unpacked into your local Nix store by doing:

      -
      $ gunzip < [% filename %] | nix-store --import
      +
      $ gunzip < [% HTML.escape(filename) %] | nix-store --import

      or to download and unpack in one command:

      -
      $ curl [% uri %] | gunzip | nix-store --import
      +
      $ curl [% HTML.escape(uri) %] | gunzip | nix-store --import

      The package can then be found in the path [% - product.path %]. You’ll probably also want to do

      + product.path | html %]. You’ll probably also want to do

      -
      $ nix-env -i [% product.path %]
      +
      $ nix-env -i [% HTML.escape(product.path) %]

      to actually install the package in your Nix user environment.

      @@ -174,16 +174,16 @@ Channel expression tarball - [% IF product.subtype != "-" %]for [% product.subtype %][% END %] + [% IF product.subtype != "-" %]for [% product.subtype | html %][% END %] [% ELSE %] File - [% product.subtype %] + [% HTML.escape(product.subtype) %] [% END %] [% END %] - - [% product.name %] + uri) %]> + [% product.name | html %] @@ -191,12 +191,12 @@ [% INCLUDE renderProductLinks %] - - + +
      File size:[% product.filesize %] bytes ([% mibs(product.filesize / (1024 * 1024)) %] MiB)
      SHA-256 hash:[% product.sha256hash %]
      Full path:[% product.path %]
      SHA-256 hash:[% product.sha256hash | html %]
      Full path:[% product.path | html %]
      [% END %] [% IF localStore %] - Contents + contents) %]>Contents [% END %] @@ -211,15 +211,15 @@ [% CASE "coverage" %] Code coverage - + uri) %]> Analysis report [% CASE DEFAULT %] Report - - [% product.subtype %] + uri) %]> + [% product.subtype | html %] [% END %] @@ -240,7 +240,7 @@ Documentation - + uri) %]> [% SWITCH product.subtype %] [% CASE "readme" %] Read Me! @@ -249,7 +249,7 @@ [% CASE "release-notes" %] Release notes [% CASE DEFAULT %] - [% product.subtype %] + [% HTML.escape(product.subtype) %] [% END %] @@ -266,12 +266,12 @@ - [% product.type %] + [% product.type | html %] - [% product %] + [% HTML.escape(product) %] -- 2.50.1 From bb78a58ea2aaa231454460339936de38d28ab82c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 16:26:26 +0200 Subject: [PATCH 38/57] build: Properly escape all input values --- src/root/build.tt | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index 93629427..cf8e454d 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -37,7 +37,7 @@ END; seen.${step.drvpath} = 1; log = c.uri_for('/build' build.id 'nixlog' step.stepnr); %] - [% step.stepnr %] + [% HTML.escape(step.stepnr) %] [% IF step.type == 0 %] Build of [% INCLUDE renderOutputs outputs=step.buildstepoutputs %] @@ -151,7 +151,7 @@ END; - + @@ -168,9 +168,9 @@ END; END; %]; [%+ IF nrFinished == nrConstituents && nrFailedConstituents == 0 %] - all [% nrConstituents %] constituent builds succeeded + all [% HTML.escape(nrConstituents) %] constituent builds succeeded [% ELSE %] - [% nrFailedConstituents %] out of [% nrConstituents %] constituent builds failed + [% HTML.escape(nrFailedConstituents) %] out of [% HTML.escape(nrConstituents) %] constituent builds failed [% IF nrFinished < nrConstituents %] ([% nrConstituents - nrFinished %] still pending) [% END %] @@ -180,24 +180,24 @@ END; - + [% IF build.releasename %] - + [% ELSE %] - + [% END %] [% IF eval %] @@ -336,12 +336,12 @@ END; [% IF eval.nixexprinput %] - + [% END %] - + @@ -361,11 +361,11 @@ END; - + - + @@ -412,9 +412,9 @@ END; [% FOREACH metric IN build.buildmetrics %] - - - + + + [% END %] @@ -456,8 +456,8 @@ END; [% FOREACH input IN build.dependents %] - - + + [% END %] @@ -484,7 +484,7 @@ END; [% ELSIF runcommandlogProblem == "disabled-jobset" %] This jobset does not enable Dynamic RunCommand support. [% ELSE %] - Dynamic RunCommand is not enabled: [% runcommandlogProblem %]. + Dynamic RunCommand is not enabled: [% HTML.escape(runcommandlogProblem) %]. [% END %] [% END %] @@ -503,7 +503,7 @@ END;
      -
      [% runcommandlog.command | html%]
      +
      [% runcommandlog.command | html %]
      [% IF not runcommandlog.is_running() %] [% IF runcommandlog.did_fail_with_signal() %] -- 2.50.1 From 06c6bd1b7b00fad27fe57f41a15d0b5b700f138d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janne=20He=C3=9F?= Date: Sat, 2 Aug 2025 16:56:04 +0200 Subject: [PATCH 39/57] templates: Use HTML.attributes for all links --- src/root/build.tt | 30 +++++++++++------------ src/root/channel-contents.tt | 2 +- src/root/common.tt | 42 ++++++++++++++++----------------- src/root/dashboard.tt | 2 +- src/root/deps.tt | 2 +- src/root/job.tt | 12 +++++----- src/root/jobset-channels-tab.tt | 6 ++--- src/root/jobset-eval.tt | 20 ++++++++-------- src/root/jobset-jobs-tab.tt | 4 ++-- src/root/jobset.tt | 2 +- src/root/layout.tt | 2 +- src/root/log.tt | 8 +++---- src/root/machine-status.tt | 4 ++-- src/root/overview.tt | 2 +- src/root/runcommand-log.tt | 4 ++-- src/root/steps.tt | 4 ++-- src/root/style.tt | 10 ++++---- src/root/users.tt | 4 ++-- 18 files changed, 80 insertions(+), 80 deletions(-) diff --git a/src/root/build.tt b/src/root/build.tt index cf8e454d..818763bd 100644 --- a/src/root/build.tt +++ b/src/root/build.tt @@ -112,16 +112,16 @@ END; [% IF c.user_exists %] [% IF available %] [% IF build.keep %] - Unkeep + c.uri_for('/build' build.id 'keep' 0)) %]>Unkeep [% ELSE %] - Keep + c.uri_for('/build' build.id 'keep' 1)) %]>Keep [% END %] [% END %] [% IF build.finished %] - Restart + c.uri_for('/build' build.id 'restart')) %]>Restart [% ELSE %] - Cancel - Bump up + c.uri_for('/build' build.id 'cancel')) %]>Cancel + c.uri_for('/build' build.id 'bump')) %]>Bump up [% END %] [% END %]
      @@ -197,8 +197,8 @@ END;
      [% END %] @@ -226,9 +226,9 @@ END; [% END %] @@ -376,14 +376,14 @@ END; + ( chartsURL) %]>history) [% END %] [% IF build.finished && build.closuresize %] + ( chartsURL) %]>history) [% END %] [% IF build.finished && build.buildproducts %] @@ -532,9 +532,9 @@ END; [% IF runcommandlog.uuid != undef %] [% runLog = c.uri_for('/build', build.id, 'runcommandlog', runcommandlog.uuid) %] [% END %] diff --git a/src/root/channel-contents.tt b/src/root/channel-contents.tt index 083d6ae5..ff79dd0f 100644 --- a/src/root/channel-contents.tt +++ b/src/root/channel-contents.tt @@ -49,7 +49,7 @@ installed, you can subscribe to this channel by once executing

      [% b = pkg.build %] - + [% END %] - + [% IF !hideJobName %] [% END; IF linkToAll %] - + [% END; END; @@ -176,7 +176,7 @@ BLOCK renderBuildList; END; -BLOCK renderLink %][% title %][% END; +BLOCK renderLink %] uri) %]>[% title %][% END; BLOCK maybeLink; @@ -216,12 +216,12 @@ BLOCK editString; %] BLOCK renderFullBuildLink; - INCLUDE renderFullJobNameOfBuild build=build %] build [% build.id %][% + INCLUDE renderFullJobNameOfBuild build=build %] c.uri_for('/build' build.id)) %]>build [% build.id %][% END; BLOCK renderBuildIdLink; %] -build [% id %] + c.uri_for('/build' id)) %]>build [% id %] [% END; @@ -320,7 +320,7 @@ END; BLOCK renderShortInputValue; IF input.type == "build" || input.type == "sysbuild" %] - [% input.dependency.id %] + c.uri_for('/build' input.dependency.id)) %]>[% input.dependency.id %] [% ELSIF input.type == "string" %] "[% HTML.escape(input.value) %]" [% ELSIF input.type == "nix" || input.type == "boolean" %] @@ -338,7 +338,7 @@ BLOCK renderDiffUri; url = bi1.uri; path = url.replace(base, ''); IF url.match(base) %] - [% contents %] + m.uri.replace('_path_', path).replace('_1_', bi1.revision).replace('_2_', bi2.revision)) %]>[% contents %] [% nouri = 0; END; END; @@ -347,13 +347,13 @@ BLOCK renderDiffUri; url = res.0; branch = res.1; IF bi1.type == "hg" || bi1.type == "git" %] - c.uri_for('/api/scmdiff', { uri = url, rev1 = bi1.revision, rev2 = bi2.revision, type = bi1.type, branch = branch - })) %]">[% contents %] + })) %]>[% contents %] [% ELSE; contents; END; @@ -443,10 +443,10 @@ BLOCK renderInputDiff; %] BLOCK renderPager %] - + [% IF !jobset && !build %] [% END %] @@ -540,7 +540,7 @@ BLOCK renderEvals %] [% END; IF linkToAll %] - + [% END %]
      Build ID:[% build.id %][% HTML.escape(build.id) %]
      Status:
      System:[% build.system %][% build.system | html %]
      Release name:[% HTML.escape(build.releasename) %][% build.releasename | html %]
      Nix name:[% build.nixname %][% build.nixname | html %]
      Part of: - evaluation [% eval.id %] + evaluation [% HTML.escape(eval.id) %] [% IF nrEvals > 1 +%] (and [% nrEvals - 1 %] others)[% END %]
      Nix expression:file [% HTML.escape(eval.nixexprpath) %] in input [% HTML.escape(eval.nixexprinput) %]file [% eval.nixexprpath | html %] in input [% eval.nixexprinput | html %]
      Nix name:[% build.nixname %][% build.nixname | html %]
      Short description:
      System:[% build.system %][% build.system | html %]
      Derivation store path:[% build.drvpath %][% build.drvpath | html %]
      Output store paths:
      c.uri_for('/job' project.name jobset.name job 'metric' metric.name)) %]">[%HTML.escape(metric.name)%][%metric.value%][%metric.unit%] c.uri_for('/job' project.name jobset.name job 'metric' metric.name)) %]">[%metric.name | html%][%HTML.escape(metric.value)%][% HTML.escape(metric.unit) %]
      [% INCLUDE renderFullBuildLink build=input.build %][% input.name %][% input.build.system %][% input.name | html %][% input.build.system | html %] [% INCLUDE renderDateTime timestamp = input.build.timestamp %]
      Part of: - evaluation [% HTML.escape(eval.id) %] - [% IF nrEvals > 1 +%] (and [% nrEvals - 1 %] others)[% END %] + c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id])) %]>evaluation [% HTML.escape(eval.id) %] + [% IF nrEvals > 1 +%] (and c.uri_for('/build' build.id 'evals')) %]>[% nrEvals - 1 %] others)[% END %]
      Logfile: [% actualLog = cachedBuildStep ? c.uri_for('/build' cachedBuild.id 'nixlog' cachedBuildStep.stepnr) : c.uri_for('/build' build.id 'log') %] - pretty - raw - tail + actualLog) %]>pretty + actualLog _ "/raw") %]>raw + actualLog _ "/tail") %]>tail
      Closure size: [% mibs(build.closuresize / (1024 * 1024)) %] MiB - (history)
      Output size: [% mibs(build.size / (1024 * 1024)) %] MiB - (history)
      [% b.id %] c.uri_for('/build' b.id)) %]>[% b.id %] [% b.get_column('releasename') || b.nixname %] [% b.system %] diff --git a/src/root/common.tt b/src/root/common.tt index 7a93cc95..beee86ca 100644 --- a/src/root/common.tt +++ b/src/root/common.tt @@ -55,17 +55,17 @@ BLOCK renderRelativeDate %] [% END; BLOCK renderProjectName %] -[% project %] + c.uri_for('/project' project)) %]>[% project %] [% END; BLOCK renderJobsetName %] -[% jobset %] + c.uri_for('/jobset' project jobset)) %]>[% jobset %] [% END; BLOCK renderJobName %] -[% job %] + c.uri_for('/job' project jobset job)) %]>[% job %] [% END; @@ -140,10 +140,10 @@ BLOCK renderBuildListBody; [% IF showSchedulingInfo %] [% IF busy %]Started[% ELSE %]Queued[% END %][% build.id %] link) %]>[% build.id %] - [% IF !hideJobsetName %][%build.jobset.get_column("project")%]:[%build.jobset.get_column("name")%]:[% END %][%build.get_column("job")%] + link) %]>[% IF !hideJobsetName %][%build.jobset.get_column("project")%]:[%build.jobset.get_column("name")%]:[% END %][%build.get_column("job")%] [% IF showStepName %] [% INCLUDE renderDrvInfo step=build.buildsteps releasename=build.nixname %] [% END %] @@ -158,7 +158,7 @@ BLOCK renderBuildListBody;
      More...
      linkToAll) %]>More...
      [% eval.id %] link) %]>[% eval.id %][% INCLUDE renderFullJobsetName project=eval.jobset.project.name jobset=eval.jobset.name %]
      More...
      linkToAll) %]>More...
      @@ -548,7 +548,7 @@ BLOCK renderEvals %] BLOCK renderLogLinks %] -(log, raw, tail) +( url) %]>log, "$url/raw") %]>raw, "$url/tail") %]>tail) [% END; diff --git a/src/root/dashboard.tt b/src/root/dashboard.tt index 06b8de15..0daf3dad 100644 --- a/src/root/dashboard.tt +++ b/src/root/dashboard.tt @@ -24,7 +24,7 @@ [% INCLUDE renderFullJobName project=j.job.get_column('project') jobset=j.job.get_column('jobset') job=j.job.job %] [% FOREACH b IN j.builds %] - [% INCLUDE renderBuildStatusIcon size=16 build=b %] + c.uri_for('/build' b.id)) %]>[% INCLUDE renderBuildStatusIcon size=16 build=b %] [% END %] [% END %] diff --git a/src/root/deps.tt b/src/root/deps.tt index 6daa9725..b9f3ba7e 100644 --- a/src/root/deps.tt +++ b/src/root/deps.tt @@ -11,7 +11,7 @@ [% END %] [% IF node.buildStep %] - [% node.name %] [% + c.uri_for('/build' node.buildStep.get_column('build'))) %]>[% node.name %] [% IF buildStepLogExists(node.buildStep); INCLUDE renderLogLinks url=c.uri_for('/build' node.buildStep.get_column('build') 'nixlog' node.buildStep.stepnr); END %] diff --git a/src/root/job.tt b/src/root/job.tt index 7e475f69..acb874a3 100644 --- a/src/root/job.tt +++ b/src/root/job.tt @@ -10,8 +10,8 @@ [% IF !jobExists(jobset, job) %]
      This job is not a member of the latest evaluation of its jobset. This means it was +[% HTML.attributes(href => c.uri_for('/jobset' project.name jobset.name +'evals')) %]>latest evaluation of its jobset. This means it was removed or had an evaluation error.
      [% END %] @@ -58,7 +58,7 @@ removed or had an evaluation error. [% agg_ = aggregates.$agg %] [% END %] @@ -70,7 +70,7 @@ removed or had an evaluation error. [% FOREACH agg IN aggs %] [% r = aggregates.$agg.constituents.$j; IF r.id %] - + c.uri_for('/build' r.id)) %]> [% INCLUDE renderBuildStatusIcon size=16 build=r %] [% END %] @@ -89,8 +89,8 @@ removed or had an evaluation error. diff --git a/src/root/jobset-channels-tab.tt b/src/root/jobset-channels-tab.tt index 692f2682..3fa4ba69 100644 --- a/src/root/jobset-channels-tab.tt +++ b/src/root/jobset-channels-tab.tt @@ -14,7 +14,7 @@ [% FOREACH eval IN evalIds %] [% END %] @@ -22,9 +22,9 @@ [% FOREACH chan IN channels-%] - [% chan %] + c.uri_for('/channel/custom' project.name jobset.name chan)) %]>[% chan %] [% FOREACH eval IN evalIds %] - [% r = evals.$eval.builds.$chan; IF r.id %][% INCLUDE renderBuildStatusIcon size=16 build=r %][% END %] + [% r = evals.$eval.builds.$chan; IF r.id %] c.uri_for('/build' r.id)) %]>[% INCLUDE renderBuildStatusIcon size=16 build=r %][% END %] [% END %] [% END %] diff --git a/src/root/jobset-eval.tt b/src/root/jobset-eval.tt index 11965ce2..bd29525d 100644 --- a/src/root/jobset-eval.tt +++ b/src/root/jobset-eval.tt @@ -27,9 +27,9 @@ eval.checkouttime %]s and evaluation took [% eval.evaltime %]s.

      [% IF otherEval %]

      Comparisons are relative to [% INCLUDE renderFullJobsetName -project=otherEval.jobset.project.name jobset=otherEval.jobset.name %] evaluation [% otherEval.id %].

      +project=otherEval.jobset.project.name jobset=otherEval.jobset.name %] evaluation c.uri_for(c.controller('JobsetEval').action_for('view'), +[otherEval.id])) %]>[% otherEval.id %].

      [% END %] @@ -45,18 +45,18 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), @@ -99,7 +99,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), [% INCLUDE renderBuildListBody builds=builds.slice(0, (size > max ? max : size) - 1) hideProjectName=1 hideJobsetName=1 busy=0 %] [% IF size > max; params = c.req.params; params.full = 1 %] - ([% size - max %] more builds omitted) + c.uri_for(c.controller('JobsetEval').action_for('view'), [eval.id], params) _ tabname) %]>([% size - max %] more builds omitted) [% END %] [% INCLUDE renderBuildListFooter %] [% END %] @@ -136,7 +136,7 @@ c.uri_for(c.controller('JobsetEval').action_for('view'), [% END %] [% IF size > max; params = c.req.params; params.full = 1 %] - ([% size - max %] more jobs omitted) + ([% size - max %] more jobs omitted) [% END %] diff --git a/src/root/jobset-jobs-tab.tt b/src/root/jobset-jobs-tab.tt index 707d329e..2785e802 100644 --- a/src/root/jobset-jobs-tab.tt +++ b/src/root/jobset-jobs-tab.tt @@ -52,7 +52,7 @@ [% FOREACH eval IN evalIds %] [% END %] @@ -62,7 +62,7 @@ [% INCLUDE renderJobName project=project.name jobset=jobset.name job=j %] [% FOREACH eval IN evalIds %] - [% r = evals.$eval.builds.$j; IF r.id %][% INCLUDE renderBuildStatusIcon size=16 build=r %][% END %] + [% r = evals.$eval.builds.$j; IF r.id %] c.uri_for('/build' r.id)) %]>[% INCLUDE renderBuildStatusIcon size=16 build=r %][% END %] [% END %] [% END %] diff --git a/src/root/jobset.tt b/src/root/jobset.tt index 3e594756..1e570cf3 100644 --- a/src/root/jobset.tt +++ b/src/root/jobset.tt @@ -188,7 +188,7 @@ diff --git a/src/root/layout.tt b/src/root/layout.tt index b520b455..37f4cb7f 100644 --- a/src/root/layout.tt +++ b/src/root/layout.tt @@ -24,7 +24,7 @@