Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
strategy:
fail-fast: false
matrix:
helm: ['2.17.0', '3.4.2', '3.7.1']
helm: ['2.17.0', '3.4.2', '3.7.1', '3.18.5']
env:
FIXTURES_GIT_REPO: ${{ format('{0}/{1}', github.server_url, github.event.pull_request.head.repo.full_name || github.repository) }}
FIXTURES_GIT_REF: ${{ github.head_ref || github.ref_name }}
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,23 @@ As this plugin uses `git` CLI to clone repos. You can configure private access i

- **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)
- **using https**: Use a [credentials helper](https://git-scm.com/docs/gitcredentials)
- **using helm credentials**: Use `helm repo add --username user --password pass` to provide credentials

#### Helm Credentials Support

> **Note:** This feature requires Helm v3.14.0 or later.

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:

```bash
# Add a repository with credentials
helm repo add my-repo --username myuser --password mypass git+https://github.com/company/charts@charts?ref=main

# The credentials are automatically used for git operations
helm fetch my-repo/my-chart
```

**Note**: When both Helm credentials and existing git authentication (SSH keys, credential helpers) are available, Helm credentials take precedence for the current operation.

### Note on SSH relative paths

Expand Down
35 changes: 29 additions & 6 deletions helm-git-plugin.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ readonly git_quiet
# Set default value for TMPDIR
export TMPDIR="${TMPDIR:-/tmp}"

# Initialize git credential variables
git_username="${HELM_PLUGIN_USERNAME:-}"
git_password="${HELM_PLUGIN_PASSWORD:-}"
unset HELM_PLUGIN_USERNAME
unset HELM_PLUGIN_PASSWORD

# Cache repos or charts depending on the cache path existing in the environment variables
cache_repos_enabled=0
if [ -n "${HELM_GIT_REPO_CACHE:-}" ]; then
Expand Down Expand Up @@ -94,15 +100,15 @@ warning() {
git_try() {
_git_repo=$1

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

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

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

Expand All @@ -112,7 +118,24 @@ git_fetch_ref() {
_git_ref="${2?Mising git_ref as second parameter}"

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

# git_cmd(git_arguments...)
# Execute git command with credential helper if credentials are available
git_cmd() {
if [ -n "${git_username:-}" ]; then
trace "Git credential helper configured with username: ${git_username}"
# shellcheck disable=SC2016
GIT_USERNAME="${git_username}" GIT_PASSWORD="${git_password}" git -c credential.helper='!f() { echo "username=${GIT_USERNAME}"; echo "password=${GIT_PASSWORD}"; }; f' "$@"
ret=$?
else
trace "No Helm plugin credentials found, using existing git authentication"
git "$@"
ret=$?
fi

return $ret
}

#git_cache_intercept(git_repo, git_ref)
Expand All @@ -133,7 +156,7 @@ git_cache_intercept() {
mkdir -p "${repo_path}" &&
cd "${repo_path}" &&
git init --bare ${git_quiet} >"${git_output}" 2>&1 &&
git remote add origin "${_git_repo}" >"${git_output}" 2>&1
git_cmd remote add origin "${_git_repo}" >"${git_output}" 2>&1
} >&2 || debug "Could not setup ${_git_repo}" && return 1
else
debug "${_git_repo} exists in cache"
Expand Down Expand Up @@ -170,7 +193,7 @@ git_checkout() {
cd "$_target_path"
git init ${git_quiet} >"${git_output}" 2>&1
git config pull.ff only >"${git_output}" 2>&1
git remote add origin "$_git_repo" >"${git_output}" 2>&1
git_cmd remote add origin "$_git_repo" >"${git_output}" 2>&1
}
if [ "$_sparse" = "1" ] && [ -n "$_git_path" ]; then
git config core.sparseCheckout true >"${git_output}" 2>&1
Expand Down Expand Up @@ -408,7 +431,7 @@ main() {
fi

# Setup exit trap
# shellcheck disable=SC2317,SC2329
# shellcheck disable=SC2317,SC2329
exit_trap() {
[ "$cleanup" = 1 ] || return 0
rm -rf "$git_root_path" "${helm_home_target_path:-}"
Expand Down
167 changes: 167 additions & 0 deletions tests/07-credentials.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#!/usr/bin/env bats

load 'test-helper'

setup_file() {
if ! helm_supports_credentials; then
echo "# Skipping credential tests - Helm version < 3.14.0 does not support credential passing" >&2
fi
}

@test "should setup git credentials when HELM_PLUGIN_USERNAME and HELM_PLUGIN_PASSWORD are set" {
if ! helm_supports_credentials; then
skip "Helm version < 3.14.0 does not support credential passing"
fi

export HELM_PLUGIN_USERNAME="testuser"
export HELM_PLUGIN_PASSWORD="testpass"

# Source the plugin and check that credentials are stored
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username}" && echo "git_password=${git_password}"'
[ $status = 0 ]
[[ "$output" == *"git_username=testuser"* ]]
[[ "$output" == *"git_password=testpass"* ]]

# Check that the original HELM_PLUGIN_* variables are unset for security
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}"'
[ $status = 0 ]
[[ "$output" == *"HELM_PLUGIN_USERNAME=unset"* ]]
[[ "$output" == *"HELM_PLUGIN_PASSWORD=unset"* ]]

# Check that the internal git_* variables are NOT exported to child processes
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}"'
[ $status = 0 ]
[[ "$output" == *"git_username=unset"* ]]
[[ "$output" == *"git_password=unset"* ]]
}

@test "should not setup git credentials when HELM_PLUGIN_USERNAME is missing" {
if ! helm_supports_credentials; then
skip "Helm version < 3.14.0 does not support credential passing"
fi

unset HELM_PLUGIN_USERNAME
export HELM_PLUGIN_PASSWORD="testpass"

# Source the plugin and verify git_username is empty
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username:-empty}"'
[ $status = 0 ]
[[ "$output" == *"git_username=empty"* ]]
}

@test "should not setup git credentials when HELM_PLUGIN_PASSWORD is missing" {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case is wrongly named @copilot

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Renamed test from "should not setup git credentials when HELM_PLUGIN_PASSWORD is missing" to "should setup git credentials with username only (empty password allowed)" which accurately describes the test behavior - username is captured and used even when password is missing.

Changes in commit be2947b.

if ! helm_supports_credentials; then
skip "Helm version < 3.14.0 does not support credential passing"
fi

export HELM_PLUGIN_USERNAME="testuser"
unset HELM_PLUGIN_PASSWORD

# Source the plugin and verify credentials are set (username without password is allowed)
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username}" && echo "git_password=${git_password:-empty}"'
[ $status = 0 ]
[[ "$output" == *"git_username=testuser"* ]]
[[ "$output" == *"git_password=empty"* ]]
}

@test "should not setup git credentials when both are missing" {
if ! helm_supports_credentials; then
skip "Helm version < 3.14.0 does not support credential passing"
fi

unset HELM_PLUGIN_USERNAME
unset HELM_PLUGIN_PASSWORD

# Source the plugin and verify both are empty
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username:-empty}" && echo "git_password=${git_password:-empty}"'
[ $status = 0 ]
[[ "$output" == *"git_username=empty"* ]]
[[ "$output" == *"git_password=empty"* ]]
}

