forked from redhat-developer/rhdh
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocal-hermeto-build.sh
More file actions
executable file
·399 lines (353 loc) · 12.7 KB
/
Copy pathlocal-hermeto-build.sh
File metadata and controls
executable file
·399 lines (353 loc) · 12.7 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
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
#!/bin/bash
#
# Copyright Red Hat, Inc.
# This program and the accompanying materials are made
# available under the terms of the Eclipse Public License 2.0
# which is available at https://www.eclipse.org/legal/epl-2.0/
#
# SPDX-License-Identifier: EPL-2.0
#
# This script simulates the Konflux build process locally using Hermeto.
# It can either build the dependency cache or build a container image.
set -e
set -uo pipefail
#######################################
# Constants
#######################################
readonly LOCAL_CACHE_BASEDIR='./hermeto-cache/'
readonly HERMETO_IMAGE='quay.io/konflux-ci/hermeto:latest'
# Target platform for cross-builds (e.g., linux/arm64, linux/amd64)
# Set via environment variable or defaults to native platform
TARGET_PLATFORM="${TARGET_PLATFORM:-}"
#######################################
# Normalizes architecture names to Linux conventions used by RPM repos.
# Globals:
# None
# Arguments:
# arch: Architecture name to normalize
# Outputs:
# The normalized architecture name (e.g., aarch64, x86_64)
#######################################
normalize_arch() {
local arch="$1"
case "${arch}" in
arm64) echo "aarch64" ;; # macOS/docker uses arm64, Linux RPMs use aarch64
amd64) echo "x86_64" ;; # docker uses amd64, Linux uses x86_64
*) echo "${arch}" ;; # Pass through (x86_64, aarch64, etc.)
esac
}
#######################################
# Derives the architecture name from TARGET_PLATFORM.
# Falls back to native architecture if TARGET_PLATFORM is not set.
# Globals:
# TARGET_PLATFORM
# Arguments:
# None
# Outputs:
# The architecture name (e.g., aarch64, x86_64)
#######################################
get_target_arch() {
if [[ -z "${TARGET_PLATFORM}" ]]; then
# Native architecture - normalize for macOS (arm64 -> aarch64)
normalize_arch "$(uname -m)"
return
fi
local platform_arch="${TARGET_PLATFORM#*/}" # Extract arch from linux/arch
normalize_arch "${platform_arch}"
}
TARGET_ARCH="$(get_target_arch)"
#######################################
# Cleans node_modules and yarn cache in the root and dynamic-plugins directory
# Globals:
# None
# Arguments:
# component_dir: Path to the component directory
# Outputs:
# None
#######################################
clean_directories() {
local component_dir="$1"
local directories=("${component_dir}" "${component_dir}/dynamic-plugins")
for directory in "${directories[@]}"; do
if [[ -d "${directory}" ]]; then
pushd "${directory}" > /dev/null
rm -rf node_modules
yarn cache clean
echo "Cleaned node_modules and yarn cache in ${directory}"
popd > /dev/null
fi
done
return
}
#######################################
# Prints usage information and exits.
# Globals:
# None
# Arguments:
# None
#######################################
usage() {
cat << EOF
Usage: Tries to somewhat simulate the Konflux build process by building a hermeto cache using dependencies found in the given
component directory. Then builds a container image using the hermeto cache.
Required:
-d, --directory <path> The directory of the component to build
Options:
-i, --image <name> Container image name (e.g., quay.io/example/image:tag)
Required to build image unless --no-image is specified
--no-cache Skip cache build (use existing cache). Script will build the cache by default this is is specified.
--no-image Skip image build (only build cache)
--clean Automatically remove node_modules and yarn cache in the root/dynamic-plugins directory
-h, --help Show this help message
Environment variables:
TARGET_PLATFORM Target platform for podman (e.g., linux/arm64, linux/amd64).
If not set, builds for the native platform.
The architecture (aarch64, x86_64) is automatically derived.
Examples (assume you are in the root of the rhdh repository):
$0 -d . --no-image # Build cache only (build cache by default unless --no-cache is specified)
$0 -d . -i quay.io/example/image:tag # Builds cache and image
$0 -d . -i quay.io/example/image:tag --no-cache # Build image only (hermeto cache must exist)
$0 -d . --clean # Clean node_modules and yarn cache in the root/dynamic-plugins directory
Cross-platform build (ARM on x86), requires \`qemu-user-static\` to be installed:
TARGET_PLATFORM=linux/arm64 $0 -d . -i quay.io/example/image:tag
Notes:
- Please remove all \`node_modules\` and run \`yarn cache clean\` in the root
and ./dynamic-plugins directories before running the script.
- Remove any folders with additional \`yarn.lock\` files outside of the main \`yarn.lock\`
files in the root and \`./dynamic-plugins\` directories.
- After building the cache, you should revert any changes to the
\`python/requirements*.txt\` files before running the script again.
EOF
exit 1
}
#######################################
# Check for GNU sed on macOS
#######################################
check_gnu_sed() {
if [[ "$OSTYPE" == "darwin"* ]]; then
if ! sed --version 2>/dev/null | grep -q "GNU sed"; then
echo "Error: GNU sed is required on macOS." >&2
echo "Install it with: brew install gnu-sed" >&2
echo "Then add to your PATH: export PATH=\"\$(brew --prefix)/opt/gnu-sed/libexec/gnubin:\$PATH\"" >&2
exit 1
fi
fi
}
#######################################
# Transforms a Containerfile to inject Hermeto/cachi2 configuration.
# Globals:
# None
# Arguments:
# containerfile: Path to the original Containerfile
# transformed_containerfile: Path to write the transformed Containerfile
#######################################
transform_containerfile() {
local containerfile="$1"
local transformed_containerfile="$2"
cp "${containerfile}" "${transformed_containerfile}"
# Configure dnf to use the cachi2 repo
# Use TARGET_ARCH for cross-platform builds instead of $(uname -m)
sed -i "/RUN *\(dnf\|microdnf\) install/i RUN rm -r /etc/yum.repos.d/* && cp /cachi2/output/deps/rpm/${TARGET_ARCH}/repos.d/hermeto.repo /etc/yum.repos.d/" \
"${transformed_containerfile}"
# inject the cachi2 env variables to every RUN command
sed -i 's/^\s*RUN /RUN . \/cachi2\/cachi2.env \&\& /' "$transformed_containerfile"
}
#######################################
# Builds the dependency cache using Hermeto.
# Globals:
# HERMETO_IMAGE
# TARGET_PLATFORM
# TARGET_ARCH
# Arguments:
# local_cache_dir: Path to the local cache directory
# local_cache_output_dir: Path to the cache output directory
#######################################
build_cache() {
local local_cache_dir="$1"
local local_cache_output_dir="$2"
local platform_args=()
# Set platform args if TARGET_PLATFORM is specified
if [[ -n "${TARGET_PLATFORM}" ]]; then
platform_args=("--platform" "${TARGET_PLATFORM}")
echo "Building cache for platform: ${TARGET_PLATFORM} (arch: ${TARGET_ARCH})"
fi
# Ensure the local cache dir exists
mkdir -p "${local_cache_output_dir}"
# Ensure the latest hermeto image
podman pull "${platform_args[@]}" "${HERMETO_IMAGE}"
# Build cache
podman run --rm -ti \
"${platform_args[@]}" \
-v "${PWD}:/source:z" \
-v "${local_cache_dir}:/cachi2:z" \
-w /source \
"${HERMETO_IMAGE}" \
--log-level DEBUG \
fetch-deps --dev-package-managers \
--source . \
--output /cachi2/output \
'[{"type": "rpm", "path": "."}, {"type": "yarn","path": "."}, {"type": "yarn","path": "./dynamic-plugins"}, {"type": "pip","path": "./python", "allow_binary": "false"}]'
podman run --rm -ti \
"${platform_args[@]}" \
-v "${PWD}:/source:z" \
-v "${local_cache_dir}:/cachi2:z" \
-w /source \
"${HERMETO_IMAGE}" \
generate-env --format env --output /cachi2/cachi2.env /cachi2/output
podman run --rm -ti \
"${platform_args[@]}" \
-v "${PWD}:/source:z" \
-v "${local_cache_dir}:/cachi2:z" \
-w /source \
"${HERMETO_IMAGE}" \
inject-files /cachi2/output
return 0
}
#######################################
# Builds a container image using the hermeto cache.
# Globals:
# TARGET_PLATFORM
# TARGET_ARCH
# Arguments:
# component_dir: Path to the component directory
# local_cache_dir: Path to the local cache directory
# image: Name of the container image to build
#######################################
build_image() {
local component_dir="$1"
local local_cache_dir="$2"
local image="$3"
local platform_args=()
# Set platform args if TARGET_PLATFORM is specified
if [[ -n "${TARGET_PLATFORM}" ]]; then
platform_args=("--platform" "${TARGET_PLATFORM}")
echo "Building image for platform: ${TARGET_PLATFORM} (arch: ${TARGET_ARCH})"
fi
# Ensure the local cache dir exists
if [[ ! -d "${local_cache_dir}" ]]; then
echo "Local cache dir does not exist. Please run the script without --no-cache first."
echo "example: $0 -d ${component_dir} -i <image>"
exit 1
fi
# Transform the containerfile to simulate Konflux build
transform_containerfile \
"${component_dir}/build/containerfiles/Containerfile" \
"${component_dir}/build/containerfiles/Containerfile.hermeto"
# Prevent podman from injecting host RHEL subscriptions into the container.
# Podman automatically mounts host subscription secrets (/run/secrets/redhat.repo,
# /run/secrets/rhsm, /run/secrets/etc-pki-entitlement) which enables RHEL repos
# not in the hermeto cache. With --network none, dnf/microdnf fails trying to
# access these repos. Mount empty paths over these secrets to block injection.
EMPTY_DIR=$(mktemp -d)
trap 'rm -rf "${EMPTY_DIR}"' EXIT
podman build -t "${image}" \
"${platform_args[@]}" \
--network none \
--no-cache \
-f "${component_dir}/build/containerfiles/Containerfile.hermeto" \
-v "${local_cache_dir}:/cachi2" \
-v /dev/null:/run/secrets/redhat.repo \
-v "${EMPTY_DIR}:/run/secrets/rhsm:z" \
-v "${EMPTY_DIR}:/run/secrets/etc-pki-entitlement:z" \
"${component_dir}"
}
#######################################
# Main entry point for the script.
# Globals:
# LOCAL_CACHE_BASEDIR
# Arguments:
# Command line arguments
#######################################
main() {
check_gnu_sed
local component_dir=""
local image=""
local no_cache=false
local no_image=false
local clean=false
while [[ $# -gt 0 ]]; do
case "$1" in
-d|--directory)
if [[ -z "${2:-}" ]]; then
echo "Error: -d/--directory requires a path argument" >&2
usage
fi
component_dir="$2"
shift 2
;;
-i|--image)
if [[ -z "${2:-}" ]]; then
echo "Error: -i/--image requires an image name argument" >&2
usage
fi
image="$2"
shift 2
;;
--no-cache)
no_cache=true
shift
;;
--no-image)
no_image=true
shift
;;
--clean)
clean=true
shift
;;
-h|--help)
usage
;;
*)
echo "Error: Unknown option: $1" >&2
usage
;;
esac
done
if [[ -z "${component_dir}" ]]; then
echo "Error: Directory is required. Use -d or --directory to specify." >&2
usage
fi
if [[ "${no_cache}" == true && "${no_image}" == true ]]; then
echo "Error: Nothing to do - both cache and image builds are disabled" >&2
usage
fi
# If image is not provided, implicitly skip image build
if [[ -z "${image}" ]]; then
no_image=true
fi
if [[ "${clean}" == true ]]; then
clean_directories "${component_dir}"
else
read -p "This script requires removal of node_modules and yarn cache in the root/dynamic-plugins directory. Continue? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
echo "Exiting..."
exit 1
fi
clean_directories "${component_dir}"
fi
mkdir -p "${LOCAL_CACHE_BASEDIR}"
# Resolve paths
local resolved_component_dir
local local_cache_dir
local local_cache_output_dir
resolved_component_dir="$(realpath "${component_dir}")"
local_cache_dir="$(realpath "${LOCAL_CACHE_BASEDIR}")/$(basename "${resolved_component_dir}")"
local_cache_output_dir="${local_cache_dir}/output"
echo "Component dir: ${resolved_component_dir}"
echo "Local cache dir: ${local_cache_dir}"
if [[ "${no_cache}" == false ]]; then
echo "Building cache..."
build_cache "${local_cache_dir}" "${local_cache_output_dir}"
else
echo "Skipping cache build (--no-cache specified)"
fi
if [[ "${no_image}" == false ]]; then
echo "Building image..."
build_image "${resolved_component_dir}" "${local_cache_dir}" "${image}"
else
echo "Skipping image build (--no-image specified or -i/--image not provided)"
fi
}
main "$@"