urun-cli 0.7.6__tar.gz → 0.7.8__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 (37) hide show
  1. {urun_cli-0.7.6 → urun_cli-0.7.8}/CHANGELOG.md +25 -0
  2. {urun_cli-0.7.6 → urun_cli-0.7.8}/PKG-INFO +33 -1
  3. {urun_cli-0.7.6 → urun_cli-0.7.8}/README.md +32 -0
  4. {urun_cli-0.7.6 → urun_cli-0.7.8}/pyproject.toml +2 -1
  5. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/api.py +12 -0
  6. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/catalog.py +4 -4
  7. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/cli.py +288 -50
  8. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/deps.py +203 -22
  9. urun_cli-0.7.8/src/urun/hub_builds.py +328 -0
  10. urun_cli-0.7.8/src/urun/kubectl_plugin.py +688 -0
  11. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/rust.py +1 -1
  12. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/serve.py +73 -1
  13. {urun_cli-0.7.6 → urun_cli-0.7.8}/.gitignore +0 -0
  14. {urun_cli-0.7.6 → urun_cli-0.7.8}/LICENSE +0 -0
  15. {urun_cli-0.7.6 → urun_cli-0.7.8}/SECURITY.md +0 -0
  16. {urun_cli-0.7.6 → urun_cli-0.7.8}/hatch_build.py +0 -0
  17. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/__init__.py +0 -0
  18. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/_bin/README.md +0 -0
  19. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/buildlogs.py +0 -0
  20. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/client_tokens.py +0 -0
  21. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/config.py +0 -0
  22. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/dev.py +0 -0
  23. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/devconsole.py +0 -0
  24. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/devstream.py +0 -0
  25. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/discovery.py +0 -0
  26. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/errors.py +0 -0
  27. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/fleet.py +0 -0
  28. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/frontend_launch.py +0 -0
  29. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/launch_auth.py +0 -0
  30. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/manifest.py +0 -0
  31. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/nodes.py +0 -0
  32. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/openai_proxy_bin.py +0 -0
  33. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/repo_deps.py +0 -0
  34. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/reserve.py +0 -0
  35. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/retry.py +0 -0
  36. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/runtime_health.py +0 -0
  37. {urun_cli-0.7.6 → urun_cli-0.7.8}/src/urun/scratch.py +0 -0
@@ -1,5 +1,30 @@
1
1
  ## Unreleased
2
2
 
3
+ - `urun release promote <release_id> [--environment main]` — make an existing
4
+ release current again (clean rollback, no marker commits). Server side it
5
+ takes the same path as a same-content `urun deploy`: the control plane
6
+ re-registers the release and republishes the release pointer.
7
+ - Deploy deps collector: `@app.function(deps=...)` may now reference a
8
+ module-level `Dependencies(...)` constant imported from a file shipped with
9
+ the app (`from sense_deploy import SENSE_DEPS`) — resolved statically by the
10
+ same side-effect-free evaluator, including re-export chains and the
11
+ `import mod` + `mod.SYM` attribute form. An attached `deps=` that cannot be
12
+ statically resolved now FAILS THE DEPLOY LOUDLY naming `file:line`, the
13
+ import it came through, and the fix — previously it silently shipped an
14
+ EMPTY manifest (no python pins, no files) and the app's boot self-test died
15
+ with `No module named ...` after fleet rollout (sense, 2026-10-27/28).
16
+ - `kubectl urun` — a kubectl plugin (`kubectl-urun`, installed alongside
17
+ `urun`) with four verbs over the `gpu.urun.sh/v1alpha1` GPUWorker surface:
18
+ `logs` (the `logs.urun.sh/v1` log subresource, with `-f` follow
19
+ passthrough, `--since`, `--tail`, `-s <service>`), `cordon`/`uncordon`
20
+ (`spec.unschedulable`), `drain` (cordon, then wait for the `Draining`
21
+ condition to reach `False`/`Drained`, nonzero on `--timeout`), and
22
+ `describe` (node block, services, conditions, events, liveness lease,
23
+ claim summary). Claim-owned workers are cordoned through their OWNING
24
+ GPUWorkerClaim (resolved via `spec.claimRef`) because the claim
25
+ reconciler re-renders the worker's spec; the patched object is printed.
26
+ All verbs ride a bounded `kubectl proxy --port=0` child, so
27
+ `--kubeconfig`/`--context`/`-n` resolve exactly as kubectl resolves them.
3
28
  - Security (ENG-570 / pentest urun-cli-01, mirrored in urun-python
4
29
  `urun/core/store/repo.py`): `store.repo(...)` specs parsed from untrusted
5
30
  app source are validated before any git call. The `@<ref>` pin must be a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: urun-cli
3
- Version: 0.7.6
3
+ Version: 0.7.8
4
4
  Summary: End-user CLI for deploying apps to urun
5
5
  Project-URL: Homepage, https://urun.sh
6
6
  Project-URL: Repository, https://github.com/urun-sh/urun-cli
