mirror of
https://github.com/balena-io/balena-cli.git
synced 2024-12-19 13:47:52 +00:00
Merge pull request #1118 from balena-io/1116-private-reg-build-deploy
Extend private registry support to balena build and deploy commands
This commit is contained in:
commit
6eddd1ccd3
@ -1351,15 +1351,15 @@ Pin the preloaded device to the preloaded release on provision
|
|||||||
|
|
||||||
#### --docker, -P <docker>
|
#### --docker, -P <docker>
|
||||||
|
|
||||||
Path to a local docker socket
|
Path to a local docker socket (e.g. /var/run/docker.sock)
|
||||||
|
|
||||||
#### --dockerHost, -h <dockerHost>
|
#### --dockerHost, -h <dockerHost>
|
||||||
|
|
||||||
The address of the host containing the docker daemon
|
Docker daemon hostname or IP address (dev machine or balena device)
|
||||||
|
|
||||||
#### --dockerPort, -p <dockerPort>
|
#### --dockerPort, -p <dockerPort>
|
||||||
|
|
||||||
The port on which the host docker daemon is listening
|
Docker daemon TCP port number (hint: 2375 for balena devices)
|
||||||
|
|
||||||
#### --ca <ca>
|
#### --ca <ca>
|
||||||
|
|
||||||
@ -1691,7 +1691,9 @@ name of container to stop
|
|||||||
## build [source]
|
## build [source]
|
||||||
|
|
||||||
Use this command to build an image or a complete multicontainer project
|
Use this command to build an image or a complete multicontainer project
|
||||||
with the provided docker daemon.
|
with the provided docker daemon in your development machine or balena
|
||||||
|
device. (See also the `balena push` command for the option of building
|
||||||
|
images in balena's cloud builders.)
|
||||||
|
|
||||||
You must provide either an application or a device-type/architecture
|
You must provide either an application or a device-type/architecture
|
||||||
pair to use the balena Dockerfile pre-processor
|
pair to use the balena Dockerfile pre-processor
|
||||||
@ -1738,17 +1740,21 @@ Run an emulated build using Qemu
|
|||||||
|
|
||||||
Display full log output
|
Display full log output
|
||||||
|
|
||||||
|
#### --registry-secrets, -R <secrets.yml|.json>
|
||||||
|
|
||||||
|
Path to a YAML or JSON file with passwords for a private Docker registry
|
||||||
|
|
||||||
#### --docker, -P <docker>
|
#### --docker, -P <docker>
|
||||||
|
|
||||||
Path to a local docker socket
|
Path to a local docker socket (e.g. /var/run/docker.sock)
|
||||||
|
|
||||||
#### --dockerHost, -h <dockerHost>
|
#### --dockerHost, -h <dockerHost>
|
||||||
|
|
||||||
The address of the host containing the docker daemon
|
Docker daemon hostname or IP address (dev machine or balena device)
|
||||||
|
|
||||||
#### --dockerPort, -p <dockerPort>
|
#### --dockerPort, -p <dockerPort>
|
||||||
|
|
||||||
The port on which the host docker daemon is listening
|
Docker daemon TCP port number (hint: 2375 for balena devices)
|
||||||
|
|
||||||
#### --ca <ca>
|
#### --ca <ca>
|
||||||
|
|
||||||
@ -1780,11 +1786,14 @@ Squash newly built layers into a single new layer
|
|||||||
|
|
||||||
## deploy <appName> [image]
|
## deploy <appName> [image]
|
||||||
|
|
||||||
Use this command to deploy an image or a complete multicontainer project
|
|
||||||
to an application, optionally building it first.
|
|
||||||
|
|
||||||
Usage: `deploy <appName> ([image] | --build [--source build-dir])`
|
Usage: `deploy <appName> ([image] | --build [--source build-dir])`
|
||||||
|
|
||||||
|
Use this command to deploy an image or a complete multicontainer project to an
|
||||||
|
application, optionally building it first. The source images are searched for
|
||||||
|
(and optionally built) using the docker daemon in your development machine or
|
||||||
|
balena device. (See also the `balena push` command for the option of building
|
||||||
|
the image in balena's cloud builders.)
|
||||||
|
|
||||||
Unless an image is specified, this command will look into the current directory
|
Unless an image is specified, this command will look into the current directory
|
||||||
(or the one specified by --source) for a compose file. If one is found, this
|
(or the one specified by --source) for a compose file. If one is found, this
|
||||||
command will deploy each service defined in the compose file, building it first
|
command will deploy each service defined in the compose file, building it first
|
||||||
@ -1830,17 +1839,21 @@ Run an emulated build using Qemu
|
|||||||
|
|
||||||
Display full log output
|
Display full log output
|
||||||
|
|
||||||
|
#### --registry-secrets, -R <secrets.yml|.json>
|
||||||
|
|
||||||
|
Path to a YAML or JSON file with passwords for a private Docker registry
|
||||||
|
|
||||||
#### --docker, -P <docker>
|
#### --docker, -P <docker>
|
||||||
|
|
||||||
Path to a local docker socket
|
Path to a local docker socket (e.g. /var/run/docker.sock)
|
||||||
|
|
||||||
#### --dockerHost, -h <dockerHost>
|
#### --dockerHost, -h <dockerHost>
|
||||||
|
|
||||||
The address of the host containing the docker daemon
|
Docker daemon hostname or IP address (dev machine or balena device)
|
||||||
|
|
||||||
#### --dockerPort, -p <dockerPort>
|
#### --dockerPort, -p <dockerPort>
|
||||||
|
|
||||||
The port on which the host docker daemon is listening
|
Docker daemon TCP port number (hint: 2375 for balena devices)
|
||||||
|
|
||||||
#### --ca <ca>
|
#### --ca <ca>
|
||||||
|
|
||||||
|
@ -50,7 +50,9 @@ module.exports =
|
|||||||
primary: true
|
primary: true
|
||||||
help: '''
|
help: '''
|
||||||
Use this command to build an image or a complete multicontainer project
|
Use this command to build an image or a complete multicontainer project
|
||||||
with the provided docker daemon.
|
with the provided docker daemon in your development machine or balena
|
||||||
|
device. (See also the `balena push` command for the option of building
|
||||||
|
images in balena's cloud builders.)
|
||||||
|
|
||||||
You must provide either an application or a device-type/architecture
|
You must provide either an application or a device-type/architecture
|
||||||
pair to use the balena Dockerfile pre-processor
|
pair to use the balena Dockerfile pre-processor
|
||||||
@ -94,7 +96,7 @@ module.exports =
|
|||||||
action: (params, options, done) ->
|
action: (params, options, done) ->
|
||||||
# compositions with many services trigger misleading warnings
|
# compositions with many services trigger misleading warnings
|
||||||
require('events').defaultMaxListeners = 1000
|
require('events').defaultMaxListeners = 1000
|
||||||
|
{ validateComposeOptions } = require('../utils/compose_ts')
|
||||||
{ exitWithExpectedError } = require('../utils/patterns')
|
{ exitWithExpectedError } = require('../utils/patterns')
|
||||||
helpers = require('../utils/helpers')
|
helpers = require('../utils/helpers')
|
||||||
Logger = require('../utils/logger')
|
Logger = require('../utils/logger')
|
||||||
@ -109,6 +111,8 @@ module.exports =
|
|||||||
options.source ?= params.source
|
options.source ?= params.source
|
||||||
delete params.source
|
delete params.source
|
||||||
|
|
||||||
|
validateComposeOptions(options)
|
||||||
|
|
||||||
{ application, arch, deviceType } = options
|
{ application, arch, deviceType } = options
|
||||||
|
|
||||||
if (not (arch? and deviceType?) and not application?) or (application? and (arch? or deviceType?))
|
if (not (arch? and deviceType?) and not application?) or (application? and (arch? or deviceType?))
|
||||||
|
@ -122,11 +122,14 @@ module.exports =
|
|||||||
signature: 'deploy <appName> [image]'
|
signature: 'deploy <appName> [image]'
|
||||||
description: 'Deploy a single image or a multicontainer project to a balena application'
|
description: 'Deploy a single image or a multicontainer project to a balena application'
|
||||||
help: '''
|
help: '''
|
||||||
Use this command to deploy an image or a complete multicontainer project
|
|
||||||
to an application, optionally building it first.
|
|
||||||
|
|
||||||
Usage: `deploy <appName> ([image] | --build [--source build-dir])`
|
Usage: `deploy <appName> ([image] | --build [--source build-dir])`
|
||||||
|
|
||||||
|
Use this command to deploy an image or a complete multicontainer project to an
|
||||||
|
application, optionally building it first. The source images are searched for
|
||||||
|
(and optionally built) using the docker daemon in your development machine or
|
||||||
|
balena device. (See also the `balena push` command for the option of building
|
||||||
|
the image in balena's cloud builders.)
|
||||||
|
|
||||||
Unless an image is specified, this command will look into the current directory
|
Unless an image is specified, this command will look into the current directory
|
||||||
(or the one specified by --source) for a compose file. If one is found, this
|
(or the one specified by --source) for a compose file. If one is found, this
|
||||||
command will deploy each service defined in the compose file, building it first
|
command will deploy each service defined in the compose file, building it first
|
||||||
@ -170,7 +173,7 @@ module.exports =
|
|||||||
action: (params, options, done) ->
|
action: (params, options, done) ->
|
||||||
# compositions with many services trigger misleading warnings
|
# compositions with many services trigger misleading warnings
|
||||||
require('events').defaultMaxListeners = 1000
|
require('events').defaultMaxListeners = 1000
|
||||||
|
{ validateComposeOptions } = require('../utils/compose_ts')
|
||||||
helpers = require('../utils/helpers')
|
helpers = require('../utils/helpers')
|
||||||
Logger = require('../utils/logger')
|
Logger = require('../utils/logger')
|
||||||
|
|
||||||
@ -185,6 +188,8 @@ module.exports =
|
|||||||
appName = options.application if not appName?
|
appName = options.application if not appName?
|
||||||
delete options.application
|
delete options.application
|
||||||
|
|
||||||
|
validateComposeOptions(options)
|
||||||
|
|
||||||
if not appName?
|
if not appName?
|
||||||
throw new Error('Please specify the name of the application to deploy')
|
throw new Error('Please specify the name of the application to deploy')
|
||||||
|
|
||||||
|
@ -18,8 +18,6 @@ import { CommandDefinition } from 'capitano';
|
|||||||
import { stripIndent } from 'common-tags';
|
import { stripIndent } from 'common-tags';
|
||||||
import { BalenaSDK } from 'balena-sdk';
|
import { BalenaSDK } from 'balena-sdk';
|
||||||
|
|
||||||
import { BuildError } from '../utils/device/errors';
|
|
||||||
|
|
||||||
// An regex to detect an IP address, from https://www.regular-expressions.info/ip.html
|
// An regex to detect an IP address, from https://www.regular-expressions.info/ip.html
|
||||||
const IP_REGEX = new RegExp(
|
const IP_REGEX = new RegExp(
|
||||||
/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/,
|
/\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b/,
|
||||||
@ -98,42 +96,6 @@ async function getAppOwner(sdk: BalenaSDK, appName: string) {
|
|||||||
return selected.extra;
|
return selected.extra;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RegistrySecrets {
|
|
||||||
[registryAddress: string]: {
|
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function parseRegistrySecrets(
|
|
||||||
secretsFilename: string,
|
|
||||||
): Promise<RegistrySecrets> {
|
|
||||||
const { fs } = await require('mz');
|
|
||||||
const {
|
|
||||||
addCanonicalDockerHubEntry,
|
|
||||||
RegistrySecretValidator,
|
|
||||||
} = await require('resin-multibuild');
|
|
||||||
try {
|
|
||||||
let isYaml = false;
|
|
||||||
if (/.+\.ya?ml$/i.test(secretsFilename)) {
|
|
||||||
isYaml = true;
|
|
||||||
} else if (!/.+\.json$/i.test(secretsFilename)) {
|
|
||||||
throw new Error('Filename must end with .json, .yml or .yaml');
|
|
||||||
}
|
|
||||||
const raw = (await fs.readFile(secretsFilename)).toString();
|
|
||||||
const registrySecrets = new RegistrySecretValidator().validateRegistrySecrets(
|
|
||||||
isYaml ? (await require('js-yaml')).safeLoad(raw) : JSON.parse(raw),
|
|
||||||
);
|
|
||||||
addCanonicalDockerHubEntry(registrySecrets);
|
|
||||||
return registrySecrets;
|
|
||||||
} catch (error) {
|
|
||||||
error.message =
|
|
||||||
`Error validating registry secrets file "${secretsFilename}":\n` +
|
|
||||||
error.message;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const push: CommandDefinition<
|
export const push: CommandDefinition<
|
||||||
{
|
{
|
||||||
applicationOrDevice: string;
|
applicationOrDevice: string;
|
||||||
@ -217,6 +179,8 @@ export const push: CommandDefinition<
|
|||||||
const remote = await import('../utils/remote-build');
|
const remote = await import('../utils/remote-build');
|
||||||
const deviceDeploy = await import('../utils/device/deploy');
|
const deviceDeploy = await import('../utils/device/deploy');
|
||||||
const { exitWithExpectedError } = await import('../utils/patterns');
|
const { exitWithExpectedError } = await import('../utils/patterns');
|
||||||
|
const { parseRegistrySecrets } = await import('../utils/compose_ts');
|
||||||
|
const { BuildError } = await import('../utils/device/errors');
|
||||||
|
|
||||||
const appOrDevice: string | null = params.applicationOrDevice;
|
const appOrDevice: string | null = params.applicationOrDevice;
|
||||||
if (appOrDevice == null) {
|
if (appOrDevice == null) {
|
||||||
|
@ -41,6 +41,12 @@ exports.appendOptions = (opts) ->
|
|||||||
description: 'Display full log output'
|
description: 'Display full log output'
|
||||||
boolean: true
|
boolean: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
signature: 'registry-secrets',
|
||||||
|
alias: 'R',
|
||||||
|
parameter: 'secrets.yml|.json',
|
||||||
|
description: 'Path to a YAML or JSON file with passwords for a private Docker registry',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
exports.generateOpts = (options) ->
|
exports.generateOpts = (options) ->
|
||||||
|
@ -25,6 +25,51 @@ import { Composition } from 'resin-compose-parse';
|
|||||||
import { DeviceInfo } from './device/api';
|
import { DeviceInfo } from './device/api';
|
||||||
import Logger = require('./logger');
|
import Logger = require('./logger');
|
||||||
|
|
||||||
|
export interface RegistrySecrets {
|
||||||
|
[registryAddress: string]: {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseRegistrySecrets(
|
||||||
|
secretsFilename: string,
|
||||||
|
): Promise<RegistrySecrets> {
|
||||||
|
const { fs } = require('mz');
|
||||||
|
try {
|
||||||
|
let isYaml = false;
|
||||||
|
if (/.+\.ya?ml$/i.test(secretsFilename)) {
|
||||||
|
isYaml = true;
|
||||||
|
} else if (!/.+\.json$/i.test(secretsFilename)) {
|
||||||
|
throw new Error('Filename must end with .json, .yml or .yaml');
|
||||||
|
}
|
||||||
|
const raw = (await fs.readFile(secretsFilename)).toString();
|
||||||
|
const registrySecrets = new MultiBuild.RegistrySecretValidator().validateRegistrySecrets(
|
||||||
|
isYaml ? require('js-yaml').safeLoad(raw) : JSON.parse(raw),
|
||||||
|
);
|
||||||
|
MultiBuild.addCanonicalDockerHubEntry(registrySecrets);
|
||||||
|
return registrySecrets;
|
||||||
|
} catch (error) {
|
||||||
|
error.message =
|
||||||
|
`Error validating registry secrets file "${secretsFilename}":\n` +
|
||||||
|
error.message;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the compose-specific command-line options defined in compose.coffee.
|
||||||
|
* This function is meant to be called very early on to validate users' input,
|
||||||
|
* before any project loading / building / deploying.
|
||||||
|
*/
|
||||||
|
export async function validateComposeOptions(options: { [opt: string]: any }) {
|
||||||
|
if (options['registry-secrets']) {
|
||||||
|
options['registry-secrets'] = await parseRegistrySecrets(
|
||||||
|
options['registry-secrets'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a BuildTask array of "resolved build tasks" by calling multibuild
|
* Create a BuildTask array of "resolved build tasks" by calling multibuild
|
||||||
* .splitBuildStream() and performResolution(), and add build stream error
|
* .splitBuildStream() and performResolution(), and add build stream error
|
||||||
|
@ -14,19 +14,19 @@ exports.appendConnectionOptions = appendConnectionOptions = (opts) ->
|
|||||||
{
|
{
|
||||||
signature: 'docker'
|
signature: 'docker'
|
||||||
parameter: 'docker'
|
parameter: 'docker'
|
||||||
description: 'Path to a local docker socket'
|
description: 'Path to a local docker socket (e.g. /var/run/docker.sock)'
|
||||||
alias: 'P'
|
alias: 'P'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
signature: 'dockerHost'
|
signature: 'dockerHost'
|
||||||
parameter: 'dockerHost'
|
parameter: 'dockerHost'
|
||||||
description: 'The address of the host containing the docker daemon'
|
description: 'Docker daemon hostname or IP address (dev machine or balena device) '
|
||||||
alias: 'h'
|
alias: 'h'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
signature: 'dockerPort'
|
signature: 'dockerPort'
|
||||||
parameter: 'dockerPort'
|
parameter: 'dockerPort'
|
||||||
description: 'The port on which the host docker daemon is listening'
|
description: 'Docker daemon TCP port number (hint: 2375 for balena devices)'
|
||||||
alias: 'p'
|
alias: 'p'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -147,6 +147,8 @@ exports.generateBuildOpts = (options) ->
|
|||||||
opts.squash = true
|
opts.squash = true
|
||||||
if options.buildArg?
|
if options.buildArg?
|
||||||
opts.buildargs = parseBuildArgs(options.buildArg)
|
opts.buildargs = parseBuildArgs(options.buildArg)
|
||||||
|
if not _.isEmpty(options['registry-secrets'])
|
||||||
|
opts.registryconfig = options['registry-secrets']
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
exports.getDocker = (options) ->
|
exports.getDocker = (options) ->
|
||||||
|
Loading…
Reference in New Issue
Block a user