Skip to content

Commit de30dea

Browse files
Copilotaslafy-z
andauthored
feat: Support helm --username and --password (#347)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Zadkiel AHARONIAN <hello@zadkiel.fr>
1 parent 1cded0a commit de30dea

File tree

5 files changed

+231
-13
lines changed

5 files changed

+231
-13
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
strategy:
2828
fail-fast: false
2929
matrix:
30-
helm: ['2.17.0', '3.4.2', '3.7.1']
30+
helm: ['2.17.0', '3.4.2', '3.7.1', '3.18.5']
3131
env:
3232
FIXTURES_GIT_REPO: ${{ format('{0}/{1}', github.server_url, github.event.pull_request.head.repo.full_name || github.repository) }}
3333
FIXTURES_GIT_REF: ${{ github.head_ref || github.ref_name }}

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,23 @@ As this plugin uses `git` CLI to clone repos. You can configure private access i
9898

9999
- **using ssh**: Start a [ssh-agent daemon](https://help.github.com/articles/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent/#adding-your-ssh-key-to-the-ssh-agent)
100100
- **using https**: Use a [credentials helper](https://git-scm.com/docs/gitcredentials)
101+
- **using helm credentials**: Use `helm repo add --username user --password pass` to provide credentials
102+
103+
#### Helm Credentials Support
104+
105+
> **Note:** This feature requires Helm v3.14.0 or later.
106+
107+
This plugin supports Helm's built-in credential passing mechanism. When you use `helm repo add` with `--username` and `--password` flags, the plugin automatically configures git to use these credentials:
108+
109+
```bash
110+
# Add a repository with credentials
111+
helm repo add my-repo --username myuser --password mypass git+https://github.com/company/charts@charts?ref=main
112+
113+
# The credentials are automatically used for git operations
114+
helm fetch my-repo/my-chart
115+
```
116+
117+
**Note**: When both Helm credentials and existing git authentication (SSH keys, credential helpers) are available, Helm credentials take precedence for the current operation.
101118

102119
### Note on SSH relative paths
103120

helm-git-plugin.sh

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ readonly git_quiet
4444
# Set default value for TMPDIR
4545
export TMPDIR="${TMPDIR:-/tmp}"
4646

47+
# Initialize git credential variables
48+
git_username="${HELM_PLUGIN_USERNAME:-}"
49+
git_password="${HELM_PLUGIN_PASSWORD:-}"
50+
unset HELM_PLUGIN_USERNAME
51+
unset HELM_PLUGIN_PASSWORD
52+
4753
# Cache repos or charts depending on the cache path existing in the environment variables
4854
cache_repos_enabled=0
4955
if [ -n "${HELM_GIT_REPO_CACHE:-}" ]; then
@@ -90,19 +96,36 @@ warning() {
9096

9197
## Functions
9298

99+
# git_cmd(git_arguments...)
100+
# Execute git command with credential helper if credentials are available
101+
git_cmd() {
102+
_ret=0
103+
if [ -n "${git_username:-}" ]; then
104+
trace "[git, username:${git_username}] $*"
105+
# shellcheck disable=SC2016
106+
GIT_TERMINAL_PROMPT=0 GIT_USERNAME="${git_username}" GIT_PASSWORD="${git_password}" git -c credential.helper='!f() { echo "username=${GIT_USERNAME}"; echo "password=${GIT_PASSWORD}"; }; f' "$@"
107+
_ret=$?
108+
else
109+
trace "[git] $*"
110+
GIT_TERMINAL_PROMPT=0 git "$@"
111+
_ret=$?
112+
fi
113+
return $_ret
114+
}
115+
93116
# git_try(git_repo)
94117
git_try() {
95118
_git_repo=$1
96119

97-
GIT_TERMINAL_PROMPT=0 git ls-remote "$_git_repo" --refs >"${git_output}" 2>&1 || return 1
120+
git_cmd ls-remote "$_git_repo" --refs >"${git_output}" 2>&1 || return 1
98121
}
99122

100123
#git_get_default_branch(git_repo_path)
101124
git_get_default_branch() {
102125
_git_repo="${1?Missing git_repo as first parameter}"
103126

104127
# Fetch default branch from remote
105-
_git_symref=$(GIT_TERMINAL_PROMPT=0 git ls-remote --symref "${_git_repo}" origin HEAD 2>"${git_output}") || return
128+
_git_symref=$(git_cmd ls-remote --symref "${_git_repo}" origin HEAD 2>"${git_output}") || return
106129
echo "$_git_symref" | awk '/^ref:/ {sub(/refs\/heads\//, "", $2); print $2}' || return
107130
}
108131

@@ -112,7 +135,7 @@ git_fetch_ref() {
112135
_git_ref="${2?Mising git_ref as second parameter}"
113136

114137
# Fetches any kind of ref to its right place, tags, annotated tags, branches and commit refs
115-
GIT_DIR="${_git_repo_path}" git fetch -u --depth=1 origin "refs/*/${_git_ref}:refs/*/${_git_ref}" "${_git_ref}" >"${git_output}" 2>&1
138+
GIT_DIR="${_git_repo_path}" git_cmd fetch -u --depth=1 origin "refs/*/${_git_ref}:refs/*/${_git_ref}" "${_git_ref}" >"${git_output}" 2>&1
116139
}
117140

118141
#git_cache_intercept(git_repo, git_ref)
@@ -132,14 +155,14 @@ git_cache_intercept() {
132155
{
133156
mkdir -p "${repo_path}" &&
134157
cd "${repo_path}" &&
135-
git init --bare ${git_quiet} >"${git_output}" 2>&1 &&
136-
git remote add origin "${_git_repo}" >"${git_output}" 2>&1
158+
git_cmd init --bare ${git_quiet} >"${git_output}" 2>&1 &&
159+
git_cmd remote add origin "${_git_repo}" >"${git_output}" 2>&1
137160
} >&2 || debug "Could not setup ${_git_repo}" && return 1
138161
else
139162
debug "${_git_repo} exists in cache"
140163
fi
141164
debug "Making sure we have the requested ref #${_git_ref}"
142-
if [ -z "$(GIT_DIR="${repo_path}" git tag -l "${_git_ref}" 2>"${git_output}")" ]; then
165+
if [ -z "$(GIT_DIR="${repo_path}" git_cmd tag -l "${_git_ref}" 2>"${git_output}")" ]; then
143166
debug "Did not find ${_git_ref} in our cache for ${_git_repo}, fetching...."
144167
# This fetches properly tags, annotated tags, branches and commits that match the name and leave them at the right place
145168
git_fetch_ref "${repo_path}" "${_git_ref}" ||
@@ -168,18 +191,18 @@ git_checkout() {
168191

169192
{
170193
cd "$_target_path"
171-
git init ${git_quiet} >"${git_output}" 2>&1
172-
git config pull.ff only >"${git_output}" 2>&1
173-
git remote add origin "$_git_repo" >"${git_output}" 2>&1
194+
git_cmd init ${git_quiet} >"${git_output}" 2>&1
195+
git_cmd config pull.ff only >"${git_output}" 2>&1
196+
git_cmd remote add origin "$_git_repo" >"${git_output}" 2>&1
174197
}
175198
if [ "$_sparse" = "1" ] && [ -n "$_git_path" ]; then
176-
git config core.sparseCheckout true >"${git_output}" 2>&1
199+
git_cmd config core.sparseCheckout true >"${git_output}" 2>&1
177200
mkdir -p .git/info
178201
echo "$_git_path/*" >.git/info/sparse-checkout
179202
fi
180203
git_fetch_ref "${PWD}/.git" "${_git_ref}" ||
181204
error "Unable to fetch remote. Check your Git url."
182-
git checkout ${git_quiet} "${_git_ref}" >"${git_output}" 2>&1 ||
205+
git_cmd checkout ${git_quiet} "${_git_ref}" >"${git_output}" 2>&1 ||
183206
error "Unable to checkout ref. Check your Git ref ($_git_ref) and path ($_git_path)."
184207
# shellcheck disable=SC2010,SC2012
185208
if [ "$(ls -A | grep -v '^.git$' -c)" = "0" ]; then
@@ -408,7 +431,7 @@ main() {
408431
fi
409432

410433
# Setup exit trap
411-
# shellcheck disable=SC2317,SC2329
434+
# shellcheck disable=SC2317,SC2329
412435
exit_trap() {
413436
[ "$cleanup" = 1 ] || return 0
414437
rm -rf "$git_root_path" "${helm_home_target_path:-}"

tests/07-credentials.bats

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
#!/usr/bin/env bats
2+
3+
load 'test-helper'
4+
5+
setup_file() {
6+
if ! helm_supports_credentials; then
7+
echo "# Skipping credential tests - Helm version < 3.14.0 does not support credential passing" >&2
8+
fi
9+
}
10+
11+
@test "should setup git credentials when HELM_PLUGIN_USERNAME and HELM_PLUGIN_PASSWORD are set" {
12+
if ! helm_supports_credentials; then
13+
skip "Helm version < 3.14.0 does not support credential passing"
14+
fi
15+
16+
export HELM_PLUGIN_USERNAME="testuser"
17+
export HELM_PLUGIN_PASSWORD="testpass"
18+
19+
# Source the plugin and check that credentials are stored
20+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username}" && echo "git_password=${git_password}"'
21+
[ $status = 0 ]
22+
[[ "$output" == *"git_username=testuser"* ]]
23+
[[ "$output" == *"git_password=testpass"* ]]
24+
25+
# Check that the original HELM_PLUGIN_* variables are unset for security
26+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "HELM_PLUGIN_USERNAME=${HELM_PLUGIN_USERNAME:-unset}" && echo "HELM_PLUGIN_PASSWORD=${HELM_PLUGIN_PASSWORD:-unset}"'
27+
[ $status = 0 ]
28+
[[ "$output" == *"HELM_PLUGIN_USERNAME=unset"* ]]
29+
[[ "$output" == *"HELM_PLUGIN_PASSWORD=unset"* ]]
30+
31+
# Check that the internal git_* variables are NOT exported to child processes
32+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && bash -c "echo git_username=\${git_username:-unset}; echo git_password=\${git_password:-unset}"'
33+
[ $status = 0 ]
34+
[[ "$output" == *"git_username=unset"* ]]
35+
[[ "$output" == *"git_password=unset"* ]]
36+
}
37+
38+
@test "should not setup git credentials when HELM_PLUGIN_USERNAME is missing" {
39+
if ! helm_supports_credentials; then
40+
skip "Helm version < 3.14.0 does not support credential passing"
41+
fi
42+
43+
unset HELM_PLUGIN_USERNAME
44+
export HELM_PLUGIN_PASSWORD="testpass"
45+
46+
# Source the plugin and verify git_username is empty
47+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username:-empty}"'
48+
[ $status = 0 ]
49+
[[ "$output" == *"git_username=empty"* ]]
50+
}
51+
52+
@test "should setup git credentials with username only (empty password allowed)" {
53+
if ! helm_supports_credentials; then
54+
skip "Helm version < 3.14.0 does not support credential passing"
55+
fi
56+
57+
export HELM_PLUGIN_USERNAME="testuser"
58+
unset HELM_PLUGIN_PASSWORD
59+
60+
# Source the plugin and verify credentials are set (username without password is allowed)
61+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username}" && echo "git_password=${git_password:-empty}"'
62+
[ $status = 0 ]
63+
[[ "$output" == *"git_username=testuser"* ]]
64+
[[ "$output" == *"git_password=empty"* ]]
65+
}
66+
67+
@test "should not setup git credentials when both are missing" {
68+
if ! helm_supports_credentials; then
69+
skip "Helm version < 3.14.0 does not support credential passing"
70+
fi
71+
72+
unset HELM_PLUGIN_USERNAME
73+
unset HELM_PLUGIN_PASSWORD
74+
75+
# Source the plugin and verify both are empty
76+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username:-empty}" && echo "git_password=${git_password:-empty}"'
77+
[ $status = 0 ]
78+
[[ "$output" == *"git_username=empty"* ]]
79+
[[ "$output" == *"git_password=empty"* ]]
80+
}
81+
82+
@test "helm_git main should use credentials with username and password" {
83+
if ! helm_supports_credentials; then
84+
skip "Helm version < 3.14.0 does not support credential passing"
85+
fi
86+
87+
export HELM_PLUGIN_USERNAME="testuser"
88+
export HELM_PLUGIN_PASSWORD="testpass"
89+
export HELM_GIT_TRACE=1
90+
91+
# Test that git_cmd uses credentials by checking trace output
92+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && git_cmd --version 2>&1'
93+
94+
# Check that the trace message about using credentials appears
95+
[[ "$output" == *"[git, username:testuser] --version"* ]]
96+
[[ "$output" == *"git version"* ]]
97+
}
98+
99+
@test "git credential helper should work with environment variables" {
100+
if ! helm_supports_credentials; then
101+
skip "Helm version < 3.14.0 does not support credential passing"
102+
fi
103+
104+
export HELM_PLUGIN_USERNAME="testuser"
105+
export HELM_PLUGIN_PASSWORD="testpass"
106+
107+
# Test the credential helper by simulating what git_cmd does
108+
run bash -c 'GIT_USERNAME="testuser" GIT_PASSWORD="testpass" bash <<EOF
109+
echo "username=\${GIT_USERNAME}"
110+
echo "password=\${GIT_PASSWORD}"
111+
EOF'
112+
[ $status = 0 ]
113+
[[ "$output" == *"username=testuser"* ]]
114+
[[ "$output" == *"password=testpass"* ]]
115+
}
116+
117+
@test "should handle credentials with special characters" {
118+
if ! helm_supports_credentials; then
119+
skip "Helm version < 3.14.0 does not support credential passing"
120+
fi
121+
122+
export HELM_PLUGIN_USERNAME="user@domain.com"
123+
export HELM_PLUGIN_PASSWORD="pass/with/special&chars"
124+
125+
# Source the plugin and verify credentials with special characters are stored
126+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username}" && echo "git_password=${git_password}"'
127+
[ $status = 0 ]
128+
[[ "$output" == *"git_username=user@domain.com"* ]]
129+
[[ "$output" == *"git_password=pass/with/special&chars"* ]]
130+
131+
# Test that the credential helper can handle special characters
132+
run bash -c 'GIT_USERNAME="user@domain.com" GIT_PASSWORD="pass/with/special&chars" bash <<EOF
133+
echo "username=\${GIT_USERNAME}"
134+
echo "password=\${GIT_PASSWORD}"
135+
EOF'
136+
[ $status = 0 ]
137+
[[ "$output" == *"username=user@domain.com"* ]]
138+
[[ "$output" == *"password=pass/with/special&chars"* ]]
139+
}
140+
141+
@test "git_cmd should use credentials when available" {
142+
if ! helm_supports_credentials; then
143+
skip "Helm version < 3.14.0 does not support credential passing"
144+
fi
145+
146+
export HELM_PLUGIN_USERNAME="testuser"
147+
export HELM_PLUGIN_PASSWORD="testpass"
148+
149+
# Test git_cmd function with credentials
150+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && git_cmd --version'
151+
[ $status = 0 ]
152+
[[ "$output" == *"git version"* ]]
153+
}
154+
155+
@test "git_cmd should work normally when no credentials" {
156+
if ! helm_supports_credentials; then
157+
skip "Helm version < 3.14.0 does not support credential passing"
158+
fi
159+
160+
unset HELM_PLUGIN_USERNAME
161+
unset HELM_PLUGIN_PASSWORD
162+
163+
# Test git_cmd function without credentials
164+
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && git_cmd --version'
165+
[ $status = 0 ]
166+
[[ "$output" == *"git version"* ]]
167+
}

tests/test-helper.bash

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,14 @@ set_chart_cache_strategy() {
5454
HELM_GIT_CHART_CACHE_STRATEGY="$1"
5555
export HELM_GIT_CHART_CACHE_STRATEGY
5656
}
57+
58+
# Check if Helm version supports credential passing (>= 3.14.0)
59+
helm_supports_credentials() {
60+
v=$($HELM_BIN version --short 2>/dev/null | sed 's/^v//' | cut -d+ -f1 | cut -d- -f1)
61+
[ -n "$v" ] || return 1
62+
# shellcheck disable=SC2046
63+
set -- $(echo "$v" | awk -F. '{print $1,$2,($3?$3:0)}')
64+
[ "$1" -gt 3 ] ||
65+
{ [ "$1" -eq 3 ] && [ "$2" -gt 14 ]; } ||
66+
{ [ "$1" -eq 3 ] && [ "$2" -eq 14 ] && [ "$3" -ge 0 ]; }
67+
}

0 commit comments

Comments
 (0)