Skip to content

Commit

Permalink
Handle outputs problem #3481
Browse files Browse the repository at this point in the history
- introduced output helper
- storing now outputs directly to
  file by own helper method
  instead of using core.setOutput(..)
  • Loading branch information
de-jcup committed Dec 13, 2024
1 parent 72972ab commit c5a0146
Show file tree
Hide file tree
Showing 7 changed files with 152 additions and 43 deletions.
17 changes: 14 additions & 3 deletions github-actions/scan/README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,19 @@ npm run test

==== Integration-Test
As a precondition to run the integration tests locally you have to
execute `01-start.sh $secHubServerVersion $sechubServerPortNr $pdsVersion $pdsPortN`
inside the integration test folder.

- execute `__test__/01-start.sh $secHubServerVersion $sechubServerPortNr $pdsVersion $pdsPortNr`
An example:

TIP: You can also start a SecHub server and a PDS (both in integration test mode) instead of using the `01-start` script.
[source,bash]
----
# Next lines will start a SecHub server of version 2.4.0 and a PDS with version 2.1.0
cd ./github-actions/scan/__test__/integrationtest
./01-start.sh 2.4.0 8443 2.1.0 8444
----

TIP: You can also start a SecHub server and a PDS from IDE (both in integration test mode) instead of using the `01-start` script.

After the script has been executed, you can execute integration tests multiple times via following command:

Expand Down Expand Up @@ -176,7 +185,9 @@ In this setup the tests can be executed from sidebar and from links created insi

[TIP]
====
Unfortunately, the Jest UI integration works only for npm script "test". But to handle integration tests different (the tests shall only be executed when all is build and servers are started) they are not executed by "test" script.
Unfortunately, the Jest UI integration works only for npm script "test".
But to handle integration tests different (the tests shall only be executed
when all is build and servers are started) they are not executed by "test" script.
If you want to **debug an integration test**, there is a temporary workaround necessary while you debug the test:
Expand Down
46 changes: 46 additions & 0 deletions github-actions/scan/__test__/output-helper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
import * as outputHelper from '../src/output-helper';

import * as fs from 'fs';
import * as path from 'path';

describe('storeOutput', () => {
const outputPath = path.join(__dirname, 'test_output.txt');

beforeAll(() => {
process.env.GITHUB_OUTPUT = outputPath;
});

afterEach(() => {
if (fs.existsSync(outputPath)) {
fs.unlinkSync(outputPath);
}
});

it('should append a line with key=value to the file', () => {
/* execute */
outputHelper.storeOutput('TEST_KEY', 'TEST_VALUE');

/* test */
const content = fs.readFileSync(outputPath, 'utf8');
expect(content).toBe('TEST_KEY=TEST_VALUE\n');
});

it('should append multiple lines correctly', () => {
/* execute */
outputHelper.storeOutput('KEY1', 'VALUE1');
outputHelper.storeOutput('KEY2', 'VALUE2');

/* test */
const content = fs.readFileSync(outputPath, 'utf8');
expect(content).toBe('KEY1=VALUE1\nKEY2=VALUE2\n');
});

it('should throw an error if GITHUB_OUTPUT is not set', () => {
/* prepare */
delete process.env.GITHUB_OUTPUT;

/* execute + test */
expect(() => outputHelper.storeOutput('KEY', 'VALUE')).toThrow('GITHUB_OUTPUT environment variable is not set');
});
});
67 changes: 35 additions & 32 deletions github-actions/scan/__test__/post-scan.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
// SPDX-License-Identifier: MIT

import * as core from '@actions/core';
import * as outputHelper from '../src/output-helper';
import { collectReportData, reportOutputs } from '../src/post-scan';
import { getReport } from '../src/sechub-cli';
import { LAUNCHER_CONTEXT_DEFAULTS } from '../src/launcher';

jest.mock('@actions/core');
jest.mock('../src/output-helper');
const mockedCore = core as jest.Mocked<typeof core>;
const mockedOutputHelper = outputHelper as jest.Mocked<typeof outputHelper>;

