get_helm.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. #!/usr/bin/env bash
  2. # Copyright The Helm Authors.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. # The install script is based off of the MIT-licensed script from glide,
  16. # the package manager for Go: https://github.com/Masterminds/glide.sh/blob/master/get
  17. : ${BINARY_NAME:="helm"}
  18. : ${USE_SUDO:="true"}
  19. : ${DEBUG:="false"}
  20. : ${VERIFY_CHECKSUM:="true"}
  21. : ${VERIFY_SIGNATURES:="false"}
  22. : ${HELM_INSTALL_DIR:="/usr/local/bin"}
  23. : ${GPG_PUBRING:="pubring.kbx"}
  24. HAS_CURL="$(type "curl" &> /dev/null && echo true || echo false)"
  25. HAS_WGET="$(type "wget" &> /dev/null && echo true || echo false)"
  26. HAS_OPENSSL="$(type "openssl" &> /dev/null && echo true || echo false)"
  27. HAS_GPG="$(type "gpg" &> /dev/null && echo true || echo false)"
  28. HAS_GIT="$(type "git" &> /dev/null && echo true || echo false)"
  29. HAS_TAR="$(type "tar" &> /dev/null && echo true || echo false)"
  30. # initArch discovers the architecture for this system.
  31. initArch() {
  32. ARCH=$(uname -m)
  33. case $ARCH in
  34. armv5*) ARCH="armv5";;
  35. armv6*) ARCH="armv6";;
  36. armv7*) ARCH="arm";;
  37. aarch64) ARCH="arm64";;
  38. x86) ARCH="386";;
  39. x86_64) ARCH="amd64";;
  40. i686) ARCH="386";;
  41. i386) ARCH="386";;
  42. esac
  43. }
  44. # initOS discovers the operating system for this system.
  45. initOS() {
  46. OS=$(echo `uname`|tr '[:upper:]' '[:lower:]')
  47. case "$OS" in
  48. # Minimalist GNU for Windows
  49. mingw*|cygwin*) OS='windows';;
  50. esac
  51. }
  52. # runs the given command as root (detects if we are root already)
  53. runAsRoot() {
  54. if [ $EUID -ne 0 -a "$USE_SUDO" = "true" ]; then
  55. sudo "${@}"
  56. else
  57. "${@}"
  58. fi
  59. }
  60. # verifySupported checks that the os/arch combination is supported for
  61. # binary builds, as well whether or not necessary tools are present.
  62. verifySupported() {
  63. local supported="darwin-amd64\ndarwin-arm64\nlinux-386\nlinux-amd64\nlinux-arm\nlinux-arm64\nlinux-loong64\nlinux-ppc64le\nlinux-s390x\nlinux-riscv64\nwindows-amd64\nwindows-arm64"
  64. if ! echo "${supported}" | grep -q "${OS}-${ARCH}"; then
  65. echo "No prebuilt binary for ${OS}-${ARCH}."
  66. echo "To build from source, go to https://github.com/helm/helm"
  67. exit 1
  68. fi
  69. if [ "${HAS_CURL}" != "true" ] && [ "${HAS_WGET}" != "true" ]; then
  70. echo "Either curl or wget is required"
  71. exit 1
  72. fi
  73. if [ "${VERIFY_CHECKSUM}" == "true" ] && [ "${HAS_OPENSSL}" != "true" ]; then
  74. echo "In order to verify checksum, openssl must first be installed."
  75. echo "Please install openssl or set VERIFY_CHECKSUM=false in your environment."
  76. exit 1
  77. fi
  78. if [ "${VERIFY_SIGNATURES}" == "true" ]; then
  79. if [ "${HAS_GPG}" != "true" ]; then
  80. echo "In order to verify signatures, gpg must first be installed."
  81. echo "Please install gpg or set VERIFY_SIGNATURES=false in your environment."
  82. exit 1
  83. fi
  84. if [ "${OS}" != "linux" ]; then
  85. echo "Signature verification is currently only supported on Linux."
  86. echo "Please set VERIFY_SIGNATURES=false or verify the signatures manually."
  87. exit 1
  88. fi
  89. fi
  90. if [ "${HAS_GIT}" != "true" ]; then
  91. echo "[WARNING] Could not find git. It is required for plugin installation."
  92. fi
  93. if [ "${HAS_TAR}" != "true" ]; then
  94. echo "[ERROR] Could not find tar. It is required to extract the helm binary archive."
  95. exit 1
  96. fi
  97. }
  98. # checkDesiredVersion checks if the desired version is available.
  99. checkDesiredVersion() {
  100. if [ "x$DESIRED_VERSION" == "x" ]; then
  101. # Get tag from release URL
  102. local latest_release_url="https://get.helm.sh/helm4-latest-version"
  103. local latest_release_response=""
  104. if [ "${HAS_CURL}" == "true" ]; then
  105. latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true )
  106. elif [ "${HAS_WGET}" == "true" ]; then
  107. latest_release_response=$( wget "$latest_release_url" -q -O - 2>&1 || true )
  108. fi
  109. TAG=$( echo "$latest_release_response" | grep '^v[0-9]' )
  110. if [ "x$TAG" == "x" ]; then
  111. printf "Could not retrieve the latest release tag information from %s: %s\n" "${latest_release_url}" "${latest_release_response}"
  112. exit 1
  113. fi
  114. else
  115. TAG=$DESIRED_VERSION
  116. fi
  117. }
  118. # checkHelmInstalledVersion checks which version of helm is installed and
  119. # if it needs to be changed.
  120. checkHelmInstalledVersion() {
  121. if [[ -f "${HELM_INSTALL_DIR}/${BINARY_NAME}" ]]; then
  122. local version=$("${HELM_INSTALL_DIR}/${BINARY_NAME}" version --template="{{ .Version }}")
  123. if [[ "$version" == "$TAG" ]]; then
  124. echo "Helm ${version} is already ${DESIRED_VERSION:-latest}"
  125. return 0
  126. else
  127. echo "Helm ${TAG} is available. Changing from version ${version}."
  128. return 1
  129. fi
  130. else
  131. return 1
  132. fi
  133. }
  134. # downloadFile downloads the latest binary package and also the checksum
  135. # for that binary.
  136. downloadFile() {
  137. HELM_DIST="helm-$TAG-$OS-$ARCH.tar.gz"
  138. DOWNLOAD_URL="https://get.helm.sh/$HELM_DIST"
  139. CHECKSUM_URL="$DOWNLOAD_URL.sha256"
  140. HELM_TMP_ROOT="$(mktemp -dt helm-installer-XXXXXX)"
  141. HELM_TMP_FILE="$HELM_TMP_ROOT/$HELM_DIST"
  142. HELM_SUM_FILE="$HELM_TMP_ROOT/$HELM_DIST.sha256"
  143. echo "Downloading $DOWNLOAD_URL"
  144. if [ "${HAS_CURL}" == "true" ]; then
  145. curl -SsL "$CHECKSUM_URL" -o "$HELM_SUM_FILE"
  146. curl -SsL "$DOWNLOAD_URL" -o "$HELM_TMP_FILE"
  147. elif [ "${HAS_WGET}" == "true" ]; then
  148. wget -q -O "$HELM_SUM_FILE" "$CHECKSUM_URL"
  149. wget -q -O "$HELM_TMP_FILE" "$DOWNLOAD_URL"
  150. fi
  151. }
  152. # verifyFile verifies the SHA256 checksum of the binary package
  153. # and the GPG signatures for both the package and checksum file
  154. # (depending on settings in environment).
  155. verifyFile() {
  156. if [ "${VERIFY_CHECKSUM}" == "true" ]; then
  157. verifyChecksum
  158. fi
  159. if [ "${VERIFY_SIGNATURES}" == "true" ]; then
  160. verifySignatures
  161. fi
  162. }
  163. # installFile installs the Helm binary.
  164. installFile() {
  165. HELM_TMP="$HELM_TMP_ROOT/$BINARY_NAME"
  166. mkdir -p "$HELM_TMP"
  167. tar xf "$HELM_TMP_FILE" -C "$HELM_TMP"
  168. HELM_TMP_BIN="$HELM_TMP/$OS-$ARCH/helm"
  169. echo "Preparing to install $BINARY_NAME into ${HELM_INSTALL_DIR}"
  170. runAsRoot cp "$HELM_TMP_BIN" "$HELM_INSTALL_DIR/$BINARY_NAME"
  171. echo "$BINARY_NAME installed into $HELM_INSTALL_DIR/$BINARY_NAME"
  172. }
  173. # verifyChecksum verifies the SHA256 checksum of the binary package.
  174. verifyChecksum() {
  175. printf "Verifying checksum... "
  176. local sum=$(openssl sha1 -sha256 ${HELM_TMP_FILE} | awk '{print $2}')
  177. local expected_sum=$(cat ${HELM_SUM_FILE})
  178. if [ "$sum" != "$expected_sum" ]; then
  179. echo "SHA sum of ${HELM_TMP_FILE} does not match. Aborting."
  180. exit 1
  181. fi
  182. echo "Done."
  183. }
  184. # verifySignatures obtains the latest KEYS file from GitHub main branch
  185. # as well as the signature .asc files from the specific GitHub release,
  186. # then verifies that the release artifacts were signed by a maintainer's key.
  187. verifySignatures() {
  188. printf "Verifying signatures... "
  189. local keys_filename="KEYS"
  190. local github_keys_url="https://raw.githubusercontent.com/helm/helm/main/${keys_filename}"
  191. if [ "${HAS_CURL}" == "true" ]; then
  192. curl -SsL "${github_keys_url}" -o "${HELM_TMP_ROOT}/${keys_filename}"
  193. elif [ "${HAS_WGET}" == "true" ]; then
  194. wget -q -O "${HELM_TMP_ROOT}/${keys_filename}" "${github_keys_url}"
  195. fi
  196. local gpg_keyring="${HELM_TMP_ROOT}/keyring.gpg"
  197. local gpg_homedir="${HELM_TMP_ROOT}/gnupg"
  198. mkdir -p -m 0700 "${gpg_homedir}"
  199. local gpg_stderr_device="/dev/null"
  200. if [ "${DEBUG}" == "true" ]; then
  201. gpg_stderr_device="/dev/stderr"
  202. fi
  203. gpg --batch --quiet --homedir="${gpg_homedir}" --import "${HELM_TMP_ROOT}/${keys_filename}" 2> "${gpg_stderr_device}"
  204. gpg --batch --no-default-keyring --keyring "${gpg_homedir}/${GPG_PUBRING}" --export > "${gpg_keyring}"
  205. local github_release_url="https://github.com/helm/helm/releases/download/${TAG}"
  206. if [ "${HAS_CURL}" == "true" ]; then
  207. curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc"
  208. curl -SsL "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" -o "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc"
  209. elif [ "${HAS_WGET}" == "true" ]; then
  210. wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc"
  211. wget -q -O "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" "${github_release_url}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc"
  212. fi
  213. local error_text="If you think this might be a potential security issue,"
  214. error_text="${error_text}\nplease see here: https://github.com/helm/community/blob/master/SECURITY.md"
  215. local num_goodlines_sha=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)')
  216. if [[ ${num_goodlines_sha} -lt 2 ]]; then
  217. echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz.sha256!"
  218. echo -e "${error_text}"
  219. exit 1
  220. fi
  221. local num_goodlines_tar=$(gpg --verify --keyring="${gpg_keyring}" --status-fd=1 "${HELM_TMP_ROOT}/helm-${TAG}-${OS}-${ARCH}.tar.gz.asc" 2> "${gpg_stderr_device}" | grep -c -E '^\[GNUPG:\] (GOODSIG|VALIDSIG)')
  222. if [[ ${num_goodlines_tar} -lt 2 ]]; then
  223. echo "Unable to verify the signature of helm-${TAG}-${OS}-${ARCH}.tar.gz!"
  224. echo -e "${error_text}"
  225. exit 1
  226. fi
  227. echo "Done."
  228. }
  229. # fail_trap is executed if an error occurs.
  230. fail_trap() {
  231. result=$?
  232. if [ "$result" != "0" ]; then
  233. if [[ -n "$INPUT_ARGUMENTS" ]]; then
  234. echo "Failed to install $BINARY_NAME with the arguments provided: $INPUT_ARGUMENTS"
  235. help
  236. else
  237. echo "Failed to install $BINARY_NAME"
  238. fi
  239. echo -e "\tFor support, go to https://github.com/helm/helm."
  240. fi
  241. cleanup
  242. exit $result
  243. }
  244. # testVersion tests the installed client to make sure it is working.
  245. testVersion() {
  246. set +e
  247. HELM="$(command -v $BINARY_NAME)"
  248. if [ "$?" = "1" ]; then
  249. echo "$BINARY_NAME not found. Is $HELM_INSTALL_DIR on your "'$PATH?'
  250. exit 1
  251. fi
  252. set -e
  253. }
  254. # help provides possible cli installation arguments
  255. help () {
  256. echo "Accepted cli arguments are:"
  257. echo -e "\t[--help|-h ] ->> prints this help"
  258. echo -e "\t[--version|-v <desired_version>] . When not defined it fetches the latest release tag from the Helm CDN"
  259. echo -e "\te.g. --version v4.0.0 or -v canary"
  260. echo -e "\t[--no-sudo] ->> install without sudo"
  261. }
  262. # cleanup temporary files to avoid https://github.com/helm/helm/issues/2977
  263. cleanup() {
  264. if [[ -d "${HELM_TMP_ROOT:-}" ]]; then
  265. rm -rf "$HELM_TMP_ROOT"
  266. fi
  267. }
  268. # Execution
  269. #Stop execution on any error
  270. trap "fail_trap" EXIT
  271. set -e
  272. # Set debug if desired
  273. if [ "${DEBUG}" == "true" ]; then
  274. set -x
  275. fi
  276. # Parsing input arguments (if any)
  277. export INPUT_ARGUMENTS="${@}"
  278. set -u
  279. while [[ $# -gt 0 ]]; do
  280. case $1 in
  281. '--version'|-v)
  282. shift
  283. if [[ $# -ne 0 ]]; then
  284. export DESIRED_VERSION="${1}"
  285. if [[ "$1" != "v"* ]]; then
  286. echo "Expected version arg ('${DESIRED_VERSION}') to begin with 'v', fixing..."
  287. export DESIRED_VERSION="v${1}"
  288. fi
  289. else
  290. echo -e "Please provide the desired version. e.g. --version v4.0.0 or -v canary"
  291. exit 0
  292. fi
  293. ;;
  294. '--no-sudo')
  295. USE_SUDO="false"
  296. ;;
  297. '--help'|-h)
  298. help
  299. exit 0
  300. ;;
  301. *) exit 1
  302. ;;
  303. esac
  304. shift
  305. done
  306. set +u
  307. initArch
  308. initOS
  309. verifySupported
  310. checkDesiredVersion
  311. if ! checkHelmInstalledVersion; then
  312. downloadFile
  313. verifyFile
  314. installFile
  315. fi
  316. testVersion
  317. cleanup