Merge pull request #2384 from balena-io/2376-dockerignore-corner-cases

push/build: Add test cases for .dockerignore filtering corner cases
This commit is contained in:
bulldozer-balena[bot] 2021-11-22 02:23:22 +00:00 committed by GitHub
commit 2f706c0200
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 37 deletions

View File

@ -32,6 +32,7 @@ import {
ExpectedTarStreamFilesByService,
getDockerignoreWarn1,
getDockerignoreWarn2,
getDockerignoreWarn3,
} from '../projects';
const repoPath = path.normalize(path.join(__dirname, '..', '..'));
@ -184,6 +185,10 @@ describe('balena build', function () {
`[Info] No "docker-compose.yml" file found at "${projectPath}"`,
`[Info] Creating default composition with source: "${projectPath}"`,
'[Build] main Step 1/4 : FROM busybox',
...getDockerignoreWarn1(
[path.join(projectPath, 'src', '.dockerignore')],
'build',
),
];
if (isWindows) {
const fname = path.join(projectPath, 'src', 'windows-crlf.sh');
@ -383,7 +388,6 @@ describe('balena build', function () {
'file1.sh': { fileSize: 12, type: 'file' },
},
service2: {
'.dockerignore': { fileSize: 12, type: 'file' },
'Dockerfile-alt': { fileSize: 13, type: 'file' },
'file2-crlf.sh': {
fileSize: isWindows ? 12 : 14,
@ -513,13 +517,7 @@ describe('balena build', function () {
'[Build] service1 Step 1/4 : FROM busybox',
'[Build] service2 Step 1/4 : FROM busybox',
],
...[
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
'[Info] be used to filter service subdirectories. See "balena help build".',
`[Info] ---------------------------------------------------------------------------`,
],
...getDockerignoreWarn3('build'),
];
if (isWindows) {
expectedResponseLines.push(
@ -602,13 +600,7 @@ describe('balena build', function () {
'[Build] service1 Step 1/4 : FROM busybox',
'[Build] service2 Step 1/4 : FROM busybox',
],
...[
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
'[Info] be used to filter service subdirectories. See "balena help build".',
`[Info] ---------------------------------------------------------------------------`,
],
...getDockerignoreWarn3('build'),
];
if (isWindows) {
expectedResponseLines.push(

View File

@ -32,6 +32,7 @@ import {
ExpectedTarStreamFiles,
ExpectedTarStreamFilesByService,
getDockerignoreWarn1,
getDockerignoreWarn3,
} from '../projects';
const repoPath = path.normalize(path.join(__dirname, '..', '..'));
@ -396,13 +397,7 @@ describe('balena deploy', function () {
'[Build] service1 Step 1/4 : FROM busybox',
'[Build] service2 Step 1/4 : FROM busybox',
],
...[
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
'[Info] be used to filter service subdirectories. See "balena help deploy".',
`[Info] ---------------------------------------------------------------------------`,
],
...getDockerignoreWarn3('deploy'),
];
if (isWindows) {
expectedResponseLines.push(

View File

@ -26,9 +26,11 @@ import { expectStreamNoCRLF, testPushBuildStream } from '../docker-build';
import { cleanOutput, runCommand } from '../helpers';
import {
addRegSecretsEntries,
exists,
ExpectedTarStreamFiles,
getDockerignoreWarn1,
getDockerignoreWarn2,
getDockerignoreWarn3,
setupDockerignoreTestData,
} from '../projects';
@ -36,6 +38,7 @@ const repoPath = path.normalize(path.join(__dirname, '..', '..'));
const projectsPath = path.join(repoPath, 'tests', 'test-data', 'projects');
const itNoV13 = isV13() ? it.skip : it;
const itNoWin = process.platform === 'win32' ? it.skip : it;
const commonResponseLines = {
'build-POST-v3.json': [
@ -451,12 +454,10 @@ describe('balena push', function () {
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
const expectedFiles: ExpectedTarStreamFiles = {
'.balena/balena.yml': { fileSize: 197, type: 'file' },
'.dockerignore': { fileSize: 22, 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: 13, type: 'file' },
'service2/.dockerignore': { fileSize: 12, type: 'file' },
'service2/file2-crlf.sh': {
fileSize: isWindows ? 12 : 14,
testStream: isWindows ? expectStreamNoCRLF : undefined,
@ -503,7 +504,6 @@ describe('balena push', function () {
const projectPath = path.join(projectsPath, 'docker-compose', 'basic');
const expectedFiles: ExpectedTarStreamFiles = {
'.balena/balena.yml': { fileSize: 197, type: 'file' },
'.dockerignore': { fileSize: 22, type: 'file' },
'docker-compose.yml': { fileSize: 332, type: 'file' },
'service1/Dockerfile.template': { fileSize: 144, type: 'file' },
'service1/file1.sh': { fileSize: 12, type: 'file' },
@ -524,13 +524,7 @@ describe('balena push', function () {
);
const expectedResponseLines: string[] = [
...commonResponseLines[responseFilename],
...[
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
'[Info] be used to filter service subdirectories. See "balena help push".',
`[Info] ---------------------------------------------------------------------------`,
],
...getDockerignoreWarn3('push'),
];
if (isWindows) {
expectedResponseLines.push(
@ -553,6 +547,66 @@ describe('balena push', function () {
responseCode: 200,
});
});
// Skip on Windows because this test uses Unix domain sockets
itNoWin('should create the expected tar stream (socket file)', async () => {
// This test creates project files dynamically in a temp dir, where
// a Unix domain socket file is created and listened on. A specific
// reason use use a temp dir is that Unix domain socket paths are
// limited in length to just over 100 characters, while the project
// paths in the test-data folder easily exceed that limit.
const tmp = await import('tmp');
tmp.setGracefulCleanup();
const projectPath = await new Promise<string>((resolve, reject) => {
const opts = { template: 'tmp-XXXXXX', unsafeCleanup: true };
tmp.dir(opts, (e, p) => (e ? reject(e) : resolve(p)));
});
console.error(`[debug] Temp project dir: ${projectPath}`);
// Create a Unix Domain Socket file that should not be included in the tar stream
const net = await import('net');
const server = net.createServer();
const socketPath = path.join(projectPath, 'socket');
await new Promise<void>((resolve, reject) => {
server.on('error', reject);
try {
server.listen(socketPath, resolve);
} catch (e) {
reject(e);
}
});
console.error(`[debug] Checking existence of socket at '${socketPath}'`);
expect(await exists(socketPath), 'Socket existence').to.be.true;
await fs.writeFile(path.join(projectPath, 'Dockerfile'), 'FROM busybox\n');
const expectedFiles: ExpectedTarStreamFiles = {
Dockerfile: { fileSize: 13, type: 'file' },
};
const responseFilename = 'build-POST-v3.json';
const responseBody = await fs.readFile(
path.join(builderResponsePath, responseFilename),
'utf8',
);
await testPushBuildStream({
builderMock: builder,
commandLine: `push testApp -s ${projectPath}`,
expectedFiles,
expectedQueryParams: commonQueryParams,
expectedResponseLines: commonResponseLines[responseFilename],
projectPath,
responseBody,
responseCode: 200,
});
// Terminate Unix Domain Socket server
await new Promise<void>((resolve, reject) => {
server.close((e) => (e ? reject(e) : resolve()));
});
expect(await exists(socketPath), 'Socket existence').to.be.false;
});
});
describe('balena push: project validation', function () {

View File

@ -15,12 +15,9 @@
* limitations under the License.
*/
import * as fs from 'fs';
import { promises as fs } from 'fs';
import * as path from 'path';
import type { Headers } from 'tar-stream';
import { promisify } from 'util';
const statAsync = promisify(fs.stat);
export interface ExpectedTarStreamFile {
contents?: string;
@ -49,6 +46,15 @@ export const projectsPath = path.join(
'projects',
);
export async function exists(fPath: string) {
try {
await fs.stat(fPath);
return true;
} catch (e) {
return false;
}
}
export async function setupDockerignoreTestData({ cleanup = false } = {}) {
const { copy, remove } = await import('fs-extra');
const dockerignoreProjDir = path.join(
@ -78,7 +84,7 @@ export async function addRegSecretsEntries(
): Promise<string> {
const regSecretsPath = path.join(projectsPath, 'registry-secrets.json');
expectedFiles['.balena/registry-secrets.json'] = {
fileSize: (await statAsync(regSecretsPath)).size,
fileSize: (await fs.stat(regSecretsPath)).size,
type: 'file',
};
return regSecretsPath;
@ -115,3 +121,13 @@ export function getDockerignoreWarn2(paths: string[], cmd: string) {
);
return lines;
}
export function getDockerignoreWarn3(cmd: string) {
return [
`[Info] ---------------------------------------------------------------------------`,
'[Info] The --multi-dockerignore option is being used, and a .dockerignore file was',
'[Info] found at the project source (root) directory. Note that this file will not',
`[Info] be used to filter service subdirectories. See "balena help ${cmd}".`,
`[Info] ---------------------------------------------------------------------------`,
];
}

View File

@ -1 +1,2 @@
service1/test-ignore*
**/.dockerignore