openshift-template-client 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.
- openshift_template_client-0.1.0/AGENTS.md +82 -0
- openshift_template_client-0.1.0/CODEX_PROMPT.md +57 -0
- openshift_template_client-0.1.0/MANIFEST.in +3 -0
- openshift_template_client-0.1.0/PKG-INFO +415 -0
- openshift_template_client-0.1.0/README.md +403 -0
- openshift_template_client-0.1.0/SKILLS.md +59 -0
- openshift_template_client-0.1.0/START_HERE.md +116 -0
- openshift_template_client-0.1.0/docs/api-notes.md +81 -0
- openshift_template_client-0.1.0/docs/publishing.md +101 -0
- openshift_template_client-0.1.0/docs/requirements.md +117 -0
- openshift_template_client-0.1.0/pyproject.toml +31 -0
- openshift_template_client-0.1.0/setup.cfg +4 -0
- openshift_template_client-0.1.0/src/openshift_template_client.egg-info/PKG-INFO +415 -0
- openshift_template_client-0.1.0/src/openshift_template_client.egg-info/SOURCES.txt +20 -0
- openshift_template_client-0.1.0/src/openshift_template_client.egg-info/dependency_links.txt +1 -0
- openshift_template_client-0.1.0/src/openshift_template_client.egg-info/requires.txt +6 -0
- openshift_template_client-0.1.0/src/openshift_template_client.egg-info/top_level.txt +1 -0
- openshift_template_client-0.1.0/src/openshift_templates/__init__.py +5 -0
- openshift_template_client-0.1.0/src/openshift_templates/_client.py +506 -0
- openshift_template_client-0.1.0/src/openshift_templates/py.typed +0 -0
- openshift_template_client-0.1.0/tests/conftest.py +16 -0
- openshift_template_client-0.1.0/tests/test_client.py +866 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Project instructions
|
|
2
|
+
|
|
3
|
+
Build a minimal, synchronous Python package named `openshift-template-client`,
|
|
4
|
+
imported as `openshift_templates`. It wraps OpenShift Template processing and
|
|
5
|
+
resource apply/delete for a separate mini scheduler.
|
|
6
|
+
|
|
7
|
+
Read `docs/requirements.md` and `docs/api-notes.md` before changing behavior.
|
|
8
|
+
Read `CODEX_PROMPT.md` for the initial upgrade task. These project requirements
|
|
9
|
+
take precedence over generic skill preferences.
|
|
10
|
+
|
|
11
|
+
## Keep it small
|
|
12
|
+
|
|
13
|
+
- Support Python 3.9+. Do not raise the minimum version as part of this work.
|
|
14
|
+
- Use the existing `src/` layout and `pyproject.toml`.
|
|
15
|
+
- Keep `kubernetes` as the only direct runtime dependency. Use its DynamicClient.
|
|
16
|
+
- Prefer straightforward functions, dictionaries, and a small number of modules.
|
|
17
|
+
- Add an abstraction only when it removes demonstrated complexity.
|
|
18
|
+
- Keep scheduling, task queues, background reconciliation, and web APIs outside
|
|
19
|
+
this package. Do not introduce an operator, framework, plugin system, or CLI.
|
|
20
|
+
- pip/venv must remain supported; uv may be used as optional development tooling.
|
|
21
|
+
|
|
22
|
+
## Stateless lifecycle
|
|
23
|
+
|
|
24
|
+
- `create()` and `delete()` must work using the stored template name, template
|
|
25
|
+
namespace, target namespace, and parameters, including after a process restart.
|
|
26
|
+
- Do not require a saved inventory, database, or previous `create()` return value.
|
|
27
|
+
- Returning resource references/results in memory is fine. The caller need not
|
|
28
|
+
persist them. An existing optional reference-based deletion path may remain
|
|
29
|
+
if it stays small, but it must not become required.
|
|
30
|
+
- For the first release, reject unresolved random parameter generators in BOTH
|
|
31
|
+
create and template-based delete. Explicit parameter values may override them.
|
|
32
|
+
This intentionally conservative rule makes replay predictable.
|
|
33
|
+
- Require stable `metadata.name`; reject `generateName`.
|
|
34
|
+
- Document that stateless replay depends on a compatible template revision and
|
|
35
|
+
identical resource identities. Do not claim recovery after arbitrary template
|
|
36
|
+
changes or original-UID protection without retaining that UID.
|
|
37
|
+
|
|
38
|
+
## OpenShift behavior
|
|
39
|
+
|
|
40
|
+
- Fetch an existing Template and submit a copy to `processedtemplates`.
|
|
41
|
+
- Let OpenShift substitute parameters. Do not implement a local template engine.
|
|
42
|
+
- Use direct API calls; no `oc`/`kubectl` subprocesses.
|
|
43
|
+
- Keep template and target namespaces separate and explicit.
|
|
44
|
+
- Accept only namespaced resources in the requested target namespace. Validate
|
|
45
|
+
all rendered targets before applying or deleting any of them.
|
|
46
|
+
- Do not create CRDs, Namespaces, ClusterRoles, or other cluster-scoped resources.
|
|
47
|
+
- Apply with server-side apply; preserve field conflicts rather than forcing them.
|
|
48
|
+
- Delete in reverse rendered order. Treat 404 as already absent. Preserve 403,
|
|
49
|
+
409, validation failures, timeouts, and partial failures for the caller.
|
|
50
|
+
- Do not add automatic rollback or blind retries of mutations.
|
|
51
|
+
- Support explicit kubeconfig/context and in-cluster ServiceAccount auth.
|
|
52
|
+
- Enable TLS verification by default, with explicit `connect(verify_ssl=False)` opt-out.
|
|
53
|
+
Make request timeouts configurable.
|
|
54
|
+
- Never log tokens, parameter values, full rendered Secrets, or kubeconfig content.
|
|
55
|
+
- Importing the package must not authenticate, contact a cluster, or write files.
|
|
56
|
+
|
|
57
|
+
## Documentation and validation
|
|
58
|
+
|
|
59
|
+
- Document public functions with type hints, concise docstrings, parameters,
|
|
60
|
+
return values, raised errors, and small usable examples.
|
|
61
|
+
- Keep README usage synchronized with actual code. Distinguish current behavior
|
|
62
|
+
from requested behavior until implementation is complete.
|
|
63
|
+
- Test the observable API contract and error paths. Mock the transport or inject
|
|
64
|
+
a client; ordinary tests must not require a real OpenShift cluster.
|
|
65
|
+
- Include a test that deletes with a fresh client and no previous create result.
|
|
66
|
+
- Explicitly test generated-parameter rejection, both namespace boundaries,
|
|
67
|
+
repeated deletion, field conflicts, partial failures, and request timeouts.
|
|
68
|
+
- Tests must not mirror private implementation details or require arbitrary
|
|
69
|
+
coverage percentages.
|
|
70
|
+
- Keep the package dependency bounds unless evidence justifies a change.
|
|
71
|
+
|
|
72
|
+
Run before finishing:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
python -m pytest
|
|
76
|
+
python -m ruff check .
|
|
77
|
+
python -m ruff format --check .
|
|
78
|
+
python -m build
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Report what changed, verification results, and any remaining live-cluster checks.
|
|
82
|
+
Continue through implementation and validation; do not stop after proposing a plan.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
Upgrade this starter into a minimal, well-documented, stateless Python package
|
|
2
|
+
for OpenShift templates and the resources produced from them.
|
|
3
|
+
|
|
4
|
+
Read AGENTS.md, docs/requirements.md, docs/api-notes.md, README.md, the current
|
|
5
|
+
implementation, and tests before changing code.
|
|
6
|
+
|
|
7
|
+
Use $python-design-patterns to keep the design simple. Use $modern-python
|
|
8
|
+
selectively for development tooling; the project's Python 3.9 requirement,
|
|
9
|
+
pip/venv support, and minimal dependencies override generic tooling preferences.
|
|
10
|
+
If a skill is unavailable, follow the project instructions and continue.
|
|
11
|
+
|
|
12
|
+
The package is called openshift-template-client and imports as
|
|
13
|
+
openshift_templates. Its consumer is my separate mini scheduler.
|
|
14
|
+
|
|
15
|
+
Implement the requirements, rather than stopping after a proposal:
|
|
16
|
+
|
|
17
|
+
1. Preserve a small synchronous API: connect(), process(), create(), delete().
|
|
18
|
+
2. Fetch existing Templates from OpenShift, override supplied parameters, and
|
|
19
|
+
process them using the server's processedtemplates endpoint.
|
|
20
|
+
3. Apply/delete arbitrary supported namespaced resource kinds through the
|
|
21
|
+
Kubernetes Python client, including KubeVirt VirtualMachine.
|
|
22
|
+
4. Make deletion using template name, namespaces, and parameters the primary
|
|
23
|
+
workflow. It must work with a fresh client after restart and without saved
|
|
24
|
+
inventory or a previous create result.
|
|
25
|
+
5. Require deterministic replay. Reject unresolved random parameter generators
|
|
26
|
+
for both create and template-based delete in this first version. Reject
|
|
27
|
+
generateName and document template revision/resource identity assumptions.
|
|
28
|
+
6. Improve early validation, configurable request timeouts, type hints,
|
|
29
|
+
docstrings, partial-failure reporting, and client lifecycle handling without
|
|
30
|
+
adding unnecessary abstractions or runtime dependencies.
|
|
31
|
+
7. Keep operations scoped to the requested namespace; never require cluster
|
|
32
|
+
admin privileges, new CRDs, or cluster-scoped resource creation.
|
|
33
|
+
8. Review Secret stringData with server-side apply. Add a small normalization
|
|
34
|
+
only if needed, using the standard library, with a meaningful test.
|
|
35
|
+
9. Add focused offline tests for the public contract and important failure paths.
|
|
36
|
+
10. Update README with working local kubeconfig and in-cluster create/delete
|
|
37
|
+
examples, required permissions, stateless assumptions, and error behavior.
|
|
38
|
+
|
|
39
|
+
Use the existing package structure and keep kubernetes as the only direct
|
|
40
|
+
runtime dependency. Keep scheduling logic outside the library. Do not build a
|
|
41
|
+
CLI, web service, controller, inventory store, or framework.
|
|
42
|
+
|
|
43
|
+
Briefly state your intended changes, then implement and verify them. Run:
|
|
44
|
+
|
|
45
|
+
python -m pytest
|
|
46
|
+
python -m ruff check .
|
|
47
|
+
python -m ruff format --check .
|
|
48
|
+
python -m build
|
|
49
|
+
|
|
50
|
+
Also verify that the built wheel imports in a clean environment. Do not use real
|
|
51
|
+
cluster credentials or mutate a real cluster for the offline checks. Do not
|
|
52
|
+
publish or push the package as part of this task.
|
|
53
|
+
|
|
54
|
+
Finish with the resulting public API, usage examples, test/build results, and
|
|
55
|
+
any checks that still need a real OpenShift cluster. Make reasonable small
|
|
56
|
+
implementation decisions yourself; ask only if a missing fact changes the
|
|
57
|
+
required behavior.
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openshift-template-client
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Small Python client for OpenShift templates and their resources
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: kubernetes<35,>=30
|
|
8
|
+
Provides-Extra: dev
|
|
9
|
+
Requires-Dist: pytest<9,>=8; extra == "dev"
|
|
10
|
+
Requires-Dist: ruff<1,>=0.9; extra == "dev"
|
|
11
|
+
Requires-Dist: build<2,>=1; extra == "dev"
|
|
12
|
+
|
|
13
|
+
# openshift-template-client
|
|
14
|
+
|
|
15
|
+
A minimal synchronous Python library for a separate mini scheduler to process
|
|
16
|
+
existing OpenShift Templates and create, inspect, or delete their namespaced resources.
|
|
17
|
+
Python 3.9+; `kubernetes>=30,<35` is the only direct runtime dependency.
|
|
18
|
+
|
|
19
|
+
It uses Kubernetes `DynamicClient` discovery, including installed APIs such as
|
|
20
|
+
KubeVirt `VirtualMachine`. OpenShift performs parameter substitution; no local
|
|
21
|
+
template engine or `oc`/`kubectl` subprocess is used. Scheduling stays in the
|
|
22
|
+
caller. Importing the package does not authenticate or contact a cluster.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
After the first successful PyPI release, install remotely without cloning:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
python3 -m venv .venv
|
|
30
|
+
source .venv/bin/activate
|
|
31
|
+
python -m pip install openshift-template-client
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
To select a particular published release, use
|
|
35
|
+
`python -m pip install 'openshift-template-client==0.1.0'` (once `0.1.0` is
|
|
36
|
+
published). To upgrade later, use `python -m pip install --upgrade
|
|
37
|
+
openshift-template-client`. Import the installed package as `openshift_templates`.
|
|
38
|
+
|
|
39
|
+
Before publishing to PyPI, or to use the GitHub source, install directly from
|
|
40
|
+
the repository after pushing the code there. This requires Git:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
python -m pip install 'openshift-template-client @ git+https://github.com/Ravid-Amar/openshift-template-client.git@main'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Replace `@main` with a pushed release tag such as `@v0.1.0` to select that
|
|
47
|
+
revision. For development from a local clone:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/Ravid-Amar/openshift-template-client.git
|
|
51
|
+
cd openshift-template-client
|
|
52
|
+
python -m pip install -e '.[dev]'
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Both pip/venv and the existing `src/` layout are supported. No uv migration is
|
|
56
|
+
required. See [START_HERE.md](START_HERE.md) for wheelhouse installation offline.
|
|
57
|
+
|
|
58
|
+
Pushing a `v*` tag runs the [PyPI publishing workflow](.github/workflows/publish.yml).
|
|
59
|
+
The tag must equal `v` plus the version in `pyproject.toml`. See
|
|
60
|
+
[Publishing releases](docs/publishing.md) for the one-time GitHub/PyPI setup and
|
|
61
|
+
release commands. PyPI installation becomes available after a successful upload;
|
|
62
|
+
creating this workflow alone does not publish a package.
|
|
63
|
+
|
|
64
|
+
## Local kubeconfig: create, then delete after restart
|
|
65
|
+
|
|
66
|
+
The `worker-template` Template must already exist in `infra`. The target
|
|
67
|
+
namespace `workers` and required namespaced APIs must also exist. Adapt the
|
|
68
|
+
parameter names to your Template, including explicit values for every unresolved
|
|
69
|
+
generator. This example assumes `VM_NAME` and `IMAGE_TAG` are its parameters.
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import openshift_templates as ocp
|
|
73
|
+
|
|
74
|
+
api = ocp.connect(kubeconfig="/path/to/kubeconfig", context="dev", request_timeout=(5, 30))
|
|
75
|
+
try:
|
|
76
|
+
refs = ocp.create(
|
|
77
|
+
"worker-template",
|
|
78
|
+
{"VM_NAME": "worker-001", "IMAGE_TAG": "1.2.3"},
|
|
79
|
+
namespace="workers",
|
|
80
|
+
template_namespace="infra",
|
|
81
|
+
api=api,
|
|
82
|
+
request_timeout=(5, 30),
|
|
83
|
+
)
|
|
84
|
+
finally:
|
|
85
|
+
api.client.close()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Later, in a fresh process, use the same template and inputs. No create result,
|
|
89
|
+
resource inventory, or discovery cache needs to be retained:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
import openshift_templates as ocp
|
|
93
|
+
|
|
94
|
+
api = ocp.connect(kubeconfig="/path/to/kubeconfig", context="dev")
|
|
95
|
+
try:
|
|
96
|
+
deleted = ocp.delete(
|
|
97
|
+
"worker-template",
|
|
98
|
+
{"VM_NAME": "worker-001", "IMAGE_TAG": "1.2.3"},
|
|
99
|
+
namespace="workers",
|
|
100
|
+
template_namespace="infra",
|
|
101
|
+
api=api,
|
|
102
|
+
)
|
|
103
|
+
finally:
|
|
104
|
+
api.client.close()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`connect()` without kubeconfig/context uses the Kubernetes client's usual local
|
|
108
|
+
kubeconfig search and current context. Omitting `api` from an operation opens a
|
|
109
|
+
local client and closes it on success or failure. Injected clients remain open
|
|
110
|
+
for reuse; their owner must close `api.client`. `connect()` closes its transport
|
|
111
|
+
if discovery fails. TLS certificate verification defaults to `True`; configure
|
|
112
|
+
the cluster CA in kubeconfig. To disable verification explicitly, use
|
|
113
|
+
`connect(verify_ssl=False)` and pass that client as `api` to operations. This
|
|
114
|
+
setting applies to discovery and all resource requests, with either auth mode
|
|
115
|
+
(including `connect(in_cluster=True, verify_ssl=False)`):
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
params = {"VM_NAME": "worker-001", "IMAGE_TAG": "1.2.3"}
|
|
119
|
+
api = ocp.connect(kubeconfig="/path/to/kubeconfig", context="dev", verify_ssl=False)
|
|
120
|
+
try:
|
|
121
|
+
objects = ocp.get(
|
|
122
|
+
"worker-template", params, namespace="workers", template_namespace="infra", api=api
|
|
123
|
+
)
|
|
124
|
+
finally:
|
|
125
|
+
api.client.close()
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
With verification disabled, the server certificate is not authenticated.
|
|
129
|
+
Kubeconfig updates and HTTP debug output are disabled by `connect()`.
|
|
130
|
+
|
|
131
|
+
## In-cluster ServiceAccount
|
|
132
|
+
|
|
133
|
+
Inside a pod with ServiceAccount credentials and cluster CA mounted, use:
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
import openshift_templates as ocp
|
|
137
|
+
|
|
138
|
+
params = {"VM_NAME": "worker-002", "IMAGE_TAG": "1.2.3"}
|
|
139
|
+
api = ocp.connect(in_cluster=True)
|
|
140
|
+
try:
|
|
141
|
+
ocp.create("worker-template", params, namespace="workers", template_namespace="infra", api=api)
|
|
142
|
+
finally:
|
|
143
|
+
api.client.close()
|
|
144
|
+
|
|
145
|
+
# Later, including in a new pod/process with the same stored job inputs:
|
|
146
|
+
api = ocp.connect(in_cluster=True)
|
|
147
|
+
try:
|
|
148
|
+
ocp.delete("worker-template", params, namespace="workers", template_namespace="infra", api=api)
|
|
149
|
+
finally:
|
|
150
|
+
api.client.close()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Do not combine `in_cluster=True` with kubeconfig/context. Grant this
|
|
154
|
+
ServiceAccount the permissions below in the source and target namespaces.
|
|
155
|
+
|
|
156
|
+
## Public API
|
|
157
|
+
|
|
158
|
+
Import as `openshift_templates`. All operations are synchronous and have type
|
|
159
|
+
hints and docstrings with parameters, returns, errors, and examples.
|
|
160
|
+
|
|
161
|
+
| Function | What it does | Returns |
|
|
162
|
+
| --- | --- | --- |
|
|
163
|
+
| `connect()` | Loads local kubeconfig/context or in-cluster credentials and discovers APIs. Close its transport with `api.client.close()`. | A reusable `DynamicClient`. |
|
|
164
|
+
| `process()` | Fetches a stored Template, overrides parameters in a copy, and asks OpenShift to render it. No resources are deployed. | Rendered manifest dictionaries. |
|
|
165
|
+
| `create()` | Calls `process()`, validates every target, then applies them in order with server-side apply. `field_manager` defaults to `mini-scheduler`; conflicts are preserved. | Identity references, including returned UIDs. |
|
|
166
|
+
| `get()` | Reconstructs template identities and reads the currently deployed objects in order. It takes the same template arguments as `create()`, or `resources=refs`. | Full live dictionaries, including each kind's `status` when present. |
|
|
167
|
+
| `delete()` | Reconstructs template identities and deletes them in reverse order. Missing objects are already absent. It also accepts `resources=refs`. | Successful identity references. |
|
|
168
|
+
|
|
169
|
+
`process`, `create`, `get`, and `delete` accept `template_name`, optional
|
|
170
|
+
`parameters`, required target `namespace`, optional `template_namespace`,
|
|
171
|
+
optional injected `api`, and `request_timeout`. `get`/`delete` accept either
|
|
172
|
+
template inputs or `resources`, never both. Their `template_name` defaults to
|
|
173
|
+
`None` to allow the reference mode; it is required when `resources` is omitted.
|
|
174
|
+
Supplying neither mode is invalid. All five functions have examples
|
|
175
|
+
and explanations in their docstrings; internal helper functions explain their
|
|
176
|
+
single responsibility where they are defined.
|
|
177
|
+
|
|
178
|
+
`template_namespace` defaults to the target `namespace` only when omitted.
|
|
179
|
+
Explicitly invalid/empty namespaces fail before authentication or API requests.
|
|
180
|
+
Parameters are string-keyed mappings of strings, booleans, integers, or finite
|
|
181
|
+
floats; booleans become `true`/`false`, other scalar values become strings.
|
|
182
|
+
Unknown names and `None` values are errors. Unspecified Template defaults remain
|
|
183
|
+
intact. Overrides remove the parameter's `generate` and `from` fields in a copy;
|
|
184
|
+
the stored Template is never changed.
|
|
185
|
+
|
|
186
|
+
Each operation accepts positive seconds or a `(connect, read)` seconds tuple;
|
|
187
|
+
the default is `(10, 60)`. This is a per-request timeout, not a whole-operation
|
|
188
|
+
deadline. `connect(request_timeout=...)` configures discovery and direct client
|
|
189
|
+
calls. Pass the operation's `request_timeout` separately for its Template GET,
|
|
190
|
+
processing POST, and resource mutations. With an injected client, discovery uses
|
|
191
|
+
that client's configuration. Clients constructed by `connect()` disable
|
|
192
|
+
transport retries; injected clients should also disable mutation retries.
|
|
193
|
+
|
|
194
|
+
`process(..., for_delete=True)` remains accepted for compatibility. Deterministic
|
|
195
|
+
parameter checks now apply regardless of that flag, including processing only.
|
|
196
|
+
To inspect rendering:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
objects = ocp.process(
|
|
200
|
+
"worker-template", params, namespace="workers", template_namespace="infra", api=api
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Resource UIDs and optional deletion protection
|
|
205
|
+
|
|
206
|
+
OpenShift assigns each resource a UID and stores it in the live object's
|
|
207
|
+
`metadata.uid`. `create()` copies that UID from the server-side apply response
|
|
208
|
+
into its returned references. The package does not save those references or UIDs
|
|
209
|
+
to disk; retaining them is optional.
|
|
210
|
+
|
|
211
|
+
Template-based deletion works after a pod/process restart using the same job
|
|
212
|
+
inputs. It reprocesses the Template and deletes by name and namespace, without
|
|
213
|
+
fetching the live resources or their UIDs. No saved `create()` result is needed.
|
|
214
|
+
|
|
215
|
+
The optional reference mode skips Template processing and uses any supplied UID
|
|
216
|
+
as a deletion precondition:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
import openshift_templates as ocp
|
|
220
|
+
|
|
221
|
+
params = {"VM_NAME": "worker-001", "IMAGE_TAG": "1.2.3"}
|
|
222
|
+
api = ocp.connect() # Or connect(in_cluster=True) in a pod.
|
|
223
|
+
try:
|
|
224
|
+
refs = ocp.create(
|
|
225
|
+
"worker-template", params, namespace="workers", template_namespace="infra", api=api
|
|
226
|
+
)
|
|
227
|
+
# template_name defaults to None here because resources supplies the identities.
|
|
228
|
+
# A saved UID protects against deleting a replacement object with the same name.
|
|
229
|
+
ocp.delete(namespace="workers", resources=refs, api=api)
|
|
230
|
+
finally:
|
|
231
|
+
api.client.close()
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
If a live object's UID differs from the supplied UID, the server rejects that
|
|
235
|
+
deletion with 409, exposed through `OperationError`. References without UIDs are
|
|
236
|
+
also accepted and delete by name/namespace. Omit `template_name`, `parameters`,
|
|
237
|
+
and `template_namespace` when passing `resources`. An empty reference list is a
|
|
238
|
+
valid no-op. A UID embedded in a rendered Template is never used as a deletion
|
|
239
|
+
precondition.
|
|
240
|
+
|
|
241
|
+
`get()` returns current live UIDs, but reading one before deletion cannot prove
|
|
242
|
+
that it belongs to the original object: a replacement may already exist. Only
|
|
243
|
+
retaining the original UID provides that additional identity protection.
|
|
244
|
+
|
|
245
|
+
## Inspect deployed resources
|
|
246
|
+
|
|
247
|
+
Use the same job inputs after restart, or pass `resources=refs` from create.
|
|
248
|
+
`get()` reads the main resource endpoints, including KubeVirt VirtualMachines;
|
|
249
|
+
it does not poll, wait, or decide whether a workload is ready.
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
import openshift_templates as ocp
|
|
253
|
+
|
|
254
|
+
api = ocp.connect() # Or connect(in_cluster=True) in a pod.
|
|
255
|
+
try:
|
|
256
|
+
objects = ocp.get(
|
|
257
|
+
"worker-template",
|
|
258
|
+
{"VM_NAME": "worker-001", "IMAGE_TAG": "1.2.3"},
|
|
259
|
+
namespace="workers",
|
|
260
|
+
template_namespace="infra",
|
|
261
|
+
api=api,
|
|
262
|
+
)
|
|
263
|
+
for obj in objects:
|
|
264
|
+
print(obj["kind"], obj["metadata"]["name"], obj.get("status", {}))
|
|
265
|
+
finally:
|
|
266
|
+
api.client.close()
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
These are live responses, unlike the desired manifests from `process()`. A kind
|
|
270
|
+
such as ConfigMap may have no `status`. Read errors, including 404 for a missing
|
|
271
|
+
resource, 403, and timeouts, propagate immediately; a failed call returns no
|
|
272
|
+
partial list. Responses include the full object, so avoid logging Secret objects.
|
|
273
|
+
Each object is read sequentially; the list is not an atomic cluster snapshot.
|
|
274
|
+
|
|
275
|
+
## Stateless replay and namespace boundaries
|
|
276
|
+
|
|
277
|
+
- Both create and template-based delete reject unresolved parameter generators,
|
|
278
|
+
even if a generator appears unrelated to an object's name. Supply explicit
|
|
279
|
+
values, or use a nonempty fixed default already in the Template. No random
|
|
280
|
+
value is regenerated by these operations.
|
|
281
|
+
- Every rendered object needs a stable `metadata.name`. `metadata.generateName`
|
|
282
|
+
is rejected even when a name is also supplied.
|
|
283
|
+
- All rendered targets are discovered and validated before any resource apply
|
|
284
|
+
or delete. Only namespaced resources in the requested target namespace are
|
|
285
|
+
accepted. An omitted/empty manifest namespace becomes the target namespace;
|
|
286
|
+
another namespace, or any cluster-scoped kind, is rejected. Namespace and CRD
|
|
287
|
+
creation are outside this library.
|
|
288
|
+
- Stateless replay depends on compatible Template revisions and the same
|
|
289
|
+
parameters reproducing identical API resource kinds, names, and namespaces.
|
|
290
|
+
Keep that revision available. Renaming/removing an object can leave the old
|
|
291
|
+
object undiscoverable by replay. Apply does not prune removed objects.
|
|
292
|
+
- Use unique names per scheduler job. Manage shared resources separately.
|
|
293
|
+
Template-based deletion is by name: it can delete a replacement with the same
|
|
294
|
+
identity and provides no original-UID protection after restart.
|
|
295
|
+
|
|
296
|
+
For core `v1` Secrets, create converts UTF-8 `stringData` to base64 `data` before
|
|
297
|
+
server-side apply, preserving existing keys and giving `stringData` precedence
|
|
298
|
+
on collisions. This follows the API's merge behavior and avoids the documented
|
|
299
|
+
[Secret stringData limitations with server-side apply](https://kubernetes.io/docs/concepts/configuration/secret/#working-with-secrets).
|
|
300
|
+
Normalization uses only the standard library, works on copies, and does not
|
|
301
|
+
change `process()` output. Base64 is not encryption.
|
|
302
|
+
|
|
303
|
+
## Failures and completion
|
|
304
|
+
|
|
305
|
+
Invalid arguments and unsafe rendered identities raise `ValueError`. Failures
|
|
306
|
+
before resource mutations (authentication, discovery, Template GET/POST,
|
|
307
|
+
processing timeouts) propagate directly from the Kubernetes client/transport.
|
|
308
|
+
Other schema validation remains the server's responsibility.
|
|
309
|
+
|
|
310
|
+
Mutation failures raise `OperationError`. Its `.operation` is `Apply` or `Delete`,
|
|
311
|
+
`.resources` contains successful identity references, and `.failures` contains
|
|
312
|
+
`(reference, original_exception)` pairs. Apply stops at its first failure;
|
|
313
|
+
delete attempts every validated target in reverse order. Only an individual
|
|
314
|
+
delete's 404 is a successful no-op. Permission errors (403), field/UID conflicts
|
|
315
|
+
(409), validation errors, and timeouts remain visible. No rollback or automatic
|
|
316
|
+
mutation retry is performed.
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
try:
|
|
320
|
+
ocp.create("worker-template", params, namespace="workers", template_namespace="infra", api=api)
|
|
321
|
+
except ocp.OperationError as error:
|
|
322
|
+
accepted = error.resources
|
|
323
|
+
failed_references = [ref for ref, exception in error.failures]
|
|
324
|
+
# Let your scheduler decide how to reconcile the outcome.
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
Multi-object operations are not transactional. A mutation timeout has an unknown
|
|
328
|
+
outcome: the server may have accepted it. Success means API acceptance, not VM
|
|
329
|
+
readiness or completed background deletion. The package does not log tokens,
|
|
330
|
+
parameter values, or rendered Secrets. References and the `OperationError`
|
|
331
|
+
message contain identities only; raw exceptions/tracebacks may include server
|
|
332
|
+
response bodies, so avoid logging them wholesale.
|
|
333
|
+
|
|
334
|
+
## Namespace-scoped permissions
|
|
335
|
+
|
|
336
|
+
Grant only the API groups/kinds actually used by your Templates. Ordinary API
|
|
337
|
+
discovery access is needed; it grants no cluster-scoped mutation permission.
|
|
338
|
+
`get()` additionally needs target resource `get` permission. No operation needs
|
|
339
|
+
target resource `list` or `watch` permission.
|
|
340
|
+
|
|
341
|
+
| Namespace | API group / resource | Verbs |
|
|
342
|
+
| --- | --- | --- |
|
|
343
|
+
| Source (`infra`) | `template.openshift.io` / `templates` | `get` (can restrict to the Template's name) |
|
|
344
|
+
| Target (`workers`) | `template.openshift.io` / `processedtemplates` | `create` |
|
|
345
|
+
| Target (`workers`) | Each rendered resource, e.g. core `configmaps`, `secrets`; `kubevirt.io` / `virtualmachines` | `create`, `patch`, `delete`; add `get` for inspection |
|
|
346
|
+
|
|
347
|
+
Example Roles for a Template producing ConfigMaps, Secrets, and VirtualMachines:
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
351
|
+
kind: Role
|
|
352
|
+
metadata:
|
|
353
|
+
name: worker-template-reader
|
|
354
|
+
namespace: infra
|
|
355
|
+
rules:
|
|
356
|
+
- apiGroups: [template.openshift.io]
|
|
357
|
+
resources: [templates]
|
|
358
|
+
resourceNames: [worker-template]
|
|
359
|
+
verbs: [get]
|
|
360
|
+
---
|
|
361
|
+
apiVersion: rbac.authorization.k8s.io/v1
|
|
362
|
+
kind: Role
|
|
363
|
+
metadata:
|
|
364
|
+
name: worker-template-manager
|
|
365
|
+
namespace: workers
|
|
366
|
+
rules:
|
|
367
|
+
- apiGroups: [template.openshift.io]
|
|
368
|
+
resources: [processedtemplates]
|
|
369
|
+
verbs: [create]
|
|
370
|
+
- apiGroups: [""]
|
|
371
|
+
resources: [configmaps, secrets]
|
|
372
|
+
verbs: [create, patch, delete, get]
|
|
373
|
+
- apiGroups: [kubevirt.io]
|
|
374
|
+
resources: [virtualmachines]
|
|
375
|
+
verbs: [create, patch, delete, get]
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Have namespace owners bind each Role with a RoleBinding in its namespace to
|
|
379
|
+
your user or the pod's ServiceAccount (specify the ServiceAccount's actual
|
|
380
|
+
namespace, even for a binding in another namespace). The library does not create
|
|
381
|
+
these Roles/bindings. No ClusterRole, CRD installation, or cluster-admin grant is
|
|
382
|
+
required by this workflow. KubeVirt must already be installed if used.
|
|
383
|
+
|
|
384
|
+
## Development and validation
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
python -m pytest
|
|
388
|
+
python -m ruff check .
|
|
389
|
+
python -m ruff format --check .
|
|
390
|
+
python -m build
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Tests block real credential loading/network access and mock transport while
|
|
394
|
+
exercising the real DynamicClient's HTTP construction. They cover replay with a
|
|
395
|
+
fresh client, optional UID preconditions, live status reads, namespace/scope
|
|
396
|
+
boundaries, generators, reverse/repeated deletion, SSA conflicts, partial
|
|
397
|
+
failures, timeouts (including discovery), Secret
|
|
398
|
+
normalization, and client ownership. Builds use `pyproject.toml` and produce a
|
|
399
|
+
wheel and sdist; isolated builds may fetch build dependencies.
|
|
400
|
+
|
|
401
|
+
A clean-environment wheel import can be checked without cluster credentials:
|
|
402
|
+
|
|
403
|
+
```bash
|
|
404
|
+
python -m venv /tmp/openshift-template-wheel-check
|
|
405
|
+
/tmp/openshift-template-wheel-check/bin/python -m pip install dist/*.whl
|
|
406
|
+
/tmp/openshift-template-wheel-check/bin/python -I -c 'import openshift_templates'
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
For a fully offline install/build, prepare a wheelhouse as described in
|
|
410
|
+
[START_HERE.md](START_HERE.md). Python 3.9 syntax compatibility is maintained;
|
|
411
|
+
runtime checks here use Python 3.12 and kubernetes 34.1.0. Actual Python 3.9
|
|
412
|
+
execution and live OpenShift validation remain target-environment checks.
|
|
413
|
+
Live checks should confirm deployed-version processing, RBAC, SSA/Secret and
|
|
414
|
+
KubeVirt behavior, and admission/finalizer effects. See
|
|
415
|
+
[docs/api-notes.md](docs/api-notes.md) for endpoint details and official sources.
|