ccluster-py 0.0.7__tar.gz → 0.0.9.dev129__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 (39) hide show
  1. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/PKG-INFO +17 -11
  2. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/README.md +16 -10
  3. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/pyproject.toml +2 -2
  4. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/pyproject.toml.orig +2 -2
  5. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/ingress_nginx/__init__.py +0 -5
  6. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/deployments/compute_deployment/__init__.py +39 -0
  7. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/deployments/dynamo_deployment/__init__.py +41 -1
  8. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/deployments/inference_deployment/__init__.py +40 -1
  9. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/deployments/job_deployment/__init__.py +39 -0
  10. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/cli.py +11 -3
  11. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/client.py +22 -11
  12. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/v1alpha1/cclclusterconfig.py +39 -2
  13. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/v1alpha1/cclcomponentset.py +5 -2
  14. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/v1alpha1/cclinfraplane.py +103 -6
  15. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/LICENSE +0 -0
  16. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/__init__.py +0 -0
  17. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/__init__.py +0 -0
  18. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/argo_rollouts/__init__.py +0 -0
  19. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/cert_manager/__init__.py +0 -0
  20. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/external_dns/__init__.py +0 -0
  21. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/final_stack/__init__.py +0 -0
  22. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/fluent_bit/__init__.py +0 -0
  23. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/gpu_operator/__init__.py +0 -0
  24. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/istio/__init__.py +0 -0
  25. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/keda/__init__.py +0 -0
  26. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/kube_prometheus_stack/__init__.py +0 -0
  27. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/longhorn/__init__.py +0 -0
  28. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/metrics_server/__init__.py +0 -0
  29. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/network_operator/__init__.py +0 -0
  30. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/opentelemetry_collector/__init__.py +0 -0
  31. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/prometheus_adapter/__init__.py +0 -0
  32. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/spire/__init__.py +0 -0
  33. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/cluster_base/spire_crds/__init__.py +0 -0
  34. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/namespace_base/__init__.py +0 -0
  35. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/resources/kai_gpu_queues/__init__.py +0 -0
  36. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/catalogs/resources/persistent_volume/__init__.py +0 -0
  37. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/py.typed +0 -0
  38. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/v1alpha1/__init__.py +0 -0
  39. {ccluster_py-0.0.7 → ccluster_py-0.0.9.dev129}/src/ccluster/v1alpha1/base.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccluster-py
3
- Version: 0.0.7
3
+ Version: 0.0.9.dev129
4
4
  Summary: Pydantic models and a client for the ccluster.nvidia.com API and its Helm chart catalog
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -80,7 +80,9 @@ Every method takes the object's name by keyword.
80
80
 
81
81
  `apply_infraplane` applies a Secret named `<name>-credentials` in Argo CD's
82
82
  cluster-secret shape (`server`, and a `config` carrying the bearer token and the CA) and a
83
- `CCLInfraPlane` pointing at it;
83
+ `CCLInfraPlane` pointing at it, with the cluster's `domain`. The operator writes that
84
+ domain to the infra-plane cluster's `CCLClusterConfig`, and it cannot be changed once the
85
+ CCLInfraPlane exists;
84
86
  `delete_infraplane` deletes the Secret and the CCLInfraPlane, the CCLInfraPlane first so
85
87
  the operator's finalizer removes the mirrored Argo CD Secret, and leaves the namespace.
86
88
  `ensure_namespace` creates the namespace, and is the only method that does: keeping it
@@ -102,6 +104,7 @@ client = CCLClient("tenant-a", api_client=api_client)
102
104
  client.ensure_namespace()
