BREAKING CHANGE(DockerHost): Refactor public API to DockerHost facade; introduce DockerResource base; make resource static methods internal; support flexible descriptors and stream compatibility

This commit is contained in:
2025-11-24 12:20:30 +00:00
parent cc9c20882e
commit 6fe70e0a1d
16 changed files with 1388 additions and 335 deletions

View File

@@ -33,7 +33,7 @@ tap.test('should list networks', async () => {
});
tap.test('should create a network', async () => {
const newNetwork = await docker.DockerNetwork.createNetwork(testDockerHost, {
const newNetwork = await testDockerHost.createNetwork({
Name: 'webgateway',
});
expect(newNetwork).toBeInstanceOf(docker.DockerNetwork);
@@ -41,20 +41,15 @@ tap.test('should create a network', async () => {
});
tap.test('should remove a network', async () => {
const webgateway = await docker.DockerNetwork.getNetworkByName(
testDockerHost,
'webgateway',
);
const webgateway = await testDockerHost.getNetworkByName('webgateway');
await webgateway.remove();
});
// Images
tap.test('should pull an image from imagetag', async () => {
const image = await docker.DockerImage.createFromRegistry(testDockerHost, {
creationObject: {
imageUrl: 'hosttoday/ht-docker-node',
imageTag: 'alpine',
},
const image = await testDockerHost.createImageFromRegistry({
imageUrl: 'hosttoday/ht-docker-node',
imageTag: 'alpine',
});
expect(image).toBeInstanceOf(docker.DockerImage);
console.log(image);
@@ -71,7 +66,7 @@ tap.test('should return a change Observable', async (tools) => {
// SECRETS
tap.test('should create a secret', async () => {
const mySecret = await docker.DockerSecret.createSecret(testDockerHost, {
const mySecret = await testDockerHost.createSecret({
name: 'testSecret',
version: '1.0.3',
contentArg: `{ "hi": "wow"}`,
@@ -81,10 +76,7 @@ tap.test('should create a secret', async () => {
});
tap.test('should remove a secret by name', async () => {
const mySecret = await docker.DockerSecret.getSecretByName(
testDockerHost,
'testSecret',
);
const mySecret = await testDockerHost.getSecretByName('testSecret');
await mySecret.remove();
});
@@ -99,24 +91,19 @@ tap.test('should list all services', async (tools) => {
});
tap.test('should create a service', async () => {
const testNetwork = await docker.DockerNetwork.createNetwork(testDockerHost, {
const testNetwork = await testDockerHost.createNetwork({
Name: 'testNetwork',
});
const testSecret = await docker.DockerSecret.createSecret(testDockerHost, {
const testSecret = await testDockerHost.createSecret({
name: 'testSecret',
version: '0.0.1',
labels: {},
contentArg: '{"hi": "wow"}',
});
const testImage = await docker.DockerImage.createFromRegistry(
testDockerHost,
{
creationObject: {
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
},
},
);
const testService = await docker.DockerService.createService(testDockerHost, {
const testImage = await testDockerHost.createImageFromRegistry({
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
});
const testService = await testDockerHost.createService({
image: testImage,
labels: {},
name: 'testService',
@@ -133,14 +120,9 @@ tap.test('should create a service', async () => {
tap.test('should export images', async (toolsArg) => {
const done = toolsArg.defer();
const testImage = await docker.DockerImage.createFromRegistry(
testDockerHost,
{
creationObject: {
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
},
},
);
const testImage = await testDockerHost.createImageFromRegistry({
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
});
const fsWriteStream = plugins.smartfile.fsStream.createWriteStream(
plugins.path.join(paths.nogitDir, 'testimage.tar'),
);
@@ -155,13 +137,10 @@ tap.test('should import images', async () => {
const fsReadStream = plugins.smartfile.fsStream.createReadStream(
plugins.path.join(paths.nogitDir, 'testimage.tar'),
);
const importedImage = await docker.DockerImage.createFromTarStream(
testDockerHost,
const importedImage = await testDockerHost.createImageFromTarStream(
fsReadStream,
{
tarStream: fsReadStream,
creationObject: {
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
},
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
},
);
expect(importedImage).toBeInstanceOf(docker.DockerImage);
@@ -177,8 +156,8 @@ tap.test('should expose a working DockerImageStore', async () => {
};
await testDockerHost.addS3Storage(s3Descriptor);
//
await testDockerHost.imageStore.storeImage(
// Use the new public API instead of direct imageStore access
await testDockerHost.storeImage(
'hello2',
plugins.smartfile.fsStream.createReadStream(
plugins.path.join(paths.nogitDir, 'testimage.tar'),
@@ -186,6 +165,143 @@ tap.test('should expose a working DockerImageStore', async () => {
);
});
// CONTAINER STREAMING FEATURES
let testContainer: docker.DockerContainer;
tap.test('should get an existing container for streaming tests', async () => {
const containers = await testDockerHost.getContainers();
// Use the first running container we find
testContainer = containers.find((c) => c.State === 'running');
if (!testContainer) {
throw new Error('No running containers found for streaming tests');
}
expect(testContainer).toBeInstanceOf(docker.DockerContainer);
console.log('Using existing container for tests:', testContainer.Names[0], testContainer.Id);
});
tap.test('should stream container logs', async (tools) => {
const done = tools.defer();
const logStream = await testContainer.streamLogs({
stdout: true,
stderr: true,
timestamps: true,
});
let receivedData = false;
logStream.on('data', (chunk) => {
console.log('Received log chunk:', chunk.toString().slice(0, 100));
receivedData = true;
});
logStream.on('error', (error) => {
console.error('Stream error:', error);
done.resolve();
});
// Wait for 2 seconds to collect logs, then close
await tools.delayFor(2000);
logStream.destroy();
done.resolve();
await done.promise;
console.log('Log streaming test completed. Received data:', receivedData);
});
tap.test('should get container logs (one-shot)', async () => {
const logs = await testContainer.logs({
stdout: true,
stderr: true,
tail: 10,
});
expect(typeof logs).toEqual('string');
console.log('Container logs (last 10 lines):', logs.slice(0, 200));
});
tap.test('should execute command in container', async (tools) => {
const done = tools.defer();
const { stream, close } = await testContainer.exec('echo "Hello from exec"', {
tty: false,
attachStdout: true,
attachStderr: true,
});
let output = '';
stream.on('data', (chunk) => {
output += chunk.toString();
console.log('Exec output:', chunk.toString());
});
stream.on('end', async () => {
await close();
console.log('Exec completed. Full output:', output);
done.resolve();
});
stream.on('error', async (error) => {
console.error('Exec error:', error);
await close();
done.resolve();
});
await done.promise;
expect(output.length).toBeGreaterThan(0);
});
tap.test('should attach to container', async (tools) => {
const done = tools.defer();
const { stream, close } = await testContainer.attach({
stream: true,
stdout: true,
stderr: true,
stdin: false,
});
let receivedData = false;
stream.on('data', (chunk) => {
console.log('Attach received:', chunk.toString().slice(0, 100));
receivedData = true;
});
stream.on('error', async (error) => {
console.error('Attach error:', error);
await close();
done.resolve();
});
// Monitor for 2 seconds then detach
await tools.delayFor(2000);
await close();
done.resolve();
await done.promise;
console.log('Attach test completed. Received data:', receivedData);
});
tap.test('should get container stats', async () => {
const stats = await testContainer.stats({ stream: false });
expect(stats).toBeInstanceOf(Object);
console.log('Container stats keys:', Object.keys(stats));
});
tap.test('should inspect container', async () => {
const inspection = await testContainer.inspect();
expect(inspection).toBeInstanceOf(Object);
expect(inspection.Id).toEqual(testContainer.Id);
console.log('Container state:', inspection.State?.Status);
});
tap.test('should complete container tests', async () => {
// Using existing container, no cleanup needed
console.log('Container streaming tests completed');
});
tap.test('cleanup', async () => {
await testDockerHost.stop();
});