generic-gitlab-cicd 0.3.2__tar.gz → 0.3.4__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.
Files changed (38) hide show
  1. generic_gitlab_cicd-0.3.4/PKG-INFO +194 -0
  2. generic_gitlab_cicd-0.3.4/README.md +183 -0
  3. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/__init__.py +1 -1
  4. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/cli.py +8 -1
  5. generic_gitlab_cicd-0.3.4/generic_ci/setup.py +209 -0
  6. generic_gitlab_cicd-0.3.4/generic_gitlab_cicd.egg-info/PKG-INFO +194 -0
  7. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_gitlab_cicd.egg-info/SOURCES.txt +2 -0
  8. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/pyproject.toml +1 -1
  9. generic_gitlab_cicd-0.3.4/tests/test_setup.py +64 -0
  10. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/tests/test_sources.py +10 -0
  11. generic_gitlab_cicd-0.3.2/PKG-INFO +0 -170
  12. generic_gitlab_cicd-0.3.2/README.md +0 -159
  13. generic_gitlab_cicd-0.3.2/generic_gitlab_cicd.egg-info/PKG-INFO +0 -170
  14. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/__main__.py +0 -0
  15. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/compiler.py +0 -0
  16. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/config.py +0 -0
  17. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/dependencies.py +0 -0
  18. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/models.py +0 -0
  19. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/runtime.py +0 -0
  20. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/sources.py +0 -0
  21. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/workflows/__init__.py +0 -0
  22. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/workflows/compiler.py +0 -0
  23. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/workflows/ecosystems.py +0 -0
  24. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/workflows/helm.py +0 -0
  25. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/workflows/models.py +0 -0
  26. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/workflows/publication.py +0 -0
  27. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_ci/workflows/runtime.py +0 -0
  28. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_gitlab_cicd.egg-info/dependency_links.txt +0 -0
  29. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_gitlab_cicd.egg-info/entry_points.txt +0 -0
  30. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_gitlab_cicd.egg-info/requires.txt +0 -0
  31. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/generic_gitlab_cicd.egg-info/top_level.txt +0 -0
  32. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/setup.cfg +0 -0
  33. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/tests/test_chart.py +0 -0
  34. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/tests/test_framework.py +0 -0
  35. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/tests/test_product.py +0 -0
  36. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/tests/test_publication.py +0 -0
  37. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/tests/test_review.py +0 -0
  38. {generic_gitlab_cicd-0.3.2 → generic_gitlab_cicd-0.3.4}/tests/test_workflows.py +0 -0
@@ -0,0 +1,194 @@
1
+ Metadata-Version: 2.4
2
+ Name: generic-gitlab-cicd
3
+ Version: 0.3.4
4
+ Summary: Validated project configuration and reproducible GitLab delivery pipelines
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: pydantic<3,>=2.10
8
+ Requires-Dist: PyYAML<7,>=6
9
+ Requires-Dist: tomlkit<1,>=0.13
10
+ Requires-Dist: packaging<27,>=25
11
+
12
+ # Generic GitLab CI/CD
13
+
14
+ **Define your projects and checks. Generate a reviewable GitLab pipeline.**
15
+
16
+ Generic GitLab CI/CD is a Python toolkit that turns a small delivery configuration into a committed `.gitlab-ci.yml`. It connects your existing test commands, application builds, package publication, container builds, and Helm deployments while keeping infrastructure settings separate from application configuration.
17
+
18
+ The package is named `generic-gitlab-cicd`; the command is `generic-ci`. GitLab and your runners execute the generated pipeline. The CLI validates and generates configuration locally.
19
+
20
+ [Quick start](#quick-start) · [Workflow guide](docs/workflows-revision-one.md) · [CLI reference](docs/cli-reference.md) · [Examples](examples/workflows) · [Documentation](docs/README.md)
21
+
22
+ ## One repository. Tests, packages, and deployment.
23
+
24
+ Keep your shared Python package and API together. Generic CI tests them, publishes the package on release, and deploys the API to OpenShift.
25
+
26
+ ```yaml
27
+ version: 1
28
+
29
+ projects:
30
+ sdk:
31
+ path: packages/sdk
32
+ python: {}
33
+ checks:
34
+ tests:
35
+ script: [uv run --no-sync pytest]
36
+ package:
37
+ index: internal
38
+ release:
39
+ tag: v{version}
40
+ workflows:
41
+ merge-request:
42
+ checks: [tests]
43
+ release:
44
+ checks: [tests]
45
+ publish: true
46
+
47
+ api:
48
+ path: services/api
49
+ depends-on: [sdk]
50
+ python: {}
51
+ checks:
52
+ tests:
53
+ script: [uv run --no-sync pytest]
54
+ container:
55
+ dockerfile: Dockerfile
56
+ release:
57
+ tag: v{version}
58
+ needs: [sdk]
59
+ workflows:
60
+ merge-request:
61
+ checks: [tests]
62
+ release:
63
+ checks: [tests]
64
+ build: [container]
65
+
66
+ deployments:
67
+ api:
68
+ target: production
69
+ chart:
70
+ path: deploy/chart
71
+ values: [deploy/values.yaml]
72
+ images:
73
+ - from: api.build-image
74
+ set:
75
+ repository: apps.api.image.repository
76
+ tag: apps.api.image.tag
77
+ workflows:
78
+ release:
79
+ when: manual
80
+ ```
81
+
82
+ **What you get:**
83
+
84
+ - **On merge requests:** test affected projects. SDK changes also select the API for testing.
85
+ - **On a release tag:** run checks, publish the SDK, and build the API image. The API release depends on SDK publication; its tests and image build can run independently.
86
+ - **When you approve deployment:** deploy the built API image through Helm to OpenShift.
87
+
88
+ Both projects use a shared version: projects at `1.2.0` release under the protected tag `v1.2.0`. Dependency installation follows each project's declared dependencies and committed lockfile; `depends-on` selects work, not package replacements.
89
+
90
+ Your organization supplies prepared runtime images, registry settings, and the production target in `ci-platform.yml`. The application's `deploy/chart` and `deploy/values.yaml` describe the deployment. Start with the [shared chart](charts/generic-app) and [values example](examples/helm-values.yaml); configure the named `internal` publishing index and CI credentials through your platform.
91
+
92
+ ## Quick start
93
+
94
+ Use Python **3.11 or newer**. From a checkout of this toolkit, install the CLI into an isolated environment:
95
+
96
+ ```sh
97
+ python -m venv .venv
98
+ . .venv/bin/activate
99
+ python -m pip install .
100
+ ```
101
+
102
+ Then, from your application repository:
103
+
104
+ ```sh
105
+ generic-ci setup
106
+ ```
107
+
108
+ Setup guides you through organization templates or standalone configuration, previews files before writing, and includes local editor schemas. Start with its single-app configuration and add projects as needed, or choose an organization template for your monorepo. See the [setup guide](docs/setup-revision-one.md) for prepared images, unattended options, and editor integration.
109
+
110
+ After editing your configuration:
111
+
112
+ ```sh
113
+ generic-ci validate
114
+ generic-ci render -o .gitlab-ci.yml
115
+ ```
116
+
117
+ Review and commit `delivery.yml`, your platform configuration, and the generated `.gitlab-ci.yml` together. Validate the generated pipeline with your GitLab CI Lint, then push your branch.
118
+
119
+ **Prefer autocomplete?** Setup exports the schemas automatically. You can also refresh them from the installed CLI:
120
+
121
+ ```sh
122
+ generic-ci schema -o .generic-ci/delivery.schema.json
123
+ generic-ci schema --platform-schema -o .generic-ci/platform.schema.json
124
+ ```
125
+
126
+ Associate them with `delivery.yml` and `ci-platform.yml` in your editor. The [editor guide](docs/setup-revision-one.md#editor-completion-and-validation) includes PyCharm instructions.
127
+
128
+ ## Add the delivery features you need
129
+
130
+ | Goal | Configuration / next step |
131
+ | --- | --- |
132
+ | Test Node, pnpm, or Bun projects | Declare `node.package-manager`, commit the corresponding lockfile, and use your existing commands; see the [workflow guide](docs/workflows-revision-one.md) |
133
+ | Build application artifacts | Set `build.script` and `build.outputs`; select `build: [application]` in the workflow |
134
+ | Retest downstream projects | Add project `depends-on: [sdk]`; this propagates change selection |
135
+ | Transfer generated files to another job | Add check/build `needs: [sdk.build]`; the producer must be enabled in the same event |
136
+ | Build and push containers | Configure `container` and select `build: [container]`; the default builder is Buildah |
137
+ | Publish Python or npm-compatible packages | Configure `package` and workflow `publish`; see [publication behavior](docs/workflows-revision-one.md#package-publication--revision-four) |
138
+ | Coordinate releases | Configure project versions, tag conventions, and optional `release.needs`; see the [release workflow](docs/workflows-revision-one.md#version-bumps-and-release-button) |
139
+ | Deploy to OpenShift | Configure a Helm deployment, target, values, and image bindings; start with the [deployment example](examples/workflows/delivery.yml) and [chart values](examples/helm-values.yaml) |
140
+
141
+ `depends-on` and `needs` have different jobs: the first selects affected projects; the second transfers explicit same-event outputs. Neither implicitly installs an unreleased package. Candidate dependency testing has a separate contract described in the [workflow guide](docs/workflows-revision-one.md).
142
+
143
+ ## Share organization defaults
144
+
145
+ An organization source can provide platform defaults and starter templates. Initialize from a reviewed source revision:
146
+
147
+ ```sh
148
+ generic-ci init --repo ssh://git@gitlab.example.internal/platform/ci-config.git \
149
+ --ref v1.0.0 --template python-service
150
+ ```
151
+
152
+ The repository, ref, and template name above are examples; the source must contain the toolkit's source manifest. Initialization copies starter files and records an exact source commit. Commit the resulting descriptor and lockfile. Later source updates change inherited defaults while preserving consumer-owned template files.
153
+
154
+ For disconnected authoring, cache the locked source or import a Git bundle, then render with `--offline`. This flag controls configuration-source acquisition; it does not enforce network isolation inside application jobs. See [configuration sources](docs/configuration-sources-revision-two.md) for source layout, registration, updates, and offline setup.
155
+
156
+ ## OpenShift deployment
157
+
158
+ The bundled [generic-app chart](charts/generic-app) supports multiple applications, Services, OpenShift Routes with TLS, probes, resources, ports, volumes/mounts, and pod labels/annotations. Images use **repository + tag**; Buildah also records the pushed digest as evidence. Security contexts are configurable, with cluster defaults applying when unset.
159
+
160
+ Helm deployments support complete updates and partial updates that preserve unchanged service images. Partial updates require a compatible existing baseline; chart or configuration changes require a complete deployment. Production requires a protected ref, and candidate dependency runs cannot update persistent deployments.
161
+
162
+ You supply registry credentials, CA trust, a kubeconfig, namespace resources, and application values. The toolkit does not provision the cluster, issue certificates, create PVCs automatically, or make multi-project publication transactional. Keep release image tags immutable: the shared chart uses `IfNotPresent`.
163
+
164
+ ## Existing component users
165
+
166
+ The repository also contains [low-level GitLab CI/CD components](templates) and the older compiler, available through `--format legacy`. They are separate interfaces from the workflow configuration above. New users should start with the CLI walkthrough.
167
+
168
+ Mirror components into your GitLab instance and pin includes to an immutable revision. Consult the [component air-gap guide](docs/airgap.md) for setup. Older BuildKit/digest-only Helm adapters target chart 1.x; the current chart uses OpenShift Routes and repository/tag values. Do not mix those contracts without migrating the configuration.
169
+
170
+ ## Development and validation
171
+
172
+ From a toolkit checkout with Python, Git, Node/npm, rsync, and Helm available:
173
+
174
+ ```sh
175
+ python -m pip install . build twine uv setuptools
176
+ npm ci --ignore-scripts --no-audit --no-fund
177
+ python scripts/sync_embedded.py --check
178
+ python scripts/sync_authoring_skill.py --check
179
+ python -m unittest discover -s tests -v
180
+ npm run test:ci-local
181
+ ```
182
+
183
+ The local pipeline fixtures use pinned `gitlab-ci-local`. Real GitLab/Runner E2E runs are deliberate, through workflow dispatch or an opt-in PR label. Use focused tests while iterating; see [testing commands and evidence](docs/testing-revision-one.md) for prerequisites, integration tests, release checks, and scenario selection.
184
+
185
+ Version 0.3.2 passed [fast CI](https://github.com/Heknon/generic-gitlab-cicd/actions/runs/33975049919) and [six real GitLab E2E scenarios](https://github.com/Heknon/generic-gitlab-cicd/actions/runs/33975818144). That evidence covers the tested pipeline fixtures, not deployment to your registry or OpenShift cluster. Qualify Buildah execution, registry access, Route admission, and rollout/rollback on your infrastructure before production use. Workflow runtime execution currently targets Linux; Windows workflow execution is not supported.
186
+
187
+ ## Documentation
188
+
189
+ - [CLI commands, file ownership, and path rules](docs/cli-reference.md)
190
+ - [Checks, builds, releases, and deployment workflows](docs/workflows-revision-one.md)
191
+ - [Organization configuration sources](docs/configuration-sources-revision-two.md)
192
+ - [AI-assisted authoring and portable skill](docs/ai-authoring-revision-three.md)
193
+ - [Publishing the toolkit to PyPI](docs/pypi-release.md)
194
+ - [Full documentation map](docs/README.md)
@@ -0,0 +1,183 @@
1
+ # Generic GitLab CI/CD
2
+
3
+ **Define your projects and checks. Generate a reviewable GitLab pipeline.**
4
+
5
+ Generic GitLab CI/CD is a Python toolkit that turns a small delivery configuration into a committed `.gitlab-ci.yml`. It connects your existing test commands, application builds, package publication, container builds, and Helm deployments while keeping infrastructure settings separate from application configuration.
6
+
7
+ The package is named `generic-gitlab-cicd`; the command is `generic-ci`. GitLab and your runners execute the generated pipeline. The CLI validates and generates configuration locally.
8
+
9
+ [Quick start](#quick-start) · [Workflow guide](docs/workflows-revision-one.md) · [CLI reference](docs/cli-reference.md) · [Examples](examples/workflows) · [Documentation](docs/README.md)
10
+
11
+ ## One repository. Tests, packages, and deployment.
12
+
13
+ Keep your shared Python package and API together. Generic CI tests them, publishes the package on release, and deploys the API to OpenShift.
14
+
15
+ ```yaml
16
+ version: 1
17
+
18
+ projects:
19
+ sdk:
20
+ path: packages/sdk
21
+ python: {}
22
+ checks:
23
+ tests:
24
+ script: [uv run --no-sync pytest]
25
+ package:
26
+ index: internal
27
+ release:
28
+ tag: v{version}
29
+ workflows:
30
+ merge-request:
31
+ checks: [tests]
32
+ release:
33
+ checks: [tests]
34
+ publish: true
35
+
36
+ api:
37
+ path: services/api
38
+ depends-on: [sdk]
39
+ python: {}
40
+ checks:
41
+ tests:
42
+ script: [uv run --no-sync pytest]
43
+ container:
44
+ dockerfile: Dockerfile
45
+ release:
46
+ tag: v{version}
47
+ needs: [sdk]
48
+ workflows:
49
+ merge-request:
50
+ checks: [tests]
51
+ release:
52
+ checks: [tests]
53
+ build: [container]
54
+
55
+ deployments:
56
+ api:
57
+ target: production
58
+ chart:
59
+ path: deploy/chart
60
+ values: [deploy/values.yaml]
61
+ images:
62
+ - from: api.build-image
63
+ set:
64
+ repository: apps.api.image.repository
65
+ tag: apps.api.image.tag
66
+ workflows:
67
+ release:
68
+ when: manual
69
+ ```
70
+
71
+ **What you get:**
72
+
73
+ - **On merge requests:** test affected projects. SDK changes also select the API for testing.
74
+ - **On a release tag:** run checks, publish the SDK, and build the API image. The API release depends on SDK publication; its tests and image build can run independently.
75
+ - **When you approve deployment:** deploy the built API image through Helm to OpenShift.
76
+
77
+ Both projects use a shared version: projects at `1.2.0` release under the protected tag `v1.2.0`. Dependency installation follows each project's declared dependencies and committed lockfile; `depends-on` selects work, not package replacements.
78
+
79
+ Your organization supplies prepared runtime images, registry settings, and the production target in `ci-platform.yml`. The application's `deploy/chart` and `deploy/values.yaml` describe the deployment. Start with the [shared chart](charts/generic-app) and [values example](examples/helm-values.yaml); configure the named `internal` publishing index and CI credentials through your platform.
80
+
81
+ ## Quick start
82
+
83
+ Use Python **3.11 or newer**. From a checkout of this toolkit, install the CLI into an isolated environment:
84
+
85
+ ```sh
86
+ python -m venv .venv
87
+ . .venv/bin/activate
88
+ python -m pip install .
89
+ ```
90
+
91
+ Then, from your application repository:
92
+
93
+ ```sh
94
+ generic-ci setup
95
+ ```
96
+
97
+ Setup guides you through organization templates or standalone configuration, previews files before writing, and includes local editor schemas. Start with its single-app configuration and add projects as needed, or choose an organization template for your monorepo. See the [setup guide](docs/setup-revision-one.md) for prepared images, unattended options, and editor integration.
98
+
99
+ After editing your configuration:
100
+
101
+ ```sh
102
+ generic-ci validate
103
+ generic-ci render -o .gitlab-ci.yml
104
+ ```
105
+
106
+ Review and commit `delivery.yml`, your platform configuration, and the generated `.gitlab-ci.yml` together. Validate the generated pipeline with your GitLab CI Lint, then push your branch.
107
+
108
+ **Prefer autocomplete?** Setup exports the schemas automatically. You can also refresh them from the installed CLI:
109
+
110
+ ```sh
111
+ generic-ci schema -o .generic-ci/delivery.schema.json
112
+ generic-ci schema --platform-schema -o .generic-ci/platform.schema.json
113
+ ```
114
+
115
+ Associate them with `delivery.yml` and `ci-platform.yml` in your editor. The [editor guide](docs/setup-revision-one.md#editor-completion-and-validation) includes PyCharm instructions.
116
+
117
+ ## Add the delivery features you need
118
+
119
+ | Goal | Configuration / next step |
120
+ | --- | --- |
121
+ | Test Node, pnpm, or Bun projects | Declare `node.package-manager`, commit the corresponding lockfile, and use your existing commands; see the [workflow guide](docs/workflows-revision-one.md) |
122
+ | Build application artifacts | Set `build.script` and `build.outputs`; select `build: [application]` in the workflow |
123
+ | Retest downstream projects | Add project `depends-on: [sdk]`; this propagates change selection |
124
+ | Transfer generated files to another job | Add check/build `needs: [sdk.build]`; the producer must be enabled in the same event |
125
+ | Build and push containers | Configure `container` and select `build: [container]`; the default builder is Buildah |
126
+ | Publish Python or npm-compatible packages | Configure `package` and workflow `publish`; see [publication behavior](docs/workflows-revision-one.md#package-publication--revision-four) |
127
+ | Coordinate releases | Configure project versions, tag conventions, and optional `release.needs`; see the [release workflow](docs/workflows-revision-one.md#version-bumps-and-release-button) |
128
+ | Deploy to OpenShift | Configure a Helm deployment, target, values, and image bindings; start with the [deployment example](examples/workflows/delivery.yml) and [chart values](examples/helm-values.yaml) |
129
+
130
+ `depends-on` and `needs` have different jobs: the first selects affected projects; the second transfers explicit same-event outputs. Neither implicitly installs an unreleased package. Candidate dependency testing has a separate contract described in the [workflow guide](docs/workflows-revision-one.md).
131
+
132
+ ## Share organization defaults
133
+
134
+ An organization source can provide platform defaults and starter templates. Initialize from a reviewed source revision:
135
+
136
+ ```sh
137
+ generic-ci init --repo ssh://git@gitlab.example.internal/platform/ci-config.git \
138
+ --ref v1.0.0 --template python-service
139
+ ```
140
+
141
+ The repository, ref, and template name above are examples; the source must contain the toolkit's source manifest. Initialization copies starter files and records an exact source commit. Commit the resulting descriptor and lockfile. Later source updates change inherited defaults while preserving consumer-owned template files.
142
+
143
+ For disconnected authoring, cache the locked source or import a Git bundle, then render with `--offline`. This flag controls configuration-source acquisition; it does not enforce network isolation inside application jobs. See [configuration sources](docs/configuration-sources-revision-two.md) for source layout, registration, updates, and offline setup.
144
+
145
+ ## OpenShift deployment
146
+
147
+ The bundled [generic-app chart](charts/generic-app) supports multiple applications, Services, OpenShift Routes with TLS, probes, resources, ports, volumes/mounts, and pod labels/annotations. Images use **repository + tag**; Buildah also records the pushed digest as evidence. Security contexts are configurable, with cluster defaults applying when unset.
148
+
149
+ Helm deployments support complete updates and partial updates that preserve unchanged service images. Partial updates require a compatible existing baseline; chart or configuration changes require a complete deployment. Production requires a protected ref, and candidate dependency runs cannot update persistent deployments.
150
+
151
+ You supply registry credentials, CA trust, a kubeconfig, namespace resources, and application values. The toolkit does not provision the cluster, issue certificates, create PVCs automatically, or make multi-project publication transactional. Keep release image tags immutable: the shared chart uses `IfNotPresent`.
152
+
153
+ ## Existing component users
154
+
155
+ The repository also contains [low-level GitLab CI/CD components](templates) and the older compiler, available through `--format legacy`. They are separate interfaces from the workflow configuration above. New users should start with the CLI walkthrough.
156
+
157
+ Mirror components into your GitLab instance and pin includes to an immutable revision. Consult the [component air-gap guide](docs/airgap.md) for setup. Older BuildKit/digest-only Helm adapters target chart 1.x; the current chart uses OpenShift Routes and repository/tag values. Do not mix those contracts without migrating the configuration.
158
+
159
+ ## Development and validation
160
+
161
+ From a toolkit checkout with Python, Git, Node/npm, rsync, and Helm available:
162
+
163
+ ```sh
164
+ python -m pip install . build twine uv setuptools
165
+ npm ci --ignore-scripts --no-audit --no-fund
166
+ python scripts/sync_embedded.py --check
167
+ python scripts/sync_authoring_skill.py --check
168
+ python -m unittest discover -s tests -v
169
+ npm run test:ci-local
170
+ ```
171
+
172
+ The local pipeline fixtures use pinned `gitlab-ci-local`. Real GitLab/Runner E2E runs are deliberate, through workflow dispatch or an opt-in PR label. Use focused tests while iterating; see [testing commands and evidence](docs/testing-revision-one.md) for prerequisites, integration tests, release checks, and scenario selection.
173
+
174
+ Version 0.3.2 passed [fast CI](https://github.com/Heknon/generic-gitlab-cicd/actions/runs/33975049919) and [six real GitLab E2E scenarios](https://github.com/Heknon/generic-gitlab-cicd/actions/runs/33975818144). That evidence covers the tested pipeline fixtures, not deployment to your registry or OpenShift cluster. Qualify Buildah execution, registry access, Route admission, and rollout/rollback on your infrastructure before production use. Workflow runtime execution currently targets Linux; Windows workflow execution is not supported.
175
+
176
+ ## Documentation
177
+
178
+ - [CLI commands, file ownership, and path rules](docs/cli-reference.md)
179
+ - [Checks, builds, releases, and deployment workflows](docs/workflows-revision-one.md)
180
+ - [Organization configuration sources](docs/configuration-sources-revision-two.md)
181
+ - [AI-assisted authoring and portable skill](docs/ai-authoring-revision-three.md)
182
+ - [Publishing the toolkit to PyPI](docs/pypi-release.md)
183
+ - [Full documentation map](docs/README.md)
@@ -1,3 +1,3 @@
1
1
  """Public configuration compiler. Runtime images must carry this exact version."""
2
2
 
3
- __version__ = "0.3.2"
3
+ __version__ = "0.3.4"
@@ -13,6 +13,13 @@ from .models import Pipeline, Platform
13
13
 
14
14
  def main(argv=None):
15
15
  argv = list(sys.argv[1:] if argv is None else argv)
16
+ if argv and argv[0] == 'setup':
17
+ from .setup import setup_main
18
+ try:
19
+ return setup_main(argv[1:])
20
+ except (ValueError, OSError, EOFError, ValidationError) as error:
21
+ print(f'generic-ci setup: {error}', file=sys.stderr)
22
+ return 1
16
23
  if argv and argv[0] == 'source':
17
24
  from .sources import source_main
18
25
  try:
@@ -21,7 +28,7 @@ def main(argv=None):
21
28
  print(f'generic-ci: {error}', file=sys.stderr)
22
29
  return 1
23
30
  parser = argparse.ArgumentParser(prog="generic-ci")
24
- parser.add_argument("command", choices=["schema", "validate", "explain", "render", "init"])
31
+ parser.add_argument("command", choices=["schema", "validate", "explain", "render", "init", "setup"])
25
32
  parser.add_argument("--config")
26
33
  parser.add_argument("--format", choices=["workflows", "legacy"], default="workflows")
27
34
  parser.add_argument("--ecosystem", choices=["python", "npm", "pnpm", "bun"], default="python")
@@ -0,0 +1,209 @@
1
+ """Guided configuration authoring; never executes application commands."""
2
+ import argparse
3
+ import json
4
+ from pathlib import Path
5
+ import sys
6
+ import tempfile
7
+ from urllib.parse import urlsplit
8
+
9
+ import yaml
10
+
11
+ from .models import relative
12
+
13
+
14
+ def setup_main(argv):
15
+ parser = argparse.ArgumentParser(prog='generic-ci setup', description=__doc__)
16
+ parser.add_argument('--root', default='.')
17
+ parser.add_argument('--mode', choices=['organization', 'standalone'])
18
+ parser.add_argument('--yes', action='store_true', help='Use supplied/default answers and write without prompting')
19
+ parser.add_argument('--dry-run', action='store_true', help='Validate and preview without writing project files')
20
+ parser.add_argument('--offline', action='store_true')
21
+ for flag in ['repo', 'ref', 'source', 'template', 'app', 'path', 'ecosystem', 'test-command',
22
+ 'runtime-image', 'builder-image', 'registry', 'preview-registry', 'runner-tag',
23
+ 'deploy', 'helm-image', 'chart-oci', 'chart-version', 'namespace', 'hostname', 'port', 'tls']:
24
+ parser.add_argument('--' + flag)
25
+ args = parser.parse_args(argv)
26
+ root = Path(args.root).absolute()
27
+ if not root.is_dir():
28
+ raise ValueError('--root must be an existing application directory')
29
+ interactive = not args.yes and sys.stdin.isatty()
30
+ if not interactive and not args.yes:
31
+ raise ValueError('non-interactive setup requires --yes (use --dry-run to preview)')
32
+
33
+ def ask(key, question, default=None, choices=None):
34
+ value = getattr(args, key.replace('-', '_'))
35
+ if value is None:
36
+ if interactive:
37
+ value = input(question + (f' [{default}]' if default is not None else '') + ': ').strip() or default
38
+ else:
39
+ value = default
40
+ if value is None or value == '':
41
+ raise ValueError(f'--{key} is required')
42
+ if choices and value not in choices:
43
+ raise ValueError(f'--{key} must be one of: {", ".join(choices)}')
44
+ return value
45
+
46
+ mode = ask('mode', 'Setup path: organization or standalone', 'organization' if args.repo or args.source else 'standalone', ['organization', 'standalone'])
47
+ with tempfile.TemporaryDirectory(prefix='generic-ci-setup-') as temporary:
48
+ stage = Path(temporary)
49
+ if mode == 'organization':
50
+ from .sources import initialize
51
+ repo = None if args.source else ask('repo', 'Organization configuration Git repository (GitHub or GitLab URL)')
52
+ ref = ask('ref', 'Source revision') if repo else None
53
+ if not args.template:
54
+ initialize(stage, None, args.source, repo, ref, args.offline)
55
+ template = ask('template', 'Template name')
56
+ initialize(stage, template, args.source, repo, ref, args.offline)
57
+ descriptor = yaml.safe_load((stage / 'generic-ci.yml').read_text())
58
+ delivery_path = stage / descriptor['delivery']
59
+ config = yaml.safe_load(delivery_path.read_text())
60
+ projects = config.get('projects', {})
61
+ if projects:
62
+ selected = ask('app', 'Application in template to configure', next(iter(projects)), list(projects))
63
+ project = projects[selected]
64
+ project['path'] = relative(ask('path', 'Application directory', project.get('path', '.')))
65
+ checks = project.get('checks', {})
66
+ if len(checks) == 1:
67
+ check = next(iter(checks.values()))
68
+ existing = check.get('script', [])
69
+ if len(existing) == 1:
70
+ check['script'] = [ask('test-command', 'Confirm template check command', existing[0])]
71
+ elif args.test_command:
72
+ raise ValueError('template has multiple/no checks; edit its delivery configuration to select commands')
73
+ delivery_path.write_text(yaml.safe_dump(config, sort_keys=False))
74
+ if any(getattr(args, key) is not None for key in ['ecosystem', 'deploy', 'runtime_image', 'builder_image', 'registry', 'preview_registry', 'runner_tag', 'helm_image', 'chart_oci', 'chart_version', 'namespace', 'hostname', 'port', 'tls']):
75
+ raise ValueError('organization mode inherits infrastructure/deployment settings from its template; use standalone mode for infrastructure flags')
76
+ else:
77
+ if args.repo or args.ref or args.source or args.template:
78
+ raise ValueError('source options require organization mode')
79
+ name = ask('app', 'Application name', 'app')
80
+ path = relative(ask('path', 'Application directory relative to repository', '.'))
81
+ application = root / path
82
+ if not application.is_dir() or not application.resolve().is_relative_to(root.resolve()):
83
+ raise ValueError('application directory must exist inside the repository')
84
+ detected = 'python' if (application / 'pyproject.toml').exists() else 'npm' if (application / 'package.json').exists() else 'generic'
85
+ for lock, manager in [('pnpm-lock.yaml', 'pnpm'), ('bun.lock', 'bun')]:
86
+ if (application / lock).exists():
87
+ detected = manager
88
+ ecosystem = ask('ecosystem', 'Detected ecosystem (confirm or change)', detected, ['python', 'npm', 'pnpm', 'bun', 'generic'])
89
+ command = None
90
+ if ecosystem in ['npm', 'pnpm', 'bun'] and (application / 'package.json').exists():
91
+ if 'test' in json.loads((application / 'package.json').read_text()).get('scripts', {}):
92
+ command = ecosystem + ' run test'
93
+ test = ask('test-command', 'Test command (existing command; never executed by setup)', command)
94
+ runtime = ask('runtime-image', 'Prepared runtime image containing the matching toolkit')
95
+ builder = ask('builder-image', 'Prepared Buildah image containing the matching toolkit')
96
+ registry = ask('registry', 'Release container registry/repository prefix')
97
+ previews = ask('preview-registry', 'Preview container registry/repository prefix')
98
+ tag = ask('runner-tag', 'Runner tag')
99
+ project = {'path': path, 'checks': {'test': {'script': [test]}},
100
+ 'workflows': {'push': {'checks': ['test']}, 'merge-request': {'checks': ['test']}}}
101
+ role = 'python' if ecosystem in ['python', 'generic'] else 'bun' if ecosystem == 'bun' else 'node'
102
+ if ecosystem == 'python':
103
+ project['python'] = {}
104
+ elif ecosystem != 'generic':
105
+ project['node'] = {'package-manager': ecosystem}
106
+ platform = {'version': 1, 'defaults': {'tags': [tag]}, 'images': {role: runtime},
107
+ 'container-builder': {'image': builder},
108
+ 'registries': {'containers': registry, 'previews': previews},
109
+ 'allowed-hosts': sorted({x.split('/')[0] for x in [runtime, builder, registry, previews]}),
110
+ 'variables': {'UV_PYTHON_DOWNLOADS': 'never'}}
111
+ delivery = {'version': 1, 'projects': {name: project}}
112
+ deploy = ask('deploy', 'Create an OpenShift MR preview deployment? yes/no', 'no', ['yes', 'no'])
113
+ if deploy == 'yes':
114
+ if not (application / 'Dockerfile').is_file():
115
+ raise ValueError('deployment setup requires an existing Dockerfile in the application directory')
116
+ helm = ask('helm-image', 'Prepared Helm image containing Git, Python and the toolkit')
117
+ chart = ask('chart-oci', 'Published generic-app 2.x compatible OCI chart URL')
118
+ version = ask('chart-version', 'Pinned chart version')
119
+ namespace = ask('namespace', 'Existing preview namespace')
120
+ hostname = ask('hostname', 'Route hostname (auto lets OpenShift assign a unique hostname)' )
121
+ port = int(ask('port', 'Application listening port', '8080'))
122
+ if not 1 <= port <= 65535:
123
+ raise ValueError('--port must be between 1 and 65535')
124
+ tls = ask('tls', 'Route TLS mode', 'edge', ['edge', 'reencrypt', 'passthrough', 'none'])
125
+ project['workflows']['merge-request']['build'] = ['container']
126
+ project['container'] = {'dockerfile': 'Dockerfile', 'context': '.'}
127
+ platform['images']['helm'] = helm
128
+ platform['allowed-hosts'] = sorted(set(platform['allowed-hosts'] + [helm.split('/')[0], urlsplit(chart).hostname or '']))
129
+ platform['targets'] = {'preview': {'namespace': namespace, 'production': False, 'kubeconfig-variable': 'PREVIEW_KUBECONFIG'}}
130
+ delivery['deployments'] = {name: {'target': 'preview', 'chart': {'oci': chart, 'version': version},
131
+ 'values': ['deploy/values.yaml'], 'images': [{'from': name + '.build-image',
132
+ 'set': {'repository': 'apps.' + name + '.image.repository', 'tag': 'apps.' + name + '.image.tag'}}],
133
+ 'workflows': {'merge-request': {'when': 'manual'}}}}
134
+ # Helm values are literal: CI variables in a hostname are not expanded.
135
+ if '$' in hostname:
136
+ raise ValueError('Route hostname is literal; use auto for an OpenShift-generated hostname')
137
+ route = {'enabled': True}
138
+ if hostname != 'auto':
139
+ route['host'] = hostname
140
+ if tls != 'none':
141
+ route['tls'] = {'termination': tls, 'insecureEdgeTerminationPolicy': 'Redirect'}
142
+ values = {'apps': {name: {'enabled': True, 'replicas': 1, 'image': {'repository': previews + '/' + name, 'tag': 'setup-placeholder'},
143
+ 'config': {}, 'secretRefs': [], 'service': {'enabled': True, 'port': 80, 'targetPort': port}, 'route': route}}}
144
+ (stage / 'deploy').mkdir()
145
+ (stage / 'deploy/values.yaml').write_text(yaml.safe_dump(values, sort_keys=False))
146
+ for filename, value in [('delivery.yml', delivery), ('ci-platform.yml', platform)]:
147
+ (stage / filename).write_text(yaml.safe_dump(value, sort_keys=False))
148
+ from .cli import main
149
+ schema_dir = stage / '.generic-ci'
150
+ schema_dir.mkdir(exist_ok=True)
151
+ for name, extra in [('delivery.schema.json', []), ('platform.schema.json', ['--platform-schema'])]:
152
+ target = schema_dir / name
153
+ if target.exists():
154
+ raise ValueError('template supplies a reserved setup schema path: ' + name)
155
+ if main(['schema', *extra, '-o', str(target)]):
156
+ raise ValueError('schema generation failed')
157
+ import os
158
+ descriptor = yaml.safe_load((stage / 'generic-ci.yml').read_text()) if (stage / 'generic-ci.yml').exists() else {}
159
+ for name, schema in [(descriptor.get('delivery', 'delivery.yml'), 'delivery.schema.json'),
160
+ (descriptor.get('platform') or 'ci-platform.yml', 'platform.schema.json')]:
161
+ target = stage / name
162
+ if target.is_file():
163
+ reference = Path(os.path.relpath(schema_dir / schema, target.parent)).as_posix()
164
+ target.write_text('# yaml-language-server: $schema=' + reference + '\n' + target.read_text())
165
+ if main(['validate', '--root', str(stage), *(['--offline'] if args.offline else [])]):
166
+ raise ValueError('setup configuration did not validate; no project files written')
167
+ if main(['render', '--root', str(stage), '-o', str(stage / '.gitlab-ci.yml'), *(['--offline'] if args.offline else [])]):
168
+ raise ValueError('setup rendering failed; no project files written')
169
+ notes = stage / 'CI-SETUP.md'
170
+ if not notes.exists():
171
+ notes.write_text('# CI setup — revision one\n\nReview delivery.yml and the generated .gitlab-ci.yml before committing.\n\n'
172
+ '- Provide matching toolkit runtime images, runner tags, registry authentication and CA trust.\n'
173
+ '- Local schemas: .generic-ci/delivery.schema.json and .generic-ci/platform.schema.json. In PyCharm Settings > Languages & Frameworks > Schemas and DTDs > JSON Schema Mappings, associate each schema with its YAML file. YAML-language-server editors can use the generated schema comment.\n'
174
+ '- Commit package-manager lockfiles and declare the dependencies needed by your checks.\n'
175
+ '- For previews, provide PREVIEW_KUBECONFIG as a GitLab file variable, an existing namespace, image pull secrets and a published compatible chart.\n'
176
+ '- Review Route host uniqueness, TLS certificates/backend trust, probes and application secrets in deploy/values.yaml when present.\n'
177
+ '- Run generic-ci validate and generic-ci render -o .gitlab-ci.yml after editing.\n'
178
+ '- Validate with your GitLab CI Lint and execute a test pipeline. Setup does not build, publish, provision or deploy anything.\n')
179
+ files = sorted(p for p in stage.rglob('*') if p.is_file())
180
+ for file in files:
181
+ dest = root / file.relative_to(stage)
182
+ if dest.exists() or dest.is_symlink() or any(p.is_symlink() for p in dest.parents):
183
+ raise ValueError(f'setup will not overwrite or follow symlinks: {dest}')
184
+ if any(p.exists() and not p.is_dir() for p in dest.parents):
185
+ raise ValueError(f'parent is not a directory: {dest}')
186
+ print('Proposed files:')
187
+ for file in files:
188
+ print(' ' + file.relative_to(stage).as_posix())
189
+ print('\nDelivery configuration:\n' + (stage / descriptor.get('delivery', 'delivery.yml')).read_text())
190
+ if args.dry_run:
191
+ print('Dry run: no project files written.')
192
+ return 0
193
+ if interactive and input('Write these files? [y/N]: ').strip().lower() not in ['y', 'yes']:
194
+ print('Cancelled: no project files written.')
195
+ return 0
196
+ created = []
197
+ try:
198
+ for file in files:
199
+ dest = root / file.relative_to(stage)
200
+ dest.parent.mkdir(parents=True, exist_ok=True)
201
+ with dest.open('xb') as stream:
202
+ created.append(dest)
203
+ stream.write(file.read_bytes())
204
+ except OSError:
205
+ for file in created:
206
+ file.unlink(missing_ok=True)
207
+ raise
208
+ print('Setup complete. Review CI-SETUP.md, validate with GitLab CI Lint, and commit the configuration.')
209
+ return 0