dockerhub-api 1.3.9__tar.gz → 1.4.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.
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/PKG-INFO +11 -17
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/README.md +9 -15
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_access_tokens.py +3 -1
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_audit_logs.py +3 -3
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_base.py +1 -20
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_groups.py +4 -2
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_org_access_tokens.py +3 -1
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_orgs.py +5 -3
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_registry.py +11 -9
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_registry_base.py +0 -2
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/auth.py +4 -17
- dockerhub_api-1.4.0/dockerhub_api/connector_contracts.py +961 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/kg_ingest.py +3 -3
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/mcp/__init__.py +2 -49
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/mcp/mcp_admin.py +1 -1
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/mcp/mcp_kg.py +2 -2
- dockerhub_api-1.4.0/dockerhub_api/mcp_server.py +26 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/ontology/certification.json +3 -3
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/ontology/dockerhub.ttl +7 -12
- dockerhub_api-1.4.0/dockerhub_api/ontology/shapes/connector.shacl.ttl +64 -0
- dockerhub_api-1.4.0/dockerhub_api/prompts/dockerhub-mcp-operator.json +277 -0
- dockerhub_api-1.4.0/dockerhub_api/prompts/dockerhub-mcp-organization-administrator.json +184 -0
- dockerhub_api-1.4.0/dockerhub_api/prompts/dockerhub-mcp-release-engineer.json +156 -0
- dockerhub_api-1.4.0/dockerhub_api/prompts/dockerhub-mcp-security-analyst.json +123 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/skills/dockerhub-api-development/SKILL.md +5 -5
- dockerhub_api-1.4.0/dockerhub_api/skills/dockerhub-api-operations/SKILL.md +74 -0
- dockerhub_api-1.4.0/dockerhub_api/skills/dockerhub-image-registry-scout/SKILL.md +62 -0
- dockerhub_api-1.4.0/dockerhub_api/skills/dockerhub-org-administration/SKILL.md +109 -0
- dockerhub_api-1.4.0/dockerhub_api/skills/dockerhub-repository-management/SKILL.md +56 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api.egg-info/PKG-INFO +11 -17
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api.egg-info/SOURCES.txt +6 -13
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api.egg-info/requires.txt +1 -1
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/pyproject.toml +2 -2
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_api_audit.py +1 -1
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_api_org.py +6 -16
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_api_registry.py +11 -24
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_api_teams.py +4 -9
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_api_tokens.py +4 -12
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_api_wrapper.py +2 -8
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_auth.py +0 -6
- dockerhub_api-1.4.0/tests/test_destructive_declaration.py +98 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_kg_ingest.py +3 -3
- dockerhub_api-1.3.9/dockerhub_api/mcp/mcp_audit.py +0 -44
- dockerhub_api-1.3.9/dockerhub_api/mcp/mcp_auth.py +0 -66
- dockerhub_api-1.3.9/dockerhub_api/mcp/mcp_org.py +0 -58
- dockerhub_api-1.3.9/dockerhub_api/mcp/mcp_registry.py +0 -80
- dockerhub_api-1.3.9/dockerhub_api/mcp/mcp_repos.py +0 -58
- dockerhub_api-1.3.9/dockerhub_api/mcp/mcp_scim.py +0 -56
- dockerhub_api-1.3.9/dockerhub_api/mcp/mcp_scout.py +0 -60
- dockerhub_api-1.3.9/dockerhub_api/mcp/mcp_teams.py +0 -55
- dockerhub_api-1.3.9/dockerhub_api/mcp_server.py +0 -44
- dockerhub_api-1.3.9/dockerhub_api/ontology/shapes/connector.shacl.ttl +0 -144
- dockerhub_api-1.3.9/dockerhub_api/skills/dockerhub-api-operations/SKILL.md +0 -33
- dockerhub_api-1.3.9/dockerhub_api/skills/dockerhub-api-operations/agents/openai.yaml +0 -4
- dockerhub_api-1.3.9/dockerhub_api/skills/dockerhub-api-operations/references/catalog.md +0 -7
- dockerhub_api-1.3.9/dockerhub_api/skills/dockerhub-image-registry-scout/SKILL.md +0 -107
- dockerhub_api-1.3.9/dockerhub_api/skills/dockerhub-image-registry-scout/WORKFLOW.md +0 -93
- dockerhub_api-1.3.9/dockerhub_api/skills/dockerhub-org-administration/SKILL.md +0 -105
- dockerhub_api-1.3.9/dockerhub_api/skills/dockerhub-org-administration/WORKFLOW.md +0 -91
- dockerhub_api-1.3.9/dockerhub_api/skills/dockerhub-repository-management/SKILL.md +0 -111
- dockerhub_api-1.3.9/dockerhub_api/skills/dockerhub-repository-management/WORKFLOW.md +0 -97
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/LICENSE +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/__init__.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/__main__.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/__init__.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_auth.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_repositories.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_scim.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_scout.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api_client.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/connector_sources.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/dockerhub_input_models.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/dockerhub_response_models.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/mcp_config.json +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/ontology/__init__.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/ontology/fixtures/records.json +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/ontology/mappings/source.yaml +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/ontology/migrations/manifest.json +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api.egg-info/dependency_links.txt +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api.egg-info/entry_points.txt +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api.egg-info/top_level.txt +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/setup.cfg +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_api_repositories.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_api_scim.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_api_scout.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_dockerhub_a2a_validation.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_dockerhub_mcp_validation.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_fleet_name.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_identity_namespace_scoping.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_init_dynamics.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_models.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_rate_limit.py +0 -0
- {dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/tests/test_startup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dockerhub-api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Docker Hub API + MCP Server
|
|
5
5
|
Author-email: Repository Maintainers <maintainers@knuckles.team>
|
|
6
6
|
License: MIT
|
|
@@ -12,7 +12,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
12
12
|
Requires-Python: <3.15,>=3.12
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
|
-
Requires-Dist: scion-sdk[mcp]<0.
|
|
15
|
+
Requires-Dist: scion-sdk[mcp]<0.11,>=0.10.2
|
|
16
16
|
Requires-Dist: httpx>=0.28.1
|
|
17
17
|
Requires-Dist: python-dotenv>=1.2.4
|
|
18
18
|
Provides-Extra: test
|
|
@@ -84,8 +84,7 @@ multi-arch inspection, OCI referrers, and gated push/remove) and **Docker Scout*
|
|
|
84
84
|
expiry, with one transparent re-mint on 401.
|
|
85
85
|
- **Rate-Limit Telemetry:** `X-RateLimit-*` headers surfaced in every result;
|
|
86
86
|
HTTP 429 retried with bounded `Retry-After` backoff.
|
|
87
|
-
- **Safety by Default:** Removes and org-settings writes are
|
|
88
|
-
`DOCKERHUB_ALLOW_DESTRUCTIVE` (default `False`); secrets are redacted from tool
|
|
87
|
+
- **Safety by Default:** Removes and org-settings writes are declared destructive; the SDK asks the caller to confirm (`confirm=true`) before any request. Secrets are redacted from tool
|
|
89
88
|
results (plaintext tokens appear exactly once — on creation). Repository creation
|
|
90
89
|
stays enabled: it is the primary release-provisioning use case.
|
|
91
90
|
- **Integrated Graph Agent:** Built-in Pydantic AI agent (`dockerhub-agent`) with
|
|
@@ -126,7 +125,7 @@ One multi-stage `docker/Dockerfile` builds the MCP-only image (`--target mcp`):
|
|
|
126
125
|
|
|
127
126
|
| Image tag | Build target | Contents | Entrypoint |
|
|
128
127
|
|-----------|--------------|----------|------------|
|
|
129
|
-
| `example/dockerhub-api:mcp` | `--target mcp` | `dockerhub-api[mcp]` — **connector-focused**, includes `
|
|
128
|
+
| `example/dockerhub-api:mcp` | `--target mcp` | `dockerhub-api[mcp]` — **connector-focused**, includes `medulla`; no model-orchestration stack | `dockerhub-mcp` |
|
|
130
129
|
|
|
131
130
|
```bash
|
|
132
131
|
docker build --target mcp -t example/dockerhub-api:mcp docker/ # connector-focused MCP server
|
|
@@ -134,14 +133,14 @@ docker build --target mcp -t example/dockerhub-api:mcp docker/ # connecto
|
|
|
134
133
|
|
|
135
134
|
agent (`immutable agent digest`) with a co-located `:mcp` sidecar.
|
|
136
135
|
|
|
137
|
-
### Knowledge-graph database (`
|
|
136
|
+
### Knowledge-graph database (`medulla`)
|
|
138
137
|
|
|
139
|
-
Scion SDK core dependency (`
|
|
138
|
+
Scion SDK core dependency (`medulla`). The `[mcp]` extra keeps
|
|
140
139
|
deployments can use the bundled engine. For production or shared state, run
|
|
141
|
-
**
|
|
140
|
+
**medulla as a dedicated database service** and configure the runtime to use it.
|
|
142
141
|
Deployment recipes (single-node + Raft HA), connection configuration, and architecture
|
|
143
142
|
diagrams are documented in the
|
|
144
|
-
[
|
|
143
|
+
[medulla deployment guide](https://knuckles-team.github.io/medulla/deployment/).
|
|
145
144
|
|
|
146
145
|
---
|
|
147
146
|
|
|
@@ -178,7 +177,7 @@ _Auto-generated — do not edit (synced by the `mcp-readme-table` pre-commit hoo
|
|
|
178
177
|
| `hub_admin` | `ADMINTOOL` | Client diagnostics: 'rate_limit' returns the latest |
|
|
179
178
|
| `hub_audit` | `AUDITTOOL` | Read a Docker Hub account's audit trail: 'logs' lists events |
|
|
180
179
|
| `hub_auth` | `AUTHTOOL` | Manage Docker Hub authentication, personal access tokens (PATs), |
|
|
181
|
-
| `dockerhub_ingest_repositories` | `KGTOOL` | Natively ingest repositories into the
|
|
180
|
+
| `dockerhub_ingest_repositories` | `KGTOOL` | Natively ingest repositories into the medulla as typed :Repository/:ContainerImage nodes. |
|
|
182
181
|
| `hub_org` | `ORGTOOL` | Manage a Docker Hub organization: settings (restricted images), |
|
|
183
182
|
| `hub_registry` | `REGISTRYTOOL` | Docker Registry v2 image operations (``registry-1.docker.io``): |
|
|
184
183
|
| `hub_repos` | `REPOSTOOL` | Manage Docker Hub repositories: list/create/inspect repositories, |
|
|
@@ -259,7 +258,7 @@ _9 action-routed tool(s) (default) · 54 verbose 1:1 tool(s). Each is enabled un
|
|
|
259
258
|
|
|
260
259
|
> **Install the connector-focused `[mcp]` extra.** Examples use `dockerhub-api[mcp]` to add
|
|
261
260
|
> FastMCP / FastAPI through `scion-sdk`; the required Scion SDK core
|
|
262
|
-
> still carries `
|
|
261
|
+
> still carries `medulla`. The `[agent-runtime]` extra additionally
|
|
263
262
|
> enables model orchestration.
|
|
264
263
|
|
|
265
264
|
#### stdio Transport (local IDEs — Cursor, Claude Desktop, VS Code)
|
|
@@ -279,7 +278,6 @@ _9 action-routed tool(s) (default) · 54 verbose 1:1 tool(s). Each is enabled un
|
|
|
279
278
|
"ADMINTOOL": "True",
|
|
280
279
|
"AUDITTOOL": "True",
|
|
281
280
|
"AUTHTOOL": "True",
|
|
282
|
-
"DOCKERHUB_ALLOW_DESTRUCTIVE": "False",
|
|
283
281
|
"DOCKERHUB_TLS_PROFILE": "system",
|
|
284
282
|
"DOCKERHUB_TOKEN": "dckr_pat_your_personal_access_token",
|
|
285
283
|
"DOCKERHUB_URL": "https://hub.docker.com",
|
|
@@ -328,7 +326,6 @@ own runtime secret boundary.
|
|
|
328
326
|
"ADMINTOOL": "True",
|
|
329
327
|
"AUDITTOOL": "True",
|
|
330
328
|
"AUTHTOOL": "True",
|
|
331
|
-
"DOCKERHUB_ALLOW_DESTRUCTIVE": "False",
|
|
332
329
|
"DOCKERHUB_TLS_PROFILE": "system",
|
|
333
330
|
"DOCKERHUB_TOKEN": "dckr_pat_your_personal_access_token",
|
|
334
331
|
"DOCKERHUB_URL": "https://hub.docker.com",
|
|
@@ -376,7 +373,6 @@ docker run -i --rm \
|
|
|
376
373
|
-e ADMINTOOL=True \
|
|
377
374
|
-e AUDITTOOL=True \
|
|
378
375
|
-e AUTHTOOL=True \
|
|
379
|
-
-e DOCKERHUB_ALLOW_DESTRUCTIVE=False \
|
|
380
376
|
-e DOCKERHUB_TLS_PROFILE=system \
|
|
381
377
|
-e DOCKERHUB_TOKEN=dckr_pat_your_personal_access_token \
|
|
382
378
|
-e DOCKERHUB_URL=https://hub.docker.com \
|
|
@@ -423,8 +419,7 @@ the detailed transport contract.
|
|
|
423
419
|
|---|---|---|
|
|
424
420
|
| Reads (repos, tags, members, logs, SCIM) | allowed | — |
|
|
425
421
|
| Repository create / immutable-tag config / invites / role updates | allowed | — |
|
|
426
|
-
| Removes (PATs, OATs, groups, members, invites) |
|
|
427
|
-
| Org-settings writes (`PUT /v2/orgs/{org}/settings`) | **blocked** | `DOCKERHUB_ALLOW_DESTRUCTIVE=True` |
|
|
422
|
+
| Removes (PATs, OATs, groups, members, invites) and org-settings writes | confirmation required | `confirm=true` per call, or operator setting `SCION_ALLOW_DESTRUCTIVE` |
|
|
428
423
|
|
|
429
424
|
---
|
|
430
425
|
|
|
@@ -488,7 +483,6 @@ network transport, connector ingestion, Arbora delegation, or trace export.
|
|
|
488
483
|
| `DOCKER_REGISTRY_URL` | `https://registry-1.docker.io` | |
|
|
489
484
|
| `DOCKER_REGISTRY_AUTH_URL` | `https://auth.docker.io/token` | token-service realm; auto-discovered from the 401 challenge |
|
|
490
485
|
| `DOCKER_SCOUT_URL` | `https://api.scout.docker.com` | |
|
|
491
|
-
| `DOCKERHUB_ALLOW_DESTRUCTIVE` | `False` | |
|
|
492
486
|
| `AUTHTOOL` | `True` | |
|
|
493
487
|
| `REPOSTOOL` | `True` | |
|
|
494
488
|
| `ORGTOOL` | `True` | |
|
|
@@ -60,8 +60,7 @@ multi-arch inspection, OCI referrers, and gated push/remove) and **Docker Scout*
|
|
|
60
60
|
expiry, with one transparent re-mint on 401.
|
|
61
61
|
- **Rate-Limit Telemetry:** `X-RateLimit-*` headers surfaced in every result;
|
|
62
62
|
HTTP 429 retried with bounded `Retry-After` backoff.
|
|
63
|
-
- **Safety by Default:** Removes and org-settings writes are
|
|
64
|
-
`DOCKERHUB_ALLOW_DESTRUCTIVE` (default `False`); secrets are redacted from tool
|
|
63
|
+
- **Safety by Default:** Removes and org-settings writes are declared destructive; the SDK asks the caller to confirm (`confirm=true`) before any request. Secrets are redacted from tool
|
|
65
64
|
results (plaintext tokens appear exactly once — on creation). Repository creation
|
|
66
65
|
stays enabled: it is the primary release-provisioning use case.
|
|
67
66
|
- **Integrated Graph Agent:** Built-in Pydantic AI agent (`dockerhub-agent`) with
|
|
@@ -102,7 +101,7 @@ One multi-stage `docker/Dockerfile` builds the MCP-only image (`--target mcp`):
|
|
|
102
101
|
|
|
103
102
|
| Image tag | Build target | Contents | Entrypoint |
|
|
104
103
|
|-----------|--------------|----------|------------|
|
|
105
|
-
| `example/dockerhub-api:mcp` | `--target mcp` | `dockerhub-api[mcp]` — **connector-focused**, includes `
|
|
104
|
+
| `example/dockerhub-api:mcp` | `--target mcp` | `dockerhub-api[mcp]` — **connector-focused**, includes `medulla`; no model-orchestration stack | `dockerhub-mcp` |
|
|
106
105
|
|
|
107
106
|
```bash
|
|
108
107
|
docker build --target mcp -t example/dockerhub-api:mcp docker/ # connector-focused MCP server
|
|
@@ -110,14 +109,14 @@ docker build --target mcp -t example/dockerhub-api:mcp docker/ # connecto
|
|
|
110
109
|
|
|
111
110
|
agent (`immutable agent digest`) with a co-located `:mcp` sidecar.
|
|
112
111
|
|
|
113
|
-
### Knowledge-graph database (`
|
|
112
|
+
### Knowledge-graph database (`medulla`)
|
|
114
113
|
|
|
115
|
-
Scion SDK core dependency (`
|
|
114
|
+
Scion SDK core dependency (`medulla`). The `[mcp]` extra keeps
|
|
116
115
|
deployments can use the bundled engine. For production or shared state, run
|
|
117
|
-
**
|
|
116
|
+
**medulla as a dedicated database service** and configure the runtime to use it.
|
|
118
117
|
Deployment recipes (single-node + Raft HA), connection configuration, and architecture
|
|
119
118
|
diagrams are documented in the
|
|
120
|
-
[
|
|
119
|
+
[medulla deployment guide](https://knuckles-team.github.io/medulla/deployment/).
|
|
121
120
|
|
|
122
121
|
---
|
|
123
122
|
|
|
@@ -154,7 +153,7 @@ _Auto-generated — do not edit (synced by the `mcp-readme-table` pre-commit hoo
|
|
|
154
153
|
| `hub_admin` | `ADMINTOOL` | Client diagnostics: 'rate_limit' returns the latest |
|
|
155
154
|
| `hub_audit` | `AUDITTOOL` | Read a Docker Hub account's audit trail: 'logs' lists events |
|
|
156
155
|
| `hub_auth` | `AUTHTOOL` | Manage Docker Hub authentication, personal access tokens (PATs), |
|
|
157
|
-
| `dockerhub_ingest_repositories` | `KGTOOL` | Natively ingest repositories into the
|
|
156
|
+
| `dockerhub_ingest_repositories` | `KGTOOL` | Natively ingest repositories into the medulla as typed :Repository/:ContainerImage nodes. |
|
|
158
157
|
| `hub_org` | `ORGTOOL` | Manage a Docker Hub organization: settings (restricted images), |
|
|
159
158
|
| `hub_registry` | `REGISTRYTOOL` | Docker Registry v2 image operations (``registry-1.docker.io``): |
|
|
160
159
|
| `hub_repos` | `REPOSTOOL` | Manage Docker Hub repositories: list/create/inspect repositories, |
|
|
@@ -235,7 +234,7 @@ _9 action-routed tool(s) (default) · 54 verbose 1:1 tool(s). Each is enabled un
|
|
|
235
234
|
|
|
236
235
|
> **Install the connector-focused `[mcp]` extra.** Examples use `dockerhub-api[mcp]` to add
|
|
237
236
|
> FastMCP / FastAPI through `scion-sdk`; the required Scion SDK core
|
|
238
|
-
> still carries `
|
|
237
|
+
> still carries `medulla`. The `[agent-runtime]` extra additionally
|
|
239
238
|
> enables model orchestration.
|
|
240
239
|
|
|
241
240
|
#### stdio Transport (local IDEs — Cursor, Claude Desktop, VS Code)
|
|
@@ -255,7 +254,6 @@ _9 action-routed tool(s) (default) · 54 verbose 1:1 tool(s). Each is enabled un
|
|
|
255
254
|
"ADMINTOOL": "True",
|
|
256
255
|
"AUDITTOOL": "True",
|
|
257
256
|
"AUTHTOOL": "True",
|
|
258
|
-
"DOCKERHUB_ALLOW_DESTRUCTIVE": "False",
|
|
259
257
|
"DOCKERHUB_TLS_PROFILE": "system",
|
|
260
258
|
"DOCKERHUB_TOKEN": "dckr_pat_your_personal_access_token",
|
|
261
259
|
"DOCKERHUB_URL": "https://hub.docker.com",
|
|
@@ -304,7 +302,6 @@ own runtime secret boundary.
|
|
|
304
302
|
"ADMINTOOL": "True",
|
|
305
303
|
"AUDITTOOL": "True",
|
|
306
304
|
"AUTHTOOL": "True",
|
|
307
|
-
"DOCKERHUB_ALLOW_DESTRUCTIVE": "False",
|
|
308
305
|
"DOCKERHUB_TLS_PROFILE": "system",
|
|
309
306
|
"DOCKERHUB_TOKEN": "dckr_pat_your_personal_access_token",
|
|
310
307
|
"DOCKERHUB_URL": "https://hub.docker.com",
|
|
@@ -352,7 +349,6 @@ docker run -i --rm \
|
|
|
352
349
|
-e ADMINTOOL=True \
|
|
353
350
|
-e AUDITTOOL=True \
|
|
354
351
|
-e AUTHTOOL=True \
|
|
355
|
-
-e DOCKERHUB_ALLOW_DESTRUCTIVE=False \
|
|
356
352
|
-e DOCKERHUB_TLS_PROFILE=system \
|
|
357
353
|
-e DOCKERHUB_TOKEN=dckr_pat_your_personal_access_token \
|
|
358
354
|
-e DOCKERHUB_URL=https://hub.docker.com \
|
|
@@ -399,8 +395,7 @@ the detailed transport contract.
|
|
|
399
395
|
|---|---|---|
|
|
400
396
|
| Reads (repos, tags, members, logs, SCIM) | allowed | — |
|
|
401
397
|
| Repository create / immutable-tag config / invites / role updates | allowed | — |
|
|
402
|
-
| Removes (PATs, OATs, groups, members, invites) |
|
|
403
|
-
| Org-settings writes (`PUT /v2/orgs/{org}/settings`) | **blocked** | `DOCKERHUB_ALLOW_DESTRUCTIVE=True` |
|
|
398
|
+
| Removes (PATs, OATs, groups, members, invites) and org-settings writes | confirmation required | `confirm=true` per call, or operator setting `SCION_ALLOW_DESTRUCTIVE` |
|
|
404
399
|
|
|
405
400
|
---
|
|
406
401
|
|
|
@@ -464,7 +459,6 @@ network transport, connector ingestion, Arbora delegation, or trace export.
|
|
|
464
459
|
| `DOCKER_REGISTRY_URL` | `https://registry-1.docker.io` | |
|
|
465
460
|
| `DOCKER_REGISTRY_AUTH_URL` | `https://auth.docker.io/token` | token-service realm; auto-discovered from the 401 challenge |
|
|
466
461
|
| `DOCKER_SCOUT_URL` | `https://api.scout.docker.com` | |
|
|
467
|
-
| `DOCKERHUB_ALLOW_DESTRUCTIVE` | `False` | |
|
|
468
462
|
| `AUTHTOOL` | `True` | |
|
|
469
463
|
| `REPOSTOOL` | `True` | |
|
|
470
464
|
| `ORGTOOL` | `True` | |
|
|
@@ -5,6 +5,8 @@ CONCEPT:DH-OS.identity.jwt-auth-lifecycle-endpoint — JWT auth lifecycle (PAT m
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from scion_sdk.mcp.destructive import destructive_operation
|
|
9
|
+
|
|
8
10
|
from dockerhub_api.api.api_client_base import DockerHubApiBase
|
|
9
11
|
from dockerhub_api.dockerhub_input_models import (
|
|
10
12
|
AccessTokenCreateModel,
|
|
@@ -70,8 +72,8 @@ class DockerHubApiAccessTokens(DockerHubApiBase):
|
|
|
70
72
|
envelope["data"] = validate_lenient(AccessToken, envelope["data"])
|
|
71
73
|
return envelope
|
|
72
74
|
|
|
75
|
+
@destructive_operation
|
|
73
76
|
def delete_access_token(self, uuid: str) -> dict[str, Any]:
|
|
74
77
|
"""Delete a personal access token. Destructive — gated."""
|
|
75
|
-
self._guard_destructive("delete_access_token")
|
|
76
78
|
model = AccessTokenModel(uuid=uuid)
|
|
77
79
|
return self._request("DELETE", f"/v2/access-tokens/{model.uuid}")
|
|
@@ -20,7 +20,7 @@ class DockerHubApiAuditLogs(DockerHubApiBase):
|
|
|
20
20
|
def get_audit_logs(
|
|
21
21
|
self,
|
|
22
22
|
account: str,
|
|
23
|
-
|
|
23
|
+
audit_action: str | None = None,
|
|
24
24
|
name: str | None = None,
|
|
25
25
|
actor: str | None = None,
|
|
26
26
|
from_date: str | None = None,
|
|
@@ -30,12 +30,12 @@ class DockerHubApiAuditLogs(DockerHubApiBase):
|
|
|
30
30
|
) -> dict[str, Any]:
|
|
31
31
|
"""List audit-log events for a namespace.
|
|
32
32
|
|
|
33
|
-
Filters: ``
|
|
33
|
+
Filters: ``audit_action``, ``name`` (object), ``actor`` (username), and a
|
|
34
34
|
``from_date``/``to_date`` RFC 3339 window (sent as ``from``/``to``).
|
|
35
35
|
"""
|
|
36
36
|
model = AuditLogModel(
|
|
37
37
|
account=account,
|
|
38
|
-
action=
|
|
38
|
+
action=audit_action,
|
|
39
39
|
name=name,
|
|
40
40
|
actor=actor,
|
|
41
41
|
from_date=from_date,
|
|
@@ -10,8 +10,7 @@ headers are captured into the result envelope (and kept on the client as
|
|
|
10
10
|
``Retry-After`` backoff.
|
|
11
11
|
|
|
12
12
|
CONCEPT:DH-OS.identity.destructive-action-gating-member — destructive-action gating. Deletes and org-settings writes
|
|
13
|
-
|
|
14
|
-
``allow_destructive=True``.
|
|
13
|
+
are declared with the SDK ``destructive_operation`` decorator; the SDK asks the caller to confirm.
|
|
15
14
|
"""
|
|
16
15
|
|
|
17
16
|
import logging
|
|
@@ -40,10 +39,6 @@ DEFAULT_MAX_RETRIES = 3
|
|
|
40
39
|
DEFAULT_TIMEOUT = 30.0
|
|
41
40
|
|
|
42
41
|
|
|
43
|
-
class DestructiveOperationError(PermissionError):
|
|
44
|
-
"""Raised when a destructive operation is attempted while gated off."""
|
|
45
|
-
|
|
46
|
-
|
|
47
42
|
class DockerHubApiBase:
|
|
48
43
|
"""Shared transport, auth header, retry, and envelope logic."""
|
|
49
44
|
|
|
@@ -58,7 +53,6 @@ class DockerHubApiBase:
|
|
|
58
53
|
timeout: float = DEFAULT_TIMEOUT,
|
|
59
54
|
max_retries: int = DEFAULT_MAX_RETRIES,
|
|
60
55
|
retry_after_cap: float = DEFAULT_RETRY_AFTER_CAP,
|
|
61
|
-
allow_destructive: bool = False,
|
|
62
56
|
debug: bool = False,
|
|
63
57
|
transport: httpx.BaseTransport | None = None,
|
|
64
58
|
):
|
|
@@ -75,7 +69,6 @@ class DockerHubApiBase:
|
|
|
75
69
|
self.timeout = timeout
|
|
76
70
|
self.max_retries = max_retries
|
|
77
71
|
self.retry_after_cap = retry_after_cap
|
|
78
|
-
self.allow_destructive = allow_destructive
|
|
79
72
|
self.debug = debug
|
|
80
73
|
#: Last rate-limit snapshot seen on any response.
|
|
81
74
|
self.rate_limit: dict[str, Any] = {}
|
|
@@ -188,18 +181,6 @@ class DockerHubApiBase:
|
|
|
188
181
|
"rate_limit": dict(self.rate_limit) if self.rate_limit else {},
|
|
189
182
|
}
|
|
190
183
|
|
|
191
|
-
# ------------------------------------------------------------------ #
|
|
192
|
-
# Destructive gating
|
|
193
|
-
# ------------------------------------------------------------------ #
|
|
194
|
-
|
|
195
|
-
def _guard_destructive(self, operation: str) -> None:
|
|
196
|
-
if not self.allow_destructive:
|
|
197
|
-
raise DestructiveOperationError(
|
|
198
|
-
f"Destructive operation '{operation}' is disabled. "
|
|
199
|
-
"Build the client with allow_destructive=True or set "
|
|
200
|
-
"DOCKERHUB_ALLOW_DESTRUCTIVE=True to enable it."
|
|
201
|
-
)
|
|
202
|
-
|
|
203
184
|
# ------------------------------------------------------------------ #
|
|
204
185
|
# Request engine
|
|
205
186
|
# ------------------------------------------------------------------ #
|
|
@@ -6,6 +6,8 @@ CONCEPT:DH-OS.identity.destructive-action-gating-member — destructive-action g
|
|
|
6
6
|
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
from scion_sdk.mcp.destructive import destructive_operation
|
|
10
|
+
|
|
9
11
|
from dockerhub_api.api.api_client_base import DockerHubApiBase
|
|
10
12
|
from dockerhub_api.dockerhub_input_models import (
|
|
11
13
|
GroupCreateModel,
|
|
@@ -99,9 +101,9 @@ class DockerHubApiGroups(DockerHubApiBase):
|
|
|
99
101
|
envelope["data"] = validate_lenient(Group, envelope["data"])
|
|
100
102
|
return envelope
|
|
101
103
|
|
|
104
|
+
@destructive_operation
|
|
102
105
|
def delete_group(self, org: str, group_name: str) -> dict[str, Any]:
|
|
103
106
|
"""Delete a group. Destructive — gated."""
|
|
104
|
-
self._guard_destructive("delete_group")
|
|
105
107
|
model = GroupModel(org=org, group_name=group_name)
|
|
106
108
|
return self._request(
|
|
107
109
|
"DELETE", f"/v2/orgs/{model.org}/groups/{model.group_name}"
|
|
@@ -146,11 +148,11 @@ class DockerHubApiGroups(DockerHubApiBase):
|
|
|
146
148
|
envelope["data"] = validate_lenient(OrgMember, envelope["data"])
|
|
147
149
|
return envelope
|
|
148
150
|
|
|
151
|
+
@destructive_operation
|
|
149
152
|
def remove_group_member(
|
|
150
153
|
self, org: str, group_name: str, username: str
|
|
151
154
|
) -> dict[str, Any]:
|
|
152
155
|
"""Remove a username from a group. Destructive — gated."""
|
|
153
|
-
self._guard_destructive("remove_group_member")
|
|
154
156
|
model = GroupMemberModel(org=org, group_name=group_name, username=username)
|
|
155
157
|
return self._request(
|
|
156
158
|
"DELETE",
|
{dockerhub_api-1.3.9 → dockerhub_api-1.4.0}/dockerhub_api/api/api_client_org_access_tokens.py
RENAMED
|
@@ -5,6 +5,8 @@ CONCEPT:DH-OS.identity.jwt-auth-lifecycle-endpoint — JWT auth lifecycle (OAT m
|
|
|
5
5
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
|
+
from scion_sdk.mcp.destructive import destructive_operation
|
|
9
|
+
|
|
8
10
|
from dockerhub_api.api.api_client_base import DockerHubApiBase
|
|
9
11
|
from dockerhub_api.dockerhub_input_models import (
|
|
10
12
|
OrgAccessTokenCreateModel,
|
|
@@ -97,9 +99,9 @@ class DockerHubApiOrgAccessTokens(DockerHubApiBase):
|
|
|
97
99
|
envelope["data"] = validate_lenient(OrgAccessToken, envelope["data"])
|
|
98
100
|
return envelope
|
|
99
101
|
|
|
102
|
+
@destructive_operation
|
|
100
103
|
def delete_org_access_token(self, org: str, token_id: str) -> dict[str, Any]:
|
|
101
104
|
"""Delete an organization access token. Destructive — gated."""
|
|
102
|
-
self._guard_destructive("delete_org_access_token")
|
|
103
105
|
model = OrgAccessTokenModel(org=org, token_id=token_id)
|
|
104
106
|
return self._request(
|
|
105
107
|
"DELETE", f"/v2/orgs/{model.org}/access-tokens/{model.token_id}"
|
|
@@ -7,6 +7,8 @@ org-settings writes).
|
|
|
7
7
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
from scion_sdk.mcp.destructive import destructive_operation
|
|
11
|
+
|
|
10
12
|
from dockerhub_api.api.api_client_base import DockerHubApiBase
|
|
11
13
|
from dockerhub_api.dockerhub_input_models import (
|
|
12
14
|
BulkInviteModel,
|
|
@@ -37,6 +39,7 @@ class DockerHubApiOrgs(DockerHubApiBase):
|
|
|
37
39
|
envelope["data"] = validate_lenient(OrgSettings, envelope["data"])
|
|
38
40
|
return envelope
|
|
39
41
|
|
|
42
|
+
@destructive_operation
|
|
40
43
|
def update_org_settings(
|
|
41
44
|
self,
|
|
42
45
|
org: str,
|
|
@@ -50,7 +53,6 @@ class DockerHubApiOrgs(DockerHubApiBase):
|
|
|
50
53
|
org-approved images, with carve-outs for Docker Official Images and
|
|
51
54
|
Verified Publisher images.
|
|
52
55
|
"""
|
|
53
|
-
self._guard_destructive("update_org_settings")
|
|
54
56
|
model = OrgSettingsModel(
|
|
55
57
|
org=org,
|
|
56
58
|
restricted_images_enabled=restricted_images_enabled,
|
|
@@ -104,9 +106,9 @@ class DockerHubApiOrgs(DockerHubApiBase):
|
|
|
104
106
|
envelope["data"] = validate_lenient(OrgMember, envelope["data"])
|
|
105
107
|
return envelope
|
|
106
108
|
|
|
109
|
+
@destructive_operation
|
|
107
110
|
def remove_org_member(self, org: str, username: str) -> dict[str, Any]:
|
|
108
111
|
"""Remove a member from the organization. Destructive — gated."""
|
|
109
|
-
self._guard_destructive("remove_org_member")
|
|
110
112
|
model = OrgMemberModel(org=org, username=username)
|
|
111
113
|
return self._request("DELETE", f"/v2/orgs/{model.org}/members/{model.username}")
|
|
112
114
|
|
|
@@ -118,9 +120,9 @@ class DockerHubApiOrgs(DockerHubApiBase):
|
|
|
118
120
|
envelope["data"] = validate_lenient(InvitePage, envelope["data"])
|
|
119
121
|
return envelope
|
|
120
122
|
|
|
123
|
+
@destructive_operation
|
|
121
124
|
def delete_invite(self, invite_id: str) -> dict[str, Any]:
|
|
122
125
|
"""Cancel an invite. Destructive — gated."""
|
|
123
|
-
self._guard_destructive("delete_invite")
|
|
124
126
|
model = InviteModel(invite_id=invite_id)
|
|
125
127
|
return self._request("DELETE", f"/v2/invites/{model.invite_id}")
|
|
126
128
|
|
|
@@ -6,8 +6,8 @@ multi-arch platform resolution, digest resolution, and (gated) deletes.
|
|
|
6
6
|
CONCEPT:DH-OS.governance.oci-referrers-attestation-discovery — OCI Referrers / attestation discovery.
|
|
7
7
|
CONCEPT:DH-OS.governance.chunked-blob-push-upload — chunked blob push (upload session + manifest put).
|
|
8
8
|
|
|
9
|
-
Pushes, deletes, and blob uploads are destructive
|
|
10
|
-
|
|
9
|
+
Pushes, deletes, and blob uploads are destructive: the SDK
|
|
10
|
+
asks the caller to confirm each. ``_catalog`` is
|
|
11
11
|
intentionally omitted: Docker Hub does not support registry-wide catalog
|
|
12
12
|
listing (it requires a registry-scoped token the Hub token service won't issue).
|
|
13
13
|
"""
|
|
@@ -15,6 +15,8 @@ listing (it requires a registry-scoped token the Hub token service won't issue).
|
|
|
15
15
|
import json as _json
|
|
16
16
|
from typing import Any
|
|
17
17
|
|
|
18
|
+
from scion_sdk.mcp.destructive import destructive_operation
|
|
19
|
+
|
|
18
20
|
from dockerhub_api.api.api_client_registry_base import (
|
|
19
21
|
ACCEPT_MANIFESTS,
|
|
20
22
|
OCTET_STREAM,
|
|
@@ -96,6 +98,7 @@ class RegistryApi(RegistryApiBase):
|
|
|
96
98
|
}
|
|
97
99
|
return envelope
|
|
98
100
|
|
|
101
|
+
@destructive_operation
|
|
99
102
|
def put_manifest(
|
|
100
103
|
self,
|
|
101
104
|
repo: str,
|
|
@@ -104,7 +107,6 @@ class RegistryApi(RegistryApiBase):
|
|
|
104
107
|
media_type: str,
|
|
105
108
|
) -> dict[str, Any]:
|
|
106
109
|
"""``PUT /v2/{repo}/manifests/{reference}`` — push a manifest (gated)."""
|
|
107
|
-
self._guard_destructive("put_manifest")
|
|
108
110
|
body = manifest if isinstance(manifest, str) else _json.dumps(manifest)
|
|
109
111
|
return self._registry_request(
|
|
110
112
|
"PUT",
|
|
@@ -115,13 +117,13 @@ class RegistryApi(RegistryApiBase):
|
|
|
115
117
|
content_type=media_type,
|
|
116
118
|
)
|
|
117
119
|
|
|
120
|
+
@destructive_operation
|
|
118
121
|
def delete_manifest(self, repo: str, reference: str) -> dict[str, Any]:
|
|
119
122
|
"""``DELETE /v2/{repo}/manifests/{digest}`` — delete a tag/manifest (gated).
|
|
120
123
|
|
|
121
124
|
Docker Hub requires deletion by digest; pass a tag and it is resolved
|
|
122
125
|
to its digest first.
|
|
123
126
|
"""
|
|
124
|
-
self._guard_destructive("delete_manifest")
|
|
125
127
|
target = reference
|
|
126
128
|
if not reference.startswith("sha256:"):
|
|
127
129
|
resolved = self.resolve_digest(repo, reference)
|
|
@@ -140,13 +142,13 @@ class RegistryApi(RegistryApiBase):
|
|
|
140
142
|
"""``HEAD /v2/{repo}/blobs/{digest}`` — blob existence check."""
|
|
141
143
|
return self._registry_exists(repo, f"/blobs/{digest}")
|
|
142
144
|
|
|
145
|
+
@destructive_operation
|
|
143
146
|
def delete_blob(self, repo: str, digest: str) -> dict[str, Any]:
|
|
144
147
|
"""``DELETE /v2/{repo}/blobs/{digest}`` — delete a blob (gated).
|
|
145
148
|
|
|
146
149
|
Often returns ``405`` on Docker Hub (blob delete disabled); the status
|
|
147
150
|
is surfaced cleanly rather than masked.
|
|
148
151
|
"""
|
|
149
|
-
self._guard_destructive("delete_blob")
|
|
150
152
|
return self._registry_request(
|
|
151
153
|
"DELETE",
|
|
152
154
|
repo,
|
|
@@ -293,13 +295,14 @@ class RegistryApi(RegistryApiBase):
|
|
|
293
295
|
|
|
294
296
|
# ------------------------------- push -------------------------------- #
|
|
295
297
|
|
|
298
|
+
@destructive_operation
|
|
296
299
|
def start_upload(self, repo: str) -> dict[str, Any]:
|
|
297
300
|
"""``POST /v2/{repo}/blobs/uploads/`` — open a blob upload session (gated)."""
|
|
298
|
-
self._guard_destructive("start_upload")
|
|
299
301
|
return self._registry_request(
|
|
300
302
|
"POST", repo, "/blobs/uploads/", scope_actions="pull,push"
|
|
301
303
|
)
|
|
302
304
|
|
|
305
|
+
@destructive_operation
|
|
303
306
|
def upload_chunk(
|
|
304
307
|
self,
|
|
305
308
|
repo: str,
|
|
@@ -308,7 +311,6 @@ class RegistryApi(RegistryApiBase):
|
|
|
308
311
|
content_range: str | None = None,
|
|
309
312
|
) -> dict[str, Any]:
|
|
310
313
|
"""``PATCH {location}`` — upload one blob chunk (gated)."""
|
|
311
|
-
self._guard_destructive("upload_chunk")
|
|
312
314
|
extra = {"Content-Range": content_range} if content_range else None
|
|
313
315
|
return self._registry_request(
|
|
314
316
|
"PATCH",
|
|
@@ -320,6 +322,7 @@ class RegistryApi(RegistryApiBase):
|
|
|
320
322
|
extra_headers=extra,
|
|
321
323
|
)
|
|
322
324
|
|
|
325
|
+
@destructive_operation
|
|
323
326
|
def complete_upload(
|
|
324
327
|
self,
|
|
325
328
|
repo: str,
|
|
@@ -328,7 +331,6 @@ class RegistryApi(RegistryApiBase):
|
|
|
328
331
|
final_chunk: bytes | None = None,
|
|
329
332
|
) -> dict[str, Any]:
|
|
330
333
|
"""``PUT {location}?digest=`` — finalize a blob upload (gated)."""
|
|
331
|
-
self._guard_destructive("complete_upload")
|
|
332
334
|
return self._registry_request(
|
|
333
335
|
"PUT",
|
|
334
336
|
repo,
|
|
@@ -339,9 +341,9 @@ class RegistryApi(RegistryApiBase):
|
|
|
339
341
|
content_type=OCTET_STREAM,
|
|
340
342
|
)
|
|
341
343
|
|
|
344
|
+
@destructive_operation
|
|
342
345
|
def mount_blob(self, repo: str, digest: str, from_repo: str) -> dict[str, Any]:
|
|
343
346
|
"""``POST /v2/{repo}/blobs/uploads/?mount=&from=`` — cross-repo mount (gated)."""
|
|
344
|
-
self._guard_destructive("mount_blob")
|
|
345
347
|
return self._registry_request(
|
|
346
348
|
"POST",
|
|
347
349
|
repo,
|
|
@@ -74,7 +74,6 @@ class RegistryApiBase(DockerHubApiBase):
|
|
|
74
74
|
registry_token_manager: Any | None = None,
|
|
75
75
|
tls_profile: ResolvedTLSProfile | None = None,
|
|
76
76
|
timeout: float | None = None,
|
|
77
|
-
allow_destructive: bool = False,
|
|
78
77
|
debug: bool = False,
|
|
79
78
|
transport: httpx.BaseTransport | None = None,
|
|
80
79
|
**kwargs: Any,
|
|
@@ -85,7 +84,6 @@ class RegistryApiBase(DockerHubApiBase):
|
|
|
85
84
|
url=url or DEFAULT_REGISTRY_URL,
|
|
86
85
|
tls_profile=tls_profile,
|
|
87
86
|
timeout=timeout if timeout is not None else 30.0,
|
|
88
|
-
allow_destructive=allow_destructive,
|
|
89
87
|
debug=debug,
|
|
90
88
|
transport=transport,
|
|
91
89
|
**kwargs,
|
|
@@ -339,12 +339,6 @@ def _resolve_tls_profile(
|
|
|
339
339
|
)
|
|
340
340
|
|
|
341
341
|
|
|
342
|
-
def _resolve_allow_destructive(allow_destructive: bool | None) -> bool:
|
|
343
|
-
if allow_destructive is None:
|
|
344
|
-
return setting("DOCKERHUB_ALLOW_DESTRUCTIVE", False)
|
|
345
|
-
return allow_destructive
|
|
346
|
-
|
|
347
|
-
|
|
348
342
|
@dataclass
|
|
349
343
|
class _HubCredentials:
|
|
350
344
|
url: str
|
|
@@ -407,7 +401,6 @@ def get_client(
|
|
|
407
401
|
token: str | None = None,
|
|
408
402
|
jwt: str | None = None,
|
|
409
403
|
tls_profile: ResolvedTLSProfile | None = None,
|
|
410
|
-
allow_destructive: bool | None = None,
|
|
411
404
|
config: dict | None = None,
|
|
412
405
|
) -> Any:
|
|
413
406
|
"""Factory for the Docker Hub :class:`~dockerhub_api.api_client.Api` client.
|
|
@@ -422,8 +415,8 @@ def get_client(
|
|
|
422
415
|
password, a PAT ``dckr_pat_*``, or an org token).
|
|
423
416
|
3. Anonymous — public, unauthenticated endpoints only.
|
|
424
417
|
|
|
425
|
-
|
|
426
|
-
|
|
418
|
+
Deletes and org-settings writes are declared destructive with the SDK and ask the caller to confirm.
|
|
419
|
+
CONCEPT:DH-OS.identity.destructive-action-gating-member — destructive-action gating.
|
|
427
420
|
|
|
428
421
|
A resolved ``username`` (personal account or org) the caller's identity is
|
|
429
422
|
not entitled to is denied before any credential exchange happens.
|
|
@@ -433,7 +426,6 @@ def get_client(
|
|
|
433
426
|
creds = _resolve_hub_credentials(url, username, token, jwt, config)
|
|
434
427
|
_deny_unless_entitled(creds.username)
|
|
435
428
|
profile = _resolve_tls_profile(tls_profile)
|
|
436
|
-
allow_destructive = _resolve_allow_destructive(allow_destructive)
|
|
437
429
|
|
|
438
430
|
if creds.jwt:
|
|
439
431
|
logger.info("Using pre-minted Docker Hub JWT")
|
|
@@ -441,7 +433,6 @@ def get_client(
|
|
|
441
433
|
url=creds.url,
|
|
442
434
|
token=creds.jwt,
|
|
443
435
|
tls_profile=profile,
|
|
444
|
-
allow_destructive=allow_destructive,
|
|
445
436
|
)
|
|
446
437
|
|
|
447
438
|
if creds.username and creds.token:
|
|
@@ -459,14 +450,13 @@ def get_client(
|
|
|
459
450
|
url=creds.url,
|
|
460
451
|
token_manager=manager,
|
|
461
452
|
tls_profile=profile,
|
|
462
|
-
allow_destructive=allow_destructive,
|
|
463
453
|
)
|
|
464
454
|
|
|
465
455
|
logger.warning(
|
|
466
456
|
"No Docker Hub credentials configured — anonymous client "
|
|
467
457
|
"(public endpoints only). Set DOCKERHUB_USERNAME and DOCKERHUB_TOKEN."
|
|
468
458
|
)
|
|
469
|
-
return Api(url=creds.url, tls_profile=profile
|
|
459
|
+
return Api(url=creds.url, tls_profile=profile)
|
|
470
460
|
|
|
471
461
|
|
|
472
462
|
def _resolve_credentials(
|
|
@@ -509,7 +499,6 @@ def get_registry_client(
|
|
|
509
499
|
username: str | None = None,
|
|
510
500
|
token: str | None = None,
|
|
511
501
|
tls_profile: ResolvedTLSProfile | None = None,
|
|
512
|
-
allow_destructive: bool | None = None,
|
|
513
502
|
config: dict | None = None,
|
|
514
503
|
transport: Any | None = None,
|
|
515
504
|
) -> Any:
|
|
@@ -519,7 +508,7 @@ def get_registry_client(
|
|
|
519
508
|
scoped tokens via :class:`RegistryTokenManager` using the same
|
|
520
509
|
``DOCKER_HUB_USER`` / ``DOCKER_HUB_TOKEN`` credentials (anonymous when
|
|
521
510
|
unset, which still works for public pulls). Pushes, deletes, and blob
|
|
522
|
-
uploads are
|
|
511
|
+
uploads are declared destructive with the SDK.
|
|
523
512
|
|
|
524
513
|
A resolved ``username`` (personal account or org) the caller's identity is
|
|
525
514
|
not entitled to is denied before any token is minted.
|
|
@@ -541,7 +530,6 @@ def get_registry_client(
|
|
|
541
530
|
username, token = _resolve_credentials(username, token, config)
|
|
542
531
|
_deny_unless_entitled(username)
|
|
543
532
|
profile = _resolve_tls_profile(tls_profile)
|
|
544
|
-
allow_destructive = _resolve_allow_destructive(allow_destructive)
|
|
545
533
|
|
|
546
534
|
token_manager = RegistryTokenManager(
|
|
547
535
|
username=username,
|
|
@@ -555,7 +543,6 @@ def get_registry_client(
|
|
|
555
543
|
url=url,
|
|
556
544
|
registry_token_manager=token_manager,
|
|
557
545
|
tls_profile=profile,
|
|
558
|
-
allow_destructive=allow_destructive,
|
|
559
546
|
transport=transport,
|
|
560
547
|
)
|
|
561
548
|
|