Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ The mock API (`pkg/mockapi`) validates each successful request and response agai

`make test-integration-php-coverage` runs them for PHP line coverage of the legacy CLI: a CLI built with `legacy.PHPPath` set to `legacy/scripts/coverage/php` runs the legacy source (`TEST_CLI_PHAR_PATH`) on the system PHP, which must load PCOV. Each PHP process writes its coverage to `PHP_COVERAGE_DIR`, and `legacy/scripts/coverage/report.php` prints a summary by directory and writes an HTML report to `dist/php-coverage/html`. CI's `integration-test-php-coverage` job writes the summary to the job summary, and uploads the HTML report.

`installer_test.go` runs `installer.sh` (raw install method) against a fake GitHub server, using `RELEASES_URL` and `RELEASES_API_URL`, with an environment built from scratch so that real tokens are not used. The `test-installer` workflow and `scripts/test/installer*.sh` test the other install methods with real packages.

CI also runs the integration tests on macOS and Windows, using CLIs cross-compiled on Linux (`build-cross`). Skip tests that cannot work on a platform with `t.Skip` and a reason. The legacy CLI writes CRLF line endings on Windows: the `cmdFactory` run helpers convert them to LF.

### Configuration
Expand Down
112 changes: 97 additions & 15 deletions installer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ vendor_name="Upsun"
# Define this to force install method
: "${INSTALL_METHOD:=}"

: "${URL:=https://github.com/upsun/cli/releases/download}"
# The GitHub releases page, and the releases API endpoint (overridden in tests).
: "${RELEASES_URL:=https://github.com/upsun/cli/releases}"
: "${RELEASES_API_URL:=https://api.github.com/repos/upsun/cli/releases}"

: "${URL:=${RELEASES_URL}/download}"

# Force Upsun CLI installation in this directory instead of system directory
: "${INSTALL_DIR:=}"
Expand Down Expand Up @@ -244,21 +248,96 @@ check_curl() {
fi
}

# Print the github.com token from Composer auth JSON (read from stdin), if any.
# See https://getcomposer.org/doc/articles/authentication-for-private-packages.md#github-oauth
# This needs jq, which Upsun containers have: without it, no token is found.
github_token_from_composer_auth() {
jq -r '."github-oauth"."github.com" | strings' 2>/dev/null || true
Comment thread
pjcdawkins marked this conversation as resolved.
Resolved
}

# Try a GitHub token: use it unless GitHub rejects it.
# Arguments: token, source (for messages)
# Returns 0 if the token is used.
try_github_token() {
if [ -z "$1" ]; then
return 1
fi
# Requests to this endpoint do not count against the rate limit.
check_error_file=$(mktemp)
status=$(curl -sS -o /dev/null -w '%{http_code}' -H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $1" "${RELEASES_API_URL%%/repos/*}/rate_limit" 2>"$check_error_file" || true)
check_error=$(head -n 1 "$check_error_file")
rm -f "$check_error_file"
case "$status" in
2*)
output " [*] Using GitHub auth from $2" "success"
;;
401)
output " [ ] Ignoring the GitHub token from $2, as GitHub rejected it" "warning"
return 1
;;
*)
# A network or server error says nothing about the token, so it is kept.
output " [ ] Could not check the GitHub token from $2 (${check_error:-HTTP $status}): using it anyway" "warning"
;;
esac
GITHUB_TOKEN="$1"
}

# Use a GitHub token, if one is available, for higher API rate limits.
# Unauthenticated requests are limited per IP address, which build
# environments often share. Sources are tried in order, until GitHub
# accepts a token.
setup_github_token() {
if gh auth status >/dev/null 2>&1; then
GITHUB_TOKEN="$(gh auth token)"
if ! github_curl https://api.github.com/repos/upsun/cli/releases/latest >/dev/null 2>&1; then
GITHUB_TOKEN=""
else
output " [*] Using GitHub auth from the gh CLI" "success"
fi
elif [ ! -z "${GITHUB_TOKEN}" ]; then
if ! github_curl https://api.github.com/repos/upsun/cli/releases/latest >/dev/null 2>&1; then
GITHUB_TOKEN=""
else
output " [*] Using GitHub auth from the GITHUB_TOKEN env variable" "success"
fi
env_token="${GITHUB_TOKEN}"
GITHUB_TOKEN=""
# The env variable comes first, as with the gh CLI itself.
if try_github_token "${env_token}" "the GITHUB_TOKEN env variable"; then
return
fi
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1 \
&& try_github_token "$(gh auth token 2>/dev/null || true)" "the gh CLI"; then
return
fi
if [ -n "${COMPOSER_AUTH:-}" ] \
&& try_github_token "$(printf '%s' "${COMPOSER_AUTH}" | github_token_from_composer_auth)" "the COMPOSER_AUTH env variable"; then
return
fi
# The token that Upsun provides in project containers. It allows for higher
# rate limits but is otherwise unprivileged.
if [ -n "${HOME:-}" ] && [ -r "${HOME}/.global/auth.json" ]; then
try_github_token "$(github_token_from_composer_auth < "${HOME}/.global/auth.json")" "${HOME}/.global/auth.json" || true
fi
}

# Print the latest release tag, from the GitHub API.
latest_tag_from_api() {
api_error_file=$(mktemp)
tag=$(github_curl "${RELEASES_API_URL}/latest" 2>"$api_error_file" \
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)
if [ -z "$tag" ]; then
api_error=$(head -n 1 "$api_error_file")
output " [ ] Could not get the latest release from the GitHub API: ${api_error}" "warning" >&2
case "$api_error" in
*" 403"*|*" 429"*)
if [ -z "${GITHUB_TOKEN}" ]; then
output " Unauthenticated requests are rate limited: set GITHUB_TOKEN to use a token" "comment" >&2
Comment thread
upsun-dispatch[bot] marked this conversation as resolved.
Resolved
fi
;;
esac
fi
rm -f "$api_error_file"
printf '%s' "$tag"
}

# Print the latest release tag, from the redirect of the latest release page.
# This is not subject to the API's rate limits.
latest_tag_from_redirect() {
location=$(curl -fsS -o /dev/null -w '%{redirect_url}' "${RELEASES_URL}/latest" 2>&1 || true)
case "$location" in
*/releases/tag/*) printf '%s' "${location##*/releases/tag/}" ;;
*) output " [ ] Could not get the latest release from github.com: ${location:-no redirect}" "warning" >&2 ;;
esac
}

# Check that Gzip is installed
Expand All @@ -283,7 +362,10 @@ check_ca_certificates() {

check_version() {
if [ -z "${VERSION}" ]; then
tag=$(github_curl https://api.github.com/repos/upsun/cli/releases/latest 2>/dev/null | sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -n 1)
tag=$(latest_tag_from_api)
if [ -z "$tag" ]; then
tag=$(latest_tag_from_redirect)
fi
else
tag=${VERSION}
fi
Expand Down
Loading
Loading