diff --git a/.envrc b/.envrc index f990172..565a52a 100644 --- a/.envrc +++ b/.envrc @@ -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 diff --git a/.gitignore b/.gitignore index bbee8ad..fe07db9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,2 @@ -.idea -.devenv -.direnv -.pre-commit-config.yaml +.ren/ result -*.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 24b9507..bff742c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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 diff --git a/README.md b/README.md index ceaca42..1570632 100644 --- a/README.md +++ b/README.md @@ -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: # 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::job-deps:` 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" diff --git a/docs/examples.md b/docs/examples.md index 6f82a63..336a55a 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -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 diff --git a/docs/images/favicon.png b/docs/images/favicon.png deleted file mode 100644 index 135b030..0000000 Binary files a/docs/images/favicon.png and /dev/null differ diff --git a/docs/images/logo.png b/docs/images/logo.png deleted file mode 100644 index 2a70bf6..0000000 Binary files a/docs/images/logo.png and /dev/null differ diff --git a/docs/images/logo.svg b/docs/images/logo.svg new file mode 100755 index 0000000..d8dce95 --- /dev/null +++ b/docs/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/options.md b/docs/options.md new file mode 100644 index 0000000..4ca74a4 --- /dev/null +++ b/docs/options.md @@ -0,0 +1,3 @@ +# Options + +{% include 'options.md' %} diff --git a/docs/setup.md b/docs/setup.md index d3aa96e..c1f85df 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -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 `` 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. diff --git a/docs/soonix.md b/docs/soonix.md new file mode 100644 index 0000000..b333ab6 --- /dev/null +++ b/docs/soonix.md @@ -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; +} +``` diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000..b2ae4ff --- /dev/null +++ b/docs/style.css @@ -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; +} + diff --git a/docs/usage.md b/docs/usage.md index b28a755..44c0bf7 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -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/?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@ # recommendation: pin to the latest release/version (don't use "main" etc.) + inputs: + 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. diff --git a/docs/utilities.md b/docs/utilities.md index e5a7b96..b82091d 100644 --- a/docs/utilities.md +++ b/docs/utilities.md @@ -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::pretty`. diff --git a/examples/flake-parts/flake.lock b/examples/flake-parts/flake.lock new file mode 100644 index 0000000..2e81bcf --- /dev/null +++ b/examples/flake-parts/flake.lock @@ -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 +} diff --git a/examples/flake-parts/flake.nix b/examples/flake-parts/flake.nix new file mode 100644 index 0000000..c1b7977 --- /dev/null +++ b/examples/flake-parts/flake.nix @@ -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"; + }; +} diff --git a/examples/rensa-nix/flake.lock b/examples/rensa-nix/flake.lock new file mode 100644 index 0000000..7c47f5c --- /dev/null +++ b/examples/rensa-nix/flake.lock @@ -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 +} diff --git a/examples/rensa-nix/flake.nix b/examples/rensa-nix/flake.nix new file mode 100644 index 0000000..1f81895 --- /dev/null +++ b/examples/rensa-nix/flake.nix @@ -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"] + ]; + }; +} diff --git a/examples/rensa-nix/nix/repo/ci.nix b/examples/rensa-nix/nix/repo/ci.nix new file mode 100644 index 0000000..adb707e --- /dev/null +++ b/examples/rensa-nix/nix/repo/ci.nix @@ -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"]; + }; + }; + } diff --git a/examples/rensa-nix/nix/repo/flake.lock b/examples/rensa-nix/nix/repo/flake.lock new file mode 100644 index 0000000..fb28eb7 --- /dev/null +++ b/examples/rensa-nix/nix/repo/flake.lock @@ -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 +} diff --git a/examples/rensa-nix/nix/repo/flake.nix b/examples/rensa-nix/nix/repo/flake.nix new file mode 100644 index 0000000..ad3b77d --- /dev/null +++ b/examples/rensa-nix/nix/repo/flake.nix @@ -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;}; + }; +} diff --git a/flake.lock b/flake.lock index 38bc581..7c47f5c 100644 --- a/flake.lock +++ b/flake.lock @@ -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" } } }, diff --git a/flake.nix b/flake.nix index 0788dc0..769a6b9 100644 --- a/flake.nix +++ b/flake.nix @@ -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=" - ]; - }; } diff --git a/lib/VERSION b/lib/VERSION new file mode 100644 index 0000000..4a36342 --- /dev/null +++ b/lib/VERSION @@ -0,0 +1 @@ +3.0.0 diff --git a/lib/default.nix b/lib/default.nix index 37f6318..ff3ec88 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -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 diff --git a/lib/flake.nix b/lib/flake.nix index b2cc77a..227f9a6 100644 --- a/lib/flake.nix +++ b/lib/flake.nix @@ -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; }; } diff --git a/lib/flakeModule.nix b/lib/flakeModule.nix index 4f8a274..aadf292 100644 --- a/lib/flakeModule.nix +++ b/lib/flakeModule.nix @@ -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; } ); } diff --git a/lib/helpers.nix b/lib/helpers.nix deleted file mode 100644 index 3b4c057..0000000 --- a/lib/helpers.nix +++ /dev/null @@ -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 = []; - }; -} diff --git a/lib/impl/default.nix b/lib/impl/default.nix new file mode 100644 index 0000000..7ff5623 --- /dev/null +++ b/lib/impl/default.nix @@ -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;}; +} diff --git a/lib/impl/helpers.nix b/lib/impl/helpers.nix new file mode 100644 index 0000000..48a79fc --- /dev/null +++ b/lib/impl/helpers.nix @@ -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 = []; + }; +} diff --git a/lib/jobDeps.nix b/lib/impl/jobDeps.nix similarity index 57% rename from lib/jobDeps.nix rename to lib/impl/jobDeps.nix index 27bad19..302597d 100644 --- a/lib/jobDeps.nix +++ b/lib/impl/jobDeps.nix @@ -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; }; - }; -} + } diff --git a/lib/impl/jobPatched.nix b/lib/impl/jobPatched.nix new file mode 100644 index 0000000..c7134fb --- /dev/null +++ b/lib/impl/jobPatched.nix @@ -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; + }) + ) diff --git a/lib/jobRun.nix b/lib/impl/jobRun.nix similarity index 68% rename from lib/jobRun.nix rename to lib/impl/jobRun.nix index 5c4a118..4a1e0f6 100644 --- a/lib/jobRun.nix +++ b/lib/impl/jobRun.nix @@ -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; }; - }; -} + } diff --git a/lib/impl/modules/default.nix b/lib/impl/modules/default.nix new file mode 100644 index 0000000..9e33a7e --- /dev/null +++ b/lib/impl/modules/default.nix @@ -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 + ; +} diff --git a/lib/impl/modules/job.nix b/lib/impl/modules/job.nix new file mode 100644 index 0000000..5f7d1ee --- /dev/null +++ b/lib/impl/modules/job.nix @@ -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 can’t 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 repository’s .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, it’s 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 it’s 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 job’s `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; + }; + }; + }; +} diff --git a/lib/impl/modules/pipeline.nix b/lib/impl/modules/pipeline.nix new file mode 100644 index 0000000..32b9e0c --- /dev/null +++ b/lib/impl/modules/pipeline.nix @@ -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) variable’s 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; +} diff --git a/lib/impl/modules/root.nix b/lib/impl/modules/root.nix new file mode 100644 index 0000000..84fb5ba --- /dev/null +++ b/lib/impl/modules/root.nix @@ -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; + }; + }; +} diff --git a/lib/impl/modules/soonix.nix b/lib/impl/modules/soonix.nix new file mode 100644 index 0000000..d23d727 --- /dev/null +++ b/lib/impl/modules/soonix.nix @@ -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; + }; + }; +} diff --git a/lib/pipeline.nix b/lib/impl/pipeline.nix similarity index 67% rename from lib/pipeline.nix rename to lib/impl/pipeline.nix index 2873b22..4d9b185 100644 --- a/lib/pipeline.nix +++ b/lib/impl/pipeline.nix @@ -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::job-deps: { "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; - }; -} + } diff --git a/lib/sandbox_helper.sh b/lib/impl/sandbox_helper.sh similarity index 100% rename from lib/sandbox_helper.sh rename to lib/impl/sandbox_helper.sh index 4ea5ec5..294fc6b 100644 --- a/lib/sandbox_helper.sh +++ b/lib/impl/sandbox_helper.sh @@ -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 diff --git a/lib/jobPatch.nix b/lib/jobPatch.nix deleted file mode 100644 index 959a9c0..0000000 --- a/lib/jobPatch.nix +++ /dev/null @@ -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"]; -} diff --git a/nix/packages/pkgs.nix b/nix/packages/pkgs.nix new file mode 100644 index 0000000..34d07aa --- /dev/null +++ b/nix/packages/pkgs.nix @@ -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"]; + }; + }; +} diff --git a/scripts/finalize_nix_ci.sh b/nix/packages/scripts/finalize_nix_ci.sh similarity index 99% rename from scripts/finalize_nix_ci.sh rename to nix/packages/scripts/finalize_nix_ci.sh index baa6a0d..ecc2211 100644 --- a/scripts/finalize_nix_ci.sh +++ b/nix/packages/scripts/finalize_nix_ci.sh @@ -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" - diff --git a/scripts/setup_nix_ci.sh b/nix/packages/scripts/setup_nix_ci.sh similarity index 99% rename from scripts/setup_nix_ci.sh rename to nix/packages/scripts/setup_nix_ci.sh index 1f2af66..eaf189b 100644 --- a/scripts/setup_nix_ci.sh +++ b/nix/packages/scripts/setup_nix_ci.sh @@ -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" - diff --git a/nix/repo/ci.nix b/nix/repo/ci.nix new file mode 100644 index 0000000..a76c0d8 --- /dev/null +++ b/nix/repo/ci.nix @@ -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"; + } + ]; + }; + }; + }; + } diff --git a/nix/repo/devShells.nix b/nix/repo/devShells.nix new file mode 100644 index 0000000..10889f9 --- /dev/null +++ b/nix/repo/devShells.nix @@ -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; + }; +} diff --git a/nix/repo/docs.nix b/nix/repo/docs.nix new file mode 100644 index 0000000..052b177 --- /dev/null +++ b/nix/repo/docs.nix @@ -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; + } diff --git a/nix/repo/flake.lock b/nix/repo/flake.lock new file mode 100644 index 0000000..0627f8b --- /dev/null +++ b/nix/repo/flake.lock @@ -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 +} diff --git a/nix/repo/flake.nix b/nix/repo/flake.nix new file mode 100644 index 0000000..149ae74 --- /dev/null +++ b/nix/repo/flake.nix @@ -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; + }; +} diff --git a/nix/repo/tests.nix b/nix/repo/tests.nix new file mode 100644 index 0000000..62c0833 --- /dev/null +++ b/nix/repo/tests.nix @@ -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; + }; + }; +} diff --git a/snapshots/default.snap.json b/snapshots/default.snap.json deleted file mode 100644 index c247a78..0000000 --- a/snapshots/default.snap.json +++ /dev/null @@ -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"}} \ No newline at end of file diff --git a/snapshots/non-default.snap.json b/snapshots/non-default.snap.json deleted file mode 100644 index 9ed3256..0000000 --- a/snapshots/non-default.snap.json +++ /dev/null @@ -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"}} \ No newline at end of file diff --git a/templates/nix-gitlab-ci.yml b/templates/nix-gitlab-ci.yml index 2dcead3..14eeaa9 100644 --- a/templates/nix-gitlab-ci.yml +++ b/templates/nix-gitlab-ci.yml @@ -110,3 +110,4 @@ nix-ci:trigger: strategy: depend forward: pipeline_variables: true + diff --git a/tests/ci-lib.nix b/tests/ci-lib.nix deleted file mode 100644 index 577139c..0000000 --- a/tests/ci-lib.nix +++ /dev/null @@ -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}"))" == "{}" ]] - ''; - } - ]; - }; -} diff --git a/tests/cilib_test.nix b/tests/cilib_test.nix new file mode 100644 index 0000000..78e99a3 --- /dev/null +++ b/tests/cilib_test.nix @@ -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; + } + ]; + }; +} diff --git a/tests/default.nix b/tests/default.nix deleted file mode 100644 index 764ae46..0000000 --- a/tests/default.nix +++ /dev/null @@ -1,8 +0,0 @@ -{ - imports = [ - ./utils.nix - ./ci-lib.nix - ./helpers.nix - ./pipeline-yamls.nix - ]; -} diff --git a/tests/fixtures/flake_parts/flake.lock b/tests/fixtures/flake_parts/flake.lock new file mode 100644 index 0000000..15b9835 --- /dev/null +++ b/tests/fixtures/flake_parts/flake.lock @@ -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 +} diff --git a/tests/fixtures/flake_parts/flake.nix b/tests/fixtures/flake_parts/flake.nix new file mode 100644 index 0000000..cca696f --- /dev/null +++ b/tests/fixtures/flake_parts/flake.nix @@ -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"; + }; +} diff --git a/tests/flake_parts_test.nix b/tests/flake_parts_test.nix new file mode 100644 index 0000000..e998f45 --- /dev/null +++ b/tests/flake_parts_test.nix @@ -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" + ''; + } + ]; + }; +} diff --git a/tests/helpers.nix b/tests/helpers_test.nix similarity index 59% rename from tests/helpers.nix rename to tests/helpers_test.nix index 1d100fc..4d7e7d4 100644 --- a/tests/helpers.nix +++ b/tests/helpers_test.nix @@ -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}"; }; }; } diff --git a/tests/modules_test.nix b/tests/modules_test.nix new file mode 100644 index 0000000..e134293 --- /dev/null +++ b/tests/modules_test.nix @@ -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-.*"' + ''; + } + ]; + }; +} diff --git a/tests/pipeline-yamls.nix b/tests/pipeline-yamls.nix deleted file mode 100644 index 32effbe..0000000 --- a/tests/pipeline-yamls.nix +++ /dev/null @@ -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"; - } - ]; - }; -} diff --git a/tests/soonix_test.nix b/tests/soonix_test.nix new file mode 100644 index 0000000..c76edb4 --- /dev/null +++ b/tests/soonix_test.nix @@ -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; + } + ]; + }; +} diff --git a/tests/utils.nix b/tests/utils.nix deleted file mode 100644 index 8d9591f..0000000 --- a/tests/utils.nix +++ /dev/null @@ -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} - ''; - } - ]; - }; -} diff --git a/tests/utils_test.nix b/tests/utils_test.nix new file mode 100644 index 0000000..2e319b1 --- /dev/null +++ b/tests/utils_test.nix @@ -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' + ''; + } + ]; + }; +}