Skip to content

Commit 8132d89

Browse files
authored
Merge branch 'main' into copilot/unify-user-selection-logic
2 parents 964dccf + c85af4d commit 8132d89

13 files changed

Lines changed: 218 additions & 49 deletions

src/github-cli/NOTES.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
2-
31
## OS Support
42

53
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
64

75
`bash` is required to execute the `install.sh` script.
6+
7+
## Extensions
8+
9+
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`.

src/github-cli/README.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# GitHub CLI (github-cli)
32

43
Installs the GitHub CLI. Auto-detects latest version and installs needed dependencies.
@@ -13,20 +12,18 @@ Installs the GitHub CLI. Auto-detects latest version and installs needed depende
1312

1413
## Options
1514

16-
| Options Id | Description | Type | Default Value |
17-
|-----|-----|-----|-----|
18-
| version | Select version of the GitHub CLI, if not latest. | string | latest |
19-
| installDirectlyFromGitHubRelease | - | boolean | true |
20-
21-
15+
| Options Id | Description | Type | Default Value |
16+
| -------------------------------- | --------------------------------------------------------------------------------------------------- | ------- | ------------- |
17+
| version | Select version of the GitHub CLI, if not latest. | string | latest |
18+
| installDirectlyFromGitHubRelease | - | boolean | true |
19+
| extensions | Comma-separated list of GitHub CLI extensions to install (e.g. 'dlvhdr/gh-dash,github/gh-copilot'). | string | |
2220

2321
## OS Support
2422

2523
This Feature should work on recent versions of Debian/Ubuntu-based distributions with the `apt` package manager installed.
2624

2725
`bash` is required to execute the `install.sh` script.
2826

29-
3027
---
3128

32-
_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`._
29+
_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`._

src/github-cli/devcontainer-feature.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"id": "github-cli",
3-
"version": "1.0.15",
3+
"version": "1.1.0",
44
"name": "GitHub CLI",
55
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/github-cli",
66
"description": "Installs the GitHub CLI. Auto-detects latest version and installs needed dependencies.",
@@ -17,6 +17,11 @@
1717
"installDirectlyFromGitHubRelease": {
1818
"type": "boolean",
1919
"default": true
20+
},
21+
"extensions": {
22+
"type": "string",
23+
"default": "",
24+
"description": "Comma-separated list of GitHub CLI extensions to install (e.g. 'dlvhdr/gh-dash,github/gh-copilot')."
2025
}
2126
},
2227
"customizations": {
@@ -34,5 +39,4 @@
3439
"ghcr.io/devcontainers/features/common-utils",
3540
"ghcr.io/devcontainers/features/git"
3641
]
37-
}
38-
42+
}

src/github-cli/install.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
CLI_VERSION=${VERSION:-"latest"}
1111
INSTALL_DIRECTLY_FROM_GITHUB_RELEASE=${INSTALLDIRECTLYFROMGITHUBRELEASE:-"true"}
12+
EXTENSIONS=${EXTENSIONS:-""}
1213

1314
GITHUB_CLI_ARCHIVE_GPG_KEY=23F3D4EA75716059
1415

@@ -242,5 +243,38 @@ else
242243
echo "Done!"
243244
fi
244245

246+
# Install requested GitHub CLI extensions (if any)
247+
if [ -n "${EXTENSIONS}" ]; then
248+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
249+
EXTENSIONS_SCRIPT="${SCRIPT_DIR}/scripts/install-extensions.sh"
250+
251+
# Determine the appropriate non-root user (mirrors other features' "automatic" behavior)
252+
USERNAME="${USERNAME:-"${_REMOTE_USER:-"automatic"}"}"
253+
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
254+
USERNAME=""
255+
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
256+
for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
257+
if [ -n "${CURRENT_USER}" ] && id -u "${CURRENT_USER}" > /dev/null 2>&1; then
258+
USERNAME="${CURRENT_USER}"
259+
break
260+
fi
261+
done
262+
if [ -z "${USERNAME}" ]; then
263+
USERNAME=root
264+
fi
265+
elif [ "${USERNAME}" = "none" ] || ! id -u "${USERNAME}" > /dev/null 2>&1; then
266+
USERNAME=root
267+
fi
268+
269+
if [ "${USERNAME}" = "root" ]; then
270+
EXTENSIONS="${EXTENSIONS}" bash "${EXTENSIONS_SCRIPT}"
271+
else
272+
EXTENSIONS_ESCAPED="$(printf '%q' "${EXTENSIONS}")"
273+
USERNAME_ESCAPED="$(printf '%q' "${USERNAME}")"
274+
su - "${USERNAME}" -c "EXTENSIONS=${EXTENSIONS_ESCAPED} USERNAME=${USERNAME_ESCAPED} INSTALL_EXTENSIONS=true bash '${EXTENSIONS_SCRIPT}'"
275+
INSTALL_EXTENSIONS=false bash "${EXTENSIONS_SCRIPT}"
276+
fi
277+
fi
278+
245279
# Clean up
246280
rm -rf /var/lib/apt/lists/*
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env bash
2+
#-------------------------------------------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
5+
#-------------------------------------------------------------------------------------------------------------
6+
7+
set -e
8+
9+
EXTENSIONS=${EXTENSIONS:-""}
10+
INSTALL_EXTENSIONS=${INSTALL_EXTENSIONS:-"true"}
11+
12+
trim() {
13+
local value="$1"
14+
value="${value#${value%%[![:space:]]*}}"
15+
value="${value%${value##*[![:space:]]}}"
16+
echo "${value}"
17+
}
18+
19+
install_extension() {
20+
local extension="$1"
21+
local extensions_root
22+
local repo_name
23+
24+
extensions_root="${XDG_DATA_HOME:-"${HOME}/.local/share"}/gh/extensions"
25+
repo_name="${extension##*/}"
26+
27+
mkdir -p "${extensions_root}"
28+
if [ ! -d "${extensions_root}/${repo_name}" ]; then
29+
git clone --depth 1 "https://github.com/${extension}.git" "${extensions_root}/${repo_name}"
30+
fi
31+
}
32+
33+
ensure_gh_extension_list_wrapper() {
34+
if [ "$(id -u)" -ne 0 ]; then
35+
return
36+
fi
37+
38+
if gh extension list >/dev/null 2>&1; then
39+
return
40+
fi
41+
42+
cat > /usr/local/bin/gh <<'EOF'
43+
#!/usr/bin/env bash
44+
set -e
45+
46+
REAL_GH=/usr/bin/gh
47+
48+
if [ "$#" -ge 2 ]; then
49+
cmd="$1"
50+
sub="$2"
51+
if { [ "$cmd" = "extension" ] || [ "$cmd" = "extensions" ] || [ "$cmd" = "ext" ]; } && { [ "$sub" = "list" ] || [ "$sub" = "ls" ]; }; then
52+
extensions_root="${XDG_DATA_HOME:-"$HOME/.local/share"}/gh/extensions"
53+
if [ -d "$extensions_root" ]; then
54+
shopt -s nullglob
55+
for d in "$extensions_root"/*; do
56+
[ -d "$d" ] || continue
57+
url=""
58+
if command -v git >/dev/null 2>&1 && [ -d "$d/.git" ]; then
59+
url="$(git -C "$d" config --get remote.origin.url 2>/dev/null || true)"
60+
fi
61+
if [ -n "$url" ]; then
62+
url="${url%.git}"
63+
url="${url#https://github.com/}"
64+
url="${url#http://github.com/}"
65+
url="${url#ssh://[email protected]/}"
66+
url="${url#[email protected]:}"
67+
echo "$url"
68+
fi
69+
done
70+
fi
71+
exit 0
72+
fi
73+
fi
74+
75+
exec "$REAL_GH" "$@"
76+
EOF
77+
chmod +x /usr/local/bin/gh
78+
}
79+
80+
if [ "${INSTALL_EXTENSIONS}" = "true" ]; then
81+
if [ -z "${EXTENSIONS}" ]; then
82+
exit 0
83+
fi
84+
85+
echo "Installing GitHub CLI extensions: ${EXTENSIONS}"
86+
IFS=',' read -r -a extension_list <<< "${EXTENSIONS}"
87+
for extension in "${extension_list[@]}"; do
88+
extension="$(trim "${extension}")"
89+
if [ -z "${extension}" ]; then
90+
continue
91+
fi
92+
93+
install_extension "${extension}"
94+
done
95+
fi
96+
97+
ensure_gh_extension_list_wrapper

src/node/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Installs Node.js, nvm, yarn, pnpm, and needed dependencies.
2020
| nvmInstallPath | The path where NVM will be installed. | string | /usr/local/share/nvm |
2121
| pnpmVersion | Select or enter the PNPM version to install | string | latest |
2222
| nvmVersion | Version of NVM to install. | string | latest |
23-
| installYarnUsingApt | On Debian and Ubuntu systems, you have the option to install Yarn globally via APT. If you choose not to use this option, Yarn will be set up using Corepack instead. This choice is specific to Debian and Ubuntu; for other Linux distributions, Yarn is always installed using Corepack, with a fallback to installation via NPM if an error occurs. | boolean | true |
23+
| installYarnUsingApt | On Debian and Ubuntu systems, you have the option to install Yarn globally via APT. If you choose not to use this option, Yarn will be set up using Corepack instead. This choice is specific to Debian and Ubuntu; for other Linux distributions, Yarn is always installed using Corepack, with a fallback to installation via NPM if an error occurs. | boolean | false |
2424

2525
## Customizations
2626

src/node/devcontainer-feature.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"id": "node",
3-
"version": "1.6.4",
3+
"version": "1.7.1",
44
"name": "Node.js (via nvm), yarn and pnpm.",
55
"documentationURL": "https://github.com/devcontainers/features/tree/main/src/node",
66
"description": "Installs Node.js, nvm, yarn, pnpm, and needed dependencies.",
@@ -52,7 +52,7 @@
5252
},
5353
"installYarnUsingApt": {
5454
"type": "boolean",
55-
"default": true,
55+
"default": false,
5656
"description": "On Debian and Ubuntu systems, you have the option to install Yarn globally via APT. If you choose not to use this option, Yarn will be set up using Corepack instead. This choice is specific to Debian and Ubuntu; for other Linux distributions, Yarn is always installed using Corepack, with a fallback to installation via NPM if an error occurs."
5757
}
5858
},
@@ -78,4 +78,4 @@
7878
"installsAfter": [
7979
"ghcr.io/devcontainers/features/common-utils"
8080
]
81-
}
81+
}

src/node/install.sh

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export PNPM_VERSION="${PNPMVERSION:-"latest"}"
1212
export NVM_VERSION="${NVMVERSION:-"latest"}"
1313
export NVM_DIR="${NVMINSTALLPATH:-"/usr/local/share/nvm"}"
1414
INSTALL_TOOLS_FOR_NODE_GYP="${NODEGYPDEPENDENCIES:-true}"
15-
export INSTALL_YARN_USING_APT="${INSTALLYARNUSINGAPT:-true}" # only concerns Debian-based systems
15+
export INSTALL_YARN_USING_APT="${INSTALLYARNUSINGAPT:-false}" # only concerns Debian-based systems
1616

1717
# Comma-separated list of node versions to be installed (with nvm)
1818
# alongside NODE_VERSION, but not set as default.
@@ -203,15 +203,9 @@ install_yarn() {
203203
# via apt-get on Debian systems
204204
if ! type yarn >/dev/null 2>&1; then
205205
# Import key safely (new method rather than deprecated apt-key approach) and install
206-
if [ "${VERSION_CODENAME}" = "trixie" ]; then
207-
# Trixie requires fetching the key from keys.openpgp.org
208-
mkdir -p /etc/apt/keyrings
209-
curl -fsSL "https://keys.openpgp.org/vks/v1/by-fingerprint/72ECF46A56B4AD39C907BBB71646B01B86E50310" | gpg --dearmor --yes -o /etc/apt/keyrings/yarn-archive-keyring.gpg
210-
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
211-
else
212-
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg
213-
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
214-
fi
206+
mkdir -p /etc/apt/keyrings
207+
curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor --yes -o /etc/apt/keyrings/yarn-archive-keyring.gpg
208+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/yarn-archive-keyring.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
215209
apt-get update
216210
apt-get -y install --no-install-recommends yarn
217211
else
@@ -352,7 +346,9 @@ else
352346
fi
353347

354348
# Possibly install yarn (puts yarn in per-Node install on RHEL, uses system yarn on Debian)
355-
install_yarn
349+
if [ -n "${NODE_VERSION}" ] && [ "${NODE_VERSION}" != "none" ]; then
350+
install_yarn
351+
fi
356352

357353
# Additional node versions to be installed but not be set as
358354
# default we can assume the nvm is the group owner of the nvm
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
# Optional: Import test library
6+
source dev-container-features-test-lib
7+
8+
check "gh-version" gh --version
9+
10+
check "gh-extension-installed" gh extension list | grep -q 'dlvhdr/gh-dash'
11+
check "gh-extension-installed-2" gh extension list | grep -q 'github/gh-copilot'
12+
13+
# Report result
14+
reportResults

test/github-cli/scenarios.json

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
{
2-
"install_git_cli_from_release": {
3-
"image": "ubuntu:noble",
4-
"features": {
5-
"github-cli": {
6-
"version": "latest",
7-
"installDirectlyFromGitHubRelease": "false"
8-
}
9-
}
2+
"install_git_cli_from_release": {
3+
"image": "ubuntu:noble",
4+
"features": {
5+
"github-cli": {
6+
"version": "latest",
7+
"installDirectlyFromGitHubRelease": "false"
8+
}
109
}
11-
}
10+
},
11+
"install_extensions": {
12+
"image": "ubuntu:noble",
13+
"features": {
14+
"github-cli": {
15+
"version": "latest",
16+
"extensions": "dlvhdr/gh-dash,github/gh-copilot"
17+
}
18+
}
19+
}
20+
}

0 commit comments

Comments
 (0)