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.
@@ -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,3 @@
1
+ include *.md
2
+ recursive-include docs *.md
3
+ recursive-include tests *.py
@@ -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.