Skip to content

Commit 440329b

Browse files
committed
Merge branch 'trunk' into KIP-1052-PR
2 parents fb8a4ca + 84ab3b9 commit 440329b

File tree

696 files changed

+17172
-12654
lines changed

Some content is hidden

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

696 files changed

+17172
-12654
lines changed

.github/actions/gh-api-approve-run/action.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,13 @@ runs:
4343
shell: bash
4444
env:
4545
GH_TOKEN: ${{ inputs.gh-token }}
46+
REPO: ${{ inputs.repository }}
47+
RUN_ID: ${{ inputs.run_id }}
48+
PR_NUMBER: ${{ inputs.pr_number }}
49+
COMMIT_SHA: ${{ inputs.commit_sha }}
4650
run: |
47-
echo "Approving workflow run ${{ inputs.run_id }} for PR ${{ inputs.pr_number }} at SHA ${{ inputs.commit_sha }}";
51+
echo "Approving workflow run $RUN_ID for PR $PR_NUMBER at SHA $COMMIT_SHA";
4852
gh api --method POST \
4953
-H 'Accept: application/vnd.github+json' \
5054
-H 'X-GitHub-Api-Version: 2022-11-28' \
51-
/repos/${{ inputs.repository }}/actions/runs/${{ inputs.run_id }}/approve
55+
/repos/$REPO/actions/runs/$RUN_ID/approve

.github/actions/gh-api-update-status/action.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,15 @@ runs:
5353
shell: bash
5454
env:
5555
GH_TOKEN: ${{ inputs.gh-token }}
56+
REPO: ${{ inputs.repository }}
57+
COMMIT_SHA: ${{ inputs.commit_sha }}
58+
STATE: ${{ inputs.state }}
59+
URL: ${{ inputs.url }}
60+
DESCRIPTION: ${{ inputs.description }}
61+
CONTEXT: ${{ inputs.context }}
5662
run: |
5763
gh api --method POST -H "Accept: application/vnd.github+json" -H "X-GitHub-Api-Version: 2022-11-28" \
58-
/repos/${{ inputs.repository }}/statuses/${{ inputs.commit_sha }} \
59-
-f "state=${{ inputs.state }}" -f "target_url=${{ inputs.url }}" \
60-
-f "description=${{ inputs.description }}" \
61-
-f "context=${{ inputs.context }}"
64+
/repos/$REPO/statuses/$COMMIT_SHA \
65+
-f "state=$STATE" -f "target_url=$URL" \
66+
-f "description=$DESCRIPTION" \
67+
-f "context=$CONTEXT"

.github/configs/labeler.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ build:
1919
- '.github/**'
2020
- 'gradle/**'
2121
- '*gradlew*'
22+
- 'build.gradle'
23+
- 'settings.gradle'
2224

2325
clients:
2426
- changed-files:
@@ -91,11 +93,12 @@ transactions:
9193
- any-glob-to-any-file:
9294
- 'transaction-coordinator/**'
9395

94-
KIP-932:
96+
kip-932:
9597
- changed-files:
9698
- any-glob-to-any-file:
9799
- 'share/**'
98100
- 'share-coordinator/**'
101+
- 'core/src/*/java/kafka/server/share/**'
99102

100103
docker:
101104
- changed-files:
@@ -110,12 +113,12 @@ performance:
110113
consumer:
111114
- changed-files:
112115
- any-glob-to-any-file:
113-
- 'clients/src/main/java/org/apache/kafka/clients/consumer/**'
116+
- 'clients/src/*/java/org/apache/kafka/clients/consumer/**'
114117

115118
producer:
116119
- changed-files:
117120
- any-glob-to-any-file:
118-
- 'clients/src/main/java/org/apache/kafka/clients/producer/**'
121+
- 'clients/src/*/java/org/apache/kafka/clients/producer/**'
119122

120123
kraft:
121124
- changed-files:

.github/scripts/junit.py

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,22 @@
1414
# limitations under the License.
1515

1616
import argparse
17+
from collections import OrderedDict
1718
import dataclasses
1819
from functools import partial
1920
from glob import glob
2021
import logging
2122
import os
2223
import os.path
24+
import pathlib
25+
import re
2326
import sys
2427
from typing import Tuple, Optional, List, Iterable
2528
import xml.etree.ElementTree
2629
import html
2730

31+
import yaml
32+
2833

2934
logger = logging.getLogger()
3035
logger.setLevel(logging.DEBUG)
@@ -78,6 +83,60 @@ class TestSuite:
7883
passed_tests: List[TestCase]
7984

8085

86+
# Java method names can start with alpha, "_", or "$". Following characters can also include digits
87+
method_matcher = re.compile(r"([a-zA-Z_$][a-zA-Z0-9_$]+).*")
88+
89+
90+
def clean_test_name(test_name: str) -> str:
91+
cleaned = test_name.strip("\"").rstrip("()")
92+
m = method_matcher.match(cleaned)
93+
return m.group(1)
94+
95+
96+
class TestCatalogExporter:
97+
def __init__(self):
98+
self.all_tests = {} # module -> class -> set of methods
99+
100+
def handle_suite(self, module: str, suite: TestSuite):
101+
if module not in self.all_tests:
102+
self.all_tests[module] = OrderedDict()
103+
104+
for test in suite.failed_tests:
105+
if test.class_name not in self.all_tests[module]:
106+
self.all_tests[module][test.class_name] = set()
107+
self.all_tests[module][test.class_name].add(clean_test_name(test.test_name))
108+
for test in suite.passed_tests:
109+
if test.class_name not in self.all_tests[module]:
110+
self.all_tests[module][test.class_name] = set()
111+
self.all_tests[module][test.class_name].add(clean_test_name(test.test_name))
112+
113+
def export(self, out_dir: str):
114+
if not os.path.exists(out_dir):
115+
logger.debug(f"Creating output directory {out_dir} for test catalog export.")
116+
os.makedirs(out_dir)
117+
118+
total_count = 0
119+
for module, module_tests in self.all_tests.items():
120+
module_path = os.path.join(out_dir, module)
121+
if not os.path.exists(module_path):
122+
os.makedirs(module_path)
123+
124+
sorted_tests = {}
125+
count = 0
126+
for test_class, methods in module_tests.items():
127+
sorted_methods = sorted(methods)
128+
count += len(sorted_methods)
129+
sorted_tests[test_class] = sorted_methods
130+
131+
out_path = os.path.join(module_path, f"tests.yaml")
132+
logger.debug(f"Writing {count} tests for {module} into {out_path}.")
133+
total_count += count
134+
with open(out_path, "w") as fp:
135+
yaml.dump(sorted_tests, fp)
136+
137+
logger.debug(f"Wrote {total_count} tests into test catalog.")
138+
139+
81140
def parse_report(workspace_path, report_path, fp) -> Iterable[TestSuite]:
82141
cur_suite: Optional[TestSuite] = None
83142
partial_test_case = None
@@ -138,6 +197,20 @@ def pretty_time_duration(seconds: float) -> str:
138197
return time_fmt
139198

140199

200+
def module_path_from_report_path(base_path: str, report_path: str) -> str:
201+
"""
202+
Parse a report XML and extract the module path. Test report paths look like:
203+
204+
build/junit-xml/module[/sub-module]/[suite]/TEST-class.method.xml
205+
206+
This method strips off a base path and assumes all path segments leading up to the suite name
207+
are part of the module path.
208+
"""
209+
rel_report_path = os.path.relpath(report_path, base_path)
210+
path_segments = pathlib.Path(rel_report_path).parts
211+
return os.path.join(*path_segments[0:-2])
212+
213+
141214
if __name__ == "__main__":
142215
"""
143216
Parse JUnit XML reports and generate GitHub job summary in Markdown format.
@@ -150,16 +223,21 @@ def pretty_time_duration(seconds: float) -> str:
150223
parser = argparse.ArgumentParser(description="Parse JUnit XML results.")
151224
parser.add_argument("--path",
152225
required=False,
153-
default="build/junit-xml/**/*.xml",
154-
help="Path to XML files. Glob patterns are supported.")
226+
default="build/junit-xml",
227+
help="Base path of JUnit XML files. A glob of **/*.xml will be applied on top of this path.")
228+
parser.add_argument("--export-test-catalog",
229+
required=False,
230+
default="",
231+
help="Optional path to dump all tests")
155232

156233
if not os.getenv("GITHUB_WORKSPACE"):
157234
print("This script is intended to by run by GitHub Actions.")
158235
exit(1)
159236

160237
args = parser.parse_args()
161238

162-
reports = glob(pathname=args.path, recursive=True)
239+
glob_path = os.path.join(args.path, "**/*.xml")
240+
reports = glob(pathname=glob_path, recursive=True)
163241
logger.info(f"Found {len(reports)} JUnit results")
164242
workspace_path = get_env("GITHUB_WORKSPACE") # e.g., /home/runner/work/apache/kafka
165243

@@ -177,10 +255,13 @@ def pretty_time_duration(seconds: float) -> str:
177255
flaky_table = []
178256
skipped_table = []
179257

258+
exporter = TestCatalogExporter()
259+
180260
logger.debug(f"::group::Parsing {len(reports)} JUnit Report Files")
181261
for report in reports:
182262
with open(report, "r") as fp:
183-
logger.debug(f"Parsing {report}")
263+
module_path = module_path_from_report_path(args.path, report)
264+
logger.debug(f"Parsing file: {report}, module: {module_path}")
184265
for suite in parse_report(workspace_path, report, fp):
185266
total_skipped += suite.skipped
186267
total_errors += suite.errors
@@ -216,7 +297,17 @@ def pretty_time_duration(seconds: float) -> str:
216297
simple_class_name = skipped_test.class_name.split(".")[-1]
217298
logger.debug(f"Found skipped test: {skipped_test}")
218299
skipped_table.append((simple_class_name, skipped_test.test_name))
300+
301+
if args.export_test_catalog:
302+
exporter.handle_suite(module_path, suite)
303+
219304
logger.debug("::endgroup::")
305+
306+
if args.export_test_catalog:
307+
logger.debug(f"::group::Generating Test Catalog Files")
308+
exporter.export(args.export_test_catalog)
309+
logger.debug("::endgroup::")
310+
220311
duration = pretty_time_duration(total_time)
221312
logger.info(f"Finished processing {len(reports)} reports")
222313

.github/scripts/label_small.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/bash
2+
# Licensed to the Apache Software Foundation (ASF) under one or more
3+
# contributor license agreements. See the NOTICE file distributed with
4+
# this work for additional information regarding copyright ownership.
5+
# The ASF licenses this file to You under the Apache License, Version 2.0
6+
# (the "License"); you may not use this file except in compliance with
7+
# the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
LABEL_NAME=small
18+
MAX_SIZE=100
19+
20+
pr_diff=$(gh pr view $PR_NUM -R $GITHUB_REPOSITORY --json additions,deletions)
21+
22+
additions=$(echo "$pr_diff" | jq -r '.additions')
23+
deletions=$(echo "$pr_diff" | jq -r '.deletions')
24+
25+
total_changes=$((additions + deletions))
26+
if [ "$total_changes" -lt "$MAX_SIZE" ]; then
27+
gh issue edit $PR_NUM --add-label $LABEL_NAME -R $GITHUB_REPOSITORY
28+
else
29+
gh issue edit $PR_NUM --remove-label $LABEL_NAME -R $GITHUB_REPOSITORY
30+
fi

tools/src/test/resources/junit-platform.properties renamed to .github/scripts/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
15-
junit.jupiter.params.displayname.default = "{displayName}.{argumentsWithNames}"
15+
PyYAML~=6.0

.github/workflows/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,38 @@ By default, GitHub sends an email for each failed action run. To change this,
1919
visit https://github.com/settings/notifications and find System -> Actions.
2020
Here you can change your notification preferences.
2121

22+
## Security
23+
24+
Please read the following GitHub articles before authoring new workflows.
25+
26+
1) https://github.blog/security/supply-chain-security/four-tips-to-keep-your-github-actions-workflows-secure/
27+
2) https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/
28+
29+
### Variable Injection
30+
31+
Any workflows that use the `run` directive should avoid using the `${{ ... }}` syntax.
32+
Instead, declare all injectable variables as environment variables. For example:
33+
34+
```yaml
35+
- name: Copy RC Image to promoted image
36+
env:
37+
PROMOTED_DOCKER_IMAGE: ${{ github.event.inputs.promoted_docker_image }}
38+
RC_DOCKER_IMAGE: ${{ github.event.inputs.rc_docker_image }}
39+
run: |
40+
docker buildx imagetools create --tag $PROMOTED_DOCKER_IMAGE $RC_DOCKER_IMAGE
41+
```
42+
43+
This prevents untrusted inputs from doing script injection in the `run` steps.
44+
45+
### `pull_request_target` events
46+
47+
In addition to the above security articles, please review the [official documentation](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target)
48+
on `pull_request_target`. This event type allows PRs to trigger actions that run
49+
with elevated permission and access to repository secrets. We should only be
50+
using this for very simple tasks such as applying labels or adding comments to PRs.
51+
52+
_We must never run the untrusted PR code in the elevated `pull_request_target` context_
53+
2254
## GitHub Actions Quirks
2355

2456
### Composite Actions

0 commit comments

Comments
 (0)