mozbridge-cli 0.2.0__tar.gz → 0.3.0__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 (51) hide show
  1. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/PKG-INFO +1 -1
  2. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/docs/commands.md +190 -0
  3. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/pyproject.toml +1 -1
  4. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/scripts/release.sh +17 -0
  5. mozbridge_cli-0.3.0/src/mozbridge_cli/api.py +552 -0
  6. mozbridge_cli-0.3.0/src/mozbridge_cli/cmd_auth.py +152 -0
  7. mozbridge_cli-0.3.0/src/mozbridge_cli/cmd_deploy.py +307 -0
  8. mozbridge_cli-0.3.0/src/mozbridge_cli/cmd_env.py +115 -0
  9. mozbridge_cli-0.3.0/src/mozbridge_cli/cmd_projects.py +92 -0
  10. mozbridge_cli-0.2.0/src/mozbridge_cli/main.py → mozbridge_cli-0.3.0/src/mozbridge_cli/cmd_publish.py +8 -331
  11. mozbridge_cli-0.3.0/src/mozbridge_cli/cmd_registry.py +97 -0
  12. mozbridge_cli-0.3.0/src/mozbridge_cli/cmd_secrets.py +165 -0
  13. mozbridge_cli-0.3.0/src/mozbridge_cli/cmd_shared.py +31 -0
  14. mozbridge_cli-0.3.0/src/mozbridge_cli/main.py +49 -0
  15. mozbridge_cli-0.3.0/tests/test_deploy.py +268 -0
  16. mozbridge_cli-0.3.0/tests/test_env.py +242 -0
  17. mozbridge_cli-0.3.0/tests/test_logs.py +252 -0
  18. mozbridge_cli-0.3.0/tests/test_registry.py +196 -0
  19. mozbridge_cli-0.3.0/tests/test_secrets.py +294 -0
  20. mozbridge_cli-0.2.0/src/mozbridge_cli/api.py +0 -274
  21. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/.gitignore +0 -0
  22. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/README.md +0 -0
  23. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/__init__.py +0 -0
  24. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/__main__.py +0 -0
  25. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/auth.py +0 -0
  26. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/build.py +0 -0
  27. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/compose.py +0 -0
  28. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/config.py +0 -0
  29. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/link.py +0 -0
  30. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/local_build.py +0 -0
  31. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/runtime_secrets.py +0 -0
  32. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/src/mozbridge_cli/session.py +0 -0
  33. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/conftest.py +0 -0
  34. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_build.py +0 -0
  35. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_ci_token_auth.py +0 -0
  36. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_compose.py +0 -0
  37. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_diff.py +0 -0
  38. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_link.py +0 -0
  39. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_local_build.py +0 -0
  40. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_login.py +0 -0
  41. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_login_cli.py +0 -0
  42. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_logout.py +0 -0
  43. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_publish.py +0 -0
  44. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_publish_local.py +0 -0
  45. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_publish_multi_component.py +0 -0
  46. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_refresh.py +0 -0
  47. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_rollback.py +0 -0
  48. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_runtime_secrets.py +0 -0
  49. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_session_permissions.py +0 -0
  50. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_status.py +0 -0
  51. {mozbridge_cli-0.2.0 → mozbridge_cli-0.3.0}/tests/test_whoami.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: mozbridge-cli
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: Command-line client for the Mozbridge deployment platform: SSO login, build, publish, status, and rollback for any project hosted on Mozbridge.
5
5
  Project-URL: Homepage, https://mozbridge.com
6
6
  Project-URL: Documentation, https://github.com/jessin01/mozbridge/blob/main/cli/docs/commands.md
@@ -547,6 +547,196 @@ Rollback triggered (task_id=777).
547
547
 
548
548
  ---
549
549
 
550
+ ## `mozbridge deploy`
551
+
552
+ Deploys an already-built image tag to the linked Mozbridge project. A real,
553
+ consequential production action — same category as `mozbridge rollback`: it
554
+ mutates production the moment it's called, **not a dry-run**. Prompts for
555
+ confirmation unless `--yes` is given.
556
+
557
+ **Flags**:
558
+ | Flag | Meaning |
559
+ |---|---|
560
+ | `--frontend TAG` | Frontend image tag to deploy. |
561
+ | `--backend TAG` | Backend image tag to deploy. |
562
+ | `--env NAME` | Environment to deploy to. Default `prod`. |
563
+ | `--force` | Override a blocking preflight check. Never set automatically — a separate, explicit decision each time. |
564
+ | `--yes` | Skip the interactive confirmation prompt. |
565
+
566
+ **Requires**: link + login, same as `publish`/`status`/`rollback`. At least
567
+ one of `--frontend`/`--backend` must be given — deploying neither makes no
568
+ sense.
569
+
570
+ **What it does**: prints what it's about to do (project, which service(s),
571
+ which tag(s), which environment), then either aborts on a declined prompt or
572
+ calls `POST /api/v1/projects/{project_id}/deploy` with
573
+ `{frontend_version, backend_version, services, environment, force}`.
574
+ **`services` is built from which of `--frontend`/`--backend` you actually
575
+ gave a tag** — not the API's own default of deploying both — so the CLI only
576
+ ever asks the platform to deploy what you specified.
577
+
578
+ The backend runs a preflight check before enqueuing the deploy. A 409
579
+ response means it found a real, known-to-fail condition and refused —
580
+ each blocking check is printed with its title, detail, and suggested
581
+ action, followed by a hint to re-run with `--force` if you want to override
582
+ it. **The CLI never retries with `--force` automatically** — that is always
583
+ a separate, explicit re-invocation.
584
+
585
+ **Example (`--yes`, single service)**:
586
+ ```
587
+ $ mozbridge deploy --backend v1.9.319 --yes
588
+ This will deploy acme/web-app (prod):
589
+ backend -> v1.9.319
590
+ Deploy triggered (task_id=888).
591
+ Run `mozbridge logs` to watch it.
592
+ ```
593
+
594
+ **Example (preflight blocked)**:
595
+ ```
596
+ $ mozbridge deploy --backend v1.9.319 --yes
597
+ This will deploy acme/web-app (prod):
598
+ backend -> v1.9.319
599
+ Preflight failed — this deploy is known to fail.
600
+ - No registry credential: GHCR_TOKEN is not configured for this project.
601
+ action: Set a registry credential in Project Settings.
602
+ Pass force=true to deploy anyway.
603
+ Re-run with --force to override — this is not done automatically.
604
+ ```
605
+
606
+ **Failure modes**:
607
+ - Neither `--frontend` nor `--backend` given: clear error, exit 1, no API
608
+ call is made.
609
+ - Not linked / not logged in: identical messages to `publish` above, exit 1.
610
+ - Confirmation prompt declined (`--yes` not passed, answer is not `y`):
611
+ Typer's own `Abort` handling — the command exits non-zero without calling
612
+ the API.
613
+ - Preflight-blocked (409, structured body): every blocking check printed
614
+ clearly plus a `--force` hint, exit 1 — no automatic retry.
615
+ - Any other API error: the raw `ApiError` message, exit 1.
616
+
617
+ ---
618
+
619
+ ## `mozbridge logs`
620
+
621
+ Shows **BUILD/DEPLOY OPERATION logs** for the linked project — the log
622
+ output of a Mozbridge build or deploy task (what `publish`, `deploy`, and
623
+ `rollback` trigger). **This is not your running application's
624
+ runtime/request logs** — the platform does not expose those via any API
625
+ today, and this command is not `docker logs` for your live app.
626
+
627
+ **Flags**:
628
+ | Flag | Meaning |
629
+ |---|---|
630
+ | `TASK_ID` (argument, optional) | Task id of a build/deploy operation. Omit to use the most recent one for this project. |
631
+ | `--follow` / `-f` | Stream new log output as it arrives; exits when the run finishes. |
632
+
633
+ **Requires**: link + login, same as `status`/`diff`/`rollback`.
634
+
635
+ **What it does**:
636
+ - If `TASK_ID` is omitted: `GET /api/v1/projects/{project_id}/operations?limit=1`
637
+ (newest-first, same ordering `status`/`diff` already rely on for
638
+ deployments) to resolve the most recent operation's `task_id`.
639
+ - Without `--follow`: one `GET /api/v1/projects/operations/{task_id}` call,
640
+ printing whatever is in `result.logs` right now — a single snapshot, no
641
+ long-lived connection.
642
+ - With `--follow`: opens `GET /api/v1/projects/operations/{task_id}/stream`
643
+ (SSE) and prints each `log` event's content as it's parsed. The server
644
+ closes the stream itself once the operation reaches a terminal state
645
+ (success/failed/error) — the CLI exits cleanly at that point. A dropped
646
+ connection surfaces a clear error instead of hanging.
647
+
648
+ **Example (snapshot)**:
649
+ ```
650
+ $ mozbridge logs task-123
651
+ Step 1/5 : FROM python:3.12
652
+ Step 2/5 : COPY . /app
653
+ ```
654
+
655
+ **Example (follow)**:
656
+ ```
657
+ $ mozbridge logs task-123 --follow
658
+ Step 1/5 : FROM python:3.12
659
+ Step 2/5 : COPY . /app
660
+ Successfully built abc123
661
+ ```
662
+
663
+ **Failure modes**:
664
+ - Not linked / not logged in: identical messages to `publish` above, exit 1.
665
+ - No `TASK_ID` given and the project has no operations yet: `No operations
666
+ found for this project yet.`, exit 1.
667
+ - The operation lookup or stream request fails: the raw `ApiError` message
668
+ (snapshot mode) or `Log stream connection dropped: <error>` (`--follow`),
669
+ exit 1.
670
+
671
+ ---
672
+
673
+ ## `mozbridge env list` / `mozbridge env set` / `mozbridge env rm`
674
+
675
+ Manages the linked project's environment variables
676
+ (`GET`/`POST`/`DELETE /api/v1/projects/{project_id}/env-vars[...]`).
677
+ `EnvVar` is a plain `{key, value}` pair — distinct from the project's
678
+ separate Vault-backed secrets blocks — and nothing in the platform's own
679
+ dashboard UI masks these values, so `env list` prints them in plain text.
680
+
681
+ **Requires**: link + login, same as `status`/`diff`/`rollback`.
682
+
683
+ ### `mozbridge env list`
684
+
685
+ **Flags**: none.
686
+
687
+ **What it does**: `GET /api/v1/projects/{project_id}/env-vars`, prints every
688
+ key/value pair sorted by key. Prints `No environment variables set.` if
689
+ there are none.
690
+
691
+ ```
692
+ $ mozbridge env list
693
+ DATABASE_URL postgres://user:pass@host/db
694
+ DEBUG false
695
+ ```
696
+
697
+ ### `mozbridge env set KEY VALUE`
698
+
699
+ **What it does**: `POST /api/v1/projects/{project_id}/env-vars` with body
700
+ `{"key": KEY, "value": VALUE}`. **This is an upsert, not create-only** —
701
+ read directly off `backend/app/security.py:store_project_env_var`: the
702
+ handler loads the existing key->value dict out of Vault, does
703
+ `existing[key] = value`, and writes the whole dict back. Setting an
704
+ already-existing `KEY` silently overwrites it; there is no 409/400 and no
705
+ separate create-vs-update codepath.
706
+
707
+ The confirmation deliberately prints only the key, never the value, so a
708
+ secret-shaped `VALUE` isn't echoed a second time into your terminal beyond
709
+ the command invocation itself:
710
+
711
+ ```
712
+ $ mozbridge env set DATABASE_URL postgres://user:pass@host/db
713
+ Set DATABASE_URL.
714
+ ```
715
+
716
+ ### `mozbridge env rm KEY`
717
+
718
+ **What it does**: `DELETE /api/v1/projects/{project_id}/env-vars/{KEY}`.
719
+ **This is idempotent, not a strict "must exist" delete** — read directly
720
+ off `backend/app/security.py:delete_project_env_var`: it deletes `KEY`
721
+ `if key in existing`, and otherwise falls straight through to `return
722
+ True` — either way the route responds 200. Removing a `KEY` that was never
723
+ set still reports success; there is no 404 for a missing key, and this
724
+ command does not invent one.
725
+
726
+ ```
727
+ $ mozbridge env rm DATABASE_URL
728
+ Removed DATABASE_URL.
729
+ ```
730
+
731
+ **Failure modes (all three)**:
732
+ - Not linked / not logged in: identical messages to `publish` above, exit 1.
733
+ - Any other API error (e.g. a 500 from a Vault write/delete failure): the
734
+ raw `ApiError` message (`Could not list env vars: <detail>` / `Could not
735
+ set the env var: <detail>` / `Could not remove the env var: <detail>`),
736
+ exit 1.
737
+
738
+ ---
739
+
550
740
  ## A note on authorization for `publish` / `rollback`
