mirror of
				https://github.com/docker/build-push-action.git
				synced 2025-11-04 00:10:55 +00:00 
			
		
		
		
	*: teach action to hot load sticky disks
This change teaches the build push action to request a stickydisk every time it runs. Once the SD is hotloaded the VM will mount the buildkit root dir and starts buildkitd.
This commit is contained in:
		
					parent
					
						
							
								6f9ad79a61
							
						
					
				
			
			
				commit
				
					
						3b1df39d5d
					
				
			
		
					 8 changed files with 337 additions and 166 deletions
				
			
		
							
								
								
									
										24
									
								
								dist/index.js
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								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
											
										
									
								
							
							
								
								
									
										18
									
								
								dist/licenses.txt
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								dist/licenses.txt
									
										
									
										generated
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -611,6 +611,24 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 | 
			
		|||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 | 
			
		||||
IN THE SOFTWARE.
 | 
			
		||||
 | 
			
		||||
@iarna/toml
 | 
			
		||||
ISC
 | 
			
		||||
Copyright (c) 2016, Rebecca Turner <me@re-becca.org>
 | 
			
		||||
 | 
			
		||||
Permission to use, copy, modify, and/or distribute this software for any
 | 
			
		||||
purpose with or without fee is hereby granted, provided that the above
 | 
			
		||||
copyright notice and this permission notice appear in all copies.
 | 
			
		||||
 | 
			
		||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
			
		||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | 
			
		||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 | 
			
		||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
			
		||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 | 
			
		||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 | 
			
		||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@octokit/auth-token
 | 
			
		||||
MIT
 | 
			
		||||
The MIT License
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										38
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										38
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -9,8 +9,10 @@
 | 
			
		|||
      "dependencies": {
 | 
			
		||||
        "@actions/core": "^1.10.1",
 | 
			
		||||
        "@docker/actions-toolkit": "0.37.1",
 | 
			
		||||
        "@iarna/toml": "^2.2.5",
 | 
			
		||||
        "axios": "^1.7.7",
 | 
			
		||||
        "handlebars": "^4.7.7"
 | 
			
		||||
        "handlebars": "^4.7.7",
 | 
			
		||||
        "portfinder": "^1.0.32"
 | 
			
		||||
      },
 | 
			
		||||
      "devDependencies": {
 | 
			
		||||
        "@types/node": "^20.12.12",
 | 
			
		||||
| 
						 | 
				
			
			@ -1283,6 +1285,11 @@
 | 
			
		|||
      "deprecated": "Use @eslint/object-schema instead",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@iarna/toml": {
 | 
			
		||||
      "version": "2.2.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz",
 | 
			
		||||
      "integrity": "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@isaacs/cliui": {
 | 
			
		||||
      "version": "8.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -5952,6 +5959,35 @@
 | 
			
		|||
        "node": ">=8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/portfinder": {
 | 
			
		||||
      "version": "1.0.32",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz",
 | 
			
		||||
      "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "async": "^2.6.4",
 | 
			
		||||
        "debug": "^3.2.7",
 | 
			
		||||
        "mkdirp": "^0.5.6"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 0.12.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/portfinder/node_modules/async": {
 | 
			
		||||
      "version": "2.6.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
 | 
			
		||||
      "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "lodash": "^4.17.14"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/portfinder/node_modules/debug": {
 | 
			
		||||
      "version": "3.2.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
 | 
			
		||||
      "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "ms": "^2.1.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/prelude-ls": {
 | 
			
		||||
      "version": "1.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -28,8 +28,10 @@
 | 
			
		|||
  "dependencies": {
 | 
			
		||||
    "@actions/core": "^1.10.1",
 | 
			
		||||
    "@docker/actions-toolkit": "0.37.1",
 | 
			
		||||
    "@iarna/toml": "^2.2.5",
 | 
			
		||||
    "axios": "^1.7.7",
 | 
			
		||||
    "handlebars": "^4.7.7"
 | 
			
		||||
    "handlebars": "^4.7.7",
 | 
			
		||||
    "portfinder": "^1.0.32"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/node": "^20.12.12",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,11 @@
 | 
			
		|||
import * as core from '@actions/core';
 | 
			
		||||
import * as handlebars from 'handlebars';
 | 
			
		||||
 | 
			
		||||
import {Build} from '@docker/actions-toolkit/lib/buildx/build';
 | 
			
		||||
import {Context} from '@docker/actions-toolkit/lib/context';
 | 
			
		||||
import {GitHub} from '@docker/actions-toolkit/lib/github';
 | 
			
		||||
import {Toolkit} from '@docker/actions-toolkit/lib/toolkit';
 | 
			
		||||
import {Util} from '@docker/actions-toolkit/lib/util';
 | 
			
		||||
import { Build } from '@docker/actions-toolkit/lib/buildx/build';
 | 
			
		||||
import { Context } from '@docker/actions-toolkit/lib/context';
 | 
			
		||||
import { GitHub } from '@docker/actions-toolkit/lib/github';
 | 
			
		||||
import { Toolkit } from '@docker/actions-toolkit/lib/toolkit';
 | 
			
		||||
import { Util } from '@docker/actions-toolkit/lib/util';
 | 
			
		||||
import * as path from 'path';
 | 
			
		||||
 | 
			
		||||
export interface Inputs {
 | 
			
		||||
| 
						 | 
				
			
			@ -48,35 +48,35 @@ export async function getInputs(): Promise<Inputs> {
 | 
			
		|||
  return {
 | 
			
		||||
    'add-hosts': Util.getInputList('add-hosts'),
 | 
			
		||||
    allow: Util.getInputList('allow'),
 | 
			
		||||
    annotations: Util.getInputList('annotations', {ignoreComma: true}),
 | 
			
		||||
    attests: Util.getInputList('attests', {ignoreComma: true}),
 | 
			
		||||
    'build-args': Util.getInputList('build-args', {ignoreComma: true}),
 | 
			
		||||
    'build-contexts': Util.getInputList('build-contexts', {ignoreComma: true}),
 | 
			
		||||
    annotations: Util.getInputList('annotations', { ignoreComma: true }),
 | 
			
		||||
    attests: Util.getInputList('attests', { ignoreComma: true }),
 | 
			
		||||
    'build-args': Util.getInputList('build-args', { ignoreComma: true }),
 | 
			
		||||
    'build-contexts': Util.getInputList('build-contexts', { ignoreComma: true }),
 | 
			
		||||
    builder: core.getInput('builder'),
 | 
			
		||||
    'cache-from': Util.getInputList('cache-from', {ignoreComma: true}),
 | 
			
		||||
    'cache-to': Util.getInputList('cache-to', {ignoreComma: true}),
 | 
			
		||||
    'cache-from': Util.getInputList('cache-from', { ignoreComma: true }),
 | 
			
		||||
    'cache-to': Util.getInputList('cache-to', { ignoreComma: true }),
 | 
			
		||||
    'cgroup-parent': core.getInput('cgroup-parent'),
 | 
			
		||||
    context: core.getInput('context') || Context.gitContext(),
 | 
			
		||||
    file: core.getInput('file'),
 | 
			
		||||
    labels: Util.getInputList('labels', {ignoreComma: true}),
 | 
			
		||||
    labels: Util.getInputList('labels', { ignoreComma: true }),
 | 
			
		||||
    load: core.getBooleanInput('load'),
 | 
			
		||||
    network: core.getInput('network'),
 | 
			
		||||
    'no-cache': core.getBooleanInput('no-cache'),
 | 
			
		||||
    'no-cache-filters': Util.getInputList('no-cache-filters'),
 | 
			
		||||
    outputs: Util.getInputList('outputs', {ignoreComma: true, quote: false}),
 | 
			
		||||
    outputs: Util.getInputList('outputs', { ignoreComma: true, quote: false }),
 | 
			
		||||
    platforms: Util.getInputList('platforms'),
 | 
			
		||||
    provenance: Build.getProvenanceInput('provenance'),
 | 
			
		||||
    pull: core.getBooleanInput('pull'),
 | 
			
		||||
    push: core.getBooleanInput('push'),
 | 
			
		||||
    sbom: core.getInput('sbom'),
 | 
			
		||||
    secrets: Util.getInputList('secrets', {ignoreComma: true}),
 | 
			
		||||
    secrets: Util.getInputList('secrets', { ignoreComma: true }),
 | 
			
		||||
    'secret-envs': Util.getInputList('secret-envs'),
 | 
			
		||||
    'secret-files': Util.getInputList('secret-files', {ignoreComma: true}),
 | 
			
		||||
    'secret-files': Util.getInputList('secret-files', { ignoreComma: true }),
 | 
			
		||||
    'shm-size': core.getInput('shm-size'),
 | 
			
		||||
    ssh: Util.getInputList('ssh'),
 | 
			
		||||
    tags: Util.getInputList('tags'),
 | 
			
		||||
    target: core.getInput('target'),
 | 
			
		||||
    ulimit: Util.getInputList('ulimit', {ignoreComma: true}),
 | 
			
		||||
    ulimit: Util.getInputList('ulimit', { ignoreComma: true }),
 | 
			
		||||
    'github-token': core.getInput('github-token'),
 | 
			
		||||
    nofallback: core.getBooleanInput('nofallback')
 | 
			
		||||
  };
 | 
			
		||||
| 
						 | 
				
			
			@ -310,8 +310,6 @@ export const tlsRootCaCertificatePath = '/tmp/blacksmith_root_ca_certificate.pem
 | 
			
		|||
 | 
			
		||||
export async function getRemoteBuilderArgs(name: string, builderUrl: string): Promise<Array<string>> {
 | 
			
		||||
  const args: Array<string> = ['create', '--name', name, '--driver', 'remote'];
 | 
			
		||||
  // Add TLS paths as driver options
 | 
			
		||||
  args.push('--driver-opt', `key=${tlsClientKeyPath},cert=${tlsClientCaCertificatePath},cacert=${tlsRootCaCertificatePath}`);
 | 
			
		||||
 | 
			
		||||
  // TODO(aayush): Instead of hardcoding the platform, we should fail the build if the platform is
 | 
			
		||||
  // unsupported.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										361
									
								
								src/main.ts
									
										
									
									
									
								
							
							
						
						
									
										361
									
								
								src/main.ts
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -4,114 +4,91 @@ import * as stateHelper from './state-helper';
 | 
			
		|||
import * as core from '@actions/core';
 | 
			
		||||
import * as actionsToolkit from '@docker/actions-toolkit';
 | 
			
		||||
 | 
			
		||||
import {Buildx} from '@docker/actions-toolkit/lib/buildx/buildx';
 | 
			
		||||
import {History as BuildxHistory} from '@docker/actions-toolkit/lib/buildx/history';
 | 
			
		||||
import {Context} from '@docker/actions-toolkit/lib/context';
 | 
			
		||||
import {Docker} from '@docker/actions-toolkit/lib/docker/docker';
 | 
			
		||||
import {Exec} from '@docker/actions-toolkit/lib/exec';
 | 
			
		||||
import {GitHub} from '@docker/actions-toolkit/lib/github';
 | 
			
		||||
import {Toolkit} from '@docker/actions-toolkit/lib/toolkit';
 | 
			
		||||
import {Util} from '@docker/actions-toolkit/lib/util';
 | 
			
		||||
import { Buildx } from '@docker/actions-toolkit/lib/buildx/buildx';
 | 
			
		||||
import { History as BuildxHistory } from '@docker/actions-toolkit/lib/buildx/history';
 | 
			
		||||
import { Context } from '@docker/actions-toolkit/lib/context';
 | 
			
		||||
import { Docker } from '@docker/actions-toolkit/lib/docker/docker';
 | 
			
		||||
import { Exec } from '@docker/actions-toolkit/lib/exec';
 | 
			
		||||
import { GitHub } from '@docker/actions-toolkit/lib/github';
 | 
			
		||||
import { Toolkit } from '@docker/actions-toolkit/lib/toolkit';
 | 
			
		||||
import { Util } from '@docker/actions-toolkit/lib/util';
 | 
			
		||||
 | 
			
		||||
import {BuilderInfo} from '@docker/actions-toolkit/lib/types/buildx/builder';
 | 
			
		||||
import {ConfigFile} from '@docker/actions-toolkit/lib/types/docker/docker';
 | 
			
		||||
import {UploadArtifactResponse} from '@docker/actions-toolkit/lib/types/github';
 | 
			
		||||
import axios, {AxiosError, AxiosInstance, AxiosResponse} from 'axios';
 | 
			
		||||
import { BuilderInfo } from '@docker/actions-toolkit/lib/types/buildx/builder';
 | 
			
		||||
import { ConfigFile } from '@docker/actions-toolkit/lib/types/docker/docker';
 | 
			
		||||
import { UploadArtifactResponse } from '@docker/actions-toolkit/lib/types/github';
 | 
			
		||||
import axios, { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
 | 
			
		||||
 | 
			
		||||
import * as context from './context';
 | 
			
		||||
import { promisify } from 'util';
 | 
			
		||||
import { exec } from 'child_process';
 | 
			
		||||
import * as TOML from '@iarna/toml';
 | 
			
		||||
import portfinder from 'portfinder';
 | 
			
		||||
 | 
			
		||||
const buildxVersion = 'v0.17.0';
 | 
			
		||||
const mountPoint = '/var/lib/buildkit';
 | 
			
		||||
const device = '/dev/vdb';
 | 
			
		||||
const execAsync = promisify(exec);
 | 
			
		||||
 | 
			
		||||
async function getBlacksmithHttpClient(): Promise<AxiosInstance> {
 | 
			
		||||
  let baseURL = process.env.BUILDER_URL;
 | 
			
		||||
  if (!baseURL) {
 | 
			
		||||
    baseURL = process.env.PETNAME?.includes('staging') ? 'https://anvil-staging.fly.dev/build_tasks' : 'https://anvil.blacksmith.sh/build_tasks';
 | 
			
		||||
  }
 | 
			
		||||
  let stickyDiskMgrUrl = 'http://192.168.127.1:5556';
 | 
			
		||||
 | 
			
		||||
  core.info(`Using Blacksmith base URL: ${stickyDiskMgrUrl}`);
 | 
			
		||||
  core.info(`Using Blacksmith token: ${process.env.BLACKSMITH_STICKYDISK_TOKEN}`);
 | 
			
		||||
  core.info(`Using Github repo name: ${process.env.GITHUB_REPO_NAME}`);
 | 
			
		||||
  return axios.create({
 | 
			
		||||
    baseURL,
 | 
			
		||||
    baseURL: stickyDiskMgrUrl,
 | 
			
		||||
    headers: {
 | 
			
		||||
      Authorization: `Bearer ${process.env.BLACKSMITH_ANVIL_TOKEN}`
 | 
			
		||||
      'Authorization': `Bearer ${process.env.BLACKSMITH_STICKYDISK_TOKEN}`,
 | 
			
		||||
      'X-Github-Repo-Name': process.env.GITHUB_REPO_NAME || ''
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function reportBuildCompleted() {
 | 
			
		||||
  let retries = 0;
 | 
			
		||||
  const maxRetries = 3;
 | 
			
		||||
  while (retries < maxRetries) {
 | 
			
		||||
  try {
 | 
			
		||||
      const builderLaunchTime = stateHelper.blacksmithBuilderLaunchTime;
 | 
			
		||||
    const client = await getBlacksmithHttpClient();
 | 
			
		||||
      await client.post(`/${stateHelper.blacksmithBuildTaskId}/complete`, {
 | 
			
		||||
        builder_launch_time: builderLaunchTime,
 | 
			
		||||
        repo_name: process.env.GITHUB_REPOSITORY
 | 
			
		||||
      });
 | 
			
		||||
      return;
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      if (error.response && error.response.status < 500) {
 | 
			
		||||
        core.warning('Error completing Blacksmith build:', error);
 | 
			
		||||
        throw error;
 | 
			
		||||
      }
 | 
			
		||||
      if (retries === maxRetries - 1) {
 | 
			
		||||
        core.warning('Error completing Blacksmith build:', error);
 | 
			
		||||
        throw error;
 | 
			
		||||
      }
 | 
			
		||||
      retries++;
 | 
			
		||||
      core.warning(`Error completing Blacksmith build, retrying (${retries}/${maxRetries})...`);
 | 
			
		||||
      await new Promise(resolve => setTimeout(resolve, 200));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
    const formData = new FormData();
 | 
			
		||||
    formData.append('shouldCommit', 'true');
 | 
			
		||||
    const retryCondition = (error: AxiosError) => {
 | 
			
		||||
      return error.response?.status ? error.response.status >= 500 : false;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
async function reportBuildAbandoned(taskId: string) {
 | 
			
		||||
  let retries = 0;
 | 
			
		||||
  const maxRetries = 3;
 | 
			
		||||
  while (retries < maxRetries) {
 | 
			
		||||
    try {
 | 
			
		||||
      const client = await getBlacksmithHttpClient();
 | 
			
		||||
      const abandonURL = `/${taskId}/abandon`;
 | 
			
		||||
      await client.post(abandonURL, {
 | 
			
		||||
        repo_name: process.env.GITHUB_REPOSITORY
 | 
			
		||||
      });
 | 
			
		||||
      core.info(`Docker build abandoned, tearing down Blacksmith builder for ${stateHelper.blacksmithBuildTaskId}`);
 | 
			
		||||
    await postWithRetry(client, '/stickydisks', formData, retryCondition);
 | 
			
		||||
    return;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
      if (error.response && error.response.status < 500) {
 | 
			
		||||
        core.warning('Error abandoning Blacksmith build:', error);
 | 
			
		||||
    core.warning('Error completing Blacksmith build:', error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
      if (retries === maxRetries - 1) {
 | 
			
		||||
        core.warning('Error abandoning Blacksmith build:', error);
 | 
			
		||||
        throw error;
 | 
			
		||||
      }
 | 
			
		||||
      retries++;
 | 
			
		||||
      core.warning(`Error abandoning Blacksmith build, retrying (${retries}/${maxRetries})...`);
 | 
			
		||||
      await new Promise(resolve => setTimeout(resolve, 200));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function reportBuildFailed() {
 | 
			
		||||
  try {
 | 
			
		||||
    const client = await getBlacksmithHttpClient();
 | 
			
		||||
    await client.post(`/${stateHelper.blacksmithBuildTaskId}/fail`, {
 | 
			
		||||
      repo_name: process.env.GITHUB_REPOSITORY
 | 
			
		||||
    const formData = new FormData();
 | 
			
		||||
    formData.append('shouldCommit', 'false');
 | 
			
		||||
    const retryCondition = (error: AxiosError) => {
 | 
			
		||||
      return error.response?.status ? error.response.status >= 500 : false;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    await postWithRetry(client, '/stickydisks', formData, retryCondition);
 | 
			
		||||
    return;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    core.warning('Error completing Blacksmith build:', error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function postWithRetry(client: AxiosInstance, url: string, formData: FormData, retryCondition: (error: AxiosError) => boolean): Promise<AxiosResponse> {
 | 
			
		||||
  const maxRetries = 5;
 | 
			
		||||
  const retryDelay = 100;
 | 
			
		||||
 | 
			
		||||
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
 | 
			
		||||
    try {
 | 
			
		||||
      return await client.post(url, formData, {
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Content-Type': 'multipart/form-data'
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    core.info(`Docker build failed, tearing down Blacksmith builder for ${stateHelper.blacksmithBuildTaskId}`);
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    core.warning('Error failing Blacksmith build:', error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function postWithRetry(client: AxiosInstance, url: string, payload: unknown, retryCondition: (error: AxiosError) => boolean): Promise<AxiosResponse> {
 | 
			
		||||
  const maxRetries = 5;
 | 
			
		||||
  const retryDelay = 100;
 | 
			
		||||
 | 
			
		||||
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
 | 
			
		||||
    try {
 | 
			
		||||
      return await client.post(url, payload);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      if (attempt === maxRetries || !retryCondition(error as AxiosError)) {
 | 
			
		||||
        throw error;
 | 
			
		||||
| 
						 | 
				
			
			@ -123,13 +100,22 @@ async function postWithRetry(client: AxiosInstance, url: string, payload: unknow
 | 
			
		|||
  throw new Error('Max retries reached');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getWithRetry(client: AxiosInstance, url: string, retryCondition: (error: AxiosError) => boolean): Promise<AxiosResponse> {
 | 
			
		||||
async function getWithRetry(client: AxiosInstance, url: string, formData: FormData | null, retryCondition: (error: AxiosError) => boolean, options?: { signal?: AbortSignal }): Promise<AxiosResponse> {
 | 
			
		||||
  const maxRetries = 5;
 | 
			
		||||
  const retryDelay = 100;
 | 
			
		||||
 | 
			
		||||
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
 | 
			
		||||
    try {
 | 
			
		||||
      return await client.get(url);
 | 
			
		||||
      if (formData) {
 | 
			
		||||
        return await client.get(url, {
 | 
			
		||||
          data: formData,
 | 
			
		||||
          headers: {
 | 
			
		||||
            'Content-Type': 'multipart/form-data'
 | 
			
		||||
          },
 | 
			
		||||
          signal: options?.signal
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
      return await client.get(url, { signal: options?.signal });
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      if (attempt === maxRetries || !retryCondition(error as AxiosError)) {
 | 
			
		||||
        throw error;
 | 
			
		||||
| 
						 | 
				
			
			@ -141,69 +127,171 @@ async function getWithRetry(client: AxiosInstance, url: string, retryCondition:
 | 
			
		|||
  throw new Error('Max retries reached');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getStickyDisk(dockerfilePath: string, retryCondition: (error: AxiosError) => boolean, options?: { signal?: AbortSignal }): Promise<any> {
 | 
			
		||||
  const client = await getBlacksmithHttpClient();
 | 
			
		||||
  const formData = new FormData();
 | 
			
		||||
  formData.append('stickyDiskKey', dockerfilePath);
 | 
			
		||||
  formData.append('region', process.env.BLACKSMITH_REGION || 'eu-central');
 | 
			
		||||
  formData.append('installationModelID', process.env.BLACKSMITH_INSTALLATION_MODEL_ID || '');
 | 
			
		||||
  core.info(`Getting sticky disk for ${dockerfilePath}`);
 | 
			
		||||
  core.info(`Form data: ${JSON.stringify(formData)}`);
 | 
			
		||||
  const response = await getWithRetry(client, '/stickydisks', formData, retryCondition, options);
 | 
			
		||||
  return response.data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function getDiskSize(device: string): Promise<number> {
 | 
			
		||||
  try {
 | 
			
		||||
    const { stdout } = await execAsync(`sudo lsblk -b -n -o SIZE ${device}`);
 | 
			
		||||
    const sizeInBytes = parseInt(stdout.trim(), 10);
 | 
			
		||||
    if (isNaN(sizeInBytes)) {
 | 
			
		||||
      throw new Error('Failed to parse disk size');
 | 
			
		||||
    }
 | 
			
		||||
    return sizeInBytes;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error(`Error getting disk size: ${error.message}`);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function writeBuildkitdTomlFile(parallelism: number): Promise<void> {
 | 
			
		||||
  const diskSize = await getDiskSize(device);
 | 
			
		||||
  core.info(`disk size is ${diskSize}`);
 | 
			
		||||
  const jsonConfig: TOML.JsonMap = {
 | 
			
		||||
    "root": "/var/lib/buildkit",
 | 
			
		||||
    "grpc": {
 | 
			
		||||
      "address": ["unix:///run/buildkit/buildkitd.sock"]
 | 
			
		||||
    },
 | 
			
		||||
    "worker": {
 | 
			
		||||
      "oci": {
 | 
			
		||||
        "enabled": true,
 | 
			
		||||
        "gc": true,
 | 
			
		||||
        "gckeepstorage": diskSize.toString(),
 | 
			
		||||
        "max-parallelism": parallelism,
 | 
			
		||||
        "snapshotter": "overlayfs",
 | 
			
		||||
        "gcpolicy": [
 | 
			
		||||
          {
 | 
			
		||||
            "all": true,
 | 
			
		||||
            "keepDuration": 1209600
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            "all": true,
 | 
			
		||||
            "keepBytes": diskSize.toString()
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "containerd": {
 | 
			
		||||
        "enabled": false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const tomlString = TOML.stringify(jsonConfig);
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    await fs.promises.writeFile('buildkitd.toml', tomlString);
 | 
			
		||||
    core.debug(`TOML configuration is ${tomlString}`);
 | 
			
		||||
  } catch (err) {
 | 
			
		||||
    core.warning('error writing TOML configuration:', err);
 | 
			
		||||
    throw err;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async function startBuildkitd(parallelism: number): Promise<string> {
 | 
			
		||||
  try {
 | 
			
		||||
    await writeBuildkitdTomlFile(parallelism);
 | 
			
		||||
    await execAsync('sudo mkdir -p /run/buildkit');
 | 
			
		||||
    await execAsync('sudo chmod 755 /run/buildkit');
 | 
			
		||||
    const addr = "unix:///run/buildkit/buildkitd.sock";
 | 
			
		||||
    const { stdout: startStdout, stderr: startStderr } = await execAsync(
 | 
			
		||||
      `sudo nohup buildkitd --addr ${addr} --allow-insecure-entitlement security.insecure --config=buildkitd.toml --allow-insecure-entitlement network.host > buildkitd.log 2>&1 &`,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (startStderr) {
 | 
			
		||||
      throw new Error(`error starting buildkitd service: ${startStderr}`);
 | 
			
		||||
    }
 | 
			
		||||
    core.debug(`buildkitd daemon started successfully ${startStdout}`);
 | 
			
		||||
 | 
			
		||||
    const { stdout, stderr } = await execAsync(`pgrep -f buildkitd`);
 | 
			
		||||
    if (stderr) {
 | 
			
		||||
      throw new Error(`error finding buildkitd PID: ${stderr}`);
 | 
			
		||||
    }
 | 
			
		||||
    return addr;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    core.error('failed to start buildkitd daemon:', error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Function to gracefully shut down the buildkitd process
 | 
			
		||||
async function shutdownBuildkitd(): Promise<void> {
 | 
			
		||||
  try {
 | 
			
		||||
    await execAsync(`sudo pkill -TERM buildkitd`);
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    core.error('error shutting down buildkitd process:', error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Function to get the number of available CPUs
 | 
			
		||||
async function getNumCPUs(): Promise<number> {
 | 
			
		||||
  try {
 | 
			
		||||
    const { stdout } = await execAsync('sudo nproc');
 | 
			
		||||
    return parseInt(stdout.trim());
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    core.warning('Failed to get CPU count, defaulting to 1:', error);
 | 
			
		||||
    return 1;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// getRemoteBuilderAddr resolves the address to a remote Docker builder.
 | 
			
		||||
// If it is unable to do so because of a timeout or an error it returns null.
 | 
			
		||||
async function getRemoteBuilderAddr(inputs: context.Inputs): Promise<string | null> {
 | 
			
		||||
  const controller = new AbortController();
 | 
			
		||||
  const timeoutId = setTimeout(() => controller.abort(), 30000);
 | 
			
		||||
  try {
 | 
			
		||||
    const client = await getBlacksmithHttpClient();
 | 
			
		||||
    const dockerfilePath = context.getDockerfilePath(inputs);
 | 
			
		||||
    const payload: {dockerfile_path?: string; repo_name?: string} = {
 | 
			
		||||
      repo_name: process.env.GITHUB_REPOSITORY
 | 
			
		||||
    };
 | 
			
		||||
    if (dockerfilePath && dockerfilePath.length > 0) {
 | 
			
		||||
      payload.dockerfile_path = dockerfilePath;
 | 
			
		||||
      core.info(`Using dockerfile path: ${dockerfilePath}`);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const retryCondition = (error: AxiosError) => (error.response?.status ? error.response.status >= 500 : error.code === 'ECONNRESET');
 | 
			
		||||
    const controller = new AbortController();
 | 
			
		||||
    const timeoutId = setTimeout(() => controller.abort(), 10000);
 | 
			
		||||
 | 
			
		||||
    const response = await postWithRetry(client, '', payload, retryCondition);
 | 
			
		||||
 | 
			
		||||
    const data = response.data;
 | 
			
		||||
    const taskId = data['id'] as string;
 | 
			
		||||
    core.info(`Submitted build task: ${taskId}`);
 | 
			
		||||
    stateHelper.setBlacksmithBuildTaskId(taskId);
 | 
			
		||||
 | 
			
		||||
    const startTime = Date.now();
 | 
			
		||||
    while (Date.now() - startTime < 60000) {
 | 
			
		||||
      const response = await getWithRetry(client, `/${taskId}`, retryCondition);
 | 
			
		||||
      const data = response.data;
 | 
			
		||||
      const ec2Instance = data['ec2_instance'] ?? null;
 | 
			
		||||
      if (ec2Instance) {
 | 
			
		||||
        const elapsedTime = ((Date.now() - startTime) / 1000).toFixed(2);
 | 
			
		||||
        core.info(`Blacksmith builder agent ready after ${elapsedTime} seconds`);
 | 
			
		||||
        stateHelper.setBlacksmithBuilderLaunchTime(elapsedTime);
 | 
			
		||||
 | 
			
		||||
        const clientKey = ec2Instance['client_key'] as string;
 | 
			
		||||
        if (clientKey) {
 | 
			
		||||
          stateHelper.setBlacksmithClientKey(clientKey);
 | 
			
		||||
          await fs.promises.writeFile(context.tlsClientKeyPath, clientKey, 'utf8');
 | 
			
		||||
          core.info(`Client key written to ${context.tlsClientKeyPath}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const clientCaCertificate = ec2Instance['client_cert'] as string;
 | 
			
		||||
        if (clientCaCertificate) {
 | 
			
		||||
          stateHelper.setBlacksmithClientCaCertificate(clientCaCertificate);
 | 
			
		||||
          await fs.promises.writeFile(context.tlsClientCaCertificatePath, clientCaCertificate, 'utf8');
 | 
			
		||||
          core.info(`Client CA certificate written to ${context.tlsClientCaCertificatePath}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const rootCaCertificate = ec2Instance['root_cert'] as string;
 | 
			
		||||
        if (rootCaCertificate) {
 | 
			
		||||
          stateHelper.setBlacksmithRootCaCertificate(rootCaCertificate);
 | 
			
		||||
          await fs.promises.writeFile(context.tlsRootCaCertificatePath, rootCaCertificate, 'utf8');
 | 
			
		||||
          core.info(`Root CA certificate written to ${context.tlsRootCaCertificatePath}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return `tcp://${ec2Instance['instance_ip']}:4242` as string;
 | 
			
		||||
      }
 | 
			
		||||
      await new Promise(resolve => setTimeout(resolve, 200));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    await reportBuildAbandoned(taskId);
 | 
			
		||||
    try {
 | 
			
		||||
      await getStickyDisk(dockerfilePath, retryCondition, { signal: controller.signal });
 | 
			
		||||
      clearTimeout(timeoutId);
 | 
			
		||||
      await execAsync(`sudo mkdir -p ${mountPoint}`);
 | 
			
		||||
      await execAsync(`sudo mount ${device} ${mountPoint}`);
 | 
			
		||||
      core.debug(`${device} has been mounted to ${mountPoint}`);
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      if (error.name === 'AbortError') {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
      throw error;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Start buildkitd.
 | 
			
		||||
    const parallelism = await getNumCPUs();
 | 
			
		||||
    var buildkitdAddr = await startBuildkitd(parallelism);
 | 
			
		||||
    core.debug(`buildkitd daemon started at addr ${buildkitdAddr}`);
 | 
			
		||||
    // Change permissions on the buildkitd socket to allow non-root access
 | 
			
		||||
    const startTime = Date.now();
 | 
			
		||||
    const timeout = 3000; // 3 seconds in milliseconds
 | 
			
		||||
 | 
			
		||||
    while (Date.now() - startTime < timeout) {
 | 
			
		||||
      if (fs.existsSync('/run/buildkit/buildkitd.sock')) {
 | 
			
		||||
        // Change permissions on the buildkitd socket to allow non-root access
 | 
			
		||||
        await execAsync(`sudo chmod 666 /run/buildkit/buildkitd.sock`);
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      await new Promise(resolve => setTimeout(resolve, 100)); // Poll every 100ms
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!fs.existsSync('/run/buildkit/buildkitd.sock')) {
 | 
			
		||||
      throw new Error('buildkitd socket not found after 3s timeout');
 | 
			
		||||
    }
 | 
			
		||||
    return buildkitdAddr;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    if ((error as AxiosError).response && (error as AxiosError).response!.status === 404) {
 | 
			
		||||
      if (!inputs.nofallback) {
 | 
			
		||||
| 
						 | 
				
			
			@ -213,8 +301,6 @@ async function getRemoteBuilderAddr(inputs: context.Inputs): Promise<string | nu
 | 
			
		|||
      core.warning(`Error in getBuildkitdAddr: ${(error as Error).message}`);
 | 
			
		||||
    }
 | 
			
		||||
    return null;
 | 
			
		||||
  } finally {
 | 
			
		||||
    clearTimeout(timeoutId);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -495,6 +581,9 @@ actionsToolkit.run(
 | 
			
		|||
      });
 | 
			
		||||
    }
 | 
			
		||||
    if (stateHelper.remoteDockerBuildStatus != '') {
 | 
			
		||||
      await shutdownBuildkitd();
 | 
			
		||||
      await execAsync(`sudo umount ${mountPoint}`);
 | 
			
		||||
      core.debug(`${device} has been unmounted`);
 | 
			
		||||
      if (stateHelper.remoteDockerBuildStatus == 'success') {
 | 
			
		||||
        await reportBuildCompleted();
 | 
			
		||||
      } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -503,7 +592,7 @@ actionsToolkit.run(
 | 
			
		|||
    }
 | 
			
		||||
    if (stateHelper.tmpDir.length > 0) {
 | 
			
		||||
      await core.group(`Removing temp folder ${stateHelper.tmpDir}`, async () => {
 | 
			
		||||
        fs.rmSync(stateHelper.tmpDir, {recursive: true});
 | 
			
		||||
        fs.rmSync(stateHelper.tmpDir, { recursive: true });
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										34
									
								
								yarn.lock
									
										
									
									
									
								
							
							
						
						
									
										34
									
								
								yarn.lock
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -624,6 +624,11 @@
 | 
			
		|||
  resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz"
 | 
			
		||||
  integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
 | 
			
		||||
 | 
			
		||||
"@iarna/toml@^2.2.5":
 | 
			
		||||
  version "2.2.5"
 | 
			
		||||
  resolved "https://registry.npmjs.org/@iarna/toml/-/toml-2.2.5.tgz"
 | 
			
		||||
  integrity sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==
 | 
			
		||||
 | 
			
		||||
"@isaacs/cliui@^8.0.2":
 | 
			
		||||
  version "8.0.2"
 | 
			
		||||
  resolved "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz"
 | 
			
		||||
| 
						 | 
				
			
			@ -1509,6 +1514,13 @@ async-retry@^1.3.3:
 | 
			
		|||
  dependencies:
 | 
			
		||||
    retry "0.13.1"
 | 
			
		||||
 | 
			
		||||
async@^2.6.4:
 | 
			
		||||
  version "2.6.4"
 | 
			
		||||
  resolved "https://registry.npmjs.org/async/-/async-2.6.4.tgz"
 | 
			
		||||
  integrity sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    lodash "^4.17.14"
 | 
			
		||||
 | 
			
		||||
async@^3.2.3, async@^3.2.4:
 | 
			
		||||
  version "3.2.6"
 | 
			
		||||
  resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz"
 | 
			
		||||
| 
						 | 
				
			
			@ -1903,6 +1915,13 @@ csv-parse@^5.5.6:
 | 
			
		|||
  resolved "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.6.tgz"
 | 
			
		||||
  integrity sha512-uNpm30m/AGSkLxxy7d9yRXpJQFrZzVWLFBkS+6ngPcZkw/5k3L/jjFuj7tVnEpRn+QgmiXr21nDlhCiUK4ij2A==
 | 
			
		||||
 | 
			
		||||
debug@^3.2.7:
 | 
			
		||||
  version "3.2.7"
 | 
			
		||||
  resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
 | 
			
		||||
  integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    ms "^2.1.1"
 | 
			
		||||
 | 
			
		||||
debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4:
 | 
			
		||||
  version "4.3.7"
 | 
			
		||||
  resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
 | 
			
		||||
| 
						 | 
				
			
			@ -3223,7 +3242,7 @@ lodash.merge@^4.6.2:
 | 
			
		|||
  resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
 | 
			
		||||
  integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
 | 
			
		||||
 | 
			
		||||
lodash@^4.17.15:
 | 
			
		||||
lodash@^4.17.14, lodash@^4.17.15:
 | 
			
		||||
  version "4.17.21"
 | 
			
		||||
  resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
 | 
			
		||||
  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 | 
			
		||||
| 
						 | 
				
			
			@ -3339,14 +3358,14 @@ minimist@^1.2.5, minimist@^1.2.6:
 | 
			
		|||
  resolved "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz"
 | 
			
		||||
  integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
 | 
			
		||||
 | 
			
		||||
mkdirp@^0.5.1:
 | 
			
		||||
mkdirp@^0.5.1, mkdirp@^0.5.6:
 | 
			
		||||
  version "0.5.6"
 | 
			
		||||
  resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz"
 | 
			
		||||
  integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    minimist "^1.2.6"
 | 
			
		||||
 | 
			
		||||
ms@^2.1.3:
 | 
			
		||||
ms@^2.1.1, ms@^2.1.3:
 | 
			
		||||
  version "2.1.3"
 | 
			
		||||
  resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
 | 
			
		||||
  integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
 | 
			
		||||
| 
						 | 
				
			
			@ -3561,6 +3580,15 @@ pkg-dir@^4.2.0:
 | 
			
		|||
  dependencies:
 | 
			
		||||
    find-up "^4.0.0"
 | 
			
		||||
 | 
			
		||||
portfinder@^1.0.32:
 | 
			
		||||
  version "1.0.32"
 | 
			
		||||
  resolved "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz"
 | 
			
		||||
  integrity sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    async "^2.6.4"
 | 
			
		||||
    debug "^3.2.7"
 | 
			
		||||
    mkdirp "^0.5.6"
 | 
			
		||||
 | 
			
		||||
prelude-ls@^1.2.1:
 | 
			
		||||
  version "1.2.1"
 | 
			
		||||
  resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue