diff --git a/lib/impl/helpers.nix b/lib/impl/helpers.nix index 4b08c7f..48a79fc 100644 --- a/lib/impl/helpers.nix +++ b/lib/impl/helpers.nix @@ -84,7 +84,7 @@ in rec { unsetOr = typ: (types.either unsetType typ) // { - inherit (typ) description; + inherit (typ) description getSubOptions; }; mkUnsetOption = opts: mkOption (opts @@ -93,6 +93,14 @@ in rec { 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 diff --git a/lib/impl/modules/job.nix b/lib/impl/modules/job.nix index b8d9437..5f7d1ee 100644 --- a/lib/impl/modules/job.nix +++ b/lib/impl/modules/job.nix @@ -4,7 +4,7 @@ ... }: let inherit (lib) mkOption types filterAttrs; - inherit (cilib.helpers) filterUnset mkUnsetOption; + inherit (cilib.helpers) filterUnset mkUnsetOption eitherWithSubOptions; in rec { jobConfigSubmodule = {pipelineConfig, ...}: { options = { @@ -53,43 +53,539 @@ in rec { # GITLAB OPTIONS # gitlabOptions = { - stage = mkOption { + # see https://docs.gitlab.com/ci/yaml/ + after_script = mkUnsetOption { + type = types.listOf types.str; description = '' - Pipeline stage to run this job in. + 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. ''; - type = types.str; default = "$NIX_CI_IMAGE"; + example = { + name = "super/sql:experimental"; + entrypoint = [""]; + pull_policy = "if-not-present"; + docker = { + platform = "arm64/v8"; + user = "dave"; + }; + kubernetes.user = "1001"; + }; }; - variables = mkUnsetOption { - type = types.attrsOf types.str; + "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) + ''; }; - before_script = mkUnsetOption { - type = types.listOf types.str; + 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) + ''; }; - after_script = mkUnsetOption { + 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) + ''; }; - artifacts = mkUnsetOption { - type = types.attrs; # TODO: more granular - description = ''''; + 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) + ''; }; - rules = mkUnsetOption { - type = types.listOf types.attrs; + 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) + ''; }; - allow_failure = mkUnsetOption { - type = types.bool; + 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 { diff --git a/lib/impl/modules/pipeline.nix b/lib/impl/modules/pipeline.nix index cfb6284..32b9e0c 100644 --- a/lib/impl/modules/pipeline.nix +++ b/lib/impl/modules/pipeline.nix @@ -4,8 +4,8 @@ jobSubmodule, ... }: let - inherit (lib) mkOption types filterAttrs mergeAttrsList mapAttrs; - inherit (cilib.helpers) filterUnset mkUnsetOption toYaml toYamlPretty; + inherit (lib) mkOption types filterAttrs mergeAttrsList mapAttrs mkRenamedOptionModule literalExpression; + inherit (cilib.helpers) filterUnset unset mkUnsetOption toYaml toYamlPretty eitherWithSubOptions; pipelineConfigSubmodule = {rootConfig, ...}: { options = { @@ -30,19 +30,143 @@ # 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 { - type = types.attrsOf types.str; - description = ''''; + # 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 {