jest.mock('../src/sechub-cli');
const mockedGetReport = getReport as jest.MockedFunction<typeof getReport>;
Expand All @@ -20,7 +23,7 @@ describe('collectReportData', function () {

/* prepare */
const testContext = Object.create(LAUNCHER_CONTEXT_DEFAULTS);
testContext.reportFormats= [];
testContext.reportFormats = [];

/* execute */
collectReportData(testContext);
Expand All @@ -33,7 +36,7 @@ describe('collectReportData', function () {
it('format "json" - logs called 1 time , getReport never called', function () {
/* prepare */
const testContext = Object.create(LAUNCHER_CONTEXT_DEFAULTS);
testContext.reportFormats= ['json'];
testContext.reportFormats = ['json'];

/* execute */
collectReportData(testContext);
Expand All @@ -47,8 +50,8 @@ describe('collectReportData', function () {

/* prepare */
const testContext = Object.create(LAUNCHER_CONTEXT_DEFAULTS);
testContext.reportFormats= ['json','html'];
testContext.jobUUID=1234; // necessary for download
testContext.reportFormats = ['json', 'html'];
testContext.jobUUID = 1234; // necessary for download

collectReportData(testContext);

Expand All @@ -61,8 +64,8 @@ describe('collectReportData', function () {

/* prepare */
const testContext = Object.create(LAUNCHER_CONTEXT_DEFAULTS);
testContext.reportFormats= ['json','html'];
testContext.jobUUID=1234; // necessary for download
testContext.reportFormats = ['json', 'html'];
testContext.jobUUID = 1234; // necessary for download

/* execute */
collectReportData(testContext);
Expand All @@ -78,19 +81,19 @@ describe('collectReportData', function () {

/* prepare */
const testContext = Object.create(LAUNCHER_CONTEXT_DEFAULTS);
testContext.reportFormats= ['json','html','xyz','bla'];
testContext.jobUUID=1234; // necessary for download
testContext.reportFormats = ['json', 'html', 'xyz', 'bla'];
testContext.jobUUID = 1234; // necessary for download

fsMock.readFileSync = jest.fn(() => '{"test": "test"}'); // Mock an empty JSON report
const sampleJson = {'test': 'test'};
const sampleJson = { 'test': 'test' };

/* execute */
collectReportData(testContext);

/* test */
expect(mockedCore.info).toHaveBeenCalledTimes(4); // "json, html, xyz, bla" - 4 times logged (valid format check is not done here)
expect(mockedGetReport).toHaveBeenCalledTimes(3); // we fetch not json via getReport again (already done before), so only "html, xyz, bla" used

expect(testContext.secHubReportJsonObject).toEqual(sampleJson); // json object is available

});
Expand Down Expand Up @@ -136,13 +139,13 @@ describe('reportOutputs', function () {

/* test */
expect(mockedCore.debug).toHaveBeenCalledTimes(6);
expect(mockedCore.setOutput).toHaveBeenCalledTimes(6);
expect(mockedCore.setOutput).toBeCalledWith('scan-trafficlight', 'RED');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-count', '2');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-high', '1');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-medium', '0');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-low', '1');
expect(mockedCore.setOutput).toBeCalledWith('scan-readable-summary', 'SecHub reported traffic light color RED with 2 findings, categorized as follows: HIGH (1), LOW (1)');
expect(mockedOutputHelper.storeOutput).toHaveBeenCalledTimes(6);
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-trafficlight', 'RED');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-count', '2');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-high', '1');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-medium', '0');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-low', '1');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-readable-summary', 'SecHub reported traffic light color RED with 2 findings, categorized as follows: HIGH (1), LOW (1)');
});

it('calls set github output with correct values when JSON report did not exist', function () {
Expand All @@ -152,13 +155,13 @@ describe('reportOutputs', function () {
/* test */
expect(mockedCore.debug).toHaveBeenCalledTimes(7);
expect(mockedCore.debug).toBeCalledWith('No findings reported to be categorized.');
expect(mockedCore.setOutput).toHaveBeenCalledTimes(6);
expect(mockedCore.setOutput).toBeCalledWith('scan-trafficlight', 'FAILURE');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-count', '0');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-high', '0');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-medium', '0');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-low', '0');
expect(mockedCore.setOutput).toBeCalledWith('scan-readable-summary', 'SecHub scan could not be executed.');
expect(mockedOutputHelper.storeOutput).toHaveBeenCalledTimes(6);
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-trafficlight', 'FAILURE');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-count', '0');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-high', '0');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-medium', '0');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-low', '0');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-readable-summary', 'SecHub scan could not be executed.');
});

it('calls set github output with correct values when traffic light is green without findings.', function () {
Expand All @@ -180,13 +183,13 @@ describe('reportOutputs', function () {

/* test */
expect(mockedCore.debug).toHaveBeenCalledTimes(6);
expect(mockedCore.setOutput).toHaveBeenCalledTimes(6);
expect(mockedCore.setOutput).toBeCalledWith('scan-trafficlight', 'GREEN');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-count', '0');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-high', '0');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-medium', '0');
expect(mockedCore.setOutput).toBeCalledWith('scan-findings-low', '0');
expect(mockedCore.setOutput).toBeCalledWith('scan-readable-summary', 'SecHub reported traffic light color GREEN without findings');
expect(mockedOutputHelper.storeOutput).toHaveBeenCalledTimes(6);
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-trafficlight', 'GREEN');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-count', '0');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-high', '0');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-medium', '0');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-findings-low', '0');
expect(mockedOutputHelper.storeOutput).toBeCalledWith('scan-readable-summary', 'SecHub reported traffic light color GREEN without findings');
});
});