@@ -534,6 +534,33 @@ What it does:
534
534
  sooner. `--no-logs` skips streaming the occupant's log after creation
535
535
  (Ctrl-C also detaches without tearing the occupant down).
536
536
 
537
+ ## Operator: GPUWorker nodes (`kubectl urun`)
538
+
539
+ Installing `urun-cli` also installs the `kubectl-urun` binary; with it on
540
+ `PATH`, kubectl dispatches `kubectl urun` to it as a standard kubectl plugin.
541
+ These verbs are the operator surface over the `gpu.urun.sh/v1alpha1`
542
+ GPUWorker custom resources (the cluster must have the urun controller
543
+ installed); they resolve kubeconfig/context through kubectl itself, so
544
+ `--kubeconfig`, `--context`, and `-n` behave like any other kubectl command:
545
+
546
+ ```bash
547
+ kubectl urun logs <worker> -n urun # one-shot aggregated log
548
+ kubectl urun logs <worker> -n urun -s vineyard --tail 200 --since 30m
549
+ kubectl urun logs <worker> -n urun -f # follow; Ctrl-C detaches
550
+ kubectl urun cordon <worker> -n urun # mark unschedulable
551
+ kubectl urun uncordon <worker> -n urun
552
+ kubectl urun drain <worker> -n urun --timeout 300 # cordon + wait for Drained
553
+ kubectl urun describe <worker> -n urun # node/services/conditions/events/lease
554
+ ```
555
+
556
+ `cordon` sets `spec.unschedulable`. Claim-owned workers (workers with a
557
+ `spec.claimRef`) are re-rendered from their claim by the claim reconciler, so
558
+ `cordon`/`uncordon`/`drain` patch the OWNING CLAIM's `spec.unschedulable`
559
+ instead and print which object they patched; workers without a claim are
560
+ patched directly. `drain` cordons and then watches the worker's `Draining`
561
+ condition until it flips to `False`/`Drained` (no sessions left), exiting
562
+ nonzero on `--timeout` (default 300s).
563
+
537
564
  ## What gets deployed
538
565
 
539
566
  `urun deploy` creates a source manifest from your Python entrypoint:
@@ -579,6 +606,11 @@ def superres(): ...
579
606
  - `files=` is **purely additive**. With no `files=`, collection is exactly what
580
607
  it was before.
581
608
  - Entries are **individual files**, not directories or globs. List each file.
609
+ - `deps=` may be a module-level `Dependencies(...)` constant imported from a
610
+ file shipped with the app (`from deps_mod import APP_DEPS`) — the deploy
611
+ collector resolves it statically. Anything it cannot resolve (an import
612
+ outside the app directory, a non-literal) **fails the deploy loudly**, naming
613
+ `file:line` and the fix — it never ships an empty manifest.
582
614
  - A declared path that does not exist, escapes the app directory (`..`, a
583
615
  symlink pointing outside), or is absolute **fails the deploy immediately**,
584
616
  naming the path and the directory it was looked up under. A declared file is
@@ -509,6 +509,33 @@ What it does:
509
509
  sooner. `--no-logs` skips streaming the occupant's log after creation
510
510
  (Ctrl-C also detaches without tearing the occupant down).
511
511
 
512
+ ## Operator: GPUWorker nodes (`kubectl urun`)
513
+
514
+ Installing `urun-cli` also installs the `kubectl-urun` binary; with it on
515
+ `PATH`, kubectl dispatches `kubectl urun` to it as a standard kubectl plugin.
516
+ These verbs are the operator surface over the `gpu.urun.sh/v1alpha1`
517
+ GPUWorker custom resources (the cluster must have the urun controller
518
+ installed); they resolve kubeconfig/context through kubectl itself, so
519
+ `--kubeconfig`, `--context`, and `-n` behave like any other kubectl command:
520
+
521
+ ```bash
522
+ kubectl urun logs <worker> -n urun # one-shot aggregated log
523
+ kubectl urun logs <worker> -n urun -s vineyard --tail 200 --since 30m
524
+ kubectl urun logs <worker> -n urun -f # follow; Ctrl-C detaches
525
+ kubectl urun cordon <worker> -n urun # mark unschedulable
526
+ kubectl urun uncordon <worker> -n urun
527
+ kubectl urun drain <worker> -n urun --timeout 300 # cordon + wait for Drained
528
+ kubectl urun describe <worker> -n urun # node/services/conditions/events/lease
529
+ ```
530
+
531
+ `cordon` sets `spec.unschedulable`. Claim-owned workers (workers with a
532
+ `spec.claimRef`) are re-rendered from their claim by the claim reconciler, so
533
+ `cordon`/`uncordon`/`drain` patch the OWNING CLAIM's `spec.unschedulable`
534
+ instead and print which object they patched; workers without a claim are
535
+ patched directly. `drain` cordons and then watches the worker's `Draining`
536
+ condition until it flips to `False`/`Drained` (no sessions left), exiting
537
+ nonzero on `--timeout` (default 300s).
538
+
512
539
  ## What gets deployed
