feat(platform-services): Add platform service log streaming, improve health checks and provisioning robustness
This commit is contained in:
@@ -78,9 +78,26 @@ export class OneboxDockerManager {
|
||||
* Pull an image from a registry
|
||||
*/
|
||||
async pullImage(image: string, registry?: string): Promise<void> {
|
||||
// Skip manual image pulling - Docker will automatically pull when creating container
|
||||
const fullImage = registry ? `${registry}/${image}` : image;
|
||||
logger.debug(`Skipping manual pull for ${fullImage} - Docker will auto-pull on container creation`);
|
||||
logger.info(`Pulling image: ${fullImage}`);
|
||||
|
||||
try {
|
||||
// Parse image name and tag (e.g., "nginx:alpine" -> imageUrl: "nginx", imageTag: "alpine")
|
||||
const [imageUrl, imageTag] = fullImage.includes(':')
|
||||
? fullImage.split(':')
|
||||
: [fullImage, 'latest'];
|
||||
|
||||
// Use the library's built-in createImageFromRegistry method
|
||||
await this.dockerClient!.createImageFromRegistry({
|
||||
imageUrl,
|
||||
imageTag,
|
||||
});
|
||||
|
||||
logger.success(`Image pulled successfully: ${fullImage}`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to pull image ${fullImage}: ${getErrorMessage(error)}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -796,6 +813,34 @@ export class OneboxDockerManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get host port binding for a container's exposed port
|
||||
* @returns The host port number, or null if not bound
|
||||
*/
|
||||
async getContainerHostPort(containerID: string, containerPort: number): Promise<number | null> {
|
||||
try {
|
||||
const container = await this.dockerClient!.getContainerById(containerID);
|
||||
|
||||
if (!container) {
|
||||
throw new Error(`Container not found: ${containerID}`);
|
||||
}
|
||||
|
||||
const info = await container.inspect();
|
||||
|
||||
const portKey = `${containerPort}/tcp`;
|
||||
const bindings = info.NetworkSettings.Ports?.[portKey];
|
||||
|
||||
if (bindings && bindings.length > 0 && bindings[0].HostPort) {
|
||||
return parseInt(bindings[0].HostPort, 10);
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
logger.error(`Failed to get container host port ${containerID}:${containerPort}: ${getErrorMessage(error)}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a command in a running container
|
||||
*/
|
||||
@@ -829,8 +874,11 @@ export class OneboxDockerManager {
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for completion
|
||||
await new Promise((resolve) => stream.on('end', resolve));
|
||||
// Wait for completion with timeout
|
||||
await Promise.race([
|
||||
new Promise<void>((resolve) => stream.on('end', resolve)),
|
||||
new Promise<void>((_, reject) => setTimeout(() => reject(new Error('Exec timeout after 30s')), 30000))
|
||||
]);
|
||||
|
||||
const execInfo = await inspect();
|
||||
const exitCode = execInfo.ExitCode || 0;
|
||||
@@ -859,6 +907,10 @@ export class OneboxDockerManager {
|
||||
try {
|
||||
logger.info(`Creating platform container: ${options.name}`);
|
||||
|
||||
// Pull the image first to ensure it's available
|
||||
logger.info(`Pulling image for platform service: ${options.image}`);
|
||||
await this.pullImage(options.image);
|
||||
|
||||
// Check if container already exists
|
||||
const existingContainers = await this.dockerClient!.listContainers();
|
||||
const existing = existingContainers.find((c: any) =>
|
||||
@@ -877,8 +929,8 @@ export class OneboxDockerManager {
|
||||
const portsToExpose = options.exposePorts || [options.port];
|
||||
for (const port of portsToExpose) {
|
||||
exposedPorts[`${port}/tcp`] = {};
|
||||
// Don't bind to host ports by default - services communicate via Docker network
|
||||
portBindings[`${port}/tcp`] = [];
|
||||
// Bind to random host port so we can access from host (for provisioning)
|
||||
portBindings[`${port}/tcp`] = [{ HostIp: '127.0.0.1', HostPort: '' }];
|
||||
}
|
||||
|
||||
// Prepare volume bindings
|
||||
|
||||
Reference in New Issue
Block a user