33 changes: 29 additions & 4 deletions github-actions/scan/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28403,6 +28403,7 @@ function getReport(jobUUID, reportFormat, context) {
}

;// CONCATENATED MODULE: ./src/json-helper.ts
// SPDX-License-Identifier: MIT

/**
* Reads the given field from JSON.
Expand All @@ -28428,6 +28429,27 @@ function getFieldFromJson(field, jsonData) {
return currentKey;
}

;// CONCATENATED MODULE: ./src/github-output.ts
// SPDX-License-Identifier: MIT

const NEW_LINE_SEPARATOR = '\n';
/**
* Sets the value of an output variable for the GitHub Action.
* This method is a replacement of usage of core.setOutput(..) method.
* There were problems with core.setOutput(...), see
* - https://github.com/mercedes-benz/sechub/issues/3481#issuecomment-2539015176 and
* - https://github.com/actions/toolkit/issues/1218
*
*/
function storeOutput(field, value) {
const filePath = process.env['GITHUB_OUTPUT'] || '';
if (filePath) {
}
else {
core.setOutput(field, value);
}
}

;// CONCATENATED MODULE: ./src/post-scan.ts
// SPDX-License-Identifier: MIT

Expand All @@ -28439,7 +28461,8 @@ function getFieldFromJson(field, jsonData) {



const NEW_LINE_SEPARATOR = '\n';

const post_scan_NEW_LINE_SEPARATOR = '\n';
/**
* Collect all necessary report data, downloads additional report formats (e.g. 'html') if necessary
*/
Expand Down Expand Up @@ -28513,7 +28536,7 @@ async function uploadArtifact(context, name, files) {
if (core.isDebug()) {
const filesInWorkspace = (0,external_child_process_.execFileSync)('ls', [rootDirectory], {
encoding: 'utf-8'
}).split(NEW_LINE_SEPARATOR);
}).split(post_scan_NEW_LINE_SEPARATOR);
for (const fileName of filesInWorkspace) {
core.debug(fileName);
}
Expand All @@ -28536,7 +28559,7 @@ function resolveReportNameForScanJob(context) {
const workspaceDir = sanitize(getWorkspaceDir());
const filesInWorkspace = (0,external_child_process_.execFileSync)('ls', [workspaceDir], {
encoding: 'utf-8'
}).split(NEW_LINE_SEPARATOR);
}).split(post_scan_NEW_LINE_SEPARATOR);
if (!context.jobUUID) {
core.error('Illegal state: No job uuid resolved - not allowed at this point');
return '';
Expand Down Expand Up @@ -28662,7 +28685,7 @@ function buildSummary(trafficLight, totalFindings, findings) {
function setOutput(field, value, dataFormat) {
value = value !== null && value !== void 0 ? value : (dataFormat === 'number' ? 0 : 'FAILURE');
core.debug(`Output ${field} set to ${value}`);
core.setOutput(field, value.toString()); // Ensure value is converted to a string as GitHub Actions expects output variables to be strings.
storeOutput(field, value.toString()); // Ensure value is converted to a string as GitHub Actions expects output variables to be strings.
}

;// CONCATENATED MODULE: ./src/projectname-resolver.ts
Expand Down Expand Up @@ -28702,6 +28725,7 @@ function projectname_resolver_asJsonObject(text) {
// EXTERNAL MODULE: external "os"
var external_os_ = __nccwpck_require__(2037);
;// CONCATENATED MODULE: ./src/platform-helper.ts
// SPDX-License-Identifier: MIT

function getPlatform() {
return external_os_.platform();
Expand Down Expand Up @@ -46017,6 +46041,7 @@ const { parseHTML: esm_parseHTML } = static_namespaceObject;
const { root: esm_root } = static_namespaceObject;
//# sourceMappingURL=index.js.map
;// CONCATENATED MODULE: ./src/client-version-helper.ts
// SPDX-License-Identifier: MIT



Expand Down
2 changes: 1 addition & 1 deletion github-actions/scan/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { handleError } from './action-helper';
main().catch(handleError);

async function main(): Promise<void> {
// Seperated launcher and main method.
// Separated launcher and main method.
// Reason: launch mechanism would be loaded on imports
// before we can handle mocking in integration tests!
await launch();
Expand Down
23 changes: 23 additions & 0 deletions github-actions/scan/src/output-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT

import * as fs from 'fs';

/**
* Sets the value of an output variable for the GitHub Action.
* This method is a replacement of usage of core.setOutput(..) method.
* There were problems with core.setOutput(...), see
* - https://github.com/mercedes-benz/sechub/issues/3481#issuecomment-2539015176 and
* - https://github.com/actions/toolkit/issues/1218
*
*/
export function storeOutput(field: string, value: string) {
const outputFilePath = process.env['GITHUB_OUTPUT'] || '';

if (!outputFilePath) {
throw new Error('GITHUB_OUTPUT environment variable is not set');
}

const outputLine = `${field}=${value}\n`;

fs.appendFileSync(outputFilePath, outputLine, { encoding: 'utf8' });
}
7 changes: 4 additions & 3 deletions github-actions/scan/src/post-scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { LaunchContext } from './launcher';
import { logExitCode } from './exitcode';
import { getReport } from './sechub-cli';
import { getFieldFromJson } from './json-helper';
import { execFileSync } from "child_process";
import { sanitize } from "./shell-arg-sanitizer";
import { execFileSync } from 'child_process';
import { sanitize } from './shell-arg-sanitizer';
import { storeOutput } from './output-helper';

const NEW_LINE_SEPARATOR = '\n';

Expand Down Expand Up @@ -280,5 +281,5 @@ function setOutput(field: string, value: any, dataFormat: string) {
value = value ?? (dataFormat === 'number' ? 0 : 'FAILURE');

core.debug(`Output ${field} set to ${value}`);
core.setOutput(field, value.toString()); // Ensure value is converted to a string as GitHub Actions expects output variables to be strings.
storeOutput(field, value.toString()); // Ensure value is converted to a string as GitHub Actions expects output variables to be strings.
}

0 comments on commit c5a0146

Please sign in to comment.