513
540
 
514
541
  `urun deploy` creates a source manifest from your Python entrypoint:
@@ -554,6 +581,11 @@ def superres(): ...
554
581
  - `files=` is **purely additive**. With no `files=`, collection is exactly what
555
582
  it was before.
556
583
  - Entries are **individual files**, not directories or globs. List each file.
584
+ - `deps=` may be a module-level `Dependencies(...)` constant imported from a
585
+ file shipped with the app (`from deps_mod import APP_DEPS`) — the deploy
586
+ collector resolves it statically. Anything it cannot resolve (an import
587
+ outside the app directory, a non-literal) **fails the deploy loudly**, naming
588
+ `file:line` and the fix — it never ships an empty manifest.
557
589
  - A declared path that does not exist, escapes the app directory (`..`, a
558
590
  symlink pointing outside), or is absolute **fails the deploy immediately**,
559
591
  naming the path and the directory it was looked up under. A declared file is
@@ -20,7 +20,7 @@ name = "urun-cli"
20
20
  # so every merge remains on the already-published version until this value is
21
21
  # advanced. Wiring semantic-release into CI remains separate work; this patch
22
22
  # bump is the minimal release unblock (post-v0.6.3 fixes + the Rust feature minor).
23
- version = "0.7.6"
23
+ version = "0.7.8"
24
24
  description = "End-user CLI for deploying apps to urun"
25
25
  readme = "README.md"
26
26
  requires-python = ">=3.11"
@@ -48,6 +48,7 @@ Issues = "https://github.com/urun-sh/urun-cli/issues"
48
48
  [project.scripts]
49
49
  urun = "urun.cli:main"
50
50
  urun-cli = "urun.cli:main"
51
+ kubectl-urun = "urun.kubectl_plugin:main"
51
52
 
52
53
  [project.optional-dependencies]
53
54
  # Rust app discovery (urun/rust.py) parses with the pinned native
@@ -461,6 +461,18 @@ class ApiClient:
461
461
  def get_release(self, release_id: str) -> dict[str, Any]:
462
462
  return self._get(f"/cli/releases/{urllib.parse.quote(release_id, safe='')}")
463
463
 
464
+ def promote_release(self, release_id: str, environment: str) -> dict[str, Any]:
465
+ """Make an EXISTING release current for ``environment`` (rollback).
466
+
467
+ ``POST /cli/releases/<id>/promote`` re-fires the release's deploy event —
468
+ the same path a same-content ``urun deploy`` takes — so the control plane
469
+ re-registers it and republishes the release pointer.
470
+ """
471
+ return self._post(
472
+ f"/cli/releases/{urllib.parse.quote(release_id, safe='')}/promote",
473
+ {"environment": environment},
474
+ )
475
+
464
476
  def query_logs(self, params: dict[str, Any] | None = None) -> dict[str, Any]:
465
477
  return self._get("/cli/logs", params)
466
478
 
@@ -120,7 +120,7 @@ ENGINE_DEPS: dict[str, list[str]] = {
120
120
  "nvidia-cuda-crt==13.0.88",
121
121
  "nvidia-cuda-cccl==13.0.85",
122
122
  "nvidia-nvvm==13.0.88",
123
- "nvidia-cuda-runtime==13.0.96",
123
+ "nvidia-cuda-runtime==13.0.*",
124
124
  "numpy<2",
125
125
  "pillow>=10.0.0",
126
126
  "orjson",
@@ -162,7 +162,7 @@ ENGINE_DEPS: dict[str, list[str]] = {
162
162
  "nvidia-cuda-crt==13.0.88",
163
163
  "nvidia-cuda-cccl==13.0.85",
164
164
  "nvidia-nvvm==13.0.88",
165
- "nvidia-cuda-runtime==13.0.96",
165
+ "nvidia-cuda-runtime==13.0.*",
166
166
  "numpy<2",
167
167
  "pillow>=10.0.0",
168
168
  "orjson",
@@ -368,7 +368,7 @@ ENGINE_DEPS: dict[str, list[str]] = {
368
368
  "nvidia-cuda-crt==13.0.88",
369
369
  "nvidia-cuda-cccl==13.0.85",
370
370
  "nvidia-nvvm==13.0.88",
371
- "nvidia-cuda-runtime==13.0.96",
371
+ "nvidia-cuda-runtime==13.0.*",
372
372
  "numpy<2",
373
373
  "pillow>=10.0.0",
374
374
  "orjson",
@@ -446,7 +446,7 @@ ENGINE_DEPS: dict[str, list[str]] = {
446
446
  "nvidia-cuda-crt==13.0.88",
447
447
  "nvidia-cuda-cccl==13.0.85",
448
448
  "nvidia-nvvm==13.0.88",
449
- "nvidia-cuda-runtime==13.0.96",
449
+ "nvidia-cuda-runtime==13.0.*",
450
450
  "ninja",
451
451
  "pip",
452
452
  "nvidia-cutlass-dsl>=4.6.0.dev0,<5",