Skip to content

Commit

Permalink
Fix artifacts parsing for @opentelemetry/exporter-trace-otlp-http v…
Browse files Browse the repository at this point in the history
…ersion `<0.29.0` (#36)
  • Loading branch information
corentinmusard authored Jan 4, 2025
1 parent b8144c1 commit e9b4a58
Show file tree
Hide file tree
Showing 15 changed files with 239 additions and 344 deletions.
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.13.2] - 2025-01-04

### Fixed

- Fix artifacts parsing for `@opentelemetry/exporter-trace-otlp-http` version `<0.29.0`

### Changed

- Refactor artifacts handling

## [1.13.1] - 2024-12-31

### Fixed
Expand Down Expand Up @@ -113,7 +123,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for `https` endpoints (proto over http).
- Update to node 20.x

[unreleased]: https://github.com/corentinmusard/otel-cicd-action/compare/v1.13.1...HEAD
[unreleased]: https://github.com/corentinmusard/otel-cicd-action/compare/v1.13.2...HEAD
[1.13.2]: https://github.com/corentinmusard/otel-cicd-action/compare/v1.13.1...v1.13.2
[1.13.1]: https://github.com/corentinmusard/otel-cicd-action/compare/v1.13.0...v1.13.1
[1.13.0]: https://github.com/corentinmusard/otel-cicd-action/compare/v1.12.1...v1.13.0
[1.12.1]: https://github.com/corentinmusard/otel-cicd-action/compare/v1.12.0...v1.12.1
Expand Down
203 changes: 81 additions & 122 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/__assets__/run.rec

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/github/__assets__/getPRLabels.rec
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/repos/{owner}/{repo}/issues/{issue_number}/labels
https://api.github.com/repos/corentinmusard/otel-cicd-action/issues/18/labels
https://api.github.com/repos/corentinmusard/otel-cicd-action/issues/18/labels
200
W3siaWQiOjY1NzA0MjU5NzUsIm5vZGVfaWQiOiJMQV9rd0RPTFRpOGtzOEFBQUFCaDZDLWR3IiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9jb3JlbnRpbm11c2FyZC9vdGVsLWNpY2QtYWN0aW9uL2xhYmVscy9lbmhhbmNlbWVudCIsIm5hbWUiOiJlbmhhbmNlbWVudCIsImNvbG9yIjoiYTJlZWVmIiwiZGVmYXVsdCI6dHJ1ZSwiZGVzY3JpcHRpb24iOiJOZXcgZmVhdHVyZSBvciByZXF1ZXN0In0seyJpZCI6NzA3NDExMDQ1OSwibm9kZV9pZCI6IkxBX2t3RE9MVGk4a3M4QUFBQUJwYVpiLXciLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2NvcmVudGlubXVzYXJkL290ZWwtY2ljZC1hY3Rpb24vbGFiZWxzL3Rlc3QiLCJuYW1lIjoidGVzdCIsImNvbG9yIjoiOTM4MkY5IiwiZGVmYXVsdCI6ZmFsc2UsImRlc2NyaXB0aW9uIjoiIn1d
/repos/{owner}/{repo}/issues/{issue_number}/labels
https://api.github.com/repos/corentinmusard/otel-cicd-action/issues/19/labels
https://api.github.com/repos/corentinmusard/otel-cicd-action/issues/19/labels
200
W10=
4 changes: 2 additions & 2 deletions src/github/__assets__/getPRsLabels.rec
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/repos/{owner}/{repo}/issues/{issue_number}/labels
https://api.github.com/repos/corentinmusard/otel-cicd-action/issues/18/labels
https://api.github.com/repos/corentinmusard/otel-cicd-action/issues/18/labels
200
W3siaWQiOjY1NzA0MjU5NzUsIm5vZGVfaWQiOiJMQV9rd0RPTFRpOGtzOEFBQUFCaDZDLWR3IiwidXJsIjoiaHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9jb3JlbnRpbm11c2FyZC9vdGVsLWNpY2QtYWN0aW9uL2xhYmVscy9lbmhhbmNlbWVudCIsIm5hbWUiOiJlbmhhbmNlbWVudCIsImNvbG9yIjoiYTJlZWVmIiwiZGVmYXVsdCI6dHJ1ZSwiZGVzY3JpcHRpb24iOiJOZXcgZmVhdHVyZSBvciByZXF1ZXN0In0seyJpZCI6NzA3NDExMDQ1OSwibm9kZV9pZCI6IkxBX2t3RE9MVGk4a3M4QUFBQUJwYVpiLXciLCJ1cmwiOiJodHRwczovL2FwaS5naXRodWIuY29tL3JlcG9zL2NvcmVudGlubXVzYXJkL290ZWwtY2ljZC1hY3Rpb24vbGFiZWxzL3Rlc3QiLCJuYW1lIjoidGVzdCIsImNvbG9yIjoiOTM4MkY5IiwiZGVmYXVsdCI6ZmFsc2UsImRlc2NyaXB0aW9uIjoiIn1d
/repos/{owner}/{repo}/issues/{issue_number}/labels
https://api.github.com/repos/corentinmusard/otel-cicd-action/issues/19/labels
https://api.github.com/repos/corentinmusard/otel-cicd-action/issues/19/labels
200
W10=
2 changes: 1 addition & 1 deletion src/github/__assets__/getWorkflowRunJobs.rec

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions src/github/github.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { RestEndpointMethodTypes } from "@octokit/plugin-rest-endpoint-meth
import fetchMock from "jest-fetch-mock";
import { mock, mockDeep } from "jest-mock-extended";
import type { Octokit } from "./github";
import { type WorkflowArtifactDownload, listWorkflowRunArtifacts } from "./github";
import { listWorkflowRunArtifacts } from "./github";

