mirror of
				https://github.com/docker/setup-buildx-action.git
				synced 2025-10-31 01:20:09 +08:00 
			
		
		
		
	Save BuildKit state on client for cache support
Signed-off-by: CrazyMax <crazy-max@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									74283caced
								
							
						
					
					
						commit
						f3f23a5162
					
				| @ -197,8 +197,11 @@ Following inputs can be used as `step.with` keys | ||||
| | `endpoint`         | String  | [Optional address for docker socket](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#description) or context from `docker context ls` | | ||||
| | `config`           | String  | [BuildKit config file](https://github.com/docker/buildx/blob/master/docs/reference/buildx_create.md#config) | | ||||
| | `config-inline`    | String  | Same as `config` but inline | | ||||
| | `state-dir`        | String  | Path to [BuildKit state volume](https://github.com/docker/buildx/blob/master/docs/reference/buildx_rm.md#-keep-buildkit-state---keep-state) directory | | ||||
| 
 | ||||
| > `config` and `config-inline` are mutually exclusive. | ||||
| > :bulb: `config` and `config-inline` are mutually exclusive. | ||||
| 
 | ||||
| > :bulb: `state-dir` can only be used with the `docker-container` driver and a builder with a single node. | ||||
| 
 | ||||
| > `CSV` type must be a newline-delimited string | ||||
| > ```yaml | ||||
|  | ||||
| @ -38,6 +38,9 @@ inputs: | ||||
|   config-inline: | ||||
|     description: 'Inline BuildKit config' | ||||
|     required: false | ||||
|   state-dir: | ||||
|     description: 'Path to BuildKit state volume directory' | ||||
|     required: false | ||||
| 
 | ||||
| outputs: | ||||
|   name: | ||||
|  | ||||
							
								
								
									
										4
									
								
								dist/index.js
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								dist/index.js
									
									
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								dist/index.js.map
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								dist/index.js.map
									
									
									
										generated
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @ -3,11 +3,16 @@ import * as path from 'path'; | ||||
| import * as semver from 'semver'; | ||||
| import * as util from 'util'; | ||||
| import * as context from './context'; | ||||
| import * as docker from './docker'; | ||||
| import * as git from './git'; | ||||
| import * as github from './github'; | ||||
| import * as core from '@actions/core'; | ||||
| import * as exec from '@actions/exec'; | ||||
| import * as tc from '@actions/tool-cache'; | ||||
| import child_process from 'child_process'; | ||||
| 
 | ||||
| const uid = parseInt(child_process.execSync(`id -u`, {encoding: 'utf8'}).trim()); | ||||
| const gid = parseInt(child_process.execSync(`id -g`, {encoding: 'utf8'}).trim()); | ||||
| 
 | ||||
| export type Builder = { | ||||
|   name?: string; | ||||
| @ -81,6 +86,19 @@ export function satisfies(version: string, range: string): boolean { | ||||
|   return semver.satisfies(version, range) || /^[0-9a-f]{7}$/.exec(version) !== null; | ||||
| } | ||||
| 
 | ||||
| export async function createStateVolume(stateDir: string, nodeName: string): Promise<void> { | ||||
|   return await docker.volumeCreate(stateDir, `${nodeName}_state`); | ||||
| } | ||||
| 
 | ||||
| export async function saveStateVolume(dir: string, nodeName: string): Promise<void> { | ||||
|   const ctnid = await docker.containerCreate('busybox', `${nodeName}_state:/data`); | ||||
|   const outdir = await docker.containerCopy(ctnid, `${ctnid}:/data`); | ||||
|   await docker.volumeRemove(`${nodeName}_state`); | ||||
|   fs.rmdirSync(dir, {recursive: true}); | ||||
|   fs.renameSync(outdir, dir); | ||||
|   await docker.containerRemove(ctnid); | ||||
| } | ||||
| 
 | ||||
| export async function inspect(name: string): Promise<Builder> { | ||||
|   return await exec | ||||
|     .getExecOutput(`docker`, ['buildx', 'inspect', name], { | ||||
|  | ||||
| @ -30,6 +30,7 @@ export interface Inputs { | ||||
|   endpoint: string; | ||||
|   config: string; | ||||
|   configInline: string; | ||||
|   stateDir: string; | ||||
| } | ||||
| 
 | ||||
| export async function getInputs(): Promise<Inputs> { | ||||
| @ -42,7 +43,8 @@ export async function getInputs(): Promise<Inputs> { | ||||
|     use: core.getBooleanInput('use'), | ||||
|     endpoint: core.getInput('endpoint'), | ||||
|     config: core.getInput('config'), | ||||
|     configInline: core.getInput('config-inline') | ||||
|     configInline: core.getInput('config-inline'), | ||||
|     stateDir: core.getInput('state-dir') | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										71
									
								
								src/docker.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/docker.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| import * as fs from 'fs'; | ||||
| import * as path from 'path'; | ||||
| import * as uuid from 'uuid'; | ||||
| import * as context from './context'; | ||||
| import * as exec from '@actions/exec'; | ||||
| 
 | ||||
| export async function volumeCreate(dir: string, name: string): Promise<void> { | ||||
|   if (!fs.existsSync(dir)) { | ||||
|     fs.mkdirSync(dir, {recursive: true}); | ||||
|   } | ||||
|   return await exec | ||||
|     .getExecOutput(`docker`, ['volume', 'create', '--name', `${name}`, '--driver', 'local', '--opt', `o=bind,acl`, '--opt', 'type=none', '--opt', `device=${dir}`], { | ||||
|       ignoreReturnCode: true | ||||
|     }) | ||||
|     .then(res => { | ||||
|       if (res.stderr.length > 0 && res.exitCode != 0) { | ||||
|         throw new Error(res.stderr.trim()); | ||||
|       } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export async function volumeRemove(name: string): Promise<void> { | ||||
|   return await exec | ||||
|     .getExecOutput(`docker`, ['volume', 'rm', '-f', `${name}`], { | ||||
|       ignoreReturnCode: true | ||||
|     }) | ||||
|     .then(res => { | ||||
|       if (res.stderr.length > 0 && res.exitCode != 0) { | ||||
|         throw new Error(res.stderr.trim()); | ||||
|       } | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export async function containerCreate(image: string, volume: string): Promise<string> { | ||||
|   return await exec | ||||
|     .getExecOutput(`docker`, ['create', '--rm', '-v', `${volume}`, `${image}`], { | ||||
|       ignoreReturnCode: true | ||||
|     }) | ||||
|     .then(res => { | ||||
|       if (res.stderr.length > 0 && res.exitCode != 0) { | ||||
|         throw new Error(res.stderr.trim()); | ||||
|       } | ||||
|       return res.stdout.trim(); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export async function containerCopy(ctnid: string, src: string): Promise<string> { | ||||
|   const outdir = path.join(context.tmpDir(), `ctn-copy-${uuid.v4()}`).split(path.sep).join(path.posix.sep); | ||||
|   return await exec | ||||
|     .getExecOutput(`docker`, ['cp', '-a', `${src}`, `${outdir}`], { | ||||
|       ignoreReturnCode: true | ||||
|     }) | ||||
|     .then(res => { | ||||
|       if (res.stderr.length > 0 && res.exitCode != 0) { | ||||
|         throw new Error(res.stderr.trim()); | ||||
|       } | ||||
|       return outdir; | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| export async function containerRemove(ctnid: string): Promise<void> { | ||||
|   return await exec | ||||
|     .getExecOutput(`docker`, ['rm', '-f', '-v', `${ctnid}`], { | ||||
|       ignoreReturnCode: true | ||||
|     }) | ||||
|     .then(res => { | ||||
|       if (res.stderr.length > 0 && res.exitCode != 0) { | ||||
|         throw new Error(res.stderr.trim()); | ||||
|       } | ||||
|     }); | ||||
| } | ||||
							
								
								
									
										22
									
								
								src/main.ts
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								src/main.ts
									
									
									
									
									
								
							| @ -16,8 +16,10 @@ async function run(): Promise<void> { | ||||
|     core.endGroup(); | ||||
| 
 | ||||
|     const inputs: context.Inputs = await context.getInputs(); | ||||
|     const dockerConfigHome: string = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); | ||||
|     const builderName: string = inputs.driver == 'docker' ? 'default' : `builder-${uuid.v4()}`; | ||||
|     stateHelper.setStateDir(inputs.stateDir); | ||||
| 
 | ||||
|     const dockerConfigHome: string = process.env.DOCKER_CONFIG || path.join(os.homedir(), '.docker'); | ||||
|     if (util.isValidUrl(inputs.version)) { | ||||
|       core.startGroup(`Build and install buildx`); | ||||
|       await buildx.build(inputs.version, dockerConfigHome); | ||||
| @ -29,11 +31,15 @@ async function run(): Promise<void> { | ||||
|     } | ||||
| 
 | ||||
|     const buildxVersion = await buildx.getVersion(); | ||||
|     const builderName: string = inputs.driver == 'docker' ? 'default' : `builder-${uuid.v4()}`; | ||||
|     context.setOutput('name', builderName); | ||||
|     stateHelper.setBuilderName(builderName); | ||||
| 
 | ||||
|     if (inputs.driver !== 'docker') { | ||||
|       if (inputs.stateDir.length > 0) { | ||||
|         await core.group(`Creating BuildKit state volume from ${inputs.stateDir}`, async () => { | ||||
|           await buildx.createStateVolume(inputs.stateDir, `buildx_buildkit_${builderName}0`); | ||||
|         }); | ||||
|       } | ||||
|       core.startGroup(`Creating a new builder instance`); | ||||
|       const createArgs: Array<string> = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver]; | ||||
|       if (buildx.satisfies(buildxVersion, '>=0.3.0')) { | ||||
| @ -114,8 +120,12 @@ async function cleanup(): Promise<void> { | ||||
| 
 | ||||
|   if (stateHelper.builderName.length > 0) { | ||||
|     core.startGroup(`Removing builder`); | ||||
|     const rmArgs: Array<string> = ['buildx', 'rm', `${stateHelper.builderName}`]; | ||||
|     if (stateHelper.stateDir.length > 0) { | ||||
|       rmArgs.push('--keep-state'); | ||||
|     } | ||||
|     await exec | ||||
|       .getExecOutput('docker', ['buildx', 'rm', `${stateHelper.builderName}`], { | ||||
|       .getExecOutput('docker', rmArgs, { | ||||
|         ignoreReturnCode: true | ||||
|       }) | ||||
|       .then(res => { | ||||
| @ -125,6 +135,12 @@ async function cleanup(): Promise<void> { | ||||
|       }); | ||||
|     core.endGroup(); | ||||
|   } | ||||
| 
 | ||||
|   if (stateHelper.stateDir.length > 0) { | ||||
|     core.startGroup(`Saving state volume`); | ||||
|     await buildx.saveStateVolume(stateHelper.stateDir, stateHelper.containerName); | ||||
|     core.endGroup(); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| if (!stateHelper.IsPost) { | ||||
|  | ||||
| @ -2,8 +2,10 @@ import * as core from '@actions/core'; | ||||
| 
 | ||||
| export const IsPost = !!process.env['STATE_isPost']; | ||||
| export const IsDebug = !!process.env['STATE_isDebug']; | ||||
| 
 | ||||
| export const builderName = process.env['STATE_builderName'] || ''; | ||||
| export const containerName = process.env['STATE_containerName'] || ''; | ||||
| export const stateDir = process.env['STATE_stateDir'] || ''; | ||||
| 
 | ||||
| export function setDebug(debug: string) { | ||||
|   core.saveState('isDebug', debug); | ||||
| @ -17,6 +19,10 @@ export function setContainerName(containerName: string) { | ||||
|   core.saveState('containerName', containerName); | ||||
| } | ||||
| 
 | ||||
| export function setStateDir(stateDir: string) { | ||||
|   core.saveState('stateDir', stateDir); | ||||
| } | ||||
| 
 | ||||
| if (!IsPost) { | ||||
|   core.saveState('isPost', 'true'); | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 CrazyMax
						CrazyMax