Compare commits

...

27 commits

Author SHA1 Message Date
7cb73d2319
chore: bump version 2025-10-06 17:56:57 +02:00
5e39c4fbcc
Merge branch 'feat/v3' 2025-10-06 17:54:43 +02:00
046656458b
chore: remove old files in preparation for rewrite 2025-10-06 17:53:51 +02:00
d88ada2c41
chore: bump version 2025-09-25 14:50:24 +02:00
0cca02f442
feat: implement all (?) ci yaml keywords 2025-09-25 14:49:46 +02:00
e752f71dd1
docs: use svg logo, add style.css and fix sitemap 2025-09-25 13:18:06 +02:00
428afaf603
chore(repo): update nixmkdocs 2025-09-05 09:02:42 +02:00
00cf5b83c6
chore: bump version 2025-09-04 10:29:56 +02:00
2a0a3f5881
chore(ci): try setting ssl env vars in the test directly (cuz pure mode) 2025-09-04 08:39:37 +02:00
5e2ae29660
chore: better solution for handling store paths in variables 2025-09-04 08:34:25 +02:00
31f0e4ea13
fix(jobPatched): fix variables not being unset if all contain store paths
the goal was to remove "variables" when it's empty, but this resulted in
the original value being used if all of the variables contained store
paths. This is not supposed to happen and thus fixed with this.
2025-09-03 15:17:51 +02:00
a076f0048a
chore(CI): modify cache_files and add ssl ca certs to test job 2025-09-03 14:29:56 +02:00
cf04bf5357
fix(sandbox_helper): fix TMPDIR being initialized too late and thus removing /tmp 2025-09-03 14:14:25 +02:00
4d824900d4
fix(module): default image to $NIX_CI_IMAGE 2025-09-03 10:46:47 +02:00
6cd05e503a
fix(ci): only specify component version once 2025-09-03 10:19:11 +02:00
c4a439b839
chore(repo): update nixtest 2025-09-02 13:39:00 +02:00
436e2fde25
test: copy over most tests, add new tests for soonix 2025-09-02 12:08:29 +02:00
0bd75fd1bb
docs: add remaining docs back with some additions/improvements 2025-09-02 11:49:35 +02:00
f147295418
chore: reformat 2025-09-02 11:34:24 +02:00
cad40720a6
docs: improve option descriptions 2025-09-02 11:31:25 +02:00
e7f7043012
docs: add initial docs setup 2025-09-02 11:00:38 +02:00
aa1abf945e
docs: add improved README.md 2025-09-02 10:48:54 +02:00
46bb4fe455
chore: add examples for flake-parts and rensa-nix 2025-09-02 10:48:39 +02:00
537d5c7c87
chore: add LICENSE 2025-09-02 10:36:46 +02:00
6e4347af69
chore(repo): add soonix to generate .gitlab-ci.yml 2025-09-02 10:35:35 +02:00
e074d716c4
test: add basic test for flakeModule functionality 2025-09-01 15:46:07 +02:00
0952ab4145
feat: initial v3 rewrite 2025-09-01 15:04:20 +02:00
65 changed files with 2772 additions and 1296 deletions

3
.envrc
View file