103
105
  plane = client.apply_infraplane(
104
106
  name="edge-1",
107
+ domain="edge-1.example.com",
105
108
  server="https://10.0.0.1:6443",
106
109
  bearer_token=open("token").read(),
107
110
  ca_cert=open("ca.crt").read(),
@@ -114,7 +117,7 @@ client.delete_infraplane(name="edge-1")
114
117
  Argo CD's special-cased in-cluster address, no ServiceAccount or token needed:
115
118
 
116
119
  ```python
117
- plane = client.apply_infraplane(name="ccluster-local")
120
+ plane = client.apply_infraplane(name="ccluster-local", domain="local.example.com")
118
121
  ```
119
122
 
120
123
  From a workstation, pass an `ApiClient` built from your kubeconfig:
@@ -149,7 +152,7 @@ from ccluster.client import CCLClient, component_set_current_conditions
149
152
 
150
153
  client = CCLClient("tenant-a", api_client=api_client) # api_client as above
151
154
  client.ensure_namespace()
152
- client.apply_infraplane(name="ccluster-local")
155
+ client.apply_infraplane(name="ccluster-local", domain="local.example.com")
153
156
  cs = client.apply_component_set(
154
157
  name="local-base",
155
158
  components=[
@@ -171,8 +174,8 @@ raises `NotFoundError` for a missing set.
171
174
 
172
175
  `component_set_current_conditions` says why a set is not finished yet. It keys its
173
176
  conditions by type and drops the ones the operator computed from an older generation, so
174
- what is left is its verdict on the spec as it now stands — after a failed validation the
175
- other types still carry the previous generation's. `get_component_set` and
177
+ what is left is its verdict on the spec as it now stands — after a failed validation
178
+ `ComponentsApplied` still carries the previous generation's. `get_component_set` and
176
179
  `get_infraplane` read one object and return it as its model, or raise `NotFoundError`.
177
180
 
178
181
  ```python
@@ -185,7 +188,7 @@ if not client.is_component_set_finished(name="local-base"):
185
188
  ```
186
189
 
187
190
  A type is absent when the operator did not get far enough to write it: a set whose
188
- validation failed has no `Progressing` condition for this generation.
191
+ validation failed has no `ComponentsApplied` condition for this generation.
189
192
 
190
193
  `Progressing=False` means every Application matches the operator's current desired spec,
191
194
  Argo CD's latest comparison was of that spec and reports `Healthy` and `Synced`, and no
@@ -193,7 +196,9 @@ operation for it is pending, running or failed.
193
196
  While rollout continues, the reason is `WaveSyncing`, or `WaveSyncFailed` when the last
194
197
  sync of a component's current spec failed. The operator does not retry a failed sync; a
195
198
  later successful sync of that Application in Argo CD clears `WaveSyncFailed` and resumes
196
- the rollout.
199
+ the rollout. When validation failed and the component being waited on needs the operator
200
+ to write its Application, the reason is the `Validated` condition's, such as
201
+ `InfraPlaneNotFound`: the operator writes nothing until validation passes.
197
202
  Updates to already released components are not serialized by wave.
198
203
 
199
204
  `delete_component_set` returns as soon as the delete is accepted. The operator's finalizer
@@ -223,7 +228,7 @@ ccluster init -cp kind-ccluster-cp
223
228
  context, with that control plane:
224
229
 
225
230
  ```bash
226
- ccluster register edge-1 -n tenant-a -ip kind-ccluster-ip \
231
+ ccluster register edge-1 -n tenant-a -ip kind-ccluster-ip --domain edge-1.example.com \
227
232
  --server https://ccluster-ip-control-plane:6443
228
233
  ```
229
234
 
@@ -231,8 +236,9 @@ On the infra plane it creates the ServiceAccount `kube-system/ccluster-manager`,
231
236
  to `cluster-admin` (Argo CD installs CRDs and other cluster-scoped objects there, so it needs
232
237
  the same rights `argocd cluster add` grants), and reads the long-lived token the token
233
238
  controller writes into the Secret `kube-system/ccluster-manager-token`. On the control plane
234
- it creates the namespace if needed and calls `apply_infraplane` with that token and the
235
- cluster CA. Anyone who can read `<name>-credentials` there is cluster-admin on the infra
239
+ it creates the namespace if needed and calls `apply_infraplane` with that token, the
240
+ cluster CA and `--domain`. The control-plane operator uses the same token to write the
241
+ domain to the infra plane's `CCLClusterConfig`. Anyone who can read `<name>-credentials` there is cluster-admin on the infra
236
242
  plane, so keep read access to that namespace's Secrets tight. Running it again reuses the
237
243
  same token.
238
244
 
@@ -58,7 +58,9 @@ Every method takes the object's name by keyword.
58
58
 
59
59
  `apply_infraplane` applies a Secret named `<name>-credentials` in Argo CD's
60
60
  cluster-secret shape (`server`, and a `config` carrying the bearer token and the CA) and a
61
- `CCLInfraPlane` pointing at it;
61
+ `CCLInfraPlane` pointing at it, with the cluster's `domain`. The operator writes that
62
+ domain to the infra-plane cluster's `CCLClusterConfig`, and it cannot be changed once the
63
+ CCLInfraPlane exists;
62
64
  `delete_infraplane` deletes the Secret and the CCLInfraPlane, the CCLInfraPlane first so
63
65
  the operator's finalizer removes the mirrored Argo CD Secret, and leaves the namespace.
64
66
  `ensure_namespace` creates the namespace, and is the only method that does: keeping it
@@ -80,6 +82,7 @@ client = CCLClient("tenant-a", api_client=api_client)
80
82
  client.ensure_namespace()
81
83
  plane = client.apply_infraplane(
82
84
  name="edge-1",
85
+ domain="edge-1.example.com",
83
86
  server="https://10.0.0.1:6443",
84
87
  bearer_token=open("token").read(),
85
88
  ca_cert=open("ca.crt").read(),
@@ -92,7 +95,7 @@ client.delete_infraplane(name="edge-1")
92
95
  Argo CD's special-cased in-cluster address, no ServiceAccount or token needed:
93
96
 
94
97
  ```python
95
- plane = client.apply_infraplane(name="ccluster-local")
98
+ plane = client.apply_infraplane(name="ccluster-local", domain="local.example.com")
96
99
  ```
97
100
 
98
101
  From a workstation, pass an `ApiClient` built from your kubeconfig:
@@ -127,7 +130,7 @@ from ccluster.client import CCLClient, component_set_current_conditions
127
130
 
128
131
  client = CCLClient("tenant-a", api_client=api_client) # api_client as above
129
132
  client.ensure_namespace()
130
- client.apply_infraplane(name="ccluster-local")
133
+ client.apply_infraplane(name="ccluster-local", domain="local.example.com")
131
134
  cs = client.apply_component_set(
132
135
  name="local-base",
133
136
  components=[
@@ -149,8 +152,8 @@ raises `NotFoundError` for a missing set.
149
152
 
150
153
  `component_set_current_conditions` says why a set is not finished yet. It keys its
151
154
  conditions by type and drops the ones the operator computed from an older generation, so
152
- what is left is its verdict on the spec as it now stands — after a failed validation the
153
- other types still carry the previous generation's. `get_component_set` and
155
+ what is left is its verdict on the spec as it now stands — after a failed validation
156
+ `ComponentsApplied` still carries the previous generation's. `get_component_set` and
154
157
  `get_infraplane` read one object and return it as its model, or raise `NotFoundError`.
155
158
 
156
159
  ```python
@@ -163,7 +166,7 @@ if not client.is_component_set_finished(name="local-base"):
163
166
  ```
164
167
 
165
168
  A type is absent when the operator did not get far enough to write it: a set whose
166
- validation failed has no `Progressing` condition for this generation.
169
+ validation failed has no `ComponentsApplied` condition for this generation.
167
170
 
168
171
  `Progressing=False` means every Application matches the operator's current desired spec,
169
172
  Argo CD's latest comparison was of that spec and reports `Healthy` and `Synced`, and no
@@ -171,7 +174,9 @@ operation for it is pending, running or failed.
171
174
  While rollout continues, the reason is `WaveSyncing`, or `WaveSyncFailed` when the last
172
175
  sync of a component's current spec failed. The operator does not retry a failed sync; a
173
176
  later successful sync of that Application in Argo CD clears `WaveSyncFailed` and resumes
174
- the rollout.
177
+ the rollout. When validation failed and the component being waited on needs the operator
178
+ to write its Application, the reason is the `Validated` condition's, such as
179
+ `InfraPlaneNotFound`: the operator writes nothing until validation passes.
175
180
  Updates to already released components are not serialized by wave.
176
181
 
177
182
  `delete_component_set` returns as soon as the delete is accepted. The operator's finalizer
@@ -201,7 +206,7 @@ ccluster init -cp kind-ccluster-cp
201
206
  context, with that control plane:
202
207
 
203
208
  ```bash
204
- ccluster register edge-1 -n tenant-a -ip kind-ccluster-ip \
209
+ ccluster register edge-1 -n tenant-a -ip kind-ccluster-ip --domain edge-1.example.com \
205
210
  --server https://ccluster-ip-control-plane:6443
206
211
  ```
207
212
 
@@ -209,8 +214,9 @@ On the infra plane it creates the ServiceAccount `kube-system/ccluster-manager`,
209
214
  to `cluster-admin` (Argo CD installs CRDs and other cluster-scoped objects there, so it needs
210
215
  the same rights `argocd cluster add` grants), and reads the long-lived token the token
211
216
  controller writes into the Secret `kube-system/ccluster-manager-token`. On the control plane
212
- it creates the namespace if needed and calls `apply_infraplane` with that token and the
213
- cluster CA. Anyone who can read `<name>-credentials` there is cluster-admin on the infra
217
+ it creates the namespace if needed and calls `apply_infraplane` with that token, the
218
+ cluster CA and `--domain`. The control-plane operator uses the same token to write the
219
+ domain to the infra plane's `CCLClusterConfig`. Anyone who can read `<name>-credentials` there is cluster-admin on the infra
214
220
  plane, so keep read access to that namespace's Secrets tight. Running it again reuses the
215
221
  same token.
216
222
 
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["uv_build>=0.11,<0.12"]
2
+ requires = ["uv_build>=0.11,<0.14"]
3
3
  build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "ccluster-py"
7
- version = "0.0.7"
7
+ version = "0.0.9.dev129"
8
8
  description = "Pydantic models and a client for the ccluster.nvidia.com API and its Helm chart catalog"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -1,7 +1,7 @@
1
1
  # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
2
  # SPDX-License-Identifier: Apache-2.0
3
3
  [build-system]
4
- requires = ["uv_build>=0.11,<0.12"]
4
+ requires = ["uv_build>=0.11,<0.14"]
5
5
  build-backend = "uv_build"
6
6
 
7
7
  [project]
@@ -9,7 +9,7 @@ build-backend = "uv_build"
9
9
  # `ccluster` (src/ccluster).
10
10
  name = "ccluster-py"
11
11
  # Placeholder: the release version is the git tag, stamped in by `make python-build`.
12
- version = "0.0.7"
12
+ version = "0.0.9.dev129"
13
13
  description = "Pydantic models and a client for the ccluster.nvidia.com API and its Helm chart catalog"
14
14
  readme = "README.md"
15
15
  license = "Apache-2.0"
@@ -67,11 +67,6 @@ class Controller(BaseModel):
67
67
  extra="forbid",
68
68
  populate_by_name=True,
69
69
  )
70
- service: Toggle | None = None
71
- """
72
- Service is shipped disabled; the gateway in front of the cluster is
73
- expected to be provisioned out of band.
74
- """
75
70
  metrics: Metrics | None = None
76
71
  """
77
72
  Metrics requires the kube-prometheus-stack CRDs. Disable it on a
@@ -64,6 +64,26 @@ class Resources(BaseModel):
64
64
  """
65
65
 
66
66
 
67
+ class SecretKeyRef(BaseModel):
68
+ """
69
+ SecretKeyRef selects one key of a Secret in the release namespace. Both fields are
70
+ required.
71
+ """
72
+
73
+ model_config = ConfigDict(
74
+ extra="forbid",
75
+ populate_by_name=True,
76
+ )
77
+ secret_name: Annotated[str, Field(alias="secretName")]
78
+ """
79
+ SecretName is the Secret's name.
80
+ """
81
+ key: str
82
+ """
83
+ Key is the key in the Secret's data.
84
+ """
85
+
86
+
67
87
  class VolumeMount(BaseModel):
68
88
  """
69
89
  VolumeMount mounts an existing PersistentVolumeClaim into the container. The chart
@@ -134,6 +154,15 @@ class Values(BaseModel):
134
154
  registry and adds it to the pod's imagePullSecrets, next to the namespace's
135
155
  ecr-image-pull secret.
136
156
  """
157
+ image_pull_secret_names: Annotated[
158
+ list[str] | None, Field(alias="imagePullSecretNames")
159
+ ] = None
160
+ """
161
+ ImagePullSecretNames are Secrets in the release namespace, added to the pod's
162
+ imagePullSecrets after the chart's own; a name already listed is skipped. The
163
+ chart never creates or reads them. The kubelet reads them on each pull, so a
164
+ Secret refreshed in place needs no pod restart.
165
+ """
137
166
  resources: ResourceRequirements
138
167
  """
139
168
  Resources are the container's limits and requests. Required.
@@ -149,6 +178,16 @@ class Values(BaseModel):
149
178
  """
150
179
  EnvVars are set in the container, after the chart's own variables.
151
180
  """
181
+ secret_env_vars: Annotated[
182
+ dict[str, SecretKeyRef] | None, Field(alias="secretEnvVars")
183
+ ] = None
184
+ """
185
+ SecretEnvVars set variables in the container from keys of Secrets in the release
186
+ namespace, after EnvVars. The chart never creates or reads the Secrets. The
187
+ container does not start until each Secret has its key, and keeps the values it
188
+ started with. A variable may not also be in EnvVars, or be one the chart sets
189
+ itself.
190
+ """
152
191
  dev_shm_size: Annotated[str | None, Field(alias="devShmSize")] = None
153
192
  """
154
193
  DevShmSize is the size of the /dev/shm tmpfs, as a Kubernetes quantity. The
@@ -197,6 +197,26 @@ class RolloutStrategy(BaseModel):
197
197
  """
198
198
 
199
199
 
200
+ class SecretKeyRef(BaseModel):
201
+ """
202
+ SecretKeyRef selects one key of a Secret in the release namespace. Both fields are
203
+ required.
204
+ """
205
+
206
+ model_config = ConfigDict(
207
+ extra="forbid",
208
+ populate_by_name=True,
209
+ )
210
+ secret_name: Annotated[str, Field(alias="secretName")]
211
+ """
212
+ SecretName is the Secret's name.
213
+ """
214
+ key: str
215
+ """
216
+ Key is the key in the Secret's data.
217
+ """
218
+
219
+
200
220
  class VolumeMount(BaseModel):
201
221
  """
202
222
  VolumeMount mounts an existing PersistentVolumeClaim into the container. The chart
@@ -331,6 +351,15 @@ class Values(BaseModel):
331
351
  ImagePullSecret, when set, creates a dockerconfigjson Secret for ImageURL's
332
352
  registry and adds it to every pod's imagePullSecrets.
333
353
  """
354
+ image_pull_secret_names: Annotated[
355
+ list[str] | None, Field(alias="imagePullSecretNames")
356
+ ] = None
357
+ """
358
+ ImagePullSecretNames are Secrets in the release namespace, added to every pod's
359
+ imagePullSecrets after the chart's own; a name already listed is skipped. The
360
+ chart never creates or reads them. The kubelet reads them on each pull, so a
361
+ Secret refreshed in place needs no pod restart.
362
+ """
334
363
  domain: str
335
364
  """
336
365
  Domain is the DNS zone the deployment's hostname is created in. The Ingress
@@ -372,7 +401,7 @@ class Values(BaseModel):
372
401
  hf_token: Annotated[str | None, Field(alias="hfToken")] = None
373
402
  """
374
403
  HFToken is stored in a Secret and exposed to the Frontend and workers as
375
- HF_TOKEN; gated models need it.
404
+ HF_TOKEN; gated models need it. SecretEnvVars may not also set HF_TOKEN.
376
405
  """
377
406
  env_vars: Annotated[dict[str, str] | None, Field(alias="envVars")] = None
378
407
  """
@@ -381,6 +410,17 @@ class Values(BaseModel):
381
410
  dropped. DYN_ROUTER_MODE is the one exception: it overrides the Frontend's
382
411
  default of kv.
383
412
  """
413
+ secret_env_vars: Annotated[
414
+ dict[str, SecretKeyRef] | None, Field(alias="secretEnvVars")
415
+ ] = None
416
+ """
417
+ SecretEnvVars set variables in the Frontend and every worker from keys of Secrets
418
+ in the release namespace, after EnvVars. The chart never creates or reads the
419
+ Secrets. A container does not start until each Secret has its key, and keeps the
420
+ values it started with. A variable may not also be in EnvVars, or be one the
421
+ chart sets itself. DYN_ROUTER_MODE overrides the Frontend's default of kv, as in
422
+ EnvVars.
423
+ """
384
424
  extra_argv: Annotated[list[str] | None, Field(alias="extraArgv")] = None
385
425
  """
386
426
  ExtraArgv are literal arguments appended to python3 -m dynamo.vllm on every
@@ -175,6 +175,26 @@ class RolloutStrategy(BaseModel):
175
175
  """
176
176
 
177
177
 
178
+ class SecretKeyRef(BaseModel):
179
+ """
180
+ SecretKeyRef selects one key of a Secret in the release namespace. Both fields are
181
+ required.
182
+ """
183
+
184
+ model_config = ConfigDict(
185
+ extra="forbid",
186
+ populate_by_name=True,
187
+ )
188
+ secret_name: Annotated[str, Field(alias="secretName")]
189
+ """
190
+ SecretName is the Secret's name.
191
+ """
192
+ key: str
193
+ """
194
+ Key is the key in the Secret's data.
195
+ """
196
+
197
+
178
198
  class VolumeMount(BaseModel):
179
199
  """
180
200
  VolumeMount mounts an existing PersistentVolumeClaim into the container. The chart
@@ -245,6 +265,15 @@ class Values(BaseModel):
245
265
  registry and adds it to the pod's imagePullSecrets, next to the namespace's
246
266
  ecr-image-pull secret.
247
267
  """
268
+ image_pull_secret_names: Annotated[
269
+ list[str] | None, Field(alias="imagePullSecretNames")
270
+ ] = None
271
+ """
272
+ ImagePullSecretNames are Secrets in the release namespace, added to the pod's
273
+ imagePullSecrets after the chart's own; a name already listed is skipped. The
274
+ chart never creates or reads them. The kubelet reads them on each pull, so a
275
+ Secret refreshed in place needs no pod restart.
276
+ """
248
277
  resources: ResourceRequirements
249
278
  """
250
279
  Resources are the container's limits and requests. Required.
@@ -260,6 +289,16 @@ class Values(BaseModel):
260
289
  """
261
290
  EnvVars are set in the container, after the chart's own variables.
262
291
  """
292
+ secret_env_vars: Annotated[
293
+ dict[str, SecretKeyRef] | None, Field(alias="secretEnvVars")
294
+ ] = None
295
+ """
296
+ SecretEnvVars set variables in the container from keys of Secrets in the release
297
+ namespace, after EnvVars. The chart never creates or reads the Secrets. The
298
+ container does not start until each Secret has its key, and keeps the values it
299
+ started with. A variable may not also be in EnvVars, or be one the chart sets
300
+ itself.
301
+ """
263
302
  dev_shm_size: Annotated[str | None, Field(alias="devShmSize")] = None
264
303
  """
265
304
  DevShmSize is the size of the /dev/shm tmpfs, as a Kubernetes quantity. The
@@ -354,7 +393,7 @@ class Values(BaseModel):
354
393
  hf_token: Annotated[str | None, Field(alias="hfToken")] = None
355
394
  """
356
395
  HFToken is rendered as the HF_TOKEN environment variable, after EnvVars, so it
357
- wins over a token set there.
396
+ wins over a token set there. SecretEnvVars may not also set HF_TOKEN.
358
397
  """
359
398
  enable_node_model_cache: Annotated[
360
399
  bool | None, Field(alias="enableNodeModelCache")
@@ -90,6 +90,26 @@ class Resources(BaseModel):
90
90
  """
91
91
 
92
92
 
93
+ class SecretKeyRef(BaseModel):
94
+ """
95
+ SecretKeyRef selects one key of a Secret in the release namespace. Both fields are
96
+ required.
97
+ """
98
+
99
+ model_config = ConfigDict(
100
+ extra="forbid",
101
+ populate_by_name=True,
102
+ )
103
+ secret_name: Annotated[str, Field(alias="secretName")]
104
+ """
105
+ SecretName is the Secret's name.
106
+ """
107
+ key: str
108
+ """
109
+ Key is the key in the Secret's data.
110
+ """
111
+
112
+
93
113
  class VolumeMount(BaseModel):
94
114
  """
95
115
  VolumeMount mounts an existing PersistentVolumeClaim into the container. The chart
@@ -160,6 +180,15 @@ class Values(BaseModel):
160
180
  registry and adds it to the pod's imagePullSecrets, next to the namespace's
161
181
  ecr-image-pull secret.
162
182
  """
183
+ image_pull_secret_names: Annotated[
184
+ list[str] | None, Field(alias="imagePullSecretNames")
185
+ ] = None
186
+ """
187
+ ImagePullSecretNames are Secrets in the release namespace, added to the pod's
188
+ imagePullSecrets after the chart's own; a name already listed is skipped. The
189
+ chart never creates or reads them. The kubelet reads them on each pull, so a
190
+ Secret refreshed in place needs no pod restart.
191
+ """
163
192
  resources: ResourceRequirements
164
193
  """
165
194
  Resources are the container's limits and requests. Required.
@@ -175,6 +204,16 @@ class Values(BaseModel):
175
204
  """
176
205
  EnvVars are set in the container, after the chart's own variables.
177
206
  """
207
+ secret_env_vars: Annotated[
208
+ dict[str, SecretKeyRef] | None, Field(alias="secretEnvVars")
209
+ ] = None
210
+ """
211
+ SecretEnvVars set variables in the container from keys of Secrets in the release
212
+ namespace, after EnvVars. The chart never creates or reads the Secrets. The
213
+ container does not start until each Secret has its key, and keeps the values it
214
+ started with. A variable may not also be in EnvVars, or be one the chart sets
215
+ itself.
216
+ """
178
217
  dev_shm_size: Annotated[str | None, Field(alias="devShmSize")] = None
179
218
  """
180
219
  DevShmSize is the size of the /dev/shm tmpfs, as a Kubernetes quantity. The
@@ -111,6 +111,12 @@ def init(control_plane: str) -> None:
111
111
  required=True,
112
112
  help="Kubeconfig context of the infra-plane cluster to register.",
113
113
  )
114
+ @click.option(
115
+ "--domain",
116
+ required=True,
117
+ help="The infra-plane cluster's DNS domain, written to its CCLClusterConfig. "
118
+ "Cannot be changed once registered.",
119
+ )
114
120
  @click.option(
115
121
  "--server",
116
122
  default=None,
@@ -121,6 +127,7 @@ def register(
121
127
  name: str,
122
128
  namespace: str,
123
129
  infra_plane: str,
130
+ domain: str,
124
131
  server: str | None,
125
132
  ) -> None:
126
133
  """Register a cluster with the control plane.
@@ -128,8 +135,9 @@ def register(
128
135
  The control plane is the one saved by ccluster init. On the infra plane, creates the
129
136
  ServiceAccount kube-system/ccluster-manager, binds it to cluster-admin, and reads its
130
137
  long-lived token. On the control plane, applies the Secret NAME-credentials carrying
131
- that token and the cluster CA, and the CCLInfraPlane NAME pointing at it. Re-running
132
- reuses the same token.
138
+ that token and the cluster CA, and the CCLInfraPlane NAME pointing at it with the given
139
+ domain. The control-plane operator then writes the domain to the infra plane's
140
+ CCLClusterConfig. Re-running reuses the same token.
133
141
 
134
142
  Pass --server when the kubeconfig's address is not reachable from the control plane,
135
143
  for example a kind cluster, whose kubeconfig points at 127.0.0.1.
@@ -145,7 +153,7 @@ def register(
145
153
  client = CCLClient(namespace, api_client=control_api)
146
154
  client.ensure_namespace()
147
155
  plane = client.apply_infraplane(
148
- name=name, server=server, bearer_token=token, ca_cert=ca_cert
156
+ name=name, domain=domain, server=server, bearer_token=token, ca_cert=ca_cert
149
157
  )
150
158
  click.echo(
151
159
  f"Registered CCLInfraPlane {plane.metadata.namespace}/{plane.metadata.name} "
@@ -180,16 +180,24 @@ class CCLClient:
180
180
  self,
181
181
  *,
182
182
  name: str,
183
+ domain: str,
183
184
  server: str | None = None,
184
185
  bearer_token: str | None = None,
185
186
  ca_cert: str | bytes | None = None,
186
187
  ) -> CCLInfraPlane:
187
188
  """Create or update the CCLInfraPlane called name, for the cluster at server.
188
189
 
189
- Server-side applies a Secret named "<name>-credentials" in Argo CD's
190
- cluster-secret shape, then a CCLInfraPlane pointing at it. ca_cert is the API
191
- server's CA certificate in PEM. The call returns as soon as both objects are
192
- applied; it does not wait for the operator to report Available.
190
+ domain is the cluster's DNS domain. The operator writes it to the cluster's
191
+ CCLClusterConfig, and it cannot be changed once the CCLInfraPlane exists.
192
+
193
+ Server-side applies the CCLInfraPlane, then the Secret named "<name>-credentials"
194
+ it points at, in Argo CD's cluster-secret shape. The plane goes first so that a
195
+ domain the API server rejects - malformed, or changed on an existing plane - fails
196
+ the call before the credentials are written: the operator acts on a changed
197
+ Secret at once, so new credentials under the old domain would retarget the
198
+ existing plane. ca_cert is the API server's CA certificate in PEM. The call
199
+ returns as soon as both objects are applied; it does not wait for the operator to
200
+ report Available.
193
201
 
194
202
  server, bearer_token and ca_cert are all optional. With none given, this
195
203
  registers the cluster the operator itself runs in: server defaults to Argo CD's
@@ -204,6 +212,13 @@ class CCLClient:
204
212
  ca_cert = ca_cert.encode()
205
213
  secret_name = _credentials_secret_name(name)
206
214
 
215
+ plane = CCLInfraPlane(
216
+ metadata=ObjectMeta(name=name, namespace=self.namespace),
217
+ spec=CCLInfraPlaneSpec(
218
+ cluster_credentials_ref=LocalObjectReference(name=secret_name),
219
+ domain=domain,
220
+ ),
221
+ )
207
222
  cluster_config = ClusterConfig(
208
223
  bearer_token=bearer_token,
209
224
  tls_client_config=TLSClientConfig(
@@ -222,15 +237,11 @@ class CCLClient:
222
237
  ),
223
238
  },
224
239
  )
225
- self._apply(self._discovery.secret_gvr, secret)
226
-
227
- plane = CCLInfraPlane(
228
- metadata=ObjectMeta(name=name, namespace=self.namespace),
229
- spec=CCLInfraPlaneSpec(cluster_credentials_ref=LocalObjectReference(name=secret_name)),
230
- )
231
- return CCLInfraPlane.model_validate(
240
+ applied = CCLInfraPlane.model_validate(
232
241
  self._apply_object(self._discovery.infra_plane_gvr, plane)
233
242
  )
243
+ self._apply(self._discovery.secret_gvr, secret)
244
+ return applied
234
245
 
235
246
  def delete_infraplane(self, *, name: str) -> None:
236
247
  """Remove the CCLInfraPlane called name and its credentials Secret.
@@ -102,6 +102,32 @@ class Condition(BaseModel):
102
102
  """
103
103
 
104
104
 
105
+ class LoadBalancer(BaseModel):
106
+ """
107
+ LoadBalancerStatus is the address of a LoadBalancer Service labelled
108
+ ccluster.nvidia.com/loadbalancer. ip and hostname are both empty until the cloud
109
+ provider assigns one.
110
+ """
111
+
112
+ model_config = ConfigDict(
113
+ populate_by_name=True,
114
+ )
115
+ hostname: str | None = None
116
+ """
117
+ hostname is the LoadBalancer's DNS name, set by cloud providers whose load
118
+ balancers are addressed by name rather than by IP: the first hostname the Service
119
+ reports.
120
+ """
121
+ ip: str | None = None
122
+ """
123
+ ip is the LoadBalancer's IP address: the first IP the Service reports.
124
+ """
125
+ name: Annotated[str, Field(max_length=63, min_length=1)]
126
+ """
127
+ name is the Service's ccluster.nvidia.com/loadbalancer label value.
128
+ """
129
+
130
+
105
131
  class CCLClusterConfigStatus(BaseModel):
106
132
  """
107
133
  status defines the observed state of CCLClusterConfig
@@ -112,7 +138,8 @@ class CCLClusterConfigStatus(BaseModel):
112
138
  )
113
139
  conditions: list[Condition] | None = None
114
140
  """
115
- conditions represent the current state of the CCLClusterConfig resource.
141
+ conditions represent the current state of the infra-plane cluster's
142
+ CCLClusterConfig.
116
143
 
117
144
  Condition types reported by the infra-plane operator:
118
145
  - "Verified": every catalogs/cluster_base component is installed in this
@@ -121,7 +148,17 @@ class CCLClusterConfigStatus(BaseModel):
121
148
 
122
149
  The status of each condition is one of True, False, or Unknown.
123
150
  """
151
+ load_balancers: Annotated[
152
+ list[LoadBalancer] | None, Field(alias="loadBalancers")
153
+ ] = None
154
+ """
155
+ loadBalancers are the cluster's LoadBalancer Services labelled
156
+ ccluster.nvidia.com/loadbalancer, one per label value, as the infra-plane operator
157
+ last observed them. A value carried by more than one Service is left out and
158
+ reported in the Verified condition.
159
+ """
124
160
  observed_generation: Annotated[int | None, Field(alias="observedGeneration")] = None
125
161
  """
126
- observedGeneration is the .metadata.generation the conditions were computed from.
162
+ observedGeneration is the CCLClusterConfig's .metadata.generation the conditions
163
+ were computed from.
127
164
  """
@@ -211,7 +211,7 @@ class CCLComponentSetStatus(BaseModel):
211
211
  ] = None
212
212
  """
213
213
  componentSyncs reports the latest observed Application state for each released
214
- component.
214
+ component, judged against the spec at status.observedGeneration.
215
215
  """
216
216
  conditions: list[Condition] | None = None
217
217
  """
@@ -229,5 +229,8 @@ class CCLComponentSetStatus(BaseModel):
229
229
  """
230
230
  observed_generation: Annotated[int | None, Field(alias="observedGeneration")] = None
231
231
  """
232
- observedGeneration is the .metadata.generation the conditions were computed from.
232
+ observedGeneration is the .metadata.generation of the spec the operator last
233
+ reconciled. componentSyncs always describes this generation. A condition can be
234
+ older, as its own observedGeneration says: ComponentsApplied is not rewritten on a
235
+ pass that fails validation.
233
236
  """
@@ -64,6 +64,20 @@ class CCLInfraPlaneSpec(BaseModel):
64
64
  referenced Secret holds. See
65
65
  https://argo-cd.readthedocs.io/en/stable/operator-manual/declarative-setup/#clusters
66
66
  """
67
+ domain: Annotated[
68
+ str,
69
+ Field(
70
+ max_length=253,
71
+ min_length=1,
72
+ pattern="^[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]{0,61}[a-z0-9])?)+$",
73
+ ),
74
+ ]
75
+ """
76
+ domain is the infra-plane cluster's DNS domain. The control-plane operator writes it
77
+ to the CCLClusterConfig singleton on that cluster, which the infra-plane operator
78
+ verifies the cluster against. It must be a lowercase DNS name of at least two labels,
79
+ each at most 63 characters, and it cannot be changed once set.
80
+ """
67
81
 
68
82
 
69
83
  class Status(Enum):
@@ -133,6 +147,78 @@ class Condition(BaseModel):
133
147
  """
134
148
 
135
149
 
150
+ class LoadBalancer(BaseModel):
151
+ """
152
+ LoadBalancerStatus is the address of a LoadBalancer Service labelled
153
+ ccluster.nvidia.com/loadbalancer. ip and hostname are both empty until the cloud
154
+ provider assigns one.
155
+ """
156
+
157
+ model_config = ConfigDict(
158
+ populate_by_name=True,
159
+ )
160
+ hostname: str | None = None
161
+ """
162
+ hostname is the LoadBalancer's DNS name, set by cloud providers whose load
163
+ balancers are addressed by name rather than by IP: the first hostname the Service
164
+ reports.
165
+ """
166
+ ip: str | None = None
167
+ """
168
+ ip is the LoadBalancer's IP address: the first IP the Service reports.
169
+ """
170
+ name: Annotated[str, Field(max_length=63, min_length=1)]
171
+ """
172
+ name is the Service's ccluster.nvidia.com/loadbalancer label value.
173
+ """
174
+
175
+
176
+ class CCLClusterConfigStatus(BaseModel):
177
+ """
178
+ clusterConfig is the status of the CCLClusterConfig singleton on the infra-plane
179
+ cluster, copied verbatim whenever it changes there. It is what the infra-plane
180
+ operator reports about the cluster; ClusterConfigApplied says whether the copy is
181
+ current. Its conditions and observedGeneration are the infra-plane operator's, and
182
+ refer to the CCLClusterConfig, not to this CCLInfraPlane.
183
+ """
184
+
185
+ model_config = ConfigDict(
186
+ populate_by_name=True,
187
+ )
188
+ conditions: list[Condition] | None = None
189
+ """
190
+ conditions represent the current state of the infra-plane cluster's
191
+ CCLClusterConfig.
192
+
193
+ Condition types reported by the infra-plane operator:
194
+ - "Verified": every catalogs/cluster_base component is installed in this
195
+ cluster and configured for it. When False, the message names the charts
196
+ that failed verification.
197
+
198
+ The status of each condition is one of True, False, or Unknown.
199
+ """
200
+ load_balancers: Annotated[
201
+ list[LoadBalancer] | None, Field(alias="loadBalancers")
202
+ ] = None
203
+ """
204
+ loadBalancers are the cluster's LoadBalancer Services labelled
205
+ ccluster.nvidia.com/loadbalancer, one per label value, as the infra-plane operator
206
+ last observed them. A value carried by more than one Service is left out and
207
+ reported in the Verified condition.
208
+ """
209
+ observed_generation: Annotated[int | None, Field(alias="observedGeneration")] = None
210
+ """
211
+ observedGeneration is the CCLClusterConfig's .metadata.generation the conditions
212
+ were computed from.
213
+ """
214
+
215
+
216
+ class Condition1(Condition):
217
+ """
218
+ Condition contains details for one aspect of the current state of this API Resource.
219
+ """
220
+
221
+
136
222
  class CCLInfraPlaneStatus(BaseModel):
137
223
  """
138
224
  status defines the observed state of CCLInfraPlane
@@ -141,15 +227,26 @@ class CCLInfraPlaneStatus(BaseModel):
141
227
  model_config = ConfigDict(
142
228
  populate_by_name=True,
143
229
  )
144
- conditions: list[Condition] | None = None
230
+ cluster_config: Annotated[
231
+ CCLClusterConfigStatus | None,
232
+ Field(alias="clusterConfig", title="CCLClusterConfigStatus"),
233
+ ] = None
234
+ """
235
+ clusterConfig is the status of the CCLClusterConfig singleton on the infra-plane
236
+ cluster, copied verbatim whenever it changes there. It is what the infra-plane
237
+ operator reports about the cluster; ClusterConfigApplied says whether the copy is
238
+ current. Its conditions and observedGeneration are the infra-plane operator's, and
239
+ refer to the CCLClusterConfig, not to this CCLInfraPlane.
240
+ """
241
+ conditions: list[Condition1] | None = None
145
242
  """
146
243
  conditions represent the current state of the CCLInfraPlane resource.
147
- Each condition has a unique type and reflects the status of a specific aspect of the resource.
148
244
 
149
- Standard condition types include:
150
- - "Available": the resource is fully functional
151
- - "Progressing": the resource is being created or updated
152
- - "Degraded": the resource failed to reach or maintain its desired state
245
+ Condition types reported by the control-plane operator:
246
+ - "Available": the namespace's Argo CD AppProject exists and the cluster
247
+ Secret has been mirrored into the Argo CD namespace.
248
+ - "ClusterConfigApplied": the CCLClusterConfig singleton on the infra-plane
249
+ cluster carries this CCLInfraPlane's domain.
153
250
 
154
251
  The status of each condition is one of True, False, or Unknown.
155
252
  """
File without changes