From 2859d16b3172332f400c08d786a6538bf126be90 Mon Sep 17 00:00:00 2001 From: Paulo Castro Date: Mon, 22 Jun 2020 00:54:52 +0100 Subject: [PATCH 1/2] Improve error handling for oclif "missing required arg" Change-type: patch --- lib/errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/errors.ts b/lib/errors.ts index 124d1550..fac18123 100644 --- a/lib/errors.ts +++ b/lib/errors.ts @@ -140,7 +140,7 @@ const EXPECTED_ERROR_REGEXES = [ /^BalenaDeviceNotFound/, // balena-sdk /^BalenaExpiredToken/, // balena-sdk /^Missing \w+$/, // Capitano, - /^Missing \d required argument/, // oclif parser: RequiredArgsError, RequiredFlagError + /^Missing \d+ required arg/, // oclif parser: RequiredArgsError, RequiredFlagError /^Unexpected argument/, // oclif parser: UnexpectedArgsError /to be one of/, // oclif parser: FlagInvalidOptionError, ArgInvalidOptionError ]; From 11d1a3f5a066f826ceda41929290ccf4ce645f0e Mon Sep 17 00:00:00 2001 From: Paulo Castro Date: Sat, 20 Jun 2020 23:05:59 +0100 Subject: [PATCH 2/2] Add runtime warning for unused .dockerignore files Change-type: patch --- lib/utils/compose_ts.ts | 53 +++++-- lib/utils/ignore.ts | 13 +- tests/commands/build.spec.ts | 109 +++++++------- tests/commands/deploy.spec.ts | 85 +++++------ tests/commands/push.spec.ts | 142 ++++++++++-------- .../basic/service2/.dockerignore | 1 + .../no-docker-compose/basic/src/.dockerignore | 1 + 7 files changed, 238 insertions(+), 166 deletions(-) create mode 100644 tests/test-data/projects/docker-compose/basic/service2/.dockerignore create mode 100644 tests/test-data/projects/no-docker-compose/basic/src/.dockerignore diff --git a/lib/utils/compose_ts.ts b/lib/utils/compose_ts.ts index f843adb8..7e4137a3 100644 --- a/lib/utils/compose_ts.ts +++ b/lib/utils/compose_ts.ts @@ -193,8 +193,12 @@ export async function tarDirectory( readFile = fs.readFile; } const pack = tar.pack(); - const fileStatsList = await filterFilesWithDockerignore(dir); - for (const fileStats of fileStatsList) { + const { + filteredFileList, + dockerignoreFiles, + } = await filterFilesWithDockerignore(dir); + printDockerignoreWarn(dockerignoreFiles); + for (const fileStats of filteredFileList) { pack.entry( { name: toPosixPath(fileStats.relPath), @@ -212,9 +216,40 @@ export async function tarDirectory( return pack; } +export function printDockerignoreWarn( + dockerignoreFiles: Array, +) { + 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 - * 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 gitignoreFiles Array of absolute paths to .gitginore files */ @@ -229,22 +264,22 @@ export function printGitignoreWarn( const hr = '-------------------------------------------------------------------------------'; const msg = [' ', hr, 'Using file ignore patterns from:']; - msg.push(...ignoreFiles); + msg.push(...ignoreFiles.map((e) => `* ${e}`)); if (gitignoreFiles.length) { msg.push(stripIndent` - Note: .gitignore files are being considered because the --gitignore option was - used. This option is deprecated and will be removed in the next major version - release. For more information, see 'balena help ${Logger.command}'. + .gitignore files are being considered because the --gitignore option was used. + This option is deprecated and will be removed in the next major version release. + For more information, see 'balena help ${Logger.command}'. `); msg.push(hr); Logger.getLogger().logWarn(msg.join('\n')); } else if (dockerignoreFile && process.platform === 'win32') { 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 version release. It prevents the use of a better dockerignore parser and 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); Logger.getLogger().logWarn(msg.join('\n')); diff --git a/lib/utils/ignore.ts b/lib/utils/ignore.ts index 77a6e70f..c7db59b8 100644 --- a/lib/utils/ignore.ts +++ b/lib/utils/ignore.ts @@ -190,7 +190,7 @@ export class FileIgnorer { } } -interface FileStats { +export interface FileStats { filePath: string; relPath: string; stats: fs.Stats; @@ -256,7 +256,7 @@ async function readDockerIgnoreFile(projectDir: string): Promise { */ export async function filterFilesWithDockerignore( projectDir: string, -): Promise { +): Promise<{ filteredFileList: FileStats[]; dockerignoreFiles: FileStats[] }> { // path.resolve() also converts forward slashes to backslashes on Windows projectDir = path.resolve(projectDir); const dockerIgnoreStr = await readDockerIgnoreFile(projectDir); @@ -276,5 +276,12 @@ export async function filterFilesWithDockerignore( ]); 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 }; } diff --git a/tests/commands/build.spec.ts b/tests/commands/build.spec.ts index e8913427..ea466ba6 100644 --- a/tests/commands/build.spec.ts +++ b/tests/commands/build.spec.ts @@ -24,7 +24,6 @@ import mock = require('mock-require'); import { fs } from 'mz'; import * as path from 'path'; -import { isV12 } from '../../build/utils/version'; import { BalenaAPIMock } from '../balena-api-mock'; import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build'; import { DockerMock, dockerResponsePath } from '../docker-mock'; @@ -87,12 +86,12 @@ describe('balena build', function () { it('should create the expected tar stream (single container)', async () => { const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); - const isV12W = isWindows && isV12(); const expectedFiles: ExpectedTarStreamFiles = { + 'src/.dockerignore': { fileSize: 16, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' }, 'src/windows-crlf.sh': { - fileSize: isV12W ? 68 : 70, - testStream: isV12W ? expectStreamNoCRLF : undefined, + fileSize: isWindows ? 68 : 70, + testStream: isWindows ? expectStreamNoCRLF : undefined, type: 'file', }, Dockerfile: { fileSize: 88, type: 'file' }, @@ -107,13 +106,11 @@ describe('balena build', function () { ...commonResponseLines[responseFilename], `[Info] No "docker-compose.yml" file found at "${projectPath}"`, `[Info] Creating default composition with source: "${projectPath}"`, - isV12() - ? '[Build] main Step 1/4 : FROM busybox' - : '[Build] main Image size: 1.14 MB', + '[Build] main Step 1/4 : FROM busybox', ]; if (isWindows) { const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); - if (isV12()) { + if (isWindows) { expectedResponseLines.push( `[Info] Converting line endings CRLF -> LF for file: ${fname}`, ); @@ -142,7 +139,6 @@ describe('balena build', function () { // downloading and installing QEMU itSS('should create the expected tar stream (--emulated)', async () => { const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); - const isV12W = isWindows && isV12(); const transposedDockerfile = stripIndent` FROM busybox @@ -151,10 +147,11 @@ describe('balena build', function () { RUN ["/tmp/qemu-execve","-execve","/bin/sh","-c","chmod a+x /usr/src/*.sh"] CMD ["/usr/src/start.sh"]` + '\n'; const expectedFiles: ExpectedTarStreamFiles = { + 'src/.dockerignore': { fileSize: 16, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' }, 'src/windows-crlf.sh': { - fileSize: isV12W ? 68 : 70, - testStream: isV12W ? expectStreamNoCRLF : undefined, + fileSize: isWindows ? 68 : 70, + testStream: isWindows ? expectStreamNoCRLF : undefined, type: 'file', }, Dockerfile: { @@ -174,23 +171,25 @@ describe('balena build', function () { `[Info] Creating default composition with source: "${projectPath}"`, '[Info] Building for rpi/raspberry-pi', '[Info] Emulation is enabled', - isV12() - ? '[Build] main Step 1/4 : FROM busybox' - : '[Build] main Image size: 1.14 MB', + ...[ + '[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] -------------------------------------------------------------------------------', + ], + '[Build] main Step 1/4 : FROM busybox', '[Success] Build succeeded!', ]; if (isWindows) { const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); - if (isV12()) { - expectedResponseLines.push( - `[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.', - ); - } + expectedResponseLines.push( + `[Info] Converting line endings CRLF -> LF for file: ${fname}`, + ); } const arch = 'rpi'; 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 () => { const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); - const eol = isWindows && !isV12(); const expectedFiles: ExpectedTarStreamFiles = { + 'src/.dockerignore': { fileSize: 16, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' }, 'src/windows-crlf.sh': { - fileSize: eol ? 68 : 70, - testStream: eol ? expectStreamNoCRLF : undefined, + fileSize: 70, type: 'file', }, Dockerfile: { fileSize: 88, type: 'file' }, @@ -250,28 +248,29 @@ describe('balena build', function () { ...commonResponseLines[responseFilename], `[Info] No "docker-compose.yml" file found at "${projectPath}"`, `[Info] Creating default composition with source: "${projectPath}"`, - isV12() - ? '[Build] main Step 1/4 : FROM busybox' - : '[Build] main Image size: 1.14 MB', + ...[ + '[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] -------------------------------------------------------------------------------', + ], + '[Build] main Step 1/4 : FROM busybox', ]; if (isWindows) { const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); - if (isV12()) { - expectedResponseLines.push( - `[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.', - ); - } else { - expectedResponseLines.push( - `[Info] Converting line endings CRLF -> LF for file: ${fname}`, - ); - } + expectedResponseLines.push( + `[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.', + ); } docker.expectGetInfo({}); await testDockerBuildStream({ - commandLine: isV12() - ? `build ${projectPath} --deviceType nuc --arch amd64 --noconvert-eol` - : `build ${projectPath} --deviceType nuc --arch amd64 --convert-eol`, + commandLine: `build ${projectPath} --deviceType nuc --arch amd64 --noconvert-eol`, dockerMock: docker, expectedFilesByService: { main: expectedFiles }, expectedQueryParamsByService: { main: commonQueryParams }, @@ -302,6 +301,7 @@ describe('balena build', function () { 'file1.sh': { fileSize: 12, type: 'file' }, }, service2: { + '.dockerignore': { fileSize: 14, type: 'file' }, 'Dockerfile-alt': { fileSize: 40, type: 'file' }, 'file2-crlf.sh': { fileSize: isWindows ? 12 : 14, @@ -328,15 +328,20 @@ describe('balena build', function () { }; const expectedResponseLines: string[] = [ ...commonResponseLines[responseFilename], - ...(isV12() - ? [ - '[Build] service1 Step 1/4 : FROM busybox', - '[Build] service2 Step 1/4 : FROM busybox', - ] - : [ - `[Build] service1 Image size: 1.14 MB`, - `[Build] service2 Image size: 1.14 MB`, - ]), + ...[ + '[Build] service1 Step 1/4 : FROM busybox', + '[Build] service2 Step 1/4 : FROM busybox', + ], + ...[ + '[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) { expectedResponseLines.push( diff --git a/tests/commands/deploy.spec.ts b/tests/commands/deploy.spec.ts index a1ae5a6f..dcd77d95 100644 --- a/tests/commands/deploy.spec.ts +++ b/tests/commands/deploy.spec.ts @@ -23,7 +23,6 @@ import { fs } from 'mz'; import * as path from 'path'; import * as sinon from 'sinon'; -import { isV12 } from '../../build/utils/version'; import { BalenaAPIMock } from '../balena-api-mock'; import { expectStreamNoCRLF, testDockerBuildStream } from '../docker-build'; import { DockerMock, dockerResponsePath } from '../docker-mock'; @@ -39,9 +38,7 @@ const commonResponseLines = { '[Info] Docker Desktop detected (daemon architecture: "x86_64")', '[Info] Docker itself will determine and enable architecture emulation if required,', '[Info] without balena-cli intervention and regardless of the --emulated option.', - isV12() - ? '[Build] main Step 1/4 : FROM busybox' - : '[Build] main Image size: 1.14 MB', + '[Build] main Step 1/4 : FROM busybox', '[Info] Creating release...', '[Info] Pushing images to registry...', '[Info] Saving release...', @@ -59,20 +56,8 @@ const commonQueryParams = [ describe('balena deploy', function () { let api: BalenaAPIMock; let docker: DockerMock; - let sentryStatus: boolean | undefined; 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(() => { api = new BalenaAPIMock(); docker = new DockerMock(); @@ -106,12 +91,12 @@ describe('balena deploy', function () { it('should create the expected --build tar stream (single container)', async () => { const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); - const isV12W = isWindows && isV12(); const expectedFiles: ExpectedTarStreamFiles = { + 'src/.dockerignore': { fileSize: 16, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' }, 'src/windows-crlf.sh': { - fileSize: isV12W ? 68 : 70, - testStream: isV12W ? expectStreamNoCRLF : undefined, + fileSize: isWindows ? 68 : 70, + testStream: isWindows ? expectStreamNoCRLF : undefined, type: 'file', }, Dockerfile: { fileSize: 88, type: 'file' }, @@ -126,19 +111,23 @@ describe('balena deploy', function () { ...commonResponseLines[responseFilename], `[Info] No "docker-compose.yml" file found at "${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) { const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); - if (isV12()) { - expectedResponseLines.push( - `[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.', - ); - } + expectedResponseLines.push( + `[Info] Converting line endings CRLF -> LF for file: ${fname}`, + ); } api.expectPatchImage({}); @@ -158,8 +147,10 @@ describe('balena deploy', function () { }); 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 expectedFiles: ExpectedTarStreamFiles = { + 'src/.dockerignore': { fileSize: 16, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' }, 'src/windows-crlf.sh': { fileSize: 70, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' }, @@ -199,24 +190,34 @@ describe('balena deploy', function () { }, }); - await testDockerBuildStream({ - commandLine: `deploy testApp --build --source ${projectPath} --noconvert-eol -G`, - dockerMock: docker, - expectedFilesByService: { main: expectedFiles }, - expectedQueryParamsByService: { main: commonQueryParams }, - expectedErrorLines, - expectedExitCode, - expectedResponseLines, - projectPath, - responseBody, - responseCode: 200, - services: ['main'], - }); + try { + sentryStatus = await switchSentry(false); + sinon.stub(process, 'exit'); + + await testDockerBuildStream({ + commandLine: `deploy testApp --build --source ${projectPath} --noconvert-eol -G`, + dockerMock: docker, + expectedFilesByService: { main: expectedFiles }, + expectedQueryParamsByService: { main: commonQueryParams }, + expectedErrorLines, + expectedExitCode, + expectedResponseLines, + projectPath, + responseBody, + responseCode: 200, + services: ['main'], + }); + } finally { + await switchSentry(sentryStatus); + // @ts-ignore + process.exit.restore(); + } }); }); describe('balena deploy: project validation', function () { let api: BalenaAPIMock; + this.beforeEach(() => { api = new BalenaAPIMock(); api.expectGetWhoAmI({ optional: true, persist: true }); diff --git a/tests/commands/push.spec.ts b/tests/commands/push.spec.ts index 0240589c..724ad39f 100644 --- a/tests/commands/push.spec.ts +++ b/tests/commands/push.spec.ts @@ -22,7 +22,6 @@ import { expect } from 'chai'; import { fs } from 'mz'; import * as path from 'path'; -import { isV12 } from '../../build/utils/version'; import { BalenaAPIMock } from '../balena-api-mock'; import { BuilderMock, builderResponsePath } from '../builder-mock'; import { expectStreamNoCRLF, testPushBuildStream } from '../docker-build'; @@ -107,12 +106,12 @@ describe('balena push', function () { it('should create the expected tar stream (single container)', async () => { const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); - const isV12W = isWindows && isV12(); const expectedFiles: ExpectedTarStreamFiles = { + 'src/.dockerignore': { fileSize: 16, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' }, 'src/windows-crlf.sh': { - fileSize: isV12W ? 68 : 70, - testStream: isV12W ? expectStreamNoCRLF : undefined, + fileSize: isWindows ? 68 : 70, + testStream: isWindows ? expectStreamNoCRLF : undefined, type: 'file', }, Dockerfile: { fileSize: 88, type: 'file' }, @@ -124,19 +123,24 @@ describe('balena push', function () { path.join(builderResponsePath, responseFilename), '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) { const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); - if (isV12()) { - expectedResponseLines.push( - `[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.', - ); - } + expectedResponseLines.push( + `[Info] Converting line endings CRLF -> LF for file: ${fname}`, + ); } await testPushBuildStream({ @@ -154,6 +158,7 @@ describe('balena push', function () { it('should create the expected tar stream (alternative Dockerfile)', async () => { const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); const expectedFiles: ExpectedTarStreamFiles = { + 'src/.dockerignore': { fileSize: 16, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' }, 'src/windows-crlf.sh': { fileSize: 70, type: 'file' }, Dockerfile: { fileSize: 88, type: 'file' }, @@ -165,6 +170,19 @@ describe('balena push', function () { path.join(builderResponsePath, responseFilename), '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) => 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`, expectedFiles, expectedQueryParams, - expectedResponseLines: commonResponseLines[responseFilename], + expectedResponseLines, projectPath, responseBody, responseCode: 200, @@ -183,12 +201,11 @@ describe('balena push', function () { it('should create the expected tar stream (single container, --[no]convert-eol)', async () => { const projectPath = path.join(projectsPath, 'no-docker-compose', 'basic'); - const eol = isWindows && !isV12(); const expectedFiles: ExpectedTarStreamFiles = { + 'src/.dockerignore': { fileSize: 16, type: 'file' }, 'src/start.sh': { fileSize: 89, type: 'file' }, 'src/windows-crlf.sh': { - fileSize: eol ? 68 : 70, - testStream: eol ? expectStreamNoCRLF : undefined, + fileSize: 70, type: 'file', }, Dockerfile: { fileSize: 88, type: 'file' }, @@ -200,26 +217,30 @@ describe('balena push', function () { path.join(builderResponsePath, responseFilename), '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) { const fname = path.join(projectPath, 'src', 'windows-crlf.sh'); - if (isV12()) { - expectedResponseLines.push( - `[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.', - ); - } else { - expectedResponseLines.push( - `[Info] Converting line endings CRLF -> LF for file: ${fname}`, - ); - } + expectedResponseLines.push( + `[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.', + ); } await testPushBuildStream({ builderMock: builder, - commandLine: isV12() - ? `push testApp -s ${projectPath} -R ${regSecretsPath} --noconvert-eol` - : `push testApp -s ${projectPath} -R ${regSecretsPath} -l`, + commandLine: `push testApp -s ${projectPath} -R ${regSecretsPath} --noconvert-eol`, expectedFiles, expectedQueryParams: commonQueryParams, expectedResponseLines, @@ -269,17 +290,15 @@ describe('balena push', function () { 'utf8', ); const expectedResponseLines = [ - ...(isV12() - ? [ - '[Warn] Using file ignore patterns from:', - `[Warn] ${path.join(projectPath, '.dockerignore')}`, - `[Warn] ${path.join(projectPath, '.gitignore')}`, - `[Warn] ${path.join(projectPath, 'src', '.gitignore')}`, - '[Warn] Note: .gitignore files are being considered because the --gitignore option was', - '[Warn] used. This option is deprecated and will be removed in the next major version', - "[Warn] release. For more information, see 'balena help push'.", - ] - : []), + ...[ + '[Warn] Using file ignore patterns from:', + `[Warn] * ${path.join(projectPath, '.dockerignore')}`, + `[Warn] * ${path.join(projectPath, '.gitignore')}`, + `[Warn] * ${path.join(projectPath, 'src', '.gitignore')}`, + '[Warn] .gitignore files are being considered because the --gitignore option was used.', + '[Warn] This option is deprecated and will be removed in the next major version release.', + "[Warn] For more information, see 'balena help push'.", + ], ...commonResponseLines[responseFilename], ]; @@ -394,12 +413,12 @@ describe('balena push', function () { const expectedResponseLines = isWindows ? [ '[Warn] Using file ignore patterns from:', - `[Warn] ${path.join(projectPath, '.dockerignore')}`, - '[Warn] The --gitignore option was used, but not .gitignore files were found.', + `[Warn] * ${path.join(projectPath, '.dockerignore')}`, + '[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] 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] 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]; @@ -425,6 +444,7 @@ describe('balena push', function () { 'service1/Dockerfile.template': { fileSize: 144, type: 'file' }, 'service1/file1.sh': { fileSize: 12, type: 'file' }, 'service2/Dockerfile-alt': { fileSize: 40, type: 'file' }, + 'service2/.dockerignore': { fileSize: 14, type: 'file' }, 'service2/file2-crlf.sh': { fileSize: isWindows ? 12 : 14, testStream: isWindows ? expectStreamNoCRLF : undefined, @@ -439,6 +459,16 @@ describe('balena push', function () { ); const expectedResponseLines: string[] = [ ...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) { expectedResponseLines.push( @@ -508,24 +538,16 @@ describe('balena push: project validation', function () { 'basic', 'service1', ); - const expectedErrorLines = isV12() - ? [ - '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 --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 expectedErrorLines = [ + 'Error: "docker-compose.y[a]ml" file found in parent directory: please check that', + "the correct source folder was specified. (Suppress with '--noparent-check'.)", + ]; const { out, err } = await runCommand( `push testApp --source ${projectPath} --nolive`, ); 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 () => { diff --git a/tests/test-data/projects/docker-compose/basic/service2/.dockerignore b/tests/test-data/projects/docker-compose/basic/service2/.dockerignore new file mode 100644 index 00000000..fe46fc1e --- /dev/null +++ b/tests/test-data/projects/docker-compose/basic/service2/.dockerignore @@ -0,0 +1 @@ +file2-crlf.sh diff --git a/tests/test-data/projects/no-docker-compose/basic/src/.dockerignore b/tests/test-data/projects/no-docker-compose/basic/src/.dockerignore new file mode 100644 index 00000000..964e3172 --- /dev/null +++ b/tests/test-data/projects/no-docker-compose/basic/src/.dockerignore @@ -0,0 +1 @@ +windows-crlf.sh