@ -1 +1,2 @@
use flake . --impure --accept-flake-config
source $(fetchurl https://gitlab.com/rensa-nix/direnv/-/raw/v0.3.0/direnvrc "sha256-u7+KEz684NnIZ+Vh5x5qLrt8rKdnUNexewBoeTcEVHQ=")
use ren //repo/devShells/default

6
.gitignore vendored
View file

@ -1,6 +1,2 @@
.idea
.devenv
.direnv
.pre-commit-config.yaml
.ren/
result
*.xml

View file

@ -1,49 +1,57 @@
include:
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/nix-gitlab-ci@$CI_COMMIT_SHA
inputs:
version: $CI_COMMIT_SHORT_SHA
stages:
- build-images
- build
- trigger
# Generated by soonix, DO NOT EDIT
build:image:
stage: build-images
parallel:
matrix:
- ARCH: ["x86_64-linux", "aarch64-linux"]
image: nixpkgs/nix-flakes:latest
script:
- nix build .#image --system $ARCH
after_script:
- install -D result dist/nix-ci-$ARCH.tar.gz
- install -D result dist/nix-ci-$ARCH.tar.gz
artifacts:
paths:
- dist
deploy:image:
- dist
image: nixpkgs/nix-flakes:latest
parallel:
matrix:
- ARCH:
- x86_64-linux
- aarch64-linux
script:
- nix build .#image --system $ARCH
stage: build-images
deploy:image:
before_script:
- 'nix profile install nixpkgs#buildah
export PATH="$PATH:$HOME/.nix-profile/bin"
export REGISTRY_AUTH_FILE=${HOME}/auth.json
echo "$CI_REGISTRY_PASSWORD" | buildah login -u "$CI_REGISTRY_USER" --password-stdin
$CI_REGISTRY
mkdir -p /etc/containers && echo ''{"default":[{"type":"insecureAcceptAnything"}]}''
> /etc/containers/policy.json
mkdir -p /var/tmp
'
image: nixpkgs/nix-flakes:latest
needs:
- build:image
before_script:
- nix profile install nixpkgs#buildah
- export PATH="$PATH:$HOME/.nix-profile/bin"
- export REGISTRY_AUTH_FILE=''${HOME}/auth.json
- echo "$CI_REGISTRY_PASSWORD" | buildah login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- mkdir -p /etc/containers && echo '{"default":[{"type":"insecureAcceptAnything"}]}' > /etc/containers/policy.json
- mkdir -p /var/tmp
- build:image
script:
- export NORMALIZED_BRANCH=${CI_COMMIT_BRANCH/\//-}
- buildah manifest create localhost/nix-ci
- buildah manifest add localhost/nix-ci docker-archive:dist/nix-ci-x86_64-linux.tar.gz
- buildah manifest add localhost/nix-ci docker-archive:dist/nix-ci-aarch64-linux.tar.gz
- buildah manifest push --all localhost/nix-ci docker://''${CI_REGISTRY_IMAGE}/nix-ci:${CI_COMMIT_SHORT_SHA}
# branches
- |
if [ -z "$CI_COMMIT_TAG" ]; then
buildah manifest push --all localhost/nix-ci docker://''${CI_REGISTRY_IMAGE}/nix-ci:${NORMALIZED_BRANCH/main/latest}
fi
# tags
- |
if [ -n "$CI_COMMIT_TAG" ]; then
buildah manifest push --all localhost/nix-ci docker://''${CI_REGISTRY_IMAGE}/nix-ci:${CI_COMMIT_TAG}
fi
- "export NORMALIZED_BRANCH=${CI_COMMIT_BRANCH/\\//-}\nbuildah manifest create localhost/nix-ci\n\
buildah manifest add localhost/nix-ci docker-archive:dist/nix-ci-x86_64-linux.tar.gz\n\
buildah manifest add localhost/nix-ci docker-archive:dist/nix-ci-aarch64-linux.tar.gz\n\
buildah manifest push --all localhost/nix-ci docker://${CI_REGISTRY_IMAGE}/nix-ci:${CI_COMMIT_SHORT_SHA}\n\
# branches\nif [ -z \"$CI_COMMIT_TAG\" ]; then\n buildah manifest push --all\
\ localhost/nix-ci docker://${CI_REGISTRY_IMAGE}/nix-ci:${NORMALIZED_BRANCH/main/latest}\n\
fi\n# tags\nif [ -n \"$CI_COMMIT_TAG\" ]; then\n buildah manifest push --all\
\ localhost/nix-ci docker://${CI_REGISTRY_IMAGE}/nix-ci:${CI_COMMIT_TAG}\nfi\n"
stage: build-images
include:
- component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/nix-gitlab-ci@$CI_COMMIT_SHORT_SHA
inputs:
cache_files:
- flake.*
- nix/repo/ci.nix
version: $CI_COMMIT_SHORT_SHA
stages:
- build-images
- build
- trigger

View file

@ -13,7 +13,7 @@ This allows easily using any Nix package in CI.
Also makes it possible to split CI parts in a separate module which can be imported in multiple projects.
## Usage
## Usage (with flake-parts)
```nix
# flake.nix
@ -28,23 +28,27 @@ Also makes it possible to split CI parts in a separate module which can be impor
...
perSystem = {pkgs, ...}: {
# ci is a shortcut and creates a "default" pipeline
ci = {
stages = ["test"];
jobs = {
"test" = {
stage = "test";
nix.deps = [pkgs.unixtools.ping];
script = [
"ping -c 5 8.8.8.8"
];
config = {
# configure Nix-GitLab-CI here, see docs for options
};
pipelines."default" = {
stages = ["test"];
jobs = {
"test" = {
stage = "test";
nix.deps = [pkgs.unixtools.ping];
script = [
"ping -c 5 8.8.8.8"
];
};
};
};
};
# runs on a merge request for example
pipelines."merge_request_event" = {
stages = ["some_stage"];
jobs = { ... };
# runs on a merge request for example
pipelines."merge_request_event" = {
stages = ["some_stage"];
jobs = { ... };
};
};
...
}
@ -52,6 +56,9 @@ Also makes it possible to split CI parts in a separate module which can be impor
}
```
Now either use this in your .gitlab-ci.yml or setup Soonix to auto generate this
file for you with the right version (see the [docs][docs-soonix] for more).
```yaml
# .gitlab-ci.yml
include:
@ -60,6 +67,19 @@ include:
version: <version> # docker image tag, use the same version as a above
```
## Usage (directly)
```nix
let
cilib = inputs.nix-gitlab-ci.lib {inherit pkgs;};
in
cilib.mkCI {
config = ...;
pipelines."default" = ...;
};
# exposes `soonix` for the soonix hook and `packages` which contain the configs, jobs etc.
```
## Utilities
### Disable Caching temporarily
@ -87,3 +107,5 @@ There is also `.#gitlab-ci:pipeline:<pipeline name>:job-deps:<name>` which gener
## Thanks to
Some parts of this implementation are adapted/inspired from https://gitlab.com/Cynerd/gitlab-ci-nix
[docs-soonix]: https://nix-gitlab-ci.projects,tf/soonix "Soonix Integration"

View file

@ -1,16 +1,11 @@
# Example Configs
## V2
- [TECHNOFAB/nix-gitlab-ci](https://gitlab.com/TECHNOFAB/nix-gitlab-ci)
See `flake.nix` for some random example jobs.
- [TECHNOFAB/nixlets](https://gitlab.com/TECHNOFAB/nixlets)
- [TECHNOFAB/nixible](https://gitlab.com/TECHNOFAB/nixible)
- [TECHNOFAB/nixmkdocs](https://gitlab.com/TECHNOFAB/nixmkdocs)
- [TECHNOFAB/tofunix](https://gitlab.com/TECHNOFAB/tofunix)
## Old / V1
- [TECHNOFAB/coder-templates](https://gitlab.com/TECHNOFAB/coder-templates)
- [TECHNOFAB/nixtest](https://gitlab.com/TECHNOFAB/nixtest)
!!! note

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

1
docs/images/logo.svg Executable file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="240" height="240" fill="none"><rect width="240" height="240" fill="url(#a)" rx="27"/><path fill="#E24329" d="M82.1 61.8c1 0 2 .4 2.7 1 .8.6 1.4 1.4 1.6 2.4l12.2 37.1h49.1l12.1-37.1c.3-1 .9-1.8 1.6-2.4a4.8 4.8 0 0 1 5.5-.3c.9.5 1.5 1.3 1.9 2.2l18 47 .1.4a33.4 33.4 0 0 1-11 38.5l-27.6 20.7-13.6 10.3-8.2 6.2a5.6 5.6 0 0 1-6.7 0l-8.3-6.2L98 171.3l-27.4-20.5-.2-.1A33.4 33.4 0 0 1 59.3 112l.2-.5 18-46.9a4.7 4.7 0 0 1 4.6-3Zm22.5 48c-7.5 0-11.2 3.8-11.2 11.3v21.6c0 7.5 3.7 11.3 11.2 11.3h16.6l5-3.8V144h-21c-.5 0-.7-.3-.7-.8v-22.6c0-.5.2-.7.7-.7h21v-6.2l-5-3.9h-16.6Zm29.8 0V154h11.1v-44.2h-11.1Z"/><path fill="#FC6D26" d="M187 112a33.4 33.4 0 0 1-11.1 38.6v.1l-27.6 20.6-25-18.9 2.9-2.2v-.2l8.2-6.2V154h11.1v-18.6l17-13c7.2-5.3 15.5-9 24.3-10.8l.1.5Z"/><path fill="#FCA326" d="m148.3 171.3-13.6 10.3-8.2 6.2a5.6 5.6 0 0 1-6.7 0l-8.3-6.2L98 171.3l22.9-17.3h.3l2-1.6 25 19Z"/><path fill="#FC6D26" d="M59.5 111.6c8.7 1.8 17 5.5 24.1 10.9l9.8 7.3v13c0 7.4 3.7 11.2 11.2 11.2H121l-23 17.3-27.3-20.5-.2-.1A33.4 33.4 0 0 1 59.3 112l.2-.5ZM112 144h-7c-.4 0-.6-.3-.6-.8v-5l7.6 5.8Z"/><path fill="#FC6D26" d="m209.9 159.2-12.4 22-29.5-.1 14.6 25.5-6.2 10.8h-12.8l-20.1-35 2.1-1.6 12-9 16.8-12.6H210Zm-156-50v.2l-.2.5a39.4 39.4 0 0 0 5.2 37.6L41 178.3l-12.8-21.7L43 131H13.7l-6.3-10.9 6.4-11.1 40.1.1Zm110.5 50-2.5 1.9 2.5-1.9ZM149 48l14.7-25.4h12.6l6.4 11L169 56.9c-1.5-.7-3.2-1-4.9-1h-.4c-2.2 0-4.3.9-6 2.2h-.1v.1a10.8 10.8 0 0 0-3.6 5.2l-7.7 23.8L109 22.6l25.3-.3L149 48Z"/><path fill="url(#b)" d="m209.9 159.2-12.4 22-29.5-.1 14.6 25.5-6.2 10.8h-12.8l-20.1-35 2.1-1.6 12-9 16.8-12.6H210Zm-156-50v.2l-.2.5a39.4 39.4 0 0 0 5.2 37.6L41 178.3l-12.8-21.7L43 131H13.7l-6.3-10.9 6.4-11.1 40.1.1Zm110.5 50-2.5 1.9 2.5-1.9ZM149 48l14.7-25.4h12.6l6.4 11L169 56.9c-1.5-.7-3.2-1-4.9-1h-.4c-2.2 0-4.3.9-6 2.2h-.1v.1a10.8 10.8 0 0 0-3.6 5.2l-7.7 23.8L109 22.6l25.3-.3L149 48Z"/><path fill="#E24329" d="m94.3 176 13.6 10.3 9 6.8 14 24.3-25.1.3L91 192l-14.7 25.4H63.9l-6.5-11 21-36.2-6.1-10.8 22 16.6Zm76.2-21.4-2.7 2 2.8-2Zm-106-9.6a33.3 33.3 0 0 0 .5.5l-.4-.5Zm147.2-61.6L197 109h29.3l6.3 10.9-6.4 11h-31.6c1.2-7 .5-14.2-2-20.9l-.3-.5-8.3-21.8 15-25.9 12.7 21.7Zm-25 28.2.2.5a34.5 34.5 0 0 1 1.5 18.7c1.3-6.3.8-12.7-1.5-18.7l-.1-.5-6.7-17.3 6.7 17.3Zm-89.6-53h30l12.7 22h-42L92 63.4c-.6-2.1-1.8-4-3.5-5.3l-.4-.3a10.8 10.8 0 0 0-5.3-2H82c-2 0-4 .5-5.8 1.6-2 1.2-3.5 3-4.3 5v.1l-7 18H30l12.4-21.9H72L57.3 33.3l6.2-10.8h12.8l20.8 36.2Z"/><path fill="url(#c)" d="m94.3 176 13.6 10.3 9 6.8 14 24.3-25.1.3L91 192l-14.7 25.4H63.9l-6.5-11 21-36.2-6.1-10.8 22 16.6Zm76.2-21.4-2.7 2 2.8-2Zm-106-9.6a33.3 33.3 0 0 0 .5.5l-.4-.5Zm147.2-61.6L197 109h29.3l6.3 10.9-6.4 11h-31.6c1.2-7 .5-14.2-2-20.9l-.3-.5-8.3-21.8 15-25.9 12.7 21.7Zm-25 28.2.2.5a34.5 34.5 0 0 1 1.5 18.7c1.3-6.3.8-12.7-1.5-18.7l-.1-.5-6.7-17.3 6.7 17.3Zm-89.6-53h30l12.7 22h-42L92 63.4c-.6-2.1-1.8-4-3.5-5.3l-.4-.3a10.8 10.8 0 0 0-5.3-2H82c-2 0-4 .5-5.8 1.6-2 1.2-3.5 3-4.3 5v.1l-7 18H30l12.4-21.9H72L57.3 33.3l6.2-10.8h12.8l20.8 36.2Z"/><defs><linearGradient id="b" x1="181.2" x2="141" y1="114.6" y2="45.1" gradientUnits="userSpaceOnUse"><stop stop-color="#699AD7"/><stop offset=".2" stop-color="#7EB1DD"/><stop offset="1" stop-color="#7EBAE4"/></linearGradient><linearGradient id="c" x1="151.1" x2="191.8" y1="167.7" y2="98.7" gradientUnits="userSpaceOnUse"><stop stop-color="#415E9A"/><stop offset=".2" stop-color="#4A6BAF"/><stop offset="1" stop-color="#5277C3"/></linearGradient><radialGradient id="a" cx="0" cy="0" r="1" gradientTransform="rotate(90 0 120) scale(120)" gradientUnits="userSpaceOnUse"><stop offset=".5" stop-color="#091A3D"/><stop offset="1" stop-color="#000819"/></radialGradient></defs></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

3
docs/options.md Normal file
View file

@ -0,0 +1,3 @@
# Options
{% include 'options.md' %}

View file

@ -36,8 +36,7 @@ flake module within your `flake-parts` configuration.
perSystem = { pkgs, ... }: {
# define your CI pipelines
# ci = { ... };
# pipelines."merge_request_event" = { ... };
# ci.pipelines."merge_request_event" = { ... };
};
};
}
@ -73,3 +72,10 @@ include:
Again, ensure `<version>` matches the version used in your `flake.nix`.
This component includes a job (`build:nix-ci`) that will evaluate your Nix
configuration and generate the `.gitlab-ci.yml` used for the pipeline run.
!!! note
Since V3 [Soonix](https://soonix.projects.tf) is supported, this can
automatically generate the `.gitlab-ci.yml` for you, with the version
automatically following the flake.
See [Soonix Integration](./soonix.md) for more.

20
docs/soonix.md Normal file
View file

@ -0,0 +1,20 @@
# Soonix Integration
[Soonix](https://soonix.projects.tf) can be used to automatically generate the
`.gitlab-ci.yml` for you.
This will by default include the CI/CD component with the same version as the
flake (using the `VERSION` file in this repo).
You can specify some options to configure this, like changing the component URL
or adding extra data to your `.gitlab-ci.yml` (like this repo does to bootstrap
Nix-GitLab-CI). See [Options](./options.md#configsoonix) for all the config options.
You can use it like this:
```nix
let
ci = cilib.mkCI { ... };
in {
soonix.hooks."ci" = ci.soonix;
}
```

15
docs/style.css Normal file
View file

@ -0,0 +1,15 @@
.md-header__button.md-logo {
margin: 0;
padding-top: .2rem;
padding-bottom: .2rem;
}
[dir="ltr"] .md-header__title {
margin-left: 0;
}
.md-header__button.md-logo img,
.md-header__button.md-logo svg {
height: 2rem;
}

View file

@ -1,33 +1,73 @@
# Usage
To create a basic pipeline, configure it by setting `ci` in `perSystem`.
The schema is similar to the `.gitlab-ci.yml`, only jobs are defined differently:
## Usage (with flake-parts)
```nix
ci = {
# Nix GitLab CI specific config, see `configType` in `flakeModule.nix`
config = {};
jobs = {
"job-a" = {};
"job-b" = {};
};
};
# flake.nix
{
...
inputs.nix-gitlab-ci.url = "gitlab:TECHNOFAB/nix-gitlab-ci/<version>?dir=lib"; # recommendation: pin to the latest release/version
outputs = {...}: flake-parts.lib.mkFlake {...} {
imports = [
inputs.nix-gitlab-ci.flakeModule
];
...
perSystem = {pkgs, ...}: {
ci = {
config = {
# configure Nix-GitLab-CI here, see docs for options
};
pipelines."default" = {
stages = ["test"];
jobs = {
"test" = {
stage = "test";
nix.deps = [pkgs.unixtools.ping];
script = [
"ping -c 5 8.8.8.8"
];
};
};
};
# runs on a merge request for example
pipelines."merge_request_event" = {
stages = ["some_stage"];
jobs = { ... };
};
};
...
}
}
}
```
For every job, there are a couple of settings you can adjust aswell:
Now either use this in your .gitlab-ci.yml or setup Soonix to auto generate this
file for you with the right version (see the [docs][docs-soonix] for more).
```yaml
# .gitlab-ci.yml
include:
- component: gitlab.com/TECHNOFAB/nix-gitlab-ci/nix-gitlab-ci@<version> # recommendation: pin to the latest release/version (don't use "main" etc.)
inputs:
version: <version> # docker image tag, use the same version as a above
```
## Usage (directly)
```nix
"job-a" = {
# see `jobType` in `flakeModule.nix`
nix = {
enable = true; # is this a nix-based job?
deps = []; # dependencies to install for this job
# for gitlab runner cache:
enable-runner-cache = false;
runner-cache-key = "";
let
cilib = inputs.nix-gitlab-ci.lib {inherit pkgs;};
in
cilib.mkCI {
config = ...;
pipelines."default" = ...;
};
};
# exposes `soonix` for the soonix hook and `packages` which contain the configs, jobs etc.
```
______________________________________________________________________
Since V2 multiple pipelines are supported.
See [Multiple Pipelines](./multi_pipeline.md) for more.

View file

@ -57,3 +57,8 @@ correctly resolved across different architectures).
You can use this to inspect the environment that would be set up for a job without
running the full script.
## Viewing generated config in YAML
Since the GitLab CI config is generated simply using JSON, it's hard to read and
debug. For debugging V3 now adds another package `gitlab-ci:pipeline:<pipeline name>:pretty`.

95
examples/flake-parts/flake.lock generated Normal file
View file

@ -0,0 +1,95 @@
{
"nodes": {
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1756770412,
"narHash": "sha256-+uWLQZccFHwqpGqr2Yt5VsW/PbeJVTn9Dk6SHWhNRPw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "4524271976b625a4a605beefd893f270620fd751",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"nix-gitlab-ci": {
"locked": {
"dir": "lib",
"lastModified": 1752052838,
"narHash": "sha256-EqP4xB8YTVXWPCCchnVtQbuq0bKa79TUEcPF3hjuX/k=",
"owner": "TECHNOFAB",
"repo": "nix-gitlab-ci",
"rev": "0c6949f585a2c1ea2cf85fc01445496f7c75faae",
"type": "gitlab"
},
"original": {
"dir": "lib",
"owner": "TECHNOFAB",
"repo": "nix-gitlab-ci",
"type": "gitlab"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1756696532,
"narHash": "sha256-6FWagzm0b7I/IGigOv9pr6LL7NQ86mextfE8g8Q6HBg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "58dcbf1ec551914c3756c267b8b9c8c86baa1b2f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1754788789,
"narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "a73b9c743612e4244d865a2fdee11865283c04e6",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"root": {
"inputs": {
"flake-parts": "flake-parts",
"nix-gitlab-ci": "nix-gitlab-ci",
"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
}

View file

@ -0,0 +1,39 @@
{
outputs = {
flake-parts,
systems,
...
} @ inputs:
flake-parts.lib.mkFlake {inherit inputs;} {
imports = [
inputs.nix-gitlab-ci.flakeModule
];
systems = import systems;
flake = {};
perSystem = _: {
ci = {
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";
# NOTE: better pin to a version
nix-gitlab-ci.url = "gitlab:TECHNOFAB/nix-gitlab-ci?dir=lib";
};
}

63
examples/rensa-nix/flake.lock generated Normal file
View 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
}

View file

@ -0,0 +1,30 @@
{
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 "ci")
];
}
{
packages = ren.select self [
["repo" "ci" "packages"]
];
};
}

View file

@ -0,0 +1,18 @@
{inputs, ...}: let
inherit (inputs) cilib;
in
cilib.mkCI {
pipelines = {
"default" = {
stages = ["example"];
jobs."example" = {
stage = "example";
script = ["echo hello world"];
};
};
"test".jobs."example" = {
stage = ".pre";
script = ["echo hello world"];
};
};
}

28
examples/rensa-nix/nix/repo/flake.lock generated Normal file
View file

@ -0,0 +1,28 @@
{
"nodes": {
"nix-gitlab-ci-lib": {
"locked": {
"dir": "lib",
"lastModified": 1752052838,
"narHash": "sha256-EqP4xB8YTVXWPCCchnVtQbuq0bKa79TUEcPF3hjuX/k=",
"owner": "TECHNOFAB",
"repo": "nix-gitlab-ci",
"rev": "0c6949f585a2c1ea2cf85fc01445496f7c75faae",
"type": "gitlab"
},
"original": {
"dir": "lib",
"owner": "TECHNOFAB",
"repo": "nix-gitlab-ci",
"type": "gitlab"
}
},
"root": {
"inputs": {
"nix-gitlab-ci-lib": "nix-gitlab-ci-lib"
}
}
},
"root": "root",
"version": 7
}

View file

@ -0,0 +1,10 @@
{
inputs = {
nix-gitlab-ci-lib.url = "gitlab:TECHNOFAB/nix-gitlab-ci?dir=lib";
};
outputs = i:
i
// {
cilib = i.nix-gitlab-ci-lib.lib {inherit (i.parent) pkgs;};
};
}

371
flake.lock generated
View file

@ -1,246 +1,12 @@
{
"nodes": {
"cachix": {
"inputs": {
"devenv": [
"devenv"
],
"flake-compat": [
"devenv"
],
"git-hooks": [
"devenv"
],
"nixpkgs": "nixpkgs"
},
"locked": {
"lastModified": 1737621947,
"narHash": "sha256-8HFvG7fvIFbgtaYAY2628Tb89fA55nPm2jSiNs0/Cws=",
"owner": "cachix",
"repo": "cachix",
"rev": "f65a3cd5e339c223471e64c051434616e18cc4f5",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "latest",
"repo": "cachix",
"type": "github"
}
},
"devenv": {
"inputs": {
"cachix": "cachix",
"flake-compat": "flake-compat",
"git-hooks": "git-hooks",
"nix": "nix",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1743687534,
"narHash": "sha256-QIh5jKWE0aZ4N77Zu3kz0RjA22zqqy5p6PfJnOW5rPE=",
"owner": "cachix",
"repo": "devenv",
"rev": "efd5d68a6483573410565d0b940e1a67b6f92591",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "devenv",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"devenv",
"nix",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1743550720,
"narHash": "sha256-hIshGgKZCgWh6AYJpJmRgFdR3WUbkY04o82X05xqQiY=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "c621e8422220273271f52058f618c94e405bb0f5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"devenv"
],
"gitignore": "gitignore",
"nixpkgs": [
"devenv",
"nixpkgs"
]
},
"locked": {
"lastModified": 1740849354,
"narHash": "sha256-oy33+t09FraucSZ2rZ6qnD1Y1c8azKKmQuCvF2ytUko=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "4a709a8ce9f8c08fa7ddb86761fe488ff7858a07",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"devenv",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"libgit2": {
"flake": false,
"locked": {
"lastModified": 1697646580,
"narHash": "sha256-oX4Z3S9WtJlwvj0uH9HlYcWv+x1hqp8mhXl7HsLu2f0=",
"owner": "libgit2",
"repo": "libgit2",
"rev": "45fd9ed7ae1a9b74b957ef4f337bc3c8b3df01b5",
"type": "github"
},
"original": {
"owner": "libgit2",
"repo": "libgit2",
"type": "github"
}
},
"mkdocs-material-umami": {
"locked": {
"lastModified": 1745840856,
"narHash": "sha256-1Ad1JTMQMP6YsoIKAA+SBCE15qWrYkGue9/lXOLnu9I=",
"owner": "technofab",
"repo": "mkdocs-material-umami",
"rev": "3ac9b194450f6b779c37b8d16fec640198e5cd0a",
"type": "gitlab"
},
"original": {
"owner": "technofab",
"repo": "mkdocs-material-umami",
"type": "gitlab"
}
},
"nix": {
"inputs": {
"flake-compat": [
"devenv"
],
"flake-parts": "flake-parts",
"libgit2": "libgit2",
"nixpkgs": "nixpkgs_2",
"nixpkgs-23-11": [
"devenv"
],
"nixpkgs-regression": [
"devenv"
],
"pre-commit-hooks": [
"devenv"
]
},
"locked": {
"lastModified": 1741798497,
"narHash": "sha256-E3j+3MoY8Y96mG1dUIiLFm2tZmNbRvSiyN7CrSKuAVg=",
"owner": "domenkozar",
"repo": "nix",
"rev": "f3f44b2baaf6c4c6e179de8cbb1cc6db031083cd",
"type": "github"
},
"original": {
"owner": "domenkozar",
"ref": "devenv-2.24",
"repo": "nix",
"type": "github"
}
},
"nix-mkdocs": {
"locked": {
"dir": "lib",
"lastModified": 1745175318,
"narHash": "sha256-eJOTw3SK4psqBPP2hNJ4D/13/oi/FLoM0tYKoCGVFP8=",
"owner": "technofab",
"repo": "nixmkdocs",
"rev": "6d6e0139060c896ae14de4b9c82335655a384643",
"type": "gitlab"
},
"original": {
"dir": "lib",
"owner": "technofab",
"repo": "nixmkdocs",
"type": "gitlab"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1733212471,
"narHash": "sha256-M1+uCoV5igihRfcUKrr1riygbe73/dzNnzPsmaLCmpo=",
"lastModified": 1756542300,
"narHash": "sha256-tlOn88coG5fzdyqz6R93SQL5Gpq+m/DsWpekNFhqPQk=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "55d15ad12a74eb7d4646254e13638ad0c4128776",
"rev": "d7600c775f877cd87b4f5a831c28aa94137377aa",
"type": "github"
},
"original": {
@ -252,11 +18,11 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1743296961,
"narHash": "sha256-b1EdN3cULCqtorQ4QeWgLMrd5ZGOjLSLemfa00heasc=",
"lastModified": 1754184128,
"narHash": "sha256-AjhoyBL4eSyXf01Bmc6DiuaMrJRNdWopmdnMY0Pa/M0=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "e4822aea2a6d1cdd36653c134cacfd64c97ff4fa",
"rev": "02e72200e6d56494f4a7c0da8118760736e41b60",
"type": "github"
},
"original": {
@ -265,131 +31,30 @@
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1717432640,
"narHash": "sha256-+f9c4/ZX5MWDOuB1rKoWj+lBNm0z0rs4CK47HBLxy1o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "88269ab3044128b7c2f4c7d68448b2fb50456870",
"type": "github"
"ren": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"original": {
"owner": "NixOS",
"ref": "release-24.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1733477122,
"narHash": "sha256-qamMCz5mNpQmgBwc8SB5tVMlD5sbwVIToVZtSxMph9s=",
"owner": "cachix",
"repo": "devenv-nixpkgs",
"rev": "7bd9e84d0452f6d2e63b6e6da29fe73fac951857",
"type": "github"
},
"original": {
"owner": "cachix",
"ref": "rolling",
"repo": "devenv-nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1745377448,
"narHash": "sha256-jhZDfXVKdD7TSEGgzFJQvEEZ2K65UMiqW5YJ2aIqxMA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "507b63021ada5fee621b6ca371c4fca9ca46f52c",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1735554305,
"narHash": "sha256-zExSA1i/b+1NMRhGGLtNfFGXgLtgo+dcuzHzaWA6w3Q=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "0e82ab234249d8eee3e8c91437802b32c74bb3fd",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixtest": {
"locked": {
"dir": "lib",
"lastModified": 1748945995,
"narHash": "sha256-OWflapMq78nCUT7N0vS/AuK4E8y7p8oh0zjnDioJ4Lw=",
"owner": "technofab",
"repo": "nixtest",
"rev": "392e8796f386373a36aecd3216925accf2714a1a",
"lastModified": 1756370106,
"narHash": "sha256-l84ojcHuQWBwn4BRxQsMMfQpcq/Az/sHh/hSqFgVtyg=",
"owner": "rensa-nix",
"repo": "core",
"rev": "9c1a29fa9ba7cbbb78b9e47eb8afbcd29303a3b4",
"type": "gitlab"
},
"original": {
"dir": "lib",
"owner": "technofab",
"ref": "1.0.0",
"repo": "nixtest",
"owner": "rensa-nix",
"repo": "core",
"type": "gitlab"
}
},
"root": {
"inputs": {
"devenv": "devenv",
"flake-parts": "flake-parts_2",
"mkdocs-material-umami": "mkdocs-material-umami",
"nix-mkdocs": "nix-mkdocs",
"nixpkgs": "nixpkgs_4",
"nixtest": "nixtest",
"systems": "systems",
"treefmt-nix": "treefmt-nix"
}
},
"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"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": "nixpkgs_5"
},
"locked": {
"lastModified": 1743677901,
"narHash": "sha256-eWZln+k+L/VHO69tUTzEmgeDWNQNKIpSUa9nqQgBrSE=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "57dabe2a6255bd6165b2437ff6c2d1f6ee78421a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
"nixpkgs": "nixpkgs",
"ren": "ren"
}
}
},

327
flake.nix
View file

@ -1,304 +1,37 @@
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
ren.url = "gitlab:rensa-nix/core?dir=lib";
};
outputs = {
flake-parts,
systems,
self,
ren,
...
} @ inputs:
flake-parts.lib.mkFlake {inherit inputs;} {
imports = [
inputs.devenv.flakeModule
inputs.treefmt-nix.flakeModule
inputs.nix-mkdocs.flakeModule
inputs.nixtest.flakeModule
./lib/flakeModule.nix
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"]
];
systems = import systems;
flake = {};
perSystem = {
pkgs,
config,
system,
...
}: rec {
imports = [
./tests
];
treefmt = {
projectRootFile = "flake.nix";
programs = {
alejandra.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";
};
};
devenv.shells.default = {
containers = pkgs.lib.mkForce {};
packages = with pkgs; [dive skopeo];
pre-commit = {
hooks = {
treefmt = {
enable = true;
packageOverrides.treefmt = config.treefmt.build.wrapper;
};
};
};
};
doc = {
path = ./docs;
deps = pp: [
pp.mkdocs-material
(pp.callPackage inputs.mkdocs-material-umami {})
];
config = {
site_name = "Nix GitLab CI";
repo_name = "TECHNOFAB/nix-gitlab-ci";
repo_url = "https://gitlab.com/TECHNOFAB/nix-gitlab-ci";
edit_uri = "edit/main/docs/";
theme = {
name = "material";
features = ["content.code.copy" "content.action.edit"];
icon.repo = "simple/gitlab";
logo = "images/logo.png";
favicon = "images/favicon.png";
palette = [
{
scheme = "default";
media = "(prefers-color-scheme: light)";
primary = "deep orange";
accent = "orange";
toggle = {
icon = "material/brightness-7";
name = "Switch to dark mode";
};
}
{
scheme = "slate";
media = "(prefers-color-scheme: dark)";
primary = "deep orange";
accent = "orange";
toggle = {
icon = "material/brightness-4";
name = "Switch to light mode";
};
}
];
};
plugins = ["search" "material-umami"];
nav = [
{"Introduction" = "index.md";}
{"Setup" = "setup.md";}
{"Usage" = "usage.md";}
{"CI/CD Component" = "cicd_component.md";}
{"Environment Variables" = "environment_variables.md";}
{"Caching" = "caching.md";}
{"Multiple Pipelines" = "multi_pipeline.md";}
{"Utilities" = "utilities.md";}
{"Kubernetes Runner Example" = "kubernetes_runner.md";}
{"Example Configs" = "examples.md";}
];
markdown_extensions = [
{
"pymdownx.highlight".pygments_lang_class = true;
}
"pymdownx.inlinehilite"
"pymdownx.snippets"
"pymdownx.superfences"
"fenced_code"
"admonition"
];
extra.analytics = {
provider = "umami";
site_id = "28f7c904-db22-4c2b-9ee4-ed42e14b6db9";
src = "https://analytics.tf/umami";
domains = "nix-gitlab-ci.projects.tf";
feedback = {
title = "Was this page helpful?";
ratings = [
{
icon = "material/thumb-up-outline";
name = "This page is helpful";
data = "good";
note = "Thanks for your feedback!";
}
{
icon = "material/thumb-down-outline";
name = "This page could be improved";
data = "bad";
note = "Thanks for your feedback! Please leave feedback by creating an issue :)";
}
];
};
};
};
};
# should set the "default" pipeline
ci = {
stages = ["test" "nixtest" "build" "deploy"];
jobs = {
"test" = {
stage = "test";
nix = {
deps = [pkgs.hello pkgs.curl];
enable-runner-cache = true;
};
variables = {
TEST = "test";
TEST_WITH_DERIVATION = "${pkgs.hello}/test";
};
script = [
"hello"
"curl google.de"
"echo $TEST $TEST_WITH_DERIVATION"
];
};
"test-default" = {
stage = "test";
nix.deps = [pkgs.hello];
script = ["hello"];
};
"test-non-nix" = {
nix.enable = false;
stage = "test";
image = "alpine:latest";
script = [
"echo \"This job will not be modified to use nix\""
];
};
# -- actually useful jobs --
"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";
}
];
};
"nixtest" = {
stage = "nixtest";
script = [
# sh
"nix run .#nixtests:run -- --junit=junit.xml --pure"
];
allow_failure = true;
artifacts = {
when = "always";
reports.junit = "junit.xml";
};
};
};
};
pipelines."non-default" = {
stages = ["test"];
jobs = {
"test" = {
stage = "test";
script = [
"echo Hello from another pipeline"
];
};
};
};
packages = let
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);
in {
setup-script = setupScript;
finalize-script = finalizeScript;
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"];
};
};
};
checks = packages;
};
};
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
# flake & devenv related
flake-parts.url = "github:hercules-ci/flake-parts";
systems.url = "github:nix-systems/default-linux";
devenv.url = "github:cachix/devenv";
treefmt-nix.url = "github:numtide/treefmt-nix";
nix-mkdocs.url = "gitlab:technofab/nixmkdocs?dir=lib";
mkdocs-material-umami.url = "gitlab:technofab/mkdocs-material-umami";
nixtest.url = "gitlab:technofab/nixtest/1.0.0?dir=lib";
};
nixConfig = {
extra-substituters = [
"https://cache.nixos.org/"
"https://nix-community.cachix.org"
"https://devenv.cachix.org"
];
extra-trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"devenv.cachix.org-1:w1cLUi8dv3hnoSPGAuibQv+f9TZLr6cv/Hm9XgU50cw="
];
};
}

1
lib/VERSION Normal file
View file

@ -0,0 +1 @@
3.0.0

View file

@ -1,8 +1,29 @@
args: {
helpers = import ./helpers.nix args;
jobDeps = import ./jobDeps.nix args;
jobRun = import ./jobRun.nix args;
jobPatch = import ./jobPatch.nix args;
pipeline = import ./pipeline.nix args;
utils = import ./utils.nix args;
}
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 trimWith;
impl = import ./impl {inherit lib pkgs cilib;};
cilib = {
inherit (impl) helpers modules mkPipeline mkJobRun mkJobDeps mkJobPatched;
utils = import ./utils.nix {inherit pkgs;};
version = trimWith {
start = true;
end = true;
} (builtins.readFile ./VERSION);
mkCI = config:
(evalModules {
modules = [
cilib.modules.nixCiSubmodule
{
inherit config;
}
];
}).config;
};
in
cilib

View file

@ -1,8 +1,6 @@
{
description = "Nix-GitLab-CI lib";
outputs = {...}: {
flakeModule = import ./flakeModule.nix;
lib = import ./default.nix;
outputs = _i: {
lib = import ./.;
flakeModule = ./flakeModule.nix;
};
}

View file

@ -10,163 +10,15 @@
...
}: let
cilib = import ./. {inherit lib pkgs;};
inherit (cilib.pipeline) mkPipeline;
inherit (lib) types mkOption;
cfg = config.ci.config;
subType = options: types.submodule {inherit options;};
mkNullOption = type:
mkOption {
default = null;
type = types.nullOr type;
};
configType = subType {
nix-jobs-by-default = mkOption {
type = types.bool;
default = true;
description = "Handle jobs nix-based by default or via opt-in (in a job set nix.enable = true) if false";
};
};
jobType = subType {
# nix ci opts
nix = mkOption {
type = subType {
enable = mkOption {
type = types.bool;
default = cfg.nix-jobs-by-default;
description = "Handle this job as a nix job";
};
deps = mkOption {
type = types.listOf types.package;
default = [];
description = "Dependencies/packages to install for this job";
};
enable-runner-cache = 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.
'';
};
runner-cache-key = mkOption {
type = types.str;
default = "$CI_JOB_NAME-$CI_COMMIT_REF_SLUG";
description = "Cache key to use for the runner nix cache. Requires enable-runner-cache = true";
};
};
default = {};
description = "Configure Nix Gitlab CI for each job individually";
};
# gitlab opts
script = mkOption {
type = types.listOf types.str;
default = [];
};
stage = mkOption {
type = types.str;
default = "test";
};
image = mkOption {
type = types.str;
default = "$NIX_CI_IMAGE";
};
after_script = mkNullOption (types.listOf types.str);
allow_failure = mkNullOption (types.either types.attrs types.bool);
artifacts = mkNullOption (types.attrs);
before_script = mkNullOption (types.listOf types.str);
cache = mkNullOption (types.either (types.listOf types.attrs) types.attrs);
coverage = mkNullOption (types.str);
dependencies = mkNullOption (types.listOf types.str);
environment = mkNullOption (types.either types.attrs types.str);
extends = mkNullOption (types.str);
hooks = mkNullOption (types.attrs);
id_tokens = mkNullOption (types.attrs);
"inherit" = mkNullOption (types.attrs);
interruptible = mkNullOption (types.bool);
needs = mkNullOption (types.listOf (types.either types.str types.attrs));
publish = mkNullOption (types.str);
pages = mkNullOption (types.attrs);
parallel = mkNullOption (types.either types.int types.attrs);
release = mkNullOption (types.attrs);
retry = mkNullOption (types.either types.int types.attrs);
rules = mkNullOption (types.listOf types.attrs);
resource_group = mkNullOption (types.str);
secrets = mkNullOption (types.attrs);
services = mkNullOption (types.listOf types.attrs);
start_in = mkNullOption (types.str);
tags = mkNullOption (types.listOf types.str);
timeout = mkNullOption (types.str);
variables = mkNullOption (types.attrs);
when = mkNullOption (types.str);
};
ciType = subType {
config = mkOption {
type = configType;
description = ''
Configuration options for the nix part itself
'';
default = {};
};
image = mkNullOption (types.str);
variables = mkNullOption (types.attrs);
default = mkNullOption (types.attrs);
stages = mkNullOption (types.listOf types.str);
include = mkNullOption (types.attrs);
workflow = mkNullOption (types.attrs);
jobs = mkOption {
type = types.lazyAttrsOf jobType;
default = {};
};
};
in {
options = {
pipelines = mkOption {
type = types.lazyAttrsOf ciType;
description = ''
Create multiple GitLab CI pipelines.
See README.md for more information about how a pipeline is selected.
'';
default = {};
apply = op: let
# NOTE: show warning if "default" is set and config.ci is not {}
legacyMode = config.ci != {};
defaultExists = builtins.hasAttr "default" op;
value =
{
"default" = config.ci;
}
// op;
in
if defaultExists && legacyMode
then builtins.trace "Warning: config.ci is overwritten by pipelines.default" value
else value;
};
ci = mkOption {
type = ciType;
description = ''
Note: this is a shorthand for writing `pipelines."default"`
Generate a Gitlab CI configuration which can be used to trigger a child pipeline.
This will inject code which pre-downloads the nix deps before each job and adds them to PATH.
'';
type = types.submodule cilib.modules.nixCiSubmodule;
default = {};
};
};
config.legacyPackages = lib.fold (pipeline: acc: acc // pipeline) {} (
map (
pipeline_name:
(mkPipeline {
pipeline = config.pipelines."${pipeline_name}";
name = pipeline_name;
}).packages
) (builtins.attrNames config.pipelines)
);
config.legacyPackages = config.ci.packages;
}
);
}

