Skip to content

Commit

Permalink
Change wait to match any condition, add mode to enable previous funti…
Browse files Browse the repository at this point in the history
…onality (#293)
  • Loading branch information
jacobtomlinson authored Jan 22, 2024
1 parent 927962f commit 37693e1
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 14 deletions.
32 changes: 32 additions & 0 deletions docs/examples/creating_resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,38 @@ while not await pod.ready():
`````

## Create a Job and wait for it to either succeed or fail

Create a new {py:class}`Job <kr8s.objects.Job>` and wait for it to either succeed or fail with {py:func}`Job.wait() <kr8s.objects.Job.wait()>`.

`````{tab-set}
````{tab-item} Sync
:sync: sync
```python
from kr8s.objects import Job
job = Job(...)
job.create()
job.wait(["condition=Complete", "condition=Failed"])
```
````
````{tab-item} Async
:sync: async
```python
from kr8s.asyncio.objects import Jod
job = await Job(...)
await job.create()
await job.wait(["condition=Complete", "condition=Failed"])
```
````
`````

## Create a Secret

Create a {py:class}`Secret <kr8s.objects.Secret>` with several keys.
Expand Down
89 changes: 75 additions & 14 deletions kr8s/_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,17 @@
import re
import sys
import time
from typing import Any, AsyncGenerator, BinaryIO, Dict, List, Optional, Type, Union
from typing import (
Any,
AsyncGenerator,
BinaryIO,
Dict,
List,
Literal,
Optional,
Type,
Union,
)

import anyio
import httpx
Expand Down Expand Up @@ -361,8 +371,19 @@ async def list(cls, **kwargs) -> List[APIObjectType]:
api = await kr8s.asyncio.api()
return await api._get(kind=cls, **kwargs)

async def _test_conditions(self, conditions: list) -> bool:
"""Test if conditions are met."""
async def _test_conditions(
self, conditions: list, mode: Literal["any", "all"] = "any"
) -> bool:
"""Test if conditions are met.
Args:
conditions: A list of conditions to test.
mode: Match any condition with "any" or all conditions with "all". Defaults to "any".
Returns:
bool: True if any condition is met, False otherwise.
"""
results = []
for condition in conditions:
if condition.startswith("condition"):
condition = "=".join(condition.split("=")[1:])
Expand All @@ -377,24 +398,64 @@ async def _test_conditions(self, conditions: list) -> bool:
status_conditions = list_dict_unpack(
self.status.get("conditions", []), "type", "status"
)
if status_conditions.get(field, None) != value:
return False
results.append(status_conditions.get(field, None) == value)
elif condition == "delete":
if await self._exists():
return False
results.append(not await self._exists())
elif condition.startswith("jsonpath"):
matches = re.search(JSONPATH_CONDITION_EXPRESSION, condition)
expression = matches.group("expression")
condition = matches.group("condition")
[value] = jsonpath.findall(expression, self._raw)
if value != condition:
return False
results.append(value == condition)
else:
raise ValueError(f"Unknown condition type {condition}")
return True
if mode == "any":
return any(results)
elif mode == "all":
return all(results)
else:
raise ValueError(f"Unknown wait mode '{mode}', must be 'any' or 'all'")

async def wait(
self,
conditions: Union[List[str], str],
mode: Literal["any", "all"] = "any",
timeout: int = None,
):
"""Wait for conditions to be met.
Args:
conditions: A list of conditions to wait for.
mode: Match any condition with "any" or all conditions with "all". Defaults to "any".
timeout: Timeout in seconds.
async def wait(self, conditions: Union[List[str], str], timeout: int = None):
"""Wait for conditions to be met."""
Example:
Wait for a Pod to be ready.
>>> from kr8s.objects import Pod
>>> pod = Pod.get("my-pod")
>>> pod.wait("condition=Ready")
Wait for a Job to either succeed or fail.
>>> from kr8s.objects import Job
>>> job = Job.get("my-jod")
>>> job.wait(["condition=Complete", "condition=Failed"])
Wait for a Pod to be initialized and ready to start containers.
>>> from kr8s.objects import Pod
>>> pod = Pod.get("my-pod")
>>> pod.wait(["condition=Initialized", "condition=PodReadyToStartContainers"], mode="all")
Note:
As of the current Kubertenetes release when this function was written (1.29) kubectl doesn't support
multiple conditions. There is a PR to implement this but it hasn't been merged yet
https://github.com/kubernetes/kubernetes/pull/119205.
Given that ``for`` is a reserved word anyway we can't exactly match the kubectl API so
we use ``condition`` in combination with a ``mode`` instead.
"""
if isinstance(conditions, str):
conditions = [conditions]

Expand All @@ -404,10 +465,10 @@ async def wait(self, conditions: Union[List[str], str], timeout: int = None):
except NotFoundError:
if set(conditions) == {"delete"}:
return
if await self._test_conditions(conditions):
if await self._test_conditions(conditions, mode=mode):
return
async for _ in self._watch():
if await self._test_conditions(conditions):
if await self._test_conditions(conditions, mode=mode):
return

async def annotate(self, annotations: dict = None, **kwargs) -> None:
Expand Down
20 changes: 20 additions & 0 deletions kr8s/tests/test_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,26 @@ async def test_pod_wait_ready(example_pod_spec):
await pod.wait("delete")


async def test_pod_wait_multiple_conditions(example_pod_spec):
pod = await Pod(example_pod_spec)
await pod.create()
await pod.wait(conditions=["condition=Failed", "condition=Ready"])
with pytest.raises(TimeoutError):
await pod.wait(
conditions=["condition=Failed", "condition=Ready"], mode="all", timeout=0.1
)
await pod.wait(
conditions=[
"condition=Initialized",
"condition=ContainersReady",
],
mode="all",
)
with pytest.raises(ValueError):
await pod.wait(conditions=["condition=Failed", "condition=Ready"], mode="foo")
await pod.delete()


def test_pod_wait_ready_sync(example_pod_spec):
pod = SyncPod(example_pod_spec)
pod.create()
Expand Down

0 comments on commit 37693e1

Please sign in to comment.