init project

This commit is contained in:
root
2026-07-31 13:12:54 -04:00
parent 0da92d5e02
commit f3863f760c
7215 changed files with 1860260 additions and 1 deletions
+242
View File
@@ -0,0 +1,242 @@
var protobuf = require("protobufjs");
var path = require("path");
// Constants
var BUILDKIT_TRACE_ID = "moby.buildkit.trace";
var BUILDKIT_IMAGE_ID = "moby.image.id";
var PROTO_TYPE = "moby.buildkit.v1.StatusResponse";
var ENCODING_UTF8 = "utf8";
var ENCODING_BASE64 = "base64";
var StatusResponse;
// Load the protobuf schema
function loadProto() {
if (StatusResponse) return StatusResponse;
var root = protobuf.loadSync(
path.resolve(__dirname, "proto", "buildkit_status.proto")
);
StatusResponse = root.lookupType(PROTO_TYPE);
return StatusResponse;
}
/**
* Decodes a BuildKit trace message
* @param {string} base64Data - Base64-encoded protobuf data from aux field
* @returns {Object} Decoded status response with vertexes, logs, etc.
*/
function decodeBuildKitStatus(base64Data) {
var StatusResponse = loadProto();
// Handle empty messages
if (!base64Data || base64Data.length === 0) {
return {
vertexes: [],
statuses: [],
logs: [],
warnings: []
};
}
var buffer = Buffer.from(base64Data, ENCODING_BASE64);
var message = StatusResponse.decode(buffer);
return StatusResponse.toObject(message, {
longs: String,
enums: String,
bytes: String,
defaults: true
});
}
/**
* Formats BuildKit status into human-readable text
* @param {Object} status - Decoded status response
* @returns {string[]} Array of human-readable log lines
*/
function formatBuildKitStatus(status) {
var lines = [];
// Process vertexes (build steps)
if (status.vertexes && status.vertexes.length > 0) {
status.vertexes.forEach(function(vertex) {
if (vertex.name && vertex.started && !vertex.completed) {
lines.push("[" + vertex.digest.substring(0, 12) + "] " + vertex.name);
}
if (vertex.error) {
lines.push("ERROR: " + vertex.error);
}
if (vertex.completed && vertex.cached) {
lines.push("CACHED: " + vertex.name);
}
});
}
// Process logs (command output)
if (status.logs && status.logs.length > 0) {
status.logs.forEach(function(log) {
var msg = Buffer.from(log.msg).toString(ENCODING_UTF8);
if (msg.trim()) {
lines.push(msg.trimEnd());
}
});
}
// Process status updates (progress)
if (status.statuses && status.statuses.length > 0) {
status.statuses.forEach(function(s) {
if (s.name && s.total > 0) {
var percent = Math.floor((s.current / s.total) * 100);
lines.push(s.name + ": " + percent + "% (" + s.current + "/" + s.total + ")");
}
});
}
// Process warnings
if (status.warnings && status.warnings.length > 0) {
status.warnings.forEach(function(warning) {
var msg = Buffer.from(warning.short).toString(ENCODING_UTF8);
lines.push("WARNING: " + msg);
});
}
return lines;
}
/**
* Parse a BuildKit stream line and extract human-readable logs
* @param {string} line - JSON line from build stream
* @returns {Object} { isBuildKit: boolean, logs: string[], raw: Object }
*/
function parseBuildKitLine(line) {
try {
var json = JSON.parse(line);
// Check if it's a BuildKit trace message
if (json.id === BUILDKIT_TRACE_ID && json.aux !== undefined) {
var status = decodeBuildKitStatus(json.aux);
var logs = formatBuildKitStatus(status);
return {
isBuildKit: true,
logs: logs,
raw: status
};
}
// Check if it's the final image ID
if (json.id === BUILDKIT_IMAGE_ID && json.aux && json.aux.ID) {
return {
isBuildKit: true,
logs: ["Built image: " + json.aux.ID],
raw: json.aux
};
}
// Not a BuildKit message
return {
isBuildKit: false,
logs: [],
raw: json
};
} catch (e) {
return {
isBuildKit: false,
logs: [],
raw: null,
error: e.message
};
}
}
/**
* Follow progress of a stream, automatically handling both BuildKit and regular output.
* This provides the same ergonomics as modem.followProgress but decodes BuildKit logs.
*
* @param {Stream} stream - Stream from buildImage(), pull(), push(), etc.
* @param {Function} onFinished - Called when stream ends: (err, output) => void
* @param {Function} onProgress - Called for each log event: (event) => void
* @returns {void}
*/
function followProgress(stream, onFinished, onProgress) {
var buffer = '';
var output = [];
var finished = false;
stream.on('data', onStreamEvent);
stream.on('error', onStreamError);
stream.on('end', onStreamEnd);
stream.on('close', onStreamEnd);
function onStreamEvent(data) {
buffer += data.toString();
// Process complete lines
var lines = buffer.split('\n');
buffer = lines.pop(); // Save incomplete line
lines.forEach(function(line) {
if (!line.trim()) return;
processLine(line);
});
}
function processLine(line) {
try {
// Try to parse as BuildKit or regular Docker output
var result = parseBuildKitLine(line);
if (result.isBuildKit) {
// BuildKit message - create events from decoded logs
result.logs.forEach(function(log) {
var event = { stream: log + '\n' };
output.push(event);
if (onProgress) onProgress(event);
});
} else if (result.raw) {
// Regular Docker message
output.push(result.raw);
if (onProgress) onProgress(result.raw);
}
} catch (e) {
// If parsing fails, try plain JSON
try {
var json = JSON.parse(line);
output.push(json);
if (onProgress) onProgress(json);
} catch (e2) {
// Ignore parse errors
}
}
}
function onStreamError(err) {
finished = true;
stream.removeListener('data', onStreamEvent);
stream.removeListener('error', onStreamError);
stream.removeListener('end', onStreamEnd);
stream.removeListener('close', onStreamEnd);
if (onFinished) onFinished(err, output);
}
function onStreamEnd() {
if (finished) return;
finished = true;
// Process any remaining data in buffer
if (buffer.trim()) {
processLine(buffer);
}
stream.removeListener('data', onStreamEvent);
stream.removeListener('error', onStreamError);
stream.removeListener('end', onStreamEnd);
stream.removeListener('close', onStreamEnd);
if (onFinished) onFinished(null, output);
}
}
module.exports = {
followProgress: followProgress
};
+135
View File
@@ -0,0 +1,135 @@
var util = require('./util');
/**
* Represents a config
* @param {Object} modem docker-modem
* @param {String} id Config's id
*/
var Config = function(modem, id) {
this.modem = modem;
this.id = id;
};
Config.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Inspect
*
* @param {Object} opts Options (optional)
* @param {Function} callback Callback, if specified Docker will be queried.
* @return {Object} Name only if callback isn't specified.
*/
Config.prototype.inspect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/configs/' + this.id,
method: 'GET',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'config not found',
500: 'server error',
503: 'node is not part of a swarm'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Update a config.
*
* @param {object} opts
* @param {function} callback
*/
Config.prototype.update = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/configs/' + this.id + '/update?',
method: 'POST',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'config not found',
500: 'server error',
503: 'node is not part of a swarm'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Removes the config
* @param {[Object]} opts Remove options (optional)
* @param {Function} callback Callback
*/
Config.prototype.remove = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/configs/' + this.id,
method: 'DELETE',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
204: true,
404: 'config not found',
500: 'server error',
503: 'node is not part of a swarm'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
module.exports = Config;
+1085
View File
File diff suppressed because it is too large Load Diff
+1903
View File
File diff suppressed because it is too large Load Diff
+139
View File
@@ -0,0 +1,139 @@
var util = require('./util');
/**
* Represents an Exec
* @param {Object} modem docker-modem
* @param {String} id Exec's ID
*/
var Exec = function(modem, id) {
this.modem = modem;
this.id = id;
};
Exec.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Start the exec call that was setup.
*
* @param {object} opts
* @param {function} callback
*/
Exec.prototype.start = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/exec/' + this.id + '/start',
method: 'POST',
abortSignal: args.opts.abortSignal,
isStream: true,
allowEmpty: true,
hijack: args.opts.hijack,
openStdin: args.opts.stdin,
statusCodes: {
200: true,
204: true,
404: 'no such exec',
409: 'container stopped/paused',
500: 'container not running'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
if (err) return args.callback(err, data);
args.callback(err, data);
});
}
};
/**
* Resize the exec call that was setup.
*
* @param {object} opts
* @param {function} callback
*/
Exec.prototype.resize = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/exec/' + this.id + '/resize?',
method: 'POST',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'no such exec',
500: 'container not running'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
if (err) return args.callback(err, data);
args.callback(err, data);
});
}
};
/**
* Get low-level information about the exec call.
*
* @param {Object} opts Options (optional)
* @param {function} callback
*/
Exec.prototype.inspect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/exec/' + this.id + '/json',
method: 'GET',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'no such exec',
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
if (err) return args.callback(err, data);
args.callback(err, data);
});
}
};
module.exports = Exec;
+279
View File
@@ -0,0 +1,279 @@
var util = require('./util');
/**
* Represents an image
* @param {Object} modem docker-modem
* @param {String} name Image's name
*/
var Image = function(modem, name) {
this.modem = modem;
this.name = name;
};
Image.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Inspect
* @param {Object} opts Inspect options, only 'manifests' (optional)
* @param {Function} callback Callback, if specified Docker will be queried.
* @return {Object} Name only if callback isn't specified.
*/
Image.prototype.inspect = function(opts, callback) {
var args = util.processArgs(opts, callback);
var self = this;
var opts = {
path: '/images/' + this.name + '/json',
method: 'GET',
options: args.opts,
statusCodes: {
200: true,
404: 'no such image',
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(opts, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(opts, function(err, data) {
if (err) return args.callback(err, data);
args.callback(err, data);
});
}
};
/**
* Distribution
* @param {Object} opts
* @param {Function} callback Callback, if specified Docker will be queried.
* @return {Object} Name only if callback isn't specified.
*/
Image.prototype.distribution = function(opts, callback) {
var args = util.processArgs(opts, callback);
var self = this;
var fopts = {
path: '/distribution/' + this.name + '/json',
method: 'GET',
statusCodes: {
200: true,
401: 'no such image',
500: 'server error'
},
authconfig: (args.opts) ? args.opts.authconfig : undefined
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(fopts, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(fopts, function(err, data) {
if (err) return args.callback(err, data);
args.callback(err, data);
});
}
};
/**
* History
* @param {Function} callback Callback
*/
Image.prototype.history = function(callback) {
var self = this;
var opts = {
path: '/images/' + this.name + '/history',
method: 'GET',
statusCodes: {
200: true,
404: 'no such image',
500: 'server error'
}
};
if(callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(opts, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(opts, function(err, data) {
if (err) return callback(err, data);
callback(err, data);
});
}
};
/**
* Get
* @param {Function} callback Callback with data stream.
*/
Image.prototype.get = function(callback) {
var self = this;
var opts = {
path: '/images/' + this.name + '/get',
method: 'GET',
isStream: true,
statusCodes: {
200: true,
500: 'server error'
}
};
if(callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(opts, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(opts, function(err, data) {
if (err) return callback(err, data);
callback(err, data);
});
}
};
/**
* Push
* @param {Object} opts Push options, like 'registry' (optional)
* @param {Function} callback Callback with stream.
* @param {Object} auth Registry authentication
*/
Image.prototype.push = function(opts, callback, auth) {
var self = this;
var args = util.processArgs(opts, callback);
var isStream = true;
if (args.opts.stream === false) {
isStream = false;
}
var optsf = {
path: '/images/' + this.name + '/push?',
method: 'POST',
options: args.opts,
authconfig: args.opts.authconfig || auth,
abortSignal: args.opts.abortSignal,
isStream: isStream,
statusCodes: {
200: true,
404: 'no such image',
500: 'server error'
}
};
delete optsf.options.authconfig;
if(callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
callback(err, data);
});
}
};
/**
* Tag
* @param {Object} opts Tag options, like 'repo' (optional)
* @param {Function} callback Callback
*/
Image.prototype.tag = function(opts, callback) {
var self = this;
var optsf = {
path: '/images/' + this.name + '/tag?',
method: 'POST',
options: opts,
abortSignal: opts && opts.abortSignal,
statusCodes: {
200: true, // unofficial, but proxies may return it
201: true,
400: 'bad parameter',
404: 'no such image',
409: 'conflict',
500: 'server error'
}
};
if(callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
callback(err, data);
});
}
};
/**
* Removes the image
* @param {[Object]} opts Remove options (optional)
* @param {Function} callback Callback
*/
Image.prototype.remove = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/images/' + this.name + '?',
method: 'DELETE',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'no such image',
409: 'conflict',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
module.exports = Image;
+171
View File
@@ -0,0 +1,171 @@
var util = require('./util');
/**
* Represents an network
* @param {Object} modem docker-modem
* @param {String} id Network's id
*/
var Network = function(modem, id) {
this.modem = modem;
this.id = id;
};
Network.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Inspect
* @param {Function} callback Callback, if specified Docker will be queried.
* @return {Object} Id only if callback isn't specified.
*/
Network.prototype.inspect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var opts = {
path: '/networks/' + this.id + '?',
method: 'GET',
statusCodes: {
200: true,
404: 'no such network',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(opts, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(opts, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Removes the network
* @param {[Object]} opts Remove options (optional)
* @param {Function} callback Callback
*/
Network.prototype.remove = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/networks/' + this.id,
method: 'DELETE',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
204: true,
404: 'no such network',
409: 'conflict',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Connects a container to a network
* @param {[Object]} opts Connect options (optional)
* @param {Function} callback Callback
*/
Network.prototype.connect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/networks/' + this.id + '/connect',
method: 'POST',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
201: true,
404: 'network or container is not found',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Disconnects a container from a network
* @param {[Object]} opts Disconnect options (optional)
* @param {Function} callback Callback
*/
Network.prototype.disconnect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/networks/' + this.id + '/disconnect',
method: 'POST',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
201: true,
404: 'network or container is not found',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
module.exports = Network;
+135
View File
@@ -0,0 +1,135 @@
var util = require('./util');
/**
* Represents an Node
* @param {Object} modem docker-modem
* @param {String} id Node's ID
*/
var Node = function(modem, id) {
this.modem = modem;
this.id = id;
};
Node.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Query Docker for Node details.
*
* @param {Object} opts Options (optional)
* @param {function} callback
*/
Node.prototype.inspect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/nodes/' + this.id,
method: 'GET',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'no such node',
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Update a node.
*
* @param {object} opts
* @param {function} callback
*/
Node.prototype.update = function(opts, callback) {
var self = this;
if (!callback && typeof opts === 'function') {
callback = opts;
}
var optsf = {
path: '/nodes/' + this.id + '/update?',
method: 'POST',
abortSignal: opts && opts.abortSignal,
statusCodes: {
200: true,
404: 'no such node',
406: 'node is not part of a swarm',
500: 'server error'
},
options: opts
};
if(callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
callback(err, data);
});
}
};
/**
* Remove a Node.
* Warning: This method is not documented in the API.
*
* @param {object} opts
* @param {function} callback
*/
Node.prototype.remove = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/nodes/' + this.id + '?',
method: 'DELETE',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'no such node',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
module.exports = Node;
+372
View File
@@ -0,0 +1,372 @@
var util = require('./util');
/**
* Represents a plugin
* @param {Object} modem docker-modem
* @param {String} name Plugin's name
*/
var Plugin = function(modem, name, remote) {
this.modem = modem;
this.name = name;
this.remote = remote || name;
};
Plugin.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Inspect
*
* @param {Object} opts Options (optional)
* @param {Function} callback Callback, if specified Docker will be queried.
* @return {Object} Name only if callback isn't specified.
*/
Plugin.prototype.inspect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/plugins/' + this.name + '/json',
method: 'GET',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'plugin is not installed',
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Removes the plugin
* @param {[Object]} opts Remove options (optional)
* @param {Function} callback Callback
*/
Plugin.prototype.remove = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/plugins/' + this.name + '?',
method: 'DELETE',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'plugin is not installed',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
if (err) return args.callback(err, data);
args.callback(err, data);
});
}
};
/**
* get privileges
* @param {Object} opts Options (optional)
* @param {Function} callback Callback
* @return {Object} Name only if callback isn't specified.
*/
Plugin.prototype.privileges = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/plugins/privileges?',
method: 'GET',
options: {
'remote': this.remote
},
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Installs a new plugin
* @param {Object} opts Create options
* @param {Function} callback Callback
*/
Plugin.prototype.pull = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
if(args.opts._query && !args.opts._query.name) {
args.opts._query.name = this.name;
}
if(args.opts._query && !args.opts._query.remote) {
args.opts._query.remote = this.remote;
}
var optsf = {
path: '/plugins/pull?',
method: 'POST',
abortSignal: args.opts.abortSignal,
isStream: true,
options: args.opts,
statusCodes: {
200: true, // unofficial, but proxies may return it
204: true,
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Enable
* @param {Object} opts Plugin enable options (optional)
* @param {Function} callback Callback
*/
Plugin.prototype.enable = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/plugins/' + this.name + '/enable?',
method: 'POST',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Disable
* @param {Object} opts Plugin disable options (optional)
* @param {Function} callback Callback
*/
Plugin.prototype.disable = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/plugins/' + this.name + '/disable',
method: 'POST',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Push
* @param {Object} opts Plugin push options (optional)
* @param {Function} callback Callback
*/
Plugin.prototype.push = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/plugins/' + this.name + '/push',
method: 'POST',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'plugin not installed',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* COnfigure
* @param {Object} opts Plugin configure options (optional)
* @param {Function} callback Callback
*/
Plugin.prototype.configure = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/plugins/' + this.name + '/set',
method: 'POST',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
204: true,
404: 'plugin not installed',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Upgrade plugin
*
* @param {object} auth
* @param {object} opts
* @param {function} callback
*/
Plugin.prototype.upgrade = function(auth, opts, callback) {
var self = this;
if (!callback && typeof opts === 'function') {
callback = opts;
opts = auth;
auth = opts.authconfig || undefined;
}
var optsf = {
path: '/plugins/' + this.name + '/upgrade?',
method: 'POST',
abortSignal: opts && opts.abortSignal,
statusCodes: {
200: true,
204: true,
404: 'plugin not installed',
500: 'server error'
},
authconfig: auth,
options: opts
};
if(callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
callback(err, data);
});
}
};
module.exports = Plugin;
+54
View File
@@ -0,0 +1,54 @@
syntax = "proto3";
package moby.filesync.v1;
option go_package = "auth";
service Auth{
rpc Credentials(CredentialsRequest) returns (CredentialsResponse);
rpc FetchToken(FetchTokenRequest) returns (FetchTokenResponse);
rpc GetTokenAuthority(GetTokenAuthorityRequest) returns (GetTokenAuthorityResponse);
rpc VerifyTokenAuthority(VerifyTokenAuthorityRequest) returns (VerifyTokenAuthorityResponse);
}
message CredentialsRequest {
string Host = 1;
}
message CredentialsResponse {
string Username = 1;
string Secret = 2;
}
message FetchTokenRequest {
string ClientID = 1;
string Host = 2;
string Realm = 3;
string Service = 4;
repeated string Scopes = 5;
}
message FetchTokenResponse {
string Token = 1;
int64 ExpiresIn = 2; // seconds
int64 IssuedAt = 3; // timestamp
}
message GetTokenAuthorityRequest {
string Host = 1;
bytes Salt = 2;
}
message GetTokenAuthorityResponse {
bytes PublicKey = 1;
}
message VerifyTokenAuthorityRequest {
string Host = 1;
bytes Payload = 2;
bytes Salt = 3;
}
message VerifyTokenAuthorityResponse {
bytes Signed = 1;
}
+81
View File
@@ -0,0 +1,81 @@
syntax = "proto3";
package moby.buildkit.v1;
// Minimal definitions for decoding BuildKit status messages
// Based on https://github.com/moby/buildkit/blob/master/api/services/control/control.proto
// Related to https://github.com/moby/buildkit/blob/master/solver/pb/ops.proto (vertices map to the solver op DAG)
message StatusResponse {
repeated Vertex vertexes = 1;
repeated VertexStatus statuses = 2;
repeated VertexLog logs = 3;
repeated VertexWarning warnings = 4;
}
message Vertex {
string digest = 1;
repeated string inputs = 2;
string name = 3;
bool cached = 4;
Timestamp started = 5;
Timestamp completed = 6;
string error = 7;
ProgressGroup progressGroup = 8;
}
message VertexStatus {
string ID = 1;
string vertex = 2;
string name = 3;
int64 current = 4;
int64 total = 5;
Timestamp timestamp = 6;
Timestamp started = 7;
Timestamp completed = 8;
}
message VertexLog {
string vertex = 1;
Timestamp timestamp = 2;
int64 stream = 3;
bytes msg = 4;
}
message VertexWarning {
string vertex = 1;
int64 level = 2;
bytes short = 3;
repeated bytes detail = 4;
string url = 5;
SourceInfo info = 6;
repeated Range ranges = 7;
}
message ProgressGroup {
string id = 1;
string name = 2;
bool weak = 3;
}
// Simplified Timestamp to match google.protobuf.Timestamp wire format
message Timestamp {
int64 seconds = 1;
int32 nanos = 2;
}
message SourceInfo {
string filename = 1;
bytes data = 2;
// definition and language fields omitted - not needed for log decoding
}
message Range {
Position start = 1;
Position end = 2;
}
message Position {
int32 line = 1;
int32 character = 2;
}
+134
View File
@@ -0,0 +1,134 @@
var util = require('./util');
/**
* Represents a secret
* @param {Object} modem docker-modem
* @param {String} id Secret's id
*/
var Secret = function(modem, id) {
this.modem = modem;
this.id = id;
};
Secret.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Inspect
* @param {Object} opts Options (optional)
* @param {Function} callback Callback, if specified Docker will be queried.
* @return {Object} Name only if callback isn't specified.
*/
Secret.prototype.inspect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/secrets/' + this.id,
method: 'GET',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'secret not found',
406: 'node is not part of a swarm',
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Update a secret.
*
* @param {object} opts
* @param {function} callback
*/
Secret.prototype.update = function(opts, callback) {
var self = this;
if (!callback && typeof opts === 'function') {
callback = opts;
}
var optsf = {
path: '/secrets/' + this.id + '/update?',
method: 'POST',
abortSignal: opts && opts.abortSignal,
statusCodes: {
200: true,
404: 'secret not found',
500: 'server error'
},
options: opts
};
if(callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
callback(err, data);
});
}
};
/**
* Removes the secret
* @param {[Object]} opts Remove options (optional)
* @param {Function} callback Callback
*/
Secret.prototype.remove = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/secrets/' + this.id,
method: 'DELETE',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
204: true,
404: 'secret not found',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
module.exports = Secret;
+183
View File
@@ -0,0 +1,183 @@
var util = require('./util');
/**
* Represents an Service
* @param {Object} modem docker-modem
* @param {String} id Service's ID
*/
var Service = function(modem, id) {
this.modem = modem;
this.id = id;
};
Service.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Query Docker for service details.
*
* @param {Object} opts Options (optional)
* @param {function} callback
*/
Service.prototype.inspect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/services/' + this.id,
method: 'GET',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'no such service',
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Delete Service
*
* @param {Object} opts Options (optional)
* @param {function} callback
*/
Service.prototype.remove = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/services/' + this.id,
method: 'DELETE',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
204: true,
404: 'no such service',
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Update service
*
* @param {object} auth
* @param {object} opts
* @param {function} callback
*/
Service.prototype.update = function(auth, opts, callback) {
var self = this;
if (!callback) {
var t = typeof opts;
if(t === 'function'){
callback = opts;
opts = auth;
auth = opts.authconfig || undefined;
} else if (t === 'undefined'){
opts = auth;
auth = opts.authconfig || undefined;
}
}
var optsf = {
path: '/services/' + this.id + '/update?',
method: 'POST',
abortSignal: opts && opts.abortSignal,
statusCodes: {
200: true,
404: 'no such service',
500: 'server error'
},
authconfig: auth,
options: opts
};
if(callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
callback(err, data);
});
}
};
/**
* Service logs
* @param {Object} opts Logs options. (optional)
* @param {Function} callback Callback with data
*/
Service.prototype.logs = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback, {});
var optsf = {
path: '/services/' + this.id + '/logs?',
method: 'GET',
abortSignal: args.opts.abortSignal,
isStream: args.opts.follow || false,
statusCodes: {
200: true,
404: 'no such service',
500: 'server error',
503: 'node is not part of a swarm'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
module.exports = Service;
+63
View File
@@ -0,0 +1,63 @@
var grpc = require("@grpc/grpc-js"),
protoLoader = require("@grpc/proto-loader"),
path = require("path"),
uuid = require("uuid").v4;
function withSession(docker, auth, handler) {
const sessionId = uuid();
const opts = {
method: "POST",
path: "/session",
hijack: true,
headers: {
Upgrade: "h2c",
"X-Docker-Expose-Session-Uuid": sessionId,
"X-Docker-Expose-Session-Name": "testcontainers",
},
statusCodes: {
200: true,
500: "server error",
},
};
docker.modem.dial(opts, function (err, socket) {
if (err) {
return handler(err, null, () => undefined);
}
const server = new grpc.Server();
const creds = grpc.ServerCredentials.createInsecure();
const injector = server.createConnectionInjector(creds);
injector.injectConnection(socket);
const pkg = protoLoader.loadSync(
path.resolve(__dirname, "proto", "auth.proto")
);
const service = grpc.loadPackageDefinition(pkg);
server.addService(service.moby.filesync.v1.Auth.service, {
Credentials({ request }, callback) {
// We probably want to have the possibility to pass credentials per
// hots. The correct one could be returned based on `request.Host`
if (auth) {
callback(null, {
Username: auth.username,
Secret: auth.password,
});
} else {
callback(null, {});
}
},
});
function done() {
server.forceShutdown();
socket.end();
}
handler(null, sessionId, done);
});
}
module.exports = withSession;
+97
View File
@@ -0,0 +1,97 @@
var util = require('./util');
/**
* Represents an Task
* @param {Object} modem docker-modem
* @param {String} id Task's ID
*/
var Task = function(modem, id) {
this.modem = modem;
this.id = id;
this.defaultOptions = {
log: {}
};
};
Task.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Query Docker for Task details.
*
* @param {Object} opts Options (optional)
* @param {function} callback
*/
Task.prototype.inspect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/tasks/' + this.id,
method: 'GET',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'unknown task',
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Task logs
* @param {Object} opts Logs options. (optional)
* @param {Function} callback Callback with data
*/
Task.prototype.logs = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback, this.defaultOptions.log);
var optsf = {
path: '/tasks/' + this.id + '/logs?',
method: 'GET',
abortSignal: args.opts.abortSignal,
isStream: args.opts.follow || false,
statusCodes: {
101: true,
200: true,
404: 'no such container',
500: 'server error',
503: 'node is not part of a swarm'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
module.exports = Task;
+106
View File
@@ -0,0 +1,106 @@
var DockerIgnore = require('@balena/dockerignore');
var fs = require('fs');
var path = require('path');
var tar = require('tar-fs');
var zlib = require('zlib');
// https://github.com/HenrikJoreteg/extend-object/blob/v0.1.0/extend-object.js
var arr = [];
var each = arr.forEach;
var slice = arr.slice;
module.exports.extend = function(obj) {
each.call(slice.call(arguments, 1), function(source) {
if (source) {
for (var prop in source) {
obj[prop] = source[prop];
}
}
});
return obj;
};
module.exports.processArgs = function(opts, callback, defaultOpts) {
if (!callback && typeof opts === 'function') {
callback = opts;
opts = null;
}
return {
callback: callback,
opts: module.exports.extend({}, defaultOpts, opts)
};
};
/**
* Parse the given repo tag name (as a string) and break it out into repo/tag pair.
* // if given the input http://localhost:8080/woot:latest
* {
* repository: 'http://localhost:8080/woot',
* tag: 'latest'
* }
* @param {String} input Input e.g: 'repo/foo', 'ubuntu', 'ubuntu:latest'
* @return {Object} input parsed into the repo and tag.
*/
module.exports.parseRepositoryTag = function(input) {
var separatorPos;
var digestPos = input.indexOf('@');
var colonPos = input.lastIndexOf(':');
// @ symbol is more important
if (digestPos >= 0) {
separatorPos = digestPos;
} else if (colonPos >= 0) {
separatorPos = colonPos;
} else {
// no colon nor @
return {
repository: input
};
}
// last colon is either the tag (or part of a port designation)
var tag = input.slice(separatorPos + 1);
// if it contains a / its not a tag and is part of the url
if (tag.indexOf('/') === -1) {
return {
repository: input.slice(0, separatorPos),
tag: tag
};
}
return {
repository: input
};
};
module.exports.prepareBuildContext = function(file, next) {
if (file && file.context) {
fs.readFile(path.join(file.context, '.dockerignore'), (err, data) => {
let ignoreFn;
let filterFn;
if (!err) {
const dockerIgnore = DockerIgnore({ ignorecase: false }).add(data.toString());
filterFn = dockerIgnore.createFilter();
ignoreFn = (path) => {
return !filterFn(path);
}
}
const entries = file.src.slice() || []
const pack = tar.pack(file.context, {
entries: filterFn ? entries.filter(filterFn) : entries,
ignore: ignoreFn // Only works on directories
});
next(pack.pipe(zlib.createGzip()));
})
} else {
next(file);
}
}
+90
View File
@@ -0,0 +1,90 @@
var util = require('./util');
/**
* Represents a volume
* @param {Object} modem docker-modem
* @param {String} name Volume's name
*/
var Volume = function(modem, name) {
this.modem = modem;
this.name = name;
};
Volume.prototype[require('util').inspect.custom] = function() { return this; };
/**
* Inspect
* @param {Object} opts Options (optional)
* @param {Function} callback Callback, if specified Docker will be queried.
* @return {Object} Name only if callback isn't specified.
*/
Volume.prototype.inspect = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/volumes/' + this.name,
method: 'GET',
abortSignal: args.opts.abortSignal,
statusCodes: {
200: true,
404: 'no such volume',
500: 'server error'
}
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
/**
* Removes the volume
* @param {[Object]} opts Remove options (optional)
* @param {Function} callback Callback
*/
Volume.prototype.remove = function(opts, callback) {
var self = this;
var args = util.processArgs(opts, callback);
var optsf = {
path: '/volumes/' + this.name,
method: 'DELETE',
abortSignal: args.opts.abortSignal,
statusCodes: {
204: true,
404: 'no such volume',
409: 'conflict',
500: 'server error'
},
options: args.opts
};
if(args.callback === undefined) {
return new this.modem.Promise(function(resolve, reject) {
self.modem.dial(optsf, function(err, data) {
if (err) {
return reject(err);
}
resolve(data);
});
});
} else {
this.modem.dial(optsf, function(err, data) {
args.callback(err, data);
});
}
};
module.exports = Volume;