View file

@ -1,49 +0,0 @@
{lib, ...} @ args: let
inherit (lib) isAttrs filterAttrs mapAttrs;
in rec {
prepend = key: arr: job:
job
// lib.optionalAttrs (job.nix.enable or false) {
${key} =
arr
++ (job.${key} or []);
};
append = key: arr: job:
job
// lib.optionalAttrs (job.nix.enable or false) {
${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);
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 {});
# 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 = [];
};
}

12
lib/impl/default.nix Normal file
View 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;};
}

123
lib/impl/helpers.nix Normal file
View file

@ -0,0 +1,123 @@
{
pkgs,
lib,
} @ args: let
inherit
(lib)
types
isAttrs
filterAttrs
mapAttrs
mkOption
mkOptionType
isType
literalExpression
pipe
hasInfix
concatMapAttrs
optionalAttrs
;
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:
pipe value [
builtins.toJSON
builtins.unsafeDiscardOutputDependency
builtins.unsafeDiscardStringContext
(builtins.toFile name)
];
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 = shouldContain: job:
concatMapAttrs (
name: value:
optionalAttrs ((hasInfix "/nix/store/" value) == shouldContain) {
${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 = typ:
(types.either unsetType typ)
// {
inherit (typ) description getSubOptions;
};
mkUnsetOption = opts:
mkOption (opts
// {
type = unsetOr opts.type;
default = opts.default or unset;
defaultText = literalExpression "unset";
});
eitherWithSubOptions = typ_one: typ_two:
(types.either typ_one typ_two)
// {
getSubOptions =
if (typ_one.getSubOptions "test" != {})
then typ_one.getSubOptions
else typ_two.getSubOptions;
};
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 = [];
};
}

View file

@ -1,21 +1,21 @@
{
lib,
pkgs,
...
helpers,
}: let
cilib = import ./. {inherit lib pkgs;};
inherit (cilib.helpers) filterJobVariables stdenvMinimal;
in {
mkJobDeps = {
inherit (lib) concatLines mapAttrsToList makeBinPath;
inherit (helpers) filterJobVariables stdenvMinimal;
in
{
key,
job,
nixConfig,
}: let
variablesWithStorePaths = filterJobVariables true job;
variableExports = lib.concatLines (
lib.mapAttrsToList (name: value: "export ${name}=\"${value}\"") variablesWithStorePaths
variableExports = concatLines (
mapAttrsToList (name: value: "export ${name}=\"${value}\"") variablesWithStorePaths
);
script = ''
export PATH="${lib.makeBinPath (job.nix.deps or [])}:$PATH";
export PATH="${makeBinPath (nixConfig.deps or [])}:$PATH";
# variables containing nix derivations:
${variableExports}
'';
@ -32,5 +32,4 @@ in {
passthru = {
inherit script;
};
};
}
}

43
lib/impl/jobPatched.nix Normal file
View file

@ -0,0 +1,43 @@
{
lib,
helpers,
}: let
inherit (lib) toList optionalAttrs optional;
inherit (helpers) prependToBeforeScript appendToAfterScript filterJobVariables;
in
{
key,
job,
pipelineName,
nixConfig,
}:
(builtins.removeAttrs job ["variables" "cache"])
// (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;
})
)

View file

