Skip to content

Commit

Permalink
[8.16] [Security Solution][Serverless] Github tickets / notifications (
Browse files Browse the repository at this point in the history
…elastic#197265) (elastic#199313)

# Backport

This will backport the following commits from `main` to `8.16`:
- [[Security Solution][Serverless] Github tickets / notifications
(elastic#197265)](elastic#197265)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT
[{"author":{"name":"dkirchan","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-07T13:17:54Z","message":"[Security
Solution][Serverless] Github tickets / notifications (elastic#197265)\n\n##
Summary\r\n\r\nThis PR is accompanied with the [PR in
kibana-operations\r\nrepo](elastic/kibana-operations#236)
and\r\nintegrates the security solution quality gate pipelines with
the\r\nfunctionality to create ticket when a test fails and then notify
the\r\nrespective **codeowner** team with a _Test failed alert_. Also
when a\r\ntest fails a second time and a ticket exist a new comment is
added in\r\nthe same issue that the test failed again in a given
pipeline.\r\n\r\nLast a similar flow exist when a test is skipped by a
team. \r\n\r\n**Specifications:**\r\n\r\n- In the
`failed_test_reporter_cli.ts` [a new field is
introduced\r\n](https://github.com/elastic/kibana/compare/main...security-mki-github-tickets#diff-bb801c18fd2e1a3a36a3b39fbf02c1abe337c46c201ad5a01239a9d2501d4b56R47)`prependTitle`\r\nwhich
is initialized by the environmental
variable\r\n`process.env.PREPEND_FAILURE_TITLE` if exists, in order to
add a custom\r\ntext of preference before the issue title.\r\n\r\nThe
scope of this is to be able to give the team the opportunity to
add\r\nsome tags or any convention agreed within the team in the issue
created\r\nin order to easily understand the context without opening the
ticket. If\r\nthis field is not initialized, the normal existing flow
proceeds.\r\n[Here](elastic#197133) an
example of\r\nthe prependTitle usage can be seen where the tags
`[MKI][QA]` are\r\nprepended in order to identify where did the test
fail, having the same\r\nexactly tests running on both CI and MKI. This
means that a github issue\r\nwith the exact same title would be created
for both cases if this\r\nprepend title field would not exist.\r\n\r\n-
In
the\r\n[junit_transformer/lib.ts](https://github.com/elastic/kibana/compare/main...security-mki-github-tickets#diff-31c5651af613c7d02139f3e9fccd00ddb997f2502523372dd19db9e0659a66d6R278)\r\na
new functionality is introduced to cover an existing issue.
The\r\nexisting issue is: the fact that we are retrying the failed test
in\r\ncypress in the `parallel_serverless` script, leads to two junit
xml\r\nresult files completely identical with only difference the
execution\r\ntime and the timestamps. This change will take one by one
the xml\r\noutputs, transform them exactly as it was happening before
and then save\r\nthe file, but also remove the time and timestamp
fields, convert it to a\r\nstring and add it to a \"state\" list. Then
in a next file if it is\r\nalready parsed and saved to the list having
the exact same results\r\nwithin, it deletes the file instead of saving
it transformed.\r\n\r\nThe problem before this fix was the fact that
when two xml outputs\r\nexisted, a github ticket was created and when
parsing-uploading the\r\nsecond file it was immediately failing for a
second time as well. This\r\nmeans that `new-failure` notification was
never triggered after the\r\ngithub actions flow split, and the existing
failure was always\r\ntriggered, something that most teams have
disabled.\r\n\r\nWith the fix, the identical files are parsed but only
once uploaded and\r\nthe new failure flow works
again.","sha":"9353497d0adea457c13643bc8fa1a26a05422e8f","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:
SecuritySolution","backport:prev-minor","v8.16.0","v8.17.0"],"title":"[Security
Solution][Serverless] Github tickets /
notifications","number":197265,"url":"https://github.com/elastic/kibana/pull/197265","mergeCommit":{"message":"[Security
Solution][Serverless] Github tickets / notifications (elastic#197265)\n\n##
Summary\r\n\r\nThis PR is accompanied with the [PR in
kibana-operations\r\nrepo](elastic/kibana-operations#236)
and\r\nintegrates the security solution quality gate pipelines with
the\r\nfunctionality to create ticket when a test fails and then notify
the\r\nrespective **codeowner** team with a _Test failed alert_. Also
when a\r\ntest fails a second time and a ticket exist a new comment is
added in\r\nthe same issue that the test failed again in a given
pipeline.\r\n\r\nLast a similar flow exist when a test is skipped by a
team. \r\n\r\n**Specifications:**\r\n\r\n- In the
`failed_test_reporter_cli.ts` [a new field is
introduced\r\n](https://github.com/elastic/kibana/compare/main...security-mki-github-tickets#diff-bb801c18fd2e1a3a36a3b39fbf02c1abe337c46c201ad5a01239a9d2501d4b56R47)`prependTitle`\r\nwhich
is initialized by the environmental
variable\r\n`process.env.PREPEND_FAILURE_TITLE` if exists, in order to
add a custom\r\ntext of preference before the issue title.\r\n\r\nThe
scope of this is to be able to give the team the opportunity to
add\r\nsome tags or any convention agreed within the team in the issue
created\r\nin order to easily understand the context without opening the
ticket. If\r\nthis field is not initialized, the normal existing flow
proceeds.\r\n[Here](elastic#197133) an
example of\r\nthe prependTitle usage can be seen where the tags
`[MKI][QA]` are\r\nprepended in order to identify where did the test
fail, having the same\r\nexactly tests running on both CI and MKI. This
means that a github issue\r\nwith the exact same title would be created
for both cases if this\r\nprepend title field would not exist.\r\n\r\n-
In
the\r\n[junit_transformer/lib.ts](https://github.com/elastic/kibana/compare/main...security-mki-github-tickets#diff-31c5651af613c7d02139f3e9fccd00ddb997f2502523372dd19db9e0659a66d6R278)\r\na
new functionality is introduced to cover an existing issue.
The\r\nexisting issue is: the fact that we are retrying the failed test
in\r\ncypress in the `parallel_serverless` script, leads to two junit
xml\r\nresult files completely identical with only difference the
execution\r\ntime and the timestamps. This change will take one by one
the xml\r\noutputs, transform them exactly as it was happening before
and then save\r\nthe file, but also remove the time and timestamp
fields, convert it to a\r\nstring and add it to a \"state\" list. Then
in a next file if it is\r\nalready parsed and saved to the list having
the exact same results\r\nwithin, it deletes the file instead of saving
it transformed.\r\n\r\nThe problem before this fix was the fact that
when two xml outputs\r\nexisted, a github ticket was created and when
parsing-uploading the\r\nsecond file it was immediately failing for a
second time as well. This\r\nmeans that `new-failure` notification was
never triggered after the\r\ngithub actions flow split, and the existing
failure was always\r\ntriggered, something that most teams have
disabled.\r\n\r\nWith the fix, the identical files are parsed but only
once uploaded and\r\nthe new failure flow works
again.","sha":"9353497d0adea457c13643bc8fa1a26a05422e8f"}},"sourceBranch":"main","suggestedTargetBranches":["8.16","8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/197265","number":197265,"mergeCommit":{"message":"[Security
Solution][Serverless] Github tickets / notifications (elastic#197265)\n\n##
Summary\r\n\r\nThis PR is accompanied with the [PR in
kibana-operations\r\nrepo](elastic/kibana-operations#236)
and\r\nintegrates the security solution quality gate pipelines with
the\r\nfunctionality to create ticket when a test fails and then notify
the\r\nrespective **codeowner** team with a _Test failed alert_. Also
when a\r\ntest fails a second time and a ticket exist a new comment is
added in\r\nthe same issue that the test failed again in a given
pipeline.\r\n\r\nLast a similar flow exist when a test is skipped by a
team. \r\n\r\n**Specifications:**\r\n\r\n- In the
`failed_test_reporter_cli.ts` [a new field is
introduced\r\n](https://github.com/elastic/kibana/compare/main...security-mki-github-tickets#diff-bb801c18fd2e1a3a36a3b39fbf02c1abe337c46c201ad5a01239a9d2501d4b56R47)`prependTitle`\r\nwhich
is initialized by the environmental
variable\r\n`process.env.PREPEND_FAILURE_TITLE` if exists, in order to
add a custom\r\ntext of preference before the issue title.\r\n\r\nThe
scope of this is to be able to give the team the opportunity to
add\r\nsome tags or any convention agreed within the team in the issue
created\r\nin order to easily understand the context without opening the
ticket. If\r\nthis field is not initialized, the normal existing flow
proceeds.\r\n[Here](elastic#197133) an
example of\r\nthe prependTitle usage can be seen where the tags
`[MKI][QA]` are\r\nprepended in order to identify where did the test
fail, having the same\r\nexactly tests running on both CI and MKI. This
means that a github issue\r\nwith the exact same title would be created
for both cases if this\r\nprepend title field would not exist.\r\n\r\n-
In
the\r\n[junit_transformer/lib.ts](https://github.com/elastic/kibana/compare/main...security-mki-github-tickets#diff-31c5651af613c7d02139f3e9fccd00ddb997f2502523372dd19db9e0659a66d6R278)\r\na
new functionality is introduced to cover an existing issue.
The\r\nexisting issue is: the fact that we are retrying the failed test
in\r\ncypress in the `parallel_serverless` script, leads to two junit
xml\r\nresult files completely identical with only difference the
execution\r\ntime and the timestamps. This change will take one by one
the xml\r\noutputs, transform them exactly as it was happening before
and then save\r\nthe file, but also remove the time and timestamp
fields, convert it to a\r\nstring and add it to a \"state\" list. Then
in a next file if it is\r\nalready parsed and saved to the list having
the exact same results\r\nwithin, it deletes the file instead of saving
it transformed.\r\n\r\nThe problem before this fix was the fact that
when two xml outputs\r\nexisted, a github ticket was created and when
parsing-uploading the\r\nsecond file it was immediately failing for a
second time as well. This\r\nmeans that `new-failure` notification was
never triggered after the\r\ngithub actions flow split, and the existing
failure was always\r\ntriggered, something that most teams have
disabled.\r\n\r\nWith the fix, the identical files are parsed but only
once uploaded and\r\nthe new failure flow works
again.","sha":"9353497d0adea457c13643bc8fa1a26a05422e8f"}},{"branch":"8.16","label":"v8.16.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: dkirchan <[email protected]>
  • Loading branch information
kibanamachine and dkirchan authored Nov 7, 2024
1 parent 8fe43ee commit 1bcd05c
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ run(

let branch: string = '';
let pipeline: string = '';
let prependTitle: string = '';
if (updateGithub) {
let isPr = false;

Expand All @@ -52,6 +53,7 @@ run(
pipeline = process.env.BUILDKITE_PIPELINE_SLUG || '';
isPr = process.env.BUILDKITE_PULL_REQUEST === 'true';
updateGithub = process.env.REPORT_FAILED_TESTS_TO_GITHUB === 'true';
prependTitle = process.env.PREPEND_FAILURE_TITLE || '';
} else {
// JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others
const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//);
Expand Down Expand Up @@ -154,7 +156,14 @@ run(
continue;
}

const newIssue = await createFailureIssue(buildUrl, failure, githubApi, branch, pipeline);
const newIssue = await createFailureIssue(
buildUrl,
failure,
githubApi,
branch,
pipeline,
prependTitle
);
existingIssues.addNewlyCreated(failure, newIssue);
pushMessage('Test has not failed recently on tracked branches');
if (updateGithub) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,53 @@ describe('createFailureIssue()', () => {
}
`);
});

it('creates new github issue with title prepended', async () => {
const api = new GithubApi();

await createFailureIssue(
'https://build-url',
{
classname: 'some.classname',
failure: 'this is the failure text',
name: 'test name',
time: '2018-01-01T01:00:00Z',
likelyIrrelevant: false,
},
api,
'main',
'kibana-on-merge',
'[MKI][QA]'
);

expect(api.createIssue).toMatchInlineSnapshot(`
[MockFunction] {
"calls": Array [
Array [
"Failing test: [MKI][QA] some.classname - test name",
"A test failed on a tracked branch
\`\`\`
this is the failure text
\`\`\`
First failure: [kibana-on-merge - main](https://build-url)
<!-- kibanaCiData = {\\"failed-test\\":{\\"test.class\\":\\"some.classname\\",\\"test.name\\":\\"test name\\",\\"test.failCount\\":1}} -->",
Array [
"failed-test",
],
],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
`);
});
});

describe('updateFailureIssue()', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ export async function createFailureIssue(
failure: TestFailure,
api: GithubApi,
branch: string,
pipeline: string
pipeline: string,
prependTitle: string = ''
) {
const title = `Failing test: ${failure.classname} - ${failure.name}`;
// PrependTitle is introduced to provide some clarity by prepending the failing test title
// in order to give the whole info in the title according to each team's preference.
const title =
prependTitle && prependTitle.trim() !== ''
? `Failing test: ${prependTitle} ${failure.classname} - ${failure.name}`
: `Failing test: ${failure.classname} - ${failure.name}`;

// Github API body length maximum is 65536 characters
// Let's keep consistency with Mocha output that is truncated to 8192 characters
Expand Down
38 changes: 38 additions & 0 deletions x-pack/plugins/security_solution/scripts/junit_transformer/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ import { PathReporter } from 'io-ts/lib/PathReporter';
import globby from 'globby';
import del from 'del';

// Function to remove specific fields from an XML object in order to
// compare them as strings.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function removeFields(obj: any, fieldsToRemove: string[]): any {
for (const key in obj) {
if (fieldsToRemove.includes(key)) {
delete obj[key];
} else if (typeof obj[key] === 'object') {
obj[key] = removeFields(obj[key], fieldsToRemove); // Recursively remove fields
}
}
return obj;
}

/**
* Updates the `name` and `classname` attributes of each testcase.
* `name` will have the value of `classname` appended to it. This makes sense because they each contain part of the bdd spec.
Expand Down Expand Up @@ -174,6 +188,10 @@ export async function command({ flags, log }: CommandArgs) {
throw createFlagError('please provide a single --reportName flag');
}

// Going to be used in order to store results as string in order to compare
// and remove duplicated reports.
const xmlResultFiles: string[] = [];

for (const path of await globby(flags.pathPattern)) {
// Read the file
const source: string = await fs.readFile(path, 'utf8');
Expand Down Expand Up @@ -242,6 +260,26 @@ ${boilerplate}
rootDirectory: flags.rootDirectory,
});

// We need to check if a XML Junit report is duplicate
// Only if we remove the time and timestamp and the rest of the
// report as a string is completely identical.
const fieldsToRemove = ['time', 'timestamp'];
const tempReport = await parseStringPromise(reportString);
const truncatedReport = removeFields(tempReport, fieldsToRemove);

// Rebuild the XML to compare (optional, if you want to compare XML strings)
const builder = new Builder();
const rebuildComparableReport = builder.buildObject(truncatedReport);

// Compare the cleaned and rebuilt XML objects or strings
if (xmlResultFiles.includes(rebuildComparableReport)) {
// If the report is a duplicate, we need to remove the file
// in order to be excluded from the uploaded results.
await del(path, { force: true });
continue;
}
xmlResultFiles.push(rebuildComparableReport);

// If the writeInPlace flag was passed, overwrite the original file, otherwise log the output to stdout
if (flags.writeInPlace) {
log.info(`Wrote transformed junit report to ${path}`);
Expand Down

0 comments on commit 1bcd05c

Please sign in to comment.