145 lines
4.9 KiB
Markdown
145 lines
4.9 KiB
Markdown
# gitlab-sim
|
||||
|
|
|
|||
|
|
A local tool to validate and lint `.gitlab-ci.yml` pipelines without needing a GitLab server.
|
|||
|
|
|
|||
|
|
## Features
|
|||
|
|
|
|||
|
|
- **YAML validation** — detects malformed pipeline files early
|
|||
|
|
- **Stage validation** — every job's `stage` must be declared in `stages`
|
|||
|
|
- **`extends:` resolution** — resolves single and multi-level template inheritance before linting, so derived jobs are evaluated against their fully merged definition
|
|||
|
|
- **`needs:` DAG validation** — checks that `needs:` references exist, respect stage ordering, and contain no circular dependencies
|
|||
|
|
- **Deprecation warnings** — flags `only`/`except` usage in favour of `rules`
|
|||
|
|
|
|||
|
|
## Requirements
|
|||
|
|
|
|||
|
|
- Go 1.21 or later
|
|||
|
|
- [Task](https://taskfile.dev) (optional, for development tasks)
|
|||
|
|
|
|||
|
|
## Installation
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
git clone https://git.k3nny.fr/gitlab-sim
|
|||
|
|
cd gitlab-sim
|
|||
|
|
go build -o gitlab-sim ./cmd/gitlab-sim/...
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Or with Task:
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
task build
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Usage
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
gitlab-sim <pipeline.yml>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Exits `0` when no errors are found, `1` when at least one error is reported.
|
|||
|
|
|
|||
|
|
### Example output
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
# Clean pipeline
|
|||
|
|
OK: .gitlab-ci.yml — no issues found (5 jobs, 3 stages)
|
|||
|
|
|
|||
|
|
# Pipeline with issues
|
|||
|
|
[ERROR] job "deploy": stage "production" is not defined in 'stages'
|
|||
|
|
[ERROR] job "test": needs unknown job "build-app"
|
|||
|
|
[WARNING] job "old-job": 'only'/'except' are deprecated; prefer 'rules'
|
|||
|
|
|
|||
|
|
3 finding(s): 2 error(s)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Lint rules
|
|||
|
|
|
|||
|
|
### Pipeline-level
|
|||
|
|
|
|||
|
|
| Severity | Rule |
|
|||
|
|
|----------|------|
|
|||
|
|
| ERROR | `workflow.rules[*].when` is not `always` or `never` |
|
|||
|
|
| WARNING | No `stages` defined (GitLab falls back to default stages) |
|
|||
|
|
|
|||
|
|
### Job-level — structure
|
|||
|
|
|
|||
|
|
| Severity | Rule |
|
|||
|
|
|----------|------|
|
|||
|
|
| ERROR | Job is missing required `script` (or `run`) — non-trigger, non-template jobs |
|
|||
|
|
| ERROR | Job references a `stage` not declared in `stages` |
|
|||
|
|
| ERROR | `only` and `rules` used together on the same job |
|
|||
|
|
| ERROR | `except` and `rules` used together on the same job |
|
|||
|
|
| WARNING | `only`/`except` used (deprecated, prefer `rules`) |
|
|||
|
|
|
|||
|
|
### Job-level — keyword constraints
|
|||
|
|
|
|||
|
|
| Severity | Rule |
|
|||
|
|
|----------|------|
|
|||
|
|
| ERROR | `when` is not one of `on_success`, `on_failure`, `always`, `manual`, `delayed`, `never` |
|
|||
|
|
| ERROR | `when: delayed` without `start_in` |
|
|||
|
|
| ERROR | `start_in` set but `when` is not `delayed` |
|
|||
|
|
| ERROR | `parallel` integer not in range 2–200 |
|
|||
|
|
| ERROR | `parallel` map form missing `matrix` key |
|
|||
|
|
| ERROR | `retry` integer not in range 0–2 |
|
|||
|
|
| ERROR | `retry.max` not in range 0–2 |
|
|||
|
|
| ERROR | `retry.when` contains an invalid failure type |
|
|||
|
|
| ERROR | `allow_failure` is not a boolean or a map with `exit_codes` |
|
|||
|
|
| ERROR | `interruptible` is not a boolean |
|
|||
|
|
| ERROR | `trigger` job also has `script` |
|
|||
|
|
| ERROR | `trigger` map missing `project` or `include` |
|
|||
|
|
| ERROR | `coverage` is not a regex pattern wrapped in `/` |
|
|||
|
|
| ERROR | `release` missing required `tag_name` |
|
|||
|
|
| ERROR | `environment.url` set without `environment.name` |
|
|||
|
|
| ERROR | `environment.action` is not one of `start`, `stop`, `prepare`, `verify`, `access` |
|
|||
|
|
| ERROR | `artifacts.when` is not `on_success`, `on_failure`, or `always` |
|
|||
|
|
| ERROR | `artifacts.expose_as` set without `artifacts.paths` |
|
|||
|
|
| ERROR | `cache.when` is not `on_success`, `on_failure`, or `always` |
|
|||
|
|
| ERROR | `cache.policy` is not `pull`, `push`, or `pull-push` |
|
|||
|
|
| ERROR | `rules[*].when` is not one of the valid `when` values |
|
|||
|
|
| ERROR | `image` map form missing `name` key |
|
|||
|
|
| ERROR | `inherit.default` / `inherit.variables` is not a boolean or list |
|
|||
|
|
| WARNING | `pages` job `artifacts.paths` does not include `public` |
|
|||
|
|
|
|||
|
|
### Cross-job graph
|
|||
|
|
|
|||
|
|
| Severity | Rule |
|
|||
|
|
|----------|------|
|
|||
|
|
| ERROR | `needs:` references a job that does not exist |
|
|||
|
|
| ERROR | `needs:` references a job in a later stage |
|
|||
|
|
| ERROR | Circular dependency detected in `needs:` graph |
|
|||
|
|
| ERROR | `dependencies:` references a job that does not exist |
|
|||
|
|
| ERROR | `dependencies:` references a job in the same or a later stage |
|
|||
|
|
| ERROR | `extends:` references an unknown job |
|
|||
|
|
| ERROR | Cycle detected in `extends:` graph |
|
|||
|
|
|
|||
|
|
### Hidden jobs (templates)
|
|||
|
|
|
|||
|
|
Jobs whose name starts with `.` are treated as reusable templates and skipped for most rules. This matches GitLab's own behaviour.
|
|||
|
|
|
|||
|
|
## Development
|
|||
|
|
|
|||
|
|
This project uses [Task](https://taskfile.dev) as a task runner.
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
task # list available tasks
|
|||
|
|
task build # compile the binary
|
|||
|
|
task test # run Go unit tests
|
|||
|
|
task lint-go # run go vet
|
|||
|
|
task validate # run the binary against all testdata fixtures
|
|||
|
|
task ci # full check: vet → test → build → validate
|
|||
|
|
task clean # remove build artifacts
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Project structure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
.
|
|||
|
|
├── cmd/gitlab-sim/ # CLI entrypoint
|
|||
|
|
├── internal/
|
|||
|
|
│ ├── linter/ # lint rules and findings
|
|||
|
|
│ ├── model/ # pipeline data structures and YAML parser
|
|||
|
|
│ └── resolver/ # extends: resolution
|
|||
|
|
├── testdata/ # sample pipelines used for manual validation
|
|||
|
|
├── Taskfile.yml
|
|||
|
|
└── go.mod
|
|||
|
|
```
|