mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-21 17:56:57 +00:00
Integrate new resin-multibuild major version (private docker registry
authentication support for the docker-compose 'image' instruction). Resolves: #1114 Change-type: minor Signed-off-by: Paulo Castro <paulo@balena.io>
This commit is contained in:
parent
8a6ee5905a
commit
c1e94e661f
@ -109,7 +109,10 @@ async function parseRegistrySecrets(
|
|||||||
secretsFilename: string,
|
secretsFilename: string,
|
||||||
): Promise<RegistrySecrets> {
|
): Promise<RegistrySecrets> {
|
||||||
const { fs } = await require('mz');
|
const { fs } = await require('mz');
|
||||||
const { RegistrySecretValidator } = await require('resin-multibuild');
|
const {
|
||||||
|
addCanonicalDockerHubEntry,
|
||||||
|
RegistrySecretValidator,
|
||||||
|
} = await require('resin-multibuild');
|
||||||
try {
|
try {
|
||||||
let isYaml = false;
|
let isYaml = false;
|
||||||
if (/.+\.ya?ml$/i.test(secretsFilename)) {
|
if (/.+\.ya?ml$/i.test(secretsFilename)) {
|
||||||
@ -118,9 +121,11 @@ async function parseRegistrySecrets(
|
|||||||
throw new Error('Filename must end with .json, .yml or .yaml');
|
throw new Error('Filename must end with .json, .yml or .yaml');
|
||||||
}
|
}
|
||||||
const raw = (await fs.readFile(secretsFilename)).toString();
|
const raw = (await fs.readFile(secretsFilename)).toString();
|
||||||
return new RegistrySecretValidator().validateRegistrySecrets(
|
const registrySecrets = new RegistrySecretValidator().validateRegistrySecrets(
|
||||||
isYaml ? (await require('js-yaml')).safeLoad(raw) : JSON.parse(raw),
|
isYaml ? (await require('js-yaml')).safeLoad(raw) : JSON.parse(raw),
|
||||||
);
|
);
|
||||||
|
addCanonicalDockerHubEntry(registrySecrets);
|
||||||
|
return registrySecrets;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.message =
|
error.message =
|
||||||
`Error validating registry secrets file "${secretsFilename}":\n` +
|
`Error validating registry secrets file "${secretsFilename}":\n` +
|
||||||
|
@ -197,16 +197,8 @@ exports.buildProject = (
|
|||||||
# Tar up the directory, ready for the build stream
|
# Tar up the directory, ready for the build stream
|
||||||
tarDirectory(projectPath)
|
tarDirectory(projectPath)
|
||||||
.then (tarStream) ->
|
.then (tarStream) ->
|
||||||
builder.splitBuildStream(composition, tarStream)
|
{ makeBuildTasks } = require('./compose_ts')
|
||||||
.tap (tasks) ->
|
Promise.resolve(makeBuildTasks(composition, tarStream, { arch, deviceType }, logger))
|
||||||
# Updates each task as a side-effect
|
|
||||||
builder.performResolution(tasks, arch, deviceType)
|
|
||||||
.map (task) ->
|
|
||||||
if not task.external and not task.resolved
|
|
||||||
throw new Error(
|
|
||||||
"Project type for service '#{task.serviceName}' could not be determined. " +
|
|
||||||
'Please add a Dockerfile'
|
|
||||||
)
|
|
||||||
.map (task) ->
|
.map (task) ->
|
||||||
d = imageDescriptorsByServiceName[task.serviceName]
|
d = imageDescriptorsByServiceName[task.serviceName]
|
||||||
|
|
||||||
|
@ -14,28 +14,98 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import * as Bluebird from 'bluebird';
|
||||||
|
import * as tar from 'tar-stream';
|
||||||
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
import { RegistrySecrets } from 'resin-multibuild';
|
import * as MultiBuild from 'resin-multibuild';
|
||||||
import { Pack } from 'tar-stream';
|
import { Composition } from 'resin-compose-parse';
|
||||||
|
|
||||||
|
import { DeviceInfo } from './device/api';
|
||||||
|
import Logger = require('./logger');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a callback function that takes a tar-stream Pack object as argument
|
* Create a BuildTask array of "resolved build tasks" by calling multibuild
|
||||||
* and uses it to add the '.balena/registry-secrets.json' metadata file that
|
* .splitBuildStream() and performResolution(), and add build stream error
|
||||||
* contains usernames and passwords for private docker registries. The builder
|
* handlers and debug logging.
|
||||||
* will remove the file from the tar stream and use the secrets to pull base
|
* Both `balena build` and `balena deploy` call this function.
|
||||||
* images from users' private registries.
|
|
||||||
* @param registrySecrets JS object containing registry usernames and passwords
|
|
||||||
* @returns A callback function, or undefined if registrySecrets is empty
|
|
||||||
*/
|
*/
|
||||||
export function getTarStreamCallbackForRegistrySecrets(
|
export async function makeBuildTasks(
|
||||||
registrySecrets: RegistrySecrets,
|
composition: Composition,
|
||||||
): ((pack: Pack) => void) | undefined {
|
tarStream: Readable,
|
||||||
if (Object.keys(registrySecrets).length > 0) {
|
deviceInfo: DeviceInfo,
|
||||||
return (pack: Pack) => {
|
logger: Logger,
|
||||||
pack.entry(
|
): Promise<MultiBuild.BuildTask[]> {
|
||||||
{ name: '.balena/registry-secrets.json' },
|
const buildTasks = await MultiBuild.splitBuildStream(composition, tarStream);
|
||||||
JSON.stringify(registrySecrets),
|
|
||||||
);
|
logger.logDebug('Found build tasks:');
|
||||||
};
|
_.each(buildTasks, task => {
|
||||||
}
|
let infoStr: string;
|
||||||
|
if (task.external) {
|
||||||
|
infoStr = `image pull [${task.imageName}]`;
|
||||||
|
} else {
|
||||||
|
infoStr = `build [${task.context}]`;
|
||||||
|
}
|
||||||
|
logger.logDebug(` ${task.serviceName}: ${infoStr}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.logDebug(
|
||||||
|
`Resolving services with [${deviceInfo.deviceType}|${deviceInfo.arch}]`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await performResolution(buildTasks, deviceInfo);
|
||||||
|
|
||||||
|
logger.logDebug('Found project types:');
|
||||||
|
_.each(buildTasks, task => {
|
||||||
|
if (task.external) {
|
||||||
|
logger.logDebug(` ${task.serviceName}: External image`);
|
||||||
|
} else {
|
||||||
|
logger.logDebug(` ${task.serviceName}: ${task.projectType}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return buildTasks;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function performResolution(
|
||||||
|
tasks: MultiBuild.BuildTask[],
|
||||||
|
deviceInfo: DeviceInfo,
|
||||||
|
): Promise<MultiBuild.BuildTask[]> {
|
||||||
|
const { cloneTarStream } = require('tar-utils');
|
||||||
|
|
||||||
|
return await new Promise<MultiBuild.BuildTask[]>((resolve, reject) => {
|
||||||
|
const buildTasks = MultiBuild.performResolution(
|
||||||
|
tasks,
|
||||||
|
deviceInfo.arch,
|
||||||
|
deviceInfo.deviceType,
|
||||||
|
{ error: [reject] },
|
||||||
|
);
|
||||||
|
// Do one task at a time (Bluebird.each instead of Bluebird.all)
|
||||||
|
// in order to reduce peak memory usage. Resolves to buildTasks.
|
||||||
|
Bluebird.each(buildTasks, buildTask => {
|
||||||
|
// buildStream is falsy for "external" tasks (image pull)
|
||||||
|
if (!buildTask.buildStream) {
|
||||||
|
return buildTask;
|
||||||
|
}
|
||||||
|
// Consume each task.buildStream in order to trigger the
|
||||||
|
// resolution events that define fields like:
|
||||||
|
// task.dockerfile, task.dockerfilePath,
|
||||||
|
// task.projectType, task.resolved
|
||||||
|
// This mimics what is currently done in `resin-builder`.
|
||||||
|
return cloneTarStream(buildTask.buildStream).then(
|
||||||
|
(clonedStream: tar.Pack) => {
|
||||||
|
buildTask.buildStream = clonedStream;
|
||||||
|
if (!buildTask.external && !buildTask.resolved) {
|
||||||
|
throw new Error(
|
||||||
|
`Project type for service "${
|
||||||
|
buildTask.serviceName
|
||||||
|
}" could not be determined. Missing a Dockerfile?`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return buildTask;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}).then(resolve, reject);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -19,13 +19,18 @@ import * as Bluebird from 'bluebird';
|
|||||||
import * as Docker from 'dockerode';
|
import * as Docker from 'dockerode';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { Composition } from 'resin-compose-parse';
|
import { Composition } from 'resin-compose-parse';
|
||||||
import { BuildTask, LocalImage, RegistrySecrets } from 'resin-multibuild';
|
import {
|
||||||
|
BuildTask,
|
||||||
|
getAuthConfigObj,
|
||||||
|
LocalImage,
|
||||||
|
RegistrySecrets,
|
||||||
|
} from 'resin-multibuild';
|
||||||
import * as semver from 'resin-semver';
|
import * as semver from 'resin-semver';
|
||||||
import { Readable } from 'stream';
|
import { Readable } from 'stream';
|
||||||
|
|
||||||
import Logger = require('../logger');
|
import Logger = require('../logger');
|
||||||
import { displayBuildLog } from './logs';
|
import { displayBuildLog } from './logs';
|
||||||
|
import { makeBuildTasks } from '../compose_ts';
|
||||||
import { DeviceInfo } from './api';
|
import { DeviceInfo } from './api';
|
||||||
import * as LocalPushErrors from './errors';
|
import * as LocalPushErrors from './errors';
|
||||||
|
|
||||||
@ -148,36 +153,12 @@ export async function performBuilds(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const multibuild = await import('resin-multibuild');
|
const multibuild = await import('resin-multibuild');
|
||||||
|
|
||||||
const buildTasks = await multibuild.splitBuildStream(composition, tarStream);
|
const buildTasks = await makeBuildTasks(
|
||||||
|
composition,
|
||||||
logger.logDebug('Found build tasks:');
|
tarStream,
|
||||||
_.each(buildTasks, task => {
|
deviceInfo,
|
||||||
let infoStr: string;
|
logger,
|
||||||
if (task.external) {
|
|
||||||
infoStr = `image pull [${task.imageName}]`;
|
|
||||||
} else {
|
|
||||||
infoStr = `build [${task.context}]`;
|
|
||||||
}
|
|
||||||
logger.logDebug(` ${task.serviceName}: ${infoStr}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.logDebug(
|
|
||||||
`Resolving services with [${deviceInfo.deviceType}|${deviceInfo.arch}]`,
|
|
||||||
);
|
);
|
||||||
await multibuild.performResolution(
|
|
||||||
buildTasks,
|
|
||||||
deviceInfo.arch,
|
|
||||||
deviceInfo.deviceType,
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.logDebug('Found project types:');
|
|
||||||
_.each(buildTasks, task => {
|
|
||||||
if (!task.external) {
|
|
||||||
logger.logDebug(` ${task.serviceName}: ${task.projectType}`);
|
|
||||||
} else {
|
|
||||||
logger.logDebug(` ${task.serviceName}: External image`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.logDebug('Probing remote daemon for cache images');
|
logger.logDebug('Probing remote daemon for cache images');
|
||||||
await assignDockerBuildOpts(docker, buildTasks, opts);
|
await assignDockerBuildOpts(docker, buildTasks, opts);
|
||||||
@ -247,16 +228,23 @@ async function assignDockerBuildOpts(
|
|||||||
|
|
||||||
logger.logDebug(`Using ${images.length} on-device images for cache...`);
|
logger.logDebug(`Using ${images.length} on-device images for cache...`);
|
||||||
|
|
||||||
_.each(buildTasks, (task: BuildTask) => {
|
await Bluebird.map(buildTasks, async (task: BuildTask) => {
|
||||||
task.dockerOpts = {
|
task.dockerOpts = {
|
||||||
cachefrom: images,
|
cachefrom: images,
|
||||||
labels: {
|
labels: {
|
||||||
'io.resin.local.image': '1',
|
'io.resin.local.image': '1',
|
||||||
'io.resin.local.service': task.serviceName,
|
'io.resin.local.service': task.serviceName,
|
||||||
},
|
},
|
||||||
registryconfig: opts.registrySecrets,
|
|
||||||
t: generateImageName(task.serviceName),
|
t: generateImageName(task.serviceName),
|
||||||
};
|
};
|
||||||
|
if (task.external) {
|
||||||
|
task.dockerOpts.authconfig = await getAuthConfigObj(
|
||||||
|
task.imageName!,
|
||||||
|
opts.registrySecrets,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
task.dockerOpts.registryconfig = opts.registrySecrets;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,6 +288,15 @@ export function printErrorMessage(message: string) {
|
|||||||
console.error(chalk.red(`\n${messages.getHelp}\n`));
|
console.error(chalk.red(`\n${messages.getHelp}\n`));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a friendly error message and exit the CLI with an error code, BYPASSING
|
||||||
|
* error reporting through Sentry.io's platform (raven.Raven.captureException).
|
||||||
|
* Note that lib/errors.ts provides top-level error handling code to catch any
|
||||||
|
* otherwise uncaught errors, AND to report them through Sentry.io. But many
|
||||||
|
* "expected" errors (say, a JSON parsing error in a file provided by the user)
|
||||||
|
* don't warrant reporting through Sentry.io. For such mundane errors, catch
|
||||||
|
* them and call this function.
|
||||||
|
*/
|
||||||
export function exitWithExpectedError(message: string | Error): never {
|
export function exitWithExpectedError(message: string | Error): never {
|
||||||
if (message instanceof Error) {
|
if (message instanceof Error) {
|
||||||
({ message } = message);
|
({ message } = message);
|
||||||
|
@ -155,13 +155,12 @@
|
|||||||
"raven": "^2.5.0",
|
"raven": "^2.5.0",
|
||||||
"reconfix": "^0.1.0",
|
"reconfix": "^0.1.0",
|
||||||
"request": "^2.81.0",
|
"request": "^2.81.0",
|
||||||
"resin-bundle-resolve": "^0.6.0",
|
|
||||||
"resin-cli-form": "^2.0.1",
|
"resin-cli-form": "^2.0.1",
|
||||||
"resin-cli-visuals": "^1.4.0",
|
"resin-cli-visuals": "^1.4.0",
|
||||||
"resin-compose-parse": "^2.0.0",
|
"resin-compose-parse": "^2.0.0",
|
||||||
"resin-doodles": "0.0.1",
|
"resin-doodles": "0.0.1",
|
||||||
"resin-image-fs": "^5.0.2",
|
"resin-image-fs": "^5.0.2",
|
||||||
"resin-multibuild": "^0.10.0",
|
"resin-multibuild": "^2.1.0",
|
||||||
"resin-release": "^1.2.0",
|
"resin-release": "^1.2.0",
|
||||||
"resin-semver": "^1.4.0",
|
"resin-semver": "^1.4.0",
|
||||||
"resin-stream-logger": "^0.1.2",
|
"resin-stream-logger": "^0.1.2",
|
||||||
@ -171,7 +170,8 @@
|
|||||||
"split": "^1.0.1",
|
"split": "^1.0.1",
|
||||||
"string-width": "^2.1.1",
|
"string-width": "^2.1.1",
|
||||||
"strip-ansi-stream": "^1.0.0",
|
"strip-ansi-stream": "^1.0.0",
|
||||||
"tar-stream": "^1.5.5",
|
"tar-stream": "^1.6.2",
|
||||||
|
"tar-utils": "^1.1.0",
|
||||||
"through2": "^2.0.3",
|
"through2": "^2.0.3",
|
||||||
"tmp": "0.0.31",
|
"tmp": "0.0.31",
|
||||||
"typed-error": "^3.0.0",
|
"typed-error": "^3.0.0",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user