diff --git a/.editorconfig b/.editorconfig index af8bb198..a72d1fd9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -12,6 +12,12 @@ indent_style = tab indent_style = space indent_size = 2 +# Non-standard config for shfmt +[[shell]] +indent_style = space +indent_size = 2 +simplify = true + [*.py] indent_style = space indent_size = 4 diff --git a/mise.devbase.lock b/mise.devbase.lock index ec76db51..68ba447a 100644 --- a/mise.devbase.lock +++ b/mise.devbase.lock @@ -484,36 +484,36 @@ checksum = "sha256:fd5a6dc69e46a0970593e70d85a7e75f16714e9c61d6d72ccc324eb82df5b url = "https://github.com/gotestyourself/gotestsum/releases/download/v1.13.0/gotestsum_1.13.0_windows_amd64.tar.gz" [[tools.kubeconform]] -version = "0.6.4" +version = "0.8.0" backend = "aqua:yannh/kubeconform" [tools.kubeconform."platforms.linux-arm64"] -checksum = "sha256:582a8a3eb3c33feb065928728ddac6e544c4f0b1234fd78fc1e4c4175a422b22" -url = "https://github.com/yannh/kubeconform/releases/download/v0.6.4/kubeconform-linux-arm64.tar.gz" +checksum = "sha256:1f53fc8e81258197a35e8603054162a5af1de8c5af13746c71ab680d9534ed87" +url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-linux-arm64.tar.gz" [tools.kubeconform."platforms.linux-arm64-musl"] -checksum = "sha256:582a8a3eb3c33feb065928728ddac6e544c4f0b1234fd78fc1e4c4175a422b22" -url = "https://github.com/yannh/kubeconform/releases/download/v0.6.4/kubeconform-linux-arm64.tar.gz" +checksum = "sha256:1f53fc8e81258197a35e8603054162a5af1de8c5af13746c71ab680d9534ed87" +url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-linux-arm64.tar.gz" [tools.kubeconform."platforms.linux-x64"] -checksum = "sha256:2b4ebeaa4d5ac4843cf8f7b7e66a8874252b6b71bc7cbfc4ef1cbf85acec7c07" -url = "https://github.com/yannh/kubeconform/releases/download/v0.6.4/kubeconform-linux-amd64.tar.gz" +checksum = "sha256:9bc2bffbf71f261128533edaf912153948b7ff238f9a531ae6d34466ec287883" +url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-linux-amd64.tar.gz" [tools.kubeconform."platforms.linux-x64-musl"] -checksum = "sha256:2b4ebeaa4d5ac4843cf8f7b7e66a8874252b6b71bc7cbfc4ef1cbf85acec7c07" -url = "https://github.com/yannh/kubeconform/releases/download/v0.6.4/kubeconform-linux-amd64.tar.gz" +checksum = "sha256:9bc2bffbf71f261128533edaf912153948b7ff238f9a531ae6d34466ec287883" +url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-linux-amd64.tar.gz" [tools.kubeconform."platforms.macos-arm64"] -checksum = "sha256:95f9097e54c2c1abe9099d0fedc57a2d5c674b2e32231b901a6e60411899afdd" -url = "https://github.com/yannh/kubeconform/releases/download/v0.6.4/kubeconform-darwin-arm64.tar.gz" +checksum = "sha256:f84f4dfbebf4a6b0b230385fa065a39ea35e02608c2b50d025dcf64775a69d67" +url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-darwin-arm64.tar.gz" [tools.kubeconform."platforms.macos-x64"] -checksum = "sha256:805bbc2ab900925be01b8e72039d3689c0a6d4f8aa4b2fad24dd1243387a2b18" -url = "https://github.com/yannh/kubeconform/releases/download/v0.6.4/kubeconform-darwin-amd64.tar.gz" +checksum = "sha256:71dbc87ac9f24099a62b93570e65aa06312ba6ac8aea63b7f86e9d999edf5a92" +url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-darwin-amd64.tar.gz" [tools.kubeconform."platforms.windows-x64"] -checksum = "sha256:1f6ad9eaab24d14363433897aee4b9049184b10ac3681de0d23105f3a114bf69" -url = "https://github.com/yannh/kubeconform/releases/download/v0.6.4/kubeconform-windows-amd64.zip" +checksum = "sha256:e3f56102bcf4f50b034a567e2482a1c5330799983ddd655952310211aef73d93" +url = "https://github.com/yannh/kubeconform/releases/download/v0.8.0/kubeconform-windows-amd64.zip" [[tools.mage]] version = "1.14.0" @@ -580,29 +580,36 @@ checksum = "sha256:8a4e35ab0b331c85d73567b12f2a444df187f483e5079ceffa6bda1faa2e7 url = "https://github.com/koalaman/shellcheck/releases/download/v0.11.0/shellcheck-v0.11.0.zip" [[tools.shfmt]] -version = "3.7.0" +version = "3.13.1" backend = "aqua:mvdan/sh" [tools.shfmt."platforms.linux-arm64"] -url = "https://github.com/mvdan/sh/releases/download/v3.7.0/shfmt_v3.7.0_linux_arm64" +checksum = "sha256:32d92acaa5cd8abb29fc49dac123dc412442d5713967819d8af2c29f1b3857c7" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_linux_arm64" [tools.shfmt."platforms.linux-arm64-musl"] -url = "https://github.com/mvdan/sh/releases/download/v3.7.0/shfmt_v3.7.0_linux_arm64" +checksum = "sha256:32d92acaa5cd8abb29fc49dac123dc412442d5713967819d8af2c29f1b3857c7" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_linux_arm64" [tools.shfmt."platforms.linux-x64"] -url = "https://github.com/mvdan/sh/releases/download/v3.7.0/shfmt_v3.7.0_linux_amd64" +checksum = "sha256:fb096c5d1ac6beabbdbaa2874d025badb03ee07929f0c9ff67563ce8c75398b1" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_linux_amd64" [tools.shfmt."platforms.linux-x64-musl"] -url = "https://github.com/mvdan/sh/releases/download/v3.7.0/shfmt_v3.7.0_linux_amd64" +checksum = "sha256:fb096c5d1ac6beabbdbaa2874d025badb03ee07929f0c9ff67563ce8c75398b1" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_linux_amd64" [tools.shfmt."platforms.macos-arm64"] -url = "https://github.com/mvdan/sh/releases/download/v3.7.0/shfmt_v3.7.0_darwin_arm64" +checksum = "sha256:9680526be4a66ea1ffe988ed08af58e1400fe1e4f4aef5bd88b20bb9b3da33f8" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_darwin_arm64" [tools.shfmt."platforms.macos-x64"] -url = "https://github.com/mvdan/sh/releases/download/v3.7.0/shfmt_v3.7.0_darwin_amd64" +checksum = "sha256:6feedafc72915794163114f512348e2437d080d0047ef8b8fa2ec63b575f12af" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_darwin_amd64" [tools.shfmt."platforms.windows-x64"] -url = "https://github.com/mvdan/sh/releases/download/v3.7.0/shfmt_v3.7.0_windows_amd64.exe" +checksum = "sha256:60cd368533d0ad73fa86d93d5bbf95ef40587245ce684ed138c1b31557b5fe97" +url = "https://github.com/mvdan/sh/releases/download/v3.13.1/shfmt_v3.13.1_windows_amd64.exe" [[tools.terraform]] version = "1.4.4" diff --git a/mise.devbase.toml b/mise.devbase.toml index 48e6ae57..34c5e5db 100644 --- a/mise.devbase.toml +++ b/mise.devbase.toml @@ -22,9 +22,9 @@ buf = "1.60.0" go-jsonnet = "0.19.1" gofumpt = "0.9.2" golangci-lint = "2.9.0" -kubeconform = "0.6.4" +kubeconform = "0.8.0" shellcheck = "0.11.0" -shfmt = "3.7.0" +shfmt = "3.13.1" terraform = "1.4.4" tombi = "0.7.27" "github:getoutreach/kubecfg" = "0.34.0" diff --git a/orbs/shared/executors/testbed-machine.yml b/orbs/shared/executors/testbed-machine.yml index 12137f41..5db60fb6 100644 --- a/orbs/shared/executors/testbed-machine.yml +++ b/orbs/shared/executors/testbed-machine.yml @@ -1,6 +1,6 @@ description: Standard executor for machine runtimes machine: - image: ubuntu-2404:2026.05.1 + image: ubuntu-2404:current docker_layer_caching: true environment: TEST_RESULTS: /tmp/test-results diff --git a/shell/build-jsonnet.sh b/shell/build-jsonnet.sh index 73423f5a..7064cd77 100755 --- a/shell/build-jsonnet.sh +++ b/shell/build-jsonnet.sh @@ -12,23 +12,19 @@ source "$SCRIPTS_DIR/lib/box.sh" # shellcheck source=./lib/docker.sh source "$SCRIPTS_DIR/lib/docker.sh" +# shellcheck source=./lib/git_cache.sh +source "$SCRIPTS_DIR/lib/git_cache.sh" + # shellcheck source=./lib/mise/stub.sh source "$SCRIPTS_DIR/lib/mise/stub.sh" # Cache a local copy of the `jsonnet-libs` directory on disk if it doesn't yet exist. Do this # because it helps us avoid accessing jsonnet-libs via raw.githubusercontent.com, which has -# aggressive rate limits that we can easily hit. Estimated API usage reduction is +10x since before -# we'd make 1 request per file (15+ *sonnet files), now we clone at most once per run. -JSONNET_LIBS_REPO="$HOME/.outreach/.cache/jsonnet-libs" - -if [[ -d $JSONNET_LIBS_REPO ]]; then - pushd "$JSONNET_LIBS_REPO" >/dev/null || fatal "Could not find jsonnet-libs cache dir" - git pull --quiet - popd >/dev/null || fatal "Could not change directory out of jsonnet-libs cache dir" -else - mkdir -p "$(dirname "$JSONNET_LIBS_REPO")" - git clone --quiet --single-branch git@github.com:getoutreach/jsonnet-libs "$JSONNET_LIBS_REPO" >/dev/null -fi +# aggressive rate limits that we can easily hit. cache_git_repo clones the repo once and then +# refreshes it with a shallow fetch on later runs, instead of one request per file (15+ *sonnet +# files) as before. +info "Caching jsonnet-libs" >&2 +jsonnetLibsCacheDir="$(cache_git_repo https://github.com/getoutreach/jsonnet-libs)" action=$1 @@ -46,7 +42,7 @@ email="${DEV_EMAIL:-$(git config user.email || echo 'devbase@outreach.io')}" appImageRegistry="${DEVENV_DEPLOY_IMAGE_REGISTRY:-"$(get_docker_pull_registry)"}" mise_exec_tool_with_bin github:getoutreach/kubecfg kubecfg \ - --jpath "$JSONNET_LIBS_REPO" \ + --jpath "$jsonnetLibsCacheDir" \ --jurl http://k8s-clusters.outreach.cloud/ \ -n "$namespace" \ --context "dev-environment" "$action" "$(get_repo_directory)/$jsonnetSourcePath/$jsonnetManifestPath" \ diff --git a/shell/kubeconform.sh b/shell/kubeconform.sh new file mode 100755 index 00000000..db5982ff --- /dev/null +++ b/shell/kubeconform.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# This is a wrapper around mise to run kubeconform. +# Useful for using the correct version of kubeconform +# with your editor, with the correct cache. +set -euo pipefail + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" + +# shellcheck source=./lib/git_cache.sh +source "$DIR/lib/git_cache.sh" + +# shellcheck source=./lib/mise/stub.sh +source "$DIR/lib/mise/stub.sh" + +# The Kubernetes version we validate against. Owned here (not passed in by +# callers) so we can both cache the matching schemas and tell kubeconform +# which version to use. +k8sVersion="$(get_tool_version kubernetes)" +# get_tool_version prints "null" when the key is missing. Guard against it: +# an unset version would cache non-existent "vnull-standalone" dirs and, with +# -ignore-missing-schemas, silently skip all validation while reporting green. +if [[ -z $k8sVersion || $k8sVersion == "null" ]]; then + fatal "Could not determine the kubernetes version from versions.yaml" +fi + +# Do not add default to this list. Verified in v0.8.0, the magic +# "default" value adds the raw.githubusercontent.com URL template for +# kubernetes-json-schema, which is rate limited by GitHub. +schemaLocations=() + +# Cache the kubernetes-json-schema repo. Pass the repo root as the schema +# location (no .json template): for a bare directory, kubeconform appends +# v-standalone{-strict}/.json itself. Sparse-checkout only the +# version directories we need, since the full repo is multiple GB. +info "Schema cache: Kubernetes" >&2 +k8sCacheDir="$(cache_git_repo https://github.com/yannh/kubernetes-json-schema kubeconform \ + "v${k8sVersion}-standalone" "v${k8sVersion}-standalone-strict")" +schemaLocations+=("$k8sCacheDir") + +# Cache the CRDs catalog. It is small (~20 MB), so clone it in full and use +# its explicit path template. +info "Schema cache: CRDs catalog" >&2 +crdCacheDir="$(cache_git_repo https://github.com/datreeio/CRDs-catalog kubeconform)" +schemaLocations+=("$crdCacheDir/{{ .Group }}/{{ .ResourceKind }}_{{ .ResourceAPIVersion }}.json") + +args=(-kubernetes-version "$k8sVersion") +for location in "${schemaLocations[@]}"; do + args+=(-schema-location "$location") +done + +mise_exec_tool kubeconform "${args[@]}" "$@" diff --git a/shell/lib/git_cache.sh b/shell/lib/git_cache.sh new file mode 100644 index 00000000..dc805595 --- /dev/null +++ b/shell/lib/git_cache.sh @@ -0,0 +1,84 @@ +#!/usr/bin/env bash + +DEVBASE_CACHE_DIR="$HOME/.outreach/.cache" + +# cache_git_repo [cacheSubdir] [sparsePath...] +# +# Cache the given git repository URL to avoid using +# raw.githubusercontent.com URLs, which are rate limited by GitHub. +# For network/space reasons, this uses a shallow checkout. +# +# If specified, cacheSubdir namespaces the cached git repo. +# +# If one or more sparsePath arguments are given, only those top-level +# paths are materialized (via a blobless, sparse checkout). This avoids +# checking out enormous repositories in full when only a few directories +# are needed. +# +# Prints out the cache directory path. +# +# Assumes that logging.sh is sourced. (Logs are sent to stderr.) +cache_git_repo() { + local gitURL="$1" + local cacheSubdir="${2:-}" + # Drop the two leading positional args so "$@" is just the sparse paths. + # `shift 2` fails when fewer than two args were passed (e.g. a caller that + # omits cacheSubdir), so fall back to shifting whatever is present. + shift 2 || shift $# + local sparsePaths=("$@") + + # Derive the cache dir name from the repo's last path segment. Normalize a + # trailing slash and a ".git" suffix first so equivalent URLs map to the + # same name. Callers caching repos whose last segment could collide must + # pass distinct cacheSubdir values to disambiguate them. + local cacheDir cacheBasename normalizedURL="${gitURL%/}" + cacheBasename="$(basename "${normalizedURL%.git}")" + if [[ -n $cacheSubdir ]]; then + cacheDir="$DEVBASE_CACHE_DIR/$cacheSubdir/$cacheBasename" + else + cacheDir="$DEVBASE_CACHE_DIR/$cacheBasename" + fi + + # True if every requested sparse path is present and non-empty. A blobless + # sparse checkout can leave a path unmaterialized after a failed fetch. + _sparse_paths_materialized() { + local path + for path in "${sparsePaths[@]}"; do + [[ -n "$(find "$cacheDir/$path" -type f -print -quit 2>/dev/null)" ]] || return 1 + done + } + + if [[ -d $cacheDir ]] && git -C "$cacheDir" rev-parse --git-dir >/dev/null 2>&1; then + info_sub "Updating local cache" >&2 + # A usable checkout already exists; tolerate a transient refresh failure. + if ! { git -C "$cacheDir" fetch --depth 1 && + git -C "$cacheDir" reset --hard -q origin/HEAD; }; then + warn "Could not refresh cache at $cacheDir; using the existing checkout" >&2 + fi + if [[ ${#sparsePaths[@]} -gt 0 ]]; then + # Local re-apply; tolerate failure so a blip cannot abort a usable cache. + if ! git -C "$cacheDir" sparse-checkout set "${sparsePaths[@]}"; then + warn "Could not update sparse paths at $cacheDir; using the existing checkout" >&2 + fi + # A cache missing its schemas would let -ignore-missing-schemas pass + # vacuously; fail loudly instead. + if ! _sparse_paths_materialized; then + fatal "Cache at $cacheDir is missing requested paths: ${sparsePaths[*]}" + fi + fi + else + # A leftover directory that is not a healthy git repo (e.g. an + # interrupted clone) is treated as a cache miss: remove it and re-clone. + [[ -d $cacheDir ]] && rm -rf "$cacheDir" + info_sub "Setting up local cache" >&2 + if [[ ${#sparsePaths[@]} -gt 0 ]]; then + git clone --depth 1 --single-branch --filter=blob:none --sparse \ + "$gitURL" "$cacheDir" + git -C "$cacheDir" sparse-checkout set "${sparsePaths[@]}" + else + git clone --depth 1 --single-branch "$gitURL" "$cacheDir" + fi + fi + + echo "$cacheDir" +} diff --git a/shell/lib/git_cache_test.bats b/shell/lib/git_cache_test.bats new file mode 100644 index 00000000..3d92ec27 --- /dev/null +++ b/shell/lib/git_cache_test.bats @@ -0,0 +1,146 @@ +#!/usr/bin/env bash + +# Required for `run --separate-stderr` (test 1), which isolates stdout. +bats_require_minimum_version 1.5.0 + +bats_load_library "bats-support/load.bash" +bats_load_library "bats-assert/load.bash" + +load git_cache.sh +load logging.sh +load test_helper.sh + +setup() { + TESTROOT="$(mktempdir devbase-git-cache-XXXXXX)" + + # Build a local "origin" repo that mimics a schema repo layout, so tests + # never touch the network. cache_git_repo clones it via a file:// URL. + ORIGIN="$TESTROOT/origin" + git init -q --initial-branch=main "$ORIGIN" + git -C "$ORIGIN" config user.email "git-cache@devbase.test" + git -C "$ORIGIN" config user.name "Test User" + mkdir -p "$ORIGIN/v1.25.16-standalone-strict" \ + "$ORIGIN/v1.26.0-standalone-strict" "$ORIGIN/unwanted" + echo '{"type":"object"}' >"$ORIGIN/v1.25.16-standalone-strict/deployment-apps-v1.json" + echo '{"type":"object"}' >"$ORIGIN/v1.26.0-standalone-strict/deployment-apps-v1.json" + echo 'junk' >"$ORIGIN/unwanted/big.txt" + git -C "$ORIGIN" add -A + git -C "$ORIGIN" commit -qm "initial schemas" + + # Redirect the cache root at the temp dir instead of $HOME/.outreach/.cache. + DEVBASE_CACHE_DIR="$TESTROOT/cache" +} + +teardown() { + rm -rf "$TESTROOT" +} + +@test "cache_git_repo without sparse paths clones the full working tree" { + # Use --separate-stderr so $output is only the cache dir path; the function + # and git clone log to stderr. + run --separate-stderr cache_git_repo "file://$ORIGIN" kubeconform + assert_success + local cacheDir="$DEVBASE_CACHE_DIR/kubeconform/origin" + assert_output "$cacheDir" + assert [ -f "$cacheDir/v1.25.16-standalone-strict/deployment-apps-v1.json" ] + assert [ -f "$cacheDir/v1.26.0-standalone-strict/deployment-apps-v1.json" ] + assert [ -f "$cacheDir/unwanted/big.txt" ] +} + +@test "cache_git_repo with sparse paths materializes only those directories" { + run cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + assert_success + local cacheDir="$DEVBASE_CACHE_DIR/kubeconform/origin" + assert [ -f "$cacheDir/v1.25.16-standalone-strict/deployment-apps-v1.json" ] + assert [ ! -d "$cacheDir/v1.26.0-standalone-strict" ] + assert [ ! -d "$cacheDir/unwanted" ] +} + +@test "cache_git_repo accepts multiple sparse paths" { + run cache_git_repo "file://$ORIGIN" kubeconform \ + v1.25.16-standalone-strict v1.26.0-standalone-strict + assert_success + local cacheDir="$DEVBASE_CACHE_DIR/kubeconform/origin" + assert [ -d "$cacheDir/v1.25.16-standalone-strict" ] + assert [ -d "$cacheDir/v1.26.0-standalone-strict" ] + assert [ ! -d "$cacheDir/unwanted" ] +} + +@test "cache_git_repo updates an existing sparse cache and preserves sparsity" { + cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + # Second call hits the update path (cache dir already exists). + run cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + assert_success + local cacheDir="$DEVBASE_CACHE_DIR/kubeconform/origin" + assert [ -f "$cacheDir/v1.25.16-standalone-strict/deployment-apps-v1.json" ] + assert [ ! -d "$cacheDir/unwanted" ] +} + +@test "cache_git_repo sparse update prints only the cache dir path on stdout" { + # Warm the cache, then update. The update path must not leak git reset + # output (e.g. "HEAD is now at ...") onto stdout; callers capture stdout as + # the cache dir path, so it must be EXACTLY the path. + cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + run --separate-stderr cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + assert_success + local cacheDir="$DEVBASE_CACHE_DIR/kubeconform/origin" + assert_output "$cacheDir" +} + +@test "cache_git_repo non-sparse update prints only the cache dir path on stdout" { + cache_git_repo "file://$ORIGIN" kubeconform + run --separate-stderr cache_git_repo "file://$ORIGIN" kubeconform + assert_success + local cacheDir="$DEVBASE_CACHE_DIR/kubeconform/origin" + assert_output "$cacheDir" +} + +@test "cache_git_repo warns and reuses the checkout when a warm-cache fetch fails" { + # Warm the cache, then make origin unreachable to simulate a transient + # network failure on a later run. A usable local checkout already exists, + # so the fetch failure must be tolerated: the function warns (on stderr) + # and returns the cache dir with the previously-materialized schemas intact. + cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + rm -rf "$ORIGIN" + + run --separate-stderr cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + assert_success + local cacheDir="$DEVBASE_CACHE_DIR/kubeconform/origin" + assert_output "$cacheDir" + assert [ -f "$cacheDir/v1.25.16-standalone-strict/deployment-apps-v1.json" ] + # The failure is reported clearly rather than leaking a bare git "fatal:". + assert [ -n "$stderr" ] +} + +@test "cache_git_repo self-heals a corrupt (non-git) cache dir" { + # Simulate an interrupted clone: the cache dir exists but is not a git + # repo. The old code took the update path and `git fetch` errored; the + # function must instead treat this as a cache miss and re-clone. + local cacheDir="$DEVBASE_CACHE_DIR/kubeconform/origin" + mkdir -p "$cacheDir" + echo 'partial junk' >"$cacheDir/leftover.txt" + + run --separate-stderr cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + assert_success + assert_output "$cacheDir" + # Proves it re-cloned rather than aborting on the corrupt dir. + assert [ -f "$cacheDir/v1.25.16-standalone-strict/deployment-apps-v1.json" ] +} + +@test "cache_git_repo fails when the cache is missing requested paths and cannot refresh" { + # A cache dir can be a healthy git repo yet lack the requested schema + # directory (e.g. a blobless fetch failed while switching versions). If it + # cannot be refreshed, returning it would let kubeconform + # -ignore-missing-schemas silently pass without validating anything, so the + # function must fail loudly instead. + cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + local cacheDir="$DEVBASE_CACHE_DIR/kubeconform/origin" + # Remove the materialized schemas but leave the git repo intact, and make + # origin unreachable so the refresh cannot restore them. + rm -rf "$cacheDir/v1.25.16-standalone-strict" + rm -rf "$ORIGIN" + + run cache_git_repo "file://$ORIGIN" kubeconform v1.25.16-standalone-strict + assert_failure + assert_output --partial "missing requested paths" +} diff --git a/shell/lib/github.sh b/shell/lib/github.sh index 5516d4aa..e98bfb11 100644 --- a/shell/lib/github.sh +++ b/shell/lib/github.sh @@ -85,6 +85,13 @@ install_latest_github_release() { info "Using $slug:${binary_name} version: ($tag)" + # Strip a leading `v` (before a digit) so mise receives a normalized version. + # mise 2026.6.14+ drops the `v` when installing a pre-release tag but re-adds + # it during `mise which`/`mise where`, so the tool resolves as not installed. + if [[ $tag =~ ^v[0-9] ]]; then + tag="${tag#v}" + fi + # Need to export GITHUB_TOKEN so that future calls to `mise` # continue to use it for the configured private repos. if [[ -z ${GITHUB_TOKEN:-} ]]; then diff --git a/shell/lib/github_test.bats b/shell/lib/github_test.bats index dff6c25c..50bf27c1 100644 --- a/shell/lib/github_test.bats +++ b/shell/lib/github_test.bats @@ -18,8 +18,11 @@ setup() { export MISE_GLOBAL_CONFIG_ROOT="$MISE_CONFIG_DIR" export MISE_GLOBAL_CONFIG_FILE="$MISE_CONFIG_DIR/global.toml" export MISE_OVERRIDE_CONFIG_FILENAMES="global.toml" - echo '[tools]' >>"$MISE_GLOBAL_CONFIG_FILE" - echo 'github-cli = "latest"' >>"$MISE_GLOBAL_CONFIG_FILE" + { + echo '[tools]' + echo 'github-cli = "latest"' + echo 'wait-for-gh-rate-limit = "1.1.1"' + } >>"$MISE_GLOBAL_CONFIG_FILE" } teardown() { @@ -49,6 +52,7 @@ teardown() { assert mise which stencil run "$(mise which stencil)" --version + assert_success } @test "install_latest_github_release should be able to download and install the latest pre-release of a repo" { @@ -61,5 +65,6 @@ teardown() { assert mise which stencil run "$(mise which stencil)" --version + assert_success assert_output --regexp "(rc|unstable)" } diff --git a/shell/linters/kubecfg.sh b/shell/linters/kubecfg.sh index fd1c93a1..11dc9300 100644 --- a/shell/linters/kubecfg.sh +++ b/shell/linters/kubecfg.sh @@ -20,12 +20,9 @@ kubecfg_kubeconform() { return 1 fi - if ! mise_exec_tool kubeconform \ - -schema-location default \ + if ! "$DIR/kubeconform.sh" \ -ignore-missing-schemas \ -strict \ - -kubernetes-version "$(get_tool_version kubernetes)" \ - -schema-location 'https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json' \ <"$tempFile"; then error "Failed to validate generated YAML" return 1 diff --git a/shell/shfmt.sh b/shell/shfmt.sh index 73a655c8..2e0f2634 100755 --- a/shell/shfmt.sh +++ b/shell/shfmt.sh @@ -9,7 +9,4 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" # shellcheck source=./lib/mise/stub.sh source "$DIR/lib/mise/stub.sh" -# Always set simplify mode. -args=("--simplify" "$@") - -mise_exec_tool shfmt "${args[@]}" +mise_exec_tool shfmt "$@"