Skip to content

Commit d179eb0

Browse files
Copilotabdurriq
andcommitted
Add common-setup.sh helper script with user selection logic and comprehensive tests
Co-authored-by: abdurriq <[email protected]>
1 parent f17e2c2 commit d179eb0

2 files changed

Lines changed: 281 additions & 0 deletions

File tree

src/_common/common-setup.sh

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
resolved_username="${_REMOTE_USER}"
41+
else
42+
# Try to find a non-root user from a list of common usernames
43+
# The list includes: devcontainer, vscode, node, codespace, and the user with UID 1000
44+
local possible_users=("devcontainer" "vscode" "node" "codespace" "$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')")
45+
46+
for current_user in "${possible_users[@]}"; do
47+
# Skip empty entries
48+
if [ -z "${current_user}" ]; then
49+
continue
50+
fi
51+
52+
# Check if user exists
53+
if id -u "${current_user}" > /dev/null 2>&1; then
54+
resolved_username="${current_user}"
55+
break
56+
fi
57+
done
58+
59+
# If no user found, use the fallback
60+
if [ -z "${resolved_username}" ]; then
61+
resolved_username="${fallback_user}"
62+
fi
63+
fi
64+
elif [ "${input_username}" = "none" ]; then
65+
# Explicit "none" means use root
66+
resolved_username="root"
67+
else
68+
# Specific username provided - validate it exists
69+
if id -u "${input_username}" > /dev/null 2>&1; then
70+
resolved_username="${input_username}"
71+
else
72+
# User doesn't exist, fall back to root
73+
resolved_username="root"
74+
fi
75+
fi
76+
77+
echo "${resolved_username}"
78+
}

