| 19 | |
| 20 | |
| 21 | def wait( |
| 22 | condition: str, |
| 23 | resource: str, |
| 24 | timeout_secs: int = 300, |
| 25 | context: str = DEFAULT_K8S_CONTEXT_NAME, |
| 26 | *, |
| 27 | label: str | None = None, |
| 28 | namespace: str | None = None, |
| 29 | server: str | None = None, |
| 30 | ) -> None: |
| 31 | # Use a shorter per-invocation timeout so the outer retry loop can |
| 32 | # handle transient kubectl errors (e.g. slow image pulls, apiserver blips). |
| 33 | per_attempt_secs = min(timeout_secs, 60) |
| 34 | cmd = [ |
| 35 | "kubectl", |
| 36 | "wait", |
| 37 | "--for", |
| 38 | condition, |
| 39 | resource, |
| 40 | "--timeout", |
| 41 | f"{per_attempt_secs}s", |
| 42 | "--context", |
| 43 | context, |
| 44 | ] |
| 45 | |
| 46 | if label is not None: |
| 47 | cmd.extend(["--selector", label]) |
| 48 | |
| 49 | if namespace is not None: |
| 50 | cmd.extend(["--namespace", namespace]) |
| 51 | |
| 52 | if server is not None: |
| 53 | cmd.extend(["--server", server]) |
| 54 | |
| 55 | ui.progress(f'waiting for {" ".join(cmd)} ... ') |
| 56 | |
| 57 | error = None |
| 58 | for remaining in ui.timeout_loop(timeout_secs, tick=0.1): |
| 59 | try: |
| 60 | output = subprocess.check_output(cmd, stderr=subprocess.STDOUT).decode( |
| 61 | "ascii" |
| 62 | ) |
| 63 | # output is: |
| 64 | # - an empty string when a 'delete' condition is satisfied |
| 65 | # - 'condition met' for all other conditions |
| 66 | if len(output) == 0 or "condition met" in output: |
| 67 | ui.progress("success!", finish=True) |
| 68 | return |
| 69 | except subprocess.CalledProcessError as e: |
| 70 | # use a less verbose output than log_subprocess_error here |
| 71 | LOGGER.info(f"{e} {e.output.decode('ascii')}") |
| 72 | error = e |
| 73 | |
| 74 | ui.progress(finish=True) |
| 75 | print_pods() |
| 76 | raise UIError(f"kubectl wait never returned 'condition met': {error}") |