@ -1,19 +1,19 @@
{
lib,
pkgs,
...
helpers,
}: let
cilib = import ./. {inherit lib pkgs;};
inherit (cilib.helpers) filterJobVariables;
in {
mkJobRun = {
inherit (lib) concatLines mapAttrsToList getExe;
inherit (helpers) filterJobVariables;
in
{
key,
job,
jobDeps,
}: let
variablesWithoutStorePaths = filterJobVariables false job;
variableExports = lib.concatLines (
lib.mapAttrsToList (name: value: "export ${name}=\"${value}\"") variablesWithoutStorePaths
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" ''
@ -24,25 +24,24 @@ in {
# run before_script, script and after_script
echo -e "\e[32mRunning before_script...\e[0m"
set -x
${lib.concatLines (job.before_script or [])}
${concatLines (job.before_script or [])}
{ set +x; } 2>/dev/null
echo -e "\e[32mRunning script...\e[0m"
set -x
${lib.concatLines job.script}
${concatLines job.script}
{ set +x; } 2>/dev/null
echo -e "\e[32mRunning after_script...\e[0m"
set -x
${lib.concatLines (job.after_script or [])}
${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 ${lib.getExe sandboxHelper} ${actualJobScript} $@
exec ${getExe sandboxHelper} ${actualJobScript} $@
''
// {
passthru = {
inherit jobDeps actualJobScript;
};
};
}
}

View 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
;
}

663
lib/impl/modules/job.nix Normal file
View file

@ -0,0 +1,663 @@
{
lib,
cilib,
...
}: let
inherit (lib) mkOption types filterAttrs;
inherit (cilib.helpers) filterUnset mkUnsetOption eitherWithSubOptions;
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`](#pipelinesnamejobsnamenixenablerunnercache).
'';
};
};
};
jobSubmodule = {
name,
config,
pipelineName,
pipelineConfig,
...
}: let
#
# GITLAB OPTIONS
#
gitlabOptions = {
# see https://docs.gitlab.com/ci/yaml/
after_script = mkUnsetOption {
type = types.listOf types.str;
description = ''
Override a set of commands that are executed after job.
[Docs](https://docs.gitlab.com/ci/yaml/#after_script)
'';
};
allow_failure = mkUnsetOption {
type = eitherWithSubOptions types.bool (types.submodule {
options = {
exit_codes = mkUnsetOption {
type = types.either types.number (types.listOf types.number);
description = ''
Use `allow_failure.exit_codes` to control when a job should be allowed to fail.
The job is `allow_failure = true` for any of the listed exit codes, and `allow_failure = false` for any other exit code.
'';
};
};
});
description = ''
Allow job to fail. A failed job does not cause the pipeline to fail.
[Docs](https://docs.gitlab.com/ci/yaml/#allow_failure)
'';
};
artifacts = mkUnsetOption {
# TODO: can be used in pipeline.default aswell
type = types.submodule {
options = {
paths = mkUnsetOption {
type = types.listOf types.str;
description = ''
Paths are relative to the project directory (`$CI_PROJECT_DIR`) and cant directly link outside it.
'';
};
excludes = mkUnsetOption {
type = types.listOf types.str;
description = ''
Use `exclude` to prevent files from being added to an artifacts archive.
'';
};
expire_in = mkUnsetOption {
type = types.str;
description = ''
Use `expire_in` to specify how long [job artifacts](https://docs.gitlab.com/ci/jobs/job_artifacts/) are stored before they expire and are deleted.
'';
};
expose_as = mkUnsetOption {
type = types.str;
description = ''
Use the `expose_as` keyword to [expose artifacts in the merge request UI](https://docs.gitlab.com/ci/jobs/job_artifacts/#link-to-job-artifacts-in-the-merge-request-ui).
'';
};
name = mkUnsetOption {
type = types.str;
description = ''
Use the `name` keyword to define the name of the created artifacts archive. You can specify a unique name for every archive.
'';
};
public = mkUnsetOption {
type = types.bool;
description = ''
Use `public` to determine whether the job artifacts should be publicly available.
'';
};
access = mkUnsetOption {
type = types.enum [
"all"
"developer"
"maintainer"
"none"
];
description = ''
Use `access` to determine who can access the job artifacts from the GitLab UI or API.
This option does not prevent you from forwarding artifacts to downstream pipelines.
'';
};
reports = mkUnsetOption {
type = types.attrs;
description = ''
Use `reports` to collect artifacts generated by included templates in jobs.
'';
};
untracked = mkUnsetOption {
type = types.bool;
description = ''
Use `untracked` to add all Git untracked files as artifacts (along with the paths defined in `paths`).
`untracked` ignores configuration in the repositorys .gitignore, so matching artifacts in .gitignore are included.
'';
};
when = mkUnsetOption {
type = types.enum [
"on_success"
"on_failure"
"always"
];
description = ''
Use `when` to upload artifacts on job failure or despite the failure.
'';
};
};
};
description = ''
List of files and directories to attach to a job on success.
[Docs](https://docs.gitlab.com/ci/yaml/#artifacts)
'';
};
before_script = mkUnsetOption {
type = types.listOf types.str;
description = ''
Override a set of commands that are executed before job.
[Docs](https://docs.gitlab.com/ci/yaml/#before_script)
'';
};
cache = mkUnsetOption {
# could be more granular
type = types.either (types.listOf types.attrs) types.attrs;
description = ''
List of files that should be cached between subsequent runs.
[Docs](https://docs.gitlab.com/ci/yaml/#cache)
'';
};
coverage = mkUnsetOption {
type = types.str;
description = ''
Code coverage settings for a given job.
[Docs](https://docs.gitlab.com/ci/yaml/#coverage)
'';
};
dast_configuration = mkUnsetOption {
type = types.attrs;
description = ''
Use configuration from DAST profiles on a job level.
[Docs](https://docs.gitlab.com/ci/yaml/#dast_configuration)
'';
};
dependencies = mkUnsetOption {
type = types.listOf types.str;
description = ''
Restrict which artifacts are passed to a specific job by providing a list of jobs to fetch artifacts from.
[Docs](https://docs.gitlab.com/ci/yaml/#dependencies)
'';
};
environment = mkUnsetOption {
type = eitherWithSubOptions types.str (types.submodule {
options = {
name = mkUnsetOption {
type = types.str;
example = "production";
description = ''
Set a name for an environment.
'';
};
url = mkUnsetOption {
type = types.str;
example = "https://prod.example.com";
description = ''
Set a URL for an environment.
'';
};
on_stop = mkUnsetOption {
type = types.str;
example = "down";
description = ''
Closing (stopping) environments can be achieved with the `on_stop` keyword defined under `environment`.
It declares a different job that runs to close the environment.
'';
};
action = mkUnsetOption {
type = types.enum ["start" "prepare" "stop" "verify" "access"];
description = ''
Use the `action` keyword to specify how the job interacts with the environment.
'';
};
auto_stop_in = mkUnsetOption {
type = types.str;
description = ''
The `auto_stop_in` keyword specifies the lifetime of the environment.
When an environment expires, GitLab automatically stops it.
'';
};
kubernetes = mkUnsetOption {
type = types.attrs; # no we don't go that deep :D
description = ''
Use the `kubernetes` keyword to configure the
[dashboard for Kubernetes](https://docs.gitlab.com/ci/environments/kubernetes_dashboard/) and
[GitLab-managed Kubernetes resources](https://docs.gitlab.com/user/clusters/agent/managed_kubernetes_resources/)
for an environment.
'';
};
deployment_tier = mkUnsetOption {
type = types.enum [
"production"
"staging"
"testing"
"development"
"other"
];
description = ''
Use the `deployment_tier` keyword to specify the tier of the deployment environment.
'';
};
};
});
description = ''
Name of an environment to which the job deploys.
See the implementation for nested options, or check out the docs:
[Docs](https://docs.gitlab.com/ci/yaml/#environment)
'';
example = {
name = "review/$CI_COMMIT_REF_SLUG";
url = "https://$CI_COMMIT_REF_SLUG.review.example.com";
action = "stop";
auto_stop_in = "1 day";
deployment_tier = "staging";
};
};
extends = mkUnsetOption {
type = types.either types.str (types.listOf types.str);
description = ''
Configuration entries that this job inherits from.
[Docs](https://docs.gitlab.com/ci/yaml/#extends)
'';
};
hooks = mkUnsetOption {
type = types.attrs;
description = ''
Use `hooks` to specify lists of commands to execute on the runner at certain stages of job execution,
like before retrieving the Git repository.
[Docs](https://docs.gitlab.com/ci/yaml/#hooks)
'';
};
identity = mkUnsetOption {
type = types.str;
description = ''
Authenticate with third party services using identity federation.
[Docs](https://docs.gitlab.com/ci/yaml/#identity)
'';
};
id_tokens = mkUnsetOption {
type = types.attrs;
description = ''
Use `id_tokens` to create [ID tokens](https://docs.gitlab.com/ci/secrets/id_token_authentication/) to authenticate with third party services
[Docs](https://docs.gitlab.com/ci/yaml/#id_tokens)
'';
example = {
ID_TOKEN_1.aud = "https://vault.example.com";
ID_TOKEN_2.aud = [
"https://gcp.com"
"https://aws.com"
];
SIGSTORE_ID_TOKEN.aud = "sigstore";
};
};
image = mkOption {
# could be more granular
type = types.either types.str types.attrs;
description = ''
Container/OCI image to use for this job.
!!! warning
Setting this will mess with Nix-GitLab-CI, so be careful and only use for non-nix jobs.
'';
default = "$NIX_CI_IMAGE";
example = {
name = "super/sql:experimental";
entrypoint = [""];
pull_policy = "if-not-present";
docker = {
platform = "arm64/v8";
user = "dave";
};
kubernetes.user = "1001";
};
};
"inherit" = mkUnsetOption {
type = types.submodule {
options = {
default = mkUnsetOption {
type = types.either types.bool (types.listOf types.str);
description = ''
Use `inherit.default` to control the inheritance of [default keywords](https://docs.gitlab.com/ci/yaml/#default).
'';
};
variables = mkUnsetOption {
type = types.either types.bool (types.listOf types.str);
description = ''
Use `inherit.variables` to control the inheritance of [default variables](https://docs.gitlab.com/ci/yaml/#default-variables).
'';
};
};
};
description = ''
Select which global defaults all jobs inherit.
[Docs](https://docs.gitlab.com/ci/yaml/#inherit)
'';
};
interruptible = mkUnsetOption {
type = types.bool;
description = ''
Defines if a job can be canceled when made redundant by a newer run.
[Docs](https://docs.gitlab.com/ci/yaml/#interruptible)
'';
};
manual_confirmation = mkUnsetOption {
type = types.str;
description = ''
Define a custom confirmation message for a manual job.
[Docs](https://docs.gitlab.com/ci/yaml/#manual_confirmation)
'';
};
needs = mkUnsetOption {
# could be done more granular
type = types.listOf (types.either types.str types.attrs);
description = ''
Execute jobs earlier than the stage ordering.
[Docs](https://docs.gitlab.com/ci/yaml/#needs)
'';
};
pages = mkUnsetOption {
type = eitherWithSubOptions types.bool (types.submodule {
options = {
publish = mkUnsetOption {
type = types.str;
description = ''
Use `pages.publish` to configure the content directory of a [`pages` job](https://docs.gitlab.com/ci/yaml/#pages).
'';
};
path_prefix = mkUnsetOption {
type = types.str;
description = ''
Use `pages.path_prefix` to configure a path prefix for [parallel deployments](https://docs.gitlab.com/user/project/pages/#parallel-deployments) of GitLab Pages.
'';
};
expire_in = mkUnsetOption {
type = types.str;
description = ''
Use `expire_in` to specify how long a deployment should be available before it expires.
After the deployment is expired, its deactivated by a cron job running every 10 minutes.
'';
};
};
});
description = ''
Upload the result of a job to use with GitLab Pages.
[Docs](https://docs.gitlab.com/ci/yaml/#pages)
'';
};
parallel = mkUnsetOption {
type = eitherWithSubOptions types.number (types.listOf (types.submodule {
options = {
matrix = mkUnsetOption {
type = types.attrs;
description = ''
Use `parallel.matrix` to run a job multiple times in parallel in a single pipeline, but with different variable values for each instance of the job.
'';
};
};
}));
description = ''
How many instances of a job should be run in parallel.
[Docs](https://docs.gitlab.com/ci/yaml/#parallel)
'';
example = {
matrix = [
{
PROVIDER = "aws";
STACK = [
"monitoring"
"app1"
"app2"
];
}
{
PROVIDER = "ovh";
STACK = ["monitoring" "backup" "app"];
}
{
PROVIDER = ["gcp" "vultr"];
STACK = ["data" "processing"];
}
];
};
};
release = mkUnsetOption {
# could be more granular
type = types.attrs;
description = ''
Instructs the runner to generate a [release](https://docs.gitlab.com/user/project/releases/) object.
[Docs](https://docs.gitlab.com/ci/yaml/#release)
'';
};
resource_group = mkUnsetOption {
type = types.str;
description = ''
Limit job concurrency.
[Docs](https://docs.gitlab.com/ci/yaml/#resource_group)
'';
};
retry = mkUnsetOption {
type = eitherWithSubOptions (types.ints.between 0 2) (types.submodule {
options = {
exit_codes = mkUnsetOption {
type = types.either types.int (types.listOf types.int);
description = ''
Use `retry.exit_codes` with `retry.max` to retry jobs for only specific failure cases.
'';
};
when = mkUnsetOption {
type = types.either types.str (types.listOf types.str);
description = ''
Use `retry.when` with `retry.max` to retry jobs for only specific failure cases.
'';
};
max = mkUnsetOption {
type = types.ints.between 0 2;
description = ''
`retry.max` is the maximum number of retries, like retry, and can be 0, 1, or 2.
'';
};
};
});
description = ''
When and how many times a job can be auto-retried in case of a failure.
[Docs](https://docs.gitlab.com/ci/yaml/#retry)
'';
};
rules = mkUnsetOption {
# could be more granular
type = types.listOf types.attrs;
description = ''
List of conditions to evaluate and determine selected attributes of a job, and whether or not its created.
[Docs](https://docs.gitlab.com/ci/yaml/#rules)
'';
};
script = mkOption {
type = types.listOf types.str;
description = ''
Shell script that is executed by a runner.
[Docs](https://docs.gitlab.com/ci/yaml/#script)
'';
};
secrets = mkUnsetOption {
# could be more granular
type = types.attrs;
description = ''
The CI/CD secrets the job needs.
[Docs](https://docs.gitlab.com/ci/yaml/#secrets)
'';
};
services = mkUnsetOption {
# could be more granular
type = types.attrs;
description = ''
Use Docker services images.
[Docs](https://docs.gitlab.com/ci/yaml/#services)
'';
};
stage = mkOption {
type = types.str;
description = ''
Defines a job stage.
[Docs](https://docs.gitlab.com/ci/yaml/#stage)
'';
};
tags = mkUnsetOption {
type = types.listOf types.str;
description = ''
List of tags that are used to select a runner.
[Docs](https://docs.gitlab.com/ci/yaml/#tags)
'';
};
timeout = mkUnsetOption {
type = types.str;
description = ''
Define a custom job-level timeout that takes precedence over the project-wide setting.
[Docs](https://docs.gitlab.com/ci/yaml/#timeout)
'';
};
trigger = mkUnsetOption {
# could be more granular
type = types.either types.str types.attrs;
description = ''
Defines a downstream pipeline trigger.
[Docs](https://docs.gitlab.com/ci/yaml/#trigger)
'';
};
when = mkUnsetOption {
type = types.enum ["on_success" "on_failure" "never" "always" "manual" "delayed"];
description = ''
When to run job. See also [`manual_confirmation`](#pipelinesnamejobsnamemanual_confirmation)
[Docs](https://docs.gitlab.com/ci/yaml/#when)
'';
};
variables = mkUnsetOption {
type = types.attrsOf types.str;
description = ''
You can use job variables in commands in the jobs `script`, `before_script`, or `after_script` sections, and also with some job keywords.
Check the **Supported values** section of each job keyword to see if it supports variables.
[Docs](https://docs.gitlab.com/ci/yaml/#job-variables)
'';
};
};
in {
options =
{
nix = mkOption {
description = ''
Nix-GitLab-CI config options for this job.
'';
type = types.submoduleWith {
modules = [jobConfigSubmodule];
specialArgs.pipelineConfig = pipelineConfig;
};
default = {};
};
finalConfig = mkOption {
description = ''
Final configuration of this job. (readonly)
'';
readOnly = true;
internal = true;
type = types.attrs;
};
depsDrv = mkOption {
description = ''
Derivation containing all the dependencies of this job. (readonly)
'';
readOnly = true;
internal = true;
type = types.package;
};
runnerDrv = mkOption {
description = ''
Derivation containing the script for running the job locally. (readonly)
'';
readOnly = true;
internal = true;
type = types.package;
};
packages = mkOption {
description = ''
Final packages for this job, eg. for running the job or getting it's deps. (readonly)
'';
readOnly = true;
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;
};
};
};
}

View file

@ -0,0 +1,228 @@
{
lib,
cilib,
jobSubmodule,
...
}: let
inherit (lib) mkOption types filterAttrs mergeAttrsList mapAttrs mkRenamedOptionModule literalExpression;
inherit (cilib.helpers) filterUnset unset mkUnsetOption toYaml toYamlPretty eitherWithSubOptions;
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 = {
default = mkOption {
type = types.submodule {
# allow other keys aswell, maybe define them in the future? https://docs.gitlab.com/ci/yaml/#default
freeformType = types.anything;
options = {
image = mkUnsetOption {
type = types.str;
description = ''
Default image to use for this entire pipeline.
!!! note
Moved from top level to `default`: [GitLab Docs](https://docs.gitlab.com/ci/yaml/deprecated_keywords/).
'';
};
services = mkUnsetOption {
type = types.listOf types.anything;
description = ''
!!! note
Moved from top level to `default`: [GitLab Docs](https://docs.gitlab.com/ci/yaml/deprecated_keywords/).
'';
};
cache = mkUnsetOption {
# could be more granular
type = types.either (types.listOf types.attrs) types.attrs;
description = ''
!!! note
Moved from top level to `default`: [GitLab Docs](https://docs.gitlab.com/ci/yaml/deprecated_keywords/).
'';
};
before_script = mkUnsetOption {
type = types.listOf types.str;
description = ''
!!! note
Moved from top level to `default`: [GitLab Docs](https://docs.gitlab.com/ci/yaml/deprecated_keywords/).
'';
};
after_script = mkUnsetOption {
type = types.listOf types.str;
description = ''
!!! note
Moved from top level to `default`: [GitLab Docs](https://docs.gitlab.com/ci/yaml/deprecated_keywords/).
'';
};
};
};
# required for it to show up in the docs, but not in the config
default = unset;
defaultText = literalExpression "unset";
description = ''
Custom default values for job keywords.
'';
};
include = mkUnsetOption {
type = types.attrs;
description = ''
Import configuration from other YAML files.
[Docs](https://docs.gitlab.com/ci/yaml/#include)
'';
};
stages = mkOption {
type = types.listOf types.str;
default = [];
# .pre and .post always exist
apply = val: [".pre"] ++ val ++ [".post"];
description = ''
The names and order of the pipeline stages.
[Docs](https://docs.gitlab.com/ci/yaml/#stages)
!!! note
`.pre` and `.post` are added automatically.
'';
};
variables = mkUnsetOption {
# default/global variables can have descriptions etc.
type = types.attrsOf (eitherWithSubOptions types.str (types.submodule {
options = {
description = mkUnsetOption {
type = types.str;
description = ''
Use the `description` keyword to define a description for a default variable.
'';
};
value = mkUnsetOption {
type = types.str;
description = ''
Use the `value` keyword to define a pipeline-level (default) variables value.
'';
};
options = mkUnsetOption {
type = types.listOf types.str;
description = ''
Use `options` to define an array of values that are [selectable in the UI when running a pipeline manually](https://docs.gitlab.com/ci/pipelines/#configure-a-list-of-selectable-prefilled-variable-values).
'';
};
expand = mkUnsetOption {
type = types.bool;
description = ''
Use the `expand` keyword to configure a variable to be expandable or not.
'';
};
};
}));
description = ''
Define default CI/CD variables for all jobs in the pipeline.
Supports strings or attrs as values, for more info see [here](https://docs.gitlab.com/ci/yaml/#variablesdescription).
[Docs](https://docs.gitlab.com/ci/yaml/#default-variables)
'';
};
workflow = mkUnsetOption {
type = types.attrs;
description = ''
Control what types of pipeline run.
[Docs](https://docs.gitlab.com/ci/yaml/#workflow)
'';
};
};
in {
_file = ./pipeline.nix;
# from https://docs.gitlab.com/ci/yaml/#default
imports = builtins.map (val: mkRenamedOptionModule [val] ["default" val]) [
"after_script"
"artifacts"
"before_script"
"cache"
"hooks"
"id_tokens"
"image"
"interruptible"
"retry"
"services"
"tags"
"timeout"
];
options =
{
nix = mkOption {
description = ''
Nix-GitLab-CI config options for this pipeline.
'';
type = types.submoduleWith {
modules = [pipelineConfigSubmodule];
specialArgs.rootConfig = rootConfig;
};
default = {};
};
finalConfig = mkOption {
description = ''
Final config of the pipeline. (readonly)
'';
readOnly = true;
type = types.attrs;
};
packages = mkOption {
description = ''
Final packages for use in CI. (readonly)
'';
readOnly = 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}" = 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));
};
};
in {
inherit pipelineSubmodule pipelineConfigSubmodule;
}

74
lib/impl/modules/root.nix Normal file
View file

@ -0,0 +1,74 @@
{
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`](#pipelinesnamejobsnamenixenable)
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. (readonly)
'';
readOnly = true;
type = types.attrsOf types.package;
};
soonix = mkOption {
description = ''
Soonix config for generating `.gitlab-ci.yml`. (readonly)
See [`config.soonix`](#configsoonix) for configuring this.
'';
readOnly = 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;
};
};
}

View file

@ -0,0 +1,69 @@
{
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;
};
};
}

View file

@ -1,20 +1,20 @@
{
lib,
pkgs,
...
helpers,
mkJobDeps,
mkJobRun,
mkJobPatched,
}: let
cilib = import ./. {inherit lib pkgs;};
inherit (cilib.helpers) filterAttrsRec customMapAttrs toYaml;
inherit (cilib.jobDeps) mkJobDeps;
inherit (cilib.jobRun) mkJobRun;
inherit (cilib.jobPatch) mkJobPatched;
in {
mkPipeline = {
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" "config"]);
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
@ -22,7 +22,7 @@ in {
jobsMappedForDeps =
customMapAttrs (key: job: {
name = "gitlab-ci:pipeline:${name}:job-deps:${key}";
value = mkJobDeps {inherit key job;};
value = mkJobDeps {inherit key job nixConfig;};
})
jobs;
# allows the user to directly run the script
@ -30,7 +30,7 @@ in {
customMapAttrs (key: job: {
name = "gitlab-ci:pipeline:${name}:job:${key}";
value = mkJobRun {
inherit key job;
inherit key job nixConfig;
jobDeps = jobsMappedForDeps."gitlab-ci:pipeline:${name}:job-deps:${key}";
};
})
@ -39,10 +39,10 @@ in {
jobsPatched =
customMapAttrs (key: job: {
name = key;
value = assert lib.assertMsg (builtins.elem job.stage (rest.stages or [])) "stage '${job.stage}' of job '${key}' does not exist";
value = assert assertMsg (builtins.elem job.stage (rest.stages or [])) "stage '${job.stage}' of job '${key}' does not exist";
mkJobPatched {
inherit key job;
pipeline_name = name;
inherit key job nixConfig;
pipelineName = name;
};
})
jobs;
@ -53,9 +53,9 @@ in {
# 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;
};
}
}

View file

@ -35,6 +35,7 @@ done
if [ "$NO_SANDBOX" = false ]; then
echo "Running with simple sandboxing"
TMPDIR=$(mktemp -dt "nix-gitlab-ci.XXX")
if [ "$KEEP_TMP" = false ]; then
trap "rm -rf '$TMPDIR'" EXIT
else
@ -49,7 +50,6 @@ if [ "$NO_SANDBOX" = false ]; then
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

View file

@ -1,49 +0,0 @@
{
lib,
pkgs,
...
}: let
cilib = import ./. {inherit lib pkgs;};
inherit (lib) toList;
inherit (cilib.helpers) prependToBeforeScript appendToAfterScript filterJobVariables;
in {
mkJobPatched = {
key,
job,
pipeline_name,
}:
builtins.removeAttrs (
(prependToBeforeScript [
"source setup_nix_ci \"gitlab-ci:pipeline:${pipeline_name}:job-deps:${key}\""
]
(appendToAfterScript [
"finalize_nix_ci"
]
job))
// lib.optionalAttrs job.nix.enable (
(let
variables =
(filterJobVariables false job)
// lib.optionalAttrs job.nix.enable-runner-cache {
NIX_CI_CACHE_STRATEGY = "runner";
};
in
# filter empty variables
lib.optionalAttrs (variables != {}) {
inherit variables;
})
// (let
cache =
(toList (job.cache or []))
++ (lib.optional (job.nix.enable-runner-cache) {
key = job.nix.runner-cache-key;
paths = [".nix-cache/"];
});
in
# filter empty cache
lib.optionalAttrs (cache != []) {
inherit cache;
})
)
) ["nix"];
}

48
nix/packages/pkgs.nix Normal file
View 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"];
};
};
}

View file

@ -38,4 +38,3 @@ echo -e "\\e[0Ksection_start:`date +%s`:finalize_nix_ci[collapsed=true]\\r\\e[0K
echo "Caching disabled, not uploading $COUNT new store entries..."
fi
echo -e "\\e[0Ksection_end:`date +%s`:finalize_nix_ci\\r\\e[0K"

View file

@ -46,4 +46,3 @@ echo -e "\\e[0Ksection_start:`date +%s`:nix_setup[collapsed=true]\\r\\e[0KSettin
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"

99
nix/repo/ci.nix Normal file
View file

@ -0,0 +1,99 @@
{inputs, ...}: let
inherit (inputs) cilib;
in
cilib.mkCI {
config.soonix = {
componentUrl = "$CI_SERVER_FQDN/$CI_PROJECT_PATH/nix-gitlab-ci";
componentVersion = "$CI_COMMIT_SHORT_SHA";
componentInputs.cache_files = ["flake.*" "nix/repo/ci.nix"];
# bootstrapping still needs to be done in the gitlab-ci.yml directly,
# the child pipeline can then use the built images to test them
extraData = {
stages = ["build-images" "build" "trigger"];
"build:image" = {
stage = "build-images";
parallel.matrix = [
{ARCH = ["x86_64-linux" "aarch64-linux"];}
];
image = "nixpkgs/nix-flakes:latest";
script = ["nix build .#image --system $ARCH"];
after_script = ["install -D result dist/nix-ci-$ARCH.tar.gz"];
artifacts.paths = ["dist"];
};
"deploy:image" = {
stage = "build-images";
image = "nixpkgs/nix-flakes:latest";
needs = ["build:image"];
before_script = [
# sh
''
nix profile install nixpkgs#buildah
export PATH="$PATH:$HOME/.nix-profile/bin"
export REGISTRY_AUTH_FILE=''${HOME}/auth.json
echo "$CI_REGISTRY_PASSWORD" | buildah login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
mkdir -p /etc/containers && echo '{"default":[{"type":"insecureAcceptAnything"}]}' > /etc/containers/policy.json
mkdir -p /var/tmp
''
];
script = [
# sh
''
export NORMALIZED_BRANCH=''${CI_COMMIT_BRANCH/\//-}
buildah manifest create localhost/nix-ci
buildah manifest add localhost/nix-ci docker-archive:dist/nix-ci-x86_64-linux.tar.gz
buildah manifest add localhost/nix-ci docker-archive:dist/nix-ci-aarch64-linux.tar.gz
buildah manifest push --all localhost/nix-ci docker://''${CI_REGISTRY_IMAGE}/nix-ci:''${CI_COMMIT_SHORT_SHA}
# branches
if [ -z "$CI_COMMIT_TAG" ]; then
buildah manifest push --all localhost/nix-ci docker://''${CI_REGISTRY_IMAGE}/nix-ci:''${NORMALIZED_BRANCH/main/latest}
fi
# tags
if [ -n "$CI_COMMIT_TAG" ]; then
buildah manifest push --all localhost/nix-ci docker://''${CI_REGISTRY_IMAGE}/nix-ci:''${CI_COMMIT_TAG}
fi
''
];
};
};
};
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";
}
];
};
};
};
}

35
nix/repo/devShells.nix Normal file
View file

@ -0,0 +1,35 @@
{
cell,
inputs,
...
}: let
inherit (inputs) pkgs devshell treefmt soonix;
inherit (cell) ci;
in {
default = devshell.mkShell {
imports = [soonix.devshellModule];
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" ".gitlab-ci.yml"];
mdformat.command = let
pkg = pkgs.python3.withPackages (p: [
p.mdformat
p.mdformat-mkdocs
]);
in "${pkg}/bin/mdformat";
};
})
];
soonix.hooks."ci" = ci.soonix;
};
}

80
nix/repo/docs.nix Normal file
View file

@ -0,0 +1,80 @@
{inputs, ...}: let
inherit (inputs) pkgs cilib doclib;
optionsDoc = doclib.mkOptionDocs {
module = cilib.modules.nixCiSubmodule;
roots = [
{
url = "https://gitlab.com/TECHNOFAB/nix-gitlab-ci/-/blob/main/lib";
path = "${inputs.self}/lib";
}
];
};
optionsDocs = pkgs.runCommand "options-docs" {} ''
mkdir -p $out
ln -s ${optionsDoc} $out/options.md
'';
in
(doclib.mkDocs {
docs."default" = {
base = "${inputs.self}";
path = "${inputs.self}/docs";
material = {
enable = true;
colors = {
primary = "deep orange";
accent = "orange";
};
umami = {
enable = true;
src = "https://analytics.tf/umami";
siteId = "28f7c904-db22-4c2b-9ee4-ed42e14b6db9";
domains = ["nix-gitlab-ci.projects.tf"];
};
};
macros = {
enable = true;
includeDir = toString optionsDocs;
};
config = {
site_name = "Nix-GitLab-CI";
site_url = "https://nix-gitlab-ci.projects.tf";
repo_name = "TECHNOFAB/nix-gitlab-ci";
repo_url = "https://gitlab.com/TECHNOFAB/nix-gitlab-ci";
extra_css = ["style.css"];
theme = {
logo = "images/logo.svg";
icon.repo = "simple/gitlab";
favicon = "images/logo.svg";
};
nav = [
{"Introduction" = "index.md";}
{"Setup" = "setup.md";}
{"Usage" = "usage.md";}
{"CI/CD Component" = "cicd_component.md";}
{"Environment Variables" = "environment_variables.md";}
{"Caching" = "caching.md";}
{"Multiple Pipelines" = "multi_pipeline.md";}
{"Soonix Integration" = "soonix.md";}
{"Utilities" = "utilities.md";}
{"Kubernetes Runner Example" = "kubernetes_runner.md";}
{"Example Configs" = "examples.md";}
{"Options" = "options.md";}
];
markdown_extensions = [
{
"pymdownx.highlight".pygments_lang_class = true;
}
"pymdownx.inlinehilite"
"pymdownx.snippets"
"pymdownx.superfences"
"pymdownx.escapeall"
"fenced_code"
"admonition"
];
};
};
}).packages
// {
inherit optionsDocs;
}

99
nix/repo/flake.lock generated Normal file
View file

@ -0,0 +1,99 @@
{
"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"
}
},
"nixmkdocs-lib": {
"locked": {
"dir": "lib",
"lastModified": 1757055638,
"narHash": "sha256-KHYSkEreFe4meXzSdEbknC/HwaQSNClQkc8vzHlAsMM=",
"owner": "TECHNOFAB",
"repo": "nixmkdocs",
"rev": "7840a5febdbeaf2da90babf6c94b3d0929d2bf74",
"type": "gitlab"
},
"original": {
"dir": "lib",
"owner": "TECHNOFAB",
"repo": "nixmkdocs",
"type": "gitlab"
}
},
"nixtest-lib": {
"locked": {
"dir": "lib",
"lastModified": 1756812148,
"narHash": "sha256-0g8KNk4zoLApA51PBHOWqPLRYpprjrQuSzNCjfBQgu8=",
"owner": "TECHNOFAB",
"repo": "nixtest",
"rev": "5741109cc9ec2b6d41b56abd3f5bc51ed7a9a228",
"type": "gitlab"
},
"original": {
"dir": "lib",
"owner": "TECHNOFAB",
"repo": "nixtest",
"type": "gitlab"
}
},
"root": {
"inputs": {
"devshell-lib": "devshell-lib",
"nixmkdocs-lib": "nixmkdocs-lib",
"nixtest-lib": "nixtest-lib",
"soonix-lib": "soonix-lib",
"treefmt-nix": "treefmt-nix"
}
},
"soonix-lib": {
"locked": {
"dir": "lib",
"lastModified": 1756797658,
"narHash": "sha256-4rkyP4oaoqG/FFVL7W8U+8hGer4tOBPff/2SeN5tJYQ=",
"owner": "TECHNOFAB",
"repo": "soonix",
"rev": "3baef660cf8b87391d475a0455dd66fae0e60008",
"type": "gitlab"
},
"original": {
"dir": "lib",
"owner": "TECHNOFAB",
"repo": "soonix",
"type": "gitlab"
}
},
"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
}

22
nix/repo/flake.nix Normal file
View file

@ -0,0 +1,22 @@
{
inputs = {
devshell-lib.url = "gitlab:rensa-nix/devshell?dir=lib";
nixtest-lib.url = "gitlab:TECHNOFAB/nixtest?dir=lib";
soonix-lib.url = "gitlab:TECHNOFAB/soonix?dir=lib";
nixmkdocs-lib.url = "gitlab:TECHNOFAB/nixmkdocs?dir=lib";
treefmt-nix = {
url = "github:numtide/treefmt-nix";
flake = false;
};
};
outputs = i:
i
// {
devshell = i.devshell-lib.lib {inherit (i.parent) pkgs;};
soonix = i.soonix-lib.lib {inherit (i.parent) pkgs;};
ntlib = i.nixtest-lib.lib {inherit (i.parent) pkgs;};
doclib = i.nixmkdocs-lib.lib {inherit (i.parent) pkgs;};
cilib = import "${i.parent.self}/lib" {inherit (i.parent) pkgs;};
treefmt = import i.treefmt-nix;
};
}

10
nix/repo/tests.nix Normal file
View file

@ -0,0 +1,10 @@
{inputs, ...}: let
inherit (inputs) pkgs ntlib cilib;
in {
tests = ntlib.mkNixtest {
modules = ntlib.autodiscover {dir = "${inputs.self}/tests";};
args = {
inherit pkgs ntlib cilib;
};
};
}

View file

@ -1 +0,0 @@
{"docs":{"after_script":["finalize_nix_ci"],"artifacts":{"paths":["public"]},"before_script":["source setup_nix_ci \"gitlab-ci:pipeline:default:job-deps:docs\""],"image":"$NIX_CI_IMAGE","script":["nix build .#docs:default\nmkdir -p public\ncp -r result/. public/\n"],"stage":"build"},"nixtest":{"after_script":["finalize_nix_ci"],"allow_failure":true,"artifacts":{"reports":{"junit":"junit.xml"},"when":"always"},"before_script":["source setup_nix_ci \"gitlab-ci:pipeline:default:job-deps:nixtest\""],"image":"$NIX_CI_IMAGE","script":["nix run .#nixtests:run -- --junit=junit.xml --pure"],"stage":"nixtest"},"pages":{"artifacts":{"paths":["public"]},"image":"alpine:latest","rules":[{"if":"$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"}],"script":["true"],"stage":"deploy"},"stages":["test","nixtest","build","deploy"],"test":{"after_script":["finalize_nix_ci"],"before_script":["source setup_nix_ci \"gitlab-ci:pipeline:default:job-deps:test\""],"cache":[{"key":"$CI_JOB_NAME-$CI_COMMIT_REF_SLUG","paths":[".nix-cache/"]}],"image":"$NIX_CI_IMAGE","script":["hello","curl google.de","echo $TEST $TEST_WITH_DERIVATION"],"stage":"test","variables":{"NIX_CI_CACHE_STRATEGY":"runner","TEST":"test"}},"test-default":{"after_script":["finalize_nix_ci"],"before_script":["source setup_nix_ci \"gitlab-ci:pipeline:default:job-deps:test-default\""],"image":"$NIX_CI_IMAGE","script":["hello"],"stage":"test"},"test-non-nix":{"image":"alpine:latest","script":["echo \"This job will not be modified to use nix\""],"stage":"test"}}

View file

@ -1 +0,0 @@
{"stages":["test"],"test":{"after_script":["finalize_nix_ci"],"before_script":["source setup_nix_ci \"gitlab-ci:pipeline:non-default:job-deps:test\""],"image":"$NIX_CI_IMAGE","script":["echo Hello from another pipeline"],"stage":"test"}}

View file

@ -110,3 +110,4 @@ nix-ci:trigger:
strategy: depend
forward:
pipeline_variables: true

View file

@ -1,140 +0,0 @@
{
lib,
pkgs,
...
}: let
cilib = import ./../lib {inherit lib pkgs;};
in {
nixtest.suites."CI Lib" = {
pos = __curPos;
tests = let
inherit (cilib.jobDeps) mkJobDeps;
inherit (cilib.jobRun) mkJobRun;
inherit (cilib.jobPatch) mkJobPatched;
inherit (cilib.pipeline) mkPipeline;
deps = mkJobDeps {
key = "test";
job = {
nix.deps = [pkgs.hello];
variables.TEST = "${pkgs.curl}";
};
};
in [
{
name = "jobDeps";
type = "script";
script =
# sh
''
export PATH=${lib.makeBinPath [pkgs.gnugrep]}
grep -q "/nix/store" ${deps}
grep -q 'hello/bin:$PATH' ${deps}
grep -q "export TEST=" ${deps}
grep -q "curl" ${deps}
'';
}
{
name = "jobRun";
type = "script";
script = let
run = mkJobRun {
key = "test";
job.script = ["hello"];
jobDeps = deps;
};
in
# sh
''
export PATH=${lib.makeBinPath [pkgs.gnugrep]}
grep -q "sandbox-helper" ${run}/bin/gitlab-ci-job:test
grep -q "gitlab-ci-job-test-raw" ${run}/bin/gitlab-ci-job:test
grep -q "gitlab-ci-job-deps-test" ${run.passthru.actualJobScript}
grep -q "Running script..." ${run.passthru.actualJobScript}
grep -q "hello" ${run.passthru.actualJobScript}
'';
}
{
name = "jobPatched nix disabled";
expected = {};
actual = mkJobPatched {
key = "test";
pipeline_name = "test";
job.nix.enable = false;
};
}
{
name = "jobPatched without runner cache";
expected = {
after_script = ["finalize_nix_ci"];
before_script = ["source setup_nix_ci \"gitlab-ci:pipeline:test:job-deps:test\""];
};
actual = mkJobPatched {
key = "test";
pipeline_name = "test";
job.nix = {
enable = true;
enable-runner-cache = false;
};
};
}
{
name = "jobPatched with runner cache";
expected = {
after_script = ["finalize_nix_ci"];
before_script = ["source setup_nix_ci \"gitlab-ci:pipeline:test:job-deps:test\""];
cache = [
{
key = "test";
paths = [".nix-cache/"];
}
];
variables."NIX_CI_CACHE_STRATEGY" = "runner";
};
actual = mkJobPatched {
key = "test";
pipeline_name = "test";
job.nix = {
enable = true;
enable-runner-cache = true;
runner-cache-key = "test";
};
};
}
{
name = "mkPipeline empty";
expected = {};
actual =
(mkPipeline {
name = "test";
pipeline.jobs = {};
}).finalConfig;
}
{
name = "mkPipeline empty packages";
type = "script";
script = let
pipeline = builtins.toFile "pipeline-test" (builtins.toJSON
(mkPipeline {
name = "test";
pipeline.jobs = {};
}).packages);
in
# sh
''
set -euo pipefail
export PATH=${lib.makeBinPath [pkgs.jq pkgs.gnugrep pkgs.coreutils]}
# single key
jq 'keys | length == 1' "${pipeline}" | grep -q true
# key is exactly "gitlab-ci:pipeline:test"
jq -r 'keys[0]' "${pipeline}" | grep -qx "gitlab-ci:pipeline:test"
# value contains "/nix/store/"
jq -r '.["gitlab-ci:pipeline:test"]' "${pipeline}" | grep -q "/nix/store/"
# value contains "gitlab-ci-test.yml"
jq -r '.["gitlab-ci:pipeline:test"]' "${pipeline}" | grep -q "gitlab-ci-test.yml"
# file only contains "{}"
[[ "$(cat $(jq -r '.["gitlab-ci:pipeline:test"]' "${pipeline}"))" == "{}" ]]
'';
}
];
};
}

168
tests/cilib_test.nix Normal file
View file

