Merge 6cf2e0b522 into daebbfb0ad
This commit is contained in:
commit
8c55dc7c01
|
|
@ -1819,7 +1819,16 @@ repositories:
|
||||||
oci: true
|
oci: true
|
||||||
```
|
```
|
||||||
|
|
||||||
It is important not to include a scheme for the URL as helm requires that these are not present for OCI registries
|
It is important not to include a scheme for the URL as helm requires that these are not present for OCI registries.
|
||||||
|
|
||||||
|
The URL can optionally include an organization or repository path. Helmfile will automatically extract the registry hostname for authentication:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
repositories:
|
||||||
|
- name: myOCIRegistry
|
||||||
|
url: quay.io/my-organization
|
||||||
|
oci: true
|
||||||
|
```
|
||||||
|
|
||||||
Secondly the credentials for the OCI registry can either be specified within `helmfile.yaml` similar to
|
Secondly the credentials for the OCI registry can either be specified within `helmfile.yaml` similar to
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ type DiffKey struct {
|
||||||
type Helm struct {
|
type Helm struct {
|
||||||
Charts []string
|
Charts []string
|
||||||
Repo []string
|
Repo []string
|
||||||
|
RegistryLoginHost string // Captures the registry host for OCI login
|
||||||
Releases []Release
|
Releases []Release
|
||||||
Deleted []Release
|
Deleted []Release
|
||||||
Linted []Release
|
Linted []Release
|
||||||
|
|
@ -104,6 +105,7 @@ func (helm *Helm) UpdateRepo() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (helm *Helm) RegistryLogin(name, username, password, caFile, certFile, keyFile string, skipTLSVerify bool) error {
|
func (helm *Helm) RegistryLogin(name, username, password, caFile, certFile, keyFile string, skipTLSVerify bool) error {
|
||||||
|
helm.RegistryLoginHost = name
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error {
|
func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error {
|
||||||
|
|
|
||||||
|
|
@ -592,7 +592,10 @@ func (st *HelmState) SyncRepos(helm RepoUpdater, shouldSkip map[string]bool) ([]
|
||||||
username, password := gatherUsernamePassword(repo.Name, repo.Username, repo.Password)
|
username, password := gatherUsernamePassword(repo.Name, repo.Username, repo.Password)
|
||||||
var err error
|
var err error
|
||||||
if repo.OCI {
|
if repo.OCI {
|
||||||
err = helm.RegistryLogin(repo.URL, username, password, repo.CaFile, repo.CertFile, repo.KeyFile, repo.SkipTLSVerify)
|
// For OCI registries, extract just the registry host for login
|
||||||
|
// helm registry login expects "registry.io" not "registry.io/org/path"
|
||||||
|
registryHost := extractRegistryHost(repo.URL)
|
||||||
|
err = helm.RegistryLogin(registryHost, username, password, repo.CaFile, repo.CertFile, repo.KeyFile, repo.SkipTLSVerify)
|
||||||
} else {
|
} else {
|
||||||
err = helm.AddRepo(repo.Name, repo.URL, repo.CaFile, repo.CertFile, repo.KeyFile, username, password, repo.Managed, repo.PassCredentials, repo.SkipTLSVerify)
|
err = helm.AddRepo(repo.Name, repo.URL, repo.CaFile, repo.CertFile, repo.KeyFile, username, password, repo.Managed, repo.PassCredentials, repo.SkipTLSVerify)
|
||||||
}
|
}
|
||||||
|
|
@ -607,6 +610,23 @@ func (st *HelmState) SyncRepos(helm RepoUpdater, shouldSkip map[string]bool) ([]
|
||||||
return updated, nil
|
return updated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractRegistryHost extracts the registry hostname (and optional port) from a URL.
|
||||||
|
// For OCI registries, helm registry login requires just the host, not the full path.
|
||||||
|
// Examples:
|
||||||
|
// - "quay.io/org/repo" -> "quay.io"
|
||||||
|
// - "registry:443/helm-charts" -> "registry:443"
|
||||||
|
// - "myregistry.azurecr.io" -> "myregistry.azurecr.io"
|
||||||
|
func extractRegistryHost(repoURL string) string {
|
||||||
|
// Find the first slash after the initial part
|
||||||
|
slashIndex := strings.Index(repoURL, "/")
|
||||||
|
if slashIndex == -1 {
|
||||||
|
// No slash, return the whole URL
|
||||||
|
return repoURL
|
||||||
|
}
|
||||||
|
// Return everything before the first slash
|
||||||
|
return repoURL[:slashIndex]
|
||||||
|
}
|
||||||
|
|
||||||
func gatherUsernamePassword(repoName string, username string, password string) (string, string) {
|
func gatherUsernamePassword(repoName string, username string, password string) (string, string) {
|
||||||
var user, pass string
|
var user, pass string
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1248,6 +1248,103 @@ func TestHelmState_SyncRepos(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHelmState_SyncRepos_OCI(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
repos []RepositorySpec
|
||||||
|
envs map[string]string
|
||||||
|
wantRegistryHost string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OCI registry with organization path",
|
||||||
|
repos: []RepositorySpec{
|
||||||
|
{
|
||||||
|
Name: "ociregistry",
|
||||||
|
URL: "quay.io/ORG_NAME",
|
||||||
|
OCI: true,
|
||||||
|
Username: "testuser",
|
||||||
|
Password: "testpass",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantRegistryHost: "quay.io",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OCI registry with multiple path segments",
|
||||||
|
repos: []RepositorySpec{
|
||||||
|
{
|
||||||
|
Name: "myregistry",
|
||||||
|
URL: "registry.example.com/org/team/repo",
|
||||||
|
OCI: true,
|
||||||
|
Username: "user",
|
||||||
|
Password: "pass",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantRegistryHost: "registry.example.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OCI registry without path",
|
||||||
|
repos: []RepositorySpec{
|
||||||
|
{
|
||||||
|
Name: "simpleregistry",
|
||||||
|
URL: "myregistry.azurecr.io",
|
||||||
|
OCI: true,
|
||||||
|
Username: "azureuser",
|
||||||
|
Password: "azurepass",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantRegistryHost: "myregistry.azurecr.io",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OCI registry with port and path",
|
||||||
|
repos: []RepositorySpec{
|
||||||
|
{
|
||||||
|
Name: "localregistry",
|
||||||
|
URL: "localhost:5000/charts",
|
||||||
|
OCI: true,
|
||||||
|
Username: "local",
|
||||||
|
Password: "local",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantRegistryHost: "localhost:5000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OCI registry with environment credentials",
|
||||||
|
repos: []RepositorySpec{
|
||||||
|
{
|
||||||
|
Name: "envregistry",
|
||||||
|
URL: "registry.io/org",
|
||||||
|
OCI: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
envs: map[string]string{
|
||||||
|
"ENVREGISTRY_USERNAME": "envuser",
|
||||||
|
"ENVREGISTRY_PASSWORD": "envpass",
|
||||||
|
},
|
||||||
|
wantRegistryHost: "registry.io",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
for k, v := range tt.envs {
|
||||||
|
t.Setenv(k, v)
|
||||||
|
}
|
||||||
|
helm := &exectest.Helm{}
|
||||||
|
state := &HelmState{
|
||||||
|
ReleaseSetSpec: ReleaseSetSpec{
|
||||||
|
Repositories: tt.repos,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err := state.SyncRepos(helm, map[string]bool{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("HelmState.SyncRepos() error = %v", err)
|
||||||
|
}
|
||||||
|
if helm.RegistryLoginHost != tt.wantRegistryHost {
|
||||||
|
t.Errorf("HelmState.SyncRepos() RegistryLoginHost = %q, want %q", helm.RegistryLoginHost, tt.wantRegistryHost)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestHelmState_SyncReleases(t *testing.T) {
|
func TestHelmState_SyncReleases(t *testing.T) {
|
||||||
postRenderer := "foo.sh"
|
postRenderer := "foo.sh"
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -3095,6 +3192,53 @@ func TestReverse(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_extractRegistryHost(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
repoURL string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "URL with organization and repository path",
|
||||||
|
repoURL: "quay.io/ORG_NAME",
|
||||||
|
expected: "quay.io",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL with registry port and path",
|
||||||
|
repoURL: "registry:443/helm-charts",
|
||||||
|
expected: "registry:443",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL with multiple path segments",
|
||||||
|
repoURL: "registry.io/org/repo/subpath",
|
||||||
|
expected: "registry.io",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL with registry only, no path",
|
||||||
|
repoURL: "myregistry.azurecr.io",
|
||||||
|
expected: "myregistry.azurecr.io",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "URL with registry and port only",
|
||||||
|
repoURL: "localhost:5000",
|
||||||
|
expected: "localhost:5000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Docker Hub style URL",
|
||||||
|
repoURL: "registry-1.docker.io/library",
|
||||||
|
expected: "registry-1.docker.io",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got := extractRegistryHost(tt.repoURL)
|
||||||
|
if got != tt.expected {
|
||||||
|
t.Errorf("extractRegistryHost(%q) = %q, want %q", tt.repoURL, got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_gatherUsernamePassword(t *testing.T) {
|
func Test_gatherUsernamePassword(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
repoName string
|
repoName string
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue