Add runtime warning for unused .dockerignore files

Change-type: patch
This commit is contained in:
Paulo Castro 2020-06-20 23:05:59 +01:00
parent 2859d16b31
commit 11d1a3f5a0
7 changed files with 238 additions and 166 deletions

View File

@ -193,8 +193,12 @@ export async function tarDirectory(
readFile = fs.readFile; readFile = fs.readFile;
} }
const pack = tar.pack(); const pack = tar.pack();
const fileStatsList = await filterFilesWithDockerignore(dir); const {
for (const fileStats of fileStatsList) { filteredFileList,
dockerignoreFiles,
} = await filterFilesWithDockerignore(dir);
printDockerignoreWarn(dockerignoreFiles);
for (const fileStats of filteredFileList) {
pack.entry( pack.entry(
{ {
name: toPosixPath(fileStats.relPath), name: toPosixPath(fileStats.relPath),
@ -212,9 +216,40 @@ export async function tarDirectory(
return pack; return pack;
} }
export function printDockerignoreWarn(
dockerignoreFiles: Array<import('./ignore').FileStats>,
) {
const nonRootFiles = dockerignoreFiles.filter(
(fileStats: import('./ignore').FileStats) => {
const dirname = path.dirname(fileStats.relPath);
return !!dirname && dirname !== '.';
},
);
if (nonRootFiles.length === 0) {
return;
}
const hr =
'-------------------------------------------------------------------------------';
const msg = [
' ',
hr,
'The following .dockerignore file(s) will not be used:',
];
msg.push(...nonRootFiles.map((fileStats) => `* ${fileStats.filePath}`));
msg.push(stripIndent`
Only one .dockerignore file at the source folder (project root) is used.
Additional .dockerignore files are disregarded. Microservices (multicontainer)
apps should place the .dockerignore file alongside the docker-compose.yml file.
See issue: https://github.com/balena-io/balena-cli/issues/1870
See also CLI v12 release notes: https://git.io/Jf7hz
`);
msg.push(hr);
Logger.getLogger().logWarn(msg.join('\n'));
}
/** /**
* Print a deprecation warning if any '.gitignore' or '.dockerignore' file is * Print a deprecation warning if any '.gitignore' or '.dockerignore' file is
* found and the --gitignore (-g) option has been provided. * found and the --gitignore (-g) option has been provided (v11 compatibility).
* @param dockerignoreFile Absolute path to a .dockerignore file * @param dockerignoreFile Absolute path to a .dockerignore file
* @param gitignoreFiles Array of absolute paths to .gitginore files * @param gitignoreFiles Array of absolute paths to .gitginore files
*/ */
@ -229,22 +264,22 @@ export function printGitignoreWarn(
const hr = const hr =
'-------------------------------------------------------------------------------'; '-------------------------------------------------------------------------------';
const msg = [' ', hr, 'Using file ignore patterns from:']; const msg = [' ', hr, 'Using file ignore patterns from:'];
msg.push(...ignoreFiles); msg.push(...ignoreFiles.map((e) => `* ${e}`));
if (gitignoreFiles.length) { if (gitignoreFiles.length) {
msg.push(stripIndent` msg.push(stripIndent`
Note: .gitignore files are being considered because the --gitignore option was .gitignore files are being considered because the --gitignore option was used.
used. This option is deprecated and will be removed in the next major version This option is deprecated and will be removed in the next major version release.
release. For more information, see 'balena help ${Logger.command}'. For more information, see 'balena help ${Logger.command}'.
`); `);
msg.push(hr); msg.push(hr);
Logger.getLogger().logWarn(msg.join('\n')); Logger.getLogger().logWarn(msg.join('\n'));
} else if (dockerignoreFile && process.platform === 'win32') { } else if (dockerignoreFile && process.platform === 'win32') {
msg.push(stripIndent` msg.push(stripIndent`
The --gitignore option was used, but not .gitignore files were found. The --gitignore option was used, but no .gitignore files were found.
The --gitignore option is deprecated and will be removed in the next major The --gitignore option is deprecated and will be removed in the next major
version release. It prevents the use of a better dockerignore parser and version release. It prevents the use of a better dockerignore parser and
filter library that fixes several issues on Windows and improves compatibility filter library that fixes several issues on Windows and improves compatibility
with "docker build". For more information, see 'balena help ${Logger.command}'. with 'docker build'. For more information, see 'balena help ${Logger.command}'.
`); `);
msg.push(hr); msg.push(hr);
Logger.getLogger().logWarn(msg.join('\n')); Logger.getLogger().logWarn(msg.join('\n'));

View File

@ -190,7 +190,7 @@ export class FileIgnorer {
} }
} }
interface FileStats { export interface FileStats {
filePath: string; filePath: string;
relPath: string; relPath: string;
stats: fs.Stats; stats: fs.Stats;
@ -256,7 +256,7 @@ async function readDockerIgnoreFile(projectDir: string): Promise<string> {
*/ */
export async function filterFilesWithDockerignore( export async function filterFilesWithDockerignore(
projectDir: string, projectDir: string,
): Promise<FileStats[]> { ): Promise<{ filteredFileList: FileStats[]; dockerignoreFiles: FileStats[] }> {
// path.resolve() also converts forward slashes to backslashes on Windows // path.resolve() also converts forward slashes to backslashes on Windows
projectDir = path.resolve(projectDir); projectDir = path.resolve(projectDir);
const dockerIgnoreStr = await readDockerIgnoreFile(projectDir); const dockerIgnoreStr = await readDockerIgnoreFile(projectDir);
@ -276,5 +276,12 @@ export async function filterFilesWithDockerignore(
]); ]);
const files = await listFiles(projectDir); const files = await listFiles(projectDir);
return files.filter((file: FileStats) => !ig.ignores(file.relPath)); const dockerignoreFiles: FileStats[] = [];
const filteredFileList = files.filter((file: FileStats) => {
if (path.basename(file.relPath) === '.dockerignore') {
dockerignoreFiles.push(file);
}
return !ig.ignores(file.relPath);
});
return { filteredFileList, dockerignoreFiles };
} }

View File

@ -24,7 +24,6 @@ import mock = require('mock-require');
import { fs } from 'mz'; import { fs } from 'mz';
import * as path from 'path'; import * as path from 'path';
import { isV12 } from '../../build/utils/version';
import { BalenaAPIMock } from '../balena-api-mock'; import { BalenaAPIMock } from '../balena-api-mock';
import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build'; import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build';
import { DockerMock, dockerResponsePath } from '../docker-mock'; import { DockerMock, dockerResponsePath } from '../docker-mock';
@ -87,12 +86,12 @@ describe('balena build', function () {
it('should create the expected tar stream (single container)', async () => { it('should create the expected tar stream (single container)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const isV12W = isWindows && isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/.dockerignore': { fileSize: 16, type: 'file' },
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { 'src/windows-crlf.sh': {
fileSize: isV12W ? 68 : 70, fileSize: isWindows ? 68 : 70,
testStream: isV12W ? expectStreamNoCRLF : undefined, testStream: isWindows ? expectStreamNoCRLF : undefined,
type: 'file', type: 'file',
}, },
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
@ -107,13 +106,11 @@ describe('balena build', function () {
...commonResponseLines[responseFilename], ...commonResponseLines[responseFilename],
`[Info] No "docker-compose.yml" file found at "${projectPath}"`, `[Info] No "docker-compose.yml" file found at "${projectPath}"`,
`[Info] Creating default composition with source: "${projectPath}"`, `[Info] Creating default composition with source: "${projectPath}"`,
isV12() '[Build] main Step 1/4 : FROM busybox',
? '[Build] main Step 1/4 : FROM busybox'
: '[Build] main Image size: 1.14 MB',
]; ];
if (isWindows) { if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
if (isV12()) { if (isWindows) {
expectedResponseLines.push( expectedResponseLines.push(
`[Info] Converting line endings CRLF -> LF for file: ${fname}`, `[Info] Converting line endings CRLF -> LF for file: ${fname}`,
); );
@ -142,7 +139,6 @@ describe('balena build', function () {
// downloading and installing QEMU // downloading and installing QEMU
itSS('should create the expected tar stream (--emulated)', async () => { itSS('should create the expected tar stream (--emulated)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const isV12W = isWindows && isV12();
const transposedDockerfile = const transposedDockerfile =
stripIndent` stripIndent`
FROM busybox FROM busybox
@ -151,10 +147,11 @@ describe('balena build', function () {
RUN ["/tmp/qemu-execve","-execve","/bin/sh","-c","chmod a+x /usr/src/*.sh"] RUN ["/tmp/qemu-execve","-execve","/bin/sh","-c","chmod a+x /usr/src/*.sh"]
CMD ["/usr/src/start.sh"]` + '\n'; CMD ["/usr/src/start.sh"]` + '\n';
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/.dockerignore': { fileSize: 16, type: 'file' },
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { 'src/windows-crlf.sh': {
fileSize: isV12W ? 68 : 70, fileSize: isWindows ? 68 : 70,
testStream: isV12W ? expectStreamNoCRLF : undefined, testStream: isWindows ? expectStreamNoCRLF : undefined,
type: 'file', type: 'file',
}, },
Dockerfile: { Dockerfile: {
@ -174,23 +171,25 @@ describe('balena build', function () {
`[Info] Creating default composition with source: "${projectPath}"`, `[Info] Creating default composition with source: "${projectPath}"`,
'[Info] Building for rpi/raspberry-pi', '[Info] Building for rpi/raspberry-pi',
'[Info] Emulation is enabled', '[Info] Emulation is enabled',
isV12() ...[
? '[Build] main Step 1/4 : FROM busybox' '[Warn] -------------------------------------------------------------------------------',
: '[Build] main Image size: 1.14 MB', '[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] Only one .dockerignore file at the source folder (project root) is used.',
'[Warn] Additional .dockerignore files are disregarded. Microservices (multicontainer)',
'[Warn] apps should place the .dockerignore file alongside the docker-compose.yml file.',
'[Warn] See issue: https://github.com/balena-io/balena-cli/issues/1870',
'[Warn] See also CLI v12 release notes: https://git.io/Jf7hz',
'[Warn] -------------------------------------------------------------------------------',
],
'[Build] main Step 1/4 : FROM busybox',
'[Success] Build succeeded!', '[Success] Build succeeded!',
]; ];
if (isWindows) { if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
if (isV12()) { expectedResponseLines.push(
expectedResponseLines.push( `[Info] Converting line endings CRLF -> LF for file: ${fname}`,
`[Info] Converting line endings CRLF -> LF for file: ${fname}`, );
);
} else {
expectedResponseLines.push(
`[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
);
}
} }
const arch = 'rpi'; const arch = 'rpi';
const deviceType = 'raspberry-pi'; const deviceType = 'raspberry-pi';
@ -230,12 +229,11 @@ describe('balena build', function () {
it('should create the expected tar stream (single container, --[no]convert-eol)', async () => { it('should create the expected tar stream (single container, --[no]convert-eol)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const eol = isWindows && !isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/.dockerignore': { fileSize: 16, type: 'file' },
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { 'src/windows-crlf.sh': {
fileSize: eol ? 68 : 70, fileSize: 70,
testStream: eol ? expectStreamNoCRLF : undefined,
type: 'file', type: 'file',
}, },
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
@ -250,28 +248,29 @@ describe('balena build', function () {
...commonResponseLines[responseFilename], ...commonResponseLines[responseFilename],
`[Info] No "docker-compose.yml" file found at "${projectPath}"`, `[Info] No "docker-compose.yml" file found at "${projectPath}"`,
`[Info] Creating default composition with source: "${projectPath}"`, `[Info] Creating default composition with source: "${projectPath}"`,
isV12() ...[
? '[Build] main Step 1/4 : FROM busybox' '[Warn] -------------------------------------------------------------------------------',
: '[Build] main Image size: 1.14 MB', '[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] Only one .dockerignore file at the source folder (project root) is used.',
'[Warn] Additional .dockerignore files are disregarded. Microservices (multicontainer)',
'[Warn] apps should place the .dockerignore file alongside the docker-compose.yml file.',
'[Warn] See issue: https://github.com/balena-io/balena-cli/issues/1870',
'[Warn] See also CLI v12 release notes: https://git.io/Jf7hz',
'[Warn] -------------------------------------------------------------------------------',
],
'[Build] main Step 1/4 : FROM busybox',
]; ];
if (isWindows) { if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
if (isV12()) { expectedResponseLines.push(
expectedResponseLines.push( `[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
`[Warn] CRLF (Windows) line endings detected in file: ${fname}`, '[Warn] Windows-format line endings were detected in some files, but were not converted due to `--noconvert-eol` option.',
'[Warn] Windows-format line endings were detected in some files, but were not converted due to `--noconvert-eol` option.', );
);
} else {
expectedResponseLines.push(
`[Info] Converting line endings CRLF -> LF for file: ${fname}`,
);
}
} }
docker.expectGetInfo({}); docker.expectGetInfo({});
await testDockerBuildStream({ await testDockerBuildStream({
commandLine: isV12() commandLine: `build ${projectPath} --deviceType nuc --arch amd64 --noconvert-eol`,
? `build ${projectPath} --deviceType nuc --arch amd64 --noconvert-eol`
: `build ${projectPath} --deviceType nuc --arch amd64 --convert-eol`,
dockerMock: docker, dockerMock: docker,
expectedFilesByService: { main: expectedFiles }, expectedFilesByService: { main: expectedFiles },
expectedQueryParamsByService: { main: commonQueryParams }, expectedQueryParamsByService: { main: commonQueryParams },
@ -302,6 +301,7 @@ describe('balena build', function () {
'file1.sh': { fileSize: 12, type: 'file' }, 'file1.sh': { fileSize: 12, type: 'file' },
}, },
service2: { service2: {
'.dockerignore': { fileSize: 14, type: 'file' },
'Dockerfile-alt': { fileSize: 40, type: 'file' }, 'Dockerfile-alt': { fileSize: 40, type: 'file' },
'file2-crlf.sh': { 'file2-crlf.sh': {
fileSize: isWindows ? 12 : 14, fileSize: isWindows ? 12 : 14,
@ -328,15 +328,20 @@ describe('balena build', function () {
}; };
const expectedResponseLines: string[] = [ const expectedResponseLines: string[] = [
...commonResponseLines[responseFilename], ...commonResponseLines[responseFilename],
...(isV12() ...[
? [ '[Build] service1 Step 1/4 : FROM busybox',
'[Build] service1 Step 1/4 : FROM busybox', '[Build] service2 Step 1/4 : FROM busybox',
'[Build] service2 Step 1/4 : FROM busybox', ],
] ...[
: [ '[Warn] The following .dockerignore file(s) will not be used:',
`[Build] service1 Image size: 1.14 MB`, `[Warn] * ${path.join(projectPath, 'service2', '.dockerignore')}`,
`[Build] service2 Image size: 1.14 MB`, '[Warn] Only one .dockerignore file at the source folder (project root) is used.',
]), '[Warn] Additional .dockerignore files are disregarded. Microservices (multicontainer)',
'[Warn] apps should place the .dockerignore file alongside the docker-compose.yml file.',
'[Warn] See issue: https://github.com/balena-io/balena-cli/issues/1870',
'[Warn] See also CLI v12 release notes: https://git.io/Jf7hz',
'[Warn] -------------------------------------------------------------------------------',
],
]; ];
if (isWindows) { if (isWindows) {
expectedResponseLines.push( expectedResponseLines.push(

View File

@ -23,7 +23,6 @@ import { fs } from 'mz';
import * as path from 'path'; import * as path from 'path';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import { isV12 } from '../../build/utils/version';
import { BalenaAPIMock } from '../balena-api-mock'; import { BalenaAPIMock } from '../balena-api-mock';
import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build'; import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build';
import { DockerMock, dockerResponsePath } from '../docker-mock'; import { DockerMock, dockerResponsePath } from '../docker-mock';
@ -39,9 +38,7 @@ const commonResponseLines = {
'[Info] Docker Desktop detected (daemon architecture: "x86_64")', '[Info] Docker Desktop detected (daemon architecture: "x86_64")',
'[Info] Docker itself will determine and enable architecture emulation if required,', '[Info] Docker itself will determine and enable architecture emulation if required,',
'[Info] without balena-cli intervention and regardless of the --emulated option.', '[Info] without balena-cli intervention and regardless of the --emulated option.',
isV12() '[Build] main Step 1/4 : FROM busybox',
? '[Build] main Step 1/4 : FROM busybox'
: '[Build] main Image size: 1.14 MB',
'[Info] Creating release...', '[Info] Creating release...',
'[Info] Pushing images to registry...', '[Info] Pushing images to registry...',
'[Info] Saving release...', '[Info] Saving release...',
@ -59,20 +56,8 @@ const commonQueryParams = [
describe('balena deploy', function () { describe('balena deploy', function () {
let api: BalenaAPIMock; let api: BalenaAPIMock;
let docker: DockerMock; let docker: DockerMock;
let sentryStatus: boolean | undefined;
const isWindows = process.platform === 'win32'; const isWindows = process.platform === 'win32';
this.beforeAll(async () => {
sentryStatus = await switchSentry(false);
sinon.stub(process, 'exit');
});
this.afterAll(async () => {
await switchSentry(sentryStatus);
// @ts-ignore
process.exit.restore();
});
this.beforeEach(() => { this.beforeEach(() => {
api = new BalenaAPIMock(); api = new BalenaAPIMock();
docker = new DockerMock(); docker = new DockerMock();
@ -106,12 +91,12 @@ describe('balena deploy', function () {
it('should create the expected --build tar stream (single container)', async () => { it('should create the expected --build tar stream (single container)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const isV12W = isWindows && isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/.dockerignore': { fileSize: 16, type: 'file' },
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { 'src/windows-crlf.sh': {
fileSize: isV12W ? 68 : 70, fileSize: isWindows ? 68 : 70,
testStream: isV12W ? expectStreamNoCRLF : undefined, testStream: isWindows ? expectStreamNoCRLF : undefined,
type: 'file', type: 'file',
}, },
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
@ -126,19 +111,23 @@ describe('balena deploy', function () {
...commonResponseLines[responseFilename], ...commonResponseLines[responseFilename],
`[Info] No "docker-compose.yml" file found at "${projectPath}"`, `[Info] No "docker-compose.yml" file found at "${projectPath}"`,
`[Info] Creating default composition with source: "${projectPath}"`, `[Info] Creating default composition with source: "${projectPath}"`,
...[
'[Warn] -------------------------------------------------------------------------------',
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] Only one .dockerignore file at the source folder (project root) is used.',
'[Warn] Additional .dockerignore files are disregarded. Microservices (multicontainer)',
'[Warn] apps should place the .dockerignore file alongside the docker-compose.yml file.',
'[Warn] See issue: https://github.com/balena-io/balena-cli/issues/1870',
'[Warn] See also CLI v12 release notes: https://git.io/Jf7hz',
'[Warn] -------------------------------------------------------------------------------',
],
]; ];
if (isWindows) { if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
if (isV12()) { expectedResponseLines.push(
expectedResponseLines.push( `[Info] Converting line endings CRLF -> LF for file: ${fname}`,
`[Info] Converting line endings CRLF -> LF for file: ${fname}`, );
);
} else {
expectedResponseLines.push(
`[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
);
}
} }
api.expectPatchImage({}); api.expectPatchImage({});
@ -158,8 +147,10 @@ describe('balena deploy', function () {
}); });
it('should update a release with status="failed" on error (single container)', async () => { it('should update a release with status="failed" on error (single container)', async () => {
let sentryStatus: boolean | undefined;
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/.dockerignore': { fileSize: 16, type: 'file' },
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { fileSize: 70, type: 'file' }, 'src/windows-crlf.sh': { fileSize: 70, type: 'file' },
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
@ -199,24 +190,34 @@ describe('balena deploy', function () {
}, },
}); });
await testDockerBuildStream({ try {
commandLine: `deploy testApp --build --source ${projectPath} --noconvert-eol -G`, sentryStatus = await switchSentry(false);
dockerMock: docker, sinon.stub(process, 'exit');
expectedFilesByService: { main: expectedFiles },
expectedQueryParamsByService: { main: commonQueryParams }, await testDockerBuildStream({
expectedErrorLines, commandLine: `deploy testApp --build --source ${projectPath} --noconvert-eol -G`,
expectedExitCode, dockerMock: docker,
expectedResponseLines, expectedFilesByService: { main: expectedFiles },
projectPath, expectedQueryParamsByService: { main: commonQueryParams },
responseBody, expectedErrorLines,
responseCode: 200, expectedExitCode,
services: ['main'], expectedResponseLines,
}); projectPath,
responseBody,
responseCode: 200,
services: ['main'],
});
} finally {
await switchSentry(sentryStatus);
// @ts-ignore
process.exit.restore();
}
}); });
}); });
describe('balena deploy: project validation', function () { describe('balena deploy: project validation', function () {
let api: BalenaAPIMock; let api: BalenaAPIMock;
this.beforeEach(() => { this.beforeEach(() => {
api = new BalenaAPIMock(); api = new BalenaAPIMock();
api.expectGetWhoAmI({ optional: true, persist: true }); api.expectGetWhoAmI({ optional: true, persist: true });

View File

@ -22,7 +22,6 @@ import { expect } from 'chai';
import { fs } from 'mz'; import { fs } from 'mz';
import * as path from 'path'; import * as path from 'path';
import { isV12 } from '../../build/utils/version';
import { BalenaAPIMock } from '../balena-api-mock'; import { BalenaAPIMock } from '../balena-api-mock';
import { BuilderMock, builderResponsePath } from '../builder-mock'; import { BuilderMock, builderResponsePath } from '../builder-mock';
import { expectStreamNoCRLF, testPushBuildStream } from '../docker-build'; import { expectStreamNoCRLF, testPushBuildStream } from '../docker-build';
@ -107,12 +106,12 @@ describe('balena push', function () {
it('should create the expected tar stream (single container)', async () => { it('should create the expected tar stream (single container)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const isV12W = isWindows && isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/.dockerignore': { fileSize: 16, type: 'file' },
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { 'src/windows-crlf.sh': {
fileSize: isV12W ? 68 : 70, fileSize: isWindows ? 68 : 70,
testStream: isV12W ? expectStreamNoCRLF : undefined, testStream: isWindows ? expectStreamNoCRLF : undefined,
type: 'file', type: 'file',
}, },
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
@ -124,19 +123,24 @@ describe('balena push', function () {
path.join(builderResponsePath, responseFilename), path.join(builderResponsePath, responseFilename),
'utf8', 'utf8',
); );
const expectedResponseLines = [...commonResponseLines[responseFilename]]; const expectedResponseLines = [
...commonResponseLines[responseFilename],
...[
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] Only one .dockerignore file at the source folder (project root) is used.',
'[Warn] Additional .dockerignore files are disregarded. Microservices (multicontainer)',
'[Warn] apps should place the .dockerignore file alongside the docker-compose.yml file.',
'[Warn] See issue: https://github.com/balena-io/balena-cli/issues/1870',
'[Warn] See also CLI v12 release notes: https://git.io/Jf7hz',
'[Warn] -------------------------------------------------------------------------------',
],
];
if (isWindows) { if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
if (isV12()) { expectedResponseLines.push(
expectedResponseLines.push( `[Info] Converting line endings CRLF -> LF for file: ${fname}`,
`[Info] Converting line endings CRLF -> LF for file: ${fname}`, );
);
} else {
expectedResponseLines.push(
`[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
'[Warn] Windows-format line endings were detected in some files. Consider using the `--convert-eol` option.',
);
}
} }
await testPushBuildStream({ await testPushBuildStream({
@ -154,6 +158,7 @@ describe('balena push', function () {
it('should create the expected tar stream (alternative Dockerfile)', async () => { it('should create the expected tar stream (alternative Dockerfile)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/.dockerignore': { fileSize: 16, type: 'file' },
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { fileSize: 70, type: 'file' }, 'src/windows-crlf.sh': { fileSize: 70, type: 'file' },
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
@ -165,6 +170,19 @@ describe('balena push', function () {
path.join(builderResponsePath, responseFilename), path.join(builderResponsePath, responseFilename),
'utf8', 'utf8',
); );
const expectedResponseLines = [
...commonResponseLines[responseFilename],
...[
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] Only one .dockerignore file at the source folder (project root) is used.',
'[Warn] Additional .dockerignore files are disregarded. Microservices (multicontainer)',
'[Warn] apps should place the .dockerignore file alongside the docker-compose.yml file.',
'[Warn] See issue: https://github.com/balena-io/balena-cli/issues/1870',
'[Warn] See also CLI v12 release notes: https://git.io/Jf7hz',
'[Warn] -------------------------------------------------------------------------------',
],
];
const expectedQueryParams = commonQueryParams.map((i) => const expectedQueryParams = commonQueryParams.map((i) =>
i[0] === 'dockerfilePath' ? ['dockerfilePath', 'Dockerfile-alt'] : i, i[0] === 'dockerfilePath' ? ['dockerfilePath', 'Dockerfile-alt'] : i,
); );
@ -174,7 +192,7 @@ describe('balena push', function () {
commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} --dockerfile Dockerfile-alt --noconvert-eol`, commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} --dockerfile Dockerfile-alt --noconvert-eol`,
expectedFiles, expectedFiles,
expectedQueryParams, expectedQueryParams,
expectedResponseLines: commonResponseLines[responseFilename], expectedResponseLines,
projectPath, projectPath,
responseBody, responseBody,
responseCode: 200, responseCode: 200,
@ -183,12 +201,11 @@ describe('balena push', function () {
it('should create the expected tar stream (single container, --[no]convert-eol)', async () => { it('should create the expected tar stream (single container, --[no]convert-eol)', async () => {
const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic');
const eol = isWindows && !isV12();
const expectedFiles: ExpectedTarStreamFiles = { const expectedFiles: ExpectedTarStreamFiles = {
'src/.dockerignore': { fileSize: 16, type: 'file' },
'src/start.sh': { fileSize: 89, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' },
'src/windows-crlf.sh': { 'src/windows-crlf.sh': {
fileSize: eol ? 68 : 70, fileSize: 70,
testStream: eol ? expectStreamNoCRLF : undefined,
type: 'file', type: 'file',
}, },
Dockerfile: { fileSize: 88, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' },
@ -200,26 +217,30 @@ describe('balena push', function () {
path.join(builderResponsePath, responseFilename), path.join(builderResponsePath, responseFilename),
'utf8', 'utf8',
); );
const expectedResponseLines = [...commonResponseLines[responseFilename]]; const expectedResponseLines = [
...commonResponseLines[responseFilename],
...[
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'src', '.dockerignore')}`,
'[Warn] Only one .dockerignore file at the source folder (project root) is used.',
'[Warn] Additional .dockerignore files are disregarded. Microservices (multicontainer)',
'[Warn] apps should place the .dockerignore file alongside the docker-compose.yml file.',
'[Warn] See issue: https://github.com/balena-io/balena-cli/issues/1870',
'[Warn] See also CLI v12 release notes: https://git.io/Jf7hz',
'[Warn] -------------------------------------------------------------------------------',
],
];
if (isWindows) { if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
if (isV12()) { expectedResponseLines.push(
expectedResponseLines.push( `[Warn] CRLF (Windows) line endings detected in file: ${fname}`,
`[Warn] CRLF (Windows) line endings detected in file: ${fname}`, '[Warn] Windows-format line endings were detected in some files, but were not converted due to `--noconvert-eol` option.',
'[Warn] Windows-format line endings were detected in some files, but were not converted due to `--noconvert-eol` option.', );
);
} else {
expectedResponseLines.push(
`[Info] Converting line endings CRLF -> LF for file: ${fname}`,
);
}
} }
await testPushBuildStream({ await testPushBuildStream({
builderMock: builder, builderMock: builder,
commandLine: isV12() commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} --noconvert-eol`,
? `push testApp -s ${projectPath} -R ${regSecretsPath} --noconvert-eol`
: `push testApp -s ${projectPath} -R ${regSecretsPath} -l`,
expectedFiles, expectedFiles,
expectedQueryParams: commonQueryParams, expectedQueryParams: commonQueryParams,
expectedResponseLines, expectedResponseLines,
@ -269,17 +290,15 @@ describe('balena push', function () {
'utf8', 'utf8',
); );
const expectedResponseLines = [ const expectedResponseLines = [
...(isV12() ...[
? [ '[Warn] Using file ignore patterns from:',
'[Warn] Using file ignore patterns from:', `[Warn] * ${path.join(projectPath, '.dockerignore')}`,
`[Warn] ${path.join(projectPath, '.dockerignore')}`, `[Warn] * ${path.join(projectPath, '.gitignore')}`,
`[Warn] ${path.join(projectPath, '.gitignore')}`, `[Warn] * ${path.join(projectPath, 'src', '.gitignore')}`,
`[Warn] ${path.join(projectPath, 'src', '.gitignore')}`, '[Warn] .gitignore files are being considered because the --gitignore option was used.',
'[Warn] Note: .gitignore files are being considered because the --gitignore option was', '[Warn] This option is deprecated and will be removed in the next major version release.',
'[Warn] used. This option is deprecated and will be removed in the next major version', "[Warn] For more information, see 'balena help push'.",
"[Warn] release. For more information, see 'balena help push'.", ],
]
: []),
...commonResponseLines[responseFilename], ...commonResponseLines[responseFilename],
]; ];
@ -394,12 +413,12 @@ describe('balena push', function () {
const expectedResponseLines = isWindows const expectedResponseLines = isWindows
? [ ? [
'[Warn] Using file ignore patterns from:', '[Warn] Using file ignore patterns from:',
`[Warn] ${path.join(projectPath, '.dockerignore')}`, `[Warn] * ${path.join(projectPath, '.dockerignore')}`,
'[Warn] The --gitignore option was used, but not .gitignore files were found.', '[Warn] The --gitignore option was used, but no .gitignore files were found.',
'[Warn] The --gitignore option is deprecated and will be removed in the next major', '[Warn] The --gitignore option is deprecated and will be removed in the next major',
'[Warn] version release. It prevents the use of a better dockerignore parser and', '[Warn] version release. It prevents the use of a better dockerignore parser and',
'[Warn] filter library that fixes several issues on Windows and improves compatibility', '[Warn] filter library that fixes several issues on Windows and improves compatibility',
'[Warn] with "docker build". For more information, see \'balena help push\'.', "[Warn] with 'docker build'. For more information, see 'balena help push'.",
...commonResponseLines[responseFilename], ...commonResponseLines[responseFilename],
] ]
: commonResponseLines[responseFilename]; : commonResponseLines[responseFilename];
@ -425,6 +444,7 @@ describe('balena push', function () {
'service1/Dockerfile.template': { fileSize: 144, type: 'file' }, 'service1/Dockerfile.template': { fileSize: 144, type: 'file' },
'service1/file1.sh': { fileSize: 12, type: 'file' }, 'service1/file1.sh': { fileSize: 12, type: 'file' },
'service2/Dockerfile-alt': { fileSize: 40, type: 'file' }, 'service2/Dockerfile-alt': { fileSize: 40, type: 'file' },
'service2/.dockerignore': { fileSize: 14, type: 'file' },
'service2/file2-crlf.sh': { 'service2/file2-crlf.sh': {
fileSize: isWindows ? 12 : 14, fileSize: isWindows ? 12 : 14,
testStream: isWindows ? expectStreamNoCRLF : undefined, testStream: isWindows ? expectStreamNoCRLF : undefined,
@ -439,6 +459,16 @@ describe('balena push', function () {
); );
const expectedResponseLines: string[] = [ const expectedResponseLines: string[] = [
...commonResponseLines[responseFilename], ...commonResponseLines[responseFilename],
...[
'[Warn] The following .dockerignore file(s) will not be used:',
`[Warn] * ${path.join(projectPath, 'service2', '.dockerignore')}`,
'[Warn] Only one .dockerignore file at the source folder (project root) is used.',
'[Warn] Additional .dockerignore files are disregarded. Microservices (multicontainer)',
'[Warn] apps should place the .dockerignore file alongside the docker-compose.yml file.',
'[Warn] See issue: https://github.com/balena-io/balena-cli/issues/1870',
'[Warn] See also CLI v12 release notes: https://git.io/Jf7hz',
'[Warn] -------------------------------------------------------------------------------',
],
]; ];
if (isWindows) { if (isWindows) {
expectedResponseLines.push( expectedResponseLines.push(
@ -508,24 +538,16 @@ describe('balena push: project validation', function () {
'basic', 'basic',
'service1', 'service1',
); );
const expectedErrorLines = isV12() const expectedErrorLines = [
? [ 'Error: "docker-compose.y[a]ml" file found in parent directory: please check that',
'Error: "docker-compose.y[a]ml" file found in parent directory: please check that', "the correct source folder was specified. (Suppress with '--noparent-check'.)",
"the correct source folder was specified. (Suppress with '--noparent-check'.)", ];
]
: ['The --nolive flag is only valid when pushing to a local mode device'];
const expectedOutputLines = isV12()
? []
: [
'[Warn] "docker-compose.y[a]ml" file found in parent directory: please check that',
"[Warn] the correct source folder was specified. (Suppress with '--noparent-check'.)",
];
const { out, err } = await runCommand( const { out, err } = await runCommand(
`push testApp --source ${projectPath} --nolive`, `push testApp --source ${projectPath} --nolive`,
); );
expect(cleanOutput(err, true)).to.include.members(expectedErrorLines); expect(cleanOutput(err, true)).to.include.members(expectedErrorLines);
expect(cleanOutput(out, true)).to.include.members(expectedOutputLines); expect(out).to.be.empty;
}); });
it('should suppress a parent folder check with --noparent-check', async () => { it('should suppress a parent folder check with --noparent-check', async () => {

View File

@ -0,0 +1 @@
file2-crlf.sh

View File

@ -0,0 +1 @@
windows-crlf.sh