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/
junit.xml
result

View file

@ -16,14 +16,20 @@ args: let
} (builtins.readFile ./VERSION);
mkCI = config:
(evalModules {
modules = [
cilib.modules.nixCiSubmodule
{
inherit config;
}
];
}).config;
let
result = (evalModules {
modules = [
cilib.modules.nixCiSubmodule
{
inherit 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
cilib

View file

@ -8,5 +8,5 @@
mkJobRun = import ./jobRun.nix {inherit lib pkgs helpers;};
mkJobPatched = import ./jobPatched.nix {inherit lib helpers;};
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,
pkgs,
cilib,
}: rec {
inherit
(import ./root.nix {
inherit lib pipelineSubmodule soonixSubmodule;
inherit lib pkgs pipelineSubmodule soonixSubmodule;
})
configSubmodule
nixCiSubmodule
;
inherit
(import ./pipeline.nix {
inherit lib cilib jobSubmodule;
inherit lib pkgs cilib jobSubmodule;
})
pipelineConfigSubmodule
pipelineSubmodule
;
inherit
(import ./job.nix {
inherit lib cilib;
inherit lib pkgs cilib;
})
jobConfigSubmodule
jobSubmodule

View file

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

View file

@ -2,9 +2,10 @@
lib,
cilib,
jobSubmodule,
pkgs,
...
}: 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;
pipelineConfigSubmodule = {rootConfig, ...}: {
@ -153,7 +154,7 @@
in {
_file = ./pipeline.nix;
# 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"
"artifacts"
"before_script"
@ -211,16 +212,20 @@
// gitlabOptions;
config = let
attrsToKeep = builtins.attrNames gitlabOptions;
jobs = config.jobs;
# Propagate job assertions up to pipeline level
jobAssertions = lib.concatLists (map (job: job.assertions) (attrValues jobs));
in {
assertions = jobAssertions;
finalConfig =
(filterUnset (filterAttrs (n: _v: builtins.elem n attrsToKeep) config))
// mapAttrs (_name: value: value.finalConfig) config.jobs;
// mapAttrs (_name: value: value.finalConfig) jobs;
packages =
{
"gitlab-ci:pipeline:${name}" = toYaml "gitlab-ci-config.json" 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 {

View file

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

View file

@ -98,6 +98,59 @@
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;
}
];
};
}