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` | | | `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`           | 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 | | | `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 | > `CSV` type must be a newline-delimited string | ||||||
| > ```yaml | > ```yaml | ||||||
|  | |||||||
| @ -38,6 +38,9 @@ inputs: | |||||||
|   config-inline: |   config-inline: | ||||||
|     description: 'Inline BuildKit config' |     description: 'Inline BuildKit config' | ||||||
|     required: false |     required: false | ||||||
|  |   state-dir: | ||||||
|  |     description: 'Path to BuildKit state volume directory' | ||||||
|  |     required: false | ||||||
| 
 | 
 | ||||||
| outputs: | outputs: | ||||||
|   name: |   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 semver from 'semver'; | ||||||
| import * as util from 'util'; | import * as util from 'util'; | ||||||
| import * as context from './context'; | import * as context from './context'; | ||||||
|  | import * as docker from './docker'; | ||||||
| import * as git from './git'; | import * as git from './git'; | ||||||
| import * as github from './github'; | import * as github from './github'; | ||||||
| import * as core from '@actions/core'; | import * as core from '@actions/core'; | ||||||
| import * as exec from '@actions/exec'; | import * as exec from '@actions/exec'; | ||||||
| import * as tc from '@actions/tool-cache'; | 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 = { | export type Builder = { | ||||||
|   name?: string; |   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; |   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> { | export async function inspect(name: string): Promise<Builder> { | ||||||
|   return await exec |   return await exec | ||||||
|     .getExecOutput(`docker`, ['buildx', 'inspect', name], { |     .getExecOutput(`docker`, ['buildx', 'inspect', name], { | ||||||
|  | |||||||
| @ -30,6 +30,7 @@ export interface Inputs { | |||||||
|   endpoint: string; |   endpoint: string; | ||||||
|   config: string; |   config: string; | ||||||
|   configInline: string; |   configInline: string; | ||||||
|  |   stateDir: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export async function getInputs(): Promise<Inputs> { | export async function getInputs(): Promise<Inputs> { | ||||||
| @ -42,7 +43,8 @@ export async function getInputs(): Promise<Inputs> { | |||||||
|     use: core.getBooleanInput('use'), |     use: core.getBooleanInput('use'), | ||||||
|     endpoint: core.getInput('endpoint'), |     endpoint: core.getInput('endpoint'), | ||||||
|     config: core.getInput('config'), |     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(); |     core.endGroup(); | ||||||
| 
 | 
 | ||||||
|     const inputs: context.Inputs = await context.getInputs(); |     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)) { |     if (util.isValidUrl(inputs.version)) { | ||||||
|       core.startGroup(`Build and install buildx`); |       core.startGroup(`Build and install buildx`); | ||||||
|       await buildx.build(inputs.version, dockerConfigHome); |       await buildx.build(inputs.version, dockerConfigHome); | ||||||
| @ -29,11 +31,15 @@ async function run(): Promise<void> { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const buildxVersion = await buildx.getVersion(); |     const buildxVersion = await buildx.getVersion(); | ||||||
|     const builderName: string = inputs.driver == 'docker' ? 'default' : `builder-${uuid.v4()}`; |  | ||||||
|     context.setOutput('name', builderName); |     context.setOutput('name', builderName); | ||||||
|     stateHelper.setBuilderName(builderName); |     stateHelper.setBuilderName(builderName); | ||||||
| 
 | 
 | ||||||
|     if (inputs.driver !== 'docker') { |     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`); |       core.startGroup(`Creating a new builder instance`); | ||||||
|       const createArgs: Array<string> = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver]; |       const createArgs: Array<string> = ['buildx', 'create', '--name', builderName, '--driver', inputs.driver]; | ||||||
|       if (buildx.satisfies(buildxVersion, '>=0.3.0')) { |       if (buildx.satisfies(buildxVersion, '>=0.3.0')) { | ||||||
| @ -114,8 +120,12 @@ async function cleanup(): Promise<void> { | |||||||
| 
 | 
 | ||||||
|   if (stateHelper.builderName.length > 0) { |   if (stateHelper.builderName.length > 0) { | ||||||
|     core.startGroup(`Removing builder`); |     core.startGroup(`Removing builder`); | ||||||
|  |     const rmArgs: Array<string> = ['buildx', 'rm', `${stateHelper.builderName}`]; | ||||||
|  |     if (stateHelper.stateDir.length > 0) { | ||||||
|  |       rmArgs.push('--keep-state'); | ||||||
|  |     } | ||||||
|     await exec |     await exec | ||||||
|       .getExecOutput('docker', ['buildx', 'rm', `${stateHelper.builderName}`], { |       .getExecOutput('docker', rmArgs, { | ||||||
|         ignoreReturnCode: true |         ignoreReturnCode: true | ||||||
|       }) |       }) | ||||||
|       .then(res => { |       .then(res => { | ||||||
| @ -125,6 +135,12 @@ async function cleanup(): Promise<void> { | |||||||
|       }); |       }); | ||||||
|     core.endGroup(); |     core.endGroup(); | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   if (stateHelper.stateDir.length > 0) { | ||||||
|  |     core.startGroup(`Saving state volume`); | ||||||
|  |     await buildx.saveStateVolume(stateHelper.stateDir, stateHelper.containerName); | ||||||
|  |     core.endGroup(); | ||||||
|  |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| if (!stateHelper.IsPost) { | if (!stateHelper.IsPost) { | ||||||
|  | |||||||
| @ -2,8 +2,10 @@ import * as core from '@actions/core'; | |||||||
| 
 | 
 | ||||||
| export const IsPost = !!process.env['STATE_isPost']; | export const IsPost = !!process.env['STATE_isPost']; | ||||||
| export const IsDebug = !!process.env['STATE_isDebug']; | export const IsDebug = !!process.env['STATE_isDebug']; | ||||||
|  | 
 | ||||||
| export const builderName = process.env['STATE_builderName'] || ''; | export const builderName = process.env['STATE_builderName'] || ''; | ||||||
| export const containerName = process.env['STATE_containerName'] || ''; | export const containerName = process.env['STATE_containerName'] || ''; | ||||||
|  | export const stateDir = process.env['STATE_stateDir'] || ''; | ||||||
| 
 | 
 | ||||||
| export function setDebug(debug: string) { | export function setDebug(debug: string) { | ||||||
|   core.saveState('isDebug', debug); |   core.saveState('isDebug', debug); | ||||||
| @ -17,6 +19,10 @@ export function setContainerName(containerName: string) { | |||||||
|   core.saveState('containerName', containerName); |   core.saveState('containerName', containerName); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | export function setStateDir(stateDir: string) { | ||||||
|  |   core.saveState('stateDir', stateDir); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| if (!IsPost) { | if (!IsPost) { | ||||||
|   core.saveState('isPost', 'true'); |   core.saveState('isPost', 'true'); | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 CrazyMax
						CrazyMax