Skip to content

Commit dfa1bdd

Browse files
authored
Merge branch 'main' into oas-soft-check
2 parents 540d565 + fe2b6ef commit dfa1bdd

63 files changed

Lines changed: 2381 additions & 280 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.buildkite/pipelines/artifacts.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,5 @@ steps:
157157
imageProject: elastic-images-prod
158158
provider: gcp
159159
machineType: n2-standard-2
160+
diskSizeGb: 180
160161
timeout_in_minutes: 30

.buildkite/scripts/steps/checks/api_contracts.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,17 @@ if [[ -n "${GITHUB_PR_MERGE_BASE:-}" ]]; then
1616
MERGE_BASE_ARGS=(--mergeBase "$GITHUB_PR_MERGE_BASE")
1717
fi
1818

19+
REPORT_DIR="packages/kbn-api-contracts/target/reports"
20+
STACK_REPORT="$REPORT_DIR/stack-impact.json"
21+
SERVERLESS_REPORT="$REPORT_DIR/serverless-impact.json"
22+
rm -f "$STACK_REPORT" "$SERVERLESS_REPORT"
23+
1924
echo "Checking stack API contracts..."
2025
node scripts/check_api_contracts.js \
2126
--distribution stack \
2227
--specPath oas_docs/output/kibana.yaml \
2328
--baseBranch "$BASE_BRANCH" \
29+
--reportPath "$STACK_REPORT" \
2430
"${MERGE_BASE_ARGS[@]+"${MERGE_BASE_ARGS[@]}"}" &
2531
STACK_PID=$!
2632

@@ -29,6 +35,7 @@ node scripts/check_api_contracts.js \
2935
--distribution serverless \
3036
--specPath oas_docs/output/kibana.serverless.yaml \
3137
--baseBranch "$BASE_BRANCH" \
38+
--reportPath "$SERVERLESS_REPORT" \
3239
"${MERGE_BASE_ARGS[@]+"${MERGE_BASE_ARGS[@]}"}" &
3340
SERVERLESS_PID=$!
3441

@@ -38,5 +45,10 @@ wait $STACK_PID || STACK_EXIT=$?
3845
wait $SERVERLESS_PID || SERVERLESS_EXIT=$?
3946

4047
if [ $STACK_EXIT -ne 0 ] || [ $SERVERLESS_EXIT -ne 0 ]; then
48+
echo --- Notify API owners
49+
if [[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" ]]; then
50+
ts-node .buildkite/scripts/steps/checks/notify_api_contract_owners.ts \
51+
"$STACK_REPORT" "$SERVERLESS_REPORT" || echo "Warning: failed to post PR notification"
52+
fi
4153
exit 1
4254
fi
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
jest.mock('#pipeline-utils', () => ({
11+
upsertComment: jest.fn(),
12+
}));
13+
14+
import { buildCommentBody, type ImpactEntry } from './notify_api_contract_owners';
15+
16+
const entry = (overrides: Partial<ImpactEntry> = {}): ImpactEntry => ({
17+
path: '/api/spaces/space',
18+
method: 'GET',
19+
reason: 'Endpoint removed',
20+
terraformResource: 'elasticstack_kibana_space',
21+
owners: ['@elastic/kibana-security'],
22+
...overrides,
23+
});
24+
25+
describe('buildCommentBody', () => {
26+
it('renders a markdown table with one entry', () => {
27+
const body = buildCommentBody([entry()]);
28+
29+
expect(body).toContain('## API Contract Breaking Changes');
30+
expect(body).toContain('| `/api/spaces/space` `GET`');
31+
expect(body).toContain('elasticstack_kibana_space');
32+
expect(body).toContain('@elastic/kibana-security');
33+
});
34+
35+
it('deduplicates owners in the cc line', () => {
36+
const entries = [
37+
entry({ owners: ['@elastic/kibana-security'] }),
38+
entry({ path: '/api/spaces/space/{id}', owners: ['@elastic/kibana-security'] }),
39+
];
40+
const body = buildCommentBody(entries);
41+
42+
const ccLine = body.split('\n').find((l) => l.startsWith('cc '))!;
43+
const mentions = ccLine.replace('cc ', '').trim().split(' ');
44+
expect(mentions).toEqual(['@elastic/kibana-security']);
45+
});
46+
47+
it('aggregates multiple distinct owners', () => {
48+
const entries = [
49+
entry({ owners: ['@elastic/kibana-security'] }),
50+
entry({
51+
path: '/api/fleet/agent_policies',
52+
method: 'POST',
53+
terraformResource: 'elasticstack_fleet_agent_policy',
54+
owners: ['@elastic/fleet'],
55+
}),
56+
];
57+
const body = buildCommentBody(entries);
58+
59+
expect(body).toContain('@elastic/kibana-security');
60+
expect(body).toContain('@elastic/fleet');
61+
});
62+
63+
it('shows _unknown_ when no owners exist', () => {
64+
const body = buildCommentBody([entry({ owners: [] })]);
65+
66+
expect(body).toContain('cc _unknown_');
67+
});
68+
69+
it('escapes pipe characters in the reason field', () => {
70+
const body = buildCommentBody([entry({ reason: 'field|was|removed' })]);
71+
72+
expect(body).toContain('field\\|was\\|removed');
73+
expect(body).not.toContain('field|was|removed');
74+
});
75+
76+
it('escapes newlines in the reason field', () => {
77+
const body = buildCommentBody([entry({ reason: 'line1\nline2' })]);
78+
79+
expect(body).toContain('line1 line2');
80+
expect(body).not.toContain('line1\nline2');
81+
});
82+
83+
it('omits method badge when method is undefined', () => {
84+
const body = buildCommentBody([entry({ method: undefined })]);
85+
86+
expect(body).toContain('| `/api/spaces/space` |');
87+
expect(body).not.toMatch(/`GET`|`POST`|`PUT`|`DELETE`/);
88+
});
89+
});
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
import { readFileSync, existsSync } from 'fs';
11+
import { upsertComment } from '#pipeline-utils';
12+
13+
export interface ImpactEntry {
14+
path: string;
15+
method?: string;
16+
reason: string;
17+
terraformResource: string;
18+
owners: string[];
19+
}
20+
21+
interface ImpactReport {
22+
impactedChanges: ImpactEntry[];
23+
}
24+
25+
const COMMENT_CONTEXT = 'api-contracts-tf-breaking';
26+
27+
const ALLOWLIST_PATH = 'packages/kbn-api-contracts/allowlist.json';
28+
const README_PATH = 'packages/kbn-api-contracts/README.md';
29+
30+
export const buildCommentBody = (entries: ImpactEntry[]): string => {
31+
const allOwners = [...new Set(entries.flatMap((e) => e.owners || []))];
32+
const ownerMentions = allOwners.length > 0 ? allOwners.join(' ') : '_unknown_';
33+
34+
const escapeCell = (text: string): string => text.replace(/\|/g, '\\|').replace(/\n/g, ' ');
35+
36+
const rows = entries
37+
.map((e) => {
38+
const method = e.method ? ` \`${e.method.toUpperCase()}\`` : '';
39+
return `| \`${e.path}\`${method} | ${escapeCell(e.terraformResource)} | ${escapeCell(
40+
e.reason
41+
)} | ${(e.owners || []).join(', ')} |`;
42+
})
43+
.join('\n');
44+
45+
return `## API Contract Breaking Changes — Terraform Provider Impact
46+
47+
cc ${ownerMentions}
48+
49+
The following breaking change(s) affect APIs consumed by the [Elastic Terraform Provider](https://github.com/elastic/terraform-provider-elasticstack).
50+
51+
| Endpoint | Terraform Resource | Reason | Owners |
52+
|----------|--------------------|--------|--------|
53+
${rows}
54+
55+
### What to do
56+
57+
1. **Fix the breaking change** if it was unintentional.
58+
2. **If intentional**, add an approved entry to [\`${ALLOWLIST_PATH}\`](https://github.com/elastic/kibana/blob/main/${ALLOWLIST_PATH}) and coordinate with \`@elastic/terraform-provider\`.
59+
60+
See the [\`@kbn/api-contracts\` README](https://github.com/elastic/kibana/blob/main/${README_PATH}) for details on the allowlist schema and workflow.`;
61+
};
62+
63+
async function main() {
64+
const reportPaths = process.argv.slice(2);
65+
66+
const allEntries: ImpactEntry[] = [];
67+
for (const reportPath of reportPaths) {
68+
if (!existsSync(reportPath)) {
69+
continue;
70+
}
71+
try {
72+
const report: ImpactReport = JSON.parse(readFileSync(reportPath, 'utf-8'));
73+
if (!Array.isArray(report.impactedChanges)) {
74+
console.error(`Report at ${reportPath} has no impactedChanges array, skipping`);
75+
continue;
76+
}
77+
allEntries.push(...report.impactedChanges);
78+
} catch {
79+
console.error(`Failed to parse report at ${reportPath}, skipping`);
80+
}
81+
}
82+
83+
if (allEntries.length === 0) {
84+
console.log('No TF-impacting breaking changes to report');
85+
return;
86+
}
87+
88+
const deduped = Array.from(
89+
new Map(allEntries.map((e) => [`${e.path}::${e.method ?? ''}`, e])).values()
90+
);
91+
92+
const body = buildCommentBody(deduped);
93+
console.log('Posting PR comment notifying API owners...');
94+
95+
await upsertComment({
96+
commentBody: body,
97+
commentContext: COMMENT_CONTEXT,
98+
clearPrevious: true,
99+
});
100+
101+
console.log('PR comment posted successfully');
102+
}
103+
104+
if (require.main === module) {
105+
main().catch((error) => {
106+
console.error('Failed to post API contract notification:', error);
107+
process.exit(1);
108+
});
109+
}

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2715,6 +2715,7 @@ x-pack/solutions/security/test/security_solution_api_integration/test_suites/sie
27152715

27162716
/x-pack/solutions/security/test/serverless/functional/test_suites/ftr/discover @elastic/security-threat-hunting
27172717
x-pack/solutions/security/test/serverless/functional/configs/config.context_awareness.ts @elastic/security-threat-hunting
2718+
/x-pack/solutions/security/plugins/security_solution/common/endpoint/schema/resolver.ts @elastic/security-threat-hunting
27182719

27192720
## Security Solution Threat Hunting areas - Threat Hunting Investigations
27202721

0 commit comments

Comments
 (0)