Skip to content

Commit

Permalink
Merge pull request #790 from robjackstewart/control-cache-for-features
Browse files Browse the repository at this point in the history
Disable cache on feature build when `--build-no-cache` is passed
  • Loading branch information
samruddhikhandale authored Apr 15, 2024
2 parents 9d98f30 + 1d9a38c commit 6710d4c
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/spec-node/containerFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ export async function extendImage(params: DockerResolverParameters, config: Subs
'build',
);
}
if (params.buildNoCache) {
args.push('--no-cache');
}
for (const buildArg in featureBuildInfo.buildArgs) {
args.push('--build-arg', `${buildArg}=${featureBuildInfo.buildArgs[buildArg]}`);
}
Expand Down
67 changes: 67 additions & 0 deletions src/test/cli.build.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,73 @@ describe('Dev Containers CLI', function () {
});
});

it('should not use docker cache for features when `--no-cache` flag is passed', async () => {
// Arrange
const testFolder = `${__dirname}/configs/image-with-features`;
const devContainerJson = `${testFolder}/.devcontainer.json`;

const devContainerFileContents = JSON.parse(fs.readFileSync(devContainerJson, 'utf8'));
const baseImage = devContainerFileContents.image;

const originalImageName = 'feature-cache-test-original-image';
const cachedImageName = 'feature-cache-test-rerun-image';
const nonCachedImageName = 'feature-cache-test-no-cache-image';

const commandBase = `${cli} build --workspace-folder ${testFolder}`;
const buildCommand = `${commandBase} --image-name ${originalImageName}`;
const cachedBuildCommand = `${commandBase} --image-name ${cachedImageName}`;
const buildWithoutCacheCommand = `${commandBase} --image-name ${nonCachedImageName} --no-cache`;

function arrayStartsWithArray(subject: string[], startsWith: string[]) {
if (subject.length < startsWith.length) {
return false;
}
for (let i = 0; i < startsWith.length; i++) {
if (subject[i] !== startsWith[i]) {
return false;
}
}
return true;
}

function haveCommonEntries(arr1: string[], arr2: string[]) {
return arr1.every(item => arr2.includes(item));
}

// Act
await shellExec(`docker pull ${baseImage}`); // pull base image so we can inspect it later
await shellExec(buildCommand);
await shellExec(cachedBuildCommand);
await shellExec(buildWithoutCacheCommand);

// Assert
const baseImageInspectCommandResult = await shellExec(`docker inspect ${baseImage}`);
const originalImageInspectCommandResult = await shellExec(`docker inspect ${originalImageName}`);
const cachedImageInspectCommandResult = await shellExec(`docker inspect ${cachedImageName}`);
const noCacheImageInspectCommandResult = await shellExec(`docker inspect ${nonCachedImageName}`);

const baseImageDetails = JSON.parse(baseImageInspectCommandResult.stdout);
const originalImageDetails = JSON.parse(originalImageInspectCommandResult.stdout);
const cachedImageDetails = JSON.parse(cachedImageInspectCommandResult.stdout);
const noCacheImageDetails = JSON.parse(noCacheImageInspectCommandResult.stdout);

const baseImageLayers: string[] = baseImageDetails[0].RootFS.Layers;
const originalImageLayers: string[] = originalImageDetails[0].RootFS.Layers;
const cachedImageLayers: string[] = cachedImageDetails[0].RootFS.Layers;
const nonCachedImageLayers: string[] = noCacheImageDetails[0].RootFS.Layers;

assert.equal(arrayStartsWithArray(originalImageLayers, baseImageLayers), true, 'because the image is made up of feature layers on top of the base image');
assert.equal(arrayStartsWithArray(cachedImageLayers, baseImageLayers), true, 'because the image is made up of feature layers on top of the base image');
assert.equal(arrayStartsWithArray(nonCachedImageLayers, baseImageLayers), true, 'because the image is made up of feature layers on top of the base image');

const originalImageWithoutBaseImageLayers = originalImageLayers.slice(baseImageLayers.length);
const cachedImageWithoutBaseImageLayers = cachedImageLayers.slice(baseImageLayers.length);
const nonCachedImageWithoutBaseImageLayers = nonCachedImageLayers.slice(baseImageLayers.length);

assert.deepEqual(originalImageWithoutBaseImageLayers, cachedImageWithoutBaseImageLayers, 'because they are the same image built sequentially therefore the second should have used caching');
assert.equal(haveCommonEntries(cachedImageWithoutBaseImageLayers, nonCachedImageWithoutBaseImageLayers), false, 'because we passed the --no-cache argument which disables the use of the cache, therefore the non-base image layers should have nothin in common');
});

it('should fail with "not found" error when config is not found', async () => {
let success = false;
try {
Expand Down

0 comments on commit 6710d4c

Please sign in to comment.