shlib.bash 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  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. # shlib.bash — A shell utility library.
  12. # die [ARGS...]
  13. #
  14. # Outputs ARGS to stderr, then exits the script with a failing exit status.
  15. die() {
  16. echo "$@" >&2
  17. exit 1
  18. }
  19. # run PROGRAM [ARGS...]
  20. #
  21. # Runs PROGRAM, but informs the user first. Specifically, run outputs "PROGRAM
  22. # ARGS..." to stderr, then executes PROGRAM with the specified ARGS.
  23. run() {
  24. echo "\$ $*" >&2
  25. "$@"
  26. }
  27. # command_exists PROGRAM
  28. #
  29. # Returns successfully if PROGRAM exists in $PATH, or unsuccessfully otherwise.
  30. # Outputs nothing.
  31. command_exists() {
  32. hash "$1" 2>/dev/null
  33. }
  34. # version_compat MINIMUM ACTUAL
  35. # version_compat [VERSIONS...]
  36. #
  37. # Checks whether VERSIONS is in sorted order according to version comparison
  38. # rules. Typical usage is to provide exactly two versions, in which case the
  39. # function checks that ACTUAL is greater than or equal to MINIMUM.
  40. version_compat() {
  41. printf "%s\n" "$@" | sort --check=silent --version-sort
  42. }
  43. # git_empty_tree
  44. #
  45. # Outputs the 40-character SHA-1 hash of Git's empty tree object.
  46. git_empty_tree() {
  47. git hash-object -t tree /dev/null
  48. }
  49. # git_files [PATTERNS...]
  50. #
  51. # Lists the files known to Git that match the PATTERNS, with the following
  52. # differences from `git ls-files`:
  53. #
  54. # 1. files matched by a gitignore rule are excluded,
  55. # 2. deleted but unstaged files are excluded,
  56. # 3. symlinks are excluded.
  57. #
  58. git_files() {
  59. git diff --ignore-submodules=all --raw "$(git_empty_tree)" -- "$@" \
  60. | awk '$2 != 120000 {print $6}'
  61. }
  62. # try COMMAND [ARGS...]
  63. #
  64. # Runs COMMAND with the specified ARGS without aborting the script if the
  65. # command fails. See also try_last_failed and try_status_report.
  66. try() {
  67. ci_collapsed_heading "$@"
  68. # Try the command.
  69. if "$@"; then
  70. result=$?
  71. try_last_failed=false
  72. ((++ci_try_passed))
  73. else
  74. result=$?
  75. try_last_failed=true
  76. # The command failed. Tell Buildkite to uncollapse this log section, so
  77. # that the errors are immediately visible.
  78. in_ci && ci_uncollapse_current_section
  79. echo "^^^ 🚨 Failed: $*"
  80. fi
  81. ((++ci_try_total))
  82. }
  83. try_last_failed=false
  84. # try_last_failed
  85. #
  86. # Reports whether the last command executed with `try` succeeded or failed.
  87. try_last_failed() {
  88. $try_last_failed
  89. }
  90. # try_status_report
  91. #
  92. # Exits the script with a code that reflects whether all commands executed with
  93. # `try` were successful.
  94. try_status_report() {
  95. ci_uncollapsed_heading "Status report"
  96. echo "$ci_try_passed/$ci_try_total commands passed"
  97. if ((ci_try_passed != ci_try_total)); then
  98. exit 1
  99. fi
  100. }
  101. ci_unimportant_heading() {
  102. echo "~~~" "$@" >&2
  103. }
  104. ci_collapsed_heading() {
  105. echo "---" "$@" >&2
  106. }
  107. ci_uncollapsed_heading() {
  108. echo "+++" "$@" >&2
  109. }
  110. ci_uncollapse_current_section() {
  111. if in_ci; then
  112. echo "^^^ +++" >&2
  113. fi
  114. }
  115. ci_try_passed=0
  116. ci_try_total=0
  117. # read_list PREFIX
  118. #
  119. # Appends the environment variables `PREFIX_0`, `PREFIX_1`, ... `PREFIX_N` to
  120. # the `result` global variable, stopping when `PREFIX_N` is an empty string.
  121. read_list() {
  122. result=()
  123. local i=0
  124. local param="${1}_${i}"
  125. if [[ "${!1:-}" ]]; then
  126. echo "error: mzcompose command must be an array, not a string" >&2
  127. exit 1
  128. fi
  129. while [[ "${!param:-}" ]]; do
  130. result+=("${!param}")
  131. i=$((i+1))
  132. param="${1}_${i}"
  133. done
  134. [[ ${#result[@]} -gt 0 ]] || return 1
  135. }
  136. # mapfile_shim [array]
  137. #
  138. # A limited backport of the Bash 4.0 `mapfile` built-in. Reads lines from the
  139. # standard input into the indexed array variable ARRAY. If ARRAY is unspecified,
  140. # the variable MAPFILE is used instead. Other options of `mapfile` are not
  141. # supported.
  142. mapfile_shim() {
  143. local val
  144. val=()
  145. while IFS= read -r line; do
  146. val+=("$line")
  147. done
  148. declare -ag "${1:-MAPFILE}=($(printf "%q " "${val[@]}"))"
  149. }
  150. # arch_gcc
  151. #
  152. # Computes the host architecture using GCC nomenclature: x86_64 or aarch64.
  153. # Dies if the host architecture is unknown.
  154. arch_gcc() {
  155. local arch
  156. arch=$(uname -m)
  157. case "$arch" in
  158. x86_64|aarch64) echo "$arch" ;;
  159. arm64) echo aarch64 ;;
  160. *) die "unknown host architecture \"$arch\"" ;;
  161. esac
  162. }
  163. # arch_go [ARCH-GCC]
  164. #
  165. # Converts ARCH-GCC to Go nomenclature: amd64 or arm64. If ARCH-GCC is not
  166. # specified, uses the host architecture.
  167. arch_go() {
  168. local arch=${1:-$(arch_gcc)}
  169. case "$arch" in
  170. x86_64) echo amd64 ;;
  171. aarch64) echo arm64 ;;
  172. *) die "unknown host architecture \"$arch\"" ;;
  173. esac
  174. }
  175. # red [ARGS...]
  176. #
  177. # Prints the provided text in red.
  178. red() {
  179. echo -ne "\e[31m$*\e[0m"
  180. }
  181. # green [ARGS...]
  182. #
  183. # Prints the provided text in green.
  184. green() {
  185. echo -ne "\e[32m$*\e[0m"
  186. }
  187. # white [ARGS...]
  188. #
  189. # Prints the provided text in white.
  190. white() {
  191. echo -ne "\e[97m$*\e[0m"
  192. }
  193. # in_ci
  194. #
  195. # Returns 0 if in CI and 1 otherwise
  196. in_ci() {
  197. [ -z "${BUILDKITE-}" ] && return 1
  198. return 0
  199. }
  200. # is_truthy VAR
  201. #
  202. # Returns 0 if the parameter is not one of: 0, '', no, false; and 1 otherwise
  203. is_truthy() {
  204. if [[ "$1" == "0" || "$1" == "" || "$1" == "no" || "$1" == "false" ]]; then
  205. return 1
  206. fi
  207. return 0
  208. }
  209. # trufflehog_jq_filter_common
  210. #
  211. # Filters out secrets we expect in both checked in files and logs
  212. trufflehog_jq_filter_common() {
  213. jq -c '
  214. select(
  215. .Raw != "postgres://mz_system:materialize@materialized:5432" and
  216. .Raw != "postgres://materialize:materialize@materialized:6875" and
  217. .Raw != "postgres://mz_system:materialize@materialized:6877" and
  218. .Raw != "postgres://superuser_login:some_bogus_password@materialized2:6875" and
  219. .Raw != "jdbc:postgresql://127.0.0.1:26257/defaultdb?sslmode=disable" and
  220. .Raw != "postgres://any:user@materialized:6875" and
  221. .Raw != "https://materialize:sekurity@schema-registry:8081" and
  222. .Raw != "postgresql://postgres:postgres@postgres:5432" and
  223. .Raw != "postgres://postgres:postgres@postgres:5432" and
  224. .Raw != "sub-c-4377ab04-f100-11e3-bffd-02ee2ddab7fe" and
  225. .Raw != "jdbc:postgresql://localhost:6875/materialize" and
  226. .Raw != "postgres://yugabyte:yugabyte@yugabyte:5433" and
  227. .Raw != "postgres://materialize_user:materialize_pass@postgres.materialize.svc.cluster.local:5432" and
  228. .Raw != "jdbc:postgresql://%s:%s/materialize" and
  229. .Raw != "postgres://postgres:$MATERIALIZE_PROD_SANDBOX_RDS_PASSWORD@$MATERIALIZE_PROD_SANDBOX_RDS_HOSTNAME:5432" and
  230. .Raw != "http://user:pass@example.com" and
  231. .Raw != "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDDC5MP3v1BHOgI\n5SsmrW8mjxzQGOz0IlC5jp1muW/kpEoE9TG317TEnO5Uye6zZudkFCP8YGEiN3Mc\nFbTM7eX6PjAPdnGU7khuUt/20ZM+NX5kWZPrmPTh4WQaDCL7ah1LqzBaUAMaSXq8\niuy7LGJNF8wdx8L5BjDiGTTxZXOg0Haxknc7Mbiwc9z8eb7omvzQzsOwyqocrF2u\nz86TzX1jtHP48i5CxoRHKxE94De3tNxjT/Y3OZlS4QS7iekAOQ04DVV3GIHvRUXN\n2H8ayy4+yOdhHn6ER5Jn3lti1Q5XSrxkrYn7L1Vcj6IwZQhhF5vc+ovxOYb+8ert\nEo97tIkLAgMBAAECggEAQteHHRPKz9Mzs8Sxvo4GPv0hnzFDl0DhUE4PJCKdtYoV\n8dADq2DJiu3LAZS4cJPt7Y63bGitMRg2oyPPM8G9pD5Goy3wq9zjRqexKDlXUCTt\n/T7zofRny7c94m1RWb7ablGq/vBXt90BqnajvVtvDsN+iKAqccQM4ZdI3QdrEmt1\ncHex924itzG/mqbFTAfAmVj1ZsRnJp55Txy2gqq7jX00xDM8+H49SRvUu49N64LQ\n6BUWCgWCJePRtgjSHjboAzPqSkMdaTE/WDY2zgGF3Qfq4f6JCHKfm4QylCH4gYUU\n1Kf7ttmhu9NoZO+hczobKkxP9RtXfyTRH2bsJXy2HQKBgQDhHgavxk/ln5mdMGGw\nrQud2vF9n7UwFiysYxocIC5/CWD0GAhnawchjPypbW/7vKM5Z9zhW3eH1U9P13sa\n2xHfrU5BZ16rxoBbKNpcr7VeEbUBAsDoGV24xjoecp7rB2hZ+mGik5/5Ig1Rk1KH\ndcvYy2KSi1h4Sm+mXwimmA4VDQKBgQDdzW+5FPbdM2sUB2gLMQtn3ICjDSu6IQ+k\nd0p3WlTIT51RUsPXXKkk96O5anUbeB3syY8tSKPGggsaXaeL3o09yIamtERgCnn3\nd9IS+4VKPWQlFUICU1KrD+TO7IYIX04iXBuVE5ihv0q3mslhDotmX4kS38NtKEFF\njLjA2RvAdwKBgAFkIxxw+Ett+hALnX7vAtRd5wIku4TpjisejanA1Si50RyRDXQ+\nKBQf/+u4HmoK12Nibe4Cl7GCMvRGW59l3S1pr8MdtWsQVfi6Puc1usQzDdBMyQ5m\nIbsjlnZbtPm02QM9Vd8gVGvAtx5a77aglrrnPtuy+r/7jccUbURCSkv9AoGAH9m3\nWGmVRZBzqO2jWDATxjdY1ZE3nUPQHjrvG5KCKD2ehqYO72cj9uYEwcRyyp4GFhGf\nmM4cjo3wEDowrBoqSBv6kgfC5dO7TfkL1qP9sPp93gFeeD0E2wGuRrSaTqt46eA2\nKcMloNx6W0FD98cB55KCeY5eXtdwAA/EHBVRMeMCgYAd3n6PcL6rVXyE3+wRTKK4\n+zvx5sjTAnljr5ttbEnpZafzrYIfDpB8NNjexy83AeC0O13LvSHIFoTwP8sywJRO\nRxbPMjhEBdVZ5NxlxYer7yKN+h5OBJfrLswPku7y4vdFYK3x/lMuNQO61hb1VFHc\nT2BDTbF0QSlPxFsv18B9zg==\n-----END PRIVATE KEY-----\n" and
  232. .Raw != "postgres://materialize:materialize@environmentd:6875" and
  233. .Raw != "postgres://MATERIALIZE_USERNAME:APP_SPECIFIC_PASSWORD@MATERIALIZE_HOST:6875" and
  234. .Raw != "jdbc:postgresql://MATERIALIZE_HOST:6875/materialize" and
  235. .Raw != "postgres://user:password@host:6875" and
  236. .Raw != "postgres" and
  237. .Raw != "slt" and
  238. .Raw != "e6d5833015b170e23ae819e8c5d7eaedb472ca98" and
  239. .Raw != "postgresql://materialize:AbC123dEf@ep-cool-darkness-123456.us-east-2.aws.neon.tech:5432" and
  240. .Raw != "d3aa325086974cdfb3912f28e5a8c168"
  241. )'
  242. }
  243. # trufflehog_jq_filter_files
  244. #
  245. # Filters out secrets we expect only in checked in files
  246. trufflehog_jq_filter_files() {
  247. trufflehog_jq_filter_common | jq -c '
  248. select(
  249. .Raw != "ghp_9fK8sL3x7TqR1vEzYm2pDaN4WjXbQzUtV0aN"
  250. )'
  251. }
  252. # trufflehog_jq_filter_logs
  253. #
  254. # Filters out secrets we expect only in logs during CI runs
  255. trufflehog_jq_filter_logs() {
  256. trufflehog_jq_filter_common | jq -c '
  257. select(
  258. (.Raw | contains("mz_system:materialize") | not) and
  259. (.Raw | contains("jdbc:postgresql://postgres") | not) and
  260. (.Raw | contains("mz_analytics:materialize") | not) and
  261. (.Raw | contains("mz_support:materialize") | not) and
  262. (.Raw | contains("jdbc:mysql://mysql") | not) and
  263. (.Raw | contains("superuser_login:some_bogus_password") | not) and
  264. (.Raw | contains("postgres:postgres") | not) and
  265. (.Raw | contains("jdbc:postgresql://127.0.0") | not) and
  266. (.Raw | contains("jdbc:postgresql://cockroach") | not) and
  267. (.Raw | contains("materialize:materialize") | not) and
  268. (.Raw | contains("ExpirationReaper") | not) and
  269. (.Raw | contains("u1@example.com") | not) and
  270. .Raw != "[REDACTED]"
  271. )'
  272. }