mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
feat(services): clone/move ops + owner-scoped registry credentials (C4)
Service lifecycle: POST /services/:id/clone duplicates config, volumes, domains, access gates, builder settings, and variables (secret ciphertext copied verbatim); POST /services/:id/move re-parents to another owned project. Both enforce ownership, reject name conflicts, and audit. Registries: CRUD at /registries — host-matched pull credentials for prebuilt-image deploys, encrypted at rest via secrets pkg, never returned (has_password flag). Empty password on update keeps the stored credential; host normalized (scheme/case/trailing slash). Credentials thread through BuildConfig → BuildRequest → pull auth config, used only for the pull. Surfaces: CLI services clone|move + registries group, 7 MCP tools, OpenAPI paths/schemas, frontend Lifecycle card (clone/move/delete) and Settings registries section, llms.txt + CLI.md. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
912f6f5142
commit
3664eaed1a
20 files changed
+1591
-13
No files matched your search
@@ -667,6 +667,167 @@ paths:
|
||||
'200':
|
||||
description: Reconciled runtime state
|
||||
|
||||
/services/{id}/clone:
|
||||
post:
|
||||
tags:
|
||||
- Services
|
||||
summary: Clone service
|
||||
description: Duplicates config, volumes, domains, access gates, builder settings, and variables into the same or another project. Containers are not cloned — the copy starts stopped.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Name for the clone (default "<name>-copy")
|
||||
project_id:
|
||||
type: string
|
||||
format: uuid
|
||||
description: Target project (default same project; caller must own it)
|
||||
environment:
|
||||
type: string
|
||||
enum: [production, preview, development]
|
||||
responses:
|
||||
'201':
|
||||
description: Clone created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
service_id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
message:
|
||||
type: string
|
||||
'409':
|
||||
description: Name already exists in target project
|
||||
|
||||
/services/{id}/move:
|
||||
post:
|
||||
tags:
|
||||
- Services
|
||||
summary: Move service
|
||||
description: Re-parents the service to another project the caller owns. Variables, domains, and volumes follow the service; running containers keep running and join the new project's network on next deploy.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [project_id]
|
||||
properties:
|
||||
project_id:
|
||||
type: string
|
||||
format: uuid
|
||||
responses:
|
||||
'200':
|
||||
description: Service moved
|
||||
'409':
|
||||
description: Name conflict in target project
|
||||
|
||||
/registries:
|
||||
get:
|
||||
tags:
|
||||
- Registries
|
||||
summary: List registry credentials
|
||||
description: Private-image pull credentials for the caller. Passwords are never returned — `has_password` signals presence.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Registry list
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
registries:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Registry'
|
||||
post:
|
||||
tags:
|
||||
- Registries
|
||||
summary: Add registry credential
|
||||
description: Credentials are matched by image host (e.g. ghcr.io, registry.example.com, docker.io for Docker Hub) on prebuilt-image deploys. Password stored encrypted at rest.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RegistryRequest'
|
||||
responses:
|
||||
'201':
|
||||
description: Registry created
|
||||
'409':
|
||||
description: A registry for this host already exists
|
||||
|
||||
/registries/{id}:
|
||||
put:
|
||||
tags:
|
||||
- Registries
|
||||
summary: Update registry credential
|
||||
description: Empty password keeps the stored credential — delete and recreate to remove a password.
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RegistryRequest'
|
||||
responses:
|
||||
'200':
|
||||
description: Registry updated
|
||||
'404':
|
||||
description: Registry not found
|
||||
delete:
|
||||
tags:
|
||||
- Registries
|
||||
summary: Remove registry credential
|
||||
security:
|
||||
- bearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'200':
|
||||
description: Registry removed
|
||||
'404':
|
||||
description: Registry not found
|
||||
|
||||
/services/{id}/domains:
|
||||
get:
|
||||
tags:
|
||||
@@ -5413,6 +5574,44 @@ components:
|
||||
type: string
|
||||
description: Plaintext on write — stored as bcrypt htpasswd; never returned
|
||||
|
||||
Registry:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
host:
|
||||
type: string
|
||||
description: Registry host matched against the image reference — ghcr.io, registry.example.com, docker.io for Docker Hub
|
||||
username:
|
||||
type: string
|
||||
has_password:
|
||||
type: boolean
|
||||
created_at:
|
||||
type: string
|
||||
format: date-time
|
||||
updated_at:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
RegistryRequest:
|
||||
type: object
|
||||
required:
|
||||
- name
|
||||
- host
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
host:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
description: Plaintext on write — stored encrypted; never returned. Empty on update keeps the stored credential.
|
||||
|
||||
ServiceVolume:
|
||||
type: object
|
||||
required:
|
||||
@@ -7732,5 +7931,7 @@ tags:
|
||||
description: Vulnerability scans and compliance reporting
|
||||
- name: Cron
|
||||
description: Scheduled jobs
|
||||
- name: Registries
|
||||
description: Private-image pull credentials
|
||||
- name: Gateway
|
||||
description: API gateway — upstream services, keys, analytics
|
||||
@@ -38,7 +38,9 @@ auth login|logout|status token auth and profiles
|
||||
tokens list|create|revoke personal access tokens
|
||||
projects list|get|create|delete
|
||||
services list|get|create|update|delete|start|stop|restart|redeploy
|
||||
services clone|move duplicate config or re-parent a service
|
||||
services domains list|add|remove|default|check multi-domain + DNS preflight
|
||||
registries list|add|update|remove private-image pull credentials
|
||||
volumes list|delete docker volume inventory + cleanup (admin)
|
||||
deploy <service-id> trigger a deployment (--no-cache, --commit-hash, --branch)
|
||||
deployments list|get|logs|rollback|cancel
|
||||
@@ -143,6 +145,30 @@ instance-level defaults used at service creation;
|
||||
`app_settings.capacity_policy=block` fails deployments whose requested
|
||||
memory exceeds node capacity (default `warn` logs only).
|
||||
|
||||
## Clone, move, registries
|
||||
|
||||
`services clone` duplicates config, volumes, domains, access gates,
|
||||
builder settings, and variables — ciphertext secret values are copied
|
||||
verbatim (no decrypt/re-encrypt). The clone starts stopped:
|
||||
|
||||
```bash
|
||||
containr services clone <id> --name staging-api --project <other-project-id> --environment preview
|
||||
containr services move <id> <project-id> # name conflicts → 409
|
||||
```
|
||||
|
||||
Note: cloned domains are verbatim copies — the same hostname attached to
|
||||
two services makes Traefik routing ambiguous. Re-domain or undeploy one.
|
||||
|
||||
`registries` stores private-image pull credentials, owner-scoped and
|
||||
encrypted at rest. Matched by image host on every image-sourced deploy:
|
||||
|
||||
```bash
|
||||
containr registries add ghcr ghcr.io --username <user> --password <pat>
|
||||
containr registries add hub docker.io --username <user> --password <pw> # Docker Hub
|
||||
containr registries update <id> --username <new-user> # empty --password keeps stored
|
||||
containr registries remove <id>
|
||||
```
|
||||
|
||||
## MCP server
|
||||
|
||||
`containr-mcp` (`go build ./cmd/mcp`) exposes the same API as MCP tools
|
||||
|
||||
Reference in new issue
Block a user