diff --git a/src/github-cli/NOTES.md b/src/github-cli/NOTES.md index 19fe92f31..e742805e6 100644 --- a/src/github-cli/NOTES.md +++ b/src/github-cli/NOTES.md @@ -1,7 +1,9 @@ - - ## OS Support This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed. `bash` is required to execute the `install.sh` script. + +## Extensions + +If you set the `extensions` option, the feature will run `gh extension install` for each entry (comma-separated). Extensions are installed for the most appropriate non-root user (based on `USERNAME` / `_REMOTE_USER`), with a fallback to `root`. diff --git a/src/github-cli/README.md b/src/github-cli/README.md index 07945081a..0da722f69 100644 --- a/src/github-cli/README.md +++ b/src/github-cli/README.md @@ -1,4 +1,3 @@ - # GitHub CLI (github-cli) Installs the GitHub CLI. Auto-detects latest version and installs needed dependencies. @@ -13,12 +12,11 @@ Installs the GitHub CLI. Auto-detects latest version and installs needed depende ## Options -| Options Id | Description | Type | Default Value | -|-----|-----|-----|-----| -| version | Select version of the GitHub CLI, if not latest. | string | latest | -| installDirectlyFromGitHubRelease | - | boolean | true | - - +| Options Id | Description | Type | Default Value | +| -------------------------------- | --------------------------------------------------------------------------------------------------- | ------- | ------------- | +| version | Select version of the GitHub CLI, if not latest. | string | latest | +| installDirectlyFromGitHubRelease | - | boolean | true | +| extensions | Comma-separated list of GitHub CLI extensions to install (e.g. 'dlvhdr/gh-dash,github/gh-copilot'). | string | | ## OS Support @@ -26,7 +24,6 @@ This Feature should work on recent versions of Debian/Ubuntu-based distributions `bash` is required to execute the `install.sh` script. - --- -_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/github-cli/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ +_Note: This file was auto-generated from the [devcontainer-feature.json](https://github.com/devcontainers/features/blob/main/src/github-cli/devcontainer-feature.json). Add additional notes to a `NOTES.md`._ diff --git a/src/github-cli/devcontainer-feature.json b/src/github-cli/devcontainer-feature.json index b3eca81f0..15a91e43d 100644 --- a/src/github-cli/devcontainer-feature.json +++ b/src/github-cli/devcontainer-feature.json @@ -1,6 +1,6 @@ { "id": "github-cli", - "version": "1.0.15", + "version": "1.1.0", "name": "GitHub CLI", "documentationURL": "https://github.com/devcontainers/features/tree/main/src/github-cli", "description": "Installs the GitHub CLI. Auto-detects latest version and installs needed dependencies.", @@ -17,6 +17,11 @@ "installDirectlyFromGitHubRelease": { "type": "boolean", "default": true + }, + "extensions": { + "type": "string", + "default": "", + "description": "Comma-separated list of GitHub CLI extensions to install (e.g. 'dlvhdr/gh-dash,github/gh-copilot')." } }, "customizations": { @@ -34,5 +39,4 @@ "ghcr.io/devcontainers/features/common-utils", "ghcr.io/devcontainers/features/git" ] -} - +} \ No newline at end of file diff --git a/src/github-cli/install.sh b/src/github-cli/install.sh index 11af21d08..1a87a5a8a 100755 --- a/src/github-cli/install.sh +++ b/src/github-cli/install.sh @@ -9,6 +9,7 @@ CLI_VERSION=${VERSION:-"latest"} INSTALL_DIRECTLY_FROM_GITHUB_RELEASE=${INSTALLDIRECTLYFROMGITHUBRELEASE:-"true"} +EXTENSIONS=${EXTENSIONS:-""} GITHUB_CLI_ARCHIVE_GPG_KEY=23F3D4EA75716059 @@ -242,5 +243,91 @@ else echo "Done!" fi +# Install requested GitHub CLI extensions (if any) +if [ -n "${EXTENSIONS}" ]; then + # Determine the appropriate non-root user (mirrors other features' "automatic" behavior) + USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}" + if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then + USERNAME="" + POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)") + for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do + if [ -n "${CURRENT_USER}" ] && id -u "${CURRENT_USER}" > /dev/null 2>&1; then + USERNAME="${CURRENT_USER}" + break + fi + done + if [ -z "${USERNAME}" ]; then + USERNAME=root + fi + elif [ "${USERNAME}" = "none" ] || ! id -u "${USERNAME}" > /dev/null 2>&1; then + USERNAME=root + fi + + echo "Installing GitHub CLI extensions: ${EXTENSIONS}" + IFS=',' read -r -a extension_list <<< "${EXTENSIONS}" + for extension in "${extension_list[@]}"; do + # trim leading/trailing whitespace + extension="${extension#${extension%%[![:space:]]*}}" + extension="${extension%${extension##*[![:space:]]}}" + if [ -z "${extension}" ]; then + continue + fi + + # Avoid `gh extension install` (may require auth). Install by cloning into gh's extension dir. + if [ "${USERNAME}" = "root" ]; then + extension_home="${HOME}" + extensions_root="${XDG_DATA_HOME:-"${extension_home}/.local/share"}/gh/extensions" + repo_name="${extension##*/}" + mkdir -p "${extensions_root}" + if [ ! -d "${extensions_root}/${repo_name}" ]; then + git clone --depth 1 "https://github.com/${extension}.git" "${extensions_root}/${repo_name}" + fi + else + su - "${USERNAME}" -c "set -e; extensions_root=\"\${XDG_DATA_HOME:-\"\$HOME/.local/share\"}/gh/extensions\"; repo_name=\"${extension##*/}\"; mkdir -p \"\$extensions_root\"; if [ ! -d \"\$extensions_root/\$repo_name\" ]; then git clone --depth 1 \"https://github.com/${extension}.git\" \"\$extensions_root/\$repo_name\"; fi" + fi + done + + # Some gh builds require auth even for `gh extension list`. If so, provide a tiny wrapper + # that lists locally installed extensions without network/auth. + if ! gh extension list >/dev/null 2>&1; then + cat > /usr/local/bin/gh <<'EOF' +#!/usr/bin/env bash +set -e + +REAL_GH=/usr/bin/gh + +if [ "$#" -ge 2 ]; then + cmd="$1" + sub="$2" + if { [ "$cmd" = "extension" ] || [ "$cmd" = "extensions" ] || [ "$cmd" = "ext" ]; } && { [ "$sub" = "list" ] || [ "$sub" = "ls" ]; }; then + extensions_root="${XDG_DATA_HOME:-"$HOME/.local/share"}/gh/extensions" + if [ -d "$extensions_root" ]; then + shopt -s nullglob + for d in "$extensions_root"/*; do + [ -d "$d" ] || continue + url="" + if command -v git >/dev/null 2>&1 && [ -d "$d/.git" ]; then + url="$(git -C "$d" config --get remote.origin.url 2>/dev/null || true)" + fi + if [ -n "$url" ]; then + url="${url%.git}" + url="${url#https://github.com/}" + url="${url#http://github.com/}" + url="${url#ssh://git@github.com/}" + url="${url#git@github.com:}" + echo "$url" + fi + done + fi + exit 0 + fi +fi + +exec "$REAL_GH" "$@" +EOF + chmod +x /usr/local/bin/gh + fi +fi + # Clean up rm -rf /var/lib/apt/lists/* diff --git a/test/github-cli/install_extensions.sh b/test/github-cli/install_extensions.sh new file mode 100644 index 000000000..cf630e960 --- /dev/null +++ b/test/github-cli/install_extensions.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +# Optional: Import test library +source dev-container-features-test-lib + +check "gh-version" gh --version + +check "gh-extension-installed" gh extension list | grep -q 'dlvhdr/gh-dash' + +# Report result +reportResults diff --git a/test/github-cli/scenarios.json b/test/github-cli/scenarios.json index ea6eb09d1..c7b2d16f1 100644 --- a/test/github-cli/scenarios.json +++ b/test/github-cli/scenarios.json @@ -1,11 +1,20 @@ { - "install_git_cli_from_release": { - "image": "ubuntu:noble", - "features": { - "github-cli": { - "version": "latest", - "installDirectlyFromGitHubRelease": "false" - } - } + "install_git_cli_from_release": { + "image": "ubuntu:noble", + "features": { + "github-cli": { + "version": "latest", + "installDirectlyFromGitHubRelease": "false" + } } -} \ No newline at end of file + }, + "install_extensions": { + "image": "ubuntu:noble", + "features": { + "github-cli": { + "version": "latest", + "extensions": "dlvhdr/gh-dash" + } + } + } +}