fix: pipelines can have either trigger or script

They canno thave both, so here I add assertions support and use them to avoid this case.
This commit is contained in:
Jairo Llopis 2026-05-13 08:53:15 +01:00
parent 097f775cff
commit 70dc878112
No known key found for this signature in database
GPG key ID: B24A1D10508180D8
8 changed files with 107 additions and 19 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
.ren/ .ren/
junit.xml
result result

View file

@ -16,7 +16,8 @@ args: let
} (builtins.readFile ./VERSION); } (builtins.readFile ./VERSION);
mkCI = config: mkCI = config:
(evalModules { let
result = (evalModules {
modules = [ modules = [
cilib.modules.nixCiSubmodule cilib.modules.nixCiSubmodule
{ {
@ -24,6 +25,11 @@ args: let
} }
]; ];
}).config; }).config;
# Check all assertions and warnings using NixOS infrastructure,
# then return the result with internal module options removed
checked = lib.asserts.checkAssertWarn result.assertions result.warnings result;
in
builtins.removeAttrs checked ["assertions" "warnings" "config"];
}; };
in in
cilib cilib

View file

@ -8,5 +8,5 @@
mkJobRun = import ./jobRun.nix {inherit lib pkgs helpers;}; mkJobRun = import ./jobRun.nix {inherit lib pkgs helpers;};
mkJobPatched = import ./jobPatched.nix {inherit lib helpers;}; mkJobPatched = import ./jobPatched.nix {inherit lib helpers;};
mkPipeline = import ./pipeline.nix {inherit lib helpers mkJobDeps mkJobRun mkJobPatched;}; mkPipeline = import ./pipeline.nix {inherit lib helpers mkJobDeps mkJobRun mkJobPatched;};
modules = import ./modules {inherit lib cilib;}; modules = import ./modules {inherit lib pkgs cilib;};
} }

View file

