diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml index 3266cc5..81957b4 100644 --- a/.github/workflows/test-action.yml +++ b/.github/workflows/test-action.yml @@ -10,6 +10,7 @@ permissions: contents: read pull-requests: write checks: write + actions: read jobs: test-action: diff --git a/README.md b/README.md index 073555e..7b633f4 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,7 @@ permissions: contents: read pull-requests: write checks: write + actions: read jobs: scanoss-code-scan: @@ -109,6 +110,7 @@ permissions: contents: read pull-requests: write checks: write + actions: read jobs: scanoss-code-scan: diff --git a/dist/index.js b/dist/index.js index fd78825..e3483b8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -125903,10 +125903,11 @@ async function run() { core.debug(`SCANOSS Scan Action started...`); // create policies core.debug(`Creating policies`); + const firstRunId = await (0, github_utils_1.getFirstRunId)(); //Read declared policies on input parameter 'policies' and create an instance for each one. const policies = policy_manager_1.policyManager.getPolicies(); for (const policy of policies) { - await policy.start(); + await policy.start(firstRunId); } // run scan const { scan, stdout } = await scan_service_1.scanService.scan(); @@ -126013,7 +126014,7 @@ class CopyleftPolicyCheck extends policy_check_1.PolicyCheck { if (details) { const { id } = await this.uploadArtifact(details); if (id) - details = this.concatPolicyArtifactURLToPolicyCheck(details, id); + details = await this.concatPolicyArtifactURLToPolicyCheck(details, id); } if (componentsWithCopyleft.length === 0) { return this.success(summary, details); @@ -126149,6 +126150,7 @@ class PolicyCheck { _raw; _status; _conclusion; + _firstRunId = -1; constructor(checkName) { this.octokit = (0, github_1.getOctokit)(inputs.GITHUB_TOKEN); this.checkName = checkName; @@ -126156,7 +126158,7 @@ class PolicyCheck { this._conclusion = CONCLUSION.Neutral; this.checkRunId = -1; } - async start() { + async start(runId) { const result = await this.octokit.rest.checks.create({ owner: github_1.context.repo.owner, repo: github_1.context.repo.repo, @@ -126165,6 +126167,7 @@ class PolicyCheck { }); this.checkRunId = result.data.id; this._raw = result.data; + this._firstRunId = runId; this._status = STATUS.INITIALIZED; return result.data; } @@ -126178,7 +126181,7 @@ class PolicyCheck { return this._raw; } get url() { - return `${github_1.context.serverUrl}/${github_1.context.repo.owner}/${github_1.context.repo.repo}/actions/runs/${github_1.context.runId}/job/${this.raw.id}`; + return `${github_1.context.serverUrl}/${github_1.context.repo.owner}/${github_1.context.repo.repo}/actions/runs/${this._firstRunId}/job/${this.raw.id}`; } async run(scannerResults) { if (this._status === STATUS.UNINITIALIZED) @@ -126219,7 +126222,7 @@ class PolicyCheck { exceedMaxGiHubApiLimit(text) { return text.length > this.MAX_GH_API_CONTENT_SIZE; } - concatPolicyArtifactURLToPolicyCheck(details, artifactId) { + async concatPolicyArtifactURLToPolicyCheck(details, artifactId) { const link = `\n\nDownload the ` + `[${this.getPolicyName()} Result](${github_1.context.serverUrl}/` + `${github_1.context.repo.owner}/${github_1.context.repo.repo}/actions/runs/` + @@ -126421,7 +126424,7 @@ class UndeclaredPolicyCheck extends policy_check_1.PolicyCheck { if (details) { const { id } = await this.uploadArtifact(details); if (id) - details = this.concatPolicyArtifactURLToPolicyCheck(details, id); + details = await this.concatPolicyArtifactURLToPolicyCheck(details, id); } if (nonDeclaredComponents.length === 0) { return this.success(summary, details); @@ -126980,11 +126983,12 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.createCommentOnPR = exports.getSHA = exports.isPullRequest = void 0; +exports.getFirstRunId = exports.createCommentOnPR = exports.getSHA = exports.isPullRequest = void 0; const github_1 = __nccwpck_require__(95438); const core = __importStar(__nccwpck_require__(42186)); const inputs = __importStar(__nccwpck_require__(483)); const prEvents = ['pull_request', 'pull_request_review', 'pull_request_review_comment']; +const FIND_FIRST_RUN_EVENT = 'workflow_dispatch'; function isPullRequest() { return prEvents.includes(github_1.context.eventName); } @@ -127011,6 +127015,38 @@ async function createCommentOnPR(message) { }); } exports.createCommentOnPR = createCommentOnPR; +async function getFirstRunId() { + let firstRunId = github_1.context.runId; + if (github_1.context.eventName === FIND_FIRST_RUN_EVENT) { + const firstRun = await loadFirstRun(github_1.context.repo.owner, github_1.context.repo.repo); + if (firstRun) { + core.info(`First Run ID found: ${firstRun.id}`); + firstRunId = firstRun.id; + } + } + return firstRunId; +} +exports.getFirstRunId = getFirstRunId; +async function loadFirstRun(owner, repo) { + const octokit = (0, github_1.getOctokit)(inputs.GITHUB_TOKEN); + const sha = getSHA(); + const workflowRun = await octokit.rest.actions.getWorkflowRun({ + owner, + repo, + run_id: github_1.context.runId + }); + const runs = await octokit.rest.actions.listWorkflowRuns({ + owner, + repo, + head_sha: sha, + workflow_id: workflowRun.data.workflow_id + }); + // Filter by the given SHA + const filteredRuns = runs.data.workflow_runs.filter(run => run.head_sha === sha); + // Sort by creation date to find the first run + const sortedRuns = filteredRuns.sort((a, b) => a.created_at && b.created_at ? new Date(a.created_at).getTime() - new Date(b.created_at).getTime() : 0); + return sortedRuns.length ? sortedRuns[0] : null; +} /***/ }), diff --git a/package-lock.json b/package-lock.json index c36a0ed..a22f186 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scanoss-code-scan-action", - "version": "0.2.0", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scanoss-code-scan-action", - "version": "0.2.0", + "version": "0.2.1", "license": "MIT", "dependencies": { "@actions/artifact": "^2.1.0", @@ -1633,6 +1633,19 @@ "node": ">= 18" } }, + "node_modules/@octokit/core/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/core/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, "node_modules/@octokit/endpoint": { "version": "9.0.4", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.4.tgz", @@ -1645,6 +1658,19 @@ "node": ">= 18" } }, + "node_modules/@octokit/endpoint/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/endpoint/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, "node_modules/@octokit/graphql": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz", @@ -1658,10 +1684,18 @@ "node": ">= 18" } }, - "node_modules/@octokit/openapi-types": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.1.0.tgz", - "integrity": "sha512-6G+ywGClliGQwRsjvqVYpklIfa7oRPA0vyhPQG/1Feh+B+wU0vGH1JiJ5T25d3g1JZYBHzR2qefLi9x8Gt+cpw==" + "node_modules/@octokit/graphql/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/graphql/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } }, "node_modules/@octokit/plugin-paginate-rest": { "version": "9.1.5", @@ -1677,6 +1711,19 @@ "@octokit/core": ">=5" } }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, "node_modules/@octokit/plugin-request-log": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", @@ -1699,6 +1746,19 @@ "@octokit/core": ">=5" } }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, "node_modules/@octokit/plugin-retry": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-3.0.9.tgz", @@ -1748,12 +1808,30 @@ "node": ">= 18" } }, - "node_modules/@octokit/types": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.4.0.tgz", - "integrity": "sha512-FLWs/AvZllw/AGVs+nJ+ELCDZZJk+kY0zMen118xhL2zD0s1etIUHm1odgjP7epxYU1ln7SZxEUWYop5bhsdgQ==", + "node_modules/@octokit/request-error/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/request-error/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", + "dependencies": { + "@octokit/openapi-types": "^20.0.0" + } + }, + "node_modules/@octokit/request/node_modules/@octokit/openapi-types": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-20.0.0.tgz", + "integrity": "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA==" + }, + "node_modules/@octokit/request/node_modules/@octokit/types": { + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.6.0.tgz", + "integrity": "sha512-1rhSOfRa6H9w4YwK0yrf5faDaDTb+yLyBUKOCV4xtCDB5VmIPqd/v9yr9o6SAzOAlRxMiRiCic6JVM1/kunVkw==", "dependencies": { - "@octokit/openapi-types": "^19.1.0" + "@octokit/openapi-types": "^20.0.0" } }, "node_modules/@opentelemetry/api": { diff --git a/package.json b/package.json index 2e9212f..9c15c56 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "scanoss-code-scan-action", "description": "SCANOSS Code Scan Action", - "version": "0.2.0", + "version": "0.2.1", "author": "SCANOSS", "private": true, "homepage": "https://github.com/scanoss/code-scan-action/", diff --git a/src/main.ts b/src/main.ts index aa6794e..49c0a99 100644 --- a/src/main.ts +++ b/src/main.ts @@ -21,7 +21,7 @@ THE SOFTWARE. */ -import { createCommentOnPR, isPullRequest } from './utils/github.utils'; +import { createCommentOnPR, isPullRequest, getFirstRunId } from './utils/github.utils'; import { generateJobSummary, generatePRSummary } from './services/report.service'; import * as core from '@actions/core'; import * as inputs from './app.input'; @@ -40,11 +40,12 @@ export async function run(): Promise { // create policies core.debug(`Creating policies`); + const firstRunId = await getFirstRunId(); //Read declared policies on input parameter 'policies' and create an instance for each one. const policies = policyManager.getPolicies(); for (const policy of policies) { - await policy.start(); + await policy.start(firstRunId); } // run scan diff --git a/src/policies/copyleft-policy-check.ts b/src/policies/copyleft-policy-check.ts index b9ccefd..f35d6d4 100644 --- a/src/policies/copyleft-policy-check.ts +++ b/src/policies/copyleft-policy-check.ts @@ -81,7 +81,7 @@ export class CopyleftPolicyCheck extends PolicyCheck { if (details) { const { id } = await this.uploadArtifact(details); - if (id) details = this.concatPolicyArtifactURLToPolicyCheck(details, id); + if (id) details = await this.concatPolicyArtifactURLToPolicyCheck(details, id); } if (componentsWithCopyleft.length === 0) { diff --git a/src/policies/policy-check.ts b/src/policies/policy-check.ts index d3908af..9b6c21b 100644 --- a/src/policies/policy-check.ts +++ b/src/policies/policy-check.ts @@ -64,6 +64,8 @@ export abstract class PolicyCheck { private _conclusion: CONCLUSION; + private _firstRunId = -1; + constructor(checkName: string) { this.octokit = getOctokit(inputs.GITHUB_TOKEN); this.checkName = checkName; @@ -76,7 +78,7 @@ export abstract class PolicyCheck { abstract getPolicyName(): string; - async start(): Promise { + async start(runId: number): Promise { const result = await this.octokit.rest.checks.create({ owner: context.repo.owner, repo: context.repo.repo, @@ -87,6 +89,8 @@ export abstract class PolicyCheck { this.checkRunId = result.data.id; this._raw = result.data; + this._firstRunId = runId; + this._status = STATUS.INITIALIZED; return result.data; } @@ -104,7 +108,7 @@ export abstract class PolicyCheck { } get url(): string { - return `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/job/${this.raw.id}`; + return `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${this._firstRunId}/job/${this.raw.id}`; } async run(scannerResults: ScannerResults): Promise { @@ -152,7 +156,7 @@ export abstract class PolicyCheck { return text.length > this.MAX_GH_API_CONTENT_SIZE; } - protected concatPolicyArtifactURLToPolicyCheck(details: string, artifactId: number): string { + protected async concatPolicyArtifactURLToPolicyCheck(details: string, artifactId: number): Promise { const link = `\n\nDownload the ` + `[${this.getPolicyName()} Result](${context.serverUrl}/` + diff --git a/src/policies/undeclared-policy-check.ts b/src/policies/undeclared-policy-check.ts index 43b6152..9ed8afd 100644 --- a/src/policies/undeclared-policy-check.ts +++ b/src/policies/undeclared-policy-check.ts @@ -70,7 +70,7 @@ export class UndeclaredPolicyCheck extends PolicyCheck { if (details) { const { id } = await this.uploadArtifact(details); - if (id) details = this.concatPolicyArtifactURLToPolicyCheck(details, id); + if (id) details = await this.concatPolicyArtifactURLToPolicyCheck(details, id); } if (nonDeclaredComponents.length === 0) { diff --git a/src/utils/github.utils.ts b/src/utils/github.utils.ts index fb61cd0..de71368 100644 --- a/src/utils/github.utils.ts +++ b/src/utils/github.utils.ts @@ -26,6 +26,7 @@ import * as core from '@actions/core'; import * as inputs from '../app.input'; const prEvents = ['pull_request', 'pull_request_review', 'pull_request_review_comment']; +const FIND_FIRST_RUN_EVENT = 'workflow_dispatch'; export function isPullRequest(): boolean { return prEvents.includes(context.eventName); @@ -54,3 +55,43 @@ export async function createCommentOnPR(message: string): Promise { body: message }); } + +export async function getFirstRunId(): Promise { + let firstRunId = context.runId; + if (context.eventName === FIND_FIRST_RUN_EVENT) { + const firstRun = await loadFirstRun(context.repo.owner, context.repo.repo); + if (firstRun) { + core.info(`First Run ID found: ${firstRun.id}`); + firstRunId = firstRun.id; + } + } + return firstRunId; +} + +async function loadFirstRun(owner: string, repo: string): Promise { + const octokit = getOctokit(inputs.GITHUB_TOKEN); + const sha = getSHA(); + + const workflowRun = await octokit.rest.actions.getWorkflowRun({ + owner, + repo, + run_id: context.runId + }); + + const runs = await octokit.rest.actions.listWorkflowRuns({ + owner, + repo, + head_sha: sha, + workflow_id: workflowRun.data.workflow_id + }); + + // Filter by the given SHA + const filteredRuns = runs.data.workflow_runs.filter(run => run.head_sha === sha); + + // Sort by creation date to find the first run + const sortedRuns = filteredRuns.sort((a, b) => + a.created_at && b.created_at ? new Date(a.created_at).getTime() - new Date(b.created_at).getTime() : 0 + ); + + return sortedRuns.length ? sortedRuns[0] : null; +}