mirror of
https://github.com/balena-io/balena-cli.git
synced 2025-02-20 09:26:42 +00:00
build/deploy: Add more test cases (--buildArg option)
Change-type: patch
This commit is contained in:
parent
099d755900
commit
6b208ec2ab
@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
import { expect } from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import mock = require('mock-require');
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
@ -45,13 +46,16 @@ const commonResponseLines: { [key: string]: string[] } = {
|
||||
|
||||
const commonQueryParams = {
|
||||
t: '${tag}',
|
||||
buildargs: '{}',
|
||||
buildargs: {},
|
||||
labels: '',
|
||||
};
|
||||
|
||||
const commonComposeQueryParams = {
|
||||
t: '${tag}',
|
||||
buildargs: '{"MY_VAR_1":"This is a variable","MY_VAR_2":"Also a variable"}',
|
||||
buildargs: {
|
||||
MY_VAR_1: 'This is a variable',
|
||||
MY_VAR_2: 'Also a variable',
|
||||
},
|
||||
labels: '',
|
||||
};
|
||||
|
||||
@ -375,19 +379,26 @@ describe('balena build', function () {
|
||||
'utf8',
|
||||
);
|
||||
const expectedQueryParamsByService = {
|
||||
service1: Object.entries({
|
||||
...commonComposeQueryParams,
|
||||
buildargs:
|
||||
'{"BARG1":"b1","barg2":"B2","MY_VAR_1":"This is a variable","MY_VAR_2":"Also a variable","SERVICE1_VAR":"This is a service specific variable"}',
|
||||
cachefrom: '["my/img1","my/img2"]',
|
||||
}),
|
||||
service2: Object.entries({
|
||||
...commonComposeQueryParams,
|
||||
buildargs:
|
||||
'{"BARG1":"b1","barg2":"B2","MY_VAR_1":"This is a variable","MY_VAR_2":"Also a variable"}',
|
||||
cachefrom: '["my/img1","my/img2"]',
|
||||
dockerfile: 'Dockerfile-alt',
|
||||
}),
|
||||
service1: Object.entries(
|
||||
_.merge({}, commonComposeQueryParams, {
|
||||
buildargs: {
|
||||
COMPOSE_ARG: 'A',
|
||||
barg: 'b',
|
||||
SERVICE1_VAR: 'This is a service specific variable',
|
||||
},
|
||||
cachefrom: ['my/img1', 'my/img2'],
|
||||
}),
|
||||
),
|
||||
service2: Object.entries(
|
||||
_.merge({}, commonComposeQueryParams, {
|
||||
buildargs: {
|
||||
COMPOSE_ARG: 'A',
|
||||
barg: 'b',
|
||||
},
|
||||
cachefrom: ['my/img1', 'my/img2'],
|
||||
dockerfile: 'Dockerfile-alt',
|
||||
}),
|
||||
),
|
||||
};
|
||||
const expectedResponseLines: string[] = [
|
||||
...commonResponseLines[responseFilename],
|
||||
@ -417,7 +428,7 @@ describe('balena build', function () {
|
||||
}
|
||||
docker.expectGetInfo({});
|
||||
await testDockerBuildStream({
|
||||
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 --convert-eol -G -B BARG1=b1 -B barg2=B2 --cache-from my/img1,my/img2`,
|
||||
commandLine: `build ${projectPath} --deviceType nuc --arch amd64 --convert-eol -G -B COMPOSE_ARG=A -B barg=b --cache-from my/img1,my/img2`,
|
||||
dockerMock: docker,
|
||||
expectedFilesByService,
|
||||
expectedQueryParamsByService,
|
||||
@ -464,15 +475,19 @@ describe('balena build', function () {
|
||||
'utf8',
|
||||
);
|
||||
const expectedQueryParamsByService = {
|
||||
service1: Object.entries({
|
||||
...commonComposeQueryParams,
|
||||
buildargs:
|
||||
'{"MY_VAR_1":"This is a variable","MY_VAR_2":"Also a variable","SERVICE1_VAR":"This is a service specific variable"}',
|
||||
}),
|
||||
service2: Object.entries({
|
||||
...commonComposeQueryParams,
|
||||
dockerfile: 'Dockerfile-alt',
|
||||
}),
|
||||
service1: Object.entries(
|
||||
_.merge({}, commonComposeQueryParams, {
|
||||
buildargs: { SERVICE1_VAR: 'This is a service specific variable' },
|
||||
}),
|
||||
),
|
||||
service2: Object.entries(
|
||||
_.merge({}, commonComposeQueryParams, {
|
||||
buildargs: {
|
||||
COMPOSE_ARG: 'an argument defined in the docker-compose.yml file',
|
||||
},
|
||||
dockerfile: 'Dockerfile-alt',
|
||||
}),
|
||||
),
|
||||
};
|
||||
const expectedResponseLines: string[] = [
|
||||
...commonResponseLines[responseFilename],
|
||||
|
@ -17,6 +17,7 @@
|
||||
|
||||
import { expect } from 'chai';
|
||||
import { promises as fs } from 'fs';
|
||||
import * as _ from 'lodash';
|
||||
import * as path from 'path';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
@ -53,14 +54,14 @@ const commonQueryParams = [
|
||||
['labels', ''],
|
||||
];
|
||||
|
||||
const commonComposeQueryParams = [
|
||||
['t', '${tag}'],
|
||||
[
|
||||
'buildargs',
|
||||
'{"MY_VAR_1":"This is a variable","MY_VAR_2":"Also a variable"}',
|
||||
],
|
||||
['labels', ''],
|
||||
];
|
||||
const commonComposeQueryParams = {
|
||||
t: '${tag}',
|
||||
buildargs: {
|
||||
MY_VAR_1: 'This is a variable',
|
||||
MY_VAR_2: 'Also a variable',
|
||||
},
|
||||
labels: '',
|
||||
};
|
||||
|
||||
const hr =
|
||||
'----------------------------------------------------------------------';
|
||||
@ -268,15 +269,19 @@ describe('balena deploy', function () {
|
||||
'utf8',
|
||||
);
|
||||
const expectedQueryParamsByService = {
|
||||
service1: [
|
||||
['t', '${tag}'],
|
||||
[
|
||||
'buildargs',
|
||||
'{"MY_VAR_1":"This is a variable","MY_VAR_2":"Also a variable","SERVICE1_VAR":"This is a service specific variable"}',
|
||||
],
|
||||
['labels', ''],
|
||||
],
|
||||
service2: [...commonComposeQueryParams, ['dockerfile', 'Dockerfile-alt']],
|
||||
service1: Object.entries(
|
||||
_.merge({}, commonComposeQueryParams, {
|
||||
buildargs: { SERVICE1_VAR: 'This is a service specific variable' },
|
||||
}),
|
||||
),
|
||||
service2: Object.entries(
|
||||
_.merge({}, commonComposeQueryParams, {
|
||||
buildargs: {
|
||||
COMPOSE_ARG: 'an argument defined in the docker-compose.yml file',
|
||||
},
|
||||
dockerfile: 'Dockerfile-alt',
|
||||
}),
|
||||
),
|
||||
};
|
||||
const expectedResponseLines: string[] = [
|
||||
...commonResponseLines[responseFilename],
|
||||
|
@ -465,7 +465,7 @@ describe('balena push', function () {
|
||||
const expectedFiles: ExpectedTarStreamFiles = {
|
||||
'.balena/balena.yml': { fileSize: 197, type: 'file' },
|
||||
'.dockerignore': { fileSize: 22, type: 'file' },
|
||||
'docker-compose.yml': { fileSize: 245, type: 'file' },
|
||||
'docker-compose.yml': { fileSize: 332, type: 'file' },
|
||||
'service1/Dockerfile.template': { fileSize: 144, type: 'file' },
|
||||
'service1/file1.sh': { fileSize: 12, type: 'file' },
|
||||
'service2/Dockerfile-alt': { fileSize: 40, type: 'file' },
|
||||
@ -523,7 +523,7 @@ describe('balena push', function () {
|
||||
const expectedFiles: ExpectedTarStreamFiles = {
|
||||
'.balena/balena.yml': { fileSize: 197, type: 'file' },
|
||||
'.dockerignore': { fileSize: 22, type: 'file' },
|
||||
'docker-compose.yml': { fileSize: 245, type: 'file' },
|
||||
'docker-compose.yml': { fileSize: 332, type: 'file' },
|
||||
'service1/Dockerfile.template': { fileSize: 144, type: 'file' },
|
||||
'service1/file1.sh': { fileSize: 12, type: 'file' },
|
||||
'service1/test-ignore.txt': { fileSize: 12, type: 'file' },
|
||||
|
@ -29,7 +29,12 @@ import { URL } from 'url';
|
||||
import { stripIndent } from '../lib/utils/lazy';
|
||||
import { BuilderMock } from './builder-mock';
|
||||
import { DockerMock } from './docker-mock';
|
||||
import { cleanOutput, fillTemplateArray, runCommand } from './helpers';
|
||||
import {
|
||||
cleanOutput,
|
||||
deepJsonParse,
|
||||
deepTemplateReplace,
|
||||
runCommand,
|
||||
} from './helpers';
|
||||
import {
|
||||
ExpectedTarStreamFile,
|
||||
ExpectedTarStreamFiles,
|
||||
@ -152,7 +157,7 @@ export async function testDockerBuildStream(o: {
|
||||
commandLine: string;
|
||||
dockerMock: DockerMock;
|
||||
expectedFilesByService: ExpectedTarStreamFilesByService;
|
||||
expectedQueryParamsByService: { [service: string]: string[][] };
|
||||
expectedQueryParamsByService: { [service: string]: any[][] };
|
||||
expectedErrorLines?: string[];
|
||||
expectedExitCode?: number;
|
||||
expectedResponseLines: string[];
|
||||
@ -161,15 +166,15 @@ export async function testDockerBuildStream(o: {
|
||||
responseBody: string;
|
||||
services: string[]; // e.g. ['main'] or ['service1', 'service2']
|
||||
}) {
|
||||
const expectedErrorLines = fillTemplateArray(o.expectedErrorLines || [], o);
|
||||
const expectedResponseLines = fillTemplateArray(o.expectedResponseLines, o);
|
||||
const expectedErrorLines = deepTemplateReplace(o.expectedErrorLines || [], o);
|
||||
const expectedResponseLines = deepTemplateReplace(o.expectedResponseLines, o);
|
||||
|
||||
for (const service of o.services) {
|
||||
// tagPrefix is, for example, 'myApp' if the path is 'path/to/myApp'
|
||||
const tagPrefix = o.projectPath.split(path.sep).pop();
|
||||
const tag = `${tagPrefix}_${service}`;
|
||||
const expectedFiles = o.expectedFilesByService[service];
|
||||
const expectedQueryParams = fillTemplateArray(
|
||||
const expectedQueryParams = deepTemplateReplace(
|
||||
o.expectedQueryParamsByService[service],
|
||||
{ tag, ...o },
|
||||
);
|
||||
@ -181,7 +186,9 @@ export async function testDockerBuildStream(o: {
|
||||
checkURI: async (uri: string) => {
|
||||
const url = new URL(uri, 'http://test.net/');
|
||||
const queryParams = Array.from(url.searchParams.entries());
|
||||
expect(queryParams).to.have.deep.members(expectedQueryParams);
|
||||
expect(deepJsonParse(queryParams)).to.have.deep.members(
|
||||
deepJsonParse(expectedQueryParams),
|
||||
);
|
||||
},
|
||||
checkBuildRequestBody: (buildRequestBody: string) =>
|
||||
inspectTarStream(buildRequestBody, expectedFiles, projectPath),
|
||||
@ -226,15 +233,17 @@ export async function testPushBuildStream(o: {
|
||||
responseCode: number;
|
||||
responseBody: string;
|
||||
}) {
|
||||
const expectedQueryParams = fillTemplateArray(o.expectedQueryParams, o);
|
||||
const expectedResponseLines = fillTemplateArray(o.expectedResponseLines, o);
|
||||
const expectedQueryParams = deepTemplateReplace(o.expectedQueryParams, o);
|
||||
const expectedResponseLines = deepTemplateReplace(o.expectedResponseLines, o);
|
||||
|
||||
o.builderMock.expectPostBuild({
|
||||
...o,
|
||||
checkURI: async (uri: string) => {
|
||||
const url = new URL(uri, 'http://test.net/');
|
||||
const queryParams = Array.from(url.searchParams.entries());
|
||||
expect(queryParams).to.have.deep.members(expectedQueryParams);
|
||||
expect(deepJsonParse(queryParams)).to.have.deep.members(
|
||||
deepJsonParse(expectedQueryParams),
|
||||
);
|
||||
},
|
||||
checkBuildRequestBody: (buildRequestBody) =>
|
||||
inspectTarStream(buildRequestBody, o.expectedFiles, o.projectPath),
|
||||
|
@ -47,6 +47,7 @@ function filterCliOutputForTests(testOutput: TestOutput): TestOutput {
|
||||
// TODO stop this warning message from appearing when running
|
||||
// sdk.setSharedOptions multiple times in the same process
|
||||
!line.startsWith('Shared SDK options') &&
|
||||
!line.startsWith('WARN: disabling Sentry.io error reporting') &&
|
||||
// Node 12: '[DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated'
|
||||
!line.includes('[DEP0066]'),
|
||||
),
|
||||
@ -264,23 +265,55 @@ export function fillTemplate(
|
||||
return unescaped;
|
||||
}
|
||||
|
||||
export function fillTemplateArray(
|
||||
templateStringArray: string[],
|
||||
templateVars: object,
|
||||
): string[];
|
||||
export function fillTemplateArray(
|
||||
templateStringArray: Array<string | string[]>,
|
||||
templateVars: object,
|
||||
): Array<string | string[]>;
|
||||
export function fillTemplateArray(
|
||||
templateStringArray: Array<string | string[]>,
|
||||
templateVars: object,
|
||||
): Array<string | string[]> {
|
||||
return templateStringArray.map((i) =>
|
||||
Array.isArray(i)
|
||||
? fillTemplateArray(i, templateVars)
|
||||
: fillTemplate(i, templateVars),
|
||||
);
|
||||
/**
|
||||
* Recursively navigate the `data` argument (if it is an array or object),
|
||||
* finding and replacing "template strings" such as 'hello ${name}!' with
|
||||
* the variable values given in `templateVars` such as { name: 'world' }.
|
||||
*
|
||||
* @param data Any data type (array, object, string) containing template
|
||||
* strings to be replaced
|
||||
* @param templateVars Map of template variable names to values
|
||||
*/
|
||||
export function deepTemplateReplace(
|
||||
data: any,
|
||||
templateVars: { [key: string]: any },
|
||||
): any {
|
||||
switch (typeof data) {
|
||||
case 'string':
|
||||
return fillTemplate(data, templateVars);
|
||||
case 'object':
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((i) => deepTemplateReplace(i, templateVars));
|
||||
}
|
||||
return _.mapValues(data, (value) =>
|
||||
deepTemplateReplace(value, templateVars),
|
||||
);
|
||||
default:
|
||||
// number, undefined, null, or something else
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
export const fillTemplateArray = deepTemplateReplace;
|
||||
|
||||
/**
|
||||
* Recursively navigate the `data` argument (if it is an array or object),
|
||||
* looking for strings that start with `[` or `{` which are assumed to contain
|
||||
* JSON arrays or objects that are then parsed with JSON.parse().
|
||||
* @param data
|
||||
*/
|
||||
export function deepJsonParse(data: any): any {
|
||||
if (typeof data === 'string') {
|
||||
const maybeJson = data.trim();
|
||||
if (maybeJson.startsWith('{') || maybeJson.startsWith('[')) {
|
||||
return JSON.parse(maybeJson);
|
||||
}
|
||||
} else if (Array.isArray(data)) {
|
||||
return data.map((i) => deepJsonParse(i));
|
||||
} else if (typeof data === 'object') {
|
||||
return _.mapValues(data, (value) => deepJsonParse(value));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
export async function switchSentry(
|
||||
|
@ -12,3 +12,5 @@ services:
|
||||
build:
|
||||
context: ./service2
|
||||
dockerfile: Dockerfile-alt
|
||||
args:
|
||||
- 'COMPOSE_ARG=an argument defined in the docker-compose.yml file'
|
||||
|
Loading…
x
Reference in New Issue
Block a user