Skip to content

Commit 4e6d40f

Browse files
committed
Allow users to specify cache-dir location used in auto-discovery
Signed-off-by: Curtis Vogt <curtis.vogt@gmail.com>
1 parent 463dbf0 commit 4e6d40f

6 files changed

Lines changed: 92 additions & 23 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ Options:
136136
--extract Extract the cache from the docker container (extract step). Otherwise, inject the cache (main step)
137137
--cache-map The map of actions source to container destination paths for the cache paths
138138
--dockerfile The Dockerfile to use for the auto-discovery of cache-map. Default: 'Dockerfile'
139+
--cache-dir The root directory where cache content is injected from/extracted to when using auto-discovery of the cache-map.
139140
--scratch-dir Where the action is stores some temporary files for its processing. Default: 'scratch'
140141
--skip-extraction Skip the extraction of the cache from the docker container
141142
--builder The name of the buildx builder. Default: 'default'

action.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ inputs:
44
cache-map:
55
description: "The map of actions source paths to container destination paths or mount arguments. If not provided, the Dockerfile will be used to determine the cache paths."
66
dockerfile:
7-
description: "The Dockerfile to use for the auto-discovery of cache-map. Default: `Dockerfile`"
7+
description: "The Dockerfile to use for auto-discovery of the cache-map. Default: `Dockerfile`"
88
default: "Dockerfile"
9+
cache-dir:
10+
description: "The root directory where cache content is injected from/extracted to when using auto-discovery of the cache-map. If not provided, each cache mount `target` will be used as a source path."
11+
default: ""
912
cache-source:
1013
deprecationMessage: "Use `cache-map` instead"
1114
description: "Where the cache is stored in the calling workspace. Default: `cache`"

dist/index.js