@ -0,0 +1,168 @@
{
pkgs,
cilib,
ntlib,
...
}: {
suites."CI Lib" = {
pos = __curPos;
tests = let
inherit (cilib) mkJobDeps mkJobRun mkJobPatched mkPipeline;
deps = mkJobDeps {
key = "test";
job = {
variables.TEST = "${pkgs.curl}";
};
nixConfig = {
enable = true;
deps = [pkgs.hello];
};
};
in [
{
name = "jobDeps";
type = "script";
script =
# sh
''
${ntlib.helpers.path [pkgs.gnugrep]}
${ntlib.helpers.scriptHelpers}
assert_file_contains ${deps} "/nix/store" "should contain nix store path"
assert_file_contains ${deps} '-hello-.*/bin:$PATH' "should contain hello"
assert_file_contains ${deps} "export TEST=" "should export TEST"
assert_file_contains ${deps} "curl" "should contain curl"
'';
}
{
name = "jobRun";
type = "script";
script = let
run = mkJobRun {
key = "test";
job.script = ["hello"];
jobDeps = deps;
};
in
# sh
''
${ntlib.helpers.path [pkgs.gnugrep]}
${ntlib.helpers.scriptHelpers}
assert_file_contains ${run}/bin/gitlab-ci-job:test "sandbox-helper" "should contain sandbox-helper"
assert_file_contains ${run}/bin/gitlab-ci-job:test "gitlab-ci-job-test-raw" "should contain job name"
assert_file_contains ${run.passthru.actualJobScript} "gitlab-ci-job-deps-test" "should contain job name"
assert_file_contains ${run.passthru.actualJobScript} "Running script..." "should contain 'Running script...'"
assert_file_contains ${run.passthru.actualJobScript} "hello" "should contain hello"
'';
}
{
name = "jobPatched nix disabled";
expected = {};
actual = mkJobPatched {
key = "test";
pipelineName = "test";
job = {};
nixConfig.enable = false;
};
}
{
name = "jobPatched without runner cache";
expected = {
after_script = ["finalize_nix_ci"];
before_script = ["source setup_nix_ci \"gitlab-ci:pipeline:test:job-deps:test\""];
};
actual = mkJobPatched {
key = "test";
pipelineName = "test";
job = {};
nixConfig = {
enable = true;
enableRunnerCache = false;
};
};
}
{
name = "jobPatched with runner cache";
expected = {
after_script = ["finalize_nix_ci"];
before_script = ["source setup_nix_ci \"gitlab-ci:pipeline:test:job-deps:test\""];
cache = [
{
key = "test";
paths = [".nix-cache/"];
}
];
variables."NIX_CI_CACHE_STRATEGY" = "runner";
};
actual = mkJobPatched {
key = "test";
pipelineName = "test";
job = {};
nixConfig = {
enable = true;
enableRunnerCache = true;
runnerCacheKey = "test";
};
};
}
{
name = "mkPipeline empty";
expected = {};
actual =
(mkPipeline {
name = "test";
nixConfig = {};
pipeline.jobs = {};
}).finalConfig;
}
{
name = "mkPipeline empty packages";
type = "script";
script = let
pipeline =
ntlib.helpers.toJsonFile
(mkPipeline {
name = "test";
nixConfig = {};
pipeline.jobs = {};
}).packages;
in
# sh
''
set -euo pipefail
${ntlib.helpers.path [pkgs.jq pkgs.gnugrep pkgs.coreutils]}
echo "two keys, one json one pretty"
jq 'keys | length == 2' "${pipeline}" | grep -q true
echo "key[0] is exactly 'gitlab-ci:pipeline:test'"
jq -r 'keys[0]' "${pipeline}" | grep -qx "gitlab-ci:pipeline:test"
echo "key[1] is exactly 'gitlab-ci:pipeline:test:pretty'"
jq -r 'keys[1]' "${pipeline}" | grep -qx "gitlab-ci:pipeline:test:pretty"
echo "value contains '/nix/store/'"
jq -r '.["gitlab-ci:pipeline:test"]' "${pipeline}" | grep -q "/nix/store/"
echo "value contains 'gitlab-ci-test.yml'"
jq -r '.["gitlab-ci:pipeline:test"]' "${pipeline}" | grep -q "gitlab-ci-test.yml"
echo "file only contains '{}'"
[[ "$(cat $(jq -r '.["gitlab-ci:pipeline:test"]' "${pipeline}"))" == "{}" ]]
'';
}
{
name = "handle store paths in variables";
expected = {
stages = ["test"];
test.stage = "test";
};
actual =
(mkPipeline {
name = "test";
nixConfig.enable = false;
pipeline = {
stages = ["test"];
jobs.test = {
stage = "test";
variables."TEST" = "${pkgs.hello}";
};
};
}).finalConfig;
}
];
};
}

View file

@ -1,8 +0,0 @@
{
imports = [
./utils.nix
./ci-lib.nix
./helpers.nix
./pipeline-yamls.nix
];
}

77
tests/fixtures/flake_parts/flake.lock generated vendored Normal file
View 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
}

41
tests/fixtures/flake_parts/flake.nix vendored Normal file
View file

@ -0,0 +1,41 @@
{
outputs = {
flake-parts,
systems,
...
} @ inputs:
flake-parts.lib.mkFlake {inherit inputs;} {
imports = [
"@repo_path@/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";
};
}

View file

@ -0,0 +1,38 @@
{
pkgs,
ntlib,
...
}: {
suites."flake-parts" = {
pos = __curPos;
tests = [
{
name = "flakeModule";
type = "script";
script =
# sh
''
${ntlib.helpers.scriptHelpers}
${ntlib.helpers.path (with pkgs; [coreutils nix gnused gnugrep jq])}
export SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
export NIX_SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
repo_path=${../.}
cp ${./fixtures/flake_parts}/* .
# import from the absolute path above, is easier than trying to figure out the repo path etc.
sed -i -e "s|@repo_path@|$repo_path|" flake.nix
# NOTE: --impure is required since importing modules from absolute paths is not allowed in pure mode
nix build --impure .#gitlab-ci:pipeline:default
assert "-f result" "should exist"
assert_file_contains "result" "finalize_nix_ci"
jq '.' result # check if valid json just to be sure
nix build --impure .#gitlab-ci:pipeline:default:pretty
assert "-f result" "should exist"
assert_file_contains "result" " - finalize_nix_ci"
'';
}
];
};
}

View file

@ -1,60 +1,28 @@
{
lib,
pkgs,
cilib,
...
}: let
cilib = import ./../lib {inherit lib pkgs;};
in {
nixtest.suites."Helpers" = {
}: {
suites."Helpers" = {
pos = __curPos;
tests = let
inherit (cilib) helpers;
in [
{
name = "appendToAfterScript nix disabled";
expected = {};
actual = helpers.appendToAfterScript [] {};
}
{
name = "appendToAfterScript empty";
expected = {
nix.enable = true;
after_script = [];
};
actual = helpers.appendToAfterScript [] {nix.enable = true;};
}
{
name = "appendToAfterScript";
expected = {
nix.enable = true;
after_script = ["echo after_script" "finalize_nix_ci"];
};
actual = helpers.appendToAfterScript ["finalize_nix_ci"] {
nix.enable = true;
after_script = ["echo after_script"];
};
}
{
name = "prependToBeforeScript nix disabled";
expected = {};
actual = helpers.prependToBeforeScript [] {};
}
{
name = "prependToBeforeScript empty";
expected = {
nix.enable = true;
before_script = [];
};
actual = helpers.prependToBeforeScript [] {nix.enable = true;};
}
{
name = "prependToBeforeScript";
expected = {
nix.enable = true;
before_script = ["setup_nix_ci" "echo before_script"];
};
actual = helpers.prependToBeforeScript ["setup_nix_ci"] {
nix.enable = true;
before_script = ["echo before_script"];
};
}
@ -66,18 +34,22 @@ in {
{
name = "filterAttrsRec";
expected = {world = "world";};
actual = helpers.filterAttrsRec (n: v: v != null) {
actual = helpers.filterAttrsRec (_n: v: v != null) {
hello = null;
world = "world";
};
}
{
name = "filterJobVariables with store paths";
expected = {HELLO = "${pkgs.hello}";};
expected = {
HELLO = "${pkgs.hello}";
MULTIPLE = "${pkgs.hello}:${pkgs.hello}";
};
actual = helpers.filterJobVariables true {
variables = {
HELLO = "${pkgs.hello}";
WORLD = "world";
MULTIPLE = "${pkgs.hello}:${pkgs.hello}";
};
};
}

82
tests/modules_test.nix Normal file
View file

@ -0,0 +1,82 @@
{
pkgs,
cilib,
ntlib,
...
}: {
suites."Modules" = {
pos = __curPos;
tests = let
simplePipeline = cilib.mkCI {
pipelines."test" = {
stages = ["test"];
jobs."test" = {
stage = "test";
script = ["echo hello world"];
};
};
};
in [
{
name = "empty pipelines";
expected = {};
actual =
(cilib.mkCI {}).pipelines;
}
{
name = "empty packages";
expected = {};
actual =
(cilib.mkCI {}).packages;
}
{
name = "simple pipeline";
expected = {
stages = [".pre" "test" ".post"];
"test" = {
image = "$NIX_CI_IMAGE";
stage = "test";
before_script = ["source setup_nix_ci \"gitlab-ci:pipeline:test:job-deps:test\""];
script = ["echo hello world"];
after_script = ["finalize_nix_ci"];
};
};
actual = simplePipeline.pipelines."test".finalConfig;
}
{
name = "simple pipeline yaml";
type = "script";
script = let
package = simplePipeline.packages."gitlab-ci:pipeline:test";
in
# sh
''
${ntlib.helpers.path [pkgs.gnugrep]}
${ntlib.helpers.scriptHelpers}
assert_file_contains ${package} 'gitlab-ci:pipeline:test:job-deps:test'
assert_file_contains ${package} 'finalize_nix_ci'
assert_file_contains ${package} 'echo hello world'
'';
}
{
name = "dont fail on store paths";
type = "script";
script = let
package =
(cilib.mkCI {
pipelines."test" = {
variables.EXAMPLE = "${pkgs.hello}";
};
}).packages."gitlab-ci:pipeline:test";
in
# sh
''
${ntlib.helpers.path [pkgs.gnugrep]}
${ntlib.helpers.scriptHelpers}
assert_file_contains ${package} '[".pre",".post"]'
assert_file_contains ${package} '"EXAMPLE":"/nix/store/.*-hello-.*"'
'';
}
];
};
}

View file

@ -1,26 +0,0 @@
{
lib,
pkgs,
self',
...
}: let
cilib = import ./../lib {inherit lib pkgs;};
in {
nixtest.suites."Pipeline YAMLs" = {
pos = __curPos;
tests = let
jsonFile = file: builtins.fromJSON (builtins.readFile file);
in [
{
name = "default";
type = "snapshot";
actual = jsonFile self'.legacyPackages."gitlab-ci:pipeline:default";
}
{
name = "non-default";
type = "snapshot";
actual = jsonFile self'.legacyPackages."gitlab-ci:pipeline:non-default";
}
];
};
}

77
tests/soonix_test.nix Normal file
View file

@ -0,0 +1,77 @@
{
lib,
cilib,
...
}: let
inherit (lib) trimWith;
in {
suites."Soonix" = {
pos = __curPos;
tests = let
version = trimWith {
start = true;
end = true;
} (builtins.readFile ../lib/VERSION);
in [
{
name = "default soonix config";
expected = {
data.include = [
{
component = "gitlab.com/TECHNOFAB/nix-gitlab-ci/nix-gitlab-ci@${version}";
inputs.version = version;
}
];
generator = "nix";
opts.format = "yaml";
hook = {
mode = "copy";
gitignore = false;
};
output = ".gitlab-ci.yml";
};
actual =
(cilib.mkCI {
config.soonix = {};
}).soonix;
}
{
name = "custom soonix config";
expected = {
data = {
include = [
{
component = "gitlab.com/example/nix-gitlab-ci/nix-gitlab-ci@abc";
inputs = {
version = "abc";
hello = "world";
};
}
];
"example".script = ["hello"];
};
generator = "nix";
opts.format = "yaml";
hook = {
mode = "copy";
gitignore = false;
};
output = ".gitlab-ci.yml";
};
actual =
(cilib.mkCI {
config.soonix = {
componentVersion = "abc";
componentUrl = "gitlab.com/example/nix-gitlab-ci/nix-gitlab-ci";
componentInputs = {
hello = "world";
};
extraData = {
"example".script = ["hello"];
};
};
}).soonix;
}
];
};
}

View file

@ -1,36 +0,0 @@
{
lib,
pkgs,
...
}: let
cilib = import ./../lib {inherit lib pkgs;};
in {
nixtest.suites."Utils" = {
pos = __curPos;
tests = [
{
name = "commitAndPushFiles";
type = "script";
script = let
inherit (cilib) utils;
job = builtins.toFile "test" (
builtins.unsafeDiscardStringContext (
builtins.toJSON (
utils.commitAndPushFiles {
message = "hello world";
files = ["a.md" "b.txt"];
} {}
)
)
);
in
# sh
''
export PATH=${lib.makeBinPath [pkgs.gnugrep]}
grep -q 'git commit -m \\"hello world\\"' ${job}
grep -q 'git add a.md b.txt' ${job}
'';
}
];
};
}

32
tests/utils_test.nix Normal file
View file

@ -0,0 +1,32 @@
{
pkgs,
ntlib,
cilib,
...
}: {
suites."Utils" = {
pos = __curPos;
tests = [
{
name = "commitAndPushFiles";
type = "script";
script = let
inherit (cilib) utils;
job = ntlib.helpers.toJsonFile (
utils.commitAndPushFiles {
message = "hello world";
files = ["a.md" "b.txt"];
} {}
);
in
# sh
''
${ntlib.helpers.path [pkgs.gnugrep]}
${ntlib.helpers.scriptHelpers}
assert_file_contains ${job} 'git commit -m \\"hello world\\"'
assert_file_contains ${job} 'git add a.md b.txt'
'';
}
];
};
}