const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
Expand All @@ -22,7 +22,7 @@ type DownloadArtifactResponse = RestEndpointMethodTypes["actions"]["downloadArti
describe("listWorkflowRunArtifacts", () => {
let mockContext: Context;
let mockOctokit: Octokit = mockDeep<Octokit>();
let subject: WorkflowArtifactDownload;
let artifactPath: string;

beforeAll(async () => {
mockContext = mockDeep<Context>();
Expand All @@ -47,29 +47,29 @@ describe("listWorkflowRunArtifacts", () => {
fetchMock.mockResponseOnce(() => Promise.resolve({ body: zipFile as unknown as string }));

const lookup = await listWorkflowRunArtifacts(mockContext, mockOctokit, 1);
const response = lookup("lint-and-test", "run tests");
const response = lookup.get("lint-and-test")?.get("run tests");
if (!response) {
fail("Lookup Failed: Did not parse zip file correctly");
}
subject = response;
artifactPath = response;
});

afterAll(() => {
if (subject?.path) {
fs.unlinkSync(subject.path);
if (artifactPath) {
fs.unlinkSync(artifactPath);
}
});

it("test WorkflowArtifactDownload return to be defined", () => {
expect(subject).toBeDefined();
expect(artifactPath).toBeDefined();
});

it("test WorkflowArtifactDownload path exists", () => {
expect(subject.path).toEqual("{lint-and-test}{run tests}.log");
expect(fs.existsSync(subject.path)).toBeTruthy();
expect(artifactPath).toEqual("{lint-and-test}{run tests}.log");
expect(fs.existsSync(artifactPath)).toBeTruthy();
});
it("test WorkflowArtifactDownload has data", () => {
const data = fs.readFileSync(subject.path, { encoding: "utf8", flag: "r" });
const data = fs.readFileSync(artifactPath, { encoding: "utf8", flag: "r" });
// expect(data.length).toBeGreaterThan(0);
const lines = data.split("\n");
expect(lines.length).toBeGreaterThan(1);
Expand Down
145 changes: 47 additions & 98 deletions src/github/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,21 @@ import * as artifact from "@actions/artifact";
import * as core from "@actions/core";
import type { Context } from "@actions/github/lib/context";
import type { GitHub } from "@actions/github/lib/utils";
import type { components } from "@octokit/openapi-types";
import JSZip from "jszip";

type Octokit = InstanceType<typeof GitHub>;
type WorkflowRunJob = components["schemas"]["job"];

type WorkflowArtifactMap = {
[job: string]: {
[step: string]: WorkflowArtifactDownload;
};
};

type WorkflowArtifactDownload = {
jobName: string;
stepName: string;
path: string;
};

type WorkflowArtifactLookup = (jobName: string, stepName: string) => WorkflowArtifactDownload | undefined;
type JobName = string;
type StepName = string;
type ArtifactPath = string;

async function listWorkflowRunArtifacts(
context: Context,
octokit: Octokit,
runId: number,
): Promise<WorkflowArtifactLookup> {
let artifactsLookup: WorkflowArtifactMap = {};
type StepArtifactMap = Map<StepName, ArtifactPath>;
type JobArtifactMap = Map<JobName, StepArtifactMap>;

if (runId === context.runId) {
artifactsLookup = await getSelfArtifactMap();
} else {
artifactsLookup = await getWorkflowRunArtifactMap(context, octokit, runId);
}
return (jobName: string, stepName: string) => {
try {
return artifactsLookup[jobName][stepName];
} catch (_e) {
return undefined;
}
};
async function listWorkflowRunArtifacts(context: Context, octokit: Octokit, runId: number) {
return runId === context.runId
? await getSelfArtifactMap()
: await getWorkflowRunArtifactMap(context, octokit, runId);
}

const artifactNameRegex = /\{(?<jobName>.*)\}\{(?<stepName>.*)\}/;
Expand All @@ -54,15 +30,14 @@ async function getWorkflowRunArtifactMap(context: Context, octokit: Octokit, run
per_page: 100,
});

const artifactsLookup: WorkflowArtifactMap = await artifacts.reduce(async (resultP, artifact) => {
const result = await resultP;
return await artifacts.reduce(async (resultP: Promise<JobArtifactMap>, artifact) => {
const next = await resultP;
const match = artifact.name.match(artifactNameRegex);
const next: WorkflowArtifactMap = { ...result };
if (match?.groups?.["jobName"] && match?.groups?.["stepName"]) {
const { jobName, stepName } = match.groups;
core.debug(`Found Artifact for Job<${jobName}> Step<${stepName}>`);
if (!(jobName in next)) {
next[jobName] = {};
if (!next.has(jobName)) {
next.set(jobName, new Map());
}

const downloadResponse = await octokit.rest.actions.downloadArtifact({
Expand All @@ -77,11 +52,7 @@ async function getWorkflowRunArtifactMap(context: Context, octokit: Octokit, run
// useful for testing because the artifact url expires after 1 minute
if (fs.existsSync(`${artifact.name}.log`)) {
core.debug(`Artifact ${artifact.name} already exists, skipping download`);
next[jobName][stepName] = {
jobName,
stepName,
path: filename,
};
next.get(jobName)?.set(stepName, filename);
return next;
}

Expand All @@ -93,86 +64,54 @@ async function getWorkflowRunArtifactMap(context: Context, octokit: Octokit, run
zip.files[Object.keys(zip.files)[0]].nodeStream().pipe(writeStream);
await new Promise((fulfill) => writeStream.on("finish", fulfill));
core.debug(`Downloaded Artifact ${writeStream.path.toString()}`);
next[jobName][stepName] = {
jobName,
stepName,
path: writeStream.path.toString(),
};
next.get(jobName)?.set(stepName, writeStream.path.toString());
} finally {
writeStream.close();
}
}

return next;
}, Promise.resolve({}));
return artifactsLookup;
}, Promise.resolve(new Map()));
}

async function getSelfArtifactMap() {
const client = artifact.create();
const responses = await client.downloadAllArtifacts();
const artifactsMap: WorkflowArtifactMap = responses.reduce((result, { artifactName, downloadPath }) => {
const next: WorkflowArtifactMap = { ...result };

return responses.reduce((result: JobArtifactMap, { artifactName, downloadPath }) => {
const next = result;
const match = artifactName.match(artifactNameRegex);
if (match?.groups?.["jobName"] && match?.groups?.["stepName"]) {
const { jobName, stepName } = match.groups;
core.debug(`Found Artifact for Job<${jobName}> Step<${stepName}>`);
if (!(jobName in next)) {
next[jobName] = {};
if (!next.has(jobName)) {
next.set(jobName, new Map());
}

const artifactDirFiles = fs.readdirSync(downloadPath);
if (artifactDirFiles && artifactDirFiles.length > 0) {
next[jobName][stepName] = {
jobName,
stepName,
path: path.join(downloadPath, artifactDirFiles[0]),
};
if (artifactDirFiles.length > 0) {
next.get(jobName)?.set(stepName, path.join(downloadPath, artifactDirFiles[0]));
}
}
return next;
}, {});

return artifactsMap;
}, new Map());
}

async function listJobsForWorkflowRun(context: Context, octokit: Octokit, runId: number) {
return await octokit.paginate(octokit.rest.actions.listJobsForWorkflowRun, {
async function getWorkflowRun(context: Context, octokit: Octokit, runId: number) {
const res = await octokit.rest.actions.getWorkflowRun({
...context.repo,
run_id: runId,
filter: "latest", // risk of missing a run if re-run happens between Action trigger and this query
per_page: 100,
});
return res.data;
}

type WorkflowRunJobs = {
workflowRun: components["schemas"]["workflow-run"];
jobs: WorkflowRunJob[];
workflowRunArtifacts: WorkflowArtifactLookup;
};

async function getWorkflowRunJobs(context: Context, octokit: Octokit, runId: number) {
const getWorkflowRunResponse = await octokit.rest.actions.getWorkflowRun({
async function listJobsForWorkflowRun(context: Context, octokit: Octokit, runId: number) {
return await octokit.paginate(octokit.rest.actions.listJobsForWorkflowRun, {
...context.repo,
run_id: runId,
filter: "latest", // risk of missing a run if re-run happens between Action trigger and this query
per_page: 100,
});

const workflowRunArtifacts = await listWorkflowRunArtifacts(context, octokit, runId);
const jobs = await listJobsForWorkflowRun(context, octokit, runId);

const workflowRunJobs: WorkflowRunJobs = {
workflowRun: getWorkflowRunResponse.data,
jobs,
workflowRunArtifacts,
};
return workflowRunJobs;
}

async function getPRLabels(context: Context, octokit: Octokit, prNumber: number) {
const labelResponse = await octokit.rest.issues.listLabelsOnIssue({
...context.repo,
issue_number: prNumber,
});
return labelResponse.data.map((l) => l.name);
}

async function getPRsLabels(context: Context, octokit: Octokit, prNumbers: number[]) {
Expand All @@ -184,14 +123,24 @@ async function getPRsLabels(context: Context, octokit: Octokit, prNumbers: numbe
return labels;
}

async function getPRLabels(context: Context, octokit: Octokit, prNumber: number) {
return await octokit.paginate(
octokit.rest.issues.listLabelsOnIssue,
{
...context.repo,
issue_number: prNumber,
},
(response) => response.data.map((issue) => issue.name),
);
}

export {
getWorkflowRunJobs,
getWorkflowRun,
listWorkflowRunArtifacts,
listJobsForWorkflowRun,
getPRLabels,
getPRsLabels,
type Octokit,
type WorkflowArtifactDownload,
type WorkflowArtifactLookup,
type WorkflowRunJob,
type WorkflowRunJobs,
type StepArtifactMap,
type JobArtifactMap,
};
20 changes: 1 addition & 19 deletions src/github/github2.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,11 @@
import { Context } from "@actions/github/lib/context";
import { replayOctokit } from "../replay";
import { type Octokit, getPRLabels, getPRsLabels, getWorkflowRunJobs } from "./github";
import { type Octokit, getPRLabels, getPRsLabels } from "./github";

const token = process.env["GH_TOKEN"] ?? "";
const owner = "corentinmusard";
const repo = "otel-cicd-action";

describe("getWorkflowRunJobs", () => {
let octokit: Octokit;

beforeAll(async () => {
process.env["GITHUB_REPOSITORY"] = "biomejs/biome";
octokit = await replayOctokit("getWorkflowRunJobs", token);
});

it("should return the workflow run jobs", async () => {
const context = new Context();
context.runId = 2; // different than 12541749172

const workflowRunJobs = await getWorkflowRunJobs(context, octokit, 12541749172);
expect(workflowRunJobs.jobs).toHaveLength(8);
expect(workflowRunJobs.workflowRun.id).toBe(12541749172);
}, 10000);
});

describe("getPRLabels", () => {
let octokit: Octokit;

Expand Down
Loading

0 comments on commit e9b4a58

Please sign in to comment.