fix(helm): helm pull --repo to use repo creds from helm repos file

Up to this point, helm pull --repo required --username and --password
even if the repository had already been added via helm repo add.

From now on, check repositories in the repositories file and if the URL
matches, pull the chart with the username and password from the entry.

Fixes #9599

Co-authored-by: Andreas Karis <ak.karis@gmail.com>
Co-authored-by: Felipe Santos <felipecassiors@gmail.com>
Signed-off-by: Andreas Karis <ak.karis@gmail.com>
pull/9760/head
Andreas Karis 4 years ago
parent 7031000b7d
commit b1a5d7c04c

@ -18,6 +18,7 @@ package action
import (
"fmt"
"log/slog"
"os"
"path/filepath"
"strings"
@ -73,6 +74,23 @@ func (p *Pull) SetRegistryClient(client *registry.Client) {
func (p *Pull) Run(chartRef string) (string, error) {
var out strings.Builder
// If the repository URL is set via CLI (--repo), but neither a username nor
// password are set for a pull request, check if the repository credentials
// are already stored in helm's repository config.
if p.RepoURL != "" {
if p.Username == "" && p.Password == "" {
f, err := repo.LoadFile(p.Settings.RepositoryConfig)
if err != nil {
return out.String(), err
}
if repoEntry := f.GetByURL(p.RepoURL); repoEntry != nil {
slog.Info("Using credentials from repository config")
p.Username = repoEntry.Username
p.Password = repoEntry.Password
}
}
}
c := downloader.ChartDownloader{
Out: &out,
Keyring: p.Keyring,
@ -113,7 +131,6 @@ func (p *Pull) Run(chartRef string) (string, error) {
}
defer os.RemoveAll(dest)
}
if p.RepoURL != "" {
chartURL, err := repo.FindChartInRepoURL(
p.RepoURL,

@ -393,3 +393,72 @@ func TestPullFileCompletion(t *testing.T) {
checkFileCompletion(t, "pull", false)
checkFileCompletion(t, "pull repo/chart", false)
}
func TestPullWithCredentialsFromExistingRepository(t *testing.T) {
srv := repotest.NewTempServer(
t,
repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
repotest.WithMiddleware(repotest.BasicAuthMiddleware(t)),
)
defer srv.Stop()
srv2 := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.FileServer(http.Dir(srv.Root())).ServeHTTP(w, r)
}))
defer srv2.Close()
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
// all flags will get "-d outdir" appended.
tests := []struct {
name string
args string
expectFile string
}{
{
name: "Chart fetch using repo URL",
expectFile: "./signtest-0.1.0.tgz",
args: "signtest --repo " + srv.URL() + " --username username --password password",
},
{
name: "Chart fetch using repo URL",
expectFile: "./signtest-0.1.0.tgz",
args: "signtest --repo " + srv.URL(),
},
}
outdir := t.TempDir()
repositoryConfig := filepath.Join(outdir, "repositories.yaml")
repositoryCache := outdir
registryConfig := filepath.Join(outdir, "config.json")
cmd := fmt.Sprintf("repo add test_repository %s --username %s --password %s "+
"--repository-config %s --repository-cache %s --registry-config %s",
srv.URL(), "username", "password", repositoryConfig, repositoryCache, registryConfig,
)
if _, _, err := executeActionCommand(cmd); err != nil {
t.Fatalf("got error in setup, error: %q", err)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := fmt.Sprintf("pull %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s",
tt.args,
outdir,
repositoryConfig,
repositoryCache,
registryConfig,
)
_, _, err := executeActionCommand(cmd)
if err != nil {
t.Fatalf("%q reported error: %s", tt.name, err)
}
ef := filepath.Join(outdir, tt.expectFile)
if _, err := os.Stat(ef); err != nil {
t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err)
}
})
}
}

@ -18,7 +18,9 @@ package repo // import "helm.sh/helm/v4/pkg/repo"
import (
"fmt"
"net/url"
"os"
"path"
"path/filepath"
"time"
@ -94,6 +96,38 @@ func (r *File) Get(name string) *Entry {
return nil
}
// compareURLs returns true if url1 and url2 can each be parsed, have the exact
// same scheme and host and the same cleaned path.
func compareURLs(url1, url2 string) bool {
parsed1, err := url.Parse(url1)
if err != nil {
return false
}
parsed2, err := url.Parse(url2)
if err != nil {
return false
}
if parsed1.Scheme != parsed2.Scheme {
return false
}
if parsed1.Host != parsed2.Host {
return false
}
return path.Clean(parsed1.Path) == path.Clean(parsed2.Path)
}
// GetByURL returns the first entry with the given URL if one exists, otherwise
// returns nil. For comparison, GetByURL tries to sanitize/standardize the URLs
// as much as possible.
func (r *File) GetByURL(url string) *Entry {
for _, entry := range r.Repositories {
if compareURLs(entry.URL, url) {
return entry
}
}
return nil
}
// Remove removes the entry from the list of repositories.
func (r *File) Remove(name string) bool {
cp := []*Entry{}

@ -128,6 +128,49 @@ func TestRepoFile_Get(t *testing.T) {
}
}
func TestRepoFileGetByURL(t *testing.T) {
repo := NewFile()
repo.Add(
&Entry{
Name: "first",
URL: "https://example.com/first",
},
&Entry{
Name: "second1",
URL: "https://example.comm/second",
},
&Entry{
Name: "second2",
URL: "http://example.com/second",
},
&Entry{
Name: "second3",
URL: "https://example.com/second/////////",
},
&Entry{
Name: "third",
URL: "https://example.com/third",
},
)
url := "https://example.com/second"
entry := repo.GetByURL(url)
if entry == nil {
t.Fatalf("Expected repo entry %q to be found", url)
}
expected := "second3"
if entry.Name != expected {
t.Errorf("Expected repo Name to be %q but got %q", expected, entry.Name)
}
entry = repo.GetByURL("http://nonexistent.example.com/nonexistent")
if entry != nil {
t.Errorf("Got unexpected entry %+v", entry)
}
}
func TestRemoveRepository(t *testing.T) {
sampleRepository := NewFile()
sampleRepository.Add(

Loading…
Cancel
Save