diff --git a/.trunk/.gitignore b/.trunk/.gitignore new file mode 100644 index 0000000..15966d0 --- /dev/null +++ b/.trunk/.gitignore @@ -0,0 +1,9 @@ +*out +*logs +*actions +*notifications +*tools +plugins +user_trunk.yaml +user.yaml +tmp diff --git a/.trunk/configs/.hadolint.yaml b/.trunk/configs/.hadolint.yaml new file mode 100644 index 0000000..98bf0cd --- /dev/null +++ b/.trunk/configs/.hadolint.yaml @@ -0,0 +1,4 @@ +# Following source doesn't work in most setups +ignored: + - SC1090 + - SC1091 diff --git a/.trunk/configs/.markdownlint.yaml b/.trunk/configs/.markdownlint.yaml new file mode 100644 index 0000000..b40ee9d --- /dev/null +++ b/.trunk/configs/.markdownlint.yaml @@ -0,0 +1,2 @@ +# Prettier friendly markdownlint config (all formatting rules disabled) +extends: markdownlint/style/prettier diff --git a/.trunk/configs/.shellcheckrc b/.trunk/configs/.shellcheckrc new file mode 100644 index 0000000..8c7b1ad --- /dev/null +++ b/.trunk/configs/.shellcheckrc @@ -0,0 +1,7 @@ +enable=all +source-path=SCRIPTDIR +disable=SC2154 + +# If you're having issues with shellcheck following source, disable the errors via: +# disable=SC1090 +# disable=SC1091 diff --git a/.trunk/configs/.yamllint.yaml b/.trunk/configs/.yamllint.yaml new file mode 100644 index 0000000..184e251 --- /dev/null +++ b/.trunk/configs/.yamllint.yaml @@ -0,0 +1,7 @@ +rules: + quoted-strings: + required: only-when-needed + extra-allowed: ["{|}"] + key-duplicates: {} + octal-values: + forbid-implicit-octal: true diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml new file mode 100644 index 0000000..28f6672 --- /dev/null +++ b/.trunk/trunk.yaml @@ -0,0 +1,41 @@ +# This file controls the behavior of Trunk: https://docs.trunk.io/cli +# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml +version: 0.1 +cli: + version: 1.22.2 +# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) +plugins: + sources: + - id: trunk + ref: v1.6.0 + uri: https://github.com/trunk-io/plugins +# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes) +runtimes: + enabled: + - go@1.21.0 + - node@18.12.1 + - python@3.10.8 +# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration) +lint: + enabled: + - actionlint@1.7.1 + - checkov@3.2.184 + - git-diff-check + - gofmt@1.20.4 + - golangci-lint@1.59.1 + - hadolint@2.12.0 + - markdownlint@0.41.0 + - osv-scanner@1.8.2 + - prettier@3.3.2 + - shellcheck@0.10.0 + - shfmt@3.6.0 + - taplo@0.8.1 + - trivy@0.52.2 + - trufflehog@3.79.0 + - yamllint@1.35.1 +actions: + enabled: + - trunk-announce + - trunk-check-pre-push + - trunk-fmt-pre-commit + - trunk-upgrade-available diff --git a/go.mod b/go.mod index b0f7f6f..6cb718a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module docwhat.org/temple -go 1.17 +go 1.22.4 require ( github.com/Masterminds/sprig/v3 v3.2.2 diff --git a/script/bootstrap b/script/bootstrap index a0f3424..5be125b 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -1,14 +1,110 @@ -#!/bin/bash -# -# Sets up all the dependencies, etc. so you can build and test. +#!/usr/bin/env bash +# shellcheck disable=SC2310 set -euo pipefail -## GoLangCI Linter -golangci_lint_version="v1.44.2" -golangci_lint_uri="github.com/golangci/golangci-lint/cmd/golangci-lint@${golangci_lint_version}" +root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +readonly root -go install "$golangci_lint_uri" || - go get -u "$golangci_lint_uri" +red=$(tput setaf 1 2>/dev/null || :) +green=$(tput setaf 2 2>/dev/null || :) +yellow=$(tput setaf 3 2>/dev/null || :) +readonly red green yellow -# vim: set ft=sh : +bold=$(tput bold 2>/dev/null || :) +reset=$(tput sgr0 2>/dev/null || :) +readonly bold reset + +readonly checkmark="${green}✔${reset}" +readonly crossmark="${red}✘${reset}" + +failure=0 +success=0 + +has() { + command -v "$1" >/dev/null 2>&1 +} + +success() { + printf "%s %s%s%s\n" \ + "${checkmark}" "${green}${bold}" "$1" "${reset}" + success=$((success + 1)) +} + +failure() { + printf "%s %s%s%s\n" \ + "${crossmark}" "${red}${bold}" "$1" "${reset}" + failure=$((failure + 1)) +} + +instructions() { + printf " %s%s%s\n" "${yellow}${bold}" "$1" "${reset}" +} + +declare _os='' # memoize OS +# os tries to detect the Operating System. +os() { + if [[ -z ${_os} ]]; then + case "$(uname -s)" in + Darwin) _os="macos" ;; + Linux) + if [[ -f /etc/os-release ]]; then + _os=$(sh -c '. /etc/os-release; printf "%s" "${ID}"') + else + echo _os="linux" + fi + ;; + *) _os="unknown" ;; + esac + fi + printf "%s" "${_os}" +} + +## CURL +############################################################ +if has curl; then + success "curl is installed" +else + failure "missing curl" + instructions "Please install curl." + case "$(os)" in + ubuntu | debian) + instructions "On Ubuntu or Debian, you can use:" + instructions " sudo apt-get install curl" + ;; + fedora) + instructions "On Fedora, you can use:" + instructions " sudo dnf install curl" + ;; + macos) + die "On macOS, you should already have curl installed." + ;; + *) ;; + esac +fi + +## Trunk.IO +############################################################ +printf '\n%sInstalling Trunk%s\n' "${bold}" "${reset}" +trunk_args=(install) +if [[ -n ${CI-} ]]; then + trunk_args+=(--no-progress) +fi +if "${root}/script/trunk" "${trunk_args[@]}"; then + success "trunk is installed" +else + failure "unable to install trunk" +fi + +printf '\nsuccesss: %s%d%s failures: %s%d%s\n' "${green}" "${success}" "${reset}" "${red}" "${failure}" "${reset}" + +if ((failure > 0)); then + printf '\n%sBootstrap failed!%s\n' "${red}" "${reset}" + instructions " Please fix the issues above and run this script again." + exit 1 +else + printf '\n%sSuccess! 🎉%s\n' "${green}" "${reset}" + exit 0 +fi + +# EOF diff --git a/script/trunk b/script/trunk new file mode 100755 index 0000000..ab92830 --- /dev/null +++ b/script/trunk @@ -0,0 +1,446 @@ +#!/bin/bash + +############################################################################### +# # +# Setup # +# # +############################################################################### + +set -euo pipefail + +readonly TRUNK_LAUNCHER_VERSION="1.3.1" # warning: this line is auto-updated + +readonly SUCCESS_MARK="\033[0;32m✔\033[0m" +readonly FAIL_MARK="\033[0;31m✘\033[0m" +readonly PROGRESS_MARKS=("⡿" "⢿" "⣻" "⣽" "⣾" "⣷" "⣯" "⣟") + +# This is how mktemp(1) decides where to create stuff in tmpfs. +readonly TMPDIR="${TMPDIR:-/tmp}" + +KERNEL=$(uname | tr "[:upper:]" "[:lower:]") +if [[ ${KERNEL} == mingw64* || ${KERNEL} == msys* ]]; then + KERNEL="mingw" +fi +readonly KERNEL + +MACHINE=$(uname -m) +if [[ $MACHINE == "aarch64" ]]; then + MACHINE="arm64"; +fi + +readonly MACHINE + +PLATFORM="${KERNEL}-${MACHINE}" +readonly PLATFORM + +PLATFORM_UNDERSCORE="${KERNEL}_${MACHINE}" +readonly PLATFORM_UNDERSCORE + +# https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences +# [nF is "cursor previous line" and moves to the beginning of the nth previous line +# [0K is "erase display" and clears from the cursor to the end of the screen +readonly CLEAR_LAST_MSG="\033[1F\033[0K" + +if [[ ! -z ${CI:-} && "${CI}" = true && -z ${TRUNK_LAUNCHER_QUIET:-} ]]; then + TRUNK_LAUNCHER_QUIET=1 +else + TRUNK_LAUNCHER_QUIET=${TRUNK_LAUNCHER_QUIET:-${TRUNK_QUIET:-false}} +fi + +readonly TRUNK_LAUNCHER_DEBUG + +if [[ ${TRUNK_LAUNCHER_QUIET} != false ]]; then + exec 3>&1 4>&2 &>/dev/null +fi + +TRUNK_CACHE="${TRUNK_CACHE:-}" +if [[ -n ${TRUNK_CACHE} ]]; then + : +elif [[ -n ${XDG_CACHE_HOME:-} ]]; then + TRUNK_CACHE="${XDG_CACHE_HOME}/trunk" +else + TRUNK_CACHE="${HOME}/.cache/trunk" +fi +readonly TRUNK_CACHE +readonly CLI_DIR="${TRUNK_CACHE}/cli" +mkdir -p "${CLI_DIR}" + +# platform check +readonly MINIMUM_MACOS_VERSION="10.15" +check_darwin_version() { + local osx_version + osx_version="$(sw_vers -productVersion)" + + # trunk-ignore-begin(shellcheck/SC2312): the == will fail if anything inside the $() fails + if [[ "$(printf "%s\n%s\n" "${MINIMUM_MACOS_VERSION}" "${osx_version}" | + sort --version-sort | + head -n 1)" == "${MINIMUM_MACOS_VERSION}"* ]]; then + return + fi + # trunk-ignore-end(shellcheck/SC2312) + + echo -e "${FAIL_MARK} Trunk requires at least MacOS ${MINIMUM_MACOS_VERSION}" \ + "(yours is ${osx_version}). See https://docs.trunk.io for more info." + exit 1 +} + +if [[ ${PLATFORM} == "darwin-x86_64" || ${PLATFORM} == "darwin-arm64" ]]; then + check_darwin_version +elif [[ ${PLATFORM} == "linux-x86_64" || ${PLATFORM} == "linux-arm64" || ${PLATFORM} == "windows-x86_64" || ${PLATFORM} == "mingw-x86_64" ]]; then + : +else + echo -e "${FAIL_MARK} Trunk is only supported on Linux (x64_64, arm64), MacOS (x86_64, arm64), and Windows (x86_64)." \ + "See https://docs.trunk.io for more info." + exit 1 +fi + +TRUNK_TMPDIR="${TMPDIR}/trunk-$( + set -e + id -u +)/launcher_logs" +readonly TRUNK_TMPDIR +mkdir -p "${TRUNK_TMPDIR}" + +# For the `mv $TOOL_TMPDIR/trunk $TOOL_DIR` to be atomic (i.e. just inode renames), the source and destination filesystems need to be the same +TOOL_TMPDIR=$(mktemp -d "${CLI_DIR}/tmp.XXXXXXXXXX") +readonly TOOL_TMPDIR + +cleanup() { + rm -rf "${TOOL_TMPDIR}" + if [[ $1 == "0" ]]; then + rm -rf "${TRUNK_TMPDIR}" + fi +} +trap 'cleanup $?' EXIT + +# e.g. 2022-02-16-20-40-31-0800 +dt_str() { date +"%Y-%m-%d-%H-%M-%S%z"; } + +LAUNCHER_TMPDIR="${TOOL_TMPDIR}/launcher" +readonly LAUNCHER_TMPDIR +mkdir -p "${LAUNCHER_TMPDIR}" + +if [[ -n ${TRUNK_LAUNCHER_DEBUG:-} ]]; then + set -x +fi + +# launcher awk +# +# BEGIN{ORS="";} +# use "" as the output record separator +# ORS defaults to "\n" for bwk, which results in +# $(printf "foo bar" | awk '{print $2}') == "bar\n" +# +# {gsub(/\r/, "", $0)} +# for every input record (i.e. line), the regex "\r" should be replaced with "" +# This is necessary to handle CRLF files in a portable fashion. +# +# Some StackOverflow answers suggest using RS="\r?\n" to handle CRLF files (RS is the record +# separator, i.e. the line delimiter); unfortunately, original-awk only allows single-character +# values for RS (see https://www.gnu.org/software/gawk/manual/gawk.html#awk-split-records). +lawk() { + awk 'BEGIN{ORS="";}{gsub(/\r/, "", $0)}'"${1}" "${@:2}" +} +awk_test() { + # trunk-ignore-begin(shellcheck/SC2310,shellcheck/SC2312) + # SC2310 and SC2312 are about set -e not propagating to the $(); if that happens, the string + # comparison will fail and we'll claim the user's awk doesn't work + if [[ $( + set -e + printf 'k1: v1\n \tk2: v2\r\n' | lawk '/[ \t]+k2:/{print $2}' + ) == 'v2' && + $( + set -e + printf 'k1: v1\r\n\t k2: v2\r\n' | lawk '/[ \t]+k2:/{print $2}' + ) == 'v2' ]]; then + return + fi + # trunk-ignore-end(shellcheck/SC2310,shellcheck/SC2312) + + echo -e "${FAIL_MARK} Trunk does not work with your awk;" \ + "please report this at https://slack.trunk.io." + echo -e "Your version of awk is:" + awk --version || awk -Wversion + exit 1 +} +awk_test + +readonly CURL_FLAGS="${CURL_FLAGS:- -vvv --max-time 120 --retry 3 --fail}" +readonly WGET_FLAGS="${WGET_FLAGS:- --verbose --tries=3 --limit-rate=10M}" +TMP_DOWNLOAD_LOG="${TRUNK_TMPDIR}/download-$( + set -e + dt_str +).log" +readonly TMP_DOWNLOAD_LOG + +# Detect whether we should use wget or curl. +if command -v wget &>/dev/null; then + download_cmd() { + local url="${1}" + local output_to="${2}" + # trunk-ignore-begin(shellcheck/SC2312): we don't care if wget --version errors + cat >>"${TMP_DOWNLOAD_LOG}" <&1) + +EOF + # trunk-ignore-end(shellcheck/SC2312) + + # Support BusyBox wget + if wget --help 2>&1 | grep BusyBox; then + wget "${url}" -O "${output_to}" 2>>"${TMP_DOWNLOAD_LOG}" & + else + # trunk-ignore(shellcheck/SC2086): we deliberately don't quote WGET_FLAGS + wget ${WGET_FLAGS} "${url}" --output-document "${output_to}" 2>>"${TMP_DOWNLOAD_LOG}" & + fi + } +elif command -v curl &>/dev/null; then + download_cmd() { + local url="${1}" + local output_to="${2}" + # trunk-ignore-begin(shellcheck/SC2312): we don't care if curl --version errors + cat >>"${TMP_DOWNLOAD_LOG}" <>"${TMP_DOWNLOAD_LOG}" & + } +else + download_cmd() { + echo -e "${FAIL_MARK} Cannot download '${url}'; please install curl or wget." + exit 1 + } +fi + +download_url() { + local url="${1}" + local output_to="${2}" + local progress_message="${3:-}" + + if [[ -n ${progress_message} ]]; then + echo -e "${PROGRESS_MARKS[0]} ${progress_message}..." + fi + + download_cmd "${url}" "${output_to}" + local download_pid="$!" + + local i_prog=0 + while [[ -d "/proc/${download_pid}" && -n ${progress_message} ]]; do + echo -e "${CLEAR_LAST_MSG}${PROGRESS_MARKS[${i_prog}]} ${progress_message}..." + sleep 0.2 + i_prog=$(((i_prog + 1) % ${#PROGRESS_MARKS[@]})) + done + + local download_log + if ! wait "${download_pid}"; then + download_log="${TRUNK_TMPDIR}/launcher-download-$( + set -e + dt_str + ).log" + mv "${TMP_DOWNLOAD_LOG}" "${download_log}" + echo -e "${CLEAR_LAST_MSG}${FAIL_MARK} ${progress_message}... FAILED (see ${download_log})" + echo -e "Please check your connection and try again." \ + "If you continue to see this error message," \ + "consider reporting it to us at https://slack.trunk.io." + exit 1 + fi + + if [[ -n ${progress_message} ]]; then + echo -e "${CLEAR_LAST_MSG}${SUCCESS_MARK} ${progress_message}... done" + fi + +} + +# sha256sum is in coreutils, so we prefer that over shasum, which is installed with perl +if command -v sha256sum &>/dev/null; then + : +elif command -v shasum &>/dev/null; then + sha256sum() { shasum -a 256 "$@"; } +else + sha256sum() { + echo -e "${FAIL_MARK} Cannot compute sha256; please install sha256sum or shasum" + exit 1 + } +fi + +############################################################################### +# # +# CLI resolution functions # +# # +############################################################################### + +trunk_yaml_abspath() { + local repo_head + local cwd + + if repo_head=$(git rev-parse --show-toplevel 2>/dev/null); then + echo "${repo_head}/.trunk/trunk.yaml" + elif [[ -f .trunk/trunk.yaml ]]; then + cwd="$(pwd)" + echo "${cwd}/.trunk/trunk.yaml" + else + echo "" + fi +} + +read_cli_version_from() { + local config_abspath="${1}" + local cli_version + + cli_version="$( + set -e + lawk '/[ \t]+version:/{print $2; exit;}' "${config_abspath}" + )" + if [[ -z ${cli_version} ]]; then + echo -e "${FAIL_MARK} Invalid .trunk/trunk.yaml, no cli version found." \ + "See https://docs.trunk.io for more info." >&2 + exit 1 + fi + + echo "${cli_version}" +} + +download_cli() { + local dl_version="${1}" + local expected_sha256="${2}" + local actual_sha256 + + readonly TMP_INSTALL_DIR="${LAUNCHER_TMPDIR}/install" + mkdir -p "${TMP_INSTALL_DIR}" + + TRUNK_NEW_URL_VERSION=0.10.2-beta.1 + if sort --help 2>&1 | grep BusyBox; then + readonly URL="https://trunk.io/releases/${dl_version}/trunk-${dl_version}-${PLATFORM}.tar.gz" + else + if [[ "$(printf "%s\n%s\n" "${TRUNK_NEW_URL_VERSION}" "${dl_version}" | + sort --version-sort | + head -n 1 || true)" == "${TRUNK_NEW_URL_VERSION}"* ]]; then + readonly URL="https://trunk.io/releases/${dl_version}/trunk-${dl_version}-${PLATFORM}.tar.gz" + else + readonly URL="https://trunk.io/releases/trunk-${dl_version}.${KERNEL}.tar.gz" + fi + fi + + readonly DOWNLOAD_TAR_GZ="${TMP_INSTALL_DIR}/download-${dl_version}.tar.gz" + + download_url "${URL}" "${DOWNLOAD_TAR_GZ}" "Downloading Trunk ${dl_version}" + + if [[ -n ${expected_sha256:-} ]]; then + local verifying_text="Verifying Trunk sha256..." + echo -e "${PROGRESS_MARKS[0]} ${verifying_text}" + + actual_sha256="$( + set -e + sha256sum "${DOWNLOAD_TAR_GZ}" | lawk '{print $1}' + )" + + if [[ ${actual_sha256} != "${expected_sha256}" ]]; then + echo -e "${CLEAR_LAST_MSG}${FAIL_MARK} ${verifying_text} FAILED" + echo "Expected sha256: ${expected_sha256}" + echo " Actual sha256: ${actual_sha256}" + exit 1 + fi + + echo -e "${CLEAR_LAST_MSG}${SUCCESS_MARK} ${verifying_text} done" + fi + + local unpacking_text="Unpacking Trunk..." + echo -e "${PROGRESS_MARKS[0]} ${unpacking_text}" + tar --strip-components=1 -C "${TMP_INSTALL_DIR}" -xf "${DOWNLOAD_TAR_GZ}" + echo -e "${CLEAR_LAST_MSG}${SUCCESS_MARK} ${unpacking_text} done" + + rm -f "${DOWNLOAD_TAR_GZ}" + mkdir -p "${TOOL_DIR}" + readonly OLD_TOOL_DIR="${CLI_DIR}/${version}" + # Create a backwards compatability link for old versions of trunk that want to write their + # crashpad_handlers to that dir. + if [[ ! -e ${OLD_TOOL_DIR} ]]; then + ln -sf "${TOOL_PART}" "${OLD_TOOL_DIR}" + fi + mv -n "${TMP_INSTALL_DIR}/trunk" "${TOOL_DIR}/" || true + rm -rf "${TMP_INSTALL_DIR}" +} + +############################################################################### +# # +# CLI resolution # +# # +############################################################################### + +CONFIG_ABSPATH="$( + set -e + trunk_yaml_abspath +)" +readonly CONFIG_ABSPATH + +version="${TRUNK_CLI_VERSION:-}" +if [[ -n ${version:-} ]]; then + : +elif [[ -f ${CONFIG_ABSPATH} ]]; then + version="$( + set -e + read_cli_version_from "${CONFIG_ABSPATH}" + )" + version_sha256="$( + set -e + lawk "/${PLATFORM_UNDERSCORE}:/"'{print $2}' "${CONFIG_ABSPATH}" + )" +else + readonly LATEST_FILE="${LAUNCHER_TMPDIR}/latest" + download_url "https://trunk.io/releases/latest" "${LATEST_FILE}" + version=$( + set -e + lawk '/version:/{print $2}' "${LATEST_FILE}" + ) + version_sha256=$( + set -e + lawk "/${PLATFORM_UNDERSCORE}:/"'{print $2}' "${LATEST_FILE}" + ) +fi + +readonly TOOL_PART="${version}-${PLATFORM}" +readonly TOOL_DIR="${CLI_DIR}/${TOOL_PART}" + +if [[ ! -e ${TOOL_DIR}/trunk ]]; then + download_cli "${version}" "${version_sha256:-}" + echo # add newline between launcher and CLI output +fi + +if [[ ${TRUNK_LAUNCHER_QUIET} != false ]]; then + exec 1>&3 3>&- 2>&4 4>&- +fi + +############################################################################### +# # +# CLI invocation # +# # +############################################################################### + +if [[ -n ${LATEST_FILE:-} ]]; then + mv -n "${LATEST_FILE}" "${TOOL_DIR}/version" >/dev/null 2>&1 || true +fi + +# NOTE: exec will overwrite the process image, so trap will not catch the exit signal. +# Therefore, run cleanup manually here. +cleanup 0 + +exec \ + env TRUNK_LAUNCHER_VERSION="${TRUNK_LAUNCHER_VERSION}" \ + env TRUNK_LAUNCHER_PATH="${BASH_SOURCE[0]}" \ + "${TOOL_DIR}/trunk" "$@"