mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-03-11 06:53:56 +00:00
Update push command for organizations
Change-type: patch Connects-to: #2119 Signed-off-by: Scott Lowe <scott@balena.io>
This commit is contained in:
parent
358acbd2c8
commit
27e2b03702
@ -2439,7 +2439,7 @@ Docker host TLS key file
|
|||||||
|
|
||||||
## push <applicationOrDevice>
|
## push <applicationOrDevice>
|
||||||
|
|
||||||
Start a build on the remote balenaCloud builders, or a local mode balena device.
|
Start a build on the remote balenaCloud build servers, or a local mode device.
|
||||||
|
|
||||||
When building on the balenaCloud servers, the given source directory will be
|
When building on the balenaCloud servers, the given source directory will be
|
||||||
sent to the remote server. This can be used as a drop-in replacement for the
|
sent to the remote server. This can be used as a drop-in replacement for the
|
||||||
@ -2545,6 +2545,7 @@ Examples:
|
|||||||
$ balena push myApp --source <source directory>
|
$ balena push myApp --source <source directory>
|
||||||
$ balena push myApp -s <source directory>
|
$ balena push myApp -s <source directory>
|
||||||
$ balena push myApp --release-tag key1 "" key2 "value2 with spaces"
|
$ balena push myApp --release-tag key1 "" key2 "value2 with spaces"
|
||||||
|
$ balena push myorg/myapp
|
||||||
|
|
||||||
$ balena push 10.0.0.1
|
$ balena push 10.0.0.1
|
||||||
$ balena push 10.0.0.1 --source <source directory>
|
$ balena push 10.0.0.1 --source <source directory>
|
||||||
@ -2559,7 +2560,7 @@ Examples:
|
|||||||
|
|
||||||
#### APPLICATIONORDEVICE
|
#### APPLICATIONORDEVICE
|
||||||
|
|
||||||
application name, or device address (for local pushes)
|
application name or slug, or local device IP address or hostname
|
||||||
|
|
||||||
### Options
|
### Options
|
||||||
|
|
||||||
|
@ -22,7 +22,9 @@ import { getBalenaSdk, stripIndent } from '../utils/lazy';
|
|||||||
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
|
import { dockerignoreHelp, registrySecretsHelp } from '../utils/messages';
|
||||||
import type { BalenaSDK, Application, Organization } from 'balena-sdk';
|
import type { BalenaSDK, Application, Organization } from 'balena-sdk';
|
||||||
import { ExpectedError, instanceOf } from '../errors';
|
import { ExpectedError, instanceOf } from '../errors';
|
||||||
import type { RegistrySecrets } from 'resin-multibuild';
|
import { isV13 } from '../utils/version';
|
||||||
|
import { RegistrySecrets } from 'resin-multibuild';
|
||||||
|
import { lowercaseIfSlug } from '../utils/normalization';
|
||||||
|
|
||||||
enum BuildTarget {
|
enum BuildTarget {
|
||||||
Cloud,
|
Cloud,
|
||||||
@ -30,23 +32,23 @@ enum BuildTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface FlagsDef {
|
interface FlagsDef {
|
||||||
source?: string;
|
source: string;
|
||||||
emulated: boolean;
|
emulated: boolean;
|
||||||
dockerfile?: string; // DeviceDeployOptions.dockerfilePath (alternative Dockerfile)
|
dockerfile?: string; // DeviceDeployOptions.dockerfilePath (alternative Dockerfile)
|
||||||
nocache?: boolean;
|
nocache: boolean;
|
||||||
pull?: boolean;
|
pull: boolean;
|
||||||
'noparent-check'?: boolean;
|
'noparent-check': boolean;
|
||||||
'registry-secrets'?: string;
|
'registry-secrets'?: string;
|
||||||
gitignore?: boolean;
|
gitignore?: boolean;
|
||||||
nogitignore?: boolean;
|
nogitignore?: boolean;
|
||||||
nolive?: boolean;
|
nolive: boolean;
|
||||||
detached?: boolean;
|
detached: boolean;
|
||||||
service?: string[];
|
service?: string[];
|
||||||
system?: boolean;
|
system: boolean;
|
||||||
env?: string[];
|
env?: string[];
|
||||||
'convert-eol'?: boolean;
|
'convert-eol'?: boolean;
|
||||||
'noconvert-eol'?: boolean;
|
'noconvert-eol': boolean;
|
||||||
'multi-dockerignore'?: boolean;
|
'multi-dockerignore': boolean;
|
||||||
'release-tag'?: string[];
|
'release-tag'?: string[];
|
||||||
help: void;
|
help: void;
|
||||||
}
|
}
|
||||||
@ -57,9 +59,9 @@ interface ArgsDef {
|
|||||||
|
|
||||||
export default class PushCmd extends Command {
|
export default class PushCmd extends Command {
|
||||||
public static description = stripIndent`
|
public static description = stripIndent`
|
||||||
Start a remote build on the balenaCloud build servers or a local mode device.
|
Start a build on the remote balenaCloud build servers, or a local mode device.
|
||||||
|
|
||||||
Start a build on the remote balenaCloud builders, or a local mode balena device.
|
Start a build on the remote balenaCloud build servers, or a local mode device.
|
||||||
|
|
||||||
When building on the balenaCloud servers, the given source directory will be
|
When building on the balenaCloud servers, the given source directory will be
|
||||||
sent to the remote server. This can be used as a drop-in replacement for the
|
sent to the remote server. This can be used as a drop-in replacement for the
|
||||||
@ -95,6 +97,7 @@ export default class PushCmd extends Command {
|
|||||||
'$ balena push myApp --source <source directory>',
|
'$ balena push myApp --source <source directory>',
|
||||||
'$ balena push myApp -s <source directory>',
|
'$ balena push myApp -s <source directory>',
|
||||||
'$ balena push myApp --release-tag key1 "" key2 "value2 with spaces"',
|
'$ balena push myApp --release-tag key1 "" key2 "value2 with spaces"',
|
||||||
|
'$ balena push myorg/myapp',
|
||||||
'',
|
'',
|
||||||
'$ balena push 10.0.0.1',
|
'$ balena push 10.0.0.1',
|
||||||
'$ balena push 10.0.0.1 --source <source directory>',
|
'$ balena push 10.0.0.1 --source <source directory>',
|
||||||
@ -109,8 +112,10 @@ export default class PushCmd extends Command {
|
|||||||
public static args = [
|
public static args = [
|
||||||
{
|
{
|
||||||
name: 'applicationOrDevice',
|
name: 'applicationOrDevice',
|
||||||
description: 'application name, or device address (for local pushes)',
|
description:
|
||||||
|
'application name or slug, or local device IP address or hostname',
|
||||||
required: true,
|
required: true,
|
||||||
|
parse: lowercaseIfSlug,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -122,12 +127,14 @@ export default class PushCmd extends Command {
|
|||||||
Source directory to be sent to balenaCloud or balenaOS device
|
Source directory to be sent to balenaCloud or balenaOS device
|
||||||
(default: current working dir)`,
|
(default: current working dir)`,
|
||||||
char: 's',
|
char: 's',
|
||||||
|
default: '.',
|
||||||
}),
|
}),
|
||||||
emulated: flags.boolean({
|
emulated: flags.boolean({
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
Don't use native ARM servers; force QEMU ARM emulation on Intel x86-64
|
Don't use native ARM servers; force QEMU ARM emulation on Intel x86-64
|
||||||
servers during the image build (balenaCloud).`,
|
servers during the image build (balenaCloud).`,
|
||||||
char: 'e',
|
char: 'e',
|
||||||
|
default: false,
|
||||||
}),
|
}),
|
||||||
dockerfile: flags.string({
|
dockerfile: flags.string({
|
||||||
description:
|
description:
|
||||||
@ -142,15 +149,18 @@ export default class PushCmd extends Command {
|
|||||||
updates), but the logs will not display the "Using cache" lines for each
|
updates), but the logs will not display the "Using cache" lines for each
|
||||||
build step of a Dockerfile.`,
|
build step of a Dockerfile.`,
|
||||||
char: 'c',
|
char: 'c',
|
||||||
|
default: false,
|
||||||
}),
|
}),
|
||||||
pull: flags.boolean({
|
pull: flags.boolean({
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
When pushing to a local device, force the base images to be pulled again.
|
When pushing to a local device, force the base images to be pulled again.
|
||||||
Currently this option is ignored when pushing to the balenaCloud builders.`,
|
Currently this option is ignored when pushing to the balenaCloud builders.`,
|
||||||
|
default: false,
|
||||||
}),
|
}),
|
||||||
'noparent-check': flags.boolean({
|
'noparent-check': flags.boolean({
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
Disable project validation check of 'docker-compose.yml' file in parent folder`,
|
Disable project validation check of 'docker-compose.yml' file in parent folder`,
|
||||||
|
default: false,
|
||||||
}),
|
}),
|
||||||
'registry-secrets': flags.string({
|
'registry-secrets': flags.string({
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
@ -166,6 +176,7 @@ export default class PushCmd extends Command {
|
|||||||
and changes will not be synchronized to any running containers. Note that both
|
and changes will not be synchronized to any running containers. Note that both
|
||||||
this flag and --detached and required to cause the process to end once the
|
this flag and --detached and required to cause the process to end once the
|
||||||
initial build has completed.`,
|
initial build has completed.`,
|
||||||
|
default: false,
|
||||||
}),
|
}),
|
||||||
detached: flags.boolean({
|
detached: flags.boolean({
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
@ -174,6 +185,7 @@ export default class PushCmd extends Command {
|
|||||||
applicable). When pushing to a local mode device, this option will cause
|
applicable). When pushing to a local mode device, this option will cause
|
||||||
the command to not tail application logs when the build has completed.`,
|
the command to not tail application logs when the build has completed.`,
|
||||||
char: 'd',
|
char: 'd',
|
||||||
|
default: false,
|
||||||
}),
|
}),
|
||||||
service: flags.string({
|
service: flags.string({
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
@ -186,6 +198,7 @@ export default class PushCmd extends Command {
|
|||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
Only show system logs. This can be used in combination with --service.
|
Only show system logs. This can be used in combination with --service.
|
||||||
Only valid when pushing to a local mode device.`,
|
Only valid when pushing to a local mode device.`,
|
||||||
|
default: false,
|
||||||
}),
|
}),
|
||||||
env: flags.string({
|
env: flags.string({
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
@ -199,25 +212,37 @@ export default class PushCmd extends Command {
|
|||||||
`,
|
`,
|
||||||
multiple: true,
|
multiple: true,
|
||||||
}),
|
}),
|
||||||
|
...(isV13()
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
'convert-eol': flags.boolean({
|
'convert-eol': flags.boolean({
|
||||||
description: 'No-op and deprecated since balena CLI v12.0.0',
|
description: 'No-op and deprecated since balena CLI v12.0.0',
|
||||||
char: 'l',
|
char: 'l',
|
||||||
hidden: true,
|
hidden: true,
|
||||||
|
default: false,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
'noconvert-eol': flags.boolean({
|
'noconvert-eol': flags.boolean({
|
||||||
description: `Don't convert line endings from CRLF (Windows format) to LF (Unix format).`,
|
description: `Don't convert line endings from CRLF (Windows format) to LF (Unix format).`,
|
||||||
|
default: false,
|
||||||
}),
|
}),
|
||||||
'multi-dockerignore': flags.boolean({
|
'multi-dockerignore': flags.boolean({
|
||||||
description:
|
description:
|
||||||
'Have each service use its own .dockerignore file. See "balena help push".',
|
'Have each service use its own .dockerignore file. See "balena help push".',
|
||||||
char: 'm',
|
char: 'm',
|
||||||
|
default: false,
|
||||||
exclusive: ['gitignore'],
|
exclusive: ['gitignore'],
|
||||||
}),
|
}),
|
||||||
|
...(isV13()
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
nogitignore: flags.boolean({
|
nogitignore: flags.boolean({
|
||||||
description:
|
description:
|
||||||
'No-op (default behavior) since balena CLI v12.0.0. See "balena help push".',
|
'No-op (default behavior) since balena CLI v12.0.0. See "balena help push".',
|
||||||
char: 'G',
|
char: 'G',
|
||||||
hidden: true,
|
hidden: true,
|
||||||
|
default: false,
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
gitignore: flags.boolean({
|
gitignore: flags.boolean({
|
||||||
description: stripIndent`
|
description: stripIndent`
|
||||||
@ -225,6 +250,7 @@ export default class PushCmd extends Command {
|
|||||||
to the CLI v11 behavior/implementation (deprecated) if compatibility is
|
to the CLI v11 behavior/implementation (deprecated) if compatibility is
|
||||||
required until your project can be adapted.`,
|
required until your project can be adapted.`,
|
||||||
char: 'g',
|
char: 'g',
|
||||||
|
default: false,
|
||||||
exclusive: ['multi-dockerignore'],
|
exclusive: ['multi-dockerignore'],
|
||||||
}),
|
}),
|
||||||
'release-tag': flags.string({
|
'release-tag': flags.string({
|
||||||
@ -246,75 +272,59 @@ export default class PushCmd extends Command {
|
|||||||
PushCmd,
|
PushCmd,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const logger = await Command.getLogger();
|
||||||
|
logger.logDebug(`Using build source directory: ${options.source} `);
|
||||||
|
|
||||||
const sdk = getBalenaSdk();
|
const sdk = getBalenaSdk();
|
||||||
const { validateProjectDirectory } = await import('../utils/compose_ts');
|
const { validateProjectDirectory } = await import('../utils/compose_ts');
|
||||||
|
|
||||||
const source = options.source || '.';
|
|
||||||
if (process.env.DEBUG) {
|
|
||||||
console.error(`[debug] Using ${source} as build source`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
|
const { dockerfilePath, registrySecrets } = await validateProjectDirectory(
|
||||||
sdk,
|
sdk,
|
||||||
{
|
{
|
||||||
dockerfilePath: options.dockerfile,
|
dockerfilePath: options.dockerfile,
|
||||||
noParentCheck: options['noparent-check'] || false,
|
noParentCheck: options['noparent-check'],
|
||||||
projectPath: source,
|
projectPath: options.source,
|
||||||
registrySecretsPath: options['registry-secrets'],
|
registrySecretsPath: options['registry-secrets'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const nogitignore = !options.gitignore;
|
switch (await this.getBuildTarget(params.applicationOrDevice)) {
|
||||||
const convertEol = !options['noconvert-eol'];
|
|
||||||
|
|
||||||
const appOrDevice = params.applicationOrDevice;
|
|
||||||
const buildTarget = await this.getBuildTarget(appOrDevice);
|
|
||||||
switch (buildTarget) {
|
|
||||||
case BuildTarget.Cloud:
|
case BuildTarget.Cloud:
|
||||||
|
logger.logDebug(
|
||||||
|
`Pushing to cloud for application: ${params.applicationOrDevice}`,
|
||||||
|
);
|
||||||
|
|
||||||
await this.pushToCloud(
|
await this.pushToCloud(
|
||||||
|
params.applicationOrDevice,
|
||||||
options,
|
options,
|
||||||
sdk,
|
sdk,
|
||||||
appOrDevice,
|
|
||||||
dockerfilePath,
|
dockerfilePath,
|
||||||
registrySecrets,
|
registrySecrets,
|
||||||
convertEol,
|
|
||||||
source,
|
|
||||||
nogitignore,
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case BuildTarget.Device:
|
case BuildTarget.Device:
|
||||||
|
logger.logDebug(
|
||||||
|
`Pushing to local device: ${params.applicationOrDevice}`,
|
||||||
|
);
|
||||||
await this.pushToDevice(
|
await this.pushToDevice(
|
||||||
|
params.applicationOrDevice,
|
||||||
options,
|
options,
|
||||||
sdk,
|
|
||||||
appOrDevice,
|
|
||||||
dockerfilePath,
|
dockerfilePath,
|
||||||
registrySecrets,
|
registrySecrets,
|
||||||
convertEol,
|
|
||||||
source,
|
|
||||||
nogitignore,
|
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
|
||||||
throw new ExpectedError(stripIndent`
|
|
||||||
Build target not recognized. Please provide either an application name or
|
|
||||||
device IP address.`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async pushToCloud(
|
protected async pushToCloud(
|
||||||
|
appNameOrSlug: string,
|
||||||
options: FlagsDef,
|
options: FlagsDef,
|
||||||
sdk: BalenaSDK,
|
sdk: BalenaSDK,
|
||||||
appOrDevice: string,
|
|
||||||
dockerfilePath: string,
|
dockerfilePath: string,
|
||||||
registrySecrets: RegistrySecrets,
|
registrySecrets: RegistrySecrets,
|
||||||
convertEol: boolean,
|
|
||||||
source: string,
|
|
||||||
nogitignore: boolean,
|
|
||||||
) {
|
) {
|
||||||
const _ = await import('lodash');
|
|
||||||
const remote = await import('../utils/remote-build');
|
const remote = await import('../utils/remote-build');
|
||||||
|
const { getApplication } = await import('../utils/sdk');
|
||||||
|
|
||||||
// Check for invalid options
|
// Check for invalid options
|
||||||
const localOnlyOptions: Array<keyof FlagsDef> = [
|
const localOnlyOptions: Array<keyof FlagsDef> = [
|
||||||
@ -347,36 +357,46 @@ export default class PushCmd extends Command {
|
|||||||
releaseTagValues.push('');
|
releaseTagValues.push('');
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = appOrDevice;
|
|
||||||
await Command.checkLoggedIn();
|
await Command.checkLoggedIn();
|
||||||
const [token, baseUrl, owner] = await Promise.all([
|
const [token, baseUrl] = await Promise.all([
|
||||||
sdk.auth.getToken(),
|
sdk.auth.getToken(),
|
||||||
sdk.settings.get('balenaUrl'),
|
sdk.settings.get('balenaUrl'),
|
||||||
this.getAppOwner(sdk, app),
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const application = (await getApplication(sdk, appNameOrSlug, {
|
||||||
|
$expand: {
|
||||||
|
organization: {
|
||||||
|
$select: ['handle'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$select: ['app_name'],
|
||||||
|
})) as Application & {
|
||||||
|
organization: [Organization];
|
||||||
|
};
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
dockerfilePath,
|
dockerfilePath,
|
||||||
emulated: options.emulated || false,
|
emulated: options.emulated,
|
||||||
multiDockerignore: options['multi-dockerignore'] || false,
|
multiDockerignore: options['multi-dockerignore'],
|
||||||
nocache: options.nocache || false,
|
nocache: options.nocache,
|
||||||
registrySecrets,
|
registrySecrets,
|
||||||
headless: options.detached || false,
|
headless: options.detached,
|
||||||
convertEol,
|
convertEol: !options['noconvert-eol'],
|
||||||
};
|
};
|
||||||
const args = {
|
const args = {
|
||||||
app,
|
app: application.app_name,
|
||||||
owner,
|
owner: application.organization[0].handle,
|
||||||
source,
|
source: options.source,
|
||||||
auth: token,
|
auth: token,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
nogitignore,
|
nogitignore: !options.gitignore,
|
||||||
sdk,
|
sdk,
|
||||||
opts,
|
opts,
|
||||||
};
|
};
|
||||||
const releaseId = await remote.startRemoteBuild(args);
|
const releaseId = await remote.startRemoteBuild(args);
|
||||||
if (releaseId) {
|
if (releaseId) {
|
||||||
// Above we have checked that releaseTagKeys and releaseTagValues are of the same size
|
// Above we have checked that releaseTagKeys and releaseTagValues are of the same size
|
||||||
|
const _ = await import('lodash');
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
(_.zip(releaseTagKeys, releaseTagValues) as Array<
|
(_.zip(releaseTagKeys, releaseTagValues) as Array<
|
||||||
[string, string]
|
[string, string]
|
||||||
@ -391,15 +411,11 @@ export default class PushCmd extends Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async pushToDevice(
|
protected async pushToDevice(
|
||||||
|
localDeviceAddress: string,
|
||||||
options: FlagsDef,
|
options: FlagsDef,
|
||||||
_sdk: BalenaSDK,
|
|
||||||
appOrDevice: string,
|
|
||||||
dockerfilePath: string,
|
dockerfilePath: string,
|
||||||
registrySecrets: RegistrySecrets,
|
registrySecrets: RegistrySecrets,
|
||||||
convertEol: boolean,
|
|
||||||
source: string,
|
|
||||||
nogitignore: boolean,
|
|
||||||
) {
|
) {
|
||||||
// Check for invalid options
|
// Check for invalid options
|
||||||
const remoteOnlyOptions: Array<keyof FlagsDef> = ['release-tag'];
|
const remoteOnlyOptions: Array<keyof FlagsDef> = ['release-tag'];
|
||||||
@ -410,27 +426,24 @@ export default class PushCmd extends Command {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const deviceDeploy = await import('../utils/device/deploy');
|
const deviceDeploy = await import('../utils/device/deploy');
|
||||||
const device = appOrDevice;
|
|
||||||
const servicesToDisplay = options.service;
|
|
||||||
|
|
||||||
// TODO: Support passing a different port
|
|
||||||
try {
|
try {
|
||||||
await deviceDeploy.deployToDevice({
|
await deviceDeploy.deployToDevice({
|
||||||
source,
|
source: options.source,
|
||||||
deviceHost: device,
|
deviceHost: localDeviceAddress,
|
||||||
dockerfilePath,
|
dockerfilePath,
|
||||||
registrySecrets,
|
registrySecrets,
|
||||||
multiDockerignore: options['multi-dockerignore'] || false,
|
multiDockerignore: options['multi-dockerignore'],
|
||||||
nocache: options.nocache || false,
|
nocache: options.nocache,
|
||||||
pull: options.pull || false,
|
pull: options.pull,
|
||||||
nogitignore,
|
nogitignore: !options.gitignore,
|
||||||
noParentCheck: options['noparent-check'] || false,
|
noParentCheck: options['noparent-check'],
|
||||||
nolive: options.nolive || false,
|
nolive: options.nolive,
|
||||||
detached: options.detached || false,
|
detached: options.detached,
|
||||||
services: servicesToDisplay,
|
services: options.service,
|
||||||
system: options.system || false,
|
system: options.system,
|
||||||
env: options.env || [],
|
env: options.env || [],
|
||||||
convertEol,
|
convertEol: !options['noconvert-eol'],
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { BuildError } = await import('../utils/device/errors');
|
const { BuildError } = await import('../utils/device/errors');
|
||||||
@ -442,80 +455,15 @@ export default class PushCmd extends Command {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBuildTarget(appOrDevice: string): Promise<BuildTarget | null> {
|
protected async getBuildTarget(appOrDevice: string): Promise<BuildTarget> {
|
||||||
const {
|
const { validateLocalHostnameOrIp } = await import('../utils/validation');
|
||||||
validateApplicationName,
|
|
||||||
validateDotLocalUrl,
|
|
||||||
validateIPAddress,
|
|
||||||
} = await import('../utils/validation');
|
|
||||||
|
|
||||||
// First try the application regex from the api
|
return validateLocalHostnameOrIp(appOrDevice)
|
||||||
if (validateApplicationName(appOrDevice)) {
|
? BuildTarget.Device
|
||||||
return BuildTarget.Cloud;
|
: BuildTarget.Cloud;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validateIPAddress(appOrDevice) || validateDotLocalUrl(appOrDevice)) {
|
protected checkInvalidOptions(
|
||||||
return BuildTarget.Device;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAppOwner(sdk: BalenaSDK, appName: string) {
|
|
||||||
const _ = await import('lodash');
|
|
||||||
|
|
||||||
const applications = (await sdk.models.application.getAll({
|
|
||||||
$expand: {
|
|
||||||
organization: {
|
|
||||||
$select: ['handle'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
$filter: {
|
|
||||||
$eq: [{ $tolower: { $: 'app_name' } }, appName.toLowerCase()],
|
|
||||||
},
|
|
||||||
$select: ['id'],
|
|
||||||
})) as Array<
|
|
||||||
Application & {
|
|
||||||
organization: [Organization];
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
if (applications == null || applications.length === 0) {
|
|
||||||
throw new ExpectedError(
|
|
||||||
stripIndent`
|
|
||||||
No applications found with name: ${appName}.
|
|
||||||
|
|
||||||
This could mean that the application does not exist, or you do
|
|
||||||
not have the permissions required to access it.`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applications.length === 1) {
|
|
||||||
return applications[0].organization[0].handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we got more than one application with the same name it means that the
|
|
||||||
// user has access to a collab app with the same name as a personal app. We
|
|
||||||
// present a list to the user which shows the fully qualified application
|
|
||||||
// name (user/appname) and allows them to select
|
|
||||||
const entries = _.map(applications, (app) => {
|
|
||||||
const username = app.organization[0].handle;
|
|
||||||
return {
|
|
||||||
name: `${username}/${appName}`,
|
|
||||||
extra: username,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const { selectFromList } = await import('../utils/patterns');
|
|
||||||
const selected = await selectFromList(
|
|
||||||
`${entries.length} applications found with that name, please select the application you would like to push to`,
|
|
||||||
entries,
|
|
||||||
);
|
|
||||||
|
|
||||||
return selected.extra;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkInvalidOptions(
|
|
||||||
invalidOptions: Array<keyof FlagsDef>,
|
invalidOptions: Array<keyof FlagsDef>,
|
||||||
options: FlagsDef,
|
options: FlagsDef,
|
||||||
errorMessage: string,
|
errorMessage: string,
|
||||||
|
@ -117,3 +117,8 @@ export function parseAsLocalHostnameOrIp(input: string, paramName?: string) {
|
|||||||
|
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function looksLikeAppSlug(input: string) {
|
||||||
|
// One or more non whitespace chars, /, 4 or more non whitespace chars
|
||||||
|
return /[\S]+\/[\S]{4,}/.test(input);
|
||||||
|
}
|
||||||
|
@ -66,7 +66,7 @@ const commonResponseLines = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const commonQueryParams = [
|
const commonQueryParams = [
|
||||||
['owner', 'bob'],
|
['owner', 'gh_user'],
|
||||||
['app', 'testApp'],
|
['app', 'testApp'],
|
||||||
['dockerfilePath', ''],
|
['dockerfilePath', ''],
|
||||||
['emulated', 'false'],
|
['emulated', 'false'],
|
||||||
@ -87,7 +87,7 @@ describe('balena push', function () {
|
|||||||
builder = new BuilderMock();
|
builder = new BuilderMock();
|
||||||
api.expectGetWhoAmI({ optional: true, persist: true });
|
api.expectGetWhoAmI({ optional: true, persist: true });
|
||||||
api.expectGetMixpanel({ optional: true });
|
api.expectGetMixpanel({ optional: true });
|
||||||
api.expectGetMyApplication();
|
api.expectGetApplication();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.afterEach(() => {
|
this.afterEach(() => {
|
||||||
@ -145,7 +145,7 @@ describe('balena push', function () {
|
|||||||
|
|
||||||
await testPushBuildStream({
|
await testPushBuildStream({
|
||||||
builderMock: builder,
|
builderMock: builder,
|
||||||
commandLine: `push testApp --source ${projectPath} -R ${regSecretsPath} -G`,
|
commandLine: `push testApp --source ${projectPath} -R ${regSecretsPath}`,
|
||||||
expectedFiles,
|
expectedFiles,
|
||||||
expectedQueryParams: commonQueryParams,
|
expectedQueryParams: commonQueryParams,
|
||||||
expectedResponseLines,
|
expectedResponseLines,
|
||||||
@ -345,7 +345,7 @@ describe('balena push', function () {
|
|||||||
|
|
||||||
await testPushBuildStream({
|
await testPushBuildStream({
|
||||||
builderMock: builder,
|
builderMock: builder,
|
||||||
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} -G`,
|
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath}`,
|
||||||
expectedFiles,
|
expectedFiles,
|
||||||
expectedQueryParams: commonQueryParams,
|
expectedQueryParams: commonQueryParams,
|
||||||
expectedResponseLines: commonResponseLines[responseFilename],
|
expectedResponseLines: commonResponseLines[responseFilename],
|
||||||
|
@ -17,6 +17,11 @@
|
|||||||
},
|
},
|
||||||
"__id": 43699
|
"__id": 43699
|
||||||
},
|
},
|
||||||
|
"organization": [
|
||||||
|
{
|
||||||
|
"handle": "gh_user"
|
||||||
|
}
|
||||||
|
],
|
||||||
"depends_on__application": null,
|
"depends_on__application": null,
|
||||||
"actor": 3423895,
|
"actor": 3423895,
|
||||||
"app_name": "testApp",
|
"app_name": "testApp",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user