Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions gitlab-ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# gitlab-ci

`gitlab-ci` is a KCL module for authoring GitLab CI/CD pipelines (`.gitlab-ci.yml`).
After installation, import it in KCL as `gitlab_ci`.

Schemas are generated from GitLab's official JSON Schema (`ci.json`) and
post-processed for stability. The module intentionally does **not** emit
GitLab defaults into the output YAML, to avoid silently overriding `default:`
blocks at job level.

## Install

```bash
kcl mod add gitlab-ci:0.1.0
```

## Public API

| Name | Purpose |
|---|---|
| `Pipeline` | Root `.gitlab-ci.yml` document |
| `Header` | `spec:` header document (precedes pipeline with `---`) |
| `Spec` | Content of the `spec:` section |
| `Input` | Input for `spec.inputs` (`default` is **optional**, matches `configInputs`) |
| `JobInput` | Input for `job.inputs` (`default` is **required**, matches `jobInputs`) |
| `Job` | Reusable job definition |
| `Workflow` | Content of the `workflow:` section |

Rules in `rules:` blocks are written as plain dicts. GitLab has multiple rule
shapes (workflow rules, job rules, pages rules) and a single Rule schema
cannot satisfy all of them.

## Quick start

```kcl
import gitlab_ci as ci

ci.Pipeline {
stages = ["build", "test"]
"build" = ci.Job {
stage = "build"
script = ["echo build"]
}
"test" = ci.Job {
stage = "test"
script = ["echo test"]
}
}
```

## Component-style pipeline with inputs

```kcl
import manifests
import gitlab_ci as ci

header = ci.Header {
spec = ci.Spec {
inputs = {
stage = ci.Input { default = "test" }
image = ci.Input {
$type = "string"
default = "alpine:latest"
}
required_one = ci.Input {
description = "no default = required input"
}
}
}
}

pipeline = ci.Pipeline {
"build" = ci.Job {
stage = "$[[ inputs.stage ]]"
image = "$[[ inputs.image ]]"
script = ["echo build"]
}
}

manifests.yaml_stream([header, pipeline])
```

## Job-level inputs

`job.inputs` requires `default` per GitLab schema. Use `JobInput` (not `Input`)
to get the correct constraint:

```kcl
import gitlab_ci as ci

ci.Pipeline {
"deploy" = ci.Job {
inputs = {
target = ci.JobInput { default = "staging" }
}
script = ["deploy $[[ inputs.target ]]"]
}
}
```

KCL rejects `job.inputs` entries that omit `default`.

## Defaults policy

This module strips GitLab defaults during schema generation. Writing only
`script = [...]` produces only `script:` in the YAML — no `when: on_success`,
no `interruptible: false`, no `stages: [build, test, deploy]`.

This is intentional. Emitting `interruptible: false` at job level would
override a `default: { interruptible: true }` block, silently breaking
pipeline semantics. GitLab applies its own defaults at runtime.

## Spec section requires a YAML separator

GitLab requires `spec:` to live in a separate YAML document, preceded by `---`.
This module exposes a `Header` schema for the spec section and a `Pipeline`
schema for the body. Use `manifests.yaml_stream([header, pipeline])` to emit
both with the `---` separator. This is pure KCL — no external tools needed.

## Source

The module is generated from GitLab's official schema:
https://gitlab.com/gitlab-org/gitlab/-/raw/master/app/assets/javascripts/editor/schema/ci.json
24 changes: 24 additions & 0 deletions gitlab-ci/artifacthub-pkg.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
version: 0.1.0
name: gitlab-ci
displayName: GitLab CI/CD
description: "`gitlab-ci` is a KCL module for authoring GitLab CI/CD pipelines"
links:
- name: GitLab CI/CD YAML syntax reference
url: https://docs.gitlab.com/ci/yaml/
- name: KCL homepage
url: https://kcl-lang.io/
install: |
#### Add `gitlab-ci` with tag `0.1.0` as dependency
```
kcl mod add gitlab-ci:0.1.0
```

#### Pull `gitlab-ci` with tag `0.1.0` to local
```
kcl mod pull gitlab-ci:0.1.0
```
maintainers:
- name: kcl-lang.io
email: kcl-lang.io@domainsbyproxy.com
provider:
name: kcl-lang.io
5 changes: 5 additions & 0 deletions gitlab-ci/kcl.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "gitlab-ci"
edition = "v0.12.3"
version = "0.1.0"
description = "KCL schemas for authoring GitLab CI/CD pipelines (.gitlab-ci.yml)"
Empty file added gitlab-ci/kcl.mod.lock
Empty file.
9 changes: 9 additions & 0 deletions gitlab-ci/main.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
schema GitlabCi:
r"""
GitlabCi is the entrypoint schema for a GitLab CI/CD pipeline file (.gitlab-ci.yml).
"""
ci: GitlabCiYml

check:
ci

99 changes: 99 additions & 0 deletions gitlab-ci/public.k
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"""
Public API for GitLab CI/CD pipelines.

This file provides the recommended, stable, short-named schemas for authoring
.gitlab-ci.yml configurations. The verbose internal schemas (from JSON Schema
auto-generation) live in schema.k.

Use these names instead of the internal ones:

Pipeline - root .gitlab-ci.yml (type alias of GitlabCiYml)
Header - spec/inputs header document (precedes pipeline with `---`)
Spec - spec section content (from schema.k)
Input - input definition for spec.inputs (type alias of BaseInput)
default is OPTIONAL (matches GitLab configInputs)
JobInput - input definition for job.inputs (from schema.k)
default is REQUIRED (matches GitLab jobInputs)
Job - reusable job definition (from schema.k)
Workflow - workflow section content (type alias of PipelineWorkflow)

Rules: write `rules:` entries as plain dicts (e.g. `{"if" = "$VAR", when = "manual"}`).
GitLab has multiple internal rule shapes (workflow rules, job rules, pages
rules) and a single Rule schema cannot satisfy all of them. KCL accepts plain
dicts thanks to the schema's union types.

Example - simple pipeline:

import gitlab_ci as ci

pipeline = ci.Pipeline {
stages = ["build", "test"]
"build" = ci.Job {
stage = "build"
script = ["echo build"]
}
}

Example - component with header (spec inputs, default optional):

import manifests
import gitlab_ci as ci

header = ci.Header {
spec = ci.Spec {
inputs = {
stage = ci.Input { default = "test" }
required_one = ci.Input {
description = "no default = required"
}
}
}
}

pipeline = ci.Pipeline {
"build" = ci.Job {
stage = "$[[ inputs.stage ]]"
script = ["echo build"]
}
}

manifests.yaml_stream([header, pipeline])

Example - job with inputs (default REQUIRED per GitLab spec):

pipeline = ci.Pipeline {
"deploy" = ci.Job {
inputs = {
target = ci.JobInput { default = "staging" }
}
script = ["deploy $[[ inputs.target ]]"]
}
}
"""

# Type aliases for the public API.
#
# We use `type X = Y` instead of:
# - `schema X(Y):` because empty inheritance does NOT propagate index
# signatures like `[...str]: JobV0 | JobV1 | Job`. Users would lose
# the ability to add jobs as dynamic properties on Pipeline.
# - `mixin` because mixins add behavior to a schema, not aliases for it.
# - `protocol` because protocols describe shapes for mixin targets, not
# replacements for union types.
#
# Type aliases preserve the full underlying schema including [...str].
type Pipeline = GitlabCiYml
type Workflow = PipelineWorkflow
type Input = BaseInput

schema Header:
r"""
Header document with the spec section. Must be emitted as a separate
YAML document, before the pipeline body, separated by `---`.
Use manifests.yaml_stream([header, pipeline]) to emit both.

Attributes
----------
spec : Spec, optional
"""
spec?: Spec
Loading
Loading