Files
docker/readme.hints.md

195 lines
6.8 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Docker Module - Development Hints
## getContainerById() Bug Fix (2025-11-24 - v5.0.1)
### Problem
The `getContainerById()` method had a critical bug where it would create a DockerContainer object from Docker API error responses when a container didn't exist.
**Symptoms:**
- Calling `docker.getContainerById('invalid-id')` returned a DockerContainer object with `{ message: "No such container: invalid-id" }`
- Calling `.logs()` on this invalid container returned "[object Object]" instead of logs or throwing an error
- No way to detect the error state without checking for a `.message` property
**Root Cause:**
The `DockerContainer._fromId()` method made a direct API call to `/containers/{id}/json` and blindly passed `response.body` to the constructor, even when the API returned a 404 error response.
### Solution
Changed `DockerContainer._fromId()` to use the **list+filter pattern**, matching the behavior of all other resource getter methods (DockerImage, DockerNetwork, DockerService, DockerSecret):
```typescript
// Before (buggy):
public static async _fromId(dockerHostArg: DockerHost, containerId: string): Promise<DockerContainer> {
const response = await dockerHostArg.request('GET', `/containers/${containerId}/json`);
return new DockerContainer(dockerHostArg, response.body); // Creates invalid object from error!
}
// After (fixed):
public static async _fromId(dockerHostArg: DockerHost, containerId: string): Promise<DockerContainer | undefined> {
const containers = await this._list(dockerHostArg);
return containers.find((container) => container.Id === containerId); // Returns undefined if not found
}
```
**Benefits:**
- 100% consistent with all other resource classes
- Type-safe return signature: `Promise<DockerContainer | undefined>`
- Cannot create invalid objects - `.find()` naturally returns undefined
- Users can now properly check for non-existent containers
**Usage:**
```typescript
const container = await docker.getContainerById('abc123');
if (container) {
const logs = await container.logs();
console.log(logs);
} else {
console.log('Container not found');
}
```
## OOP Refactoring - Clean Architecture (2025-11-24)
### Architecture Changes
The module has been restructured to follow a clean OOP Facade pattern:
- **DockerHost** is now the single entry point for all Docker operations
- All resource classes extend abstract `DockerResource` base class
- Static methods are prefixed with `_` to indicate internal use
- Public API is exclusively through DockerHost methods
### Key Changes
**1. Factory Pattern**
- All resource creation/retrieval goes through DockerHost:
```typescript
// Old (deprecated):
const container = await DockerContainer.getContainers(dockerHost);
const network = await DockerNetwork.createNetwork(dockerHost, descriptor);
// New (clean API):
const containers = await dockerHost.listContainers();
const network = await dockerHost.createNetwork(descriptor);
```
**2. Container Management Methods Added**
The DockerContainer class now has full CRUD and streaming operations:
**Lifecycle:**
- `container.start()` - Start container
- `container.stop(options?)` - Stop container
- `container.remove(options?)` - Remove container
- `container.refresh()` - Reload state
**Information:**
- `container.inspect()` - Get detailed info
- `container.logs(options)` - Get logs as string (one-shot)
- `container.stats(options)` - Get stats
**Streaming & Interactive:**
- `container.streamLogs(options)` - Stream logs continuously (follow mode)
- `container.attach(options)` - Attach to main process (PID 1) with bidirectional stream
- `container.exec(command, options)` - Execute commands in container interactively
**Example - Stream Logs:**
```typescript
const container = await dockerHost.getContainerById('abc123');
const logStream = await container.streamLogs({ timestamps: true });
logStream.on('data', (chunk) => {
console.log(chunk.toString());
});
```
**Example - Attach to Container:**
```typescript
const { stream, close } = await container.attach({
stdin: true,
stdout: true,
stderr: true
});
// Pipe to/from process
process.stdin.pipe(stream);
stream.pipe(process.stdout);
// Later: detach
await close();
```
**Example - Execute Command:**
```typescript
const { stream, close } = await container.exec('ls -la /app', {
tty: true
});
stream.on('data', (chunk) => {
console.log(chunk.toString());
});
stream.on('end', async () => {
await close();
});
```
**3. DockerResource Base Class**
All resource classes now extend `DockerResource`:
- Consistent `dockerHost` property (not `dockerHostRef`)
- Required `refresh()` method
- Standardized constructor pattern
**4. ImageStore Encapsulation**
- `dockerHost.imageStore` is now private
- Use `dockerHost.storeImage(name, stream)` instead
- Use `dockerHost.retrieveImage(name)` instead
**5. Creation Descriptors Support Both Primitives and Instances**
Interfaces now accept both strings and class instances:
```typescript
// Both work:
await dockerHost.createService({
image: 'nginx:latest', // String
networks: ['my-network'], // String array
secrets: ['my-secret'] // String array
});
await dockerHost.createService({
image: imageInstance, // DockerImage instance
networks: [networkInstance], // DockerNetwork array
secrets: [secretInstance] // DockerSecret array
});
```
### Migration Guide
Replace all static method calls with dockerHost methods:
- `DockerContainer.getContainers(host)` → `dockerHost.listContainers()`
- `DockerImage.createFromRegistry(host, opts)` → `dockerHost.createImageFromRegistry(opts)`
- `DockerService.createService(host, desc)` → `dockerHost.createService(desc)`
- `dockerHost.imageStore.storeImage(...)` → `dockerHost.storeImage(...)`
## smartrequest v5+ Migration (2025-11-17)
### Breaking Change
smartrequest v5.0.0+ returns web `ReadableStream` objects (Web Streams API) instead of Node.js streams.
### Solution Implemented
All streaming methods now convert web ReadableStreams to Node.js streams using:
```typescript
plugins.smartstream.nodewebhelpers.convertWebReadableToNodeReadable(webStream)
```
### Files Modified
- `ts/classes.host.ts`:
- `requestStreaming()` - Converts web stream to Node.js stream before returning
- `getEventObservable()` - Works with converted Node.js stream
- `ts/classes.image.ts`:
- `createFromTarStream()` - Uses converted Node.js stream for event handling
- `exportToTarStream()` - Uses converted Node.js stream for backpressure management
### Testing
- Build:  All 11 type errors resolved
- Tests:  Node.js tests pass (DockerHost, DockerContainer, DockerImage, DockerImageStore)
### Notes
- The conversion maintains backward compatibility with existing code expecting Node.js stream methods (`.on()`, `.emit()`, `.pause()`, `.resume()`)
- smartstream's `nodewebhelpers` module provides bidirectional conversion utilities between web and Node.js streams