mirror of https://github.com/helm/helm
Signed-off-by: Clark Tomlinson <fallen013@gmail.com>pull/13553/head
parent
b97ed6545a
commit
c55b81e10c
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package netrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultPath returns the default path to the .netrc file
|
||||||
|
func DefaultPath() string {
|
||||||
|
if os.Getenv("NETRC") != "" {
|
||||||
|
return os.Getenv("NETRC")
|
||||||
|
}
|
||||||
|
return filepath.Join(os.Getenv("HOME"), ".netrc")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Credentials represents a machine entry in .netrc file
|
||||||
|
type Credentials struct {
|
||||||
|
Machine string
|
||||||
|
Login string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCredentials returns the credentials for the given URL from .netrc file
|
||||||
|
func GetCredentials(urlStr string) (*Credentials, error) {
|
||||||
|
netrcPath := DefaultPath()
|
||||||
|
if _, err := os.Stat(netrcPath); os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(netrcPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(urlStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
host := u.Host
|
||||||
|
if strings.Contains(host, ":") {
|
||||||
|
host = strings.Split(host, ":")[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
parser := newParser(string(data))
|
||||||
|
machines, err := parser.parse()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, m := range machines {
|
||||||
|
if m.Machine == host {
|
||||||
|
return &Credentials{
|
||||||
|
Machine: m.Machine,
|
||||||
|
Login: m.Login,
|
||||||
|
Password: m.Password,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type machine struct {
|
||||||
|
Machine string
|
||||||
|
Login string
|
||||||
|
Password string
|
||||||
|
}
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
input string
|
||||||
|
pos int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newParser(input string) *parser {
|
||||||
|
return &parser{input: input}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) parse() ([]machine, error) {
|
||||||
|
var machines []machine
|
||||||
|
var current machine
|
||||||
|
|
||||||
|
for p.pos < len(p.input) {
|
||||||
|
token := p.nextToken()
|
||||||
|
if token == "" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
switch token {
|
||||||
|
case "machine":
|
||||||
|
if current.Machine != "" {
|
||||||
|
machines = append(machines, current)
|
||||||
|
current = machine{}
|
||||||
|
}
|
||||||
|
current.Machine = p.nextToken()
|
||||||
|
case "login":
|
||||||
|
current.Login = p.nextToken()
|
||||||
|
case "password":
|
||||||
|
current.Password = p.nextToken()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if current.Machine != "" {
|
||||||
|
machines = append(machines, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
return machines, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) nextToken() string {
|
||||||
|
// Skip whitespace
|
||||||
|
for p.pos < len(p.input) && (p.input[p.pos] == ' ' || p.input[p.pos] == '\t' || p.input[p.pos] == '\n' || p.input[p.pos] == '\r') {
|
||||||
|
p.pos++
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.pos >= len(p.input) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
start := p.pos
|
||||||
|
if p.input[p.pos] == '"' {
|
||||||
|
p.pos++
|
||||||
|
start = p.pos
|
||||||
|
for p.pos < len(p.input) && p.input[p.pos] != '"' {
|
||||||
|
p.pos++
|
||||||
|
}
|
||||||
|
if p.pos < len(p.input) {
|
||||||
|
token := p.input[start:p.pos]
|
||||||
|
p.pos++
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for p.pos < len(p.input) && p.input[p.pos] != ' ' && p.input[p.pos] != '\t' && p.input[p.pos] != '\n' && p.input[p.pos] != '\r' {
|
||||||
|
p.pos++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.input[start:p.pos]
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package netrc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCredentials(t *testing.T) {
|
||||||
|
// Create a temporary .netrc file
|
||||||
|
content := `machine example.com
|
||||||
|
login testuser
|
||||||
|
password testpass
|
||||||
|
machine other.com
|
||||||
|
login otheruser
|
||||||
|
password otherpass`
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
netrcPath := filepath.Join(tmpDir, ".netrc")
|
||||||
|
if err := os.WriteFile(netrcPath, []byte(content), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set NETRC env var to point to our test file
|
||||||
|
oldNetrc := os.Getenv("NETRC")
|
||||||
|
defer os.Setenv("NETRC", oldNetrc)
|
||||||
|
os.Setenv("NETRC", netrcPath)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
wantUser string
|
||||||
|
wantPass string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic URL",
|
||||||
|
url: "https://example.com/repo",
|
||||||
|
wantUser: "testuser",
|
||||||
|
wantPass: "testpass",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL with port",
|
||||||
|
url: "https://example.com:443/repo",
|
||||||
|
wantUser: "testuser",
|
||||||
|
wantPass: "testpass",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "other domain",
|
||||||
|
url: "https://other.com/repo",
|
||||||
|
wantUser: "otheruser",
|
||||||
|
wantPass: "otherpass",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no match",
|
||||||
|
url: "https://nomatch.com/repo",
|
||||||
|
wantUser: "",
|
||||||
|
wantPass: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid URL",
|
||||||
|
url: "://invalid",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := GetCredentials(tt.url)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("GetCredentials() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantErr {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tt.wantUser == "" && tt.wantPass == "" {
|
||||||
|
if got != nil {
|
||||||
|
t.Errorf("GetCredentials() = %v, want nil", got)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got == nil {
|
||||||
|
t.Fatal("GetCredentials() = nil, want credentials")
|
||||||
|
}
|
||||||
|
if got.Login != tt.wantUser {
|
||||||
|
t.Errorf("GetCredentials() username = %v, want %v", got.Login, tt.wantUser)
|
||||||
|
}
|
||||||
|
if got.Password != tt.wantPass {
|
||||||
|
t.Errorf("GetCredentials() password = %v, want %v", got.Password, tt.wantPass)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseNetrc(t *testing.T) {
|
||||||
|
content := `# comment line
|
||||||
|
machine example.com
|
||||||
|
login user1
|
||||||
|
password pass1
|
||||||
|
machine other.com login user2 password pass2
|
||||||
|
machine "quoted.com"
|
||||||
|
login "user 3"
|
||||||
|
password "pass 3"
|
||||||
|
`
|
||||||
|
p := newParser(content)
|
||||||
|
machines, err := p.parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(machines) != 3 {
|
||||||
|
t.Errorf("Expected 3 machines, got %d", len(machines))
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []machine{
|
||||||
|
{Machine: "example.com", Login: "user1", Password: "pass1"},
|
||||||
|
{Machine: "other.com", Login: "user2", Password: "pass2"},
|
||||||
|
{Machine: "quoted.com", Login: "user 3", Password: "pass 3"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, e := range expected {
|
||||||
|
if machines[i] != e {
|
||||||
|
t.Errorf("machine[%d] = %v, want %v", i, machines[i], e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
Copyright The Helm Authors.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"helm.sh/helm/v3/pkg/netrc"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClientNetrcAuth(t *testing.T) {
|
||||||
|
// Create a temporary .netrc file
|
||||||
|
content := `machine example.com
|
||||||
|
login testuser
|
||||||
|
password testpass`
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
netrcPath := filepath.Join(tmpDir, ".netrc")
|
||||||
|
if err := os.WriteFile(netrcPath, []byte(content), 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set NETRC env var to point to our test file
|
||||||
|
oldNetrc := os.Getenv("NETRC")
|
||||||
|
defer os.Setenv("NETRC", oldNetrc)
|
||||||
|
os.Setenv("NETRC", netrcPath)
|
||||||
|
|
||||||
|
// Create a new client
|
||||||
|
client, err := NewClient()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that credentials from .netrc are used
|
||||||
|
creds, err := netrc.GetCredentials("https://example.com")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if creds == nil {
|
||||||
|
t.Fatal("Expected credentials from .netrc, got nil")
|
||||||
|
}
|
||||||
|
if creds.Login != "testuser" {
|
||||||
|
t.Errorf("Expected username 'testuser', got '%s'", creds.Login)
|
||||||
|
}
|
||||||
|
if creds.Password != "testpass" {
|
||||||
|
t.Errorf("Expected password 'testpass', got '%s'", creds.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that explicit credentials override .netrc
|
||||||
|
client, err = NewClient(
|
||||||
|
ClientOptBasicAuth("explicituser", "explicitpass"),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if client.username != "explicituser" {
|
||||||
|
t.Errorf("Expected username 'explicituser', got '%s'", client.username)
|
||||||
|
}
|
||||||
|
if client.password != "explicitpass" {
|
||||||
|
t.Errorf("Expected password 'explicitpass', got '%s'", client.password)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue