mirror of
https://gitlab.com/TECHNOFAB/nix-gitlab-ci.git
synced 2025-12-11 01:30:08 +01:00
feat: initial v3 rewrite
This commit is contained in:
commit
0952ab4145
32 changed files with 1457 additions and 0 deletions
2
.envrc
Normal file
2
.envrc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
source $(fetchurl https://gitlab.com/rensa-nix/direnv/-/raw/v0.3.0/direnvrc "sha256-u7+KEz684NnIZ+Vh5x5qLrt8rKdnUNexewBoeTcEVHQ=")
|
||||
use ren //repo/devShells/default
|
||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
.ren/
|
||||
result
|
||||
63
flake.lock
generated
Normal file
63
flake.lock
generated
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1756542300,
|
||||
"narHash": "sha256-tlOn88coG5fzdyqz6R93SQL5Gpq+m/DsWpekNFhqPQk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d7600c775f877cd87b4f5a831c28aa94137377aa",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1754184128,
|
||||
"narHash": "sha256-AjhoyBL4eSyXf01Bmc6DiuaMrJRNdWopmdnMY0Pa/M0=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "02e72200e6d56494f4a7c0da8118760736e41b60",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"ren": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1756370106,
|
||||
"narHash": "sha256-l84ojcHuQWBwn4BRxQsMMfQpcq/Az/sHh/hSqFgVtyg=",
|
||||
"owner": "rensa-nix",
|
||||
"repo": "core",
|
||||
"rev": "9c1a29fa9ba7cbbb78b9e47eb8afbcd29303a3b4",
|
||||
"type": "gitlab"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "rensa-nix",
|
||||
"repo": "core",
|
||||
"type": "gitlab"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs",
|
||||
"ren": "ren"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
37
flake.nix
Normal file
37
flake.nix
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
ren.url = "gitlab:rensa-nix/core?dir=lib";
|
||||
};
|
||||
|
||||
outputs = {
|
||||
self,
|
||||
ren,
|
||||
...
|
||||
} @ inputs:
|
||||
ren.buildWith
|
||||
{
|
||||
inherit inputs;
|
||||
cellsFrom = ./nix;
|
||||
transformInputs = system: i:
|
||||
i
|
||||
// {
|
||||
pkgs = import i.nixpkgs {inherit system;};
|
||||
};
|
||||
cellBlocks = with ren.blocks; [
|
||||
(simple "devShells")
|
||||
(simple "pkgs")
|
||||
# (simple "tests")
|
||||
# (simple "docs")
|
||||
(simple "ci")
|
||||
];
|
||||
}
|
||||
{
|
||||
packages = ren.select self [
|
||||
# ["repo" "tests"]
|
||||
# ["repo" "docs"]
|
||||
["repo" "ci" "packages"]
|
||||
["packages" "pkgs"]
|
||||
];
|
||||
};
|
||||
}
|
||||
77
flakeModuleTest/flake.lock
generated
Normal file
77
flakeModuleTest/flake.lock
generated
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"nodes": {
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1754487366,
|
||||
"narHash": "sha256-pHYj8gUBapuUzKV/kN/tR3Zvqc7o6gdFB9XKXIp1SQ8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "af66ad14b28a127c5c0f3bbb298218fc63528a18",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1756636162,
|
||||
"narHash": "sha256-mBecwgUTWRgClJYqcF+y4O1bY8PQHqeDpB+zsAn+/zA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "37ff64b7108517f8b6ba5705ee5085eac636a249",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1753579242,
|
||||
"narHash": "sha256-zvaMGVn14/Zz8hnp4VWT9xVnhc8vuL3TStRqwk22biA=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "0f36c44e01a6129be94e3ade315a5883f0228a6e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"systems": "systems"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1689347949,
|
||||
"narHash": "sha256-12tWmuL2zgBgZkdoB6qXZsgJEH9LR3oUgpaQq2RbI80=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default-linux",
|
||||
"rev": "31732fcf5e8fea42e59c2488ad31a0e651500f68",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default-linux",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
43
flakeModuleTest/flake.nix
Normal file
43
flakeModuleTest/flake.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
outputs = {
|
||||
flake-parts,
|
||||
systems,
|
||||
...
|
||||
} @ inputs:
|
||||
flake-parts.lib.mkFlake {inherit inputs;} {
|
||||
imports = [
|
||||
../lib/flakeModule.nix
|
||||
];
|
||||
systems = import systems;
|
||||
flake = {};
|
||||
perSystem = {
|
||||
...
|
||||
}: {
|
||||
ci = {
|
||||
config = {
|
||||
# true is already default, just for testing
|
||||
nixJobsByDefault = true;
|
||||
};
|
||||
pipelines = {
|
||||
"default" = {
|
||||
stages = ["example"];
|
||||
jobs."example" = {
|
||||
stage = "example";
|
||||
script = ["echo hello world"];
|
||||
};
|
||||
};
|
||||
"test".jobs."example" = {
|
||||
stage = ".pre";
|
||||
script = ["echo hello world"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
systems.url = "github:nix-systems/default-linux";
|
||||
};
|
||||
}
|
||||
1
lib/VERSION
Normal file
1
lib/VERSION
Normal file
|
|
@ -0,0 +1 @@
|
|||
3.0.0-alpha.1
|
||||
26
lib/default.nix
Normal file
26
lib/default.nix
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
args: let
|
||||
# allow passing just pkgs aswell for convenience
|
||||
lib = args.lib or args.pkgs.lib;
|
||||
# makes it optional to pass if it's not explicitly needed
|
||||
pkgs = args.pkgs or (throw "[nix-gitlab-ci] pkgs argument was used but not set, please pass it");
|
||||
inherit (lib) evalModules;
|
||||
|
||||
impl = import ./impl {inherit lib pkgs cilib;};
|
||||
|
||||
cilib = {
|
||||
inherit (impl) helpers modules mkPipeline mkJobRun mkJobDeps mkJobPatched;
|
||||
utils = import ./utils.nix {inherit pkgs;};
|
||||
version = builtins.readFile ./VERSION;
|
||||
|
||||
mkCI = config:
|
||||
(evalModules {
|
||||
modules = [
|
||||
cilib.modules.nixCiSubmodule
|
||||
{
|
||||
inherit config;
|
||||
}
|
||||
];
|
||||
}).config;
|
||||
};
|
||||
in
|
||||
cilib
|
||||
6
lib/flake.nix
Normal file
6
lib/flake.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
outputs = _i: {
|
||||
lib = import ./.;
|
||||
flakeModule = ./flakeModule.nix;
|
||||
};
|
||||
}
|
||||
24
lib/flakeModule.nix
Normal file
24
lib/flakeModule.nix
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
flake-parts-lib,
|
||||
lib,
|
||||
...
|
||||
}: {
|
||||
options.perSystem = flake-parts-lib.mkPerSystemOption (
|
||||
{
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
cilib = import ./. {inherit lib pkgs;};
|
||||
inherit (lib) types mkOption;
|
||||
in {
|
||||
options = {
|
||||
ci = mkOption {
|
||||
type = types.submodule cilib.modules.nixCiSubmodule;
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
config.legacyPackages = config.ci.packages;
|
||||
}
|
||||
);
|
||||
}
|
||||
12
lib/impl/default.nix
Normal file
12
lib/impl/default.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
cilib,
|
||||
}: rec {
|
||||
helpers = import ./helpers.nix {inherit lib pkgs;};
|
||||
mkJobDeps = import ./jobDeps.nix {inherit lib helpers;};
|
||||
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;};
|
||||
}
|
||||
90
lib/impl/helpers.nix
Normal file
90
lib/impl/helpers.nix
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
{
|
||||
pkgs,
|
||||
lib,
|
||||
} @ args: let
|
||||
inherit (lib) types isAttrs filterAttrs mapAttrs mkOption mkOptionType isType;
|
||||
in rec {
|
||||
prepend = key: arr: job: {
|
||||
${key} = arr ++ (job.${key} or []);
|
||||
};
|
||||
append = key: arr: job: {
|
||||
${key} = (job.${key} or []) ++ arr;
|
||||
};
|
||||
prependToBeforeScript = prepend "before_script";
|
||||
appendToAfterScript = append "after_script";
|
||||
|
||||
# json is also valid yaml and this removes dependency on jq and/or remarshal
|
||||
# (used in pkgs.formats.json and pkgs.formats.yaml respectively)
|
||||
toYaml = name: value: builtins.toFile name (builtins.toJSON value);
|
||||
toYamlPretty = (pkgs.formats.yaml {}).generate;
|
||||
|
||||
customMapAttrs = cb: set: builtins.listToAttrs (builtins.map (key: cb key (builtins.getAttr key set)) (builtins.attrNames set));
|
||||
|
||||
filterAttrsRec = pred: v:
|
||||
if isAttrs v
|
||||
then filterAttrs pred (mapAttrs (_path: filterAttrsRec pred) v)
|
||||
else v;
|
||||
|
||||
# filter job's variables to either only those containing store paths
|
||||
# or those that do not
|
||||
filterJobVariables = nix: job:
|
||||
lib.concatMapAttrs (
|
||||
name: value:
|
||||
lib.optionalAttrs ((lib.hasInfix "/nix/store/" value) == nix) {
|
||||
${name} = value;
|
||||
}
|
||||
)
|
||||
(job.variables or {});
|
||||
|
||||
deepMerge = lhs: rhs:
|
||||
lhs
|
||||
// rhs
|
||||
// (builtins.mapAttrs (
|
||||
rName: rValue: let
|
||||
lValue = lhs.${rName} or null;
|
||||
in
|
||||
if builtins.isAttrs lValue && builtins.isAttrs rValue
|
||||
then deepMerge lValue rValue
|
||||
else if builtins.isList lValue && builtins.isList rValue
|
||||
then lValue ++ rValue
|
||||
else rValue
|
||||
)
|
||||
rhs);
|
||||
|
||||
unsetType = mkOptionType {
|
||||
name = "unset";
|
||||
description = "unset";
|
||||
descriptionClass = "noun";
|
||||
check = _value: true;
|
||||
};
|
||||
unset = {
|
||||
_type = "unset";
|
||||
};
|
||||
isUnset = isType "unset";
|
||||
unsetOr = types.either unsetType;
|
||||
mkUnsetOption = opts:
|
||||
mkOption (opts
|
||||
// {
|
||||
type = unsetOr opts.type;
|
||||
default = opts.default or unset;
|
||||
});
|
||||
|
||||
filterUnset = value:
|
||||
if builtins.isAttrs value && !builtins.hasAttr "_type" value
|
||||
then let
|
||||
filteredAttrs = builtins.mapAttrs (_n: filterUnset) value;
|
||||
in
|
||||
filterAttrs (_name: value: (!isUnset value)) filteredAttrs
|
||||
else if builtins.isList value
|
||||
then builtins.filter (elem: !isUnset elem) (map filterUnset value)
|
||||
else value;
|
||||
|
||||
# args.pkgs so "pkgs" does not need to be passed all the time
|
||||
stdenvMinimal = args.pkgs.stdenvNoCC.override {
|
||||
cc = null;
|
||||
preHook = "";
|
||||
allowedRequisites = null;
|
||||
initialPath = with args.pkgs; [coreutils findutils];
|
||||
extraNativeBuildInputs = [];
|
||||
};
|
||||
}
|
||||
35
lib/impl/jobDeps.nix
Normal file
35
lib/impl/jobDeps.nix
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
lib,
|
||||
helpers,
|
||||
}: let
|
||||
inherit (lib) concatLines mapAttrsToList makeBinPath;
|
||||
inherit (helpers) filterJobVariables stdenvMinimal;
|
||||
in
|
||||
{
|
||||
key,
|
||||
job,
|
||||
nixConfig,
|
||||
}: let
|
||||
variablesWithStorePaths = filterJobVariables true job;
|
||||
variableExports = concatLines (
|
||||
mapAttrsToList (name: value: "export ${name}=\"${value}\"") variablesWithStorePaths
|
||||
);
|
||||
script = ''
|
||||
export PATH="${makeBinPath (nixConfig.deps or [])}:$PATH";
|
||||
# variables containing nix derivations:
|
||||
${variableExports}
|
||||
'';
|
||||
in
|
||||
stdenvMinimal.mkDerivation {
|
||||
name = "gitlab-ci-job-deps-${key}";
|
||||
dontUnpack = true;
|
||||
installPhase =
|
||||
# sh
|
||||
''
|
||||
echo '${script}' > $out
|
||||
chmod +x $out
|
||||
'';
|
||||
passthru = {
|
||||
inherit script;
|
||||
};
|
||||
}
|
||||
43
lib/impl/jobPatched.nix
Normal file
43
lib/impl/jobPatched.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
lib,
|
||||
helpers,
|
||||
}: let
|
||||
inherit (lib) toList optionalAttrs optional;
|
||||
inherit (helpers) prependToBeforeScript appendToAfterScript filterJobVariables;
|
||||
in
|
||||
{
|
||||
key,
|
||||
job,
|
||||
pipelineName,
|
||||
nixConfig,
|
||||
}:
|
||||
job
|
||||
// (optionalAttrs nixConfig.enable (
|
||||
(prependToBeforeScript ["source setup_nix_ci \"gitlab-ci:pipeline:${pipelineName}:job-deps:${key}\""] job)
|
||||
// (appendToAfterScript ["finalize_nix_ci"] job)
|
||||
))
|
||||
// optionalAttrs nixConfig.enable (
|
||||
(let
|
||||
variables =
|
||||
(filterJobVariables false job)
|
||||
// optionalAttrs nixConfig.enableRunnerCache {
|
||||
NIX_CI_CACHE_STRATEGY = "runner";
|
||||
};
|
||||
in
|
||||
# filter empty variables
|
||||
optionalAttrs (variables != {}) {
|
||||
inherit variables;
|
||||
})
|
||||
// (let
|
||||
cache =
|
||||
(toList (job.cache or []))
|
||||
++ (optional nixConfig.enableRunnerCache {
|
||||
key = nixConfig.runnerCacheKey;
|
||||
paths = [".nix-cache/"];
|
||||
});
|
||||
in
|
||||
# filter empty cache
|
||||
optionalAttrs (cache != []) {
|
||||
inherit cache;
|
||||
})
|
||||
)
|
||||
47
lib/impl/jobRun.nix
Normal file
47
lib/impl/jobRun.nix
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
lib,
|
||||
pkgs,
|
||||
helpers,
|
||||
}: let
|
||||
inherit (lib) concatLines mapAttrsToList getExe;
|
||||
inherit (helpers) filterJobVariables;
|
||||
in
|
||||
{
|
||||
key,
|
||||
job,
|
||||
jobDeps,
|
||||
}: let
|
||||
variablesWithoutStorePaths = filterJobVariables false job;
|
||||
variableExports = concatLines (
|
||||
mapAttrsToList (name: value: "export ${name}=\"${value}\"") variablesWithoutStorePaths
|
||||
);
|
||||
sandboxHelper = pkgs.writeShellScriptBin "gitlab-ci-job-sandbox-helper" (builtins.readFile ./sandbox_helper.sh);
|
||||
actualJobScript = pkgs.writeShellScript "gitlab-ci-job:${key}:raw" ''
|
||||
# set up deps and environment variables containing store paths
|
||||
. ${jobDeps}
|
||||
# normal environment variables
|
||||
${variableExports}
|
||||
# run before_script, script and after_script
|
||||
echo -e "\e[32mRunning before_script...\e[0m"
|
||||
set -x
|
||||
${concatLines (job.before_script or [])}
|
||||
{ set +x; } 2>/dev/null
|
||||
echo -e "\e[32mRunning script...\e[0m"
|
||||
set -x
|
||||
${concatLines job.script}
|
||||
{ set +x; } 2>/dev/null
|
||||
echo -e "\e[32mRunning after_script...\e[0m"
|
||||
set -x
|
||||
${concatLines (job.after_script or [])}
|
||||
{ set +x; } 2>/dev/null
|
||||
'';
|
||||
in
|
||||
# this way the sandbox helper just needs to be built once
|
||||
pkgs.writeShellScriptBin "gitlab-ci-job:${key}" ''
|
||||
exec ${getExe sandboxHelper} ${actualJobScript} $@
|
||||
''
|
||||
// {
|
||||
passthru = {
|
||||
inherit jobDeps actualJobScript;
|
||||
};
|
||||
}
|
||||
32
lib/impl/modules/default.nix
Normal file
32
lib/impl/modules/default.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
lib,
|
||||
cilib,
|
||||
}: rec {
|
||||
inherit
|
||||
(import ./root.nix {
|
||||
inherit lib pipelineSubmodule soonixSubmodule;
|
||||
})
|
||||
configSubmodule
|
||||
nixCiSubmodule
|
||||
;
|
||||
inherit
|
||||
(import ./pipeline.nix {
|
||||
inherit lib cilib jobSubmodule;
|
||||
})
|
||||
pipelineConfigSubmodule
|
||||
pipelineSubmodule
|
||||
;
|
||||
inherit
|
||||
(import ./job.nix {
|
||||
inherit lib cilib;
|
||||
})
|
||||
jobConfigSubmodule
|
||||
jobSubmodule
|
||||
;
|
||||
inherit
|
||||
(import ./soonix.nix {
|
||||
inherit lib cilib;
|
||||
})
|
||||
soonixSubmodule
|
||||
;
|
||||
}
|
||||
131
lib/impl/modules/job.nix
Normal file
131
lib/impl/modules/job.nix
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
{
|
||||
lib,
|
||||
cilib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkOption types filterAttrs;
|
||||
inherit (cilib.helpers) filterUnset mkUnsetOption;
|
||||
in rec {
|
||||
jobConfigSubmodule = {pipelineConfig, ...}: {
|
||||
options = {
|
||||
enable = mkOption {
|
||||
description = "Transform this job to a nix-configured one";
|
||||
type = types.bool;
|
||||
default = pipelineConfig.nixJobsByDefault;
|
||||
};
|
||||
deps = mkOption {
|
||||
description = "Dependencies to inject into the job before running it";
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
};
|
||||
enableRunnerCache = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Cache this job using the GitLab Runner cache.
|
||||
Warning: useful for tiny jobs, but most of the time it just takes an eternity.
|
||||
'';
|
||||
};
|
||||
runnerCacheKey = mkOption {
|
||||
type = types.str;
|
||||
default = "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG";
|
||||
description = "Cache key to use for the runner nix cache. Requires enableRunnerCache = true";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
jobSubmodule = {
|
||||
name,
|
||||
config,
|
||||
pipelineName,
|
||||
pipelineConfig,
|
||||
...
|
||||
}: let
|
||||
#
|
||||
# GITLAB OPTIONS
|
||||
#
|
||||
gitlabOptions = {
|
||||
stage = mkOption {
|
||||
type = types.str;
|
||||
};
|
||||
image = mkUnsetOption {
|
||||
type = types.str;
|
||||
};
|
||||
variables = mkUnsetOption {
|
||||
type = types.attrsOf types.str;
|
||||
};
|
||||
before_script = mkUnsetOption {
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
script = mkOption {
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
after_script = mkUnsetOption {
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
artifacts = mkUnsetOption {
|
||||
type = types.attrs; # TODO: more granular
|
||||
description = '''';
|
||||
};
|
||||
rules = mkUnsetOption {
|
||||
type = types.listOf types.attrs;
|
||||
};
|
||||
allow_failure = mkUnsetOption {
|
||||
type = types.bool;
|
||||
};
|
||||
};
|
||||
in {
|
||||
options =
|
||||
{
|
||||
nix = mkOption {
|
||||
description = "Nix-GitLab-CI config options for this job";
|
||||
type = types.submoduleWith {
|
||||
modules = [jobConfigSubmodule];
|
||||
specialArgs.pipelineConfig = pipelineConfig;
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
finalConfig = mkOption {
|
||||
internal = true;
|
||||
type = types.attrs;
|
||||
};
|
||||
depsDrv = mkOption {
|
||||
internal = true;
|
||||
type = types.package;
|
||||
};
|
||||
runnerDrv = mkOption {
|
||||
internal = true;
|
||||
type = types.package;
|
||||
};
|
||||
packages = mkOption {
|
||||
internal = true;
|
||||
type = types.attrsOf types.package;
|
||||
};
|
||||
}
|
||||
// gitlabOptions;
|
||||
config = let
|
||||
attrsToKeep = builtins.attrNames gitlabOptions;
|
||||
in {
|
||||
finalConfig = cilib.mkJobPatched {
|
||||
key = name;
|
||||
job = filterUnset (filterAttrs (n: _v: builtins.elem n attrsToKeep) config);
|
||||
nixConfig = config.nix;
|
||||
inherit pipelineName;
|
||||
};
|
||||
depsDrv = cilib.mkJobDeps {
|
||||
key = name;
|
||||
job = config.finalConfig;
|
||||
nixConfig = config.nix;
|
||||
};
|
||||
runnerDrv = cilib.mkJobRun {
|
||||
key = name;
|
||||
job = config.finalConfig;
|
||||
jobDeps = config.depsDrv;
|
||||
};
|
||||
packages = {
|
||||
"gitlab-ci:pipeline:${pipelineName}:job-deps:${name}" = config.depsDrv;
|
||||
"gitlab-ci:pipeline:${pipelineName}:job:${name}" = config.runnerDrv;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
101
lib/impl/modules/pipeline.nix
Normal file
101
lib/impl/modules/pipeline.nix
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
{
|
||||
lib,
|
||||
cilib,
|
||||
jobSubmodule,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkOption types filterAttrs mergeAttrsList pipe mapAttrs;
|
||||
inherit (cilib.helpers) filterUnset mkUnsetOption toYaml toYamlPretty;
|
||||
|
||||
pipelineConfigSubmodule = {rootConfig, ...}: {
|
||||
options = {
|
||||
nixJobsByDefault = mkOption {
|
||||
description = ''
|
||||
Whether to transform all jobs to nix-configured jobs by default.
|
||||
If false, you need to set `nix.enable` for each job you want to be transformed.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = rootConfig.nixJobsByDefault;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
pipelineSubmodule = {
|
||||
name,
|
||||
config,
|
||||
rootConfig,
|
||||
...
|
||||
}: let
|
||||
#
|
||||
# GITLAB OPTIONS
|
||||
#
|
||||
gitlabOptions = {
|
||||
stages = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
# .pre and .post always exist
|
||||
apply = val: [".pre"] ++ val ++ [".post"];
|
||||
};
|
||||
variables = mkUnsetOption {
|
||||
type = types.attrsOf types.str;
|
||||
description = '''';
|
||||
};
|
||||
};
|
||||
in {
|
||||
_file = ./pipeline.nix;
|
||||
options =
|
||||
{
|
||||
nix = mkOption {
|
||||
description = "Nix-CI config options for this pipeline";
|
||||
type = types.submoduleWith {
|
||||
modules = [pipelineConfigSubmodule];
|
||||
specialArgs.rootConfig = rootConfig;
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
finalConfig = mkOption {
|
||||
description = "Final config of the pipeline";
|
||||
internal = true;
|
||||
type = types.attrs;
|
||||
};
|
||||
packages = mkOption {
|
||||
description = "Final packages for use in CI";
|
||||
internal = true;
|
||||
type = types.attrsOf types.package;
|
||||
};
|
||||
# jobs are nested to make distinguishing them from other keys in the ci config easier
|
||||
jobs = mkOption {
|
||||
description = "Jobs for this pipeline";
|
||||
type = types.attrsOf (types.submoduleWith {
|
||||
modules = [jobSubmodule];
|
||||
specialArgs = {
|
||||
pipelineName = name;
|
||||
pipelineConfig = config.nix;
|
||||
};
|
||||
});
|
||||
default = {};
|
||||
};
|
||||
}
|
||||
// gitlabOptions;
|
||||
config = let
|
||||
attrsToKeep = builtins.attrNames gitlabOptions;
|
||||
in {
|
||||
finalConfig =
|
||||
(filterUnset (filterAttrs (n: _v: builtins.elem n attrsToKeep) config))
|
||||
// mapAttrs (_name: value: value.finalConfig) config.jobs;
|
||||
packages =
|
||||
{
|
||||
"gitlab-ci:pipeline:${name}" = pipe config.finalConfig [
|
||||
builtins.toJSON
|
||||
builtins.unsafeDiscardOutputDependency
|
||||
builtins.unsafeDiscardStringContext
|
||||
(toYaml "gitlab-ci-config.json")
|
||||
];
|
||||
"gitlab-ci:pipeline:${name}:pretty" = toYamlPretty "gitlab-ci-config.yml" config.finalConfig;
|
||||
}
|
||||
// mergeAttrsList (map (job: job.packages) (builtins.attrValues config.jobs));
|
||||
};
|
||||
};
|
||||
in {
|
||||
inherit pipelineSubmodule pipelineConfigSubmodule;
|
||||
}
|
||||
61
lib/impl/modules/root.nix
Normal file
61
lib/impl/modules/root.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
lib,
|
||||
soonixSubmodule,
|
||||
pipelineSubmodule,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkOption types;
|
||||
in rec {
|
||||
configSubmodule = {
|
||||
options = {
|
||||
soonix = mkOption {
|
||||
description = "Configure the soonix '.gitlab-ci.yml' generation";
|
||||
type = types.submodule soonixSubmodule;
|
||||
default = {};
|
||||
};
|
||||
nixJobsByDefault = mkOption {
|
||||
description = ''
|
||||
Whether to transform all jobs to nix-configured jobs by default.
|
||||
If false, you need to set `nix.enable` for each job you want to be transformed.
|
||||
'';
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
nixCiSubmodule = {config, ...}: {
|
||||
options = {
|
||||
config = mkOption {
|
||||
description = "Configuration of Nix-GitLab-CI itself";
|
||||
type = types.submodule configSubmodule;
|
||||
default = {};
|
||||
};
|
||||
pipelines = mkOption {
|
||||
description = "Defines all pipelines";
|
||||
type = types.attrsOf (types.submoduleWith {
|
||||
modules = [pipelineSubmodule];
|
||||
specialArgs.rootConfig = config.config;
|
||||
});
|
||||
default = {};
|
||||
};
|
||||
|
||||
packages = mkOption {
|
||||
description = "Final packages for use in CI";
|
||||
internal = true;
|
||||
type = types.attrsOf types.package;
|
||||
};
|
||||
soonix = mkOption {
|
||||
description = "Soonix config for .gitlab-ci.yml";
|
||||
internal = true;
|
||||
type = types.attrs;
|
||||
};
|
||||
};
|
||||
config = {
|
||||
packages = lib.fold (pipeline: acc: acc // pipeline) {} (
|
||||
map (pipeline: pipeline.packages) (builtins.attrValues config.pipelines)
|
||||
);
|
||||
soonix = config.config.soonix.finalConfig;
|
||||
};
|
||||
};
|
||||
}
|
||||
61
lib/impl/modules/soonix.nix
Normal file
61
lib/impl/modules/soonix.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
lib,
|
||||
cilib,
|
||||
...
|
||||
}: let
|
||||
inherit (lib) mkOption types;
|
||||
in {
|
||||
soonixSubmodule = {config, ...}: {
|
||||
options = {
|
||||
componentVersion = mkOption {
|
||||
description = "CI/CD component version. Also get's passed to inputs → version";
|
||||
type = types.str;
|
||||
default = cilib.version;
|
||||
};
|
||||
componentUrl = mkOption {
|
||||
description = "CI/CD component url";
|
||||
type = types.str;
|
||||
default = "gitlab.com/TECHNOFAB/nix-gitlab-ci/nix-gitlab-ci";
|
||||
};
|
||||
componentInputs = mkOption {
|
||||
description = "Extra inputs to pass to the CI/CD component";
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
};
|
||||
extraData = mkOption {
|
||||
description = "Extra data to include in the .gitlab-ci.yml file";
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
};
|
||||
|
||||
finalConfig = mkOption {
|
||||
internal = true;
|
||||
type = types.attrs;
|
||||
};
|
||||
};
|
||||
config.finalConfig = {
|
||||
opts.format = "yaml";
|
||||
hook = {
|
||||
mode = "copy";
|
||||
gitignore = false;
|
||||
};
|
||||
output = ".gitlab-ci.yml";
|
||||
generator = "nix";
|
||||
data =
|
||||
cilib.helpers.deepMerge
|
||||
{
|
||||
include = [
|
||||
{
|
||||
component = "${config.componentUrl}@${config.componentVersion}";
|
||||
inputs =
|
||||
{
|
||||
version = config.componentVersion;
|
||||
}
|
||||
// config.componentInputs;
|
||||
}
|
||||
];
|
||||
}
|
||||
config.extraData;
|
||||
};
|
||||
};
|
||||
}
|
||||
61
lib/impl/pipeline.nix
Normal file
61
lib/impl/pipeline.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
lib,
|
||||
helpers,
|
||||
mkJobDeps,
|
||||
mkJobRun,
|
||||
mkJobPatched,
|
||||
}: let
|
||||
inherit (lib) assertMsg;
|
||||
inherit (helpers) filterAttrsRec customMapAttrs toYaml toYamlPretty;
|
||||
in
|
||||
{
|
||||
name,
|
||||
pipeline,
|
||||
nixConfig,
|
||||
}: let
|
||||
jobs = filterAttrsRec (_n: v: v != null) pipeline.jobs;
|
||||
rest = filterAttrsRec (_n: v: v != null) (builtins.removeAttrs pipeline ["jobs"]);
|
||||
# this allows us to nix build this to get all the mentioned dependencies from the binary cache
|
||||
# pro: we don't have to download everything, just the deps for the current job
|
||||
# before, we just allowed pkgs inside the script string directly, but now with the ability to source this file
|
||||
# we can support different architectures between runners (eg. the arch of the initial runner does not matter)
|
||||
jobsMappedForDeps =
|
||||
customMapAttrs (key: job: {
|
||||
name = "gitlab-ci:pipeline:${name}:job-deps:${key}";
|
||||
value = mkJobDeps {inherit key job nixConfig;};
|
||||
})
|
||||
jobs;
|
||||
# allows the user to directly run the script
|
||||
jobsMappedForScript =
|
||||
customMapAttrs (key: job: {
|
||||
name = "gitlab-ci:pipeline:${name}:job:${key}";
|
||||
value = mkJobRun {
|
||||
inherit key job nixConfig;
|
||||
jobDeps = jobsMappedForDeps."gitlab-ci:pipeline:${name}:job-deps:${key}";
|
||||
};
|
||||
})
|
||||
jobs;
|
||||
# build the deps specific for this job before anything, this way the deps should be fetched from the cache
|
||||
jobsPatched =
|
||||
customMapAttrs (key: job: {
|
||||
name = key;
|
||||
value = assert assertMsg (builtins.elem job.stage (rest.stages or [])) "stage '${job.stage}' of job '${key}' does not exist";
|
||||
mkJobPatched {
|
||||
inherit key job nixConfig;
|
||||
pipelineName = name;
|
||||
};
|
||||
})
|
||||
jobs;
|
||||
in {
|
||||
packages =
|
||||
# gitlab-ci:pipeline:<name>
|
||||
# gitlab-ci:pipeline:<name>:job:<name>
|
||||
# gitlab-ci:pipeline:<name>:job-deps:<name>
|
||||
{
|
||||
"gitlab-ci:pipeline:${name}" = toYaml "gitlab-ci-${name}.yml" (rest // jobsPatched);
|
||||
"gitlab-ci:pipeline:${name}:pretty" = toYamlPretty "gitlab-ci-${name}.yml" (rest // jobsPatched);
|
||||
}
|
||||
// jobsMappedForDeps
|
||||
// jobsMappedForScript;
|
||||
finalConfig = rest // jobsPatched;
|
||||
}
|
||||
73
lib/impl/sandbox_helper.sh
Normal file
73
lib/impl/sandbox_helper.sh
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
echo -e "\e[32mSetting up...\e[0m"
|
||||
|
||||
actualJobScript=$1
|
||||
shift
|
||||
|
||||
INCLUDE_DIRTY=false
|
||||
NO_SANDBOX=false
|
||||
KEEP_TMP=false
|
||||
KEEP_ENV=""
|
||||
# parse flags
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--include-dirty)
|
||||
INCLUDE_DIRTY=true
|
||||
shift
|
||||
;;
|
||||
--no-sandbox)
|
||||
NO_SANDBOX=true
|
||||
shift
|
||||
;;
|
||||
--keep-tmp)
|
||||
KEEP_TMP=true
|
||||
shift
|
||||
;;
|
||||
--keep-env)
|
||||
KEEP_ENV="$2"
|
||||
shift 2
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ "$NO_SANDBOX" = false ]; then
|
||||
echo "Running with simple sandboxing"
|
||||
if [ "$KEEP_TMP" = false ]; then
|
||||
trap "rm -rf '$TMPDIR'" EXIT
|
||||
else
|
||||
echo "Temp dir will be preserved at: $TMPDIR"
|
||||
fi
|
||||
|
||||
# check if dirty
|
||||
DIRTY_PATCH=""
|
||||
if ! git diff --quiet && ! git diff --staged --quiet; then
|
||||
echo "Warning: working tree is dirty."
|
||||
DIRTY_PATCH=$(mktemp -t "nix-gitlab-ci.XXX.patch")
|
||||
git diff --staged > "$DIRTY_PATCH"
|
||||
trap "rm -f '$DIRTY_PATCH'" EXIT
|
||||
fi
|
||||
TMPDIR=$(mktemp -dt "nix-gitlab-ci.XXX")
|
||||
git clone . $TMPDIR
|
||||
pushd $TMPDIR >/dev/null
|
||||
if [[ ! -z "$DIRTY_PATCH" && "$INCLUDE_DIRTY" = true ]]; then
|
||||
echo "Copying dirty changes..."
|
||||
git apply "$DIRTY_PATCH" 2>/dev/null || echo "Failed to copy dirty changes"
|
||||
fi
|
||||
|
||||
echo "Running job in $TMPDIR"
|
||||
env -i $(
|
||||
if [[ -n "$KEEP_ENV" ]]; then
|
||||
IFS=',' read -ra VARS <<< "$KEEP_ENV"
|
||||
for var in "${VARS[@]}"; do
|
||||
printf '%s=%q ' "$var" "${!var}"
|
||||
done
|
||||
fi
|
||||
) bash $actualJobScript
|
||||
popd >/dev/null
|
||||
else
|
||||
exec $actualJobScript
|
||||
fi
|
||||
|
||||
46
lib/utils.nix
Normal file
46
lib/utils.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{pkgs, ...}: {
|
||||
commitAndPushFiles = {
|
||||
message,
|
||||
files ? [],
|
||||
}: jobArgs:
|
||||
jobArgs
|
||||
// {
|
||||
before_script =
|
||||
(jobArgs.before_script or [])
|
||||
++ [
|
||||
# sh
|
||||
''
|
||||
echo -e "\\e[0Ksection_start:`date +%s`:commit_setup[collapsed=true]\\r\\e[0KSetting up commitAndPushFiles"
|
||||
eval "$(ssh-agent -s)" >/dev/null;
|
||||
mkdir -p ~/.ssh; touch ~/.ssh/known_hosts;
|
||||
ssh-keyscan -t rsa $CI_SERVER_HOST >> ~/.ssh/known_hosts;
|
||||
echo "$GIT_SSH_PRIV_KEY" | tr -d '\r' | ssh-add - >/dev/null;
|
||||
git config --global user.email "$GIT_EMAIL" >/dev/null;
|
||||
git config --global user.name "$GIT_NAME" >/dev/null;
|
||||
export CI_PUSH_REPO=`echo $CI_REPOSITORY_URL | sed -e "s|.*@\(.*\)|git@\1|" -e "s|/|:|"`;
|
||||
git remote rm origin && git remote add origin ''${CI_PUSH_REPO}
|
||||
echo -e "\\e[0Ksection_end:`date +%s`:commit_setup\\r\\e[0K"
|
||||
''
|
||||
];
|
||||
script = let
|
||||
addScript =
|
||||
if builtins.length files == 0
|
||||
then ""
|
||||
else "git add ${builtins.concatStringsSep " " files}";
|
||||
in
|
||||
(jobArgs.script or [])
|
||||
++ [
|
||||
# sh
|
||||
''
|
||||
echo -e "\\e[0Ksection_start:`date +%s`:commit[collapsed=true]\\r\\e[0KCommiting & pushing changes if necessary"
|
||||
${addScript}
|
||||
git diff --cached --exit-code >/dev/null &&
|
||||
echo "Nothing to commit" ||
|
||||
git commit -m "${message}" --no-verify;
|
||||
git push --tags origin ''${GIT_SOURCE_REF:-HEAD}:''${GIT_TARGET_REF:-$CI_COMMIT_REF_NAME} -o ci.skip
|
||||
echo -e "\\e[0Ksection_end:`date +%s`:commit\\r\\e[0K"
|
||||
''
|
||||
];
|
||||
nix.deps = (jobArgs.nix.deps or []) ++ [pkgs.openssh pkgs.gitMinimal pkgs.gnused];
|
||||
};
|
||||
}
|
||||
48
nix/packages/pkgs.nix
Normal file
48
nix/packages/pkgs.nix
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
inputs,
|
||||
system,
|
||||
...
|
||||
}: let
|
||||
inherit (inputs) pkgs;
|
||||
in rec {
|
||||
setupScript = pkgs.writeShellScriptBin "setup_nix_ci" (builtins.readFile ./scripts/setup_nix_ci.sh);
|
||||
finalizeScript = pkgs.writeShellScriptBin "finalize_nix_ci" (builtins.readFile ./scripts/finalize_nix_ci.sh);
|
||||
image = pkgs.dockerTools.buildImage {
|
||||
name = "nix-ci";
|
||||
fromImage = let
|
||||
hashes = {
|
||||
"x86_64-linux" = "sha256-kJ7dqje5o1KPr3RDZ7/THbhMSoiCU1C/7HshDrNfwnM=";
|
||||
"aarch64-linux" = "sha256-jz+Z3Ji+hy5d9ImOh/YOKCqy9P9/cseSov+5J/O95bg=";
|
||||
};
|
||||
# check digest of tags like nixos-24.11-aarch64-linux etc.
|
||||
digests = {
|
||||
"x86_64-linux" = "sha256:345f210dea4cbd049e2d01d13159c829066dfb6e273cdd49ea878186d17b19f7";
|
||||
"aarch64-linux" = "sha256:66163fdf446d851416dd4e9be28c0794d9c2550214a57a846957699a3f5747f6";
|
||||
};
|
||||
hash = hashes.${system} or (throw "Unsupported system");
|
||||
imageDigest = digests.${system} or (throw "Unsupported system");
|
||||
in
|
||||
pkgs.dockerTools.pullImage {
|
||||
imageName = "nixpkgs/nix-flakes";
|
||||
inherit hash imageDigest;
|
||||
};
|
||||
copyToRoot = pkgs.buildEnv {
|
||||
name = "image-root";
|
||||
paths = with pkgs;
|
||||
[
|
||||
gitMinimal
|
||||
gnugrep
|
||||
gnused
|
||||
coreutils
|
||||
diffutils
|
||||
cachix
|
||||
attic-client
|
||||
]
|
||||
++ [
|
||||
setupScript
|
||||
finalizeScript
|
||||
];
|
||||
pathsToLink = ["/bin"];
|
||||
};
|
||||
};
|
||||
}
|
||||
40
nix/packages/scripts/finalize_nix_ci.sh
Normal file
40
nix/packages/scripts/finalize_nix_ci.sh
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
echo -e "\\e[0Ksection_start:`date +%s`:finalize_nix_ci[collapsed=true]\\r\\e[0KFinalizing Nix CI..."
|
||||
nix path-info --all > /tmp/nix-store-after
|
||||
echo "Finding new paths..."
|
||||
NEW_PATHS=$(diff --new-line-format="%L" \
|
||||
--old-line-format="" --unchanged-line-format="" \
|
||||
/tmp/nix-store-before /tmp/nix-store-after)
|
||||
COUNT=$(wc -l <<<"$NEW_PATHS")
|
||||
|
||||
if [[ "$NIX_CI_CACHE_STRATEGY" == "auto" ]]; then
|
||||
export NIX_CI_CACHE_STRATEGY="${NIX_CI_RUNNER_CACHE_STRATEGY:-${NIX_CI_DEFAULT_CACHE_STRATEGY:-none}}";
|
||||
fi
|
||||
|
||||
if [ -z "$NIX_CI_DISABLE_CACHE" ]; then
|
||||
echo -e "\\e[0Ksection_start:`date +%s`:cache_push[collapsed=true]\\r\\e[0KPushing $COUNT new store paths to cache ($NIX_CI_CACHE_STRATEGY)"
|
||||
echo -n "$NEW_PATHS" | {
|
||||
case "$NIX_CI_CACHE_STRATEGY" in
|
||||
"runner")
|
||||
export RUNNER_CACHE=''${RUNNER_CACHE:-"file://$(pwd)/.nix-cache"}
|
||||
# add ^* to all store paths ending in .drv (prevent warning log spam)
|
||||
sed '/\.drv$/s/$/^*/' | nix copy --quiet --to "$RUNNER_CACHE" --stdin || true
|
||||
;;
|
||||
"attic")
|
||||
attic push --stdin ci:$ATTIC_CACHE || true
|
||||
;;
|
||||
"cachix")
|
||||
cachix push $CACHIX_CACHE || true
|
||||
;;
|
||||
"none")
|
||||
echo "Cache strategy is none, doing nothing..."
|
||||
;;
|
||||
*)
|
||||
echo "WARNING: Invalid cache strategy set: '$NIX_CI_CACHE_STRATEGY'"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
echo -e "\\e[0Ksection_end:`date +%s`:cache_push\\r\\e[0K"
|
||||
else
|
||||
echo "Caching disabled, not uploading $COUNT new store entries..."
|
||||
fi
|
||||
echo -e "\\e[0Ksection_end:`date +%s`:finalize_nix_ci\\r\\e[0K"
|
||||
48
nix/packages/scripts/setup_nix_ci.sh
Normal file
48
nix/packages/scripts/setup_nix_ci.sh
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
echo -e "\\e[0Ksection_start:`date +%s`:nix_setup[collapsed=true]\\r\\e[0KSetting up Nix CI"
|
||||
nix path-info --all > /tmp/nix-store-before
|
||||
|
||||
if [[ "$NIX_CI_CACHE_STRATEGY" == "auto" ]]; then
|
||||
export NIX_CI_CACHE_STRATEGY="${NIX_CI_RUNNER_CACHE_STRATEGY:-${NIX_CI_DEFAULT_CACHE_STRATEGY:-none}}";
|
||||
echo "NIX_CI_CACHE_STRATEGY was set to auto, selected '$NIX_CI_CACHE_STRATEGY' for this job"
|
||||
fi
|
||||
|
||||
if [ -z "$NIX_CI_DISABLE_CACHE" ]; then
|
||||
echo -e "\\e[0Ksection_start:`date +%s`:cache_setup[collapsed=true]\\r\\e[0KConfiguring cache ($NIX_CI_CACHE_STRATEGY)"
|
||||
case "$NIX_CI_CACHE_STRATEGY" in
|
||||
"runner")
|
||||
export RUNNER_CACHE=''${RUNNER_CACHE:-"file://$(pwd)/.nix-cache"}
|
||||
echo "Runner Cache: $RUNNER_CACHE"
|
||||
export NIX_CONFIG="$NIX_CONFIG
|
||||
extra-trusted-substituters = $RUNNER_CACHE?priority=10&trusted=true
|
||||
extra-substituters = $RUNNER_CACHE?priority=10&trusted=true
|
||||
"
|
||||
;;
|
||||
"attic")
|
||||
echo "Attic Cache: $ATTIC_CACHE"
|
||||
attic login --set-default ci "$ATTIC_SERVER" "$ATTIC_TOKEN" || true
|
||||
attic use "$ATTIC_CACHE" || true
|
||||
;;
|
||||
"cachix")
|
||||
echo "Cachix Cache: $CACHIX_CACHE"
|
||||
cachix use "$CACHIX_CACHE" || true
|
||||
;;
|
||||
"none")
|
||||
echo "Cache strategy is none, doing nothing..."
|
||||
;;
|
||||
*)
|
||||
echo "WARNING: Invalid cache strategy set: '$NIX_CI_CACHE_STRATEGY'"
|
||||
;;
|
||||
esac
|
||||
echo -e "\\e[0Ksection_end:`date +%s`:cache_setup\\r\\e[0K"
|
||||
else
|
||||
echo "Caching disabled (NIX_CI_DISABLE_CACHE), skipping cache configuration..."
|
||||
fi
|
||||
|
||||
# load the job's deps only if the name was passed
|
||||
if [[ ! -z $1 ]]; then
|
||||
echo -e "\\e[0Ksection_start:`date +%s`:nix_deps[collapsed=true]\\r\\e[0KFetching Nix dependencies for job"
|
||||
nix build .#$1
|
||||
source $(readlink -f result)
|
||||
echo -e "\\e[0Ksection_end:`date +%s`:nix_deps\\r\\e[0K"
|
||||
fi
|
||||
echo -e "\\e[0Ksection_end:`date +%s`:nix_setup\\r\\e[0K"
|
||||
45
nix/repo/ci.nix
Normal file
45
nix/repo/ci.nix
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{inputs, ...}: let
|
||||
inherit (inputs) cilib;
|
||||
in
|
||||
cilib.mkCI {
|
||||
pipelines."default" = {
|
||||
stages = ["test" "build" "deploy"];
|
||||
jobs = {
|
||||
"test" = {
|
||||
stage = "test";
|
||||
script = [
|
||||
"nix run .#tests -- --junit=junit.xml"
|
||||
];
|
||||
allow_failure = true;
|
||||
artifacts = {
|
||||
when = "always";
|
||||
reports.junit = "junit.xml";
|
||||
};
|
||||
};
|
||||
"docs" = {
|
||||
stage = "build";
|
||||
script = [
|
||||
# sh
|
||||
''
|
||||
nix build .#docs:default
|
||||
mkdir -p public
|
||||
cp -r result/. public/
|
||||
''
|
||||
];
|
||||
artifacts.paths = ["public"];
|
||||
};
|
||||
"pages" = {
|
||||
nix.enable = false;
|
||||
image = "alpine:latest";
|
||||
stage = "deploy";
|
||||
script = ["true"];
|
||||
artifacts.paths = ["public"];
|
||||
rules = [
|
||||
{
|
||||
"if" = "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
27
nix/repo/devShells.nix
Normal file
27
nix/repo/devShells.nix
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{inputs, ...}: let
|
||||
inherit (inputs) pkgs devshell treefmt;
|
||||
in {
|
||||
default = devshell.mkShell {
|
||||
packages = [
|
||||
pkgs.nil
|
||||
(treefmt.mkWrapper pkgs {
|
||||
programs = {
|
||||
alejandra.enable = true;
|
||||
deadnix.enable = true;
|
||||
statix.enable = true;
|
||||
mdformat.enable = true;
|
||||
yamlfmt.enable = true;
|
||||
};
|
||||
settings.formatter = {
|
||||
yamlfmt.excludes = ["templates/nix-gitlab-ci.yml"];
|
||||
mdformat.command = let
|
||||
pkg = pkgs.python3.withPackages (p: [
|
||||
p.mdformat
|
||||
p.mdformat-mkdocs
|
||||
]);
|
||||
in "${pkg}/bin/mdformat";
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
1
nix/repo/docs.nix
Normal file
1
nix/repo/docs.nix
Normal file
|
|
@ -0,0 +1 @@
|
|||
{}
|
||||
45
nix/repo/flake.lock
generated
Normal file
45
nix/repo/flake.lock
generated
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"nodes": {
|
||||
"devshell-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1755673398,
|
||||
"narHash": "sha256-51MmR+Eo1+bKDd/Ss77wwTqi4yAR2xgmyCSEbKWSpj0=",
|
||||
"owner": "rensa-nix",
|
||||
"repo": "devshell",
|
||||
"rev": "e76bef387e8a4574f9b6d37b1a424e706491af08",
|
||||
"type": "gitlab"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "rensa-nix",
|
||||
"repo": "devshell",
|
||||
"type": "gitlab"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devshell-lib": "devshell-lib",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1756662192,
|
||||
"narHash": "sha256-F1oFfV51AE259I85av+MAia221XwMHCOtZCMcZLK2Jk=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "1aabc6c05ccbcbf4a635fb7a90400e44282f61c4",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
16
nix/repo/flake.nix
Normal file
16
nix/repo/flake.nix
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
inputs = {
|
||||
devshell-lib.url = "gitlab:rensa-nix/devshell?dir=lib";
|
||||
treefmt-nix = {
|
||||
url = "github:numtide/treefmt-nix";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
outputs = i:
|
||||
i
|
||||
// {
|
||||
devshell = i.devshell-lib.lib {inherit (i.parent) pkgs;};
|
||||
cilib = import "${i.parent.self}/lib" {inherit (i.parent) pkgs;};
|
||||
treefmt = import i.treefmt-nix;
|
||||
};
|
||||
}
|
||||
113
templates/nix-gitlab-ci.yml
Normal file
113
templates/nix-gitlab-ci.yml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
spec:
|
||||
inputs:
|
||||
cache_strategy:
|
||||
type: string
|
||||
description: |
|
||||
auto (default) | none | runner | cachix | attic
|
||||
Sets the default caching strategy.
|
||||
- "auto": dynamically selects the best strategy for every job based on env variables
|
||||
- "none": disables caching
|
||||
- "runner", "cachix" & "attic": forces every job to use this strategy
|
||||
|
||||
Can be overridden by setting NIX_CI_CACHE_STRATEGY in the pipeline variables.
|
||||
default: "auto"
|
||||
cache_files:
|
||||
type: array
|
||||
description: |
|
||||
Files to use as the cache key for the generated pipeline yaml.
|
||||
If you use "ci.nix" to define CI, add that here for example.
|
||||
Note that max 2 items are allowed in cache:key:files, so use something like
|
||||
["flake.*", "ci.nix"] f. ex. to match flake.lock, flake.nix and ci.nix.
|
||||
default: ["flake.nix", "flake.lock"]
|
||||
version:
|
||||
type: string
|
||||
description: |
|
||||
Which version of the Nix CI image to use. Using a tag/version is recommended.
|
||||
stage_build:
|
||||
type: string
|
||||
description: The CI stage for building the dynamic pipeline.
|
||||
default: build
|
||||
stage_trigger:
|
||||
type: string
|
||||
description: The CI stage for triggering the dynamic pipeline.
|
||||
default: trigger
|
||||
---
|
||||
stages:
|
||||
- $[[ inputs.stage_build ]]
|
||||
- $[[ inputs.stage_trigger ]]
|
||||
variables:
|
||||
# These can be overriden, see https://docs.gitlab.com/ci/variables/#cicd-variable-precedence
|
||||
# which image should be used by default.
|
||||
NIX_CI_IMAGE: registry.gitlab.com/technofab/nix-gitlab-ci/nix-ci:$[[ inputs.version ]]
|
||||
# default cache stategy
|
||||
NIX_CI_CACHE_STRATEGY: $[[ inputs.cache_strategy ]]
|
||||
nix-ci:build:
|
||||
stage: $[[ inputs.stage_build ]]
|
||||
image: $NIX_CI_IMAGE
|
||||
cache:
|
||||
- key:
|
||||
files: $[[ inputs.cache_files ]]
|
||||
paths:
|
||||
- .nix-ci-pipelines/
|
||||
- key: nix
|
||||
paths:
|
||||
- .nix-cache/
|
||||
before_script:
|
||||
- |
|
||||
# if no explicit pipeline is requested
|
||||
if [[ -z "${NIX_CI_PIPELINE_NAME:-}" ]]; then
|
||||
# if regex matches, use pipeline "default", otherwise $CI_PIPELINE_SOURCE
|
||||
[[ "${CI_PIPELINE_SOURCE}" =~ ${NIX_CI_DEFAULT_SOURCES:-.*} ]] \
|
||||
&& NIX_CI_PIPELINE_NAME="default" \
|
||||
|| NIX_CI_PIPELINE_NAME="$CI_PIPELINE_SOURCE";
|
||||
fi
|
||||
echo "NIX_CI_GENERATED_PIPELINE_NAME=$NIX_CI_PIPELINE_NAME" >> trigger.env
|
||||
# inheritance of pipeline variables is a bit weird, so explicitly override them
|
||||
# (ctx: setting any of these in the project variables would only apply correctly
|
||||
# in this pipeline, not the child pipeline, instead weirdly enough the default
|
||||
# variables above are used). If any other variables are added at the top, add them
|
||||
# here aswell
|
||||
echo "NIX_CI_IMAGE=$NIX_CI_IMAGE" >> trigger.env
|
||||
echo "NIX_CI_CACHE_STRATEGY=$NIX_CI_CACHE_STRATEGY" >> trigger.env
|
||||
|
||||
mkdir -p .nix-ci-pipelines/
|
||||
# generated-gitlab-ci.yml exists in the cache
|
||||
[[ -f ".nix-ci-pipelines/${NIX_CI_PIPELINE_NAME}.yml" ]] && export CACHED=true && echo "A cached pipeline file exists (skip cache with NIX_CI_FORCE_BUILD)" || true
|
||||
# allow the user to manually skip the cache (when the key files are not correctly configured etc.)
|
||||
[[ -n "$NIX_CI_FORCE_BUILD" ]] && unset CACHED && echo "Caching skipped for this job (through NIX_CI_FORCE_BUILD)" || true
|
||||
|
||||
# only setup when we need to generate the pipeline yaml
|
||||
if [[ -z "$CACHED" ]]; then
|
||||
source setup_nix_ci;
|
||||
fi
|
||||
script:
|
||||
# build the pipeline if it does not exist in the cache
|
||||
- >
|
||||
if [[ -z "$CACHED" ]]; then
|
||||
nix build .#gitlab-ci:pipeline:${NIX_CI_PIPELINE_NAME} && install result .nix-ci-pipelines/${NIX_CI_PIPELINE_NAME}.yml;
|
||||
fi
|
||||
after_script:
|
||||
# save to binary cache or Gitlab CI cache only if we actually built something
|
||||
# check if /tmp/nix-store-before exists as $CACHED never exists here and the file only exists if "setup_nix_ci" is called
|
||||
- |
|
||||
if [[ -f "/tmp/nix-store-before" ]]; then
|
||||
finalize_nix_ci;
|
||||
fi
|
||||
artifacts:
|
||||
paths:
|
||||
- .nix-ci-pipelines/
|
||||
reports:
|
||||
dotenv: trigger.env
|
||||
|
||||
nix-ci:trigger:
|
||||
stage: $[[ inputs.stage_trigger ]]
|
||||
needs:
|
||||
- nix-ci:build
|
||||
trigger:
|
||||
include:
|
||||
- artifact: .nix-ci-pipelines/${NIX_CI_GENERATED_PIPELINE_NAME}.yml
|
||||
job: nix-ci:build
|
||||
strategy: depend
|
||||
forward:
|
||||
pipeline_variables: true
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue