mirror of
https://github.com/docker/build-push-action.git
synced 2025-05-07 14:09:30 +02:00
*: use axios-retry instead of handrolled retry methods
This commit is contained in:
parent
7b8642822f
commit
0186286e06
10 changed files with 475 additions and 5071 deletions
210
src/reporter.ts
210
src/reporter.ts
|
@ -1,24 +1,68 @@
|
|||
import * as core from '@actions/core';
|
||||
import axios, {AxiosError, AxiosInstance, AxiosResponse} from 'axios';
|
||||
import axios, {AxiosError, AxiosInstance, AxiosResponse, AxiosStatic } from 'axios';
|
||||
import axiosRetry from 'axios-retry';
|
||||
import {ExportRecordResponse} from '@docker/actions-toolkit/lib/types/buildx/history';
|
||||
import * as utils from './utils';
|
||||
|
||||
// Configure base axios instance for Blacksmith API.
|
||||
const createBlacksmithAPIClient = () => {
|
||||
const apiUrl = process.env.BLACKSMITH_ENV?.includes('staging')
|
||||
? 'https://stagingapi.blacksmith.sh'
|
||||
: 'https://api.blacksmith.sh';
|
||||
|
||||
const client = axios.create({
|
||||
baseURL: apiUrl,
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.BLACKSMITH_STICKYDISK_TOKEN}`,
|
||||
'X-Github-Repo-Name': process.env.GITHUB_REPO_NAME || '',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
|
||||
axiosRetry(client, {
|
||||
retries: 5,
|
||||
retryDelay: axiosRetry.exponentialDelay,
|
||||
retryCondition: (error: AxiosError) => {
|
||||
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
|
||||
(error.response?.status ? error.response.status >= 500 : false);
|
||||
}
|
||||
});
|
||||
|
||||
return client;
|
||||
};
|
||||
|
||||
export async function createBlacksmithAgentClient(): Promise<AxiosInstance> {
|
||||
const stickyDiskMgrUrl = 'http://192.168.127.1:5556';
|
||||
const client = axios.create({
|
||||
baseURL: stickyDiskMgrUrl
|
||||
});
|
||||
|
||||
axiosRetry(client, {
|
||||
retries: 5,
|
||||
retryDelay: axiosRetry.exponentialDelay,
|
||||
retryCondition: (error) => {
|
||||
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
|
||||
(error.response?.status ? error.response.status >= 500 : false);
|
||||
}
|
||||
});
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export async function reportBuildPushActionFailure(error?: Error) {
|
||||
const requestOptions = {
|
||||
stickydisk_key: process.env.GITHUB_REPO_NAME || '',
|
||||
repo_name: process.env.GITHUB_REPO_NAME || '',
|
||||
region: process.env.BLACKSMITH_REGION || 'eu-central',
|
||||
arch: process.env.BLACKSMITH_ENV?.includes('arm') ? 'arm64' : 'amd64',
|
||||
vm_id: process.env.VM_ID || '',
|
||||
petname: process.env.PETNAME || '',
|
||||
message: error?.message || ''
|
||||
};
|
||||
const retryCondition = (error: AxiosError) => {
|
||||
return error.response?.status ? error.response.status > 500 : false;
|
||||
};
|
||||
const response = await postWithRetryToBlacksmithAPI('/stickydisks/report-failed', requestOptions, retryCondition);
|
||||
return response.data;
|
||||
}
|
||||
const requestOptions = {
|
||||
stickydisk_key: process.env.GITHUB_REPO_NAME || '',
|
||||
repo_name: process.env.GITHUB_REPO_NAME || '',
|
||||
region: process.env.BLACKSMITH_REGION || 'eu-central',
|
||||
arch: process.env.BLACKSMITH_ENV?.includes('arm') ? 'arm64' : 'amd64',
|
||||
vm_id: process.env.VM_ID || '',
|
||||
petname: process.env.PETNAME || '',
|
||||
message: error?.message || ''
|
||||
};
|
||||
|
||||
const client = createBlacksmithAPIClient();
|
||||
const response = await client.post('/stickydisks/report-failed', requestOptions);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function reportBuildCompleted(exportRes?: ExportRecordResponse, blacksmithDockerBuildId?: string | null, buildRef?: string, dockerBuildDurationSeconds?: string, exposeId?: string): Promise<void> {
|
||||
if (!blacksmithDockerBuildId) {
|
||||
|
@ -27,17 +71,18 @@ export async function reportBuildCompleted(exportRes?: ExportRecordResponse, bla
|
|||
}
|
||||
|
||||
try {
|
||||
const client = await utils.getBlacksmithAgentClient();
|
||||
const agentClient = await createBlacksmithAgentClient();
|
||||
const formData = new FormData();
|
||||
formData.append('shouldCommit', 'true');
|
||||
formData.append('vmID', process.env.VM_ID || '');
|
||||
formData.append('exposeID', exposeId || '');
|
||||
formData.append('stickyDiskKey', process.env.GITHUB_REPO_NAME || '');
|
||||
const retryCondition = (error: AxiosError) => {
|
||||
return error.response?.status ? error.response.status > 500 : false;
|
||||
};
|
||||
|
||||
await postWithRetry(client, '/stickydisks', formData, retryCondition);
|
||||
await agentClient.post('/stickydisks', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
|
||||
// Report success to Blacksmith API
|
||||
const requestOptions = {
|
||||
|
@ -48,13 +93,11 @@ export async function reportBuildCompleted(exportRes?: ExportRecordResponse, bla
|
|||
|
||||
if (exportRes) {
|
||||
let buildRefSummary;
|
||||
// Extract just the ref ID from the full buildRef path
|
||||
const refId = buildRef?.split('/').pop();
|
||||
core.info(`Using buildRef ID: ${refId}`);
|
||||
if (refId && exportRes.summaries[refId]) {
|
||||
buildRefSummary = exportRes.summaries[refId];
|
||||
} else {
|
||||
// Take first summary if buildRef not found
|
||||
const summaryKeys = Object.keys(exportRes.summaries);
|
||||
if (summaryKeys.length > 0) {
|
||||
buildRefSummary = exportRes.summaries[summaryKeys[0]];
|
||||
|
@ -67,11 +110,10 @@ export async function reportBuildCompleted(exportRes?: ExportRecordResponse, bla
|
|||
}
|
||||
}
|
||||
|
||||
await postWithRetryToBlacksmithAPI(`/stickydisks/dockerbuilds/${blacksmithDockerBuildId}`, requestOptions, retryCondition);
|
||||
return;
|
||||
const client = createBlacksmithAPIClient();
|
||||
await client.post(`/stickydisks/dockerbuilds/${blacksmithDockerBuildId}`, requestOptions);
|
||||
} catch (error) {
|
||||
core.warning('Error reporting build completed:', error);
|
||||
// We don't want to fail the build if this fails so we swallow the error
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,17 +124,18 @@ export async function reportBuildFailed(dockerBuildId: string | null, dockerBuil
|
|||
}
|
||||
|
||||
try {
|
||||
const client = await utils.getBlacksmithAgentClient();
|
||||
const blacksmithAgentClient = await createBlacksmithAgentClient();
|
||||
const formData = new FormData();
|
||||
formData.append('shouldCommit', 'false');
|
||||
formData.append('vmID', process.env.VM_ID || '');
|
||||
formData.append('exposeID', exposeId || '');
|
||||
formData.append('stickyDiskKey', process.env.GITHUB_REPO_NAME || '');
|
||||
const retryCondition = (error: AxiosError) => {
|
||||
return error.response?.status ? error.response.status > 500 : false;
|
||||
};
|
||||
|
||||
await postWithRetry(client, '/stickydisks', formData, retryCondition);
|
||||
await blacksmithAgentClient.post('/stickydisks', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
|
||||
// Report failure to Blacksmith API
|
||||
const requestOptions = {
|
||||
|
@ -101,8 +144,8 @@ export async function reportBuildFailed(dockerBuildId: string | null, dockerBuil
|
|||
runtime_seconds: dockerBuildDurationSeconds
|
||||
};
|
||||
|
||||
await postWithRetryToBlacksmithAPI(`/stickydisks/dockerbuilds/${dockerBuildId}`, requestOptions, retryCondition);
|
||||
return;
|
||||
const blacksmithAPIClient = createBlacksmithAPIClient();
|
||||
await blacksmithAPIClient.post(`/stickydisks/dockerbuilds/${dockerBuildId}`, requestOptions);
|
||||
} catch (error) {
|
||||
core.warning('Error reporting build failed:', error);
|
||||
// We don't want to fail the build if this fails so we swallow the error
|
||||
|
@ -122,10 +165,8 @@ export async function reportBuild(dockerfilePath: string) {
|
|||
git_branch: process.env.GITHUB_REF_NAME || ''
|
||||
};
|
||||
core.debug(`Reporting build with options: ${JSON.stringify(requestBody, null, 2)}`);
|
||||
const retryCondition = (error: AxiosError) => {
|
||||
return error.response?.status ? error.response.status > 500 : false;
|
||||
};
|
||||
const response = await postWithRetryToBlacksmithAPI('/stickydisks/dockerbuilds', requestBody, retryCondition);
|
||||
const blacksmithAPIClient = createBlacksmithAPIClient();
|
||||
const response = await blacksmithAPIClient.post('/stickydisks/dockerbuilds', requestBody);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
const statusCode = (error as AxiosError)?.response?.status;
|
||||
|
@ -135,85 +176,14 @@ export async function reportBuild(dockerfilePath: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function postWithRetryToBlacksmithAPI(url: string, requestBody: unknown, retryCondition: (error: AxiosError) => boolean): Promise<AxiosResponse> {
|
||||
const maxRetries = 5;
|
||||
const retryDelay = 100;
|
||||
const apiUrl = process.env.BLACKSMITH_ENV?.includes('staging') ? 'https://stagingapi.blacksmith.sh' : 'https://api.blacksmith.sh';
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
core.debug(`Request headers: Authorization: Bearer ${process.env.BLACKSMITH_STICKYDISK_TOKEN}, X-Github-Repo-Name: ${process.env.GITHUB_REPO_NAME || ''}`);
|
||||
|
||||
const fullUrl = `${apiUrl}${url}`;
|
||||
core.debug(`Making request to full URL: ${fullUrl}`);
|
||||
|
||||
return await axios.post(fullUrl, requestBody, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.BLACKSMITH_STICKYDISK_TOKEN}`,
|
||||
'X-Github-Repo-Name': process.env.GITHUB_REPO_NAME || '',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (attempt === maxRetries || !retryCondition(error as AxiosError)) {
|
||||
throw error;
|
||||
}
|
||||
core.warning(`Request failed, retrying (${attempt}/${maxRetries})...`);
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
throw new Error('Max retries reached');
|
||||
}
|
||||
|
||||
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: {
|
||||
Authorization: `Bearer ${process.env.BLACKSMITH_STICKYDISK_TOKEN}`,
|
||||
'X-Github-Repo-Name': process.env.GITHUB_REPO_NAME || '',
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (attempt === maxRetries || !retryCondition(error as AxiosError)) {
|
||||
throw error;
|
||||
}
|
||||
core.warning(`Request failed, retrying (${attempt}/${maxRetries})...`);
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
throw new Error('Max retries reached');
|
||||
}
|
||||
|
||||
export 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 {
|
||||
if (formData) {
|
||||
return await client.get(url, {
|
||||
data: formData,
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.BLACKSMITH_STICKYDISK_TOKEN}`,
|
||||
'X-Github-Repo-Name': process.env.GITHUB_REPO_NAME || '',
|
||||
'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;
|
||||
}
|
||||
core.warning(`Request failed, retrying (${attempt}/${maxRetries})...`);
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
throw new Error('Max retries reached');
|
||||
}
|
||||
export async function get(client: AxiosInstance, url: string, formData: FormData | null, options?: {signal?: AbortSignal}): Promise<AxiosResponse> {
|
||||
return await client.get(url, {
|
||||
...(formData && {data: formData}),
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.BLACKSMITH_STICKYDISK_TOKEN}`,
|
||||
'X-Github-Repo-Name': process.env.GITHUB_REPO_NAME || '',
|
||||
'Content-Type': 'multipart/form-data'
|
||||
},
|
||||
signal: options?.signal
|
||||
});
|
||||
}
|
|
@ -1,12 +1,9 @@
|
|||
import * as fs from 'fs';
|
||||
import * as core from '@actions/core';
|
||||
import {AxiosError} from 'axios';
|
||||
import {exec} from 'child_process';
|
||||
import {promisify} from 'util';
|
||||
import * as TOML from '@iarna/toml';
|
||||
import {Inputs} from './context';
|
||||
import * as reporter from './reporter';
|
||||
import * as utils from './utils';
|
||||
|
||||
const mountPoint = '/var/lib/buildkit';
|
||||
const execAsync = promisify(exec);
|
||||
|
@ -146,8 +143,8 @@ async function getDiskSize(device: string): Promise<number> {
|
|||
}
|
||||
}
|
||||
|
||||
async function getStickyDisk(retryCondition: (error: AxiosError) => boolean, options?: {signal?: AbortSignal}): Promise<{expose_id: string; device: string}> {
|
||||
const client = await utils.getBlacksmithAgentClient();
|
||||
async function getStickyDisk(options?: {signal?: AbortSignal}): Promise<{expose_id: string; device: string}> {
|
||||
const client = await reporter.createBlacksmithAgentClient();
|
||||
const formData = new FormData();
|
||||
// TODO(adityamaru): Support a stickydisk-per-build flag that will namespace the stickydisks by Dockerfile.
|
||||
// For now, we'll use the repo name as the stickydisk key.
|
||||
|
@ -164,7 +161,7 @@ async function getStickyDisk(retryCondition: (error: AxiosError) => boolean, opt
|
|||
for (const pair of formData.entries()) {
|
||||
core.debug(`${pair[0]}: ${pair[1]}`);
|
||||
}
|
||||
const response = await reporter.getWithRetry(client, '/stickydisks', formData, retryCondition, options);
|
||||
const response = await reporter.get(client, '/stickydisks', formData, options);
|
||||
const exposeId = response.data?.expose_id || '';
|
||||
const device = response.data?.disk_identifier || '';
|
||||
return {expose_id: exposeId, device: device};
|
||||
|
@ -197,14 +194,13 @@ export async function startAndConfigureBuildkitd(parallelism: number, device: st
|
|||
// throws an error if it is unable to do so because of a timeout or an error
|
||||
export async function setupStickyDisk(dockerfilePath: string): Promise<{device: string; buildId?: string | null; exposeId: string}> {
|
||||
try {
|
||||
const retryCondition = (error: AxiosError) => (error.response?.status ? error.response.status >= 500 : error.code === 'ECONNRESET');
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
|
||||
let buildResponse: {docker_build_id: string} | null = null;
|
||||
let exposeId: string = '';
|
||||
let device: string = '';
|
||||
const stickyDiskResponse = await getStickyDisk(retryCondition, {signal: controller.signal});
|
||||
const stickyDiskResponse = await getStickyDisk({signal: controller.signal});
|
||||
exposeId = stickyDiskResponse.expose_id;
|
||||
device = stickyDiskResponse.device;
|
||||
if (device === '') {
|
||||
|
|
59
src/utils.ts
59
src/utils.ts
|
@ -1,59 +0,0 @@
|
|||
import axios, {AxiosError, AxiosInstance, AxiosResponse} from 'axios';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
export async function getBlacksmithAgentClient(): Promise<AxiosInstance> {
|
||||
const stickyDiskMgrUrl = 'http://192.168.127.1:5556';
|
||||
return axios.create({
|
||||
baseURL: stickyDiskMgrUrl
|
||||
});
|
||||
}
|
||||
|
||||
export 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: {
|
||||
Authorization: `Bearer ${process.env.BLACKSMITH_STICKYDISK_TOKEN}`,
|
||||
'X-Github-Repo-Name': process.env.GITHUB_REPO_NAME || '',
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (attempt === maxRetries || !retryCondition(error as AxiosError)) {
|
||||
throw error;
|
||||
}
|
||||
core.warning(`Request failed, retrying (${attempt}/${maxRetries})...`);
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
throw new Error('Max retries reached');
|
||||
}
|
||||
|
||||
export async function postWithRetryToBlacksmithAPI(url: string, requestBody: unknown, retryCondition: (error: AxiosError) => boolean): Promise<AxiosResponse> {
|
||||
const maxRetries = 5;
|
||||
const retryDelay = 100;
|
||||
const apiUrl = process.env.BLACKSMITH_ENV?.includes('staging') ? 'https://stagingapi.blacksmith.sh' : 'https://api.blacksmith.sh';
|
||||
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
const fullUrl = `${apiUrl}${url}`;
|
||||
return await axios.post(fullUrl, requestBody, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${process.env.BLACKSMITH_STICKYDISK_TOKEN}`,
|
||||
'X-Github-Repo-Name': process.env.GITHUB_REPO_NAME || '',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (attempt === maxRetries || !retryCondition(error as AxiosError)) {
|
||||
throw error;
|
||||
}
|
||||
core.warning(`Request failed, retrying (${attempt}/${maxRetries})...`);
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
throw new Error('Max retries reached');
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue