diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 66fd3f8..b222aff 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,10 +8,15 @@ on: jobs: test-autogenerated: - runs-on: ubuntu-latest + runs-on: ${{ matrix.runner }} continue-on-error: true strategy: matrix: + include: + - runner: ubuntu-24.04 + arch: amd64 + - runner: ubuntu-24.04-arm + arch: arm64 features: - databricks-cli baseImage: @@ -24,14 +29,19 @@ jobs: - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli - - name: "Generating tests for '${{ matrix.features }}' against '${{ matrix.baseImage }}'" + - name: "Generating tests for '${{ matrix.features }}' against '${{ matrix.baseImage }}' on '${{ matrix.arch }}'" run: devcontainer features test --skip-scenarios -f ${{ matrix.features }} -i ${{ matrix.baseImage }} . test-scenarios: - runs-on: ubuntu-latest + runs-on: ${{ matrix.runner }} continue-on-error: true strategy: matrix: + include: + - runner: ubuntu-24.04 + arch: amd64 + - runner: ubuntu-24.04-arm + arch: arm64 features: - databricks-cli steps: @@ -40,17 +50,24 @@ jobs: - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli - - name: "Generating tests for '${{ matrix.features }}' scenarios" + - name: "Generating tests for '${{ matrix.features }}' scenarios on '${{ matrix.arch }}'" run: devcontainer features test -f ${{ matrix.features }} --skip-autogenerated --skip-duplicated . test-global: - runs-on: ubuntu-latest + runs-on: ${{ matrix.runner }} continue-on-error: true + strategy: + matrix: + include: + - runner: ubuntu-24.04 + arch: amd64 + - runner: ubuntu-24.04-arm + arch: arm64 steps: - uses: actions/checkout@v4 - name: "Install latest devcontainer CLI" run: npm install -g @devcontainers/cli - - name: "Testing global scenarios" + - name: "Testing global scenarios on '${{ matrix.arch }}'" run: devcontainer features test --global-scenarios-only . diff --git a/README.md b/README.md index e69eb65..d43ae5a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ### `databricks-cli` -Adding `databricks-cli` to the devcontainer will install the databricks-cli, defaulting to main, with the given version +Adding `databricks-cli` to the devcontainer installs the Databricks CLI. The feature requires `common-utils`, defaults to `main`, and resolves that to the latest GitHub release before downloading the matching Linux binary for the container architecture and verifying its checksum. ```jsonc { @@ -14,9 +14,9 @@ Adding `databricks-cli` to the devcontainer will install the databricks-cli, def "features": { "ghcr.io/devcontainers/features/common-utils:2": {}, "ghcr.io/mike-fi/devcontainer-features/databricks-cli:1": { - "version": "v0.241.0" + "version": "v0.294.0" } } } ``` -> __NOTE__: This feature currently requires curl to be installed, which is part of the common-utils feature. I'm working on a solution to make it installable cross-platform. \ No newline at end of file +> __NOTE__: This feature depends on `ghcr.io/devcontainers/features/common-utils:2` and installs the Linux `amd64` and `arm64` release archives after verifying the published SHA-256 checksum. diff --git a/src/databricks-cli/README.md b/src/databricks-cli/README.md index 15fcd86..464e721 100644 --- a/src/databricks-cli/README.md +++ b/src/databricks-cli/README.md @@ -1,14 +1,14 @@ # Databricks CLI devcontainer feature -Installing the databricks-cli into your devcontainer as a feature, deminishing the need to write Dockerfiles or bash scripts. +Installing the Databricks CLI into your devcontainer as a feature, diminishing the need to write Dockerfiles or bash scripts. This feature depends on `ghcr.io/devcontainers/features/common-utils:2`. ## Example Usage ```json "features": { "ghcr.io/mike-fi/devcontainer-features/databricks-cli:1": { - "version": "v0.241.0" + "version": "v0.294.0" } } ``` @@ -17,4 +17,4 @@ Installing the databricks-cli into your devcontainer as a feature, deminishing t | Options Id | Description | Type | Default Value | |-----|-----|-----|-----| -| version | Select cli version | string | main | +| version | Select CLI version. `main` resolves to the latest GitHub release. | string | main | diff --git a/src/databricks-cli/devcontainer-feature.json b/src/databricks-cli/devcontainer-feature.json index f23642b..b8d8d9e 100644 --- a/src/databricks-cli/devcontainer-feature.json +++ b/src/databricks-cli/devcontainer-feature.json @@ -1,19 +1,19 @@ { "name": "Databricks CLI", "id": "databricks-cli", - "version": "1.0.2", - "description": "Installing the databricks-cli into the container", + "version": "1.1.0", + "description": "Installing a specific version of databricks-cli into the container", "options": { "version": { "type": "string", "proposals": [ - "v0.241.0" + "v0.294.0" ], "default": "main", - "description": "CLI version to install" + "description": "Databricks CLI version to install. Use \"main\" to resolve the latest GitHub release." } }, - "installsAfter": [ - "ghcr.io/devcontainers/features/common-utils" - ] + "dependsOn": { + "ghcr.io/devcontainers/features/common-utils:2": {} + } } diff --git a/src/databricks-cli/install.sh b/src/databricks-cli/install.sh index 0bd8f79..3178131 100644 --- a/src/databricks-cli/install.sh +++ b/src/databricks-cli/install.sh @@ -4,43 +4,116 @@ # This script is licensed under the MIT License # The software installed by this script is subject to its own license terms. For more information see NOTICE.md and the documentation provided by the software vendor. -set -e +set -eu echo "Activating feature 'databricks-cli'" -VERSION=${VERSION:-"main"} +VERSION="${VERSION:-main}" +TARGET_BIN="/usr/local/bin/databricks" - -# arch = "$(uname -m)" -# case $arch in -# x86_64) arch="amd64";; -# aarch64 | arm8*) arch="arm64";; -# aarch32 | armv7* | armvhf*) arch="arm61";; -# i?86) arch="386";; -# *) echo "('!) Architecture $arch unsupported"; exit 1;; -# esac - -# The 'install.sh' entrypoint script is always executed as the root user. -# -# These following environment variables are passed in by the dev container CLI. -# These may be useful in instances where the context of the final -# remoteUser or containerUser is useful. -# For more details, see https://containers.dev/implementors/features#user-env-var echo "The effective dev container remoteUser is '$_REMOTE_USER'" echo "The effective dev container remoteUser's home directory is '$_REMOTE_USER_HOME'" echo "The effective dev container containerUser is '$_CONTAINER_USER'" echo "The effective dev container containerUser's home directory is '$_CONTAINER_USER_HOME'" -echo "Installing Databricks CLI version: $VERSION" +require_command() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "Error: required command '$1' is not installed." + exit 1 + fi +} + +checksum_verify() { + checksum_file="$1" + archive_name="$2" + archive_checksum_file="$TMP_DIR/${archive_name}.sha256" + + if ! grep "[ *]${archive_name}\$" "$TMP_DIR/$checksum_file" >"$archive_checksum_file"; then + echo "Error: checksum entry for '$archive_name' was not found in '$checksum_file'." + exit 1 + fi + + if command -v sha256sum >/dev/null 2>&1; then + (cd "$TMP_DIR" && sha256sum -c "$(basename "$archive_checksum_file")" >/dev/null) + return + fi + + if command -v shasum >/dev/null 2>&1; then + expected_hash="$(awk '{print $1}' "$archive_checksum_file")" + actual_hash="$(shasum -a 256 "$TMP_DIR/$archive_name" | awk '{print $1}')" + [ "$expected_hash" = "$actual_hash" ] + return + fi + + echo "Error: neither 'sha256sum' nor 'shasum' is available for checksum verification." + exit 1 +} + +resolve_version() { + requested_version="$1" + + if [ "$requested_version" = "main" ] || [ "$requested_version" = "latest" ]; then + latest_release_url="$(curl -fsSL -o /dev/null -w '%{url_effective}' https://github.com/databricks/cli/releases/latest)" + resolved_version="${latest_release_url##*/}" + else + resolved_version="$requested_version" + fi + + case "$resolved_version" in + v*) printf '%s\n' "$resolved_version" ;; + *) printf 'v%s\n' "$resolved_version" ;; + esac +} + +detect_arch() { + raw_arch="$(uname -m)" + case "$raw_arch" in + x86_64|amd64) printf 'amd64\n' ;; + aarch64|arm64) printf 'arm64\n' ;; + *) + echo "Error: unsupported architecture '$raw_arch'. Supported architectures: amd64, arm64." + exit 1 + ;; + esac +} + +require_command curl +require_command tar +require_command install + +resolved_version="$(resolve_version "$VERSION")" +version_number="${resolved_version#v}" +arch="$(detect_arch)" + +archive_name="databricks_cli_${version_number}_linux_${arch}.tar.gz" +checksum_name="databricks_cli_${version_number}_SHA256SUMS_unix" +release_base_url="https://github.com/databricks/cli/releases/download/${resolved_version}" + +echo "Installing Databricks CLI version: ${resolved_version} for linux/${arch}" + +TMP_DIR="$(mktemp -d)" +STAGED_BIN="" +cleanup() { + if [ -n "$STAGED_BIN" ] && [ -e "$STAGED_BIN" ]; then + rm -f "$STAGED_BIN" + fi + rm -rf "$TMP_DIR" +} +trap cleanup EXIT INT HUP TERM + +curl -fsSLo "$TMP_DIR/$archive_name" "${release_base_url}/${archive_name}" +curl -fsSLo "$TMP_DIR/$checksum_name" "${release_base_url}/${checksum_name}" -# assert curl is available -if ! command -v curl >/dev/null 2>&1; then - echo "Error: curl is not installed. Please install curl and try again." +if ! checksum_verify "$checksum_name" "$archive_name"; then + echo "Error: checksum verification failed for '$archive_name'." exit 1 fi -curl -fsSL https://raw.githubusercontent.com/databricks/setup-cli/$VERSION/install.sh | sh +tar -xzf "$TMP_DIR/$archive_name" -C "$TMP_DIR" +STAGED_BIN="$(mktemp "${TARGET_BIN}.tmp.XXXXXX")" +install -m 0755 "$TMP_DIR/databricks" "$STAGED_BIN" +mv -f "$STAGED_BIN" "$TARGET_BIN" +STAGED_BIN="" -echo "Databricks CLI installed successfully" -chmod +x /usr/local/bin/databricks +echo "Databricks CLI installed successfully: $("$TARGET_BIN" -v)" diff --git a/test/databricks-cli/scenarios.json b/test/databricks-cli/scenarios.json index 7a7de1e..19dbea7 100644 --- a/test/databricks-cli/scenarios.json +++ b/test/databricks-cli/scenarios.json @@ -2,7 +2,7 @@ "test_version": { "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { - "databricks": { + "databricks-cli": { } } } diff --git a/test/databricks-cli/test.sh b/test/databricks-cli/test.sh new file mode 100644 index 0000000..8c5a8b5 --- /dev/null +++ b/test/databricks-cli/test.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +set -eu + +check() { + description="$1" + shift + + if "$@"; then + echo "PASS: ${description}" + else + echo "FAIL: ${description}" + exit 1 + fi +} + +check_output_contains() { + description="$1" + expected="$2" + shift 2 + + output="$("$@")" + case "$output" in + *"$expected"*) + echo "PASS: ${description}" + ;; + *) + echo "FAIL: ${description}" + echo "Output was: ${output}" + exit 1 + ;; + esac +} + +check "curl is available via common-utils" command -v curl +check "databricks is installed" command -v databricks +check "databricks binary is executable" test -x /usr/local/bin/databricks +check_output_contains "databricks reports a version" "Databricks CLI" databricks -v