Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/publish-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ jobs:
find _release -type f -name 'volcano-*' -exec cp {} dist/release-assets/ \;
cp scripts/install-volcano.sh dist/release-assets/install.sh
chmod +x dist/release-assets/install.sh
bash -n dist/release-assets/install.sh
sh -n dist/release-assets/install.sh

- name: Sign installer script
run: |
Expand Down
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ LDFLAGS := -s -w \
-X $(CONFIG_PKG).compiledFirstPartyDeviceClientID=$(FIRST_PARTY_DEVICE_CLIENT_ID) \
-X $(LOCALMODE_PKG).defaultVolcanoImage=$(DEFAULT_LOCAL_IMAGE)

.PHONY: all build local test api-e2e-smoke api-e2e-cloud localmode-e2e lint tidy check clean help openapi-generate openapi-generated-check
.PHONY: all build local test test-installer api-e2e-smoke api-e2e-cloud localmode-e2e lint tidy check clean help openapi-generate openapi-generated-check

all: build

Expand All @@ -50,9 +50,13 @@ local: ## Build volcano using variables loaded from .env.local
fi; \
$(MAKE) build

test: ## Run unit tests
test: test-installer ## Run unit tests
go test ./...

test-installer: ## Test the release installers
sh scripts/install-volcano.test.sh
node --test scripts/npm/download.test.js

openapi-generate: ## Regenerate the API client from the vendored OpenAPI contract
go generate ./internal/apiclient

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ volcano --help
Or install manually:

```bash
curl -fsSL https://github.com/Kong/volcano-cli/releases/latest/download/install.sh | bash
curl -fsSL https://github.com/Kong/volcano-cli/releases/latest/download/install.sh | sh
volcano --help
```

Expand Down
8 changes: 7 additions & 1 deletion docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,16 @@ volcano --help
Or install manually:

```bash
curl -fsSL https://github.com/Kong/volcano-cli/releases/latest/download/install.sh | bash
curl -fsSL https://github.com/Kong/volcano-cli/releases/latest/download/install.sh | sh
volcano --help
```

To install the CLI and set up Volcano in detected coding agents:

```bash
curl -fsSL https://github.com/Kong/volcano-cli/releases/latest/download/install.sh | sh -s -- --setup
```

## Upgrading

`volcano upgrade` upgrades the CLI the same way it was installed: it delegates
Expand Down
95 changes: 52 additions & 43 deletions scripts/install-volcano.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
#!/bin/sh
set -eu

readonly VOLCANO_GITHUB_RELEASES_URL="${VOLCANO_GITHUB_RELEASES_URL:-https://github.com/Kong/volcano-cli/releases}"
readonly VOLCANO_DEFAULT_VERSION="latest"
readonly VOLCANO_SIGNATURE_WORKFLOW="https://github.com/Kong/volcano-cli/.github/workflows/publish-cli.yml"
readonly VOLCANO_SIGNATURE_OIDC_ISSUER="https://token.actions.githubusercontent.com"
readonly VOLCANO_STABLE_TAG_SIGNATURE_IDENTITY_RE="^https://github[.]com/Kong/volcano-cli/[.]github/workflows/publish-cli[.]yml@refs/tags/v(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)$"
VOLCANO_INSTALL_DIR="${VOLCANO_INSTALL_DIR:-}"
RUN_SETUP=0

fail() {
echo "Error: $*" >&2
Expand All @@ -17,36 +18,38 @@ have() {
command -v "$1" >/dev/null 2>&1
}

is_semver() {
printf '%s\n' "$1" | grep -Eq '^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
}

detect_os() {
local raw
raw="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$raw" in
detect_os_raw="$(uname -s | tr '[:upper:]' '[:lower:]')"
case "$detect_os_raw" in
linux*) echo "linux" ;;
darwin*) echo "macos" ;;
mingw* | msys* | cygwin*) echo "windows" ;;
*) fail "unsupported operating system: ${raw}" ;;
*) fail "unsupported operating system: ${detect_os_raw}" ;;
esac
}

detect_arch() {
local raw
raw="$(uname -m)"
case "$raw" in
detect_arch_raw="$(uname -m)"
case "$detect_arch_raw" in
x86_64 | amd64) echo "amd64" ;;
arm64 | aarch64) echo "arm64" ;;
*) fail "unsupported architecture: ${raw}" ;;
*) fail "unsupported architecture: ${detect_arch_raw}" ;;
esac
}