Lines changed: 10 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/opts.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ export type Opts = {
77
"extract": boolean
88
"cache-map": string
99
"dockerfile": string
10+
"cache-dir": string | null
1011
"scratch-dir": string
1112
"skip-extraction": boolean
1213
"utility-image": string
@@ -23,14 +24,15 @@ export function parseOpts(args: string[]): mri.Argv<Opts> {
2324
default: {
2425
"cache-map": getInput("cache-map") || "{}",
2526
"dockerfile": getInput("dockerfile") || "Dockerfile",
27+
"cache-dir": getInput("cache-dir") || null,
2628
"scratch-dir": getInput("scratch-dir") || "scratch",
2729
"skip-extraction": (getInput("skip-extraction") || "false") === "true",
2830
"extract": process.env[`STATE_POST`] !== undefined,
2931
"utility-image": getInput("utility-image") || "ghcr.io/containerd/busybox:latest",
3032
"builder": getInput("builder") || "default",
3133
"help": false,
3234
},
33-
string: ["cache-map", "dockerfile", "scratch-dir", "cache-source", "cache-target", "utility-image", "builder"],
35+
string: ["cache-map", "dockerfile", "cache-dir", "scratch-dir", "cache-source", "cache-target", "utility-image", "builder"],
3436
boolean: ["skip-extraction", "help", "extract"],
3537
alias: {
3638
"help": ["h"],
@@ -55,7 +57,8 @@ Save 'RUN --mount=type=cache' caches on GitHub Actions or other CI platforms
5557
Options:
5658
--extract Extract the cache from the docker container (extract step). Otherwise, inject the cache (main step)
5759
--cache-map The map of actions source paths to container destination paths or mount arguments
58-
--dockerfile The Dockerfile to use for the auto-discovery of cache-map. Default: 'Dockerfile'
60+
--dockerfile The Dockerfile to use for auto-discovery of the cache-map. Default: 'Dockerfile'
61+
--cache-dir The root directory where cache content is injected from/extracted to when using auto-discovery of the cache-map.
5962
--scratch-dir Where the action is stores some temporary files for its processing. Default: 'scratch'
6063
--skip-extraction Skip the extraction of the cache from the docker container
6164
--utility-image The container image to use for injecting and extracting the cache. Default: 'ghcr.io/containerd/busybox:latest'
@@ -72,7 +75,7 @@ export type ToStringable = {
7275
export type CacheOptions = TargetPath | { target: TargetPath } & Record<string, ToStringable>
7376
export type CacheMap = Record<SourcePath, CacheOptions>
7477

75-
async function getCacheMapFromDockerfile(dockerfilePath: string): Promise<CacheMap> {
78+
async function getCacheMapFromDockerfile(dockerfilePath: string, bindRoot: string | null): Promise<CacheMap> {
7679
const dockerfileContent = await fs.readFile(dockerfilePath, "utf-8");
7780
const dockerfile = DockerfileParser.parse(dockerfileContent);
7881

@@ -89,11 +92,14 @@ async function getCacheMapFromDockerfile(dockerfilePath: string): Promise<CacheM
8992
throw new Error('cache mount must define id or target: ' + flag.toString() + ' in ' + run.toString());
9093
}
9194

95+
// The directory on the host to inject/extract the cache mount data from
96+
const bindDir = bindRoot !== null ? `${bindRoot}/${id}` : id
97+
9298
// The target in this action does not matter as long as it is
9399
// different than /var/dance-cache of course
94100
const target = "/var/cache-target";
95101

96-
cacheMap[id] = {
102+
cacheMap[bindDir] = {
97103
id,
98104
target,
99105
};
@@ -112,7 +118,7 @@ export async function getCacheMap(opts: Opts): Promise<CacheMap> {
112118
}
113119

114120
console.log(`No cache map provided. Trying to parse the Dockerfile to find the cache mount instructions...`);
115-
const cacheMapFromDockerfile = await getCacheMapFromDockerfile(opts["dockerfile"]);
121+
const cacheMapFromDockerfile = await getCacheMapFromDockerfile(opts["dockerfile"], opts["cache-dir"]);
116122
console.log(`Cache map parsed from Dockerfile: ${JSON.stringify(cacheMapFromDockerfile)}`);
117123
return cacheMapFromDockerfile;
118124
} catch (e) {

tests/opts.test.ts

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ test('parseOpts with no arguments', () => {
88
"_": [],
99
"cache-map": "{}",
1010
"dockerfile": "Dockerfile",
11+
"cache-dir": null,
1112
"scratch-dir": "scratch",
1213
"skip-extraction": false,
1314
"extract": false,
@@ -24,6 +25,7 @@ test('parseOpts with cache-map argument', () => {
2425
"_": [],
2526
"cache-map": '{"key": "value"}',
2627
"dockerfile": "Dockerfile",
28+
"cache-dir": null,
2729
"scratch-dir": "scratch",
2830
"skip-extraction": false,
2931
"extract": false,
@@ -40,6 +42,7 @@ test('parseOpts with deprecated cache-source and cache-target arguments', () =>
4042
"_": [],
4143
"cache-map": '{"source":"target"}',
4244
"dockerfile": "Dockerfile",
45+
"cache-dir": null,
4346
"scratch-dir": "scratch",
4447
"skip-extraction": false,
4548
"extract": false,
@@ -58,6 +61,7 @@ test('parseOpts with utility-image argument', () => {
5861
"_": [],
5962
"cache-map": '{}',
6063
"dockerfile": "Dockerfile",
64+
"cache-dir": null,
6165
"scratch-dir": "scratch",
6266
"skip-extraction": false,
6367
"extract": false,
@@ -74,6 +78,7 @@ test('parseOpts with builder argument', () => {
7478
"_": [],
7579
"cache-map": '{}',
7680
"dockerfile": "Dockerfile",
81+
"cache-dir": null,
7782
"scratch-dir": "scratch",
7883
"skip-extraction": false,
7984
"extract": false,
@@ -90,6 +95,24 @@ test('parseOpts with dockerfile argument', () => {
9095
"_": [],
9196
"cache-map": "{}",
9297
"dockerfile": "Dockerfile.custom",
98+
"cache-dir": null,
99+
"scratch-dir": "scratch",
100+
"skip-extraction": false,
101+
"extract": false,
102+
"h": false,
103+
"help": false,
104+
"utility-image": "ghcr.io/containerd/busybox:latest",
105+
"builder": "default"
106+
})
107+
})
108+
109+
test('parseOpts with cache-dir argument', () => {
110+
const opts = parseOpts(['--cache-dir', '/tmp/cache'])
111+
expect(opts).toEqual({
112+
"_": [],
113+
"cache-map": "{}",
114+
"dockerfile": "Dockerfile",
115+
"cache-dir": "/tmp/cache",
93116
"scratch-dir": "scratch",
94117
"skip-extraction": false,
95118
"extract": false,
@@ -106,6 +129,7 @@ test('parseOpts with help argument', () => {
106129
"_": [],
107130
"cache-map": "{}",
108131
"dockerfile": "Dockerfile",
132+
"cache-dir": null,
109133
"scratch-dir": "scratch",
110134
"skip-extraction": false,
111135
"extract": false,
@@ -128,10 +152,7 @@ test('getCacheMap with both cache-map and dockerfile specified', async () => {
128152
expect(cacheMap).toEqual({ key: 'value' })
129153
})
130154

131-
test('getCacheMapFromDockerfile', async () => {
132-
const tmpDir = await fs.mkdtemp('/tmp/dockerfile-test-');
133-
const dockerfilePath = `${tmpDir}/Dockerfile`
134-
await fs.writeFile(dockerfilePath, `
155+
const DOCKERFILE_CONTENT = `
135156
FROM alpine:latest AS builder
136157
137158
# Target absolute path, no id
@@ -154,11 +175,16 @@ RUN --mount=type=cache,id=cache2,target=/tmp/cache \
154175
WORKDIR /app2
155176
RUN --mount=type=cache,id=cache3,target=cache \
156177
echo "Hello, World!" > cache/hello.txt
157-
`);
178+
`
179+
180+
test('getCacheMapFromDockerfile without bindRoot', async ({ onTestFinished }) => {
181+
const tmpDir = await fs.mkdtemp('/tmp/dockerfile-test-')
182+
onTestFinished(() => fs.rm(tmpDir, { recursive: true }))
183+
const dockerfilePath = `${tmpDir}/Dockerfile`
184+
await fs.writeFile(dockerfilePath, DOCKERFILE_CONTENT);
158185

159186
const opts = parseOpts(['--dockerfile', dockerfilePath])
160187
const cacheMap = await getCacheMap(opts)
161-
await fs.rm(tmpDir, { recursive: true })
162188

163189
expect(cacheMap).toEqual(
164190
{
@@ -180,7 +206,39 @@ RUN --mount=type=cache,id=cache3,target=cache \
180206
}
181207
}
182208
)
183-
})
209+
});
210+
211+
test('getCacheMapFromDockerfile with bindRoot', async ({ onTestFinished }) => {
212+
const tmpDir = await fs.mkdtemp('/tmp/dockerfile-test-')
213+
onTestFinished(() => fs.rm(tmpDir, { recursive: true }))
214+
const dockerfilePath = `${tmpDir}/Dockerfile`
215+
const cacheDir: string = `${tmpDir}/cache-mount`
216+
await fs.writeFile(dockerfilePath, DOCKERFILE_CONTENT);
217+
218+
const opts = parseOpts(['--dockerfile', dockerfilePath, '--cache-dir', cacheDir])
219+
const cacheMap = await getCacheMap(opts)
220+
221+
expect(cacheMap).toEqual(
222+
{
223+
[`${cacheDir}//tmp/cache`]: {
224+
'id': '/tmp/cache',
225+
'target': '/var/cache-target'
226+
},
227+
[`${cacheDir}/cache1`]: {
228+
'id': 'cache1',
229+
'target': '/var/cache-target'
230+
},
231+
[`${cacheDir}/cache2`]: {
232+
'id': 'cache2',
233+
'target': '/var/cache-target'
234+
},
235+
[`${cacheDir}/cache3`]: {
236+
'id': 'cache3',
237+
'target': '/var/cache-target'
238+
}
239+
}
240+
)
241+
});
184242

185243
test('getCacheMap with invalid JSON', async() => {
186244
const opts = parseOpts(['--cache-map', 'invalid'])

0 commit comments

Comments
 (0)