Skip to content

Commit 99fe3bb

Browse files
theoctober19thdeusebio
authored andcommitted
[DPE-7840] Add get-manifest command to generate manifest of K8s resources without creating them
1 parent 941b9f7 commit 99fe3bb

File tree

11 files changed

+295
-82
lines changed

11 files changed

+295
-82
lines changed

spark8t/cli/service_account_registry.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Actions(str, Enum):
5050
CLEAR_CONFIG = "clear-config"
5151
PRIMARY = "get-primary"
5252
LIST = "list"
53+
GET_MANIFEST = "get-manifest"
5354

5455
def __str__(self) -> str:
5556
"""Define string representation.
@@ -122,6 +123,13 @@ def create_service_account_registry_parser(parser: ArgumentParser):
122123
action="store_true",
123124
help="Boolean to ignore Spark Integration Hub generated options.",
124125
)
126+
127+
# subparser for get-manifest
128+
parse_arguments_with(
129+
[spark_user_parser],
130+
subparsers.add_parser(Actions.GET_MANIFEST.value, parents=[base_parser]),
131+
)
132+
125133
# subparser for sa-conf-del
126134
parse_arguments_with(
127135
[spark_user_parser],
@@ -161,6 +169,11 @@ def main(args: Namespace, logger: Logger):
161169
) + PropertyFile.parse_conf_overrides(args.conf)
162170
registry.create(service_account)
163171

172+
elif args.action == Actions.GET_MANIFEST:
173+
service_account = build_service_account_from_args(args, registry)
174+
manifest = registry.create(service_account, dry_run=True)
175+
print(manifest)
176+
164177
elif args.action == Actions.DELETE:
165178
user_id = build_service_account_from_args(args, registry).id
166179
logger.info(user_id)

spark8t/kube_interface/base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ def create(
154154
resource_type: KubernetesResourceType,
155155
resource_name: str,
156156
namespace: str | None = None,
157+
dry_run: bool = False,
157158
**extra_args,
158159
) -> str:
159160
"""Create a K8s resource.

spark8t/kube_interface/kubectl.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@ def create(
296296
resource_type: str,
297297
resource_name: str,
298298
namespace: str | None = None,
299+
dry_run: bool = False,
299300
**extra_args,
300301
):
301302
"""Create a K8s resource.
@@ -310,9 +311,10 @@ def create(
310311
e.g. {"resource" : ["pods", "configmaps"]} which would translate to something like
311312
--resource=pods --resource=configmaps
312313
"""
314+
dry_run_arg = "--dry-run=client" if dry_run else ""
313315
if resource_type == KubernetesResourceType.NAMESPACE:
314-
self.exec(
315-
f"create {resource_type} {resource_name}",
316+
manifest = self.exec(
317+
f"create {resource_type} {resource_name} {dry_run_arg}",
316318
namespace=None,
317319
output="yaml",
318320
)
@@ -336,11 +338,12 @@ def create(
336338
dir=os.path.expanduser("~"),
337339
) as t:
338340
codecs.dump_all_yaml(res, t)
339-
self.exec(
340-
f"apply -f {t.name}",
341+
manifest = self.exec(
342+
f"apply -f {t.name} {dry_run_arg}",
341343
namespace=namespace,
342344
output="yaml",
343345
)
346+
return yaml.dump(manifest)
344347
else:
345348
# NOTE: removing 'username' to avoid interference with KUBECONFIG
346349
# ERROR: more than one authentication method found for admin; found [token basicAuth], only one is allowed
@@ -354,11 +357,12 @@ def create(
354357
for v in listify(values)
355358
]
356359
)
357-
self.exec(
358-
f"create {resource_type} {resource_name} {formatted_extra_args}",
360+
manifest = self.exec(
361+
f"create {resource_type} {resource_name} {formatted_extra_args} {dry_run_arg}",
359362
namespace=namespace or self.namespace,
360363
output="yaml",
361364
)
365+
return yaml.dump(manifest)
362366

363367
def delete(
364368
self, resource_type: str, resource_name: str, namespace: str | None = None

spark8t/kube_interface/lightkube.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ def create(
306306
resource_type: KubernetesResourceType,
307307
resource_name: str,
308308
namespace: str | None = None,
309+
dry_run: bool = False,
309310
**extra_args,
310311
):
311312
"""Create a K8s resource.
@@ -314,6 +315,7 @@ def create(
314315
resource_type: type of the resource to be created, e.g. service account, rolebindings, etc.
315316
resource_name: name of the resource to be created
316317
namespace: namespace where the resource is
318+
dry_run: whether to skip actual creation of resources
317319
extra_args: extra parameters that should be provided when creating the resource. Note that each parameter
318320
will be prepended with the -- in the cmd, e.g. {"role": "view"} will translate as
319321
--role=view in the command. List of parameter values against a parameter key are also accepted.
@@ -371,6 +373,7 @@ def create(
371373
"metadata": {
372374
"name": resource_name,
373375
"namespace": namespace,
376+
"labels": {GENERATED_BY_LABELNAME: SPARK8S_LABEL},
374377
},
375378
}
376379
)
@@ -382,7 +385,11 @@ def create(
382385
f"Label setting for resource name {resource_type} not supported yet."
383386
)
384387

385-
self.client.create(obj=res, name=resource_name, namespace=namespace)
388+
if not dry_run:
389+
self.client.create(obj=res, name=resource_name, namespace=namespace)
390+
if res is None:
391+
return ""
392+
return codecs.dump_all_yaml([cast(AnyResource, res)]) # mypy: ignore
386393

387394
def delete(
388395
self,

spark8t/literals.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Literals and constant module."""
22

33
MANAGED_BY_LABELNAME = "app.kubernetes.io/managed-by"
4+
GENERATED_BY_LABELNAME = "app.kubernetes.io/generated-by"
45
PRIMARY_LABELNAME = "app.kubernetes.io/spark8t-primary"
56
SPARK8S_LABEL = "spark8t"
67
HUB_LABEL = "integrator-hub-conf" # TODO revert this label to `integration-hub-conf``

spark8t/registry/k8s.py

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def set_primary(self, account_id: str, namespace: str | None = None) -> str | No
134134

135135
return account_id
136136

137-
def create(self, service_account: ServiceAccount) -> str:
137+
def create(self, service_account: ServiceAccount, dry_run=False) -> str:
138138
"""Create a new service account and return ids associated id.
139139
140140
Args:
@@ -150,7 +150,7 @@ def create(self, service_account: ServiceAccount) -> str:
150150
secretname = self._get_secret_name(username)
151151

152152
# Check if the resources to be created already exist in K8s cluster
153-
if self.kube_interface.exists(
153+
if not dry_run and self.kube_interface.exists(
154154
resource_type=KubernetesResourceType.SERVICEACCOUNT,
155155
resource_name=username,
156156
namespace=namespace,
@@ -160,7 +160,7 @@ def create(self, service_account: ServiceAccount) -> str:
160160
f"A {KubernetesResourceType.SERVICEACCOUNT} with name '{username}' already exists."
161161
)
162162

163-
if self.kube_interface.exists(
163+
if not dry_run and self.kube_interface.exists(
164164
resource_type=KubernetesResourceType.ROLE,
165165
resource_name=rolename,
166166
namespace=namespace,
@@ -170,7 +170,7 @@ def create(self, service_account: ServiceAccount) -> str:
170170
f"A {KubernetesResourceType.ROLE} with name '{rolename}' already exists."
171171
)
172172

173-
if self.kube_interface.exists(
173+
if not dry_run and self.kube_interface.exists(
174174
resource_type=KubernetesResourceType.ROLEBINDING,
175175
resource_name=rolebindingname,
176176
namespace=namespace,
@@ -180,57 +180,64 @@ def create(self, service_account: ServiceAccount) -> str:
180180
f"A {KubernetesResourceType.ROLEBINDING} with name '{rolebindingname}' already exists."
181181
)
182182

183-
self.kube_interface.create(
183+
sa_manifest = self.kube_interface.create(
184184
resource_type=KubernetesResourceType.SERVICEACCOUNT,
185185
resource_name=username,
186186
namespace=namespace,
187+
dry_run=dry_run,
187188
**{"username": username},
188189
)
189-
self.kube_interface.create(
190+
role_manifest = self.kube_interface.create(
190191
resource_type=KubernetesResourceType.ROLE,
191192
resource_name=rolename,
192193
namespace=namespace,
194+
dry_run=dry_run,
193195
**{"username": username},
194196
)
195-
self.kube_interface.create(
197+
role_binding_manifest = self.kube_interface.create(
196198
resource_type=KubernetesResourceType.ROLEBINDING,
197199
resource_name=rolebindingname,
198200
namespace=namespace,
199201
role=rolename,
202+
dry_run=dry_run,
200203
serviceaccount=account_id,
201204
username=username,
202205
)
203-
self.kube_interface.create(
206+
secret_manifest = self.kube_interface.create(
204207
resource_type=KubernetesResourceType.SECRET_GENERIC,
205208
resource_name=secretname,
206209
namespace=namespace,
210+
dry_run=dry_run,
207211
)
208-
self.kube_interface.set_label(
209-
resource_type=KubernetesResourceType.SERVICEACCOUNT,
210-
resource_name=username,
211-
label=f"{MANAGED_BY_LABELNAME}={SPARK8S_LABEL}",
212-
namespace=namespace,
213-
)
214-
self.kube_interface.set_label(
215-
resource_type=KubernetesResourceType.ROLE,
216-
resource_name=rolename,
217-
label=f"{MANAGED_BY_LABELNAME}={SPARK8S_LABEL}",
218-
namespace=namespace,
219-
)
220-
self.kube_interface.set_label(
221-
resource_type=KubernetesResourceType.ROLEBINDING,
222-
resource_name=rolebindingname,
223-
label=f"{MANAGED_BY_LABELNAME}={SPARK8S_LABEL}",
224-
namespace=namespace,
225-
)
226-
if service_account.primary is True:
227-
self.set_primary(account_id=account_id, namespace=namespace)
228-
229-
if len(service_account.extra_confs) > 0:
230-
self.set_configurations(
231-
account_id=account_id, configurations=configurations
212+
if not dry_run:
213+
self.kube_interface.set_label(
214+
resource_type=KubernetesResourceType.SERVICEACCOUNT,
215+
resource_name=username,
216+
label=f"{MANAGED_BY_LABELNAME}={SPARK8S_LABEL}",
217+
namespace=namespace,
232218
)
233-
return account_id
219+
self.kube_interface.set_label(
220+
resource_type=KubernetesResourceType.ROLE,
221+
resource_name=rolename,
222+
label=f"{MANAGED_BY_LABELNAME}={SPARK8S_LABEL}",
223+
namespace=namespace,
224+
)
225+
self.kube_interface.set_label(
226+
resource_type=KubernetesResourceType.ROLEBINDING,
227+
resource_name=rolebindingname,
228+
label=f"{MANAGED_BY_LABELNAME}={SPARK8S_LABEL}",
229+
namespace=namespace,
230+
)
231+
if service_account.primary is True:
232+
self.set_primary(account_id=account_id, namespace=namespace)
233+
234+
if len(service_account.extra_confs) > 0:
235+
self.set_configurations(
236+
account_id=account_id, configurations=configurations
237+
)
238+
239+
manifests = [sa_manifest, role_manifest, role_binding_manifest, secret_manifest]
240+
return "---\n".join(manifests)
234241

235242
def _create_account_secret(self, service_account: ServiceAccount):
236243
"""Create the secret that will contain the user configurations."""

spark8t/resources/templates/role_yaml.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1
22
kind: Role
33
metadata:
44
labels:
5-
app.kubernetes.io/managed-by: spark8t
5+
app.kubernetes.io/generated-by: spark8t
66
name: {{resourcename}}
77
namespace: {{namespace}}
88
rules:

spark8t/resources/templates/rolebinding_yaml.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: rbac.authorization.k8s.io/v1
22
kind: RoleBinding
33
metadata:
44
labels:
5-
app.kubernetes.io/managed-by: spark8t
5+
app.kubernetes.io/generated-by: spark8t
66
name: {{resourcename}}
77
namespace: {{namespace}}
88
roleRef:
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
apiVersion: v1
22
kind: ServiceAccount
33
metadata:
4-
labels:
5-
app.kubernetes.io/managed-by: spark8t
64
name: {{resourcename}}
75
namespace: {{namespace}}
6+
labels:
7+
app.kubernetes.io/generated-by: spark8t

0 commit comments

Comments
 (0)