download_file() {
local url="$1"
local output="$2"
download_file_url="$1"
download_file_output="$2"
if have curl; then
curl --fail --location --silent --show-error "$url" --output "$output"
curl --fail --location --silent --show-error "$download_file_url" --output "$download_file_output"
return
fi
if have wget; then
wget --quiet --output-document="$output" "$url"
wget --quiet --output-document="$download_file_output" "$download_file_url"
return
fi
fail "curl or wget is required to download Volcano CLI"
Expand All @@ -62,70 +65,65 @@ require_cosign_for_verification() {
}

verify_signature() {
local file="$1"
local bundle="$2"
local version="$3"
local semver_re
local identity
verify_signature_file="$1"
verify_signature_bundle="$2"
verify_signature_version="$3"

if [ "${VOLCANO_SKIP_SIGNATURE_VERIFICATION:-}" = "1" ]; then
echo "Skipping Volcano CLI signature verification because VOLCANO_SKIP_SIGNATURE_VERIFICATION=1."
return
fi

semver_re='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
case "$version" in
case "$verify_signature_version" in
latest)
cosign verify-blob "$file" \
--bundle "$bundle" \
cosign verify-blob "$verify_signature_file" \
--bundle "$verify_signature_bundle" \
--certificate-identity-regexp "$VOLCANO_STABLE_TAG_SIGNATURE_IDENTITY_RE" \
--certificate-oidc-issuer "$VOLCANO_SIGNATURE_OIDC_ISSUER"
;;
*)
if [[ "$version" =~ $semver_re ]]; then
identity="${VOLCANO_SIGNATURE_WORKFLOW}@refs/tags/${version}"
cosign verify-blob "$file" \
--bundle "$bundle" \
--certificate-identity "$identity" \
if is_semver "$verify_signature_version"; then
verify_signature_identity="${VOLCANO_SIGNATURE_WORKFLOW}@refs/tags/${verify_signature_version}"
cosign verify-blob "$verify_signature_file" \
--bundle "$verify_signature_bundle" \
--certificate-identity "$verify_signature_identity" \
--certificate-oidc-issuer "$VOLCANO_SIGNATURE_OIDC_ISSUER"
else
fail "cannot verify signature for unsupported Volcano CLI version selector: ${version}; use latest or vMAJOR.MINOR.PATCH"
fail "cannot verify signature for unsupported Volcano CLI version selector: ${verify_signature_version}; use latest or vMAJOR.MINOR.PATCH"
fi
;;
esac

echo "Verified Volcano CLI signature for ${version}."
echo "Verified Volcano CLI signature for ${verify_signature_version}."
}

release_asset_url() {
local version="$1"
local asset="$2"
local semver_re
release_asset_url_version="$1"
release_asset_url_asset="$2"

semver_re='^v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$'
case "$version" in
case "$release_asset_url_version" in
latest)
echo "${VOLCANO_GITHUB_RELEASES_URL%/}/latest/download/${asset}"
echo "${VOLCANO_GITHUB_RELEASES_URL%/}/latest/download/${release_asset_url_asset}"
;;
*)
if [[ "$version" =~ $semver_re ]]; then
echo "${VOLCANO_GITHUB_RELEASES_URL%/}/download/${version}/${asset}"
if is_semver "$release_asset_url_version"; then
echo "${VOLCANO_GITHUB_RELEASES_URL%/}/download/${release_asset_url_version}/${release_asset_url_asset}"
else
fail "unsupported Volcano CLI version selector: ${version}; use latest or vMAJOR.MINOR.PATCH"
fail "unsupported Volcano CLI version selector: ${release_asset_url_version}; use latest or vMAJOR.MINOR.PATCH"
fi
;;
esac
}

resolve_install_dir() {
local os="$1"
resolve_install_dir_os="$1"

if [ -n "$VOLCANO_INSTALL_DIR" ]; then
echo "$VOLCANO_INSTALL_DIR"
return
fi

if [ "$os" = "windows" ]; then
if [ "$resolve_install_dir_os" = "windows" ]; then
echo "${HOME}/bin"
return
fi
Expand All @@ -138,6 +136,13 @@ resolve_install_dir() {
echo "${HOME}/.local/bin"
}

for arg in "$@"; do
case "$arg" in
--setup) RUN_SETUP=1 ;;
*) fail "unknown option: ${arg}" ;;
esac
done

VERSION="${VOLCANO_VERSION:-$VOLCANO_DEFAULT_VERSION}"

if [ -z "$VERSION" ]; then
Expand Down Expand Up @@ -200,10 +205,14 @@ PATH_VOLCANO="$(command -v "$CLI_COMMAND" 2>/dev/null || true)"
if [ -z "$PATH_VOLCANO" ]; then
echo "Add ${INSTALL_DIR} to your PATH to run '${CLI_COMMAND}' from any shell."
echo "Run: ${INSTALL_PATH} --help"
elif [ "$PATH_VOLCANO" != "$INSTALL_PATH" ] && ! [ "$PATH_VOLCANO" -ef "$INSTALL_PATH" ]; then
elif [ "$PATH_VOLCANO" != "$INSTALL_PATH" ]; then
echo "Warning: '${CLI_COMMAND}' on your PATH resolves to ${PATH_VOLCANO}, not ${INSTALL_PATH}."
echo "Move ${INSTALL_DIR} earlier in your PATH or run '${INSTALL_PATH}' directly."
echo "Run: ${INSTALL_PATH} --help"
else
elif [ "$RUN_SETUP" != "1" ]; then
echo "Run: ${CLI_COMMAND} --help"
fi

if [ "$RUN_SETUP" = "1" ]; then
"$INSTALL_PATH" setup
fi
125 changes: 125 additions & 0 deletions scripts/install-volcano.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/bin/sh
set -eu

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT

mkdir -p "$TMP_DIR/bin" "$TMP_DIR/install"

cat > "$TMP_DIR/bin/curl" <<'EOF'
#!/bin/sh
set -eu

output=""
url=""
while [ "$#" -gt 0 ]; do
case "$1" in
--output)
output="$2"
shift 2
;;
https://*)
url="$1"
shift
;;
*) shift ;;
esac
done

printf '%s\n' "$url" > "$FAKE_CURL_LOG"
cp "$FAKE_VOLCANO_BINARY" "$output"
EOF
chmod +x "$TMP_DIR/bin/curl"

cat > "$TMP_DIR/bin/uname" <<'EOF'
#!/bin/sh
case "$1" in
-s) printf '%s\n' "$FAKE_UNAME_S" ;;
-m) printf '%s\n' "$FAKE_UNAME_M" ;;
*) exit 1 ;;
esac
EOF
chmod +x "$TMP_DIR/bin/uname"

cat > "$TMP_DIR/volcano" <<'EOF'
#!/bin/sh
printf '%s\n' "$*" >> "$FAKE_VOLCANO_LOG"
EOF
chmod +x "$TMP_DIR/volcano"

export FAKE_VOLCANO_BINARY="$TMP_DIR/volcano"
export FAKE_VOLCANO_LOG="$TMP_DIR/volcano.log"
export FAKE_CURL_LOG="$TMP_DIR/curl.log"
export FAKE_UNAME_S="Darwin"
export FAKE_UNAME_M="arm64"
export PATH="$TMP_DIR/bin:$PATH"
export VOLCANO_INSTALL_DIR="$TMP_DIR/install"
export VOLCANO_SKIP_SIGNATURE_VERIFICATION=1

assert_asset() {
asset_os="$1"
asset_arch="$2"
asset_name="$3"
asset_dir="$TMP_DIR/install-$asset_os-$asset_arch"

FAKE_UNAME_S="$asset_os" FAKE_UNAME_M="$asset_arch" VOLCANO_INSTALL_DIR="$asset_dir" \
sh "$ROOT/scripts/install-volcano.sh" >/dev/null
grep -Fx "https://github.com/Kong/volcano-cli/releases/latest/download/$asset_name" "$FAKE_CURL_LOG" >/dev/null
case "$asset_name" in
*.exe) test -x "$asset_dir/volcano.exe" ;;
*) test -x "$asset_dir/volcano" ;;
esac
}

assert_rejected() {
rejected_os="$1"
rejected_arch="$2"
rejected_message="$3"

if FAKE_UNAME_S="$rejected_os" FAKE_UNAME_M="$rejected_arch" \
sh "$ROOT/scripts/install-volcano.sh" >"$TMP_DIR/platform-error.log" 2>&1; then
echo "expected $rejected_os $rejected_arch to fail" >&2
exit 1
fi
grep -F "$rejected_message" "$TMP_DIR/platform-error.log" >/dev/null
}

sh "$ROOT/scripts/install-volcano.sh" >/dev/null
test -x "$TMP_DIR/install/volcano"
test ! -e "$FAKE_VOLCANO_LOG"

assert_asset Linux x86_64 volcano-linux-amd64
assert_asset Linux amd64 volcano-linux-amd64
assert_asset Linux arm64 volcano-linux-arm64
assert_asset Linux aarch64 volcano-linux-arm64
assert_asset Darwin x86_64 volcano-macos-amd64
assert_asset Darwin arm64 volcano-macos-arm64
assert_asset MINGW64_NT-10.0 x86_64 volcano-windows-amd64.exe
assert_rejected MINGW64_NT-10.0 arm64 "unsupported platform: windows-arm64"
assert_rejected Plan9 x86_64 "unsupported operating system: plan9"
assert_rejected Linux riscv64 "unsupported architecture: riscv64"

VOLCANO_VERSION=v1.2.3 sh "$ROOT/scripts/install-volcano.sh" >/dev/null
if VOLCANO_VERSION=v01.2.3 sh "$ROOT/scripts/install-volcano.sh" >"$TMP_DIR/version-error.log" 2>&1; then
echo "expected invalid version to fail" >&2
exit 1
fi
grep -F "unsupported Volcano CLI version selector: v01.2.3" "$TMP_DIR/version-error.log" >/dev/null

cat > "$TMP_DIR/bin/volcano" <<'EOF'
#!/bin/sh
exit 0
EOF
chmod +x "$TMP_DIR/bin/volcano"

setup_output="$(sh "$ROOT/scripts/install-volcano.sh" --setup)"
printf '%s\n' "$setup_output" | grep -F \
"Warning: 'volcano' on your PATH resolves to $TMP_DIR/bin/volcano, not $TMP_DIR/install/volcano." >/dev/null
test "$(cat "$FAKE_VOLCANO_LOG")" = "setup"

if sh "$ROOT/scripts/install-volcano.sh" --unknown >"$TMP_DIR/error.log" 2>&1; then
echo "expected unknown option to fail" >&2
exit 1
fi
grep -F "unknown option: --unknown" "$TMP_DIR/error.log" >/dev/null
Loading