feat(docker-watcher): phases 3+4 - Docker client & NPM client
Phase 3: Docker Engine API wrapper — pull/inspect images, container lifecycle (create/start/stop/remove/restart), network management, label-based container tracking, deterministic naming. Phase 4: Nginx Proxy Manager API client — JWT auth with auto-refresh, CRUD for proxy hosts, domain-based host lookup.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Phase 3: Docker Client
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Status:** :white_check_mark: Complete
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
@@ -9,16 +9,16 @@ Implement the Docker Engine API wrapper for container lifecycle management — p
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Create Docker client wrapper with socket connection (`/var/run/docker.sock`)
|
||||
- [ ] Task 2: Implement `PullImage(ctx, image, tag, authConfig)` — pull with optional registry auth
|
||||
- [ ] Task 3: Implement `InspectImage(ctx, image)` — extract EXPOSE ports, HEALTHCHECK, labels
|
||||
- [ ] Task 4: Implement `CreateContainer(ctx, config)` — create with name, image, env, ports, network, labels
|
||||
- [ ] Task 5: Implement `StartContainer(ctx, containerID)`, `StopContainer(ctx, containerID, timeout)`, `RemoveContainer(ctx, containerID, force)`
|
||||
- [ ] Task 6: Implement `RestartContainer(ctx, containerID, timeout)`
|
||||
- [ ] Task 7: Implement `ListContainers(ctx, filters)` — filter by labels to find managed containers
|
||||
- [ ] Task 8: Implement `EnsureNetwork(ctx, networkName)` — create network if not exists
|
||||
- [ ] Task 9: Implement `ConnectNetwork(ctx, networkID, containerID)` — attach container to network
|
||||
- [ ] Task 10: Add docker-watcher labels to all managed containers (`docker-watcher.project`, `docker-watcher.stage`, `docker-watcher.instance-id`)
|
||||
- [x] Task 1: Create Docker client wrapper with socket connection (`/var/run/docker.sock`)
|
||||
- [x] Task 2: Implement `PullImage(ctx, image, tag, authConfig)` — pull with optional registry auth
|
||||
- [x] Task 3: Implement `InspectImage(ctx, image)` — extract EXPOSE ports, HEALTHCHECK, labels
|
||||
- [x] Task 4: Implement `CreateContainer(ctx, config)` — create with name, image, env, ports, network, labels
|
||||
- [x] Task 5: Implement `StartContainer(ctx, containerID)`, `StopContainer(ctx, containerID, timeout)`, `RemoveContainer(ctx, containerID, force)`
|
||||
- [x] Task 6: Implement `RestartContainer(ctx, containerID, timeout)`
|
||||
- [x] Task 7: Implement `ListContainers(ctx, filters)` — filter by labels to find managed containers
|
||||
- [x] Task 8: Implement `EnsureNetwork(ctx, networkName)` — create network if not exists
|
||||
- [x] Task 9: Implement `ConnectNetwork(ctx, networkID, containerID)` — attach container to network
|
||||
- [x] Task 10: Add docker-watcher labels to all managed containers (`docker-watcher.project`, `docker-watcher.stage`, `docker-watcher.instance-id`)
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/docker/client.go` — Docker client wrapper, connection setup
|
||||
@@ -42,11 +42,57 @@ Implement the Docker Engine API wrapper for container lifecycle management — p
|
||||
- Auth config for private registries will come from the store (encrypted tokens)
|
||||
|
||||
## Review Checklist
|
||||
- [ ] All tasks completed
|
||||
- [ ] Proper context propagation for cancellation
|
||||
- [ ] Resource cleanup (close client, remove failed containers)
|
||||
- [ ] No hardcoded values
|
||||
- [ ] Error messages include container/image identifiers
|
||||
- [x] All tasks completed
|
||||
- [x] Proper context propagation for cancellation
|
||||
- [x] Resource cleanup (close client, remove failed containers)
|
||||
- [x] No hardcoded values
|
||||
- [x] Error messages include container/image identifiers
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
|
||||
### Exported API surface (`internal/docker` package)
|
||||
|
||||
**Client lifecycle:**
|
||||
- `docker.New() (*Client, error)` — creates client with env-based config and API version negotiation
|
||||
- `(*Client).Close() error` — releases resources
|
||||
- `(*Client).Ping(ctx) error` — checks daemon connectivity
|
||||
|
||||
**Image operations (`image.go`):**
|
||||
- `(*Client).PullImage(ctx, imageRef, tag, authConfig) error` — pulls image; authConfig is base64-encoded JSON (use `EncodeRegistryAuth` helper)
|
||||
- `(*Client).InspectImage(ctx, imageRef) (ImageInfo, error)` — returns `ImageInfo{ExposedPorts, Healthcheck, Labels}`
|
||||
- `docker.EncodeRegistryAuth(username, password, serverAddress) (string, error)` — builds auth payload for `PullImage`
|
||||
|
||||
**Container operations (`container.go`):**
|
||||
- `(*Client).CreateContainer(ctx, ContainerConfig) (containerID string, error)` — creates container with labels, env, ports, network
|
||||
- `(*Client).StartContainer(ctx, containerID) error`
|
||||
- `(*Client).StopContainer(ctx, containerID, timeoutSeconds) error`
|
||||
- `(*Client).RemoveContainer(ctx, containerID, force) error`
|
||||
- `(*Client).RestartContainer(ctx, containerID, timeoutSeconds) error`
|
||||
- `(*Client).ListContainers(ctx, labelFilters) ([]ManagedContainer, error)` — always scoped to docker-watcher labels
|
||||
- `(*Client).InspectContainerPort(ctx, containerID, containerPort) (uint16, error)` — gets auto-assigned host port
|
||||
- `docker.ContainerName(project, stage, tag) string` — deterministic name: `dw-{project}-{stage}-{tag-sanitized}`
|
||||
|
||||
**Network operations (`network.go`):**
|
||||
- `(*Client).EnsureNetwork(ctx, networkName) (networkID string, error)` — idempotent create-if-not-exists
|
||||
- `(*Client).ConnectNetwork(ctx, networkID, containerID) error`
|
||||
|
||||
**Label constants:**
|
||||
- `docker.LabelProject` = `"docker-watcher.project"`
|
||||
- `docker.LabelStage` = `"docker-watcher.stage"`
|
||||
- `docker.LabelInstanceID` = `"docker-watcher.instance-id"`
|
||||
|
||||
**Key types:**
|
||||
- `docker.ContainerConfig` — input for `CreateContainer` (Name, Image, Env, ExposedPorts, NetworkName, NetworkID, Labels, Project, Stage, InstanceID)
|
||||
- `docker.ImageInfo` — output of `InspectImage` (ExposedPorts, Healthcheck, Labels)
|
||||
- `docker.ManagedContainer` — output of `ListContainers` (ID, Name, Image, Status, State, Project, Stage, InstanceID, Ports)
|
||||
|
||||
### Dependencies added
|
||||
- `github.com/docker/docker v27.5.1+incompatible`
|
||||
- `github.com/docker/go-connections v0.5.0`
|
||||
- Run `go mod tidy` to resolve transitive dependencies before building
|
||||
|
||||
### Conventions maintained
|
||||
- `context.Context` as first parameter on all methods
|
||||
- Errors wrapped with `fmt.Errorf("context: %w", err)`
|
||||
- Package-level constants for labels
|
||||
- Immutable patterns (new maps created rather than mutating input)
|
||||
|
||||
Reference in New Issue
Block a user