doover-cli 0.3.3__tar.gz → 0.4.2__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.
- {doover_cli-0.3.3 → doover_cli-0.4.2}/PKG-INFO +1 -1
- {doover_cli-0.3.3 → doover_cli-0.4.2}/pyproject.toml +1 -1
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/api/session.py +27 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/apps/apps.py +89 -8
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/api.py +24 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/apps.py +14 -10
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_apps.py +8 -9
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_auth_unit.py +46 -0
- doover_cli-0.4.2/uv.lock +2034 -0
- doover_cli-0.3.3/uv.lock +0 -2000
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.github/CONTRIBUTING.md +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.github/ISSUE_TEMPLATE/bug-report.yaml +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.github/workflows/lint-test.yml +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.github/workflows/release-brew.yml +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.github/workflows/release-pypi.yml +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.github/workflows/release.yml +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.gitignore +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/.pre-commit-config.yaml +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/AGENT.md +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/LICENSE +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/README.md +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/debian/changelog +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/debian/control +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/debian/rules +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/docs/user_stories.md +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/__init__.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/agent.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/api/__init__.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/api/auth.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/api/errors.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/apps/app_install.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/apps/device.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/apps/device_type.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/apps/tunnel.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/channel.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/colours.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/config_schema.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/dda_logs.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/doover_config.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/grpc.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/login.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/renderer/__init__.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/renderer/_base.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/renderer/_basic.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/renderer/_default.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/renderer/_json.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/report.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/simulator.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/ui_schema.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/user.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/__init__.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/context.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/crud/__init__.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/crud/commands.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/crud/lookup.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/crud/prompting.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/crud/schema.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/crud/values.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/errors.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/formatters.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/misc.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/parsers.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/prompt.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/sentry.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/shell_commands.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/src/doover_cli/utils/state.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/__init__.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/conftest.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_agent.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_app_install.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_auth_integration.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_basic.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_crud_commands.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_crud_lookup.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_crud_prompting.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_crud_schema.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_crud_values.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_default_renderer.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_device.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_device_cli_integration.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_device_type.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_device_type_cli_integration.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_prompt.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_sentry.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_user.py +0 -0
- {doover_cli-0.3.3 → doover_cli-0.4.2}/tests/test_utils_apps.py +0 -0
|
@@ -45,6 +45,33 @@ class DooverCLISession:
|
|
|
45
45
|
auth=auth,
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
+
@classmethod
|
|
49
|
+
def from_trusted_publisher(
|
|
50
|
+
cls,
|
|
51
|
+
*,
|
|
52
|
+
provider: str = "GH",
|
|
53
|
+
oidc_token: str | None = None,
|
|
54
|
+
audience: str | None = None,
|
|
55
|
+
control_base_url: str | None = None,
|
|
56
|
+
timeout: float = 60.0,
|
|
57
|
+
) -> "DooverCLISession":
|
|
58
|
+
"""Build a session that publishes via the trusted-publisher OIDC flow —
|
|
59
|
+
no stored Doover secret. Inside GitHub Actions (provider 'GH' with
|
|
60
|
+
id-token permission) the OIDC token is fetched automatically."""
|
|
61
|
+
from pydoover.api.auth import TrustedPublisherAuthClient
|
|
62
|
+
|
|
63
|
+
# audience defaults to pydoover's DEFAULT_DOOVER_OIDC_AUDIENCE when unset.
|
|
64
|
+
kwargs = dict(
|
|
65
|
+
provider=provider,
|
|
66
|
+
oidc_token=oidc_token,
|
|
67
|
+
control_base_url=control_base_url,
|
|
68
|
+
timeout=timeout,
|
|
69
|
+
)
|
|
70
|
+
if audience:
|
|
71
|
+
kwargs["audience"] = audience
|
|
72
|
+
auth = TrustedPublisherAuthClient(**kwargs)
|
|
73
|
+
return cls(config_manager=None, profile_name=None, auth=auth)
|
|
74
|
+
|
|
48
75
|
@classmethod
|
|
49
76
|
def from_env(cls, *, timeout: float = 60.0) -> "DooverCLISession":
|
|
50
77
|
token = os.environ.get("DOOVER_API_TOKEN")
|
|
@@ -104,6 +104,21 @@ def _resolve_staging(staging: bool | None) -> bool:
|
|
|
104
104
|
return ".staging." in _control_base_url()
|
|
105
105
|
|
|
106
106
|
|
|
107
|
+
def _detect_git_commit(root_fp: Path) -> str:
|
|
108
|
+
"""Best-effort current git commit SHA for the app dir (empty if unavailable)."""
|
|
109
|
+
try:
|
|
110
|
+
result = subprocess.run(
|
|
111
|
+
["git", "rev-parse", "HEAD"],
|
|
112
|
+
cwd=root_fp,
|
|
113
|
+
capture_output=True,
|
|
114
|
+
text=True,
|
|
115
|
+
check=False,
|
|
116
|
+
)
|
|
117
|
+
except OSError:
|
|
118
|
+
return ""
|
|
119
|
+
return result.stdout.strip() if result.returncode == 0 else ""
|
|
120
|
+
|
|
121
|
+
|
|
107
122
|
def _build_container(
|
|
108
123
|
root_fp: Path, *, buildx: bool, build_args: str, image_name: str
|
|
109
124
|
) -> None:
|
|
@@ -1049,15 +1064,10 @@ def publish(
|
|
|
1049
1064
|
application_id,
|
|
1050
1065
|
root_fp,
|
|
1051
1066
|
)
|
|
1052
|
-
print(
|
|
1053
|
-
|
|
1054
|
-
print("Creating new lambda version release...")
|
|
1055
|
-
version_response = client.applications.processor_version(
|
|
1056
|
-
str(application_id),
|
|
1057
|
-
body=dict(payload),
|
|
1067
|
+
print(
|
|
1068
|
+
"Done! Run `doover app release` to publish an immutable version of this app."
|
|
1058
1069
|
)
|
|
1059
|
-
|
|
1060
|
-
renderer.render(version_response or processor_response or response)
|
|
1070
|
+
renderer.render(processor_response or response)
|
|
1061
1071
|
raise typer.Exit(0)
|
|
1062
1072
|
|
|
1063
1073
|
if build_container:
|
|
@@ -1079,6 +1089,77 @@ def publish(
|
|
|
1079
1089
|
renderer.render(response)
|
|
1080
1090
|
|
|
1081
1091
|
|
|
1092
|
+
@app.command(name="release")
|
|
1093
|
+
def release(
|
|
1094
|
+
app_fp: Annotated[
|
|
1095
|
+
Path, typer.Argument(help="Path to the application directory.")
|
|
1096
|
+
] = Path(),
|
|
1097
|
+
digest: Annotated[
|
|
1098
|
+
str | None,
|
|
1099
|
+
typer.Option(
|
|
1100
|
+
help="Pushed image digest (e.g. sha256:…) for container apps. Ignored for processors."
|
|
1101
|
+
),
|
|
1102
|
+
] = None,
|
|
1103
|
+
tag: Annotated[
|
|
1104
|
+
str | None,
|
|
1105
|
+
typer.Option(help="Optional version label (e.g. semver / image tag)."),
|
|
1106
|
+
] = None,
|
|
1107
|
+
commit: Annotated[
|
|
1108
|
+
str | None,
|
|
1109
|
+
typer.Option(
|
|
1110
|
+
help="Source git commit SHA (for browsing the code). Defaults to the repo's HEAD."
|
|
1111
|
+
),
|
|
1112
|
+
] = None,
|
|
1113
|
+
notes: Annotated[str, typer.Option(help="Optional release notes.")] = "",
|
|
1114
|
+
staging: Annotated[
|
|
1115
|
+
bool | None,
|
|
1116
|
+
typer.Option(help="Force staging mode (default: inferred from the API URL)."),
|
|
1117
|
+
] = None,
|
|
1118
|
+
app_name: Annotated[
|
|
1119
|
+
str | None,
|
|
1120
|
+
typer.Option(help="Application name in doover_config.json."),
|
|
1121
|
+
] = None,
|
|
1122
|
+
_profile: ProfileAnnotation = None,
|
|
1123
|
+
):
|
|
1124
|
+
"""Publish an immutable version (release) of an application.
|
|
1125
|
+
|
|
1126
|
+
Run `doover app publish` first (it updates the app's metadata + config/UI
|
|
1127
|
+
schema from doover_config.json). For container apps pass the pushed image
|
|
1128
|
+
--digest; for processors the lambda version is published server-side.
|
|
1129
|
+
"""
|
|
1130
|
+
_ = _profile
|
|
1131
|
+
root_fp = get_app_directory(app_fp)
|
|
1132
|
+
app_config = get_app_config(root_fp, app_name=app_name)
|
|
1133
|
+
resolved_staging = _resolve_staging(staging)
|
|
1134
|
+
|
|
1135
|
+
application_id = _get_persisted_application_id(app_config, staging=resolved_staging)
|
|
1136
|
+
if application_id is None:
|
|
1137
|
+
rich.print(
|
|
1138
|
+
"[red]Application has not been published yet. Run `doover app publish` first.[/red]"
|
|
1139
|
+
)
|
|
1140
|
+
raise typer.Exit(1)
|
|
1141
|
+
|
|
1142
|
+
if app_config.type == "DEV" and not digest:
|
|
1143
|
+
rich.print(
|
|
1144
|
+
"[red]--digest is required to release a container application.[/red]"
|
|
1145
|
+
)
|
|
1146
|
+
raise typer.Exit(1)
|
|
1147
|
+
|
|
1148
|
+
resolved_commit = commit if commit is not None else _detect_git_commit(root_fp)
|
|
1149
|
+
|
|
1150
|
+
client, renderer = get_state()
|
|
1151
|
+
with renderer.loading("Creating application version..."):
|
|
1152
|
+
version = client.create_application_version(
|
|
1153
|
+
application_id,
|
|
1154
|
+
digest=digest,
|
|
1155
|
+
tag=tag or "",
|
|
1156
|
+
commit=resolved_commit or "",
|
|
1157
|
+
notes=notes or "",
|
|
1158
|
+
)
|
|
1159
|
+
rich.print("[green]Released a new application version.[/green]")
|
|
1160
|
+
renderer.render(version)
|
|
1161
|
+
|
|
1162
|
+
|
|
1082
1163
|
@app.command(
|
|
1083
1164
|
context_settings={"allow_extra_args": True, "ignore_unknown_options": True}
|
|
1084
1165
|
)
|
|
@@ -8,6 +8,22 @@ from typer import Option
|
|
|
8
8
|
from doover_cli.api import ControlClientUnavailableError, DooverCLISession
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
def _trusted_publisher_provider() -> str | None:
|
|
12
|
+
"""The CI provider to use for the trusted-publisher (OIDC) flow, or None.
|
|
13
|
+
|
|
14
|
+
Triggers on explicit opt-in (`DOOVER_TRUSTED_PUBLISHER`, value is the provider
|
|
15
|
+
or a truthy flag meaning GitHub), or automatically inside a GitHub Actions job
|
|
16
|
+
that has `permissions: id-token: write` — so `doover app publish` just works."""
|
|
17
|
+
explicit = os.environ.get("DOOVER_TRUSTED_PUBLISHER")
|
|
18
|
+
if explicit:
|
|
19
|
+
return "GH" if explicit.lower() in ("1", "true", "yes", "github") else explicit
|
|
20
|
+
if os.environ.get("GITHUB_ACTIONS") == "true" and os.environ.get(
|
|
21
|
+
"ACTIONS_ID_TOKEN_REQUEST_TOKEN"
|
|
22
|
+
):
|
|
23
|
+
return "GH"
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
11
27
|
def setup_session(
|
|
12
28
|
profile_name: str,
|
|
13
29
|
config_manager: ConfigManager | None = None,
|
|
@@ -15,6 +31,14 @@ def setup_session(
|
|
|
15
31
|
if os.environ.get("DOOVER_API_TOKEN"):
|
|
16
32
|
return DooverCLISession.from_env()
|
|
17
33
|
|
|
34
|
+
provider = _trusted_publisher_provider()
|
|
35
|
+
if provider:
|
|
36
|
+
return DooverCLISession.from_trusted_publisher(
|
|
37
|
+
provider=provider,
|
|
38
|
+
audience=os.environ.get("DOOVER_OIDC_AUDIENCE"),
|
|
39
|
+
control_base_url=os.environ.get("DOOVER_CONTROL_API_BASE_URL"),
|
|
40
|
+
)
|
|
41
|
+
|
|
18
42
|
manager = config_manager or ConfigManager(profile_name)
|
|
19
43
|
manager.current_profile = profile_name
|
|
20
44
|
return DooverCLISession.from_profile(profile_name, config_manager=manager)
|
|
@@ -306,16 +306,20 @@ def get_app_directory(root: Path | None = None) -> Path:
|
|
|
306
306
|
|
|
307
307
|
|
|
308
308
|
def get_uv_path() -> Path:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
)
|
|
318
|
-
|
|
309
|
+
# Prefer uv on PATH (covers CI where uv is installed to a tool cache dir),
|
|
310
|
+
# then common fixed locations.
|
|
311
|
+
found = shutil.which("uv")
|
|
312
|
+
if found:
|
|
313
|
+
return Path(found)
|
|
314
|
+
|
|
315
|
+
for candidate in (
|
|
316
|
+
Path("/opt/homebrew/bin/uv"),
|
|
317
|
+
Path.home() / ".local" / "bin" / "uv",
|
|
318
|
+
):
|
|
319
|
+
if candidate.exists():
|
|
320
|
+
return candidate
|
|
321
|
+
|
|
322
|
+
raise RuntimeError("uv not found in your PATH. Please install it and try again.")
|
|
319
323
|
|
|
320
324
|
|
|
321
325
|
def call_with_uv(
|
|
@@ -662,7 +662,9 @@ def test_app_publish_processor_type_without_ui_skips_ui_export(monkeypatch, tmp_
|
|
|
662
662
|
assert "export_ui_called" not in captured
|
|
663
663
|
assert captured["build_script"] == ("./build.sh", tmp_path)
|
|
664
664
|
assert captured["processor_source"] == ("303", {"file": package_fp})
|
|
665
|
-
|
|
665
|
+
# publish uploads the package but no longer cuts a version (that's `release`)
|
|
666
|
+
assert "processor_version" not in captured
|
|
667
|
+
assert renderer.render_calls == [{"id": 303}]
|
|
666
668
|
|
|
667
669
|
|
|
668
670
|
def test_app_publish_skips_ui_export_when_config_disables_generation(
|
|
@@ -769,9 +771,7 @@ def test_app_publish_honours_explicit_staging(monkeypatch, tmp_path):
|
|
|
769
771
|
lambda ctx, app_fp, validate_: None,
|
|
770
772
|
)
|
|
771
773
|
|
|
772
|
-
result = runner.invoke(
|
|
773
|
-
app, ["app", "publish", str(tmp_path), "--staging"]
|
|
774
|
-
)
|
|
774
|
+
result = runner.invoke(app, ["app", "publish", str(tmp_path), "--staging"])
|
|
775
775
|
|
|
776
776
|
assert result.exit_code == 0
|
|
777
777
|
assert captured["body"]["deployment_data"] == "staging-data"
|
|
@@ -872,9 +872,7 @@ def test_app_publish_skips_build_when_requested_by_config(monkeypatch, tmp_path)
|
|
|
872
872
|
assert renderer.render_calls == [{"id": 101}]
|
|
873
873
|
|
|
874
874
|
|
|
875
|
-
def
|
|
876
|
-
monkeypatch, tmp_path
|
|
877
|
-
):
|
|
875
|
+
def test_app_publish_processor_builds_package_without_release(monkeypatch, tmp_path):
|
|
878
876
|
captured = {}
|
|
879
877
|
renderer = FakeRenderer()
|
|
880
878
|
app_config = FakeAppConfig(app_id=303)
|
|
@@ -938,10 +936,11 @@ def test_app_publish_processor_builds_package_and_releases_version(
|
|
|
938
936
|
assert captured["build_script"] == ("./build.sh", tmp_path)
|
|
939
937
|
assert captured["application_id"] == "303"
|
|
940
938
|
assert captured["body"] == {"file": package_fp}
|
|
941
|
-
|
|
939
|
+
# publish uploads the package; the version is cut by `doover app release`
|
|
940
|
+
assert "version" not in captured
|
|
942
941
|
assert "build_called" not in captured
|
|
943
942
|
assert "push_called" not in captured
|
|
944
|
-
assert renderer.render_calls == [{"id": 303
|
|
943
|
+
assert renderer.render_calls == [{"id": 303}]
|
|
945
944
|
|
|
946
945
|
|
|
947
946
|
def test_publish_processor_package_uses_package_zip(tmp_path):
|
|
@@ -187,3 +187,49 @@ def test_session_require_agent_id_rejects_missing_value():
|
|
|
187
187
|
|
|
188
188
|
with pytest.raises(typer.BadParameter):
|
|
189
189
|
session.require_agent_id(None)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_trusted_publisher_provider_detects_github_actions(monkeypatch):
|
|
193
|
+
from doover_cli.utils.api import _trusted_publisher_provider
|
|
194
|
+
|
|
195
|
+
monkeypatch.delenv("DOOVER_TRUSTED_PUBLISHER", raising=False)
|
|
196
|
+
monkeypatch.setenv("GITHUB_ACTIONS", "true")
|
|
197
|
+
monkeypatch.setenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", "rt")
|
|
198
|
+
|
|
199
|
+
assert _trusted_publisher_provider() == "GH"
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_trusted_publisher_provider_explicit_flag(monkeypatch):
|
|
203
|
+
from doover_cli.utils.api import _trusted_publisher_provider
|
|
204
|
+
|
|
205
|
+
monkeypatch.setenv("DOOVER_TRUSTED_PUBLISHER", "1")
|
|
206
|
+
|
|
207
|
+
assert _trusted_publisher_provider() == "GH"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_trusted_publisher_provider_absent(monkeypatch):
|
|
211
|
+
from doover_cli.utils.api import _trusted_publisher_provider
|
|
212
|
+
|
|
213
|
+
monkeypatch.delenv("DOOVER_TRUSTED_PUBLISHER", raising=False)
|
|
214
|
+
monkeypatch.delenv("GITHUB_ACTIONS", raising=False)
|
|
215
|
+
monkeypatch.delenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN", raising=False)
|
|
216
|
+
|
|
217
|
+
assert _trusted_publisher_provider() is None
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def test_session_from_trusted_publisher_builds_control_client():
|
|
221
|
+
from pydoover.api import auth as _auth
|
|
222
|
+
|
|
223
|
+
if not hasattr(_auth, "TrustedPublisherAuthClient"):
|
|
224
|
+
pytest.skip("installed pydoover lacks trusted-publisher support yet")
|
|
225
|
+
|
|
226
|
+
session = DooverCLISession.from_trusted_publisher(
|
|
227
|
+
provider="GH",
|
|
228
|
+
oidc_token="gh-oidc",
|
|
229
|
+
control_base_url="https://control.example",
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
assert session.auth.provider == "GH"
|
|
233
|
+
assert session.auth.control_base_url == "https://control.example"
|
|
234
|
+
client = session.get_control_client()
|
|
235
|
+
assert client.base_url == "https://control.example"
|