@test "helm_git main should use credentials with username and password" {
if ! helm_supports_credentials; then
skip "Helm version < 3.14.0 does not support credential passing"
fi

export HELM_PLUGIN_USERNAME="testuser"
export HELM_PLUGIN_PASSWORD="testpass"
export HELM_GIT_TRACE=1

# Test that git_cmd uses credentials by checking trace output
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && git_cmd --version 2>&1'

# Check that the trace message about using credentials appears
[[ "$output" == *"Git credential helper configured with username: testuser"* ]]
[[ "$output" == *"git version"* ]]
}

@test "git credential helper should work with environment variables" {
if ! helm_supports_credentials; then
skip "Helm version < 3.14.0 does not support credential passing"
fi

export HELM_PLUGIN_USERNAME="testuser"
export HELM_PLUGIN_PASSWORD="testpass"

# Test the credential helper by simulating what git_cmd does
run bash -c 'GIT_USERNAME="testuser" GIT_PASSWORD="testpass" bash <<EOF
echo "username=\${GIT_USERNAME}"
echo "password=\${GIT_PASSWORD}"
EOF'
[ $status = 0 ]
[[ "$output" == *"username=testuser"* ]]
[[ "$output" == *"password=testpass"* ]]
}

@test "should handle credentials with special characters" {
if ! helm_supports_credentials; then
skip "Helm version < 3.14.0 does not support credential passing"
fi

export HELM_PLUGIN_USERNAME="[email protected]"
export HELM_PLUGIN_PASSWORD="pass/with/special&chars"

# Source the plugin and verify credentials with special characters are stored
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && echo "git_username=${git_username}" && echo "git_password=${git_password}"'
[ $status = 0 ]
[[ "$output" == *"[email protected]"* ]]
[[ "$output" == *"git_password=pass/with/special&chars"* ]]

# Test that the credential helper can handle special characters
run bash -c 'GIT_USERNAME="[email protected]" GIT_PASSWORD="pass/with/special&chars" bash <<EOF
echo "username=\${GIT_USERNAME}"
echo "password=\${GIT_PASSWORD}"
EOF'
[ $status = 0 ]
[[ "$output" == *"[email protected]"* ]]
[[ "$output" == *"password=pass/with/special&chars"* ]]
}

@test "git_cmd should use credentials when available" {
if ! helm_supports_credentials; then
skip "Helm version < 3.14.0 does not support credential passing"
fi

export HELM_PLUGIN_USERNAME="testuser"
export HELM_PLUGIN_PASSWORD="testpass"

# Test git_cmd function with credentials
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && git_cmd --version'
[ $status = 0 ]
[[ "$output" == *"git version"* ]]
}

@test "git_cmd should work normally when no credentials" {
if ! helm_supports_credentials; then
skip "Helm version < 3.14.0 does not support credential passing"
fi

unset HELM_PLUGIN_USERNAME
unset HELM_PLUGIN_PASSWORD

# Test git_cmd function without credentials
run bash -c 'source "${HELM_GIT_DIRNAME}/helm-git-plugin.sh" && git_cmd --version'
[ $status = 0 ]
[[ "$output" == *"git version"* ]]
}
11 changes: 11 additions & 0 deletions tests/test-helper.bash
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,14 @@ set_chart_cache_strategy() {
HELM_GIT_CHART_CACHE_STRATEGY="$1"
export HELM_GIT_CHART_CACHE_STRATEGY
}

# Check if Helm version supports credential passing (>= 3.14.0)
helm_supports_credentials() {
v=$($HELM_BIN version --short 2>/dev/null | sed 's/^v//' | cut -d+ -f1 | cut -d- -f1)
[ -n "$v" ] || return 1
# shellcheck disable=SC2046
set -- $(echo "$v" | awk -F. '{print $1,$2,($3?$3:0)}')
[ "$1" -gt 3 ] ||
{ [ "$1" -eq 3 ] && [ "$2" -gt 14 ]; } ||
{ [ "$1" -eq 3 ] && [ "$2" -eq 14 ] && [ "$3" -ge 0 ]; }
}
Loading