@ -1,24 +1,25 @@
{ {
lib, lib,
pkgs,
cilib, cilib,
}: rec { }: rec {
inherit inherit
(import ./root.nix { (import ./root.nix {
inherit lib pipelineSubmodule soonixSubmodule; inherit lib pkgs pipelineSubmodule soonixSubmodule;
}) })
configSubmodule configSubmodule
nixCiSubmodule nixCiSubmodule
; ;
inherit inherit
(import ./pipeline.nix { (import ./pipeline.nix {
inherit lib cilib jobSubmodule; inherit lib pkgs cilib jobSubmodule;
}) })
pipelineConfigSubmodule pipelineConfigSubmodule
pipelineSubmodule pipelineSubmodule
; ;
inherit inherit
(import ./job.nix { (import ./job.nix {
inherit lib cilib; inherit lib pkgs cilib;
}) })
jobConfigSubmodule jobConfigSubmodule
jobSubmodule jobSubmodule

View file

@ -1,10 +1,11 @@
{ {
lib, lib,
cilib, cilib,
pkgs,
... ...
}: let }: let
inherit (lib) mkOption types filterAttrs; inherit (lib) mkOption types filterAttrs;
inherit (cilib.helpers) filterUnset mkUnsetOption eitherWithSubOptions; inherit (cilib.helpers) filterUnset mkUnsetOption eitherWithSubOptions isUnset;
in rec { in rec {
jobConfigSubmodule = {pipelineConfig, ...}: { jobConfigSubmodule = {pipelineConfig, ...}: {
options = { options = {
@ -511,11 +512,13 @@ in rec {
[Docs](https://docs.gitlab.com/ci/yaml/#rules) [Docs](https://docs.gitlab.com/ci/yaml/#rules)
''; '';
}; };
script = mkOption { script = mkUnsetOption {
type = types.listOf types.str; type = types.listOf types.str;
description = '' description = ''
Shell script that is executed by a runner. Shell script that is executed by a runner.
A job can have either `script` or `trigger` defined, but not both.
[Docs](https://docs.gitlab.com/ci/yaml/#script) [Docs](https://docs.gitlab.com/ci/yaml/#script)
''; '';
}; };
@ -567,6 +570,8 @@ in rec {
description = '' description = ''
Defines a downstream pipeline trigger. Defines a downstream pipeline trigger.
A job can have either `script` or `trigger` defined, but not both.
[Docs](https://docs.gitlab.com/ci/yaml/#trigger) [Docs](https://docs.gitlab.com/ci/yaml/#trigger)
''; '';
}; };
@ -589,6 +594,7 @@ in rec {
}; };
}; };
in { in {
imports = [(pkgs.path + /nixos/modules/misc/assertions.nix)];
options = options =
{ {
nix = mkOption { nix = mkOption {
@ -636,9 +642,19 @@ in rec {
} }
// gitlabOptions; // gitlabOptions;
config = let config = let
attrsToKeep = builtins.attrNames gitlabOptions; attrsToKeep = builtins.attrNames gitlabOptions;
job = filterUnset (filterAttrs (n: _v: builtins.elem n attrsToKeep) config); job = filterUnset (filterAttrs (n: _v: builtins.elem n attrsToKeep) config);
jobHasScript = builtins.hasAttr "script" job;
jobHasTrigger = builtins.hasAttr "trigger" job;
in { in {
assertions = [
{
assertion = !(jobHasScript && jobHasTrigger);
message = "Job '${name}' in pipeline '${pipelineName}' cannot have both 'script' and 'trigger' defined.";
}
];
finalConfig = cilib.mkJobPatched { finalConfig = cilib.mkJobPatched {
key = name; key = name;
nixConfig = config.nix; nixConfig = config.nix;

View file

@ -2,9 +2,10 @@
lib, lib,
cilib, cilib,
jobSubmodule, jobSubmodule,
pkgs,
... ...
}: let }: let
inherit (lib) mkOption types filterAttrs mergeAttrsList mapAttrs mkRenamedOptionModule literalExpression; inherit (lib) mkOption types filterAttrs mergeAttrsList mapAttrs mkRenamedOptionModule literalExpression attrValues;
inherit (cilib.helpers) filterUnset unset mkUnsetOption toYaml toYamlPretty eitherWithSubOptions; inherit (cilib.helpers) filterUnset unset mkUnsetOption toYaml toYamlPretty eitherWithSubOptions;
pipelineConfigSubmodule = {rootConfig, ...}: { pipelineConfigSubmodule = {rootConfig, ...}: {
@ -153,7 +154,7 @@
in { in {
_file = ./pipeline.nix; _file = ./pipeline.nix;
# from https://docs.gitlab.com/ci/yaml/#default # from https://docs.gitlab.com/ci/yaml/#default
imports = builtins.map (val: mkRenamedOptionModule [val] ["default" val]) [ imports = [(pkgs.path + /nixos/modules/misc/assertions.nix)] ++ builtins.map (val: mkRenamedOptionModule [val] ["default" val]) [
"after_script" "after_script"
"artifacts" "artifacts"
"before_script" "before_script"
@ -211,16 +212,20 @@
// gitlabOptions; // gitlabOptions;
config = let config = let
attrsToKeep = builtins.attrNames gitlabOptions; attrsToKeep = builtins.attrNames gitlabOptions;
jobs = config.jobs;
# Propagate job assertions up to pipeline level
jobAssertions = lib.concatLists (map (job: job.assertions) (attrValues jobs));
in { in {
assertions = jobAssertions;
finalConfig = finalConfig =
(filterUnset (filterAttrs (n: _v: builtins.elem n attrsToKeep) config)) (filterUnset (filterAttrs (n: _v: builtins.elem n attrsToKeep) config))
// mapAttrs (_name: value: value.finalConfig) config.jobs; // mapAttrs (_name: value: value.finalConfig) jobs;
packages = packages =
{ {
"gitlab-ci:pipeline:${name}" = toYaml "gitlab-ci-config.json" config.finalConfig; "gitlab-ci:pipeline:${name}" = toYaml "gitlab-ci-config.json" config.finalConfig;
"gitlab-ci:pipeline:${name}:pretty" = toYamlPretty "gitlab-ci-config.yml" config.finalConfig; "gitlab-ci:pipeline:${name}:pretty" = toYamlPretty "gitlab-ci-config.yml" config.finalConfig;
} }
// mergeAttrsList (map (job: job.packages) (builtins.attrValues config.jobs)); // mergeAttrsList (map (job: job.packages) (builtins.attrValues jobs));
}; };
}; };
in { in {

View file

@ -2,9 +2,10 @@
lib, lib,
soonixSubmodule, soonixSubmodule,
pipelineSubmodule, pipelineSubmodule,
pkgs,
... ...
}: let }: let
inherit (lib) mkOption types foldr; inherit (lib) mkOption types foldr mapAttrsToList concatLists;
in rec { in rec {
configSubmodule = { configSubmodule = {
options = { options = {
@ -28,6 +29,7 @@ in rec {
}; };
nixCiSubmodule = {config, ...}: { nixCiSubmodule = {config, ...}: {
imports = [(pkgs.path + /nixos/modules/misc/assertions.nix)];
options = { options = {
config = mkOption { config = mkOption {
description = '' description = ''
@ -65,6 +67,10 @@ in rec {
}; };
}; };
config = { config = {
# Propagate pipeline assertions to the root level
assertions = concatLists (
mapAttrsToList (_name: pipeline: pipeline.assertions) config.pipelines
);
packages = foldr (pipeline: acc: acc // pipeline) {} ( packages = foldr (pipeline: acc: acc // pipeline) {} (
map (pipeline: pipeline.packages) (builtins.attrValues config.pipelines) map (pipeline: pipeline.packages) (builtins.attrValues config.pipelines)
); );

View file

@ -98,6 +98,59 @@
assert_file_contains ${package} 'export EXAMPLE="/nix/store/.*-hello-.*"' assert_file_contains ${package} 'export EXAMPLE="/nix/store/.*-hello-.*"'
''; '';
} }
{
name = "trigger job without script is valid";
expected = {
trigger = "some/project";
stage = "test";
image = "$NIX_CI_IMAGE";
};
actual =
(cilib.mkCI {
pipelines."test" = {
stages = ["test"];
jobs."trigger_job" = {
stage = "test";
trigger = "some/project";
nix.enable = false;
};
};
}).pipelines."test".finalConfig."trigger_job";
}
{
name = "script job without trigger is valid";
expected = {
stage = "test";
image = "$NIX_CI_IMAGE";
before_script = ["source setup_nix_ci \"gitlab-ci:pipeline:test:job-deps:test\""];
script = ["echo hello"];
after_script = ["finalize_nix_ci"];
};
actual =
(cilib.mkCI {
pipelines."test" = {
stages = ["test"];
jobs."test" = {
stage = "test";
script = ["echo hello"];
};
};
}).pipelines."test".finalConfig."test";
}
{
name = "job cannot have both script and trigger";
expected = false;
actual = (builtins.tryEval (cilib.mkCI {
pipelines."test" = {
stages = ["test"];
jobs."bad" = {
stage = "test";
script = ["echo bad"];
trigger = "some/project";
};
};
})).success;
}
]; ];
}; };
} }