ci-builder 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. #!/usr/bin/env bash
  2. # Copyright Materialize, Inc. and contributors. All rights reserved.
  3. #
  4. # Use of this software is governed by the Business Source License
  5. # included in the LICENSE file at the root of this repository.
  6. #
  7. # As of the Change Date specified in that file, in accordance with
  8. # the Business Source License, use of this software will be governed
  9. # by the Apache License, Version 2.0.
  10. #
  11. # ci-builder — builds and releases CI builder image.
  12. # NOTE(benesch): this script is reaching the breaking point in Bash. We should
  13. # rewrite it in Python before adding much more logic to it.
  14. set -euo pipefail
  15. NIGHTLY_RUST_DATE=2025-06-28
  16. cd "$(dirname "$0")/.."
  17. . misc/shlib/shlib.bash
  18. if [[ $# -lt 2 ]]
  19. then
  20. echo "usage: $0 <command> <stable|nightly|min> [<args>...]
  21. Manages the ci-builder Docker image, which contains the dependencies required
  22. to build, test, and deploy the code in this repository.
  23. Commands:
  24. run run a command in the ci-builder image
  25. build build the ci-builder image locally
  26. exists reports via the exit code whether the ci-builder image exists
  27. tag reports the tag for the ci-builder image
  28. root-shell open a root shell to the most recently started ci-builder container
  29. For details, consult ci/builder/README.md."
  30. exit 1
  31. fi
  32. cmd=$1 && shift
  33. flavor=$1 && shift
  34. rust_date=
  35. case "$flavor" in
  36. min)
  37. docker_target=ci-builder-min
  38. rust_version=$(sed -n 's/rust-version = "\(.*\)"/\1/p' Cargo.toml)
  39. ;;
  40. stable)
  41. docker_target=ci-builder-full
  42. rust_version=$(sed -n 's/rust-version = "\(.*\)"/\1/p' Cargo.toml)
  43. ;;
  44. nightly)
  45. docker_target=ci-builder-full
  46. rust_version=nightly
  47. rust_date=/$NIGHTLY_RUST_DATE
  48. ;;
  49. *)
  50. printf "unknown CI builder flavor %q\n" "$flavor"
  51. exit 1
  52. ;;
  53. esac
  54. arch_gcc=${MZ_DEV_CI_BUILDER_ARCH:-$(arch_gcc)}
  55. arch_go=$(arch_go "$arch_gcc")
  56. cid_file=ci/builder/.${flavor%%-*}.cidfile
  57. rust_components=rustc,cargo,rust-std-$arch_gcc-unknown-linux-gnu,llvm-tools-preview
  58. if [[ $rust_version = nightly ]]; then
  59. rust_components+=,miri-preview
  60. else
  61. rust_components+=,clippy-preview,rustfmt-preview
  62. fi
  63. bazel_version=$(cat .bazelversion)
  64. uid=$(id -u)
  65. gid=$(id -g)
  66. [[ "$uid" -lt 500 ]] && uid=501
  67. [[ "$gid" -lt 500 ]] && gid=$uid
  68. build() {
  69. docker buildx build --pull \
  70. --cache-from=materialize/ci-builder:"$cache_tag" \
  71. --cache-to=type=inline,mode=max \
  72. --build-arg "ARCH_GCC=$arch_gcc" \
  73. --build-arg "ARCH_GO=$arch_go" \
  74. --build-arg "RUST_VERSION=$rust_version" \
  75. --build-arg "RUST_DATE=$rust_date" \
  76. --build-arg "RUST_COMPONENTS=$rust_components" \
  77. --build-arg "BAZEL_VERSION=$bazel_version" \
  78. --tag materialize/ci-builder:"$tag" \
  79. --tag materialize/ci-builder:"$cache_tag" \
  80. --target $docker_target \
  81. "$@" ci/builder
  82. }
  83. shasum=sha1sum
  84. if ! command_exists "$shasum"; then
  85. shasum=shasum
  86. fi
  87. if ! command_exists "$shasum"; then
  88. die "error: ci-builder: unable to find suitable SHA-1 tool; need either sha1sum or shasum"
  89. fi
  90. # The tag is the base32 encoded hash of the ci/builder directory. This logic is
  91. # similar to what mzbuild uses. Unfortunately we can't use mzbuild itself due to
  92. # a chicken-and-egg problem: mzbuild depends on the Python packages that are
  93. # *inside* this image. See materialize.git.expand_globs in the Python code for
  94. # details on this computation.
  95. files=$(cat \
  96. <(git diff --name-only -z 4b825dc642cb6eb9a060e54bf8d69288fbee4904 ci/builder .bazelversion) \
  97. <(git ls-files --others --exclude-standard -z ci/builder) \
  98. | LC_ALL=C sort -z \
  99. | xargs -0 "$shasum")
  100. files+="
  101. rust-version:$rust_version
  102. rust-date:$rust_date
  103. arch:$arch_gcc
  104. flavor:$flavor
  105. "
  106. tag=$(echo "$files" | python3 -c '
  107. import base64
  108. import hashlib
  109. import sys
  110. input = sys.stdin.buffer.read()
  111. hash = base64.b32encode(hashlib.sha1(input).digest())
  112. print(hash.decode())
  113. ')
  114. cache_tag=cache-$flavor-$rust_version-$arch_go
  115. case "$cmd" in
  116. build)
  117. build "$@"
  118. ;;
  119. exists)
  120. docker manifest inspect materialize/ci-builder:"$tag" &> /dev/null
  121. ;;
  122. tag)
  123. echo "$tag"
  124. ;;
  125. push)
  126. build "$@"
  127. docker push materialize/ci-builder:"$tag"
  128. docker push materialize/ci-builder:"$cache_tag"
  129. ;;
  130. run)
  131. docker_command=()
  132. detach_container=false
  133. container_name_param=""
  134. while [[ $# -gt 0 ]]; do
  135. case $1 in
  136. --detach)
  137. detach_container=true
  138. shift # past argument
  139. ;;
  140. --name)
  141. container_name_param="$2"
  142. shift # past argument
  143. shift # past value
  144. ;;
  145. *)
  146. docker_command+=("$1")
  147. shift # past argument
  148. ;;
  149. esac
  150. done
  151. mkdir -p target-xcompile ~/.kube
  152. args=(
  153. --cidfile "$cid_file"
  154. --rm --interactive
  155. --init
  156. --volume "$(pwd)/target-xcompile:/mnt/build"
  157. --volume "$(pwd):$(pwd)"
  158. --workdir "$(pwd)"
  159. --env XDG_CACHE_HOME=/mnt/build/cache
  160. --env AWS_ACCESS_KEY_ID
  161. --env AWS_DEFAULT_REGION
  162. --env AWS_SECRET_ACCESS_KEY
  163. --env AWS_SESSION_TOKEN
  164. --env CANARY_LOADTEST_APP_PASSWORD
  165. --env CANARY_LOADTEST_PASSWORD
  166. --env CLOUDTEST_CLUSTER_DEFINITION_FILE
  167. --env COMMON_ANCESTOR_OVERRIDE
  168. --env CONFLUENT_CLOUD_DEVEX_KAFKA_PASSWORD
  169. --env CONFLUENT_CLOUD_DEVEX_KAFKA_USERNAME
  170. --env AZURE_SERVICE_ACCOUNT_USERNAME
  171. --env AZURE_SERVICE_ACCOUNT_PASSWORD
  172. --env AZURE_SERVICE_ACCOUNT_TENANT
  173. --env GCP_SERVICE_ACCOUNT_JSON
  174. --env GITHUB_TOKEN
  175. --env GPG_KEY
  176. --env LAUNCHDARKLY_API_TOKEN
  177. --env LAUNCHDARKLY_SDK_KEY
  178. --env NIGHTLY_CANARY_APP_PASSWORD
  179. --env MZ_CI_LICENSE_KEY
  180. --env MZ_CLI_APP_PASSWORD
  181. --env MZ_SOFT_ASSERTIONS
  182. --env NO_COLOR
  183. --env NPM_TOKEN
  184. --env POLAR_SIGNALS_API_TOKEN
  185. --env PRODUCTION_ANALYTICS_USERNAME
  186. --env PRODUCTION_ANALYTICS_APP_PASSWORD
  187. --env PYPI_TOKEN
  188. --env RUST_MIN_STACK
  189. --env MZ_DEV_BUILD_SHA
  190. # For Miri with nightly Rust
  191. --env ZOOKEEPER_ADDR
  192. --env KAFKA_ADDRS
  193. --env SCHEMA_REGISTRY_URL
  194. --env STEP_START_TIMESTAMP_WITH_TZ
  195. --env POSTGRES_URL
  196. --env COCKROACH_URL
  197. # For ci-closed-issues-detect
  198. --env GITHUB_CI_ISSUE_REFERENCE_CHECKER_TOKEN
  199. # For auto_cut_release
  200. --env GIT_AUTHOR_EMAIL
  201. --env GIT_AUTHOR_NAME
  202. --env GIT_COMMITTER_EMAIL
  203. --env GIT_COMMITTER_NAME
  204. # For cloud canary
  205. --env REDPANDA_CLOUD_CLIENT_ID
  206. --env REDPANDA_CLOUD_CLIENT_SECRET
  207. --env QA_BENCHMARKING_APP_PASSWORD
  208. # For self managed docs
  209. --env BUILDKITE_BRANCH
  210. --env BUILDKITE_ORGANIZATION_SLUG
  211. --env BUILDKITE_PULL_REQUEST
  212. --env DOCKERHUB_USERNAME
  213. --env DOCKERHUB_ACCESS_TOKEN
  214. )
  215. if [[ $detach_container == "true" ]]; then
  216. args+=("--detach")
  217. fi
  218. if [[ -n "$container_name_param" ]]; then
  219. args+=("--name=$container_name_param")
  220. fi
  221. for env in $(printenv | grep -E '^(BUILDKITE|MZCOMPOSE|CI)' | sed 's/=.*//'); do
  222. args+=(--env "$env")
  223. done
  224. if [[ -t 1 ]]; then
  225. args+=(--tty)
  226. fi
  227. # Forward the host's Kubernetes config.
  228. args+=(
  229. # Need to forward the entire directory to allow creation and
  230. # deletion of config.lock.
  231. --volume "$HOME/.kube:/kube"
  232. --env "KUBECONFIG=/kube/config"
  233. )
  234. # Forward the host's SSH agent, if available.
  235. if [[ "${SSH_AUTH_SOCK:-}" ]]; then
  236. args+=(
  237. --volume "$SSH_AUTH_SOCK:/tmp/ssh-agent.sock"
  238. --env "SSH_AUTH_SOCK=/tmp/ssh-agent.sock"
  239. )
  240. fi
  241. # Forward the GitHub output file, if available.
  242. if [[ "${GITHUB_OUTPUT:-}" ]]; then
  243. args+=(
  244. --volume "$GITHUB_OUTPUT:/tmp/github-output"
  245. --env "GITHUB_OUTPUT=/tmp/github-output"
  246. )
  247. fi
  248. if [[ "$(uname -s)" = Linux ]]; then
  249. # Allow Docker-in-Docker by mounting the Docker socket in the
  250. # container. Host networking allows us to see ports created by
  251. # containers that we launch.
  252. args+=(
  253. --volume "/var/run/docker.sock:/var/run/docker.sock"
  254. --user "$(id -u):$(stat -c %g /var/run/docker.sock)"
  255. --network host
  256. --env "DOCKER_TLS_VERIFY=${DOCKER_TLS_VERIFY-}"
  257. --env "DOCKER_HOST=${DOCKER_HOST-}"
  258. )
  259. # Forward Docker daemon certificates, if requested.
  260. if [[ "${DOCKER_CERT_PATH:-}" ]]; then
  261. args+=(
  262. --volume "$DOCKER_CERT_PATH:/docker-certs"
  263. --env "DOCKER_CERT_PATH=/docker-certs"
  264. )
  265. fi
  266. # Forward Docker configuration too, if available.
  267. docker_dir=${DOCKER_CONFIG:-$HOME/.docker}
  268. if [[ -d "$docker_dir" ]]; then
  269. args+=(
  270. --volume "$docker_dir:/docker"
  271. --env "DOCKER_CONFIG=/docker"
  272. )
  273. fi
  274. # Override the Docker daemon we use to run the builder itself, if
  275. # requested.
  276. export DOCKER_HOST=${MZ_DEV_CI_BUILDER_DOCKER_HOST-${DOCKER_HOST-}}
  277. export DOCKER_TLS_VERIFY=${MZ_DEV_CI_BUILDER_DOCKER_TLS_VERIFY-${DOCKER_TLS_VERIFY-}}
  278. export DOCKER_CERT_PATH=${MZ_DEV_CI_BUILDER_DOCKER_CERT_PATH-${DOCKER_CERT_PATH-}}
  279. # Forward the host's buildkite-agent binary, if available.
  280. if command -v buildkite-agent > /dev/null 2>&1; then
  281. args+=(--volume "$(command -v buildkite-agent)":/usr/local/bin/buildkite-agent)
  282. fi
  283. # Install a persistent volume to hold Cargo metadata. We can't
  284. # forward the host's `~/.cargo` directly to the container, since
  285. # that can forward binaries in `~/.cargo/bin` that override the
  286. # version of Cargo installed in the container (!).
  287. args+=(--volume "mz-ci-builder-cargo:/cargo")
  288. else
  289. args+=(--user "$(id -u):1001")
  290. fi
  291. if [[ "${CI_BUILDER_SCCACHE:-}" ]]; then
  292. args+=(
  293. --env "RUSTC_WRAPPER=sccache"
  294. --env SCCACHE_BUCKET
  295. )
  296. fi
  297. if [[ "${CI_BAZEL_BUILD:-}" ]]; then
  298. args+=(
  299. --tmpfs "/dev/shm:exec,dev,suid,size=128g"
  300. )
  301. fi
  302. # For git-worktrees add the original repository at its original path
  303. if [[ "$(git rev-parse --git-dir)" != "$(git rev-parse --git-common-dir)" ]]; then
  304. GIT_ROOT_DIR="$(git rev-parse --git-dir | sed -e "s#/.git/worktrees/.*#/#")"
  305. args+=(--volume "$GIT_ROOT_DIR:$GIT_ROOT_DIR")
  306. fi
  307. rm -f "$cid_file"
  308. docker run "${args[@]}" "materialize/ci-builder:$tag" eatmydata "${docker_command[@]}"
  309. ;;
  310. root-shell)
  311. docker exec --interactive --tty --user 0:0 "$(<"$cid_file")" eatmydata ci/builder/root-shell.sh
  312. ;;
  313. *)
  314. printf "unknown command %q\n" "$cmd"
  315. exit 1
  316. ;;
  317. esac