551
741
 
552
742
  Both commands work today because a plain human Logto session token passes
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mozbridge-cli"
7
- version = "0.2.0"
7
+ version = "0.3.0"
8
8
  description = "Command-line client for the Mozbridge deployment platform: SSO login, build, publish, status, and rollback for any project hosted on Mozbridge."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -10,8 +10,25 @@
10
10
  set -euo pipefail
11
11
 
12
12
  ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
13
+ REPO_ROOT="$(cd "$ROOT/.." && pwd)"
13
14
  PYPROJECT="$ROOT/pyproject.toml"
14
15
 
16
+ # Hard first check: `git fetch` updates the remote-tracking ref, not the
17
+ # working tree — only merge/rebase/pull do that. mozbridge-cli 0.2.0 was
18
+ # published missing 3 commands because this build ran between a `git
19
+ # fetch` and the merge that would have actually updated the checkout.
20
+ # Refuse to build from a checkout that's behind origin/main.
21
+ if command -v git >/dev/null 2>&1 && git -C "$REPO_ROOT" rev-parse --git-dir >/dev/null 2>&1; then
22
+ git -C "$REPO_ROOT" fetch origin main --quiet 2>/dev/null || true
23
+ BEHIND="$(git -C "$REPO_ROOT" rev-list --count HEAD..origin/main 2>/dev/null || echo 0)"
24
+ if [ "${BEHIND:-0}" != "0" ]; then
25
+ echo "✗ Local main is ${BEHIND} commit(s) behind origin/main." >&2
26
+ echo " Run: git -C \"$REPO_ROOT\" merge --ff-only origin/main" >&2
27
+ echo " then re-run this script. Refusing to build a stale release." >&2
28
+ exit 1
29
+ fi
30
+ fi
31
+
15
32
  usage() {
16
33
  echo "Usage: $0 [--patch|--minor|--major] [--dry-run]" >&2
17
34
  echo " Default bump: patch" >&2