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:
@@ -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();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user