-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathbuild.sh
executable file
·348 lines (279 loc) · 11.6 KB
/
build.sh
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
#!/bin/bash
set -e
set -o pipefail
CI_DEBUG=${CI_DEBUG:-false}
REGISTRY=${REGISTRY:-"quay.io/zncdatadev"}
CACHE_REGISTRY=${CACHE_REGISTRY:-$REGISTRY}
KUBEDOOP_VERSION=${KUBEDOOP_VERSION:-"0.0.0-dev"}
KUBEDOOP_TAG="kubedoop${KUBEDOOP_VERSION}"
PATH=$PATH:$(pwd)/bin
# If CI_DEBUG is set, enable debug mode
if [ "$CI_DEBUG" = "true" ]; then
set -x
fi
function main () {
local cmd_name=$(basename $0)
local usage="
Usage: ${cmd_name} [OPTIONS] [PRODUCT...]
Build the image.
Use docker buildx bake to build the image, and push the image to the registry.
Examples:
Build all products:
${cmd_name}
Build specified product:
${cmd_name} java-base
Build specified products
${cmd_name} java-base hadoop
Build specified product and specified version:
${cmd_name} java-base:17 hadoop:3.3.1
Build and push the image:
${cmd_name} --push hadoop
Options:
-r, --registry REGISTRY Set the registry, default is 'quay.io/zncdatadev'
-p, --push Push the built image to the registry, if not set, load the image to local docker
-s, --sign Sign the image with cosign
-h, --help Show this message
"
local registry=$REGISTRY
local debug=false
local push=false
local sign=false
# Change target to array
local -a products=()
# Parse arguments
while [ "$1" != "" ]; do
case $1 in
-r | --registry )
shift
registry=$1
;;
-h | --help )
echo "$usage"
exit 0
;;
-p | --push )
push=true
;;
-s | --sign )
sign=true
;;
* )
# Handle non-option argument as target
if [[ $1 != -* ]]; then
products+=("$1")
else
echo "Error: Invalid argument '$1'"
echo "$usage"
exit 1
fi
;;
esac
shift
done
# Set registry
export REGISTRY=$registry
# Print all specified targets
if [ ${#targets[@]} -gt 0 ]; then
echo "INFO: Specified targets: ${targets[*]}"
fi
# Check system requirements if signing is requested
system_requirements $sign
# Note:
# If push is not true, we use single-architecture to build the image.
# The docker version in Ubuntu 24.04 of the current GitHub runner does not
# support multi-architecture image building with the --load parameter.
# Moreover, if push is false, we do not need to build multi-architecture images,
# just load the current system architecture image into docker with the --load parameter.
local platforms='["linux/amd64", "linux/arm64"]'
if [ "$push" = false ]; then
platforms='["linux/'$(uname -m)'"]'
fi
# Get the bakefile configuration
local bakefile=$(get_bakefile "$platforms")
# Update function call order of parameters
build_sign_image "$bakefile" $push $sign $debug "${products[*]}"
}
function system_requirements () {
local sign=$1
if ! command -v jq > /dev/null; then
echo "jq is required. Please install it refer to <https://stedolan.github.io/jq/download/>" >&2
exit 1
fi
if ! command -v yq > /dev/null; then
echo "yq is required. Please install it refer to <https://mikefarah.gitbook.io/yq/>" >&2
exit 1
fi
if [ "$sign" = true ] && ! command -v cosign > /dev/null; then
echo "cosign is required, Please install it refer to <https://docs.sigstore.dev/cosign/system_config/installation/>" >&2
exit 1
fi
}
# https://github.com/sigstore/cosign/issues/587
# Use Cosign for keyless image signing
function sign_image () {
local image=$1
local upload=$2
if [ $upload = true ]; then
cosign sign -y $image
fi
}
# Build image with docker bake
# Arguments:
# $1: str bakefile, docker bake file content, JSON object
# $2: bool push, if true, push image to registry, else load to local docker, default is false
# $3: bool sign, if true, sign image with cosign, default is false
# $4: bool debug, if true, print debug info, default is false
# $5: str products, products to build, if empty, build all products.
# A version can be specified with the product name.
# eg: 'java-base hadoop:3.3.1'
# Returns:
# None
function build_sign_image () {
local bakefile=$1
local push=$2
local sign=$3
local debug=$4
local products=$5
local image_digest_file="docker-bake-digests.json"
local cmd=("docker" "buildx" "bake")
if [ "$push" = true ]; then
cmd+=("--push")
else
cmd+=("--load")
fi
if [ "$debug" = true ]; then
cmd+=("--progress" "plain")
fi
cmd+=("--metadata-file" $image_digest_file)
cmd+=("--file" "-")
local -a to_build_targets=()
# Add all targets to command
for product in $products; do
if [ -n "$product" ]; then
# Transform target if it contains colon and dot
if [[ "$product" == *:* ]]; then
target=$(echo "$product" | sed 's/:/-/g' | sed 's/\./_/g')
fi
to_build_targets+=("$target")
fi
done
if [ ${#to_build_targets[@]} -gt 0 ]; then
echo "INFO: Building targets: ${to_build_targets[*]}" >&2
cmd+=("${to_build_targets[@]}")
else
echo "INFO: Building all targets" >&2
fi
echo "INFO: Building image: ${cmd[*]}" >&2
echo "$bakefile" | "${cmd[@]}"
echo "INFO: Signing images" >&2
if [ -f $image_digest_file ] && [ "$sign" = true ]; then
for key in $(jq -r 'keys[]' $image_digest_file); do
local image_digest=$(jq -r --arg key "$key" '.[$key]["containerimage.digest"]' $image_digest_file)
local image_name=$(jq -r --arg key "$key" '.[$key]["image.name"]' $image_digest_file)
if [ -n "$image_digest" ] && [ -n "$image_name" ]; then
local digest_tag="${image_name}@${image_digest}"
echo "INFO: Signing image: $digest_tag" >&2
sign_image $digest_tag $push
fi
done
fi
}
# Get docker bake file using project.yaml and versions.yaml in product path
# The bake file is a JSON object
# Arguments:
# $1: str, platforms, platforms for bakefile. eg: '["linux/amd64", "linux/arm64"]'
# Returns:
# JSON: bake file JSON object
#
function get_bakefile () {
local platforms=$1
# if platforms is empty, set default value
if [ -z "$platforms" ]; then
platforms='["linux/'$(uname -m)'"]'
fi
local current_sha=$(git rev-parse HEAD)
# Use yq to parse yaml file to json
local products=$(yq eval '.products' -o=json project.yaml)
local bakefile=$(jq -n '{group:{}, target: {}}')
local default_groups=$(jq -n '[]')
for product_name in $(echo "$products" | jq -r '.[]'); do
local versions_file="${product_name}/versions.yaml"
echo "INFO: Process product versions file: $versions_file" >&2
if [ -f $versions_file ]; then
# add product to default_groups
default_groups=$(echo "$default_groups" | jq --arg name "$product_name" '. + [$name]')
local product_groups=$(jq -n '[]')
local versions=$(yq eval '.versions' -o=json $versions_file)
local targets=$(jq -n '{}')
for version in $(echo "$versions" | jq -c '.[]'); do
# Construct bakefile target object
local target=$(jq -n '{}')
local product_version=$(echo "$version" | jq -r '.product')
# Transform target name, eg: kubedoop-0_0_0_dev
local target_name="${product_name}-$(echo "$product_version" | tr '.' '_')"
# Add target to product groups
product_groups=$(echo "$product_groups" | jq --arg name "$target_name" '. + [$name]')
local tags=$(jq -n --arg tag "$REGISTRY/$product_name:$product_version-$KUBEDOOP_TAG" '[$tag]')
# Construct contexts object
local contexts=$(jq -n '{}')
# Construct args object
local args=$(jq -n '{}')
for dependency in $(echo "$version" | jq -r 'keys[]'); do
# get value by dependency key
value=$(echo "$version" | jq -r --arg k "$dependency" '.[$k]')
# When dependency is in products, it is a context for target, should:
# - Add to contexts
if echo "$products" | jq -e --arg k "$dependency" '. | index($k)' > /dev/null; then
local contexts_target_key="zncdatadev/image/$dependency"
local contexts_target_value="target:$dependency-$(echo $value | tr '.' '_')"
contexts=$(echo "$contexts" | jq --arg k "$contexts_target_key" --arg v "$contexts_target_value" '. + {($k): $v}')
fi
# Transform dependency name to uppercase and replace - with _, append _VERSION
# eg: INOTIFY_TOOLS_VERSION
local transformed_key=$(echo "$dependency" | tr '[:lower:]' '[:upper:]' | tr '-' '_')"_VERSION"
# append dependency to 'args'
args=$(echo "$args" | jq --arg k "$transformed_key" --arg v "$value" '. + {($k): $v}')
done
target=$(echo "$target" | jq --arg k "args" --argjson v "$args" '. + {($k): $v}')
target=$(echo "$target" | jq --arg k "platforms" --argjson v "$platforms" '. + {($k): $v}')
target=$(echo "$target" | jq --arg k "tags" --argjson v "$tags" '. + {($k): $v}')
target=$(echo "$target" | jq --arg k "context" --arg v "$product_name" '. + {($k): $v}')
target=$(echo "$target" | jq --arg k "dockerfile" --arg v "Dockerfile" '. + {($k): $v}')
# If contexts is not empty, add to target
if [ "$contexts" != "{}" ]; then
target=$(echo "$target" | jq --arg k "contexts" --argjson v "$contexts" '. + {($k): $v}')
fi
# https://docs.docker.com/build/cache/backends/registry/
local cache_from="type=registry,mode=max,ref=$CACHE_REGISTRY/cache:$product_name-$product_version"
target=$(echo "$target" | jq --arg k "cache-from" --arg v "$cache_from" '. + {($k): [$v]}')
local cache_to="type=registry,mode=max,compression=zstd,ignore-error=true,oci-mediatypes=true,image-manifest=true,ref=$CACHE_REGISTRY/cache:$product_name-$product_version"
target=$(echo "$target" | jq --arg k "cache-to" --arg v "$cache_to" '. + {($k): [$v]}')
local datetime=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
local labels=$(jq -n '{}')
labels=$(echo "$labels" | jq --arg k "org.opencontainers.image.title" --arg v "$product_name" '. + {($k): $v}')
labels=$(echo "$labels" | jq --arg k "org.opencontainers.image.version" --arg v "$product_version" '. + {($k): $v}')
labels=$(echo "$labels" | jq --arg k "org.opencontainers.image.created" --arg v "$datetime" '. + {($k): $v}')
labels=$(echo "$labels" | jq --arg k "org.opencontainers.image.revision" --arg v "$current_sha" '. + {($k): $v}')
target=$(echo "$target" | jq --argjson v "$labels" '. + {labels: $v}')
local annotations=$(jq -n '[]')
annotations=$(echo "$annotations" | jq --arg k "org.opencontainers.image.created=$datetime" '. + [$k]')
annotations=$(echo "$annotations" | jq --arg k "org.opencontainers.image.revision=$current_sha" '. + [$k]')
target=$(echo "$target" | jq --argjson v "$annotations" '. + {annotations: $v}')
# Add target to targets
targets=$(echo "$targets" | jq --arg k "$target_name" --argjson v "$target" '. + {($k): $v}')
done
fi
echo "INFO: Processed product targets: $product_name: $(echo "$product_groups" | jq -c '.')" >&2
# Add product groups to group
bakefile=$(echo "$bakefile" | jq --arg k "$product_name" --argjson v "$product_groups" '.group |= . + {($k): {targets: $v}}')
# Add targets to bakefile
bakefile=$(echo "$bakefile" | jq --argjson v "$targets" '.target |= . + $v')
done
echo "INFO: default targets: $(echo "$default_groups" | jq -c '.')" >&2
# Add default groups to bakefile
bakefile=$(echo "$bakefile" | jq --arg k "default" --argjson v "$default_groups" '.group |= . + {($k): {targets: $v}}')
# Save bakefile to bakefile.json
jq -r '.' <<< $bakefile > ./bakefile.json
echo $(jq -c '.' <<< $bakefile)
}
main "$@"