Skip to content

Commit 6710d4c

Browse files
Merge pull request #790 from robjackstewart/control-cache-for-features
Disable cache on feature build when `--build-no-cache` is passed
2 parents 9d98f30 + 1d9a38c commit 6710d4c

File tree

2 files changed

+70
-0
lines changed

2 files changed

+70
-0
lines changed

src/spec-node/containerFeatures.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ export async function extendImage(params: DockerResolverParameters, config: Subs
9898
'build',
9999
);
100100
}
101+
if (params.buildNoCache) {
102+
args.push('--no-cache');
103+
}
101104
for (const buildArg in featureBuildInfo.buildArgs) {
102105
args.push('--build-arg', `${buildArg}=${featureBuildInfo.buildArgs[buildArg]}`);
103106
}

src/test/cli.build.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,73 @@ describe('Dev Containers CLI', function () {
6464
});
6565
});
6666

67+
it('should not use docker cache for features when `--no-cache` flag is passed', async () => {
68+
// Arrange
69+
const testFolder = `${__dirname}/configs/image-with-features`;
70+
const devContainerJson = `${testFolder}/.devcontainer.json`;
71+
72+
const devContainerFileContents = JSON.parse(fs.readFileSync(devContainerJson, 'utf8'));
73+
const baseImage = devContainerFileContents.image;
74+
75+
const originalImageName = 'feature-cache-test-original-image';
76+
const cachedImageName = 'feature-cache-test-rerun-image';
77+
const nonCachedImageName = 'feature-cache-test-no-cache-image';
78+
79+
const commandBase = `${cli} build --workspace-folder ${testFolder}`;
80+
const buildCommand = `${commandBase} --image-name ${originalImageName}`;
81+
const cachedBuildCommand = `${commandBase} --image-name ${cachedImageName}`;
82+
const buildWithoutCacheCommand = `${commandBase} --image-name ${nonCachedImageName} --no-cache`;
83+
84+
function arrayStartsWithArray(subject: string[], startsWith: string[]) {
85+
if (subject.length < startsWith.length) {
86+
return false;
87+
}
88+
for (let i = 0; i < startsWith.length; i++) {
89+
if (subject[i] !== startsWith[i]) {
90+
return false;
91+
}
92+
}
93+
return true;
94+
}
95+
96+
function haveCommonEntries(arr1: string[], arr2: string[]) {
97+
return arr1.every(item => arr2.includes(item));
98+
}
99+
100+
// Act
101+
await shellExec(`docker pull ${baseImage}`); // pull base image so we can inspect it later
102+
await shellExec(buildCommand);
103+
await shellExec(cachedBuildCommand);
104+
await shellExec(buildWithoutCacheCommand);
105+
106+
// Assert
107+
const baseImageInspectCommandResult = await shellExec(`docker inspect ${baseImage}`);
108+
const originalImageInspectCommandResult = await shellExec(`docker inspect ${originalImageName}`);
109+
const cachedImageInspectCommandResult = await shellExec(`docker inspect ${cachedImageName}`);
110+
const noCacheImageInspectCommandResult = await shellExec(`docker inspect ${nonCachedImageName}`);
111+
112+
const baseImageDetails = JSON.parse(baseImageInspectCommandResult.stdout);
113+
const originalImageDetails = JSON.parse(originalImageInspectCommandResult.stdout);
114+
const cachedImageDetails = JSON.parse(cachedImageInspectCommandResult.stdout);
115+
const noCacheImageDetails = JSON.parse(noCacheImageInspectCommandResult.stdout);
116+
117+
const baseImageLayers: string[] = baseImageDetails[0].RootFS.Layers;
118+
const originalImageLayers: string[] = originalImageDetails[0].RootFS.Layers;
119+
const cachedImageLayers: string[] = cachedImageDetails[0].RootFS.Layers;
120+
const nonCachedImageLayers: string[] = noCacheImageDetails[0].RootFS.Layers;
121+
122+
assert.equal(arrayStartsWithArray(originalImageLayers, baseImageLayers), true, 'because the image is made up of feature layers on top of the base image');
123+
assert.equal(arrayStartsWithArray(cachedImageLayers, baseImageLayers), true, 'because the image is made up of feature layers on top of the base image');
124+
assert.equal(arrayStartsWithArray(nonCachedImageLayers, baseImageLayers), true, 'because the image is made up of feature layers on top of the base image');
125+
126+
const originalImageWithoutBaseImageLayers = originalImageLayers.slice(baseImageLayers.length);
127+
const cachedImageWithoutBaseImageLayers = cachedImageLayers.slice(baseImageLayers.length);
128+
const nonCachedImageWithoutBaseImageLayers = nonCachedImageLayers.slice(baseImageLayers.length);
129+
130+
assert.deepEqual(originalImageWithoutBaseImageLayers, cachedImageWithoutBaseImageLayers, 'because they are the same image built sequentially therefore the second should have used caching');
131+
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');
132+
});
133+
67134
it('should fail with "not found" error when config is not found', async () => {
68135
let success = false;
69136
try {

0 commit comments

Comments
 (0)