Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 44 additions & 27 deletions .buildkite/pipeline-utils/buildkite/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { AxiosInstance } from 'axios';
import axios from 'axios';
import type { ExecSyncOptions } from 'child_process';
import { execFileSync, execSync } from 'child_process';

Expand Down Expand Up @@ -162,35 +160,53 @@ export interface BuildkiteWaitStep {
}

export class BuildkiteClient {
http: AxiosInstance;
exec: ExecType;
baseUrl: string;
private defaultHeaders: Record<string, string>;

constructor(config: BuildkiteClientConfig = {}) {
const BUILDKITE_TOKEN = config.token ?? process.env.BUILDKITE_TOKEN;

this.baseUrl = config.baseUrl ?? process.env.BUILDKITE_BASE_URL ?? 'https://api.buildkite.com';

// const BUILDKITE_AGENT_BASE_URL =
// process.env.BUILDKITE_AGENT_BASE_URL || 'https://agent.buildkite.com/v3';
// const BUILDKITE_AGENT_TOKEN = process.env.BUILDKITE_AGENT_TOKEN;
this.defaultHeaders = {
Authorization: `Bearer ${BUILDKITE_TOKEN}`,
};

this.exec = config.exec ?? execSync;
}

private async httpGet<T = unknown>(path: string): Promise<{ data: T; headers: Headers }> {
const url = `${this.baseUrl}/${path.replace(/^\//, '')}`;
const resp = await fetch(url, {
headers: this.defaultHeaders,
});

this.http = axios.create({
baseURL: this.baseUrl,
if (!resp.ok) {
throw new Error(`Buildkite API request failed: ${resp.status} ${resp.statusText}`);
}

const data = (await resp.json()) as T;
return { data, headers: resp.headers };
}

private async httpPost<T = unknown>(path: string, body?: unknown): Promise<{ data: T }> {
const url = `${this.baseUrl}/${path.replace(/^\//, '')}`;
const resp = await fetch(url, {
method: 'POST',
headers: {
Authorization: `Bearer ${BUILDKITE_TOKEN}`,
'Content-Type': 'application/json',
...this.defaultHeaders,
},
allowAbsoluteUrls: false,
body: body ? JSON.stringify(body) : undefined,
});

this.exec = config.exec ?? execSync;
if (!resp.ok) {
throw new Error(`Buildkite API request failed: ${resp.status} ${resp.statusText}`);
}

// this.agentHttp = axios.create({
// baseURL: BUILDKITE_AGENT_BASE_URL,
// headers: {
// Authorization: `Token ${BUILDKITE_AGENT_TOKEN}`,
// },
// });
const data = (await resp.json()) as T;
return { data };
}

getBuild = async (
Expand All @@ -200,30 +216,30 @@ export class BuildkiteClient {
): Promise<Build> => {
// TODO properly assemble URL
const link = `v2/organizations/elastic/pipelines/${pipelineSlug}/builds/${buildNumber}?include_retried_jobs=${includeRetriedJobs.toString()}`;
const resp = await this.http.get(link);
return resp.data as Build;
const resp = await this.httpGet<Build>(link);
return resp.data;
};

getBuildsAfterDate = async (
pipelineSlug: string,
date: string,
numberOfBuilds: number
): Promise<Build[]> => {
const response = await this.http.get(
const response = await this.httpGet<Build[]>(
`v2/organizations/elastic/pipelines/${pipelineSlug}/builds?created_from=${date}&per_page=${numberOfBuilds}`
);
return response.data as Build[];
return response.data;
};

getBuildForCommit = async (pipelineSlug: string, commit: string): Promise<Build | null> => {
if (commit.length !== 40) {
throw new Error(`Invalid commit hash: ${commit}, this endpoint works with full SHAs only`);
}

const response = await this.http.get(
const response = await this.httpGet<Build[]>(
`v2/organizations/elastic/pipelines/${pipelineSlug}/builds?commit=${commit}`
);
const builds = response.data as Build[];
const builds = response.data;
if (builds.length === 0) {
return null;
}
Expand Down Expand Up @@ -328,13 +344,14 @@ export class BuildkiteClient {
break;
}

const resp = await this.http.get(link);
const resp = await this.httpGet<Artifact[]>(link);
link = '';

artifacts.push(resp.data);

if (resp.headers.link) {
const result = parseLinkHeader(resp.headers.link as string, this.baseUrl);
const linkHeader = resp.headers.get('link');
if (linkHeader) {
const result = parseLinkHeader(linkHeader, this.baseUrl);
if (result?.next) {
link = result.next;
}
Expand Down Expand Up @@ -364,7 +381,7 @@ export class BuildkiteClient {
): Promise<Build> => {
const url = `v2/organizations/elastic/pipelines/${pipelineSlug}/builds`;

return (await this.http.post(url, options)).data;
return (await this.httpPost<Build>(url, options)).data;
};

cancelStep = (stepIdOrKey: string): void => {
Expand Down
83 changes: 56 additions & 27 deletions .buildkite/pipeline-utils/ci-stats/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { Method, AxiosRequestConfig } from 'axios';
import axios from 'axios';
// native fetch - no axios dependency

export interface CiStatsClientConfig {
baseUrl?: string;
Expand Down Expand Up @@ -58,9 +57,9 @@ export interface TestGroupRunOrderResponse {

interface RequestOptions {
path: string;
method?: Method;
params?: AxiosRequestConfig['params'];
body?: AxiosRequestConfig['data'];
method?: string;
params?: Record<string, string>;
body?: unknown;
maxAttempts?: number;
}

Expand All @@ -79,7 +78,7 @@ export class CiStatsClient {
}

createBuild = async () => {
const resp = await this.request<CiStatsBuild>({
return await this.request<CiStatsBuild>({
method: 'POST',
path: '/v1/build',
body: {
Expand All @@ -92,8 +91,6 @@ export class CiStatsClient {
: [],
},
});

return resp.data;
};

addGitInfo = async (buildId: string) => {
Expand Down Expand Up @@ -139,15 +136,13 @@ export class CiStatsClient {
};

getPrReport = async (buildId: string) => {
const resp = await this.request<CiStatsPrReport>({
return await this.request<CiStatsPrReport>({
method: 'GET',
path: `v2/pr_report`,
params: {
buildId,
},
});

return resp.data;
};

pickTestGroupRunOrder = async (body: {
Expand Down Expand Up @@ -182,33 +177,67 @@ export class CiStatsClient {
console.log('requesting test group run order from ci-stats:');
console.log(JSON.stringify(body, null, 2));

const resp = await axios.request<TestGroupRunOrderResponse>({
const url = `${this.baseUrl}/v2/_pick_test_group_run_order`;
const resp = await fetch(url, {
method: 'POST',
baseURL: this.baseUrl,
headers: this.defaultHeaders,
url: '/v2/_pick_test_group_run_order',
data: body,
headers: {
'Content-Type': 'application/json',
...this.defaultHeaders,
},
body: JSON.stringify(body),
});

return resp.data;
if (!resp.ok) {
throw new Error(`CI Stats request failed with status ${resp.status}`);
}

return (await resp.json()) as TestGroupRunOrderResponse;
};

private async request<T>({ method, path, params, body, maxAttempts = 3 }: RequestOptions) {
private async request<T>({
method,
path,
params,
body,
maxAttempts = 3,
}: RequestOptions): Promise<T> {
let attempt = 0;

while (true) {
attempt += 1;
try {
return await axios.request<T>({
method,
baseURL: this.baseUrl,
url: path,
params,
data: body,
headers: this.defaultHeaders,
});
const queryString = params ? '?' + new URLSearchParams(params).toString() : '';
const url = `${this.baseUrl}/${path.replace(/^\//, '')}${queryString}`;

const fetchOptions: RequestInit = {
method: method ?? 'GET',
headers: {
...this.defaultHeaders,
...(body ? { 'Content-Type': 'application/json' } : {}),
},
};

if (body) {
fetchOptions.body = JSON.stringify(body);
}

const resp = await fetch(url, fetchOptions);

if (!resp.ok) {
const errorBody = await resp.text();
let errorMessage: string | undefined;
try {
errorMessage = JSON.parse(errorBody)?.message;
} catch {
// ignore parse error
}
throw new Error(errorMessage ?? `Request failed with status ${resp.status}`);
}

const text = await resp.text();
return (text ? JSON.parse(text) : undefined) as T;
} catch (error) {
console.error('CI Stats request error:', error?.response?.data?.message);
console.error('CI Stats request error:', (error as Error).message);

if (attempt < maxAttempts) {
const sec = attempt * 3;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const { readFileSync, createWriteStream } = require('fs');
const assert = require('assert');
const { execFile } = require('child_process');
const { finished } = require('stream').promises;
const axios = require('axios');
const { Readable } = require('stream');
const fg = require('fast-glob');
const AdmZip = require('adm-zip');

Expand Down Expand Up @@ -158,18 +158,24 @@ const getSha256Hash = async (filePath) => {

process.chdir('chromium');

const response = await axios.get(
const response = await fetch(
'https://googlechromelabs.github.io/chrome-for-testing/known-good-versions-with-downloads.json'
);

if (!response.ok) {
throw new Error(
`Failed to fetch known good versions: ${response.status} ${response.statusText}`
);
}

/**
* @description list of known good versions of chromium provided by google
* @type {{
* timestamp: string
* versions: VersionDefinition[]
* }}
*/
const { versions } = response.data;
const { versions } = await response.json();

const chromiumVersion = await $('buildkite-agent', ['meta-data', 'get', 'chromium_version'], {
printToScreen: true,
Expand Down Expand Up @@ -204,13 +210,20 @@ const getSha256Hash = async (filePath) => {

const url = new URL(download.url);

const downloadResponse = await axios.get(url.toString(), { responseType: 'stream' });
const downloadResponse = await fetch(url.toString());

if (!downloadResponse.ok) {
throw new Error(
`Failed to download ${url}: ${downloadResponse.status} ${downloadResponse.statusText}`
);
}

const downloadFileName = parse(url.pathname).base;

downloadResponse.data.pipe(createWriteStream(downloadFileName));
const nodeStream = Readable.fromWeb(downloadResponse.body);
nodeStream.pipe(createWriteStream(downloadFileName));

await finished(downloadResponse.data);
await finished(nodeStream);

console.log(`---Extracting and computing checksum for ${downloadFileName}\n`);

Expand Down
29 changes: 12 additions & 17 deletions .buildkite/scripts/serverless/create_deploy_tag/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import axios from 'axios';

import { getExec } from './mock_exec';
import type { GitCommitExtract } from './info_sections/commit_info';
import type { BuildkiteBuildExtract } from './info_sections/build_info';
Expand Down Expand Up @@ -93,22 +91,19 @@ export function sendSlackMessage(payload: any) {
console.log('No SLACK_WEBHOOK_URL set, not sending slack message');
return Promise.resolve();
} else {
return axios
.post(
process.env.DEPLOY_TAGGER_SLACK_WEBHOOK_URL,
typeof payload === 'string' ? payload : JSON.stringify(payload)
)
.catch((error) => {
if (axios.isAxiosError(error) && error.response) {
console.error(
"Couldn't send slack message.",
error.response.status,
error.response.statusText,
error.message
);
} else {
console.error("Couldn't send slack message.", error.message);
const body = typeof payload === 'string' ? payload : JSON.stringify(payload);
return fetch(process.env.DEPLOY_TAGGER_SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
})
.then((response) => {
if (!response.ok) {
console.error("Couldn't send slack message.", response.status, response.statusText);
}
})
.catch((error) => {
console.error("Couldn't send slack message.", (error as Error).message);
});
}
}
Loading
Loading