dagster-k8s 0.27.2__tar.gz → 0.27.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of dagster-k8s might be problematic. Click here for more details.

Files changed (29) hide show
  1. {dagster_k8s-0.27.2/dagster_k8s.egg-info → dagster_k8s-0.27.4}/PKG-INFO +3 -3
  2. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/client.py +12 -0
  3. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/executor.py +48 -2
  4. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/job.py +26 -0
  5. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/pipes.py +2 -27
  6. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/utils.py +29 -1
  7. dagster_k8s-0.27.4/dagster_k8s/version.py +1 -0
  8. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4/dagster_k8s.egg-info}/PKG-INFO +3 -3
  9. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s.egg-info/requires.txt +1 -1
  10. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/setup.py +2 -2
  11. dagster_k8s-0.27.2/dagster_k8s/version.py +0 -1
  12. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/LICENSE +0 -0
  13. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/MANIFEST.in +0 -0
  14. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/README.md +0 -0
  15. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/__init__.py +0 -0
  16. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/component.py +0 -0
  17. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/container_context.py +0 -0
  18. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/kubernetes_version.py +0 -0
  19. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/launcher.py +0 -0
  20. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/models.py +0 -0
  21. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/ops/__init__.py +0 -0
  22. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/ops/k8s_job_op.py +0 -0
  23. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/py.typed +0 -0
  24. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s/test.py +0 -0
  25. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s.egg-info/SOURCES.txt +0 -0
  26. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s.egg-info/dependency_links.txt +0 -0
  27. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s.egg-info/not-zip-safe +0 -0
  28. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/dagster_k8s.egg-info/top_level.txt +0 -0
  29. {dagster_k8s-0.27.2 → dagster_k8s-0.27.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dagster-k8s
3
- Version: 0.27.2
3
+ Version: 0.27.4
4
4
  Summary: A Dagster integration for k8s
5
5
  Home-page: https://github.com/dagster-io/dagster/tree/master/python_modules/libraries/dagster-k8s
6
6
  Author: Dagster Labs
@@ -13,9 +13,9 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
16
- Requires-Python: >=3.9,<=3.13.3
16
+ Requires-Python: >=3.9,<3.14
17
17
  License-File: LICENSE
18
- Requires-Dist: dagster==1.11.2
18
+ Requires-Dist: dagster==1.11.4
19
19
  Requires-Dist: kubernetes<33
20
20
  Requires-Dist: google-auth!=2.23.1
21
21
  Dynamic: author
@@ -549,6 +549,18 @@ class DagsterKubernetesClient:
549
549
 
550
550
  ### Pod operations ###
551
551
 
552
+ def get_pod_by_name(self, pod_name: str, namespace: str):
553
+ """Get a pod by name.
554
+
555
+ Args:
556
+ pod_name (str): Name of the pod to get.
557
+ namespace (str): Namespace in which the pod is located.
558
+ """
559
+ check.str_param(pod_name, "pod_name")
560
+ check.str_param(namespace, "namespace")
561
+
562
+ return self.core_api.read_namespaced_pod(pod_name, namespace=namespace)
563
+
552
564
  def get_pods_in_job(self, job_name, namespace):
553
565
  """Get the pods launched by the job ``job_name``.
554
566
 
@@ -1,3 +1,4 @@
1
+ import os
1
2
  from collections.abc import Iterator
2
3
  from typing import Optional, cast
3
4
 
@@ -24,6 +25,7 @@ from dagster._core.executor.step_delegating import (
24
25
  StepHandler,
25
26
  StepHandlerContext,
26
27
  )
28
+ from dagster._utils.cached_method import cached_method
27
29
  from dagster._utils.merger import merge_dicts
28
30
 
29
31
  from dagster_k8s.client import DagsterKubernetesClient
@@ -31,6 +33,7 @@ from dagster_k8s.container_context import K8sContainerContext
31
33
  from dagster_k8s.job import (
32
34
  USER_DEFINED_K8S_JOB_CONFIG_SCHEMA,
33
35
  DagsterK8sJobConfig,
36
+ OwnerReference,
34
37
  UserDefinedDagsterK8sConfig,
35
38
  construct_dagster_k8s_job,
36
39
  get_k8s_job_name,
@@ -82,6 +85,14 @@ _K8S_EXECUTOR_CONFIG_SCHEMA = merge_dicts(
82
85
  default_value={},
83
86
  description="Per op k8s configuration overrides.",
84
87
  ),
88
+ "enable_owner_references": Field(
89
+ bool,
90
+ is_required=False,
91
+ default_value=False,
92
+ description="Whether to insert Kubernetes owner references on step jobs to their parent run pod."
93
+ " This ensures that step jobs and step pods are garbage collected when the run pod is deleted."
94
+ " For more information, see https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/",
95
+ ),
85
96
  },
86
97
  )
87
98
 
@@ -171,6 +182,9 @@ def k8s_job_executor(init_context: InitExecutorContext) -> Executor:
171
182
  load_incluster_config=load_incluster_config,
172
183
  kubeconfig_file=kubeconfig_file,
173
184
  per_step_k8s_config=exc_cfg.get("per_step_k8s_config", {}),
185
+ enable_owner_references=check.opt_bool_param(
186
+ exc_cfg.get("enable_owner_references"), "enable_owner_references", False
187
+ ),
174
188
  ),
175
189
  retries=RetryMode.from_config(exc_cfg["retries"]), # type: ignore
176
190
  max_concurrent=check.opt_int_elem(exc_cfg, "max_concurrent"),
@@ -191,7 +205,9 @@ class K8sStepHandler(StepHandler):
191
205
  load_incluster_config: bool,
192
206
  kubeconfig_file: Optional[str],
193
207
  k8s_client_batch_api=None,
208
+ k8s_client_core_api=None,
194
209
  per_step_k8s_config=None,
210
+ enable_owner_references=False,
195
211
  ):
196
212
  super().__init__()
197
213
 
@@ -199,7 +215,7 @@ class K8sStepHandler(StepHandler):
199
215
  self._executor_container_context = check.inst_param(
200
216
  container_context, "container_context", K8sContainerContext
201
217
  )
202
-
218
+ self._kubeconfig_file = None
203
219
  if load_incluster_config:
204
220
  check.invariant(
205
221
  kubeconfig_file is None,
@@ -209,13 +225,16 @@ class K8sStepHandler(StepHandler):
209
225
  else:
210
226
  check.opt_str_param(kubeconfig_file, "kubeconfig_file")
211
227
  kubernetes.config.load_kube_config(kubeconfig_file)
228
+ self._kubeconfig_file = kubeconfig_file
212
229
 
213
230
  self._api_client = DagsterKubernetesClient.production_client(
214
- batch_api_override=k8s_client_batch_api
231
+ batch_api_override=k8s_client_batch_api,
232
+ core_api_override=k8s_client_core_api,
215
233
  )
216
234
  self._per_step_k8s_config = check.opt_dict_param(
217
235
  per_step_k8s_config, "per_step_k8s_config", key_type=str, value_type=dict
218
236
  )
237
+ self._enable_owner_references = enable_owner_references
219
238
 
220
239
  def _get_step_key(self, step_handler_context: StepHandlerContext) -> str:
221
240
  step_keys_to_execute = cast(
@@ -264,6 +283,25 @@ class K8sStepHandler(StepHandler):
264
283
 
265
284
  return f"dagster-step-{name_key}"
266
285
 
286
+ @cached_method
287
+ def _detect_current_name_and_uid(
288
+ self,
289
+ ) -> Optional[tuple[str, str]]:
290
+ """Get the current pod's pod name and uid, if available."""
291
+ from dagster_k8s.utils import detect_current_namespace
292
+
293
+ hostname = os.getenv("HOSTNAME")
294
+ if not hostname:
295
+ return None
296
+
297
+ namespace = detect_current_namespace(self._kubeconfig_file)
298
+ if not namespace:
299
+ return None
300
+
301
+ pod = self._api_client.get_pod_by_name(pod_name=hostname, namespace=namespace)
302
+
303
+ return pod.metadata.name, pod.metadata.uid
304
+
267
305
  def launch_step(self, step_handler_context: StepHandlerContext) -> Iterator[DagsterEvent]:
268
306
  step_key = self._get_step_key(step_handler_context)
269
307
 
@@ -301,6 +339,13 @@ class K8sStepHandler(StepHandler):
301
339
  deployment_name_env_var = get_deployment_id_label(container_context.run_k8s_config)
302
340
  if deployment_name_env_var:
303
341
  labels["dagster/deployment-name"] = deployment_name_env_var
342
+
343
+ owner_references = []
344
+ if self._enable_owner_references:
345
+ my_pod = self._detect_current_name_and_uid()
346
+ if my_pod:
347
+ owner_references = [OwnerReference(kind="Pod", name=my_pod[0], uid=my_pod[1])]
348
+
304
349
  job = construct_dagster_k8s_job(
305
350
  job_config=job_config,
306
351
  args=args,
@@ -317,6 +362,7 @@ class K8sStepHandler(StepHandler):
317
362
  },
318
363
  {"name": "DAGSTER_RUN_STEP_KEY", "value": step_key},
319
364
  ],
365
+ owner_references=owner_references,
320
366
  )
321
367
 
322
368
  yield DagsterEvent.step_worker_starting(
@@ -85,6 +85,20 @@ DEFAULT_JOB_SPEC_CONFIG = {
85
85
  }
86
86
 
87
87
 
88
+ class OwnerReference(NamedTuple):
89
+ kind: str
90
+ name: str
91
+ uid: str
92
+
93
+ def to_dict(self) -> dict[str, Any]:
94
+ return {
95
+ "api_version": "batch/v1" if self.kind == "Job" else "v1",
96
+ "kind": self.kind,
97
+ "name": self.name,
98
+ "uid": self.uid,
99
+ }
100
+
101
+
88
102
  class UserDefinedDagsterK8sConfig(
89
103
  NamedTuple(
90
104
  "_UserDefinedDagsterK8sConfig",
@@ -754,6 +768,7 @@ def construct_dagster_k8s_job(
754
768
  component: Optional[str] = None,
755
769
  labels: Optional[Mapping[str, str]] = None,
756
770
  env_vars: Optional[Sequence[Mapping[str, Any]]] = None,
771
+ owner_references: Optional[Sequence[OwnerReference]] = None,
757
772
  ) -> kubernetes.client.V1Job:
758
773
  """Constructs a Kubernetes Job object.
759
774
 
@@ -929,6 +944,16 @@ def construct_dagster_k8s_job(
929
944
  user_defined_job_metadata = copy.deepcopy(dict(user_defined_k8s_config.job_metadata))
930
945
  user_defined_job_labels = user_defined_job_metadata.pop("labels", {})
931
946
 
947
+ owner_reference_dicts = (
948
+ [owner_reference.to_dict() for owner_reference in owner_references]
949
+ if owner_references
950
+ else []
951
+ )
952
+ if "owner_references" in user_defined_job_metadata:
953
+ user_defined_job_metadata["owner_references"] = (
954
+ owner_reference_dicts + user_defined_job_metadata["owner_references"]
955
+ )
956
+
932
957
  job = k8s_model_from_dict(
933
958
  kubernetes.client.V1Job,
934
959
  merge_dicts(
@@ -944,6 +969,7 @@ def construct_dagster_k8s_job(
944
969
  dagster_labels, user_defined_job_labels, job_config.labels
945
970
  ),
946
971
  },
972
+ {"owner_references": owner_reference_dicts} if owner_reference_dicts else {},
947
973
  ),
948
974
  "spec": job_spec_config,
949
975
  },
@@ -8,7 +8,6 @@ import time
8
8
  from collections.abc import Callable, Generator, Iterator, Mapping, Sequence
9
9
  from contextlib import contextmanager
10
10
  from datetime import datetime
11
- from pathlib import Path
12
11
  from typing import Any, Optional, Union
13
12
 
14
13
  import kubernetes
@@ -50,7 +49,7 @@ from dagster_k8s.client import (
50
49
  WaitForPodState,
51
50
  )
52
51
  from dagster_k8s.models import k8s_model_from_dict, k8s_snake_case_dict
53
- from dagster_k8s.utils import get_common_labels
52
+ from dagster_k8s.utils import detect_current_namespace, get_common_labels
54
53
 
55
54
  INIT_WAIT_TIMEOUT_FOR_READY = 1800.0 # 30mins
56
55
  INIT_WAIT_TIMEOUT_FOR_TERMINATE = 10.0 # 10s
@@ -64,7 +63,6 @@ def get_pod_name(run_id: str, op_name: str):
64
63
 
65
64
 
66
65
  DEFAULT_CONTAINER_NAME = "dagster-pipes-execution"
67
- _NAMESPACE_SECRET_PATH = Path("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
68
66
  _DEV_NULL_MESSAGE_WRITER = encode_env_var({"path": "/dev/null"})
69
67
 
70
68
  DEFAULT_CONSUME_POD_LOGS_RETRIES = 5
@@ -502,7 +500,7 @@ class PipesK8sClient(PipesClient, TreatAsResourceParam):
502
500
  context_injector=self.context_injector,
503
501
  message_reader=self.message_reader,
504
502
  ) as pipes_session:
505
- namespace = namespace or _detect_current_namespace(self.kubeconfig_file) or "default"
503
+ namespace = namespace or detect_current_namespace(self.kubeconfig_file) or "default"
506
504
  pod_name = get_pod_name(context.run_id, context.op.name)
507
505
  pod_body = build_pod_body(
508
506
  pod_name=pod_name,
@@ -593,29 +591,6 @@ class PipesK8sClient(PipesClient, TreatAsResourceParam):
593
591
  yield
594
592
 
595
593
 
596
- def _detect_current_namespace(
597
- kubeconfig_file: Optional[str], namespace_secret_path: Path = _NAMESPACE_SECRET_PATH
598
- ) -> Optional[str]:
599
- """Get the current in-cluster namespace when operating within the cluster.
600
-
601
- First attempt to read it from the `serviceaccount` secret or get it from the kubeconfig_file if it is possible.
602
- It will attempt to take from the active context if it exists and returns None if it does not exist.
603
- """
604
- if namespace_secret_path.exists():
605
- with namespace_secret_path.open() as f:
606
- # We only need to read the first line, this guards us against bad input.
607
- return f.read().strip()
608
-
609
- if not kubeconfig_file:
610
- return None
611
-
612
- try:
613
- _, active_context = kubernetes.config.list_kube_config_contexts(kubeconfig_file)
614
- return active_context["context"]["namespace"]
615
- except KeyError:
616
- return None
617
-
618
-
619
594
  def build_pod_body(
620
595
  pod_name: str,
621
596
  image: Optional[str],
@@ -1,6 +1,8 @@
1
1
  import re
2
- from typing import TYPE_CHECKING
2
+ from pathlib import Path
3
+ from typing import TYPE_CHECKING, Optional
3
4
 
5
+ import kubernetes
4
6
  from dagster import __version__ as dagster_version
5
7
 
6
8
  if TYPE_CHECKING:
@@ -32,3 +34,29 @@ def get_deployment_id_label(user_defined_k8s_config: "UserDefinedDagsterK8sConfi
32
34
  else None
33
35
  )
34
36
  return deployment_name_env_var["value"] if deployment_name_env_var else None
37
+
38
+
39
+ _NAMESPACE_SECRET_PATH = Path("/var/run/secrets/kubernetes.io/serviceaccount/namespace")
40
+
41
+
42
+ def detect_current_namespace(
43
+ kubeconfig_file: Optional[str], namespace_secret_path: Path = _NAMESPACE_SECRET_PATH
44
+ ) -> Optional[str]:
45
+ """Get the current in-cluster namespace when operating within the cluster.
46
+
47
+ First attempt to read it from the `serviceaccount` secret or get it from the kubeconfig_file if it is possible.
48
+ It will attempt to take from the active context if it exists and returns None if it does not exist.
49
+ """
50
+ if namespace_secret_path.exists():
51
+ with namespace_secret_path.open() as f:
52
+ # We only need to read the first line, this guards us against bad input.
53
+ return f.read().strip()
54
+
55
+ if not kubeconfig_file:
56
+ return None
57
+
58
+ try:
59
+ _, active_context = kubernetes.config.list_kube_config_contexts(kubeconfig_file)
60
+ return active_context["context"]["namespace"]
61
+ except KeyError:
62
+ return None
@@ -0,0 +1 @@
1
+ __version__ = "0.27.4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dagster-k8s
3
- Version: 0.27.2
3
+ Version: 0.27.4
4
4
  Summary: A Dagster integration for k8s
5
5
  Home-page: https://github.com/dagster-io/dagster/tree/master/python_modules/libraries/dagster-k8s
6
6
  Author: Dagster Labs
@@ -13,9 +13,9 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Classifier: Programming Language :: Python :: 3.13
14
14
  Classifier: License :: OSI Approved :: Apache Software License
15
15
  Classifier: Operating System :: OS Independent
16
- Requires-Python: >=3.9,<=3.13.3
16
+ Requires-Python: >=3.9,<3.14
17
17
  License-File: LICENSE
18
- Requires-Dist: dagster==1.11.2
18
+ Requires-Dist: dagster==1.11.4
19
19
  Requires-Dist: kubernetes<33
20
20
  Requires-Dist: google-auth!=2.23.1
21
21
  Dynamic: author
@@ -1,3 +1,3 @@
1
- dagster==1.11.2
1
+ dagster==1.11.4
2
2
  kubernetes<33
3
3
  google-auth!=2.23.1
@@ -41,9 +41,9 @@ setup(
41
41
  ],
42
42
  packages=find_packages(exclude=["dagster_k8s_tests*"]),
43
43
  include_package_data=True,
44
- python_requires=">=3.9,<=3.13.3",
44
+ python_requires=">=3.9,<3.14",
45
45
  install_requires=[
46
- "dagster==1.11.2",
46
+ "dagster==1.11.4",
47
47
  f"kubernetes<{KUBERNETES_VERSION_UPPER_BOUND}",
48
48
  # exclude a google-auth release that added an overly restrictive urllib3 pin that confuses dependency resolvers
49
49
  "google-auth!=2.23.1",
@@ -1 +0,0 @@
1
- __version__ = "0.27.2"
File without changes
File without changes
File without changes
File without changes