test/_common/test-common-setup.sh

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#!/bin/bash
2+
#-------------------------------------------------------------------------------------------------------------------------
3+
# Tests for common-setup.sh helper functions
4+
# These tests validate the determine_user_from_input function
5+
#-------------------------------------------------------------------------------------------------------------------------
6+
7+
set -e
8+
9+
# Source the helper script
10+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11+
source "${SCRIPT_DIR}/../../src/_common/common-setup.sh"
12+
13+
# Test counters
14+
PASSED=0
15+
FAILED=0
16+
TOTAL=0
17+
18+
# Helper function to run a test
19+
run_test() {
20+
local test_name="$1"
21+
local expected="$2"
22+
local actual="$3"
23+
24+
TOTAL=$((TOTAL + 1))
25+
26+
if [ "${expected}" = "${actual}" ]; then
27+
echo "✓ PASS: ${test_name}"
28+
PASSED=$((PASSED + 1))
29+
else
30+
echo "✗ FAIL: ${test_name}"
31+
echo " Expected: '${expected}'"
32+
echo " Actual: '${actual}'"
33+
FAILED=$((FAILED + 1))
34+
fi
35+
}
36+
37+
# Test 1: Automatic mode finds existing user or fallback
38+
test_automatic_no_users() {
39+
local result=$(determine_user_from_input "automatic")
40+
# Should find either a known user or fallback to root
41+
# On this system, there may be a UID 1000 user (like packer)
42+
local uid_1000_user=$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')
43+
local expected="${uid_1000_user:-root}"
44+
run_test "Automatic mode with no matching common users finds UID 1000 or root" "${expected}" "${result}"
45+
}
46+
47+
# Test 2: Automatic mode with fallback user
48+
test_automatic_with_fallback() {
49+
local result=$(determine_user_from_input "automatic" "vscode")
50+
# Should find a user or use the fallback - check if UID 1000 exists
51+
local uid_1000_user=$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')
52+
local expected="${uid_1000_user:-vscode}"
53+
run_test "Automatic mode with custom fallback finds UID 1000 or uses fallback" "${expected}" "${result}"
54+
}
55+
56+
# Test 3: Explicit "none" should return root
57+
test_none_returns_root() {
58+
local result=$(determine_user_from_input "none")
59+
run_test "Explicit 'none' returns root" "root" "${result}"
60+
}
61+
62+
# Test 4: Explicit "none" ignores fallback
63+
test_none_ignores_fallback() {
64+
local result=$(determine_user_from_input "none" "vscode")
65+
run_test "Explicit 'none' ignores fallback" "root" "${result}"
66+
}
67+
68+
# Test 5: Existing user (root) should return root
69+
test_existing_user_root() {
70+
local result=$(determine_user_from_input "root")
71+
run_test "Existing user 'root' returns root" "root" "${result}"
72+
}
73+
74+
# Test 6: Non-existing user should return root
75+
test_nonexisting_user() {
76+
local result=$(determine_user_from_input "nonexistentuser12345")
77+
run_test "Non-existing user returns root" "root" "${result}"
78+
}
79+
80+
# Test 7: Auto mode (synonym for automatic)
81+
test_auto_synonym() {
82+
local result=$(determine_user_from_input "auto" "customfallback")
83+
# Should behave same as automatic - find UID 1000 or use fallback
84+
local uid_1000_user=$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')
85+
local expected="${uid_1000_user:-customfallback}"
86+
run_test "Auto mode with fallback finds UID 1000 or uses fallback" "${expected}" "${result}"
87+
}
88+
89+
# Test 8: _REMOTE_USER environment variable (when set and not root)
90+
test_remote_user_set() {
91+
# Test with an existing user (root is always available)
92+
# We'll use a user that exists on the system
93+
local existing_user=$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo 'root')
94+
95+
export _REMOTE_USER="${existing_user}"
96+
local result=$(determine_user_from_input "automatic")
97+
unset _REMOTE_USER
98+
99+
run_test "_REMOTE_USER set to non-root user" "${existing_user}" "${result}"
100+
}
101+
102+
# Test 9: _REMOTE_USER set to root should use fallback logic
103+
test_remote_user_root() {
104+
export _REMOTE_USER="root"
105+
local result=$(determine_user_from_input "automatic" "mydefault")
106+
unset _REMOTE_USER
107+
108+
# Should use fallback logic - find UID 1000 or use fallback
109+
local uid_1000_user=$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')
110+
local expected="${uid_1000_user:-mydefault}"
111+
run_test "_REMOTE_USER set to root uses fallback logic" "${expected}" "${result}"
112+
}
113+
114+
# Test 10: Finding vscode user if it exists
115+
test_find_vscode_user() {
116+
# Check if vscode user exists
117+
if id -u vscode > /dev/null 2>&1; then
118+
local result=$(determine_user_from_input "automatic")
119+
# Should find vscode (it's second in priority after devcontainer)
120+
run_test "Finds vscode user in automatic mode" "vscode" "${result}"
121+
else
122+
# Skip this test if vscode user doesn't exist
123+
run_test "Finds vscode user in automatic mode (SKIPPED - user doesn't exist)" "SKIP" "SKIP"
124+
fi
125+
}
126+
127+
# Test 11: Finding devcontainer user (highest priority)
128+
test_find_devcontainer_user() {
129+
# Check if devcontainer user exists
130+
if id -u devcontainer > /dev/null 2>&1; then
131+
local result=$(determine_user_from_input "automatic")
132+
# Should find devcontainer (highest priority)
133+
run_test "Finds devcontainer user (highest priority)" "devcontainer" "${result}"
134+
else
135+
# Skip this test if devcontainer user doesn't exist
136+
run_test "Finds devcontainer user (highest priority) (SKIPPED - user doesn't exist)" "SKIP" "SKIP"
137+
fi
138+
}
139+
140+
# Test 12: Finding user with UID 1000
141+
test_find_uid_1000() {
142+
# Check if there's a user with UID 1000
143+
local uid_1000_user=$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')
144+
145+
if [ -n "${uid_1000_user}" ] && \
146+
! id -u devcontainer > /dev/null 2>&1 && \
147+
! id -u vscode > /dev/null 2>&1 && \
148+
! id -u node > /dev/null 2>&1 && \
149+
! id -u codespace > /dev/null 2>&1; then
150+
# Only test if UID 1000 exists and no higher priority users exist
151+
local result=$(determine_user_from_input "automatic")
152+
run_test "Finds user with UID 1000" "${uid_1000_user}" "${result}"
153+
else
154+
# Skip this test if conditions aren't met
155+
run_test "Finds user with UID 1000 (SKIPPED - conditions not met)" "SKIP" "SKIP"
156+
fi
157+
}
158+
159+
# Test 13: Empty input defaults to "automatic"
160+
test_empty_input() {
161+
local result=$(determine_user_from_input "" "mydefault")
162+
# Should behave as automatic mode - find UID 1000 or use fallback
163+
local uid_1000_user=$(awk -v val=1000 -F ":" '$3==val{print $1}' /etc/passwd 2>/dev/null || echo '')
164+
local expected="${uid_1000_user:-mydefault}"
165+
run_test "Empty input treated as automatic and finds UID 1000 or uses fallback" "${expected}" "${result}"
166+
}
167+
168+
# Run all tests
169+
echo "Running tests for common-setup.sh..."
170+
echo "======================================"
171+
echo ""
172+
173+
test_automatic_no_users
174+
test_automatic_with_fallback
175+
test_none_returns_root
176+
test_none_ignores_fallback
177+
test_existing_user_root
178+
test_nonexisting_user
179+
test_auto_synonym
180+
test_remote_user_set
181+
test_remote_user_root
182+
test_find_vscode_user
183+
test_find_devcontainer_user
184+
test_find_uid_1000
185+
test_empty_input
186+
187+
# Print summary
188+
echo ""
189+
echo "======================================"
190+
echo "Test Summary:"
191+
echo " Total: ${TOTAL}"
192+
echo " Passed: ${PASSED}"
193+
echo " Failed: ${FAILED}"
194+
echo "======================================"
195+
196+
# Exit with appropriate code
197+
if [ ${FAILED} -eq 0 ]; then
198+
echo "All tests passed! ✓"
199+
exit 0
200+
else
201+
echo "Some tests failed! ✗"
202+
exit 1
203+
fi

0 commit comments

Comments
 (0)