mirror of
				https://github.com/docker/build-push-action.git
				synced 2025-11-04 08:20:55 +00:00 
			
		
		
		
	Merge pull request #99 from useblacksmith/scaffold-multi-platform
src: add scaffolding for support multi-platform builds
This commit is contained in:
		
				commit
				
					
						1def72df18
					
				
			
		
					 9 changed files with 116 additions and 103 deletions
				
			
		
							
								
								
									
										2
									
								
								dist/index.js
									
										
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								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
											
										
									
								
							| 
						 | 
					@ -9,16 +9,10 @@ const config: Config.InitialOptions = {
 | 
				
			||||||
    '^.+\\.ts$': 'ts-jest',
 | 
					    '^.+\\.ts$': 'ts-jest',
 | 
				
			||||||
    '^.+\\.js$': 'babel-jest'
 | 
					    '^.+\\.js$': 'babel-jest'
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  transformIgnorePatterns: [
 | 
					  transformIgnorePatterns: ['/node_modules/(?!(@buf|@connectrpc)/)'],
 | 
				
			||||||
    '/node_modules/(?!(@buf|@connectrpc)/)'
 | 
					 | 
				
			||||||
  ],
 | 
					 | 
				
			||||||
  verbose: true,
 | 
					  verbose: true,
 | 
				
			||||||
  collectCoverage: true,
 | 
					  collectCoverage: true,
 | 
				
			||||||
  collectCoverageFrom: [
 | 
					  collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/**/__tests__/**']
 | 
				
			||||||
    'src/**/*.ts',
 | 
					 | 
				
			||||||
    '!src/**/*.d.ts',
 | 
					 | 
				
			||||||
    '!src/**/__tests__/**'
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default config;
 | 
					export default config;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										12
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
					@ -8,7 +8,7 @@
 | 
				
			||||||
      "license": "Apache-2.0",
 | 
					      "license": "Apache-2.0",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@actions/core": "^1.10.1",
 | 
					        "@actions/core": "^1.10.1",
 | 
				
			||||||
        "@buf/blacksmith_vm-agent.connectrpc_es": "^1.6.1-20250209182455-7d83cfb8ddb1.2",
 | 
					        "@buf/blacksmith_vm-agent.connectrpc_es": "^1.6.1-20250211212423-70f4f1344c53.2",
 | 
				
			||||||
        "@connectrpc/connect": "^1.6.1",
 | 
					        "@connectrpc/connect": "^1.6.1",
 | 
				
			||||||
        "@connectrpc/connect-node": "^1.6.1",
 | 
					        "@connectrpc/connect-node": "^1.6.1",
 | 
				
			||||||
        "@docker/actions-toolkit": "0.37.1",
 | 
					        "@docker/actions-toolkit": "0.37.1",
 | 
				
			||||||
| 
						 | 
					@ -2304,8 +2304,8 @@
 | 
				
			||||||
      "dev": true
 | 
					      "dev": true
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@buf/blacksmith_vm-agent.bufbuild_es": {
 | 
					    "node_modules/@buf/blacksmith_vm-agent.bufbuild_es": {
 | 
				
			||||||
      "version": "1.10.0-20250209182455-7d83cfb8ddb1.1",
 | 
					      "version": "1.10.0-20250211212423-70f4f1344c53.1",
 | 
				
			||||||
      "resolved": "https://buf.build/gen/npm/v1/@buf/blacksmith_vm-agent.bufbuild_es/-/blacksmith_vm-agent.bufbuild_es-1.10.0-20250209182455-7d83cfb8ddb1.1.tgz",
 | 
					      "resolved": "https://buf.build/gen/npm/v1/@buf/blacksmith_vm-agent.bufbuild_es/-/blacksmith_vm-agent.bufbuild_es-1.10.0-20250211212423-70f4f1344c53.1.tgz",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@buf/googleapis_googleapis.bufbuild_es": "1.10.0-20250203201857-83c0f6c19b2f.1"
 | 
					        "@buf/googleapis_googleapis.bufbuild_es": "1.10.0-20250203201857-83c0f6c19b2f.1"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
| 
						 | 
					@ -2314,10 +2314,10 @@
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "node_modules/@buf/blacksmith_vm-agent.connectrpc_es": {
 | 
					    "node_modules/@buf/blacksmith_vm-agent.connectrpc_es": {
 | 
				
			||||||
      "version": "1.6.1-20250209182455-7d83cfb8ddb1.2",
 | 
					      "version": "1.6.1-20250211212423-70f4f1344c53.2",
 | 
				
			||||||
      "resolved": "https://buf.build/gen/npm/v1/@buf/blacksmith_vm-agent.connectrpc_es/-/blacksmith_vm-agent.connectrpc_es-1.6.1-20250209182455-7d83cfb8ddb1.2.tgz",
 | 
					      "resolved": "https://buf.build/gen/npm/v1/@buf/blacksmith_vm-agent.connectrpc_es/-/blacksmith_vm-agent.connectrpc_es-1.6.1-20250211212423-70f4f1344c53.2.tgz",
 | 
				
			||||||
      "dependencies": {
 | 
					      "dependencies": {
 | 
				
			||||||
        "@buf/blacksmith_vm-agent.bufbuild_es": "1.10.0-20250209182455-7d83cfb8ddb1.1",
 | 
					        "@buf/blacksmith_vm-agent.bufbuild_es": "1.10.0-20250211212423-70f4f1344c53.1",
 | 
				
			||||||
        "@buf/googleapis_googleapis.connectrpc_es": "1.6.1-20250203201857-83c0f6c19b2f.2"
 | 
					        "@buf/googleapis_googleapis.connectrpc_es": "1.6.1-20250203201857-83c0f6c19b2f.2"
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "peerDependencies": {
 | 
					      "peerDependencies": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,7 @@
 | 
				
			||||||
  "packageManager": "yarn@3.6.3",
 | 
					  "packageManager": "yarn@3.6.3",
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@actions/core": "^1.10.1",
 | 
					    "@actions/core": "^1.10.1",
 | 
				
			||||||
    "@buf/blacksmith_vm-agent.connectrpc_es": "^1.6.1-20250209182455-7d83cfb8ddb1.2",
 | 
					    "@buf/blacksmith_vm-agent.connectrpc_es": "^1.6.1-20250211212423-70f4f1344c53.2",
 | 
				
			||||||
    "@connectrpc/connect": "^1.6.1",
 | 
					    "@connectrpc/connect": "^1.6.1",
 | 
				
			||||||
    "@connectrpc/connect-node": "^1.6.1",
 | 
					    "@connectrpc/connect-node": "^1.6.1",
 | 
				
			||||||
    "@docker/actions-toolkit": "0.37.1",
 | 
					    "@docker/actions-toolkit": "0.37.1",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ import * as main from '../main';
 | 
				
			||||||
import * as reporter from '../reporter';
 | 
					import * as reporter from '../reporter';
 | 
				
			||||||
import {getDockerfilePath} from '../context';
 | 
					import {getDockerfilePath} from '../context';
 | 
				
			||||||
import * as setupBuilder from '../setup_builder';
 | 
					import * as setupBuilder from '../setup_builder';
 | 
				
			||||||
import { Metric_MetricType } from "@buf/blacksmith_vm-agent.bufbuild_es/stickydisk/v1/stickydisk_pb";
 | 
					import {Metric_MetricType} from '@buf/blacksmith_vm-agent.bufbuild_es/stickydisk/v1/stickydisk_pb';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jest.mock('@actions/core', () => ({
 | 
					jest.mock('@actions/core', () => ({
 | 
				
			||||||
  debug: jest.fn(),
 | 
					  debug: jest.fn(),
 | 
				
			||||||
| 
						 | 
					@ -34,7 +34,9 @@ jest.mock('../setup_builder', () => ({
 | 
				
			||||||
  ...jest.requireActual('../setup_builder'),
 | 
					  ...jest.requireActual('../setup_builder'),
 | 
				
			||||||
  startAndConfigureBuildkitd: jest.fn(),
 | 
					  startAndConfigureBuildkitd: jest.fn(),
 | 
				
			||||||
  setupStickyDisk: jest.fn(),
 | 
					  setupStickyDisk: jest.fn(),
 | 
				
			||||||
  getNumCPUs: jest.fn().mockResolvedValue(4)
 | 
					  getNumCPUs: jest.fn().mockResolvedValue(4),
 | 
				
			||||||
 | 
					  leaveTailnet: jest.fn().mockResolvedValue(undefined),
 | 
				
			||||||
 | 
					  getTailscaleIP: jest.fn()
 | 
				
			||||||
}));
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('startBlacksmithBuilder', () => {
 | 
					describe('startBlacksmithBuilder', () => {
 | 
				
			||||||
| 
						 | 
					@ -42,7 +44,7 @@ describe('startBlacksmithBuilder', () => {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  beforeEach(() => {
 | 
					  beforeEach(() => {
 | 
				
			||||||
    jest.clearAllMocks();
 | 
					    jest.clearAllMocks();
 | 
				
			||||||
    mockInputs = {nofallback: false};
 | 
					    mockInputs = {nofallback: false, platforms: []};
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  test('should handle missing dockerfile path with nofallback=false', async () => {
 | 
					  test('should handle missing dockerfile path with nofallback=false', async () => {
 | 
				
			||||||
| 
						 | 
					@ -110,7 +112,7 @@ describe('startBlacksmithBuilder', () => {
 | 
				
			||||||
      buildId: mockBuildId,
 | 
					      buildId: mockBuildId,
 | 
				
			||||||
      exposeId: mockExposeId
 | 
					      exposeId: mockExposeId
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    expect(setupBuilder.startAndConfigureBuildkitd).toHaveBeenCalledWith(mockParallelism);
 | 
					    expect(setupBuilder.startAndConfigureBuildkitd).toHaveBeenCalledWith(mockParallelism, []);
 | 
				
			||||||
    expect(reporter.reportBuildPushActionFailure).not.toHaveBeenCalled();
 | 
					    expect(reporter.reportBuildPushActionFailure).not.toHaveBeenCalled();
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										32
									
								
								src/main.ts
									
										
									
									
									
								
							
							
						
						
									
										32
									
								
								src/main.ts
									
										
									
									
									
								
							| 
						 | 
					@ -20,37 +20,13 @@ import * as context from './context';
 | 
				
			||||||
import {promisify} from 'util';
 | 
					import {promisify} from 'util';
 | 
				
			||||||
import {exec} from 'child_process';
 | 
					import {exec} from 'child_process';
 | 
				
			||||||
import * as reporter from './reporter';
 | 
					import * as reporter from './reporter';
 | 
				
			||||||
import {setupStickyDisk, startAndConfigureBuildkitd, getNumCPUs} from './setup_builder';
 | 
					import {setupStickyDisk, startAndConfigureBuildkitd, getNumCPUs, leaveTailnet} from './setup_builder';
 | 
				
			||||||
import {Metric_MetricType} from '@buf/blacksmith_vm-agent.bufbuild_es/stickydisk/v1/stickydisk_pb';
 | 
					import {Metric_MetricType} from '@buf/blacksmith_vm-agent.bufbuild_es/stickydisk/v1/stickydisk_pb';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const buildxVersion = 'v0.17.0';
 | 
					const buildxVersion = 'v0.17.0';
 | 
				
			||||||
const mountPoint = '/var/lib/buildkit';
 | 
					const mountPoint = '/var/lib/buildkit';
 | 
				
			||||||
const execAsync = promisify(exec);
 | 
					const execAsync = promisify(exec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function joinTailnet(): Promise<void> {
 | 
					 | 
				
			||||||
  const token = process.env.BLACKSMITH_TAILSCALE_TOKEN;
 | 
					 | 
				
			||||||
  if (!token || token === 'unset') {
 | 
					 | 
				
			||||||
    core.debug('BLACKSMITH_TAILSCALE_TOKEN environment variable not set, skipping tailnet join');
 | 
					 | 
				
			||||||
    return;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    await execAsync(`sudo tailscale up --authkey=${token} --hostname=${process.env.VM_ID}`);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    core.info('Successfully joined tailnet');
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					 | 
				
			||||||
    throw new Error(`Failed to join tailnet: ${error.message}`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function leaveTailnet(): Promise<void> {
 | 
					 | 
				
			||||||
  try {
 | 
					 | 
				
			||||||
    await execAsync('sudo tailscale down');
 | 
					 | 
				
			||||||
  } catch (error) {
 | 
					 | 
				
			||||||
    core.warning(`Error leaving tailnet: ${error.message}`);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
async function setupBuildx(version: string, toolkit: Toolkit): Promise<void> {
 | 
					async function setupBuildx(version: string, toolkit: Toolkit): Promise<void> {
 | 
				
			||||||
  let toolPath;
 | 
					  let toolPath;
 | 
				
			||||||
  const standalone = await toolkit.buildx.isStandalone();
 | 
					  const standalone = await toolkit.buildx.isStandalone();
 | 
				
			||||||
| 
						 | 
					@ -94,8 +70,6 @@ async function setupBuildx(version: string, toolkit: Toolkit): Promise<void> {
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export async function startBlacksmithBuilder(inputs: context.Inputs): Promise<{addr: string | null; buildId: string | null; exposeId: string}> {
 | 
					export async function startBlacksmithBuilder(inputs: context.Inputs): Promise<{addr: string | null; buildId: string | null; exposeId: string}> {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    await joinTailnet();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const dockerfilePath = context.getDockerfilePath(inputs);
 | 
					    const dockerfilePath = context.getDockerfilePath(inputs);
 | 
				
			||||||
    if (!dockerfilePath) {
 | 
					    if (!dockerfilePath) {
 | 
				
			||||||
      throw new Error('Failed to resolve dockerfile path');
 | 
					      throw new Error('Failed to resolve dockerfile path');
 | 
				
			||||||
| 
						 | 
					@ -107,7 +81,7 @@ export async function startBlacksmithBuilder(inputs: context.Inputs): Promise<{a
 | 
				
			||||||
    const parallelism = await getNumCPUs();
 | 
					    const parallelism = await getNumCPUs();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const buildkitdStartTime = Date.now();
 | 
					    const buildkitdStartTime = Date.now();
 | 
				
			||||||
    const buildkitdAddr = await startAndConfigureBuildkitd(parallelism);
 | 
					    const buildkitdAddr = await startAndConfigureBuildkitd(parallelism, inputs.platforms);
 | 
				
			||||||
    const buildkitdDurationMs = Date.now() - buildkitdStartTime;
 | 
					    const buildkitdDurationMs = Date.now() - buildkitdStartTime;
 | 
				
			||||||
    await reporter.reportMetric(Metric_MetricType.BPA_BUILDKITD_READY_DURATION_MS, buildkitdDurationMs);
 | 
					    await reporter.reportMetric(Metric_MetricType.BPA_BUILDKITD_READY_DURATION_MS, buildkitdDurationMs);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -128,6 +102,8 @@ export async function startBlacksmithBuilder(inputs: context.Inputs): Promise<{a
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    core.warning(`${errorMessage}. Falling back to a local build.`);
 | 
					    core.warning(`${errorMessage}. Falling back to a local build.`);
 | 
				
			||||||
    return {addr: null, buildId: null, exposeId: ''};
 | 
					    return {addr: null, buildId: null, exposeId: ''};
 | 
				
			||||||
 | 
					  } finally {
 | 
				
			||||||
 | 
					    await leaveTailnet();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,20 +1,16 @@
 | 
				
			||||||
import * as core from '@actions/core';
 | 
					import * as core from '@actions/core';
 | 
				
			||||||
import axios, {AxiosError, AxiosInstance, AxiosResponse } from 'axios';
 | 
					import axios, {AxiosError, AxiosInstance, AxiosResponse} from 'axios';
 | 
				
			||||||
import axiosRetry from 'axios-retry';
 | 
					import axiosRetry from 'axios-retry';
 | 
				
			||||||
import {ExportRecordResponse} from '@docker/actions-toolkit/lib/types/buildx/history';
 | 
					import {ExportRecordResponse} from '@docker/actions-toolkit/lib/types/buildx/history';
 | 
				
			||||||
import FormData from 'form-data';
 | 
					import FormData from 'form-data';
 | 
				
			||||||
import { createClient } from "@connectrpc/connect";
 | 
					import {createClient} from '@connectrpc/connect';
 | 
				
			||||||
import { createGrpcTransport } from "@connectrpc/connect-node";
 | 
					import {createGrpcTransport} from '@connectrpc/connect-node';
 | 
				
			||||||
import { StickyDiskService } from "@buf/blacksmith_vm-agent.connectrpc_es/stickydisk/v1/stickydisk_connect";
 | 
					import {StickyDiskService} from '@buf/blacksmith_vm-agent.connectrpc_es/stickydisk/v1/stickydisk_connect';
 | 
				
			||||||
import { Metric, Metric_MetricType } from "@buf/blacksmith_vm-agent.bufbuild_es/stickydisk/v1/stickydisk_pb";
 | 
					import {Metric, Metric_MetricType} from '@buf/blacksmith_vm-agent.bufbuild_es/stickydisk/v1/stickydisk_pb';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Configure base axios instance for Blacksmith API.
 | 
					// Configure base axios instance for Blacksmith API.
 | 
				
			||||||
const createBlacksmithAPIClient = () => {
 | 
					const createBlacksmithAPIClient = () => {
 | 
				
			||||||
  const apiUrl = process.env.BLACKSMITH_BACKEND_URL || (
 | 
					  const apiUrl = process.env.BLACKSMITH_BACKEND_URL || (process.env.BLACKSMITH_ENV?.includes('staging') ? 'https://stagingapi.blacksmith.sh' : 'https://api.blacksmith.sh');
 | 
				
			||||||
    process.env.BLACKSMITH_ENV?.includes('staging') 
 | 
					 | 
				
			||||||
      ? 'https://stagingapi.blacksmith.sh' 
 | 
					 | 
				
			||||||
      : 'https://api.blacksmith.sh'
 | 
					 | 
				
			||||||
  );
 | 
					 | 
				
			||||||
  core.debug(`Using Blacksmith API URL: ${apiUrl}`);
 | 
					  core.debug(`Using Blacksmith API URL: ${apiUrl}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const client = axios.create({
 | 
					  const client = axios.create({
 | 
				
			||||||
| 
						 | 
					@ -30,8 +26,7 @@ const createBlacksmithAPIClient = () => {
 | 
				
			||||||
    retries: 5,
 | 
					    retries: 5,
 | 
				
			||||||
    retryDelay: axiosRetry.exponentialDelay,
 | 
					    retryDelay: axiosRetry.exponentialDelay,
 | 
				
			||||||
    retryCondition: (error: AxiosError) => {
 | 
					    retryCondition: (error: AxiosError) => {
 | 
				
			||||||
      return axiosRetry.isNetworkOrIdempotentRequestError(error) || 
 | 
					      return axiosRetry.isNetworkOrIdempotentRequestError(error) || (error.response?.status ? error.response.status >= 500 : false);
 | 
				
			||||||
             (error.response?.status ? error.response.status >= 500 : false);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,7 +36,7 @@ const createBlacksmithAPIClient = () => {
 | 
				
			||||||
export function createBlacksmithAgentClient() {
 | 
					export function createBlacksmithAgentClient() {
 | 
				
			||||||
  const transport = createGrpcTransport({
 | 
					  const transport = createGrpcTransport({
 | 
				
			||||||
    baseUrl: 'http://192.168.127.1:5557',
 | 
					    baseUrl: 'http://192.168.127.1:5557',
 | 
				
			||||||
    httpVersion: '2',
 | 
					    httpVersion: '2'
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return createClient(StickyDiskService, transport);
 | 
					  return createClient(StickyDiskService, transport);
 | 
				
			||||||
| 
						 | 
					@ -175,22 +170,19 @@ export async function post(client: AxiosInstance, url: string, formData: FormDat
 | 
				
			||||||
  return await client.post(url, formData, {
 | 
					  return await client.post(url, formData, {
 | 
				
			||||||
    headers: {
 | 
					    headers: {
 | 
				
			||||||
      ...client.defaults.headers.common,
 | 
					      ...client.defaults.headers.common,
 | 
				
			||||||
      ...(formData && { 'Content-Type': 'multipart/form-data' }),
 | 
					      ...(formData && {'Content-Type': 'multipart/form-data'})
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    signal: options?.signal
 | 
					    signal: options?.signal
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function reportMetric(
 | 
					export async function reportMetric(metricType: Metric_MetricType, value: number): Promise<void> {
 | 
				
			||||||
  metricType: Metric_MetricType,
 | 
					 | 
				
			||||||
  value: number
 | 
					 | 
				
			||||||
): Promise<void> {
 | 
					 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const agentClient = createBlacksmithAgentClient();
 | 
					    const agentClient = createBlacksmithAgentClient();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const metric = new Metric({
 | 
					    const metric = new Metric({
 | 
				
			||||||
      type: metricType,
 | 
					      type: metricType,
 | 
				
			||||||
      value: { case: "intValue", value: BigInt(value) }
 | 
					      value: {case: 'intValue', value: BigInt(value)}
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await agentClient.reportMetric({
 | 
					    await agentClient.reportMetric({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,6 +10,16 @@ const BUILDKIT_DAEMON_ADDR = 'tcp://127.0.0.1:1234';
 | 
				
			||||||
const mountPoint = '/var/lib/buildkit';
 | 
					const mountPoint = '/var/lib/buildkit';
 | 
				
			||||||
const execAsync = promisify(exec);
 | 
					const execAsync = promisify(exec);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getTailscaleIP(): Promise<string | null> {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const {stdout} = await execAsync('tailscale ip -4');
 | 
				
			||||||
 | 
					    return stdout.trim();
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    core.debug(`Error getting tailscale IP: ${error.message}`);
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function maybeFormatBlockDevice(device: string): Promise<string> {
 | 
					async function maybeFormatBlockDevice(device: string): Promise<string> {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    // Check if device is formatted with ext4
 | 
					    // Check if device is formatted with ext4
 | 
				
			||||||
| 
						 | 
					@ -52,11 +62,11 @@ export async function getNumCPUs(): Promise<number> {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function writeBuildkitdTomlFile(parallelism: number): Promise<void> {
 | 
					async function writeBuildkitdTomlFile(parallelism: number, addr: string): Promise<void> {
 | 
				
			||||||
  const jsonConfig: TOML.JsonMap = {
 | 
					  const jsonConfig: TOML.JsonMap = {
 | 
				
			||||||
    root: '/var/lib/buildkit',
 | 
					    root: '/var/lib/buildkit',
 | 
				
			||||||
    grpc: {
 | 
					    grpc: {
 | 
				
			||||||
      address: [BUILDKIT_DAEMON_ADDR]
 | 
					      address: [addr]
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    registry: {
 | 
					    registry: {
 | 
				
			||||||
      'docker.io': {
 | 
					      'docker.io': {
 | 
				
			||||||
| 
						 | 
					@ -95,13 +105,12 @@ async function writeBuildkitdTomlFile(parallelism: number): Promise<void> {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function startBuildkitd(parallelism: number): Promise<string> {
 | 
					export async function startBuildkitd(parallelism: number, addr: string): Promise<string> {
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    await writeBuildkitdTomlFile(parallelism);
 | 
					    await writeBuildkitdTomlFile(parallelism, addr);
 | 
				
			||||||
    const addr = BUILDKIT_DAEMON_ADDR;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const logStream = fs.createWriteStream('buildkitd.log');
 | 
					    const logStream = fs.createWriteStream('buildkitd.log');
 | 
				
			||||||
    const buildkitd = spawn('sudo', ['buildkitd', '--debug', '--addr', addr, '--allow-insecure-entitlement', 'security.insecure', '--config=buildkitd.toml', '--allow-insecure-entitlement', 'network.host'], {
 | 
					    const buildkitd = spawn('sudo', ['buildkitd', '--debug', '--config=buildkitd.toml', '--allow-insecure-entitlement', 'security.insecure', '--allow-insecure-entitlement', 'network.host'], {
 | 
				
			||||||
      stdio: ['ignore', 'pipe', 'pipe']
 | 
					      stdio: ['ignore', 'pipe', 'pipe']
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -138,20 +147,6 @@ async function startBuildkitd(parallelism: number): Promise<string> {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export async function getStickyDisk(options?: {signal?: AbortSignal}): Promise<{expose_id: string; device: string}> {
 | 
					export async function getStickyDisk(options?: {signal?: AbortSignal}): Promise<{expose_id: string; device: string}> {
 | 
				
			||||||
  const client = await reporter.createBlacksmithAgentClient();
 | 
					  const client = await reporter.createBlacksmithAgentClient();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -181,14 +176,65 @@ export async function getStickyDisk(options?: {signal?: AbortSignal}): Promise<{
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function joinTailnet(): Promise<void> {
 | 
				
			||||||
 | 
					  const token = process.env.BLACKSMITH_TAILSCALE_TOKEN;
 | 
				
			||||||
 | 
					  if (!token || token === 'unset') {
 | 
				
			||||||
 | 
					    core.debug('BLACKSMITH_TAILSCALE_TOKEN environment variable not set, skipping tailnet join');
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    await execAsync(`sudo tailscale up --authkey=${token} --hostname=${process.env.VM_ID}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    core.info('Successfully joined tailnet');
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    throw new Error(`Failed to join tailnet: ${error.message}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function leaveTailnet(): Promise<void> {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    // Check if we're part of a tailnet before trying to leave
 | 
				
			||||||
 | 
					    const {stdout} = await execAsync('sudo tailscale status');
 | 
				
			||||||
 | 
					    if (stdout.trim() !== '') {
 | 
				
			||||||
 | 
					      await execAsync('sudo tailscale down');
 | 
				
			||||||
 | 
					      core.debug('Successfully left tailnet');
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      core.debug('Not part of a tailnet, skipping leave');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    core.warning(`Error leaving tailnet: ${error.message}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// buildkitdTimeoutMs states the max amount of time this action will wait for the buildkitd
 | 
					// buildkitdTimeoutMs states the max amount of time this action will wait for the buildkitd
 | 
				
			||||||
// daemon to start have its socket ready. It also additionally governs how long we will wait for
 | 
					// daemon to start have its socket ready. It also additionally governs how long we will wait for
 | 
				
			||||||
// the buildkitd workers to be ready.
 | 
					// the buildkitd workers to be ready.
 | 
				
			||||||
const buildkitdTimeoutMs = 30000;
 | 
					const buildkitdTimeoutMs = 30000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function startAndConfigureBuildkitd(parallelism: number): Promise<string> {
 | 
					export async function startAndConfigureBuildkitd(parallelism: number, platforms?: string[]): Promise<string> {
 | 
				
			||||||
  const buildkitdAddr = await startBuildkitd(parallelism);
 | 
					  // For multi-platform builds, we need to use the tailscale IP
 | 
				
			||||||
  core.debug(`buildkitd daemon started at addr ${buildkitdAddr}`);
 | 
					  let buildkitdAddr = BUILDKIT_DAEMON_ADDR;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // If we are doing a multi-platform build, we need to join the tailnet and bind buildkitd to the tailscale IP.
 | 
				
			||||||
 | 
					  // We do this so that the remote VM can join the same buildkitd cluster as a worker.
 | 
				
			||||||
 | 
					  if (platforms && platforms.length > 1) {
 | 
				
			||||||
 | 
					    await joinTailnet();
 | 
				
			||||||
 | 
					    const tailscaleIP = await getTailscaleIP();
 | 
				
			||||||
 | 
					    if (!tailscaleIP) {
 | 
				
			||||||
 | 
					      throw new Error('Failed to get tailscale IP for multi-platform build');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    buildkitdAddr = `tcp://${tailscaleIP}:1234`;
 | 
				
			||||||
 | 
					    core.info(`Using tailscale IP for multi-platform build: ${buildkitdAddr}`);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const addr = await startBuildkitd(parallelism, buildkitdAddr);
 | 
				
			||||||
 | 
					  core.debug(`buildkitd daemon started at addr ${addr}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (platforms && platforms.length > 1) {
 | 
				
			||||||
 | 
					    // TODO(adityamaru): Queue docker job for multi-platform build with a well known tailscale hostname.
 | 
				
			||||||
 | 
					    // TODO(adityamaru): Wait until the VM joins the tailnet.
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Check that buildkit instance is ready by querying workers for up to 30s
 | 
					  // Check that buildkit instance is ready by querying workers for up to 30s
 | 
				
			||||||
  const startTimeBuildkitReady = Date.now();
 | 
					  const startTimeBuildkitReady = Date.now();
 | 
				
			||||||
| 
						 | 
					@ -196,10 +242,12 @@ export async function startAndConfigureBuildkitd(parallelism: number): Promise<s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  while (Date.now() - startTimeBuildkitReady < timeoutBuildkitReady) {
 | 
					  while (Date.now() - startTimeBuildkitReady < timeoutBuildkitReady) {
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const {stdout} = await execAsync(`sudo buildctl --addr ${BUILDKIT_DAEMON_ADDR} debug workers`);
 | 
					      const {stdout} = await execAsync(`sudo buildctl --addr ${addr} debug workers`);
 | 
				
			||||||
      const lines = stdout.trim().split('\n');
 | 
					      const lines = stdout.trim().split('\n');
 | 
				
			||||||
      if (lines.length > 1) {
 | 
					      // For multi-platform builds, we need at least 2 workers
 | 
				
			||||||
        // Check if we have output lines beyond the header
 | 
					      const requiredWorkers = platforms && platforms.length > 1 ? 2 : 1;
 | 
				
			||||||
 | 
					      if (lines.length > requiredWorkers) {
 | 
				
			||||||
 | 
					        core.info(`Found ${lines.length - 1} workers, required ${requiredWorkers}`);
 | 
				
			||||||
        break;
 | 
					        break;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } catch (error) {
 | 
					    } catch (error) {
 | 
				
			||||||
| 
						 | 
					@ -210,10 +258,11 @@ export async function startAndConfigureBuildkitd(parallelism: number): Promise<s
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Final check after timeout.
 | 
					  // Final check after timeout.
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const {stdout} = await execAsync(`sudo buildctl --addr ${BUILDKIT_DAEMON_ADDR} debug workers`);
 | 
					    const {stdout} = await execAsync(`sudo buildctl --addr ${addr} debug workers`);
 | 
				
			||||||
    const lines = stdout.trim().split('\n');
 | 
					    const lines = stdout.trim().split('\n');
 | 
				
			||||||
    if (lines.length <= 1) {
 | 
					    const requiredWorkers = platforms && platforms.length > 1 ? 2 : 1;
 | 
				
			||||||
      throw new Error('buildkit workers not ready after 15s timeout');
 | 
					    if (lines.length <= requiredWorkers) {
 | 
				
			||||||
 | 
					      throw new Error(`buildkit workers not ready after ${buildkitdTimeoutMs}ms timeout. Found ${lines.length - 1} workers, required ${requiredWorkers}`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  } catch (error) {
 | 
					  } catch (error) {
 | 
				
			||||||
    core.warning(`Error checking buildkit workers: ${error.message}`);
 | 
					    core.warning(`Error checking buildkit workers: ${error.message}`);
 | 
				
			||||||
| 
						 | 
					@ -225,7 +274,7 @@ export async function startAndConfigureBuildkitd(parallelism: number): Promise<s
 | 
				
			||||||
    core.warning(`Background cache pruning failed: ${error.message}`);
 | 
					    core.warning(`Background cache pruning failed: ${error.message}`);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return buildkitdAddr;
 | 
					  return addr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue