Skip to content

Commit 624b1e8

Browse files
Copilotabdurriq
andcommitted
Copy common helpers into each feature directory
Renamed `.common` to `_lib` and copied it into each feature directory that uses common-setup.sh. This ensures the helper scripts are available in the feature build context during testing. The devcontainer CLI only packages files from within the feature directory, so external directories (even if in src/) are not included in the build. Co-authored-by: abdurriq <[email protected]>
1 parent a9cb1ce commit 624b1e8

51 files changed

Lines changed: 3570 additions & 17 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/anaconda/_lib/README.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Common Helper Scripts
2+
3+
This directory contains common helper scripts that can be shared across multiple features to avoid code duplication.
4+
5+
## common-setup.sh
6+
7+
A helper script that provides common setup functions used across multiple features.
8+
9+
### Functions
10+
11+
#### `determine_user_from_input`
12+
13+
Determines the appropriate non-root user based on the input username.
14+
15+
**Usage:**
16+
```bash
17+
# Source the helper script
18+
source "${SCRIPT_DIR}/../.common/common-setup.sh"
19+
20+
# Determine the user
21+
USERNAME=$(determine_user_from_input "${USERNAME}" "root")
22+
```
23+
24+
**Parameters:**
25+
- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username)
26+
- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root")
27+
28+
**Behavior:**
29+
- **"auto" or "automatic"**:
30+
- First checks if `_REMOTE_USER` environment variable is set and is not "root"
31+
- If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list:
32+
1. `devcontainer`
33+
2. `vscode`
34+
3. `node`
35+
4. `codespace`
36+
5. User with UID 1000 (from `/etc/passwd`)
37+
- If no user is found, returns the fallback user (default: "root")
38+
39+
- **"none"**: Always returns "root"
40+
41+
- **Specific username**:
42+
- Validates the user exists using `id -u`
43+
- If the user exists, returns that username
44+
- If the user doesn't exist, returns "root"
45+
46+
**Examples:**
47+
48+
```bash
49+
# Basic usage with default fallback (root)
50+
USERNAME=$(determine_user_from_input "automatic")
51+
52+
# With custom fallback
53+
USERNAME=$(determine_user_from_input "automatic" "vscode")
54+
55+
# Explicit user
56+
USERNAME=$(determine_user_from_input "myuser")
57+
58+
# None (always returns root)
59+
USERNAME=$(determine_user_from_input "none")
60+
```
61+
62+
**Return Value:**
63+
Prints the resolved username to stdout, which can be captured using command substitution.
64+
65+
## Testing
66+
67+
Tests for the helper scripts are located in `/test/.common/`. Run the tests with:
68+
69+
```bash
70+
bash test/.common/test-common-setup.sh
71+
```
72+
73+
## Edge Cases
74+
75+
The helper handles several edge cases:
76+
77+
1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed.
78+
79+
2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created.
80+
81+
3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode.
82+
83+
4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop.
84+
85+
## Migration Guide
86+
87+
To migrate an existing feature to use the common helper:
88+
89+
### Before:
90+
```bash
91+
# Determine the appropriate non-root user
92+
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
93+
USERNAME=""
94+
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
95+
for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
96+
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
97+
USERNAME=${CURRENT_USER}
98+
break
99+
fi
100+
done
101+
if [ "${USERNAME}" = "" ]; then
102+
USERNAME=root
103+
fi
104+
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
105+
USERNAME=root
106+
fi
107+
```
108+
109+
### After:
110+
```bash
111+
# Source common helper functions
112+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
113+
source "${SCRIPT_DIR}/../.common/common-setup.sh"
114+
115+
# Determine the appropriate non-root user
116+
USERNAME=$(determine_user_from_input "${USERNAME}" "root")
117+
```
118+
119+
**Note:** For features like `common-utils` that create users and need a different fallback, use:
120+
```bash
121+
USERNAME=$(determine_user_from_input "${USERNAME}" "vscode")
122+
```

src/anaconda/_lib/common-setup.sh

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/bin/bash
2+
#-------------------------------------------------------------------------------------------------------------------------
3+
# Copyright (c) Microsoft Corporation. All rights reserved.
4+
# Licensed under the MIT License. See https://github.com/devcontainers/features/blob/main/LICENSE for license information.
5+
#-------------------------------------------------------------------------------------------------------------------------
6+
#
7+
# Helper script for common feature setup tasks, including user selection logic.
8+
# Maintainer: The Dev Container spec maintainers
9+
10+
# Determine the appropriate non-root user
11+
# Usage: determine_user_from_input USERNAME [FALLBACK_USER]
12+
#
13+
# This function resolves the USERNAME variable based on the input value:
14+
# - If USERNAME is "auto" or "automatic", it will detect an existing non-root user
15+
# - If USERNAME is "none" or doesn't exist, it will fall back to root
16+
# - Otherwise, it validates the specified USERNAME exists
17+
#
18+
# Arguments:
19+
# USERNAME - The username input (typically from feature configuration)
20+
# FALLBACK_USER - Optional fallback user when no user is found in automatic mode (defaults to "root")
21+
#
22+
# Returns:
23+
# The resolved username is printed to stdout
24+
#
25+
# Examples:
26+
# USERNAME=$(determine_user_from_input "automatic")
27+
# USERNAME=$(determine_user_from_input "vscode")
28+
# USERNAME=$(determine_user_from_input "auto" "vscode")
29+
#
30+
determine_user_from_input() {
31+
local input_username="${1:-automatic}"
32+
local fallback_user="${2:-root}"
33+
local resolved_username=""
34+
35+
if [ "${input_username}" = "auto" ] || [ "${input_username}" = "automatic" ]; then
36+
# Automatic mode: try to detect an existing non-root user
37+
38+
# First, check if _REMOTE_USER is set and is not root
39+
if [ -n "${_REMOTE_USER:-}" ] && [ "${_REMOTE_USER}" != "root" ]; then
40+
# Verify the user exists before using it
41+
if id -u "${_REMOTE_USER}" > /dev/null 2>&1; then
42+
resolved_username="${_REMOTE_USER}"
43+
else
44+
# _REMOTE_USER doesn't exist, fall through to normal detection
45+
resolved_username=""
46+
fi
47+
fi
48+
49+
# If we didn't resolve via _REMOTE_USER, try to find a non-root user
50+
if [ -z "${resolved_username}" ]; then
51+
# Try to find a non-root user from a list of common usernames
52+
# The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000
53+
local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')")
54+
55+
for current_user in "${possible_users[@]}"; do
56+
# Skip empty entries
57+
if [ -z "${current_user}" ]; then
58+
continue
59+
fi
60+
61+
# Check if user exists
62+
if id -u "${current_user}" > /dev/null 2>&1; then
63+
resolved_username="${current_user}"
64+
break
65+
fi
66+
done
67+
68+
# If no user found, use the fallback
69+
if [ -z "${resolved_username}" ]; then
70+
resolved_username="${fallback_user}"
71+
fi
72+
fi
73+
elif [ "${input_username}" = "none" ]; then
74+
# Explicit "none" means use root
75+
resolved_username="root"
76+
else
77+
# Specific username provided - validate it exists
78+
if id -u "${input_username}" > /dev/null 2>&1; then
79+
resolved_username="${input_username}"
80+
else
81+
# User doesn't exist, fall back to root
82+
resolved_username="root"
83+
fi
84+
fi
85+
86+
echo "${resolved_username}"
87+
}

src/anaconda/install.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ chmod +x /etc/profile.d/00-restore-env.sh
8383

8484
# Source common helper functions
8585
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
86-
source "${SCRIPT_DIR}/../.common/common-setup.sh"
86+
source "${SCRIPT_DIR}/_lib/common-setup.sh"
8787

8888
# Determine the appropriate non-root user
8989
USERNAME=$(determine_user_from_input "${USERNAME}" "root")

src/common-utils/_lib/README.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Common Helper Scripts
2+
3+
This directory contains common helper scripts that can be shared across multiple features to avoid code duplication.
4+
5+
## common-setup.sh
6+
7+
A helper script that provides common setup functions used across multiple features.
8+
9+
### Functions
10+
11+
#### `determine_user_from_input`
12+
13+
Determines the appropriate non-root user based on the input username.
14+
15+
**Usage:**
16+
```bash
17+
# Source the helper script
18+
source "${SCRIPT_DIR}/../.common/common-setup.sh"
19+
20+
# Determine the user
21+
USERNAME=$(determine_user_from_input "${USERNAME}" "root")
22+
```
23+
24+
**Parameters:**
25+
- `$1` (required): Input username from feature configuration (e.g., "automatic", "auto", "none", or a specific username)
26+
- `$2` (optional): Fallback username when no user is found in automatic mode (defaults to "root")
27+
28+
**Behavior:**
29+
- **"auto" or "automatic"**:
30+
- First checks if `_REMOTE_USER` environment variable is set and is not "root"
31+
- If `_REMOTE_USER` is root or not set, searches for an existing user from the priority list:
32+
1. `devcontainer`
33+
2. `vscode`
34+
3. `node`
35+
4. `codespace`
36+
5. User with UID 1000 (from `/etc/passwd`)
37+
- If no user is found, returns the fallback user (default: "root")
38+
39+
- **"none"**: Always returns "root"
40+
41+
- **Specific username**:
42+
- Validates the user exists using `id -u`
43+
- If the user exists, returns that username
44+
- If the user doesn't exist, returns "root"
45+
46+
**Examples:**
47+
48+
```bash
49+
# Basic usage with default fallback (root)
50+
USERNAME=$(determine_user_from_input "automatic")
51+
52+
# With custom fallback
53+
USERNAME=$(determine_user_from_input "automatic" "vscode")
54+
55+
# Explicit user
56+
USERNAME=$(determine_user_from_input "myuser")
57+
58+
# None (always returns root)
59+
USERNAME=$(determine_user_from_input "none")
60+
```
61+
62+
**Return Value:**
63+
Prints the resolved username to stdout, which can be captured using command substitution.
64+
65+
## Testing
66+
67+
Tests for the helper scripts are located in `/test/.common/`. Run the tests with:
68+
69+
```bash
70+
bash test/.common/test-common-setup.sh
71+
```
72+
73+
## Edge Cases
74+
75+
The helper handles several edge cases:
76+
77+
1. **Missing awk**: Some systems (like Mariner) don't have awk by default. Features should install it before sourcing the helper if needed.
78+
79+
2. **UID 1000 lookup**: The user with UID 1000 is included in the search as it's commonly the first non-system user created.
80+
81+
3. **_REMOTE_USER behavior**: When `_REMOTE_USER` is set to a non-root user, it takes priority over all other detection methods in automatic mode.
82+
83+
4. **Empty user list entries**: The helper safely handles empty entries in the user detection loop.
84+
85+
## Migration Guide
86+
87+
To migrate an existing feature to use the common helper:
88+
89+
### Before:
90+
```bash
91+
# Determine the appropriate non-root user
92+
if [ "${USERNAME}" = "auto" ] || [ "${USERNAME}" = "automatic" ]; then
93+
USERNAME=""
94+
POSSIBLE_USERS=("vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd)")
95+
for CURRENT_USER in "${POSSIBLE_USERS[@]}"; do
96+
if id -u ${CURRENT_USER} > /dev/null 2>&1; then
97+
USERNAME=${CURRENT_USER}
98+
break
99+
fi
100+
done
101+
if [ "${USERNAME}" = "" ]; then
102+
USERNAME=root
103+
fi
104+
elif [ "${USERNAME}" = "none" ] || ! id -u ${USERNAME} > /dev/null 2>&1; then
105+
USERNAME=root
106+
fi
107+
```
108+
109+
### After:
110+
```bash
111+
# Source common helper functions
112+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
113+
source "${SCRIPT_DIR}/../.common/common-setup.sh"
114+
115+
# Determine the appropriate non-root user
116+
USERNAME=$(determine_user_from_input "${USERNAME}" "root")
117+
```
118+
119+
**Note:** For features like `common-utils` that create users and need a different fallback, use:
120+
```bash
121+
USERNAME=$(determine_user_from_input "${USERNAME}" "vscode")
122+
```

0 commit comments

Comments
 (0)