balena-cli
Version:
The official balena Command Line Interface
200 lines • 7.01 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.dockerCliFlags = exports.dockerConnectionCliFlags = void 0;
exports.generateBuildOpts = generateBuildOpts;
exports.isBalenaEngine = isBalenaEngine;
exports.getDocker = getDocker;
exports.createClient = createClient;
exports.getDefaultDockerModemOpts = getDefaultDockerModemOpts;
exports.generateConnectOpts = generateConnectOpts;
const core_1 = require("@oclif/core");
const errors_1 = require("../errors");
const validation_1 = require("./validation");
exports.dockerConnectionCliFlags = {
docker: core_1.Flags.string({
description: 'Path to a local docker socket (e.g. /var/run/docker.sock)',
char: 'P',
}),
dockerHost: core_1.Flags.string({
description: 'Docker daemon hostname or IP address (dev machine or balena device) ',
char: 'h',
}),
dockerPort: core_1.Flags.integer({
description: 'Docker daemon TCP port number (hint: 2375 for balena devices)',
char: 'p',
parse: async (p) => (0, validation_1.parseAsInteger)(p, 'dockerPort'),
}),
ca: core_1.Flags.string({
description: 'Docker host TLS certificate authority file',
}),
cert: core_1.Flags.string({
description: 'Docker host TLS certificate file',
}),
key: core_1.Flags.string({
description: 'Docker host TLS key file',
}),
};
exports.dockerCliFlags = {
tag: core_1.Flags.string({
description: `\
Tag locally built Docker images. This is the 'tag' portion
in 'projectName_serviceName:tag'. The default is 'latest'.`,
char: 't',
}),
buildArg: core_1.Flags.string({
description: '[Deprecated] Set a build-time variable (eg. "-B \'ARG=value\'"). Can be specified multiple times.',
char: 'B',
multiple: true,
}),
'cache-from': core_1.Flags.string({
description: `\
Comma-separated list (no spaces) of image names for build cache resolution. \
Implements the same feature as the "docker build --cache-from" option.`,
}),
nocache: core_1.Flags.boolean({
description: "Don't use docker layer caching when building",
}),
pull: core_1.Flags.boolean({
description: 'Pull the base images again even if they exist locally',
}),
squash: core_1.Flags.boolean({
description: 'Squash newly built layers into a single new layer',
}),
...exports.dockerConnectionCliFlags,
};
function parseBuildArgs(args) {
if (!Array.isArray(args)) {
args = [args];
}
const buildArgs = {};
args.forEach(function (arg) {
var _a;
const pair = /^([^\s]+?)=([^]*)$/.exec(arg);
if (pair != null) {
buildArgs[pair[1]] = (_a = pair[2]) !== null && _a !== void 0 ? _a : '';
}
else {
throw new errors_1.ExpectedError(`Could not parse build argument: '${arg}'`);
}
});
return buildArgs;
}
function generateBuildOpts(options) {
var _a;
const opts = {};
if (options.buildArg != null) {
opts.buildargs = parseBuildArgs(options.buildArg);
}
if ((_a = options['cache-from']) === null || _a === void 0 ? void 0 : _a.trim()) {
opts.cachefrom = options['cache-from'].split(',').filter((i) => !!i.trim());
}
if (options.nocache != null) {
opts.nocache = true;
}
if (options.pull != null) {
opts.pull = true;
}
if (options['registry-secrets'] &&
Object.keys(options['registry-secrets']).length) {
opts.registryconfig = options['registry-secrets'];
}
if (options.squash != null) {
opts.squash = true;
}
if (options.tag != null) {
opts.t = options.tag;
}
return opts;
}
async function isBalenaEngine(docker) {
const dockerVersion = (await docker.version());
return !!(dockerVersion.Engine && dockerVersion.Engine.match(/balena|balaena/));
}
async function getDocker(options) {
const connectOpts = await generateConnectOpts(options);
const client = await createClient(connectOpts);
await checkThatDockerIsReachable(client);
return client;
}
async function createClient(opts) {
const Docker = await Promise.resolve().then(() => require('dockerode'));
return new Docker(opts);
}
function getDefaultDockerModemOpts(opts) {
var _a;
const connectOpts = {};
const optsOfInterest = [
'ca',
'cert',
'key',
'host',
'port',
'socketPath',
'protocol',
'username',
'timeout',
];
const Modem = require('docker-modem');
const originalDockerHost = process.env.DOCKER_HOST;
try {
if (opts.dockerHost) {
(_a = process.env).DOCKER_HOST || (_a.DOCKER_HOST = opts.dockerPort
? `${opts.dockerHost}:${opts.dockerPort}`
: opts.dockerHost);
}
const defaultOpts = new Modem();
for (const opt of optsOfInterest) {
connectOpts[opt] = defaultOpts[opt];
}
}
finally {
if (originalDockerHost) {
process.env.DOCKER_HOST = originalDockerHost;
}
else {
delete process.env.DOCKER_HOST;
}
}
return connectOpts;
}
async function generateConnectOpts(opts) {
let connectOpts = getDefaultDockerModemOpts(opts);
if (opts.docker != null && opts.dockerHost == null) {
connectOpts.socketPath = opts.docker;
delete connectOpts.host;
delete connectOpts.port;
}
else if (opts.dockerHost != null && opts.docker == null) {
connectOpts.host = opts.dockerHost;
connectOpts.port = opts.dockerPort || 2376;
delete connectOpts.socketPath;
}
else if (opts.docker != null && opts.dockerHost != null) {
throw new errors_1.ExpectedError("Both a local docker socket and docker host have been provided. Don't know how to continue.");
}
const tlsOpts = [opts.ca, opts.cert, opts.key];
if (tlsOpts.some((opt) => opt)) {
if (!tlsOpts.every((opt) => opt)) {
throw new errors_1.ExpectedError('You must provide a CA, certificate and key in order to use TLS');
}
if (!isStringArray(tlsOpts)) {
throw new errors_1.ExpectedError('TLS options (CA, certificate and key) must be file paths (strings)');
}
const { promises: fs } = await Promise.resolve().then(() => require('fs'));
const [ca, cert, key] = await Promise.all(tlsOpts.map((opt) => fs.readFile(opt, 'utf8')));
connectOpts = { ...connectOpts, ca, cert, key, protocol: 'https' };
}
return connectOpts;
}
function isStringArray(array) {
return array.every((opt) => typeof opt === 'string');
}
async function checkThatDockerIsReachable(docker) {
try {
await docker.ping();
}
catch (e) {
throw new errors_1.ExpectedError(`Docker seems to be unavailable. Is it installed and running?\n${e}`);
}
}
//# sourceMappingURL=docker.js.map
;