diff --git a/.github/actions/create-kind-cluster/action.yaml b/.github/actions/create-kind-cluster/action.yaml index 530ba02f053..7b904a4dcf9 100644 --- a/.github/actions/create-kind-cluster/action.yaml +++ b/.github/actions/create-kind-cluster/action.yaml @@ -12,10 +12,6 @@ inputs: description: The temporary directory where the certificates are stored required: false default: "" - kind-version: - description: The version of KinD to install - required: false - default: v0.29.0 with-local-registry: description: Whether the KinD cluster should be created with a local registry configuration required: false @@ -36,10 +32,10 @@ runs: using: composite steps: - name: Install KinD + # Installs the version pinned in build/tools.mk (KIND_VERSION) and adds the + # install dir to the job PATH, so the cluster-create steps below find kind. shell: bash - run: | - curl -sSLo "kind" "https://github.com/kubernetes-sigs/kind/releases/download/${{ inputs.kind-version }}/kind-linux-amd64" - chmod +x ./kind + run: make install-kind - name: Create a KinD cluster without a local registry if: ${{ inputs.with-local-registry == 'false' }} diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 7992713dbc0..3f31960ef9b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -55,9 +55,6 @@ env: # Local file path to the release binaries. RELEASE_PATH: ./release - # ORAS (OCI Registry As Storage) CLI version - ORAS_VERSION: 1.1.0 - # URL to get source code for building the image IMAGE_SRC: https://github.com/radius-project/radius @@ -158,10 +155,10 @@ jobs: username: ${{ github.actor }} password: ${{ github.token }} - - uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2.0.0 + - name: Install oras if: needs.changes.outputs.only_changed != 'true' - with: - version: ${{ env.ORAS_VERSION }} + # Version pinned in build/tools.mk (ORAS_VERSION). + run: make install-oras - name: Push latest rad cli binary to GHCR (unix-like) if: github.ref == 'refs/heads/main' && matrix.target_os != 'windows' && needs.changes.outputs.only_changed != 'true' @@ -329,9 +326,8 @@ jobs: persist-credentials: false - name: Install helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 - with: - version: v4.2.2 + # Version pinned in build/tools.mk (HELM_VERSION). + run: make install-helm - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 7c507e25ffc..5cc3a2f2de9 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -100,20 +100,13 @@ jobs: with: python-version-file: .python-version - - name: Setup Helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 - with: - version: v4.2.2 + - name: Install helm + # Version pinned in build/tools.mk (HELM_VERSION). + run: make install-helm - name: Install kubectl - run: | - KUBECTL_VER="v1.30.0" - KUBECTL_LINUX_AMD64_SHA256="7c3807c0f5c1b30110a2ff1e55da1d112a6d0096201f1beb81b269f582b5d1c5" - curl -fsSLo ./kubectl "https://dl.k8s.io/release/${KUBECTL_VER}/bin/linux/amd64/kubectl" - echo "${KUBECTL_LINUX_AMD64_SHA256} ./kubectl" | sha256sum -c - - chmod +x ./kubectl - sudo mv ./kubectl /usr/local/bin/kubectl - kubectl version --client + # Version pinned in build/tools.mk (KUBECTL_VERSION). + run: make install-kubectl - name: Install pnpm and TypeSpec tooling # `make generate-tsp-installed` enables corepack with the pinned pnpm @@ -167,13 +160,8 @@ jobs: - name: Install KinD # Lets the agent create a local Kubernetes cluster for integration and - # functional tests. - run: | - KIND_VER="v0.29.0" - curl -fsSLo ./kind "https://kind.sigs.k8s.io/dl/${KIND_VER}/kind-linux-amd64" - chmod +x ./kind - sudo mv ./kind /usr/local/bin/kind - kind --version + # functional tests. Version pinned in build/tools.mk (KIND_VERSION). + run: make install-kind - name: Install k3d # Used by the local debug environment (make debug-start) to create a @@ -195,11 +183,8 @@ jobs: stern --version - name: Install Dapr CLI - run: | - DAPR_CLI_VER="1.15.1" - wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - \ - | /bin/bash -s "${DAPR_CLI_VER}" - dapr --version + # Version pinned in build/tools.mk (DAPR_VERSION). + run: make install-dapr - name: Install Terraform # Version is read from .terraform-version, the source of truth also diff --git a/.github/workflows/functional-test-cloud.yaml b/.github/workflows/functional-test-cloud.yaml index 8e4af70115c..466959c0cc6 100644 --- a/.github/workflows/functional-test-cloud.yaml +++ b/.github/workflows/functional-test-cloud.yaml @@ -67,12 +67,6 @@ concurrency: env: GOPROXY: https://proxy.golang.org - # Helm version - HELM_VER: v4.2.2 - # KinD cluster version - KIND_VER: v0.29.0 - # Kubectl version - KUBECTL_VER: v1.25.0 # Azure Keyvault CSI driver chart version AZURE_KEYVAULT_CSI_DRIVER_VER: 1.4.2 # Azure workload identity webhook chart version @@ -147,7 +141,7 @@ jobs: run: | # Same-repo PRs are always trusted (requires write access to push branches) if [ "${HEAD_REPO}" = "${BASE_REPO}" ]; then - echo "Same-repo PR from ${PR_AUTHOR} — trusted" + echo "Same-repo PR from ${PR_AUTHOR} - trusted" echo "is-external=false" >> "${GITHUB_OUTPUT}" exit 0 fi @@ -156,10 +150,10 @@ jobs: # Uses app token which can read org membership regardless of visibility settings. # gh api returns exit code 0 for 204 (member) and non-zero for 404/302 (not a member). if gh api "orgs/${ORG}/members/${PR_AUTHOR}" --silent 2>/dev/null; then - echo "Fork PR from org member ${PR_AUTHOR} — trusted" + echo "Fork PR from org member ${PR_AUTHOR} - trusted" echo "is-external=false" >> "${GITHUB_OUTPUT}" else - echo "Fork PR from ${PR_AUTHOR} — external" + echo "Fork PR from ${PR_AUTHOR} - external" echo "is-external=true" >> "${GITHUB_OUTPUT}" fi @@ -182,7 +176,7 @@ jobs: # approval gate so every downstream job keys off one result # (needs.authorize.result == 'success') instead of repeating the boolean. # - # Runs unless the whole run was cancelled, then fails — blocking the run — when: + # Runs unless the whole run was cancelled, then fails - blocking the run - when: # - check-trust did not pass (fail-safe if the trust check itself errored), or # - approval-gate did not pass (an external contributor's approval was # rejected/'cancelled' or failed). @@ -202,11 +196,11 @@ jobs: APPROVAL_GATE_RESULT: ${{ needs.approval-gate.result }} run: | if [ "${CHECK_TRUST_RESULT}" != "success" ] && [ "${CHECK_TRUST_RESULT}" != "skipped" ]; then - echo "::error::Blocked — trust check did not pass (result=${CHECK_TRUST_RESULT})." + echo "::error::Blocked - trust check did not pass (result=${CHECK_TRUST_RESULT})." exit 1 fi if [ "${APPROVAL_GATE_RESULT}" != "success" ] && [ "${APPROVAL_GATE_RESULT}" != "skipped" ]; then - echo "::error::Blocked — approval was not granted (result=${APPROVAL_GATE_RESULT})." + echo "::error::Blocked - approval was not granted (result=${APPROVAL_GATE_RESULT})." exit 1 fi echo "Authorized to proceed." @@ -426,7 +420,6 @@ jobs: |**Unique ID** | ${{ env.UNIQUE_ID }} | |**Image tag** | ${{ env.REL_VERSION }} | - * KinD: ${{ env.KIND_VER }} * Dapr: ${{ env.DAPR_VER }} * Azure KeyVault CSI driver: ${{ env.AZURE_KEYVAULT_CSI_DRIVER_VER }} * Azure Workload identity webhook: ${{ env.AZURE_WORKLOAD_IDENTITY_WEBHOOK_VER }} @@ -758,9 +751,9 @@ jobs: RESOURCE_GROUP: ${{ env.AZURE_TEST_RESOURCE_GROUP }} AZURE_SUBSCRIPTIONID_TESTS: ${{ secrets.AZURE_SUBSCRIPTIONID_TESTS }} - - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 - with: - version: ${{ env.HELM_VER }} + - name: Install helm + # Version pinned in build/tools.mk (HELM_VERSION). + run: make install-helm # The role-to-assume is the role that the github action will assume to execute aws commands and # construct cloud control client in test code. @@ -771,6 +764,11 @@ jobs: role-session-name: GitHub_to_AWS_via_FederatedOIDC aws-region: ${{ env.AWS_REGION }} + - name: Install KinD + # Installs the version pinned in build/tools.mk (KIND_VERSION) and adds + # the install dir to the job PATH for the cluster-create step below. + run: make install-kind + # create kind cluster with OIDC provider. - name: Create KinD cluster env: @@ -779,9 +777,6 @@ jobs: run: | set -euo pipefail - curl -sSLo "kind" "https://github.com/kubernetes-sigs/kind/releases/download/${KIND_VER}/kind-linux-amd64" - chmod +x ./kind - # Parse and validate Azure workload identity values from secret JSON. OIDC_JSON="${FUNCTEST_AZURE_OIDC_JSON}" if ! echo "${OIDC_JSON}" | jq -e . >/dev/null; then @@ -803,7 +798,7 @@ jobs: openssl pkey -pubin -in sa.pub -noout >/dev/null openssl pkey -in sa.key -check -noout >/dev/null - cat < "${HOST_KUBECONFIG}" + kind get kubeconfig --name external > "${HOST_KUBECONFIG}" EXTERNAL_IP="$(docker inspect -f '{{(index .NetworkSettings.Networks "kind").IPAddress}}' external-control-plane)" if [ -z "${EXTERNAL_IP}" ]; then @@ -389,7 +380,7 @@ jobs: MAGPIE_HOST_IMAGE="${LOCAL_REGISTRY_SERVER}:${LOCAL_REGISTRY_PORT}/magpiego:${REL_VERSION}" MAGPIE_CLUSTER_IMAGE="${LOCAL_REGISTRY_NAME}:${LOCAL_REGISTRY_PORT}/magpiego:${REL_VERSION}" docker tag "${MAGPIE_HOST_IMAGE}" "${MAGPIE_CLUSTER_IMAGE}" - ./kind load docker-image "${MAGPIE_CLUSTER_IMAGE}" --name external + kind load docker-image "${MAGPIE_CLUSTER_IMAGE}" --name external # Pre-create the radius-system namespace and the target-kubeconfig secret # so the secret exists before the Radius pods start (the deployments mount @@ -529,11 +520,14 @@ jobs: echo "Port forwarding established successfully" - - name: Install Dapr CLI and control plane + - name: Install Dapr CLI if: matrix.name == 'daprrp-noncloud' - run: | - wget -q https://raw.githubusercontent.com/dapr/cli/master/install/install.sh -O - | /bin/bash -s "${DAPR_CLI_VER}" - dapr init -k --wait --timeout 600 --runtime-version "${DAPR_RUNTIME_VER}" --dashboard-version "${DAPR_DASHBOARD_VER}" + # Version pinned in build/tools.mk (DAPR_VERSION). + run: make install-dapr + + - name: Install Dapr control plane + if: matrix.name == 'daprrp-noncloud' + run: dapr init -k --wait --timeout 600 --runtime-version "${DAPR_RUNTIME_VER}" --dashboard-version "${DAPR_DASHBOARD_VER}" - name: Publish Terraform test recipes run: | diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 15676d00e91..b83d03d2b3b 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -79,9 +79,8 @@ jobs: run: make install-yq - name: Install helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 - with: - version: v4.2.2 + # Version pinned in build/tools.mk (HELM_VERSION). + run: make install-helm - name: Run Helm linter run: | diff --git a/.github/workflows/unit-tests.yaml b/.github/workflows/unit-tests.yaml index a4bb57e0791..28187ec8b2d 100644 --- a/.github/workflows/unit-tests.yaml +++ b/.github/workflows/unit-tests.yaml @@ -67,9 +67,8 @@ jobs: # Helm is required because `make test` depends on the `test-helm` target, # which installs the helm-unittest plugin and runs the chart unit tests. - name: Install helm - uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0 - with: - version: v4.2.2 + # Version pinned in build/tools.mk (HELM_VERSION). + run: make install-helm - name: Run make test (unit tests) env: diff --git a/.github/workflows/validate-installers.yaml b/.github/workflows/validate-installers.yaml index 98406c4008c..5b1419347b7 100644 --- a/.github/workflows/validate-installers.yaml +++ b/.github/workflows/validate-installers.yaml @@ -116,7 +116,9 @@ jobs: with: persist-credentials: false - - uses: oras-project/setup-oras@38de303aac69abb66f3e6255b7198bff35f323e3 # v2.0.0 + - name: Install oras + # Version pinned in build/tools.mk (ORAS_VERSION). + run: make install-oras - name: Run installer test script run: deploy/test-install.sh diff --git a/build/scripts/install-dapr.sh b/build/scripts/install-dapr.sh new file mode 100755 index 00000000000..3e3f99d98a7 --- /dev/null +++ b/build/scripts/install-dapr.sh @@ -0,0 +1,180 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Installs the Dapr CLI (the 'dapr' binary) into a user-owned directory (no sudo) +# for the current platform. Works on linux and darwin, amd64 and arm64, for both +# CI and local development; under GitHub Actions the install dir is added to the +# job PATH so later steps can run dapr. +# +# The Dapr CLI is published as a per-platform tarball on dapr/cli's GitHub +# releases. The pinned version and per-platform SHA-256 checksums (of the +# tarball) are normally provided by build/tools.mk through the environment. The +# script is generic, so when a value is not supplied it is resolved at runtime: +# * empty DAPR_VERSION -> the latest published release +# * missing checksum for platform -> read from the release's own +# '.sha256' file +# +# Usage: install-dapr.sh [install_dir] +# +# Environment (all optional): +# DAPR_VERSION Release tag, e.g. v1.15.1. Empty selects latest. +# DAPR_CHECKSUM__ SHA-256 of the tarball for that platform (e.g. +# DAPR_CHECKSUM_LINUX_AMD64). Empty fetches +# it from the release's '.sha256' file. +# DAPR_INSTALL_DIR Install directory. Default: $HOME/.local/bin. +# GITHUB_TOKEN If set, authenticates GitHub requests (higher +# rate limits; required for private repositories). + +readonly REPO="dapr/cli" +readonly RELEASES_URL="https://github.com/${REPO}/releases" + +log() { echo "[install-dapr] $*" >&2; } +fail() { + echo "[install-dapr] ERROR: $*" >&2 + exit 1 +} + +# Temporary working directory for downloads, removed on exit. Uses an explicit +# 'if' (not '&&') so the function returns 0 when WORKDIR is unset; otherwise the +# failing test would become the EXIT trap's status and abort an otherwise +# successful run, e.g. the early return when the tool is already installed. +WORKDIR="" +cleanup() { + if [ -n "${WORKDIR:-}" ] && [ -d "${WORKDIR}" ]; then + rm -rf "${WORKDIR}" + fi +} + +# curl wrapper for GitHub requests: enforces HTTPS + TLS 1.2, sets a User-Agent, +# and adds an Authorization header when GITHUB_TOKEN is set (raises API rate +# limits and allows private repositories). curl drops the Authorization header on +# cross-host redirects, so the token is not sent to the download CDN. The array is +# seeded with the User-Agent so it is never empty -- expanding an empty array +# under 'set -u' is an error on bash 3.2 (macOS). +gh_curl() { + local headers=(-H "User-Agent: dapr-cli-installer") + if [ -n "${GITHUB_TOKEN:-}" ]; then + headers+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") + fi + curl --proto '=https' --tlsv1.2 "${headers[@]}" "$@" +} + +detect_os() { + case "$(uname -s)" in + Linux) echo "linux" ;; + Darwin) echo "darwin" ;; + *) fail "unsupported OS '$(uname -s)' (supported: Linux, Darwin)" ;; + esac +} + +detect_arch() { + case "$(uname -m)" in + x86_64 | amd64) echo "amd64" ;; + aarch64 | arm64) echo "arm64" ;; + *) fail "unsupported architecture '$(uname -m)' (supported: amd64, arm64)" ;; + esac +} + +# Resolve the latest release tag by following the /releases/latest redirect. +# Avoids the GitHub API (no token, no rate limit). +resolve_latest_version() { + local effective_url + effective_url="$(gh_curl -fsSLI -o /dev/null -w '%{url_effective}' "${RELEASES_URL}/latest")" \ + || fail "could not resolve the latest Dapr CLI version" + printf '%s\n' "${effective_url##*/tag/}" +} + +# Print the SHA-256 of an asset, read from the release's own published +# '.sha256' file. dapr/cli publishes one per release artifact; each +# contains a single ' ' line. +checksum_from_release() { + local version="$1" asset="$2" + gh_curl -fsSL "${RELEASES_URL}/download/${version}/${asset}.sha256" -o "${WORKDIR}/${asset}.sha256" \ + || fail "could not download ${asset}.sha256 for ${version}" + awk 'NR == 1 { print $1 }' "${WORKDIR}/${asset}.sha256" +} + +verify_checksum() { + local expected="$1" file="$2" + if command -v sha256sum >/dev/null 2>&1; then + echo "${expected} ${file}" | sha256sum -c - >/dev/null + elif command -v shasum >/dev/null 2>&1; then + echo "${expected} ${file}" | shasum -a 256 -c - >/dev/null + else + fail "neither sha256sum nor shasum is available for checksum verification" + fi +} + +main() { + local install_dir os arch platform asset version checksum + + command -v curl >/dev/null 2>&1 || fail "curl is required but was not found" + command -v tar >/dev/null 2>&1 || fail "tar is required but was not found" + + install_dir="${1:-${DAPR_INSTALL_DIR:-}}" + [ -n "$install_dir" ] || install_dir="${HOME}/.local/bin" + + os="$(detect_os)" + arch="$(detect_arch)" + platform="${os}_${arch}" + asset="dapr_${os}_${arch}.tar.gz" + + # Normalize the requested version: strip whitespace, treat empty as the + # latest release, and accept a bare number (1.15.1) as well as a tag (v1.15.1). + version="${DAPR_VERSION:-}" + version="${version//[[:space:]]/}" + if [ -z "$version" ]; then + log "resolving latest Dapr CLI version..." + version="$(resolve_latest_version)" + elif [ "${version#[0-9]}" != "$version" ]; then + version="v${version}" + fi + [ -n "$version" ] || fail "could not determine the Dapr CLI version to install" + + if command -v dapr >/dev/null 2>&1 && dapr --version 2>/dev/null | grep -q "${version#v}"; then + log "Dapr CLI ${version} already installed: $(command -v dapr)" + return 0 + fi + + WORKDIR="$(mktemp -d)" + + # Expected checksum: prefer the value supplied for this platform, otherwise + # read it from the release's own published '.sha256' file. + case "$platform" in + linux_amd64) checksum="${DAPR_CHECKSUM_LINUX_AMD64:-}" ;; + linux_arm64) checksum="${DAPR_CHECKSUM_LINUX_ARM64:-}" ;; + darwin_amd64) checksum="${DAPR_CHECKSUM_DARWIN_AMD64:-}" ;; + darwin_arm64) checksum="${DAPR_CHECKSUM_DARWIN_ARM64:-}" ;; + *) checksum="" ;; + esac + if [ -z "$checksum" ]; then + log "no checksum supplied for ${platform}; reading it from the ${version} release..." + checksum="$(checksum_from_release "$version" "$asset")" + fi + [ -n "$checksum" ] || fail "could not determine the SHA-256 checksum for ${asset} ${version}" + + log "downloading ${asset} ${version}..." + gh_curl -fsSL "${RELEASES_URL}/download/${version}/${asset}" -o "${WORKDIR}/${asset}" \ + || fail "could not download ${asset} ${version}" + verify_checksum "$checksum" "${WORKDIR}/${asset}" + + tar -xzf "${WORKDIR}/${asset}" -C "${WORKDIR}" \ + || fail "could not extract ${asset}" + [ -f "${WORKDIR}/dapr" ] || fail "expected 'dapr' binary not found in ${asset}" + chmod 0755 "${WORKDIR}/dapr" + + mkdir -p "$install_dir" + mv "${WORKDIR}/dapr" "${install_dir}/dapr" + "${install_dir}/dapr" --version >/dev/null 2>&1 \ + || fail "installed dapr failed to run (${install_dir}/dapr)" + log "installed Dapr CLI ${version} to ${install_dir}/dapr" + + # Make dapr available to later GitHub Actions steps. + if [ -n "${GITHUB_PATH:-}" ]; then + echo "$install_dir" >> "$GITHUB_PATH" + fi +} + +trap cleanup EXIT +main "$@" diff --git a/build/scripts/install-helm.sh b/build/scripts/install-helm.sh new file mode 100755 index 00000000000..273154f6c55 --- /dev/null +++ b/build/scripts/install-helm.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Installs the Helm CLI (the 'helm' binary) into a user-owned directory (no sudo) +# for the current platform. Works on linux and darwin, amd64 and arm64, for both +# CI and local development; under GitHub Actions the install dir is added to the +# job PATH so later steps can run helm. +# +# Helm is published as a per-platform tarball on the Helm release CDN +# (get.helm.sh), not GitHub. The pinned version and per-platform SHA-256 checksums +# (of the tarball) are normally provided by build/tools.mk through the +# environment. The script is generic, so when a value is not supplied it is +# resolved at runtime: +# * empty HELM_VERSION -> the latest published release +# * missing checksum for platform -> read from the release's own +# '.sha256sum' file +# +# Usage: install-helm.sh [install_dir] +# +# Environment (all optional): +# HELM_VERSION Release tag, e.g. v4.2.2. Empty selects latest. +# HELM_CHECKSUM__ SHA-256 of the tarball for that platform (e.g. +# HELM_CHECKSUM_LINUX_AMD64). Empty fetches it from +# the release's '.sha256sum' file. +# HELM_INSTALL_DIR Install directory. Default: $HOME/.local/bin. + +readonly DOWNLOAD_URL="https://get.helm.sh" +# Used only to resolve the latest tag when HELM_VERSION is empty; binaries always +# come from the get.helm.sh CDN above. +readonly LATEST_URL="https://github.com/helm/helm/releases/latest" + +log() { echo "[install-helm] $*" >&2; } +fail() { + echo "[install-helm] ERROR: $*" >&2 + exit 1 +} + +# Temporary working directory for downloads, removed on exit. Uses an explicit +# 'if' (not '&&') so the function returns 0 when WORKDIR is unset; otherwise the +# failing test would become the EXIT trap's status and abort an otherwise +# successful run, e.g. the early return when the tool is already installed. +WORKDIR="" +cleanup() { + if [ -n "${WORKDIR:-}" ] && [ -d "${WORKDIR}" ]; then + rm -rf "${WORKDIR}" + fi +} + +# curl wrapper: enforces HTTPS + TLS 1.2 and sets a User-Agent. get.helm.sh is a +# public CDN, so no authentication is required. +helm_curl() { + curl --proto '=https' --tlsv1.2 -H "User-Agent: helm-installer" "$@" +} + +detect_os() { + case "$(uname -s)" in + Linux) echo "linux" ;; + Darwin) echo "darwin" ;; + *) fail "unsupported OS '$(uname -s)' (supported: Linux, Darwin)" ;; + esac +} + +detect_arch() { + case "$(uname -m)" in + x86_64 | amd64) echo "amd64" ;; + aarch64 | arm64) echo "arm64" ;; + *) fail "unsupported architecture '$(uname -m)' (supported: amd64, arm64)" ;; + esac +} + +# Resolve the latest release tag by following the /releases/latest redirect. +resolve_latest_version() { + local effective_url + effective_url="$(helm_curl -fsSLI -o /dev/null -w '%{url_effective}' "${LATEST_URL}")" \ + || fail "could not resolve the latest Helm version" + printf '%s\n' "${effective_url##*/tag/}" +} + +# Print the SHA-256 of the tarball, read from the release's own published +# '.sha256sum' file (a single ' ' line). +checksum_from_release() { + local tarball="$1" + helm_curl -fsSL "${DOWNLOAD_URL}/${tarball}.sha256sum" -o "${WORKDIR}/${tarball}.sha256sum" \ + || fail "could not download ${tarball}.sha256sum" + awk 'NR == 1 { print $1 }' "${WORKDIR}/${tarball}.sha256sum" +} + +verify_checksum() { + local expected="$1" file="$2" + if command -v sha256sum >/dev/null 2>&1; then + echo "${expected} ${file}" | sha256sum -c - >/dev/null + elif command -v shasum >/dev/null 2>&1; then + echo "${expected} ${file}" | shasum -a 256 -c - >/dev/null + else + fail "neither sha256sum nor shasum is available for checksum verification" + fi +} + +main() { + local install_dir os arch platform tarball version checksum extracted + + command -v curl >/dev/null 2>&1 || fail "curl is required but was not found" + command -v tar >/dev/null 2>&1 || fail "tar is required but was not found" + + install_dir="${1:-${HELM_INSTALL_DIR:-}}" + [ -n "$install_dir" ] || install_dir="${HOME}/.local/bin" + + os="$(detect_os)" + arch="$(detect_arch)" + platform="${os}_${arch}" + + # Normalize the requested version: strip whitespace, treat empty as the + # latest release, and accept a bare number (4.2.2) as well as a tag (v4.2.2). + version="${HELM_VERSION:-}" + version="${version//[[:space:]]/}" + if [ -z "$version" ]; then + log "resolving latest Helm version..." + version="$(resolve_latest_version)" + elif [ "${version#[0-9]}" != "$version" ]; then + version="v${version}" + fi + [ -n "$version" ] || fail "could not determine the Helm version to install" + + if command -v helm >/dev/null 2>&1 && helm version --short 2>/dev/null | grep -q "${version#v}"; then + log "Helm ${version} already installed: $(command -v helm)" + return 0 + fi + + tarball="helm-${version}-${os}-${arch}.tar.gz" + WORKDIR="$(mktemp -d)" + + # Expected checksum: prefer the value supplied for this platform, otherwise + # read it from the release's own published '.sha256sum' file. + case "$platform" in + linux_amd64) checksum="${HELM_CHECKSUM_LINUX_AMD64:-}" ;; + linux_arm64) checksum="${HELM_CHECKSUM_LINUX_ARM64:-}" ;; + darwin_amd64) checksum="${HELM_CHECKSUM_DARWIN_AMD64:-}" ;; + darwin_arm64) checksum="${HELM_CHECKSUM_DARWIN_ARM64:-}" ;; + *) checksum="" ;; + esac + if [ -z "$checksum" ]; then + log "no checksum supplied for ${platform}; reading it from the ${version} release..." + checksum="$(checksum_from_release "$tarball")" + fi + [ -n "$checksum" ] || fail "could not determine the SHA-256 checksum for ${tarball}" + + log "downloading ${tarball}..." + helm_curl -fsSL "${DOWNLOAD_URL}/${tarball}" -o "${WORKDIR}/${tarball}" \ + || fail "could not download ${tarball}" + verify_checksum "$checksum" "${WORKDIR}/${tarball}" + + # The tarball extracts to a '-/' directory containing the binary. + tar -xzf "${WORKDIR}/${tarball}" -C "${WORKDIR}" \ + || fail "could not extract ${tarball}" + extracted="${WORKDIR}/${os}-${arch}/helm" + [ -f "$extracted" ] || fail "expected 'helm' binary not found in ${tarball}" + chmod 0755 "$extracted" + + mkdir -p "$install_dir" + mv "$extracted" "${install_dir}/helm" + "${install_dir}/helm" version --short >/dev/null 2>&1 \ + || fail "installed helm failed to run (${install_dir}/helm)" + log "installed Helm ${version} to ${install_dir}/helm" + + # Make helm available to later GitHub Actions steps. + if [ -n "${GITHUB_PATH:-}" ]; then + echo "$install_dir" >> "$GITHUB_PATH" + fi +} + +trap cleanup EXIT +main "$@" diff --git a/build/scripts/install-kind.sh b/build/scripts/install-kind.sh new file mode 100755 index 00000000000..677b6b3097d --- /dev/null +++ b/build/scripts/install-kind.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Installs the kind (Kubernetes IN Docker) cluster tool into a user-owned +# directory (no sudo) for the current platform. Works on linux and darwin, +# amd64 and arm64, for both CI and local development; under GitHub Actions the +# install dir is added to the job PATH so later steps can run kind. +# +# The pinned version and per-platform SHA-256 checksums are normally provided by +# build/tools.mk through the environment. The script is generic, so when a value +# is not supplied it is resolved at runtime: +# * empty KIND_VERSION -> the latest published release +# * missing checksum for platform -> read from the release's own +# '.sha256sum' file +# +# Usage: install-kind.sh [install_dir] +# +# Environment (all optional): +# KIND_VERSION Release tag, e.g. v0.32.0. Empty selects latest. +# KIND_CHECKSUM__ SHA-256 for that platform (e.g. +# KIND_CHECKSUM_LINUX_AMD64). Empty fetches it from +# the release's published '.sha256sum' file. +# KIND_INSTALL_DIR Install directory. Default: $HOME/.local/bin. +# GITHUB_TOKEN If set, authenticates GitHub requests (higher rate +# limits; required for private repositories). + +readonly REPO="kubernetes-sigs/kind" +readonly RELEASES_URL="https://github.com/${REPO}/releases" + +log() { echo "[install-kind] $*" >&2; } +fail() { + echo "[install-kind] ERROR: $*" >&2 + exit 1 +} + +# Temporary working directory for downloads, removed on exit. Uses an explicit +# 'if' (not '&&') so the function returns 0 when WORKDIR is unset; otherwise the +# failing test would become the EXIT trap's status and abort an otherwise +# successful run, e.g. the early return when the tool is already installed. +WORKDIR="" +cleanup() { + if [ -n "${WORKDIR:-}" ] && [ -d "${WORKDIR}" ]; then + rm -rf "${WORKDIR}" + fi +} + +# curl wrapper for GitHub requests: enforces HTTPS + TLS 1.2, sets a User-Agent, +# and adds an Authorization header when GITHUB_TOKEN is set (raises API rate +# limits and allows private repositories). curl drops the Authorization header on +# cross-host redirects, so the token is not sent to the download CDN. The array is +# seeded with the User-Agent so it is never empty -- expanding an empty array +# under 'set -u' is an error on bash 3.2 (macOS). +gh_curl() { + local headers=(-H "User-Agent: ${REPO##*/}-installer") + if [ -n "${GITHUB_TOKEN:-}" ]; then + headers+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") + fi + curl --proto '=https' --tlsv1.2 "${headers[@]}" "$@" +} + +detect_os() { + case "$(uname -s)" in + Linux) echo "linux" ;; + Darwin) echo "darwin" ;; + *) fail "unsupported OS '$(uname -s)' (supported: Linux, Darwin)" ;; + esac +} + +detect_arch() { + case "$(uname -m)" in + x86_64 | amd64) echo "amd64" ;; + aarch64 | arm64) echo "arm64" ;; + *) fail "unsupported architecture '$(uname -m)' (supported: amd64, arm64)" ;; + esac +} + +# Resolve the latest release tag by following the /releases/latest redirect. +# Avoids the GitHub API (no token, no rate limit). +resolve_latest_version() { + local effective_url + effective_url="$(gh_curl -fsSLI -o /dev/null -w '%{url_effective}' "${RELEASES_URL}/latest")" \ + || fail "could not resolve the latest kind version" + printf '%s\n' "${effective_url##*/tag/}" +} + +# Print the SHA-256 of an asset, read from the release's own published +# '.sha256sum' file. kind publishes one such file per binary; each +# contains a single ' ' line. +checksum_from_release() { + local version="$1" asset="$2" + gh_curl -fsSL "${RELEASES_URL}/download/${version}/${asset}.sha256sum" -o "${WORKDIR}/${asset}.sha256sum" \ + || fail "could not download ${asset}.sha256sum for ${version}" + awk 'NR == 1 { print $1 }' "${WORKDIR}/${asset}.sha256sum" +} + +verify_checksum() { + local expected="$1" file="$2" + if command -v sha256sum >/dev/null 2>&1; then + echo "${expected} ${file}" | sha256sum -c - >/dev/null + elif command -v shasum >/dev/null 2>&1; then + echo "${expected} ${file}" | shasum -a 256 -c - >/dev/null + else + fail "neither sha256sum nor shasum is available for checksum verification" + fi +} + +main() { + local install_dir os arch platform asset version checksum + + command -v curl >/dev/null 2>&1 || fail "curl is required but was not found" + + install_dir="${1:-${KIND_INSTALL_DIR:-}}" + [ -n "$install_dir" ] || install_dir="${HOME}/.local/bin" + + os="$(detect_os)" + arch="$(detect_arch)" + platform="${os}_${arch}" + asset="kind-${os}-${arch}" + + # Normalize the requested version: strip whitespace, treat empty as the + # latest release, and accept a bare number (0.32.0) as well as a tag (v0.32.0). + version="${KIND_VERSION:-}" + version="${version//[[:space:]]/}" + if [ -z "$version" ]; then + log "resolving latest kind version..." + version="$(resolve_latest_version)" + elif [ "${version#[0-9]}" != "$version" ]; then + version="v${version}" + fi + [ -n "$version" ] || fail "could not determine the kind version to install" + + if command -v kind >/dev/null 2>&1 && kind --version 2>/dev/null | grep -q "${version#v}"; then + log "kind ${version} already installed: $(command -v kind)" + return 0 + fi + + WORKDIR="$(mktemp -d)" + + # Expected checksum: prefer the value supplied for this platform, otherwise + # read it from the release's own published '.sha256sum' file. + case "$platform" in + linux_amd64) checksum="${KIND_CHECKSUM_LINUX_AMD64:-}" ;; + linux_arm64) checksum="${KIND_CHECKSUM_LINUX_ARM64:-}" ;; + darwin_amd64) checksum="${KIND_CHECKSUM_DARWIN_AMD64:-}" ;; + darwin_arm64) checksum="${KIND_CHECKSUM_DARWIN_ARM64:-}" ;; + *) checksum="" ;; + esac + if [ -z "$checksum" ]; then + log "no checksum supplied for ${platform}; reading it from the ${version} release..." + checksum="$(checksum_from_release "$version" "$asset")" + fi + [ -n "$checksum" ] || fail "could not determine the SHA-256 checksum for ${asset} ${version}" + + log "downloading ${asset} ${version}..." + gh_curl -fsSL "${RELEASES_URL}/download/${version}/${asset}" -o "${WORKDIR}/kind" \ + || fail "could not download ${asset} ${version}" + verify_checksum "$checksum" "${WORKDIR}/kind" + chmod 0755 "${WORKDIR}/kind" + + mkdir -p "$install_dir" + mv "${WORKDIR}/kind" "${install_dir}/kind" + "${install_dir}/kind" version >/dev/null 2>&1 \ + || fail "installed kind failed to run (${install_dir}/kind)" + log "installed kind ${version} to ${install_dir}/kind" + + # Make kind available to later GitHub Actions steps. + if [ -n "${GITHUB_PATH:-}" ]; then + echo "$install_dir" >> "$GITHUB_PATH" + fi +} + +trap cleanup EXIT +main "$@" diff --git a/build/scripts/install-kubectl.sh b/build/scripts/install-kubectl.sh new file mode 100755 index 00000000000..74ddc14db46 --- /dev/null +++ b/build/scripts/install-kubectl.sh @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Installs the kubectl Kubernetes CLI into a user-owned directory (no sudo) for +# the current platform. Works on linux and darwin, amd64 and arm64, for both CI +# and local development; under GitHub Actions the install dir is added to the job +# PATH so later steps can run kubectl. +# +# kubectl is published on the Kubernetes release CDN (dl.k8s.io), not GitHub. The +# pinned version and per-platform SHA-256 checksums are normally provided by +# build/tools.mk through the environment. The script is generic, so when a value +# is not supplied it is resolved at runtime: +# * empty KUBECTL_VERSION -> the latest stable release (stable.txt) +# * missing checksum for platform -> read from the release's own +# 'kubectl.sha256' file +# +# Usage: install-kubectl.sh [install_dir] +# +# Environment (all optional): +# KUBECTL_VERSION Release tag, e.g. v1.30.0. Empty selects the +# latest stable release. +# KUBECTL_CHECKSUM__ SHA-256 for that platform (e.g. +# KUBECTL_CHECKSUM_LINUX_AMD64). Empty fetches it +# from the release's published 'kubectl.sha256'. +# KUBECTL_INSTALL_DIR Install directory. Default: $HOME/.local/bin. + +readonly RELEASE_URL="https://dl.k8s.io/release" + +log() { echo "[install-kubectl] $*" >&2; } +fail() { + echo "[install-kubectl] ERROR: $*" >&2 + exit 1 +} + +# Temporary working directory for downloads, removed on exit. Uses an explicit +# 'if' (not '&&') so the function returns 0 when WORKDIR is unset; otherwise the +# failing test would become the EXIT trap's status and abort an otherwise +# successful run, e.g. the early return when the tool is already installed. +WORKDIR="" +cleanup() { + if [ -n "${WORKDIR:-}" ] && [ -d "${WORKDIR}" ]; then + rm -rf "${WORKDIR}" + fi +} + +# curl wrapper: enforces HTTPS + TLS 1.2 and sets a User-Agent. dl.k8s.io is a +# public CDN, so no authentication is required. +dl_curl() { + curl --proto '=https' --tlsv1.2 -H "User-Agent: kubectl-installer" "$@" +} + +detect_os() { + case "$(uname -s)" in + Linux) echo "linux" ;; + Darwin) echo "darwin" ;; + *) fail "unsupported OS '$(uname -s)' (supported: Linux, Darwin)" ;; + esac +} + +detect_arch() { + case "$(uname -m)" in + x86_64 | amd64) echo "amd64" ;; + aarch64 | arm64) echo "arm64" ;; + *) fail "unsupported architecture '$(uname -m)' (supported: amd64, arm64)" ;; + esac +} + +# Resolve the latest stable release tag from the release channel's stable.txt. +resolve_latest_version() { + dl_curl -fsSL "${RELEASE_URL}/stable.txt" \ + || fail "could not resolve the latest stable kubectl version" +} + +# Print the SHA-256 of the kubectl binary, read from the release's own published +# 'kubectl.sha256' file (which contains just the hash). +checksum_from_release() { + local version="$1" os="$2" arch="$3" + dl_curl -fsSL "${RELEASE_URL}/${version}/bin/${os}/${arch}/kubectl.sha256" -o "${WORKDIR}/kubectl.sha256" \ + || fail "could not download kubectl.sha256 for ${version} ${os}/${arch}" + awk 'NR == 1 { print $1 }' "${WORKDIR}/kubectl.sha256" +} + +verify_checksum() { + local expected="$1" file="$2" + if command -v sha256sum >/dev/null 2>&1; then + echo "${expected} ${file}" | sha256sum -c - >/dev/null + elif command -v shasum >/dev/null 2>&1; then + echo "${expected} ${file}" | shasum -a 256 -c - >/dev/null + else + fail "neither sha256sum nor shasum is available for checksum verification" + fi +} + +main() { + local install_dir os arch platform version checksum + + command -v curl >/dev/null 2>&1 || fail "curl is required but was not found" + + install_dir="${1:-${KUBECTL_INSTALL_DIR:-}}" + [ -n "$install_dir" ] || install_dir="${HOME}/.local/bin" + + os="$(detect_os)" + arch="$(detect_arch)" + platform="${os}_${arch}" + + # Normalize the requested version: strip whitespace, treat empty as the + # latest stable release, and accept a bare number (1.30.0) as well as a tag. + version="${KUBECTL_VERSION:-}" + version="${version//[[:space:]]/}" + if [ -z "$version" ]; then + log "resolving latest stable kubectl version..." + version="$(resolve_latest_version)" + elif [ "${version#[0-9]}" != "$version" ]; then + version="v${version}" + fi + [ -n "$version" ] || fail "could not determine the kubectl version to install" + + if command -v kubectl >/dev/null 2>&1 && kubectl version --client 2>/dev/null | grep -q "${version#v}"; then + log "kubectl ${version} already installed: $(command -v kubectl)" + return 0 + fi + + WORKDIR="$(mktemp -d)" + + # Expected checksum: prefer the value supplied for this platform, otherwise + # read it from the release's own published 'kubectl.sha256' file. + case "$platform" in + linux_amd64) checksum="${KUBECTL_CHECKSUM_LINUX_AMD64:-}" ;; + linux_arm64) checksum="${KUBECTL_CHECKSUM_LINUX_ARM64:-}" ;; + darwin_amd64) checksum="${KUBECTL_CHECKSUM_DARWIN_AMD64:-}" ;; + darwin_arm64) checksum="${KUBECTL_CHECKSUM_DARWIN_ARM64:-}" ;; + *) checksum="" ;; + esac + if [ -z "$checksum" ]; then + log "no checksum supplied for ${platform}; reading it from the ${version} release..." + checksum="$(checksum_from_release "$version" "$os" "$arch")" + fi + [ -n "$checksum" ] || fail "could not determine the SHA-256 checksum for kubectl ${version} ${platform}" + + log "downloading kubectl ${version} (${os}/${arch})..." + dl_curl -fsSL "${RELEASE_URL}/${version}/bin/${os}/${arch}/kubectl" -o "${WORKDIR}/kubectl" \ + || fail "could not download kubectl ${version} for ${os}/${arch}" + verify_checksum "$checksum" "${WORKDIR}/kubectl" + chmod 0755 "${WORKDIR}/kubectl" + + mkdir -p "$install_dir" + mv "${WORKDIR}/kubectl" "${install_dir}/kubectl" + "${install_dir}/kubectl" version --client >/dev/null 2>&1 \ + || fail "installed kubectl failed to run (${install_dir}/kubectl)" + log "installed kubectl ${version} to ${install_dir}/kubectl" + + # Make kubectl available to later GitHub Actions steps. + if [ -n "${GITHUB_PATH:-}" ]; then + echo "$install_dir" >> "$GITHUB_PATH" + fi +} + +trap cleanup EXIT +main "$@" diff --git a/build/scripts/install-oras.sh b/build/scripts/install-oras.sh new file mode 100755 index 00000000000..cd8588b7191 --- /dev/null +++ b/build/scripts/install-oras.sh @@ -0,0 +1,182 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# Installs the ORAS CLI (the 'oras' binary) into a user-owned directory (no sudo) +# for the current platform. Works on linux and darwin, amd64 and arm64, for both +# CI and local development; under GitHub Actions the install dir is added to the +# job PATH so later steps can run oras. +# +# ORAS is published as a per-platform tarball on oras-project/oras's GitHub +# releases. The pinned version and per-platform SHA-256 checksums (of the +# tarball) are normally provided by build/tools.mk through the environment. The +# script is generic, so when a value is not supplied it is resolved at runtime: +# * empty ORAS_VERSION -> the latest published release +# * missing checksum for platform -> read from the release's own +# 'oras__checksums.txt' file +# +# Usage: install-oras.sh [install_dir] +# +# Environment (all optional): +# ORAS_VERSION Release tag, e.g. v1.3.2. Empty selects latest. +# ORAS_CHECKSUM__ SHA-256 of the tarball for that platform (e.g. +# ORAS_CHECKSUM_LINUX_AMD64). Empty fetches it from +# the release's 'oras__checksums.txt' file. +# ORAS_INSTALL_DIR Install directory. Default: $HOME/.local/bin. +# GITHUB_TOKEN If set, authenticates GitHub requests (higher rate +# limits; required for private repositories). + +readonly REPO="oras-project/oras" +readonly RELEASES_URL="https://github.com/${REPO}/releases" + +log() { echo "[install-oras] $*" >&2; } +fail() { + echo "[install-oras] ERROR: $*" >&2 + exit 1 +} + +# Temporary working directory for downloads, removed on exit. Uses an explicit +# 'if' (not '&&') so the function returns 0 when WORKDIR is unset; otherwise the +# failing test would become the EXIT trap's status and abort an otherwise +# successful run, e.g. the early return when the tool is already installed. +WORKDIR="" +cleanup() { + if [ -n "${WORKDIR:-}" ] && [ -d "${WORKDIR}" ]; then + rm -rf "${WORKDIR}" + fi +} + +# curl wrapper for GitHub requests: enforces HTTPS + TLS 1.2, sets a User-Agent, +# and adds an Authorization header when GITHUB_TOKEN is set (raises API rate +# limits and allows private repositories). curl drops the Authorization header on +# cross-host redirects, so the token is not sent to the download CDN. The array is +# seeded with the User-Agent so it is never empty -- expanding an empty array +# under 'set -u' is an error on bash 3.2 (macOS). +gh_curl() { + local headers=(-H "User-Agent: oras-installer") + if [ -n "${GITHUB_TOKEN:-}" ]; then + headers+=(-H "Authorization: Bearer ${GITHUB_TOKEN}") + fi + curl --proto '=https' --tlsv1.2 "${headers[@]}" "$@" +} + +detect_os() { + case "$(uname -s)" in + Linux) echo "linux" ;; + Darwin) echo "darwin" ;; + *) fail "unsupported OS '$(uname -s)' (supported: Linux, Darwin)" ;; + esac +} + +detect_arch() { + case "$(uname -m)" in + x86_64 | amd64) echo "amd64" ;; + aarch64 | arm64) echo "arm64" ;; + *) fail "unsupported architecture '$(uname -m)' (supported: amd64, arm64)" ;; + esac +} + +# Resolve the latest release tag by following the /releases/latest redirect. +# Avoids the GitHub API (no token, no rate limit). +resolve_latest_version() { + local effective_url + effective_url="$(gh_curl -fsSLI -o /dev/null -w '%{url_effective}' "${RELEASES_URL}/latest")" \ + || fail "could not resolve the latest ORAS version" + printf '%s\n' "${effective_url##*/tag/}" +} + +# Print the SHA-256 of an asset, read from the release's own published combined +# 'oras__checksums.txt' file (goreleaser format: ' '). +checksum_from_release() { + local version="$1" asset="$2" version_no_v + version_no_v="${version#v}" + gh_curl -fsSL "${RELEASES_URL}/download/${version}/oras_${version_no_v}_checksums.txt" -o "${WORKDIR}/checksums.txt" \ + || fail "could not download oras_${version_no_v}_checksums.txt for ${version}" + awk -v a="$asset" '$2 == a { print $1 }' "${WORKDIR}/checksums.txt" +} + +verify_checksum() { + local expected="$1" file="$2" + if command -v sha256sum >/dev/null 2>&1; then + echo "${expected} ${file}" | sha256sum -c - >/dev/null + elif command -v shasum >/dev/null 2>&1; then + echo "${expected} ${file}" | shasum -a 256 -c - >/dev/null + else + fail "neither sha256sum nor shasum is available for checksum verification" + fi +} + +main() { + local install_dir os arch platform version version_no_v asset checksum + + command -v curl >/dev/null 2>&1 || fail "curl is required but was not found" + command -v tar >/dev/null 2>&1 || fail "tar is required but was not found" + + install_dir="${1:-${ORAS_INSTALL_DIR:-}}" + [ -n "$install_dir" ] || install_dir="${HOME}/.local/bin" + + os="$(detect_os)" + arch="$(detect_arch)" + platform="${os}_${arch}" + + # Normalize the requested version: strip whitespace, treat empty as the + # latest release, and accept a bare number (1.3.2) as well as a tag (v1.3.2). + version="${ORAS_VERSION:-}" + version="${version//[[:space:]]/}" + if [ -z "$version" ]; then + log "resolving latest ORAS version..." + version="$(resolve_latest_version)" + elif [ "${version#[0-9]}" != "$version" ]; then + version="v${version}" + fi + [ -n "$version" ] || fail "could not determine the ORAS version to install" + + if command -v oras >/dev/null 2>&1 && oras version 2>/dev/null | grep -q "${version#v}"; then + log "ORAS ${version} already installed: $(command -v oras)" + return 0 + fi + + # The asset name embeds the version without the leading 'v'. + version_no_v="${version#v}" + asset="oras_${version_no_v}_${os}_${arch}.tar.gz" + WORKDIR="$(mktemp -d)" + + # Expected checksum: prefer the value supplied for this platform, otherwise + # read it from the release's own published checksums file. + case "$platform" in + linux_amd64) checksum="${ORAS_CHECKSUM_LINUX_AMD64:-}" ;; + linux_arm64) checksum="${ORAS_CHECKSUM_LINUX_ARM64:-}" ;; + darwin_amd64) checksum="${ORAS_CHECKSUM_DARWIN_AMD64:-}" ;; + darwin_arm64) checksum="${ORAS_CHECKSUM_DARWIN_ARM64:-}" ;; + *) checksum="" ;; + esac + if [ -z "$checksum" ]; then + log "no checksum supplied for ${platform}; reading it from the ${version} release..." + checksum="$(checksum_from_release "$version" "$asset")" + fi + [ -n "$checksum" ] || fail "could not determine the SHA-256 checksum for ${asset} ${version}" + + log "downloading ${asset} ${version}..." + gh_curl -fsSL "${RELEASES_URL}/download/${version}/${asset}" -o "${WORKDIR}/${asset}" \ + || fail "could not download ${asset} ${version}" + verify_checksum "$checksum" "${WORKDIR}/${asset}" + + tar -xzf "${WORKDIR}/${asset}" -C "${WORKDIR}" \ + || fail "could not extract ${asset}" + [ -f "${WORKDIR}/oras" ] || fail "expected 'oras' binary not found in ${asset}" + chmod 0755 "${WORKDIR}/oras" + + mkdir -p "$install_dir" + mv "${WORKDIR}/oras" "${install_dir}/oras" + "${install_dir}/oras" version >/dev/null 2>&1 \ + || fail "installed oras failed to run (${install_dir}/oras)" + log "installed ORAS ${version} to ${install_dir}/oras" + + # Make oras available to later GitHub Actions steps. + if [ -n "${GITHUB_PATH:-}" ]; then + echo "$install_dir" >> "$GITHUB_PATH" + fi +} + +trap cleanup EXIT +main "$@" diff --git a/build/tools.mk b/build/tools.mk index 7dc7664cb35..4d4488c500c 100644 --- a/build/tools.mk +++ b/build/tools.mk @@ -57,3 +57,112 @@ install-bicep: ## Install the pinned Bicep CLI into a user-owned bin dir (no sud BICEP_CHECKSUM_DARWIN_ARM64="$(BICEP_CHECKSUM_DARWIN_ARM64)" \ BICEP_INSTALL_DIR="$(BICEP_INSTALL_DIR)" \ ./build/scripts/install-bicep.sh + +# kind (Kubernetes IN Docker) - pinned version and per-platform SHA-256 checksums +# consumed by build/scripts/install-kind.sh. The script is generic: clear +# KIND_VERSION to install the latest release, and clear a checksum to have it read +# from the release's own '.sha256sum' file. Keep the checksums in sync when +# bumping KIND_VERSION. +KIND_VERSION ?= v0.32.0 +KIND_CHECKSUM_LINUX_AMD64 ?= 50030de23cf40a18505f20426f6a8506bedf13c6e509244bd1fa9463721b0f54 +KIND_CHECKSUM_LINUX_ARM64 ?= b92cd615e97585de8ddade28ed5cd7feb4248d717c233eea5b03c37298900f5d +KIND_CHECKSUM_DARWIN_AMD64 ?= 295ac6d0d634c9819c9907df45e3017d1f13166bd13c3404c45e79f7faa47498 +KIND_CHECKSUM_DARWIN_ARM64 ?= dca67911095a110c2b5c36e26df6cac860c602033e456c0db47be498cdef1ebb + +.PHONY: install-kind +install-kind: ## Install the pinned kind cluster tool into a user-owned bin dir (no sudo). + @KIND_VERSION="$(KIND_VERSION)" \ + KIND_CHECKSUM_LINUX_AMD64="$(KIND_CHECKSUM_LINUX_AMD64)" \ + KIND_CHECKSUM_LINUX_ARM64="$(KIND_CHECKSUM_LINUX_ARM64)" \ + KIND_CHECKSUM_DARWIN_AMD64="$(KIND_CHECKSUM_DARWIN_AMD64)" \ + KIND_CHECKSUM_DARWIN_ARM64="$(KIND_CHECKSUM_DARWIN_ARM64)" \ + KIND_INSTALL_DIR="$(KIND_INSTALL_DIR)" \ + ./build/scripts/install-kind.sh + +# kubectl (Kubernetes CLI) - pinned version and per-platform SHA-256 checksums +# consumed by build/scripts/install-kubectl.sh. kubectl is published on the +# Kubernetes release CDN (dl.k8s.io), not GitHub. The script is generic: clear +# KUBECTL_VERSION to install the latest stable release, and clear a checksum to +# have it read from the release's own 'kubectl.sha256' file. Keep the checksums in +# sync when bumping KUBECTL_VERSION. +KUBECTL_VERSION ?= v1.36.2 +KUBECTL_CHECKSUM_LINUX_AMD64 ?= 1e9045ec32bea85da43de85f0065358529ea7c7a152eca78154fba5b58c27d82 +KUBECTL_CHECKSUM_LINUX_ARM64 ?= c957eb8c4bea27a3bb35b269edd9082e27f027f7b76b20b5bf4afebc726c6d3e +KUBECTL_CHECKSUM_DARWIN_AMD64 ?= ce6c5e55cd17559e87e4fb5e73ebbbc2511bcf2b695d7a40c1b1461a9817d4b3 +KUBECTL_CHECKSUM_DARWIN_ARM64 ?= 4408c85c83fd3a31adaa555bdf3c7a6c81f74b19449a9060ba31ab91926f023d + +.PHONY: install-kubectl +install-kubectl: ## Install the pinned kubectl Kubernetes CLI into a user-owned bin dir (no sudo). + @KUBECTL_VERSION="$(KUBECTL_VERSION)" \ + KUBECTL_CHECKSUM_LINUX_AMD64="$(KUBECTL_CHECKSUM_LINUX_AMD64)" \ + KUBECTL_CHECKSUM_LINUX_ARM64="$(KUBECTL_CHECKSUM_LINUX_ARM64)" \ + KUBECTL_CHECKSUM_DARWIN_AMD64="$(KUBECTL_CHECKSUM_DARWIN_AMD64)" \ + KUBECTL_CHECKSUM_DARWIN_ARM64="$(KUBECTL_CHECKSUM_DARWIN_ARM64)" \ + KUBECTL_INSTALL_DIR="$(KUBECTL_INSTALL_DIR)" \ + ./build/scripts/install-kubectl.sh + +# Dapr CLI - pinned version and per-platform SHA-256 checksums (of the release +# tarball) consumed by build/scripts/install-dapr.sh. The script is generic: clear +# DAPR_VERSION to install the latest release, and clear a checksum to have it +# read from the release's own '.sha256' file. Keep the checksums in sync +# when bumping DAPR_VERSION. This pins the Dapr CLI only; the Dapr runtime and +# dashboard versions used by `dapr init -k` are pinned in the workflows. +DAPR_VERSION ?= v1.15.2 +DAPR_CHECKSUM_LINUX_AMD64 ?= 09328bc0e4353036b824c2ec9cf7cabf4d75b4fc00ca02d80ae3e4374ee27eda +DAPR_CHECKSUM_LINUX_ARM64 ?= b49244701a191c1e843211383703be9f2cd086a1db259c9789672f7e4e82ad55 +DAPR_CHECKSUM_DARWIN_AMD64 ?= 42a36e667559aef0fb6357fbe8f0fdbf1a6d9ea0ba8484c32e90ea61ddf15ba0 +DAPR_CHECKSUM_DARWIN_ARM64 ?= 176f455ea1961cdb59ab0e9ec3e4900b877576a9a2178d3b4b2619bfe947643f + +.PHONY: install-dapr +install-dapr: ## Install the pinned Dapr CLI into a user-owned bin dir (no sudo). + @DAPR_VERSION="$(DAPR_VERSION)" \ + DAPR_CHECKSUM_LINUX_AMD64="$(DAPR_CHECKSUM_LINUX_AMD64)" \ + DAPR_CHECKSUM_LINUX_ARM64="$(DAPR_CHECKSUM_LINUX_ARM64)" \ + DAPR_CHECKSUM_DARWIN_AMD64="$(DAPR_CHECKSUM_DARWIN_AMD64)" \ + DAPR_CHECKSUM_DARWIN_ARM64="$(DAPR_CHECKSUM_DARWIN_ARM64)" \ + DAPR_INSTALL_DIR="$(DAPR_INSTALL_DIR)" \ + ./build/scripts/install-dapr.sh + +# Helm CLI - pinned version and per-platform SHA-256 checksums (of the release +# tarball) consumed by build/scripts/install-helm.sh. Helm is published on the +# Helm release CDN (get.helm.sh), not GitHub. The script is generic: clear +# HELM_VERSION to install the latest release, and clear a checksum to have it read +# from the release's own '.sha256sum' file. Keep the checksums in sync +# when bumping HELM_VERSION. +HELM_VERSION ?= v4.2.2 +HELM_CHECKSUM_LINUX_AMD64 ?= 9adafecab4d406853bba163a70e9f104f47dbbf65ce24b7653bae7e36150bcb6 +HELM_CHECKSUM_LINUX_ARM64 ?= 78803142087a0069fa4b50d3f32a84d3ef25c14d1ee8a40fbccf86a6216d2f36 +HELM_CHECKSUM_DARWIN_AMD64 ?= 10c1e36ee8c5f2e2ee25a16599cb03ab74c0953cd889cacb980a49ba4b6574ba +HELM_CHECKSUM_DARWIN_ARM64 ?= 5410a0dae3d5d91f45653b161260d9301aabc4ae80ae50a6605d66884b6df8ea + +.PHONY: install-helm +install-helm: ## Install the pinned Helm CLI into a user-owned bin dir (no sudo). + @HELM_VERSION="$(HELM_VERSION)" \ + HELM_CHECKSUM_LINUX_AMD64="$(HELM_CHECKSUM_LINUX_AMD64)" \ + HELM_CHECKSUM_LINUX_ARM64="$(HELM_CHECKSUM_LINUX_ARM64)" \ + HELM_CHECKSUM_DARWIN_AMD64="$(HELM_CHECKSUM_DARWIN_AMD64)" \ + HELM_CHECKSUM_DARWIN_ARM64="$(HELM_CHECKSUM_DARWIN_ARM64)" \ + HELM_INSTALL_DIR="$(HELM_INSTALL_DIR)" \ + ./build/scripts/install-helm.sh + +# ORAS (OCI Registry As Storage) CLI - pinned version and per-platform SHA-256 +# checksums (of the release tarball) consumed by build/scripts/install-oras.sh. +# The script is generic: clear ORAS_VERSION to install the latest release, and +# clear a checksum to have it read from the release's own +# 'oras__checksums.txt' file. Keep the checksums in sync when bumping +# ORAS_VERSION. +ORAS_VERSION ?= v1.3.2 +ORAS_CHECKSUM_LINUX_AMD64 ?= 9229ccc6d17bb282039ad4a69abb16dcb887a5bce567c075d731d9b3c7ad8eaf +ORAS_CHECKSUM_LINUX_ARM64 ?= 8db4a223bd6034deff198e791ea7cb3af0840df25b7e9f370e2f1f3fd20d389b +ORAS_CHECKSUM_DARWIN_AMD64 ?= 2621f6b252b222f6fbf4e114d2fcaa0cec6b632624ffaf73143f66e4e0994f86 +ORAS_CHECKSUM_DARWIN_ARM64 ?= 7929f792cf272268412375ecad6f0fb3c20f164368d5b57966e67ad6d36eca53 + +.PHONY: install-oras +install-oras: ## Install the pinned ORAS CLI into a user-owned bin dir (no sudo). + @ORAS_VERSION="$(ORAS_VERSION)" \ + ORAS_CHECKSUM_LINUX_AMD64="$(ORAS_CHECKSUM_LINUX_AMD64)" \ + ORAS_CHECKSUM_LINUX_ARM64="$(ORAS_CHECKSUM_LINUX_ARM64)" \ + ORAS_CHECKSUM_DARWIN_AMD64="$(ORAS_CHECKSUM_DARWIN_AMD64)" \ + ORAS_CHECKSUM_DARWIN_ARM64="$(ORAS_CHECKSUM_DARWIN_ARM64)" \ + ORAS_INSTALL_DIR="$(ORAS_INSTALL_DIR)" \ + ./build/scripts/install-oras.sh