gitlab-cicd-python-wrapper 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitlab_cicd_python_wrapper-0.1.0/PKG-INFO +262 -0
- gitlab_cicd_python_wrapper-0.1.0/README.md +242 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/__init__.py +99 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/_async.py +50 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/artifacts.py +40 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/cache.py +25 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/cli.py +69 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/common.py +87 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/component.py +148 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/environment.py +22 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/globals.py +44 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/image.py +22 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/include.py +75 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/job.py +96 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/needs.py +19 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/pages.py +10 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/pipeline.py +102 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/py.typed +0 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/release.py +14 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/retry.py +12 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/rules.py +28 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/secrets.py +24 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/serialization.py +64 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/spec.py +55 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/trigger.py +23 -0
- gitlab_cicd_python_wrapper-0.1.0/gitlab_cicd_python_wrapper/variables.py +12 -0
- gitlab_cicd_python_wrapper-0.1.0/pyproject.toml +71 -0
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gitlab-cicd-python-wrapper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pydantic models wrapping GitLab CI/CD YAML with 1:1 mapping
|
|
5
|
+
License: MIT
|
|
6
|
+
Author: Szymon Richert
|
|
7
|
+
Author-email: szymon.rychu@gmail.com
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: aiofiles (>=24.0,<25.0)
|
|
16
|
+
Requires-Dist: pydantic (>=2.0,<3.0)
|
|
17
|
+
Requires-Dist: ruamel.yaml (>=0.18,<0.19)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Gitlab CICD Python Wrapper
|
|
21
|
+
|
|
22
|
+
Pydantic models wrapping every GitLab CI/CD YAML keyword with 1:1 mapping to
|
|
23
|
+
[GitLab CI/CD YAML reference](https://docs.gitlab.com/ci/yaml/).
|
|
24
|
+
|
|
25
|
+
## Features
|
|
26
|
+
|
|
27
|
+
- Programmatic pipeline generation using Python-native objects
|
|
28
|
+
- Validation framework for existing pipelines
|
|
29
|
+
- Comment-preserving round-trip serialization (load -> validate -> write = identical YAML)
|
|
30
|
+
- GitLab CI/CD Component parsing with input validation
|
|
31
|
+
- Both sync and async APIs
|
|
32
|
+
- CLI validator and pre-commit hook
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install gitlab-cicd-python-wrapper
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or with Poetry:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
poetry add gitlab-cicd-python-wrapper
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Compatibility Matrix
|
|
47
|
+
|
|
48
|
+
| GitLab Version | Pipeline Coverage | Component Coverage | Notes |
|
|
49
|
+
|----------------|-------------------|-------------------|-------|
|
|
50
|
+
| 17.x | Full | Full | All keywords as of 17.9 |
|
|
51
|
+
| 16.x | Full | Partial | Components GA in 17.0 |
|
|
52
|
+
| 15.x | Partial | N/A | Deprecated keywords still accepted |
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
### Generate a Pipeline
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from gitlab_cicd_python_wrapper import Pipeline, Job, Image, Artifacts
|
|
60
|
+
|
|
61
|
+
pipeline = Pipeline(
|
|
62
|
+
stages=["build", "test", "deploy"],
|
|
63
|
+
jobs={
|
|
64
|
+
"build": Job(
|
|
65
|
+
stage="build",
|
|
66
|
+
image=Image(name="python:3.11"),
|
|
67
|
+
script=["pip install -r requirements.txt", "python setup.py build"],
|
|
68
|
+
artifacts=Artifacts(paths=["dist/"]),
|
|
69
|
+
),
|
|
70
|
+
"test": Job(
|
|
71
|
+
stage="test",
|
|
72
|
+
script=["pytest"],
|
|
73
|
+
needs=["build"],
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
print(pipeline.to_yaml())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Validate an Existing Pipeline
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
from gitlab_cicd_python_wrapper import Pipeline
|
|
85
|
+
|
|
86
|
+
# Load and validate
|
|
87
|
+
pipeline = Pipeline.from_yaml(".gitlab-ci.yml")
|
|
88
|
+
print(f"Stages: {pipeline.stages}")
|
|
89
|
+
print(f"Jobs: {list(pipeline.jobs.keys())}")
|
|
90
|
+
|
|
91
|
+
# Validate without loading
|
|
92
|
+
errors = Pipeline.validate_file(".gitlab-ci.yml")
|
|
93
|
+
if errors:
|
|
94
|
+
for err in errors:
|
|
95
|
+
print(f"Error: {err}")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Round-Trip Editing (Comments Preserved)
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from gitlab_cicd_python_wrapper import Pipeline
|
|
102
|
+
|
|
103
|
+
# Load -> modify -> save preserves comments and formatting
|
|
104
|
+
pipeline = Pipeline.from_yaml(".gitlab-ci.yml")
|
|
105
|
+
pipeline.to_yaml("validated-output.yml")
|
|
106
|
+
# Output is byte-identical to input for unmodified pipelines
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## CLI Validator
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# Validate pipeline files
|
|
113
|
+
gitlab-cicd-validate .gitlab-ci.yml
|
|
114
|
+
|
|
115
|
+
# Validate multiple files
|
|
116
|
+
gitlab-cicd-validate .gitlab-ci.yml .gitlab/ci/*.yml
|
|
117
|
+
|
|
118
|
+
# Validate component templates
|
|
119
|
+
gitlab-cicd-validate --component templates/build.yml
|
|
120
|
+
|
|
121
|
+
# JSON output
|
|
122
|
+
gitlab-cicd-validate --format json .gitlab-ci.yml
|
|
123
|
+
|
|
124
|
+
# Strict mode (fail on warnings)
|
|
125
|
+
gitlab-cicd-validate --strict .gitlab-ci.yml
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Pre-commit Hook
|
|
129
|
+
|
|
130
|
+
Add to your `.pre-commit-config.yaml`:
|
|
131
|
+
|
|
132
|
+
```yaml
|
|
133
|
+
repos:
|
|
134
|
+
- repo: https://github.com/szymonrychu/gitlab-cicd-python-wrapper
|
|
135
|
+
rev: v0.1.0
|
|
136
|
+
hooks:
|
|
137
|
+
- id: gitlab-cicd-validate
|
|
138
|
+
args: ['--strict']
|
|
139
|
+
- id: gitlab-cicd-validate-components
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
This validates `.gitlab-ci.yml` and any YAML files under `.gitlab/ci/` automatically,
|
|
143
|
+
plus component templates under `templates/`.
|
|
144
|
+
|
|
145
|
+
## Component Parsing
|
|
146
|
+
|
|
147
|
+
Parse and validate GitLab CI/CD components with input validation:
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from gitlab_cicd_python_wrapper import Component
|
|
151
|
+
|
|
152
|
+
# Load a component template
|
|
153
|
+
component = Component.from_yaml("templates/deploy.yml")
|
|
154
|
+
|
|
155
|
+
# Inspect the spec
|
|
156
|
+
for name, input_def in component.spec.inputs.items():
|
|
157
|
+
print(f"{name}: type={input_def.type}, default={input_def.default}")
|
|
158
|
+
|
|
159
|
+
# Validate inputs (raises ValueError on failure)
|
|
160
|
+
resolved = component.validate_inputs({
|
|
161
|
+
"stage": "deploy",
|
|
162
|
+
"environment": "production",
|
|
163
|
+
"timeout": 3600,
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
# Render with inputs interpolated (returns a Pipeline)
|
|
167
|
+
pipeline = component.render({
|
|
168
|
+
"stage": "deploy",
|
|
169
|
+
"environment": "production",
|
|
170
|
+
"timeout": 3600,
|
|
171
|
+
})
|
|
172
|
+
print(pipeline.to_yaml())
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Dynamic Child Pipelines
|
|
176
|
+
|
|
177
|
+
Generate pipelines dynamically for use with `trigger:include`:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from gitlab_cicd_python_wrapper import Pipeline, Job
|
|
181
|
+
|
|
182
|
+
services = ["api", "web", "worker"]
|
|
183
|
+
|
|
184
|
+
jobs = {}
|
|
185
|
+
for svc in services:
|
|
186
|
+
jobs[f"build-{svc}"] = Job(
|
|
187
|
+
stage="build",
|
|
188
|
+
image="docker:latest",
|
|
189
|
+
script=[f"docker build -t {svc} services/{svc}/"],
|
|
190
|
+
tags=["docker"],
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
child = Pipeline(stages=["build"], jobs=jobs)
|
|
194
|
+
child.to_yaml("generated-pipeline.yml")
|
|
195
|
+
|
|
196
|
+
# Parent pipeline triggers it:
|
|
197
|
+
# trigger:
|
|
198
|
+
# include:
|
|
199
|
+
# - artifact: generated-pipeline.yml
|
|
200
|
+
# job: generate-pipeline
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Async API
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
import asyncio
|
|
207
|
+
from gitlab_cicd_python_wrapper import AsyncPipeline, AsyncComponent
|
|
208
|
+
|
|
209
|
+
async def main():
|
|
210
|
+
# Pipeline
|
|
211
|
+
pipeline = await AsyncPipeline.from_yaml(".gitlab-ci.yml")
|
|
212
|
+
print(f"Stages: {pipeline.stages}")
|
|
213
|
+
print(f"Jobs: {list(pipeline.jobs.keys())}")
|
|
214
|
+
await AsyncPipeline.to_yaml(pipeline, "validated-output.yml")
|
|
215
|
+
|
|
216
|
+
# Component
|
|
217
|
+
component = await AsyncComponent.from_yaml("templates/build.yml")
|
|
218
|
+
resolved = component.validate_inputs({"image": "python:3.12"})
|
|
219
|
+
|
|
220
|
+
asyncio.run(main())
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
## Docker
|
|
224
|
+
|
|
225
|
+
Run the validator as a container:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
docker run --rm -v $(pwd):/data szymonrychu/gitlab-cicd-python-wrapper /data/.gitlab-ci.yml
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## PyPI Configuration
|
|
232
|
+
|
|
233
|
+
To publish this library to PyPI using GitHub Actions trusted publishing:
|
|
234
|
+
|
|
235
|
+
1. Create an account at [pypi.org](https://pypi.org)
|
|
236
|
+
2. Go to "Your projects" -> "Publishing" -> "Add a new pending publisher"
|
|
237
|
+
3. Set: GitHub owner=`szymonrychu`, repo=`gitlab-cicd-python-wrapper`,
|
|
238
|
+
workflow=`pypi.yaml`, environment=`pypi`
|
|
239
|
+
4. Create a GitHub environment named `pypi` in your repo settings
|
|
240
|
+
5. Push a semantic version tag (e.g., `v0.1.0`) to trigger the publish workflow
|
|
241
|
+
|
|
242
|
+
## Development
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# Install dependencies
|
|
246
|
+
mise install
|
|
247
|
+
poetry install
|
|
248
|
+
|
|
249
|
+
# Run tests
|
|
250
|
+
poetry run pytest
|
|
251
|
+
|
|
252
|
+
# Run linters
|
|
253
|
+
pre-commit run --all-files
|
|
254
|
+
|
|
255
|
+
# Run CLI
|
|
256
|
+
poetry run gitlab-cicd-validate .gitlab-ci.yml
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## License
|
|
260
|
+
|
|
261
|
+
MIT
|
|
262
|
+
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# Gitlab CICD Python Wrapper
|
|
2
|
+
|
|
3
|
+
Pydantic models wrapping every GitLab CI/CD YAML keyword with 1:1 mapping to
|
|
4
|
+
[GitLab CI/CD YAML reference](https://docs.gitlab.com/ci/yaml/).
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- Programmatic pipeline generation using Python-native objects
|
|
9
|
+
- Validation framework for existing pipelines
|
|
10
|
+
- Comment-preserving round-trip serialization (load -> validate -> write = identical YAML)
|
|
11
|
+
- GitLab CI/CD Component parsing with input validation
|
|
12
|
+
- Both sync and async APIs
|
|
13
|
+
- CLI validator and pre-commit hook
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install gitlab-cicd-python-wrapper
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Or with Poetry:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
poetry add gitlab-cicd-python-wrapper
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Compatibility Matrix
|
|
28
|
+
|
|
29
|
+
| GitLab Version | Pipeline Coverage | Component Coverage | Notes |
|
|
30
|
+
|----------------|-------------------|-------------------|-------|
|
|
31
|
+
| 17.x | Full | Full | All keywords as of 17.9 |
|
|
32
|
+
| 16.x | Full | Partial | Components GA in 17.0 |
|
|
33
|
+
| 15.x | Partial | N/A | Deprecated keywords still accepted |
|
|
34
|
+
|
|
35
|
+
## Quick Start
|
|
36
|
+
|
|
37
|
+
### Generate a Pipeline
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from gitlab_cicd_python_wrapper import Pipeline, Job, Image, Artifacts
|
|
41
|
+
|
|
42
|
+
pipeline = Pipeline(
|
|
43
|
+
stages=["build", "test", "deploy"],
|
|
44
|
+
jobs={
|
|
45
|
+
"build": Job(
|
|
46
|
+
stage="build",
|
|
47
|
+
image=Image(name="python:3.11"),
|
|
48
|
+
script=["pip install -r requirements.txt", "python setup.py build"],
|
|
49
|
+
artifacts=Artifacts(paths=["dist/"]),
|
|
50
|
+
),
|
|
51
|
+
"test": Job(
|
|
52
|
+
stage="test",
|
|
53
|
+
script=["pytest"],
|
|
54
|
+
needs=["build"],
|
|
55
|
+
),
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
print(pipeline.to_yaml())
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Validate an Existing Pipeline
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from gitlab_cicd_python_wrapper import Pipeline
|
|
66
|
+
|
|
67
|
+
# Load and validate
|
|
68
|
+
pipeline = Pipeline.from_yaml(".gitlab-ci.yml")
|
|
69
|
+
print(f"Stages: {pipeline.stages}")
|
|
70
|
+
print(f"Jobs: {list(pipeline.jobs.keys())}")
|
|
71
|
+
|
|
72
|
+
# Validate without loading
|
|
73
|
+
errors = Pipeline.validate_file(".gitlab-ci.yml")
|
|
74
|
+
if errors:
|
|
75
|
+
for err in errors:
|
|
76
|
+
print(f"Error: {err}")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Round-Trip Editing (Comments Preserved)
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from gitlab_cicd_python_wrapper import Pipeline
|
|
83
|
+
|
|
84
|
+
# Load -> modify -> save preserves comments and formatting
|
|
85
|
+
pipeline = Pipeline.from_yaml(".gitlab-ci.yml")
|
|
86
|
+
pipeline.to_yaml("validated-output.yml")
|
|
87
|
+
# Output is byte-identical to input for unmodified pipelines
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## CLI Validator
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Validate pipeline files
|
|
94
|
+
gitlab-cicd-validate .gitlab-ci.yml
|
|
95
|
+
|
|
96
|
+
# Validate multiple files
|
|
97
|
+
gitlab-cicd-validate .gitlab-ci.yml .gitlab/ci/*.yml
|
|
98
|
+
|
|
99
|
+
# Validate component templates
|
|
100
|
+
gitlab-cicd-validate --component templates/build.yml
|
|
101
|
+
|
|
102
|
+
# JSON output
|
|
103
|
+
gitlab-cicd-validate --format json .gitlab-ci.yml
|
|
104
|
+
|
|
105
|
+
# Strict mode (fail on warnings)
|
|
106
|
+
gitlab-cicd-validate --strict .gitlab-ci.yml
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Pre-commit Hook
|
|
110
|
+
|
|
111
|
+
Add to your `.pre-commit-config.yaml`:
|
|
112
|
+
|
|
113
|
+
```yaml
|
|
114
|
+
repos:
|
|
115
|
+
- repo: https://github.com/szymonrychu/gitlab-cicd-python-wrapper
|
|
116
|
+
rev: v0.1.0
|
|
117
|
+
hooks:
|
|
118
|
+
- id: gitlab-cicd-validate
|
|
119
|
+
args: ['--strict']
|
|
120
|
+
- id: gitlab-cicd-validate-components
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This validates `.gitlab-ci.yml` and any YAML files under `.gitlab/ci/` automatically,
|
|
124
|
+
plus component templates under `templates/`.
|
|
125
|
+
|
|
126
|
+
## Component Parsing
|
|
127
|
+
|
|
128
|
+
Parse and validate GitLab CI/CD components with input validation:
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from gitlab_cicd_python_wrapper import Component
|
|
132
|
+
|
|
133
|
+
# Load a component template
|
|
134
|
+
component = Component.from_yaml("templates/deploy.yml")
|
|
135
|
+
|
|
136
|
+
# Inspect the spec
|
|
137
|
+
for name, input_def in component.spec.inputs.items():
|
|
138
|
+
print(f"{name}: type={input_def.type}, default={input_def.default}")
|
|
139
|
+
|
|
140
|
+
# Validate inputs (raises ValueError on failure)
|
|
141
|
+
resolved = component.validate_inputs({
|
|
142
|
+
"stage": "deploy",
|
|
143
|
+
"environment": "production",
|
|
144
|
+
"timeout": 3600,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
# Render with inputs interpolated (returns a Pipeline)
|
|
148
|
+
pipeline = component.render({
|
|
149
|
+
"stage": "deploy",
|
|
150
|
+
"environment": "production",
|
|
151
|
+
"timeout": 3600,
|
|
152
|
+
})
|
|
153
|
+
print(pipeline.to_yaml())
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Dynamic Child Pipelines
|
|
157
|
+
|
|
158
|
+
Generate pipelines dynamically for use with `trigger:include`:
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
from gitlab_cicd_python_wrapper import Pipeline, Job
|
|
162
|
+
|
|
163
|
+
services = ["api", "web", "worker"]
|
|
164
|
+
|
|
165
|
+
jobs = {}
|
|
166
|
+
for svc in services:
|
|
167
|
+
jobs[f"build-{svc}"] = Job(
|
|
168
|
+
stage="build",
|
|
169
|
+
image="docker:latest",
|
|
170
|
+
script=[f"docker build -t {svc} services/{svc}/"],
|
|
171
|
+
tags=["docker"],
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
child = Pipeline(stages=["build"], jobs=jobs)
|
|
175
|
+
child.to_yaml("generated-pipeline.yml")
|
|
176
|
+
|
|
177
|
+
# Parent pipeline triggers it:
|
|
178
|
+
# trigger:
|
|
179
|
+
# include:
|
|
180
|
+
# - artifact: generated-pipeline.yml
|
|
181
|
+
# job: generate-pipeline
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Async API
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
import asyncio
|
|
188
|
+
from gitlab_cicd_python_wrapper import AsyncPipeline, AsyncComponent
|
|
189
|
+
|
|
190
|
+
async def main():
|
|
191
|
+
# Pipeline
|
|
192
|
+
pipeline = await AsyncPipeline.from_yaml(".gitlab-ci.yml")
|
|
193
|
+
print(f"Stages: {pipeline.stages}")
|
|
194
|
+
print(f"Jobs: {list(pipeline.jobs.keys())}")
|
|
195
|
+
await AsyncPipeline.to_yaml(pipeline, "validated-output.yml")
|
|
196
|
+
|
|
197
|
+
# Component
|
|
198
|
+
component = await AsyncComponent.from_yaml("templates/build.yml")
|
|
199
|
+
resolved = component.validate_inputs({"image": "python:3.12"})
|
|
200
|
+
|
|
201
|
+
asyncio.run(main())
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Docker
|
|
205
|
+
|
|
206
|
+
Run the validator as a container:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
docker run --rm -v $(pwd):/data szymonrychu/gitlab-cicd-python-wrapper /data/.gitlab-ci.yml
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## PyPI Configuration
|
|
213
|
+
|
|
214
|
+
To publish this library to PyPI using GitHub Actions trusted publishing:
|
|
215
|
+
|
|
216
|
+
1. Create an account at [pypi.org](https://pypi.org)
|
|
217
|
+
2. Go to "Your projects" -> "Publishing" -> "Add a new pending publisher"
|
|
218
|
+
3. Set: GitHub owner=`szymonrychu`, repo=`gitlab-cicd-python-wrapper`,
|
|
219
|
+
workflow=`pypi.yaml`, environment=`pypi`
|
|
220
|
+
4. Create a GitHub environment named `pypi` in your repo settings
|
|
221
|
+
5. Push a semantic version tag (e.g., `v0.1.0`) to trigger the publish workflow
|
|
222
|
+
|
|
223
|
+
## Development
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
# Install dependencies
|
|
227
|
+
mise install
|
|
228
|
+
poetry install
|
|
229
|
+
|
|
230
|
+
# Run tests
|
|
231
|
+
poetry run pytest
|
|
232
|
+
|
|
233
|
+
# Run linters
|
|
234
|
+
pre-commit run --all-files
|
|
235
|
+
|
|
236
|
+
# Run CLI
|
|
237
|
+
poetry run gitlab-cicd-validate .gitlab-ci.yml
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Gitlab CICD Python Wrapper - Pydantic models for GitLab CI/CD YAML."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from gitlab_cicd_python_wrapper._async import AsyncComponent, AsyncPipeline
|
|
6
|
+
from gitlab_cicd_python_wrapper.artifacts import ArtifactReports, Artifacts
|
|
7
|
+
from gitlab_cicd_python_wrapper.cache import Cache, CacheKey
|
|
8
|
+
from gitlab_cicd_python_wrapper.common import (
|
|
9
|
+
ArtifactAccess,
|
|
10
|
+
ArtifactWhen,
|
|
11
|
+
AutoCancelOnJobFailure,
|
|
12
|
+
AutoCancelOnNewCommit,
|
|
13
|
+
CachePolicy,
|
|
14
|
+
CacheWhen,
|
|
15
|
+
DeploymentTier,
|
|
16
|
+
EnvironmentAction,
|
|
17
|
+
InputType,
|
|
18
|
+
RetryWhen,
|
|
19
|
+
WhenCondition,
|
|
20
|
+
)
|
|
21
|
+
from gitlab_cicd_python_wrapper.component import Component
|
|
22
|
+
from gitlab_cicd_python_wrapper.environment import Environment
|
|
23
|
+
from gitlab_cicd_python_wrapper.globals import AutoCancel, Default, Workflow
|
|
24
|
+
from gitlab_cicd_python_wrapper.image import Image, Service
|
|
25
|
+
from gitlab_cicd_python_wrapper.include import (
|
|
26
|
+
ComponentReference,
|
|
27
|
+
IncludeComponent,
|
|
28
|
+
IncludeItem,
|
|
29
|
+
IncludeLocal,
|
|
30
|
+
IncludeProject,
|
|
31
|
+
IncludeRemote,
|
|
32
|
+
IncludeTemplate,
|
|
33
|
+
)
|
|
34
|
+
from gitlab_cicd_python_wrapper.job import AllowFailure, Inherit, Job, Parallel
|
|
35
|
+
from gitlab_cicd_python_wrapper.needs import Need, NeedsPipeline
|
|
36
|
+
from gitlab_cicd_python_wrapper.pages import Pages
|
|
37
|
+
from gitlab_cicd_python_wrapper.pipeline import Pipeline
|
|
38
|
+
from gitlab_cicd_python_wrapper.release import Release
|
|
39
|
+
from gitlab_cicd_python_wrapper.retry import Retry
|
|
40
|
+
from gitlab_cicd_python_wrapper.rules import Rule, WorkflowRule
|
|
41
|
+
from gitlab_cicd_python_wrapper.secrets import Secret, VaultConfig, VaultEngine
|
|
42
|
+
from gitlab_cicd_python_wrapper.spec import ComponentInput, ComponentSpec, InputRule
|
|
43
|
+
from gitlab_cicd_python_wrapper.trigger import Trigger, TriggerForward
|
|
44
|
+
from gitlab_cicd_python_wrapper.variables import Variable
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"AllowFailure",
|
|
48
|
+
"ArtifactAccess",
|
|
49
|
+
"ArtifactReports",
|
|
50
|
+
"ArtifactWhen",
|
|
51
|
+
"Artifacts",
|
|
52
|
+
"AsyncComponent",
|
|
53
|
+
"AsyncPipeline",
|
|
54
|
+
"AutoCancel",
|
|
55
|
+
"AutoCancelOnJobFailure",
|
|
56
|
+
"AutoCancelOnNewCommit",
|
|
57
|
+
"Cache",
|
|
58
|
+
"CacheKey",
|
|
59
|
+
"CachePolicy",
|
|
60
|
+
"CacheWhen",
|
|
61
|
+
"Component",
|
|
62
|
+
"ComponentInput",
|
|
63
|
+
"ComponentReference",
|
|
64
|
+
"ComponentSpec",
|
|
65
|
+
"Default",
|
|
66
|
+
"DeploymentTier",
|
|
67
|
+
"Environment",
|
|
68
|
+
"EnvironmentAction",
|
|
69
|
+
"Image",
|
|
70
|
+
"IncludeComponent",
|
|
71
|
+
"IncludeItem",
|
|
72
|
+
"IncludeLocal",
|
|
73
|
+
"IncludeProject",
|
|
74
|
+
"IncludeRemote",
|
|
75
|
+
"IncludeTemplate",
|
|
76
|
+
"Inherit",
|
|
77
|
+
"InputRule",
|
|
78
|
+
"InputType",
|
|
79
|
+
"Job",
|
|
80
|
+
"Need",
|
|
81
|
+
"NeedsPipeline",
|
|
82
|
+
"Pages",
|
|
83
|
+
"Parallel",
|
|
84
|
+
"Pipeline",
|
|
85
|
+
"Release",
|
|
86
|
+
"Retry",
|
|
87
|
+
"RetryWhen",
|
|
88
|
+
"Rule",
|
|
89
|
+
"Secret",
|
|
90
|
+
"Service",
|
|
91
|
+
"Trigger",
|
|
92
|
+
"TriggerForward",
|
|
93
|
+
"Variable",
|
|
94
|
+
"VaultConfig",
|
|
95
|
+
"VaultEngine",
|
|
96
|
+
"WhenCondition",
|
|
97
|
+
"Workflow",
|
|
98
|
+
"WorkflowRule",
|
|
99
|
+
]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import aiofiles
|
|
6
|
+
|
|
7
|
+
from gitlab_cicd_python_wrapper.component import Component
|
|
8
|
+
from gitlab_cicd_python_wrapper.pipeline import Pipeline
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AsyncPipeline:
|
|
12
|
+
@classmethod
|
|
13
|
+
async def from_yaml(cls, source: str | Path) -> Pipeline:
|
|
14
|
+
if isinstance(source, Path):
|
|
15
|
+
async with aiofiles.open(source) as f:
|
|
16
|
+
content = await f.read()
|
|
17
|
+
return Pipeline.from_yaml(content)
|
|
18
|
+
return Pipeline.from_yaml(source)
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
async def to_yaml(cls, pipeline: Pipeline, target: str | Path | None = None) -> str:
|
|
22
|
+
result = pipeline.to_yaml()
|
|
23
|
+
if target is not None:
|
|
24
|
+
async with aiofiles.open(target, "w") as f:
|
|
25
|
+
await f.write(result)
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
async def validate_file(cls, path: Path) -> list[str]:
|
|
30
|
+
async with aiofiles.open(path) as f:
|
|
31
|
+
content = await f.read()
|
|
32
|
+
return Pipeline.validate_file_from_string(content)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class AsyncComponent:
|
|
36
|
+
@classmethod
|
|
37
|
+
async def from_yaml(cls, source: str | Path) -> Component:
|
|
38
|
+
if isinstance(source, Path):
|
|
39
|
+
async with aiofiles.open(source) as f:
|
|
40
|
+
content = await f.read()
|
|
41
|
+
return Component.from_yaml(content)
|
|
42
|
+
return Component.from_yaml(source)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
async def to_yaml(cls, component: Component, target: str | Path | None = None) -> str:
|
|
46
|
+
result = component.to_yaml()
|
|
47
|
+
if target is not None:
|
|
48
|
+
async with aiofiles.open(target, "w") as f:
|
|
49
|
+
await f.write(result)
|
|
50
|
+
return result
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, ConfigDict
|
|
4
|
+
|
|
5
|
+
from gitlab_cicd_python_wrapper.common import ArtifactAccess, ArtifactWhen
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ArtifactReports(BaseModel):
|
|
9
|
+
model_config = ConfigDict(populate_by_name=True, extra="allow")
|
|
10
|
+
|
|
11
|
+
junit: list[str] | str | None = None
|
|
12
|
+
coverage_report: dict[str, str] | None = None
|
|
13
|
+
codequality: list[str] | str | None = None
|
|
14
|
+
sast: list[str] | str | None = None
|
|
15
|
+
dependency_scanning: list[str] | str | None = None
|
|
16
|
+
container_scanning: list[str] | str | None = None
|
|
17
|
+
dast: list[str] | str | None = None
|
|
18
|
+
license_scanning: list[str] | str | None = None
|
|
19
|
+
performance: list[str] | str | None = None
|
|
20
|
+
dotenv: list[str] | str | None = None
|
|
21
|
+
terraform: list[str] | str | None = None
|
|
22
|
+
metrics: list[str] | str | None = None
|
|
23
|
+
requirements: list[str] | str | None = None
|
|
24
|
+
secret_detection: list[str] | str | None = None
|
|
25
|
+
cyclonedx: list[str] | str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Artifacts(BaseModel):
|
|
29
|
+
model_config = ConfigDict(populate_by_name=True, extra="forbid")
|
|
30
|
+
|
|
31
|
+
paths: list[str] | None = None
|
|
32
|
+
exclude: list[str] | None = None
|
|
33
|
+
expire_in: str | None = None
|
|
34
|
+
expose_as: str | None = None
|
|
35
|
+
name: str | None = None
|
|
36
|
+
public: bool | None = None
|
|
37
|
+
access: ArtifactAccess | None = None
|
|
38
|
+
reports: ArtifactReports | None = None
|
|
39
|
+
untracked: bool | None = None
|
|
40
|
+
when: ArtifactWhen | None = None
|