Skip to content

Commit f46352e

Browse files
committed
Add guardrails for plugin packaging regressions. fixes #8041
1 parent 85a7dd2 commit f46352e

6 files changed

Lines changed: 992 additions & 0 deletions
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with 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,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
#
18+
---
19+
20+
# Check that every marketplace plugin still works when a user installs it.
21+
#
22+
# Two failure modes, neither of which any other job here can see:
23+
#
24+
# * a plugin zip that ships its dependencies but no plugin jar — an assembly
25+
# <include> that matches no artifact produces exactly this, silently;
26+
# * a plugin whose own classes reference a package that is on no classpath once
27+
# installed, because a dependency was mis-scoped or dropped by a wildcard
28+
# <exclusion> and so landed neither in the plugin's lib/ nor in lib/core.
29+
#
30+
# Both only fail in a user's install. Issue #8036 (hop-tech-parquet) was the second
31+
# kind: it worked only because another plugin happened to put the same Hadoop jars
32+
# in lib/core, and broke when that plugin left the default client.
33+
#
34+
# This job builds nothing. It downloads the published client and plugin zips and
35+
# checks those, which is why it costs about three minutes instead of a full
36+
# assembly build. The consequence is that it runs after a merge, not on a PR: a
37+
# pull request does not produce plugin zips (pr_build_code.yml passes
38+
# -Dassemblies=false -Dmodule.zips=false), so there is nothing for it to look at
39+
# until the nightly deploy publishes new snapshots.
40+
#
41+
# The client zip is the baseline, deliberately, and the container image is not. The
42+
# image is built from a client that has already had install-wave1-plugins.sh run
43+
# over it, so every plugin is present and each one can borrow the others' jars —
44+
# which is precisely how this class of bug hides.
45+
name: Marketplace plugin classpath
46+
47+
on:
48+
schedule:
49+
# Snapshots are published by the "Hop Orchestration Platform" job, which deploys
50+
# on merge rather than on a schedule — three to five times on a normal weekday.
51+
# (The daily IT job runs `clean install` and publishes nothing, so its schedule
52+
# is not what this job should track.) The fetch always resolves the newest
53+
# snapshot from maven-metadata.xml, so a run is never stale relative to main; it
54+
# is only ever as old as the last merge. Two slots keep the worst-case delay at
55+
# about twelve hours: midday catches the European morning's merges, late evening
56+
# catches the rest of the day.
57+
- cron: '0 12,22 * * *'
58+
workflow_dispatch:
59+
inputs:
60+
version:
61+
description: 'Version to check (default: this branch''s project version, e.g. 2.20.0-SNAPSHOT). A release like 2.19.0 checks what users actually downloaded.'
62+
required: false
63+
default: ''
64+
type: string
65+
66+
concurrency:
67+
group: marketplace-classpath-${{ github.ref }}
68+
cancel-in-progress: true
69+
70+
jobs:
71+
check:
72+
name: Check marketplace plugin zips
73+
runs-on: ubuntu-latest
74+
env:
75+
# The nightly integration-test run's test report. "lastCompletedBuild" keeps the
76+
# links current without plumbing build numbers between two CI systems. The API
77+
# hangs off this root, while the human-readable per-suite pages sit one level
78+
# down under "(root)" — that segment is required for the pages and must not be
79+
# used for the API.
80+
IT_REPORT_ROOT: 'https://ci-builds.apache.org/job/Hop/job/Hop-integration-tests/lastCompletedBuild/testReport'
81+
82+
steps:
83+
- uses: actions/checkout@v4
84+
85+
# jdeps is a JDK tool: a JRE does not have it.
86+
- name: Set up JDK 21
87+
uses: actions/setup-java@v4
88+
with:
89+
java-version: '21'
90+
distribution: 'temurin'
91+
92+
- name: Resolve version
93+
id: version
94+
run: |
95+
set -euo pipefail
96+
version='${{ github.event.inputs.version }}'
97+
if [ -z "$version" ]; then
98+
# -N keeps this to the root pom rather than loading the whole reactor.
99+
version=$(mvn -N -q -DforceStdout help:evaluate -Dexpression=project.version)
100+
fi
101+
[ -n "$version" ] || { echo "could not determine the project version"; exit 1; }
102+
echo "version=$version" >> "$GITHUB_OUTPUT"
103+
echo "checking $version"
104+
105+
# Releases come from the ASF dist archive, snapshots from the snapshot
106+
# repository. The plugin list is read from the client's own
107+
# full-client-env.yaml, so the list and the baseline are always the same
108+
# build.
109+
- name: Download client and marketplace plugin zips
110+
run: |
111+
./tools/fetch-apache-marketplace.sh \
112+
--version '${{ steps.version.outputs.version }}' \
113+
--dest "$RUNNER_TEMP/marketplace"
114+
115+
# A renamed integration-test project silently turns a report link into a 404,
116+
# which reads as "covered" when it is not. Warn rather than fail: Jenkins being
117+
# unreachable says nothing about the plugin zips this job is here to check.
118+
# Derived from optional-plugins.yaml and the integration-tests/ directories, so
119+
# a new plugin whose IT project follows the naming convention is picked up
120+
# without editing anything.
121+
- name: Pair plugins with their integration-test projects
122+
run: |
123+
./tools/marketplace-it-suites.sh | tee "$RUNNER_TEMP/it-suites.txt"
124+
125+
- name: Verify the integration-test suite names still exist
126+
continue-on-error: true
127+
run: |
128+
set -uo pipefail
129+
suites=$(curl -fsSL --max-time 60 \
130+
"$IT_REPORT_ROOT/api/json?tree=suites\[name\]" | tr ',' '\n' |
131+
sed -n 's/.*"name":"\([^"]*\)".*/\1/p' | sort -u)
132+
if [ -z "$suites" ]; then
133+
echo "::warning::could not read the Jenkins test report; skipping the link check"
134+
exit 0
135+
fi
136+
missing=0
137+
while read -r plugin suite; do
138+
case "$plugin" in ''|\#*) continue ;; esac
139+
if ! printf '%s\n' "$suites" | grep -qxF "$suite"; then
140+
echo "::warning::$plugin maps to integration-test suite '$suite', which is not in the latest run"
141+
missing=$((missing + 1))
142+
fi
143+
done < "$RUNNER_TEMP/it-suites.txt"
144+
echo "$missing stale mapping(s)"
145+
146+
- name: Check
147+
run: |
148+
./tools/check-plugin-classpath.sh \
149+
--client "$RUNNER_TEMP/marketplace/client.zip" \
150+
--plugins "$RUNNER_TEMP/marketplace/plugins.txt" \
151+
--label '${{ steps.version.outputs.version }}' \
152+
--junit reports/marketplace-classpath.xml \
153+
--report reports/summary.md \
154+
--it-suites "$RUNNER_TEMP/it-suites.txt" \
155+
--it-base "$IT_REPORT_ROOT/(root)"
156+
157+
# Rendered on the run's summary page, so the per-plugin status is the first
158+
# thing a reader sees rather than something buried in the log.
159+
- name: Publish summary
160+
if: always()
161+
run: cat reports/summary.md >> "$GITHUB_STEP_SUMMARY" || true
162+
163+
# Proves the check can still fail. Without this a packaging change that makes
164+
# every plugin unanalysable would show up as a green run.
165+
- name: Self test
166+
run: |
167+
./tools/check-plugin-classpath.sh \
168+
--client "$RUNNER_TEMP/marketplace/client.zip" \
169+
--plugins "$RUNNER_TEMP/marketplace/plugins.txt" \
170+
--label '${{ steps.version.outputs.version }}' \
171+
--self-test
172+
173+
- name: Upload report
174+
if: always()
175+
uses: actions/upload-artifact@v4
176+
with:
177+
name: marketplace-classpath-report
178+
path: reports/
179+
if-no-files-found: ignore

0 commit comments

Comments
 (0)