feat(tests): Add comprehensive tests for Docker image export and streaming functionality
This commit is contained in:
@@ -262,12 +262,19 @@ export class DockerHost {
|
||||
// Parse the response body based on content type
|
||||
let body;
|
||||
const contentType = response.headers['content-type'] || '';
|
||||
if (contentType.includes('application/json')) {
|
||||
|
||||
// Docker's streaming endpoints (like /images/create) return newline-delimited JSON
|
||||
// which can't be parsed as a single JSON object
|
||||
const isStreamingEndpoint = routeArg.includes('/images/create') ||
|
||||
routeArg.includes('/images/load') ||
|
||||
routeArg.includes('/build');
|
||||
|
||||
if (contentType.includes('application/json') && !isStreamingEndpoint) {
|
||||
body = await response.json();
|
||||
} else {
|
||||
body = await response.text();
|
||||
// Try to parse as JSON if it looks like JSON
|
||||
if (body && (body.startsWith('{') || body.startsWith('['))) {
|
||||
// Try to parse as JSON if it looks like JSON and is not a streaming response
|
||||
if (!isStreamingEndpoint && body && (body.startsWith('{') || body.startsWith('['))) {
|
||||
try {
|
||||
body = JSON.parse(body);
|
||||
} catch {
|
||||
@@ -299,7 +306,8 @@ export class DockerHost {
|
||||
.header('Content-Type', 'application/json')
|
||||
.header('X-Registry-Auth', this.registryToken)
|
||||
.header('Host', 'docker.sock')
|
||||
.options({ keepAlive: false });
|
||||
.timeout(600000) // Set 10 minute timeout for streaming operations
|
||||
.options({ keepAlive: false, autoDrain: false }); // Disable auto-drain for streaming
|
||||
|
||||
// If we have a readStream, use the new stream method with logging
|
||||
if (readStream) {
|
||||
|
@@ -250,6 +250,12 @@ export class DockerImage {
|
||||
public async exportToTarStream(): Promise<plugins.smartstream.stream.Readable> {
|
||||
logger.log('info', `Exporting image ${this.RepoTags[0]} to tar stream.`);
|
||||
const response = await this.dockerHost.requestStreaming('GET', `/images/${encodeURIComponent(this.RepoTags[0])}/get`);
|
||||
|
||||
// Check if response is a Node.js stream
|
||||
if (!response || typeof response.on !== 'function') {
|
||||
throw new Error('Failed to get streaming response for image export');
|
||||
}
|
||||
|
||||
let counter = 0;
|
||||
const webduplexStream = new plugins.smartstream.SmartDuplex({
|
||||
writeFunction: async (chunk, tools) => {
|
||||
@@ -259,17 +265,25 @@ export class DockerImage {
|
||||
return chunk;
|
||||
}
|
||||
});
|
||||
|
||||
response.on('data', (chunk) => {
|
||||
if (!webduplexStream.write(chunk)) {
|
||||
response.pause();
|
||||
webduplexStream.once('drain', () => {
|
||||
response.resume();
|
||||
})
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
response.on('end', () => {
|
||||
webduplexStream.end();
|
||||
})
|
||||
});
|
||||
|
||||
response.on('error', (error) => {
|
||||
logger.log('error', `Error during image export: ${error.message}`);
|
||||
webduplexStream.destroy(error);
|
||||
});
|
||||
|
||||
return webduplexStream;
|
||||
}
|
||||
}
|
||||
|
@@ -89,6 +89,11 @@ export class DockerService {
|
||||
}> = [];
|
||||
|
||||
for (const network of serviceCreationDescriptor.networks) {
|
||||
// Skip null networks (can happen if network creation fails)
|
||||
if (!network) {
|
||||
logger.log('warn', 'Skipping null network in service creation');
|
||||
continue;
|
||||
}
|
||||
networkArray.push({
|
||||
Target: network.Name,
|
||||
Aliases: [serviceCreationDescriptor.networkAlias],
|
||||
|
Reference in New Issue
Block a user