-
Notifications
You must be signed in to change notification settings - Fork 29.2k
322 lines (300 loc) · 13.3 KB
/
release.yml
File metadata and controls
322 lines (300 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
#
# This workflow is intended for use in forked repositories
# when manually dispatching this to create an RC.
# To enable full release functionality, developers should manually configure
# the following GitHub Secrets in their repository settings:
#
# - ASF_USERNAME:
# Your Apache Software Foundation (ASF) account ID.
#
# - ASF_PASSWORD:
# The password associated with your ASF account.
#
# - ASF_NEXUS_TOKEN:
# ASF Nexus API token associated with your ASF account.
# Can be found in https://repository.apache.org/#profile;User%20Token
# It is written in `<password>...</password>` and ignore `User Code`.
#
# - GPG_PRIVATE_KEY:
# Your GPG private key, exported using:
# gpg --armor --export-secret-keys ABCD1234 > private.key
# Ensure this key is registered with a public key server.
# For more details, refer to:
# https://spark.apache.org/release-process.html#preparing-gpg-key
#
# - GPG_PASSPHRASE:
# The passphrase for your GPG private key.
#
# - PYPI_API_TOKEN:
# When you finalize the release, PyPI API token is required. It can be created in
# https://pypi.org/manage/account/ once you have the permission to the projects in:
# - https://pypi.org/project/pyspark/
# - https://pypi.org/project/pyspark-connect/
# - https://pypi.org/project/pyspark-client/
# Ask private@spark.apache.org to have the permission if you do not have.
#
# This workflow supports dry runs by default. If the required GitHub Secrets are not provided,
# only dry runs will be executed.
#
# In case something goes wrong during the process and a release candidate (RC) needs to be
# cleaned up, follow these steps:
#
# 1. Revert the RC-related commits, such as:
# - "Preparing development version 3.5.7-SNAPSHOT"
# - "Preparing Spark release v3.5.6-rc1"
#
# 2. Delete the RC tag from the remote repository, for example:
# - git push --delete apache v3.5.6-rc1
#
# 3. Remove the RC artifacts from SVN:
# - RC=v3.5.6-rc1 && svn rm https://dist.apache.org/repos/dist/dev/spark/"${RC}"-bin/ -m "Removing RC artifacts."
# - RC=v3.5.6-rc1 && svn rm https://dist.apache.org/repos/dist/dev/spark/"${RC}"-docs/ -m "Removing RC artifacts."
#
# 4. Drop the staging repository if it exists (https://repository.apache.org/#stagingRepositories)
name: Release Apache Spark
on:
schedule:
- cron: '0 7 * * *'
workflow_dispatch:
inputs:
branch:
description: 'Branch to release. Leave it empty to launch a dryrun. Dispatch this workflow only in the forked repository.'
required: true
default: master
release-version:
description: 'Release version. Leave it empty to launch a dryrun.'
required: false
rc-count:
description: 'RC number. Leave it empty to launch a dryrun.'
required: false
finalize:
description: 'Whether to convert RC to the official release (IRREVERSIBLE)'
required: true
default: false
jobs:
release:
name: Release Apache Spark
runs-on: ubuntu-latest
# Allow workflow to run only in the following cases:
# 1. In the apache/spark repository:
# - Only allow dry runs (i.e., both 'branch' and 'release-version' inputs are empty).
# 2. In forked repositories:
# - Allow real runs when both 'branch' and 'release-version' are provided.
# - Allow dry runs only if manually dispatched (not on a schedule).
if: |
(
github.repository == 'apache/spark' &&
inputs.branch == '' &&
inputs.release-version == ''
) || (
github.repository != 'apache/spark' &&
(
(inputs.branch != '' && inputs.release-version != '') || github.event_name == 'workflow_dispatch'
)
)
steps:
- name: Checkout Spark repository
uses: actions/checkout@v6
with:
repository: apache/spark
ref: "${{ inputs.branch }}"
- name: Use master branch's base Dockerfile for release
# The release Docker image should always use master's base Dockerfile which is actively maintained.
# Old branch Dockerfiles may have broken dependencies (expired GPG keys, outdated base images, etc.)
run: |
git fetch origin master --depth=1
git checkout origin/master -- dev/create-release/spark-rm/Dockerfile.base
echo "Using master branch's Dockerfile.base for building release image"
- name: Free up disk space
run: |
if [ -f ./dev/free_disk_space ]; then
./dev/free_disk_space
fi
- name: Release Apache Spark
env:
GIT_BRANCH: "${{ inputs.branch }}"
RELEASE_VERSION: "${{ inputs.release-version }}"
SPARK_RC_COUNT: "${{ inputs.rc-count }}"
IS_FINALIZE: "${{ inputs.finalize }}"
GIT_NAME: "${{ github.actor }}"
ASF_USERNAME: "${{ secrets.ASF_USERNAME }}"
ASF_PASSWORD: "${{ secrets.ASF_PASSWORD }}"
ASF_NEXUS_TOKEN: "${{ secrets.ASF_NEXUS_TOKEN }}"
GPG_PRIVATE_KEY: "${{ secrets.GPG_PRIVATE_KEY }}"
GPG_PASSPHRASE: "${{ secrets.GPG_PASSPHRASE }}"
PYPI_API_TOKEN: "${{ secrets.PYPI_API_TOKEN }}"
DEBUG_MODE: 1
ANSWER: y
run: |
if [ "$IS_FINALIZE" = "true" ]; then
echo ""
echo "┌────────────────────────────────────────────────────────────────────────────┐"
echo "│ !!! WARNING !!! │"
echo "├────────────────────────────────────────────────────────────────────────────┤"
echo "│ This step will CONVERT THE RC ARTIFACTS into THE OFFICIAL RELEASE. │"
echo "│ │"
echo "│ This action is IRREVERSIBLE. │"
echo "│ │"
echo "│ The workflow will continue in 60 seconds. │"
echo "│ Cancel this workflow now if you do NOT intend to finalize the release. │"
echo "└────────────────────────────────────────────────────────────────────────────┘"
echo ""
sleep 60
fi
if { [ -n "$RELEASE_VERSION" ] && [ -z "$SPARK_RC_COUNT" ]; } || { [ -z "$RELEASE_VERSION" ] && [ -n "$SPARK_RC_COUNT" ]; }; then
echo "Error: Either provide both 'Release version' and 'RC number', or leave both empty for a dryrun."
exit 1
fi
if [ -z "$RELEASE_VERSION" ] && [ -z "$SPARK_RC_COUNT" ]; then
echo "Dry run mode enabled"
export DRYRUN_MODE=1
ASF_PASSWORD="not_used"
GPG_PRIVATE_KEY="not_used"
GPG_PASSPHRASE="not_used"
ASF_USERNAME="gurwls223"
ASF_NEXUS_TOKEN="not_used"
export SKIP_TAG=1
unset RELEASE_VERSION
else
echo "Full release mode enabled"
export DRYRUN_MODE=0
fi
export ASF_PASSWORD GPG_PRIVATE_KEY GPG_PASSPHRASE ASF_USERNAME ASF_NEXUS_TOKEN
export GIT_BRANCH="${GIT_BRANCH:-master}"
[ -n "$RELEASE_VERSION" ] && export RELEASE_VERSION
if [ "$DRYRUN_MODE" = "1" ]; then
gpg --batch --gen-key <<EOF
Key-Type: RSA
Key-Length: 4096
Name-Real: Test CI User
Name-Email: gurwls223@apache.org
Expire-Date: 1
%no-protection
%commit
EOF
else
gpg --batch --import <<< "$GPG_PRIVATE_KEY"
fi
RELEASE_DIR="$PWD"/spark-release
OUTPUT_DIR="$RELEASE_DIR/output"
mkdir -p "$RELEASE_DIR"
# Start the release process
CMD="dev/create-release/do-release-docker.sh -d \"$RELEASE_DIR\""
if [ "$DRYRUN_MODE" = "1" ]; then
CMD="$CMD -n"
fi
if [ "$IS_FINALIZE" = "true" ]; then
CMD="$CMD -s finalize"
fi
echo "Running release command: $CMD"
bash -c "$CMD" &
RELEASE_PID=$!
# Stream Docker build logs to the job console. Wait until each file exists (they appear when each
# run_silent starts). If the release script exits early, stop waiting so we do not hang forever on
# docker-build.log when the base image build fails.
TAIL_PID_BASE=
TAIL_PID1=
BASE_LOG_FILE="$RELEASE_DIR/docker-build-base.log"
echo "Waiting for log file: $BASE_LOG_FILE"
while [ ! -f "$BASE_LOG_FILE" ]; do
if ! kill -0 "$RELEASE_PID" 2>/dev/null; then
echo "Release process exited before $BASE_LOG_FILE was created."
break
fi
sleep 3
done
if [ -f "$BASE_LOG_FILE" ]; then
echo "Base log file found. Starting tail."
tail -F "$BASE_LOG_FILE" &
TAIL_PID_BASE=$!
fi
LOG_FILE="$RELEASE_DIR/docker-build.log"
echo "Waiting for log file: $LOG_FILE"
while [ ! -f "$LOG_FILE" ]; do
if ! kill -0 "$RELEASE_PID" 2>/dev/null; then
echo "Release process exited before $LOG_FILE was created."
break
fi
sleep 3
done
if [ -f "$LOG_FILE" ]; then
echo "Docker image log file found. Starting tail."
tail -F "$LOG_FILE" &
TAIL_PID1=$!
fi
(
LOGGED_FILES=()
while true; do
for file in "$OUTPUT_DIR"/*.log; do
[[ -f "$file" ]] || continue
if [[ ! " ${LOGGED_FILES[@]} " =~ " ${file} " ]]; then
echo "Tailing new log file: $file"
tail -F "$file" &
LOGGED_FILES+=("$file")
fi
done
sleep 3
done
) &
TAIL_PID2=$!
wait $RELEASE_PID
[ -n "${TAIL_PID_BASE:-}" ] && kill "$TAIL_PID_BASE" 2>/dev/null || true
[ -n "${TAIL_PID1:-}" ] && kill "$TAIL_PID1" 2>/dev/null || true
kill "$TAIL_PID2" 2>/dev/null || true
# Redact sensitive information in log files
shopt -s globstar nullglob
FILES=("$RELEASE_DIR/docker-build-base.log" "$RELEASE_DIR/docker-build.log" "$OUTPUT_DIR/"*.log)
PATTERNS=("$ASF_USERNAME" "$ASF_PASSWORD" "$GPG_PRIVATE_KEY" "$GPG_PASSPHRASE" "$PYPI_API_TOKEN" "$ASF_NEXUS_TOKEN")
for file in "${FILES[@]}"; do
[ -f "$file" ] || continue
cp "$file" "$file.bak"
for pattern in "${PATTERNS[@]}"; do
[ -n "$pattern" ] || continue # Skip empty patterns
# Safely escape special characters for sed
escaped_pattern=${pattern//\\/\\\\} # Escape backslashes
escaped_pattern=${escaped_pattern//\//\\/} # Escape forward slashes
escaped_pattern=${escaped_pattern//&/\\&} # Escape &
escaped_pattern=${escaped_pattern//$'\n'/} # Remove newlines
escaped_pattern=${escaped_pattern//$'\r'/} # Remove carriage returns (optional)
# Redact the pattern
sed -i.bak "s/${escaped_pattern}/***/g" "$file"
done
rm -f "$file.bak"
done
# Zip logs/output
if [ "$DRYRUN_MODE" = "1" ]; then
zip logs.zip "$RELEASE_DIR/docker-build-base.log" "$RELEASE_DIR/docker-build.log" "$OUTPUT_DIR/"*.log
zip -9 output.zip -r "$OUTPUT_DIR"
else
zip -P "$ASF_PASSWORD" logs.zip "$RELEASE_DIR/docker-build-base.log" "$RELEASE_DIR/docker-build.log" "$OUTPUT_DIR/"*.log
zip -9 -P "$ASF_PASSWORD" output.zip -r "$OUTPUT_DIR"
fi
- name: Upload logs
if: always()
uses: actions/upload-artifact@v6
with:
name: build-logs
path: logs.zip
- name: Upload output
if: always()
uses: actions/upload-artifact@v6
with:
name: build-output
path: output.zip