pull/11771/merge
Christoph Obexer 14 hours ago committed by GitHub
commit d78240ecbc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -77,6 +77,8 @@ type EnvSettings struct {
Debug bool
// RegistryConfig is the path to the registry config file.
RegistryConfig string
// RegistryAliasConfig is the path to the registry alias config file.
RegistryAliasConfig string
// RepositoryConfig is the path to the repositories file.
RepositoryConfig string
// RepositoryCache is the path to the repository cache directory.
@ -109,6 +111,7 @@ func New() *EnvSettings {
KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false),
PluginsDirectory: envOr("HELM_PLUGINS", helmpath.DataPath("plugins")),
RegistryConfig: envOr("HELM_REGISTRY_CONFIG", helmpath.ConfigPath("registry/config.json")),
RegistryAliasConfig: envOr("HELM_REGISTRY_ALIAS_CONFIG", helmpath.ConfigPath("registry/aliases.yaml")),
RepositoryConfig: envOr("HELM_REPOSITORY_CONFIG", helmpath.ConfigPath("repositories.yaml")),
RepositoryCache: envOr("HELM_REPOSITORY_CACHE", helmpath.CachePath("repository")),
ContentCache: envOr("HELM_CONTENT_CACHE", helmpath.CachePath("content")),
@ -162,6 +165,7 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
fs.BoolVar(&s.Debug, "debug", s.Debug, "enable verbose output")
fs.StringVar(&s.RegistryConfig, "registry-config", s.RegistryConfig, "path to the registry config file")
fs.StringVar(&s.RegistryAliasConfig, "registry-alias-config", s.RegistryAliasConfig, "path to the registry alias config file")
fs.StringVar(&s.RepositoryConfig, "repository-config", s.RepositoryConfig, "path to the file containing repository names and URLs")
fs.StringVar(&s.RepositoryCache, "repository-cache", s.RepositoryCache, "path to the directory containing cached repository indexes")
fs.StringVar(&s.ContentCache, "content-cache", s.ContentCache, "path to the directory containing cached content (e.g. charts)")
@ -241,20 +245,21 @@ func envColorMode() string {
func (s *EnvSettings) EnvVars() map[string]string {
envvars := map[string]string{
"HELM_BIN": os.Args[0],
"HELM_CACHE_HOME": helmpath.CachePath(""),
"HELM_CONFIG_HOME": helmpath.ConfigPath(""),
"HELM_DATA_HOME": helmpath.DataPath(""),
"HELM_DEBUG": fmt.Sprint(s.Debug),
"HELM_PLUGINS": s.PluginsDirectory,
"HELM_REGISTRY_CONFIG": s.RegistryConfig,
"HELM_REPOSITORY_CACHE": s.RepositoryCache,
"HELM_CONTENT_CACHE": s.ContentCache,
"HELM_REPOSITORY_CONFIG": s.RepositoryConfig,
"HELM_NAMESPACE": s.Namespace(),
"HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory),
"HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit),
"HELM_QPS": strconv.FormatFloat(float64(s.QPS), 'f', 2, 32),
"HELM_BIN": os.Args[0],
"HELM_CACHE_HOME": helmpath.CachePath(""),
"HELM_CONFIG_HOME": helmpath.ConfigPath(""),
"HELM_DATA_HOME": helmpath.DataPath(""),
"HELM_DEBUG": fmt.Sprint(s.Debug),
"HELM_PLUGINS": s.PluginsDirectory,
"HELM_REGISTRY_CONFIG": s.RegistryConfig,
"HELM_REGISTRY_ALIAS_CONFIG": s.RegistryAliasConfig,
"HELM_REPOSITORY_CACHE": s.RepositoryCache,
"HELM_CONTENT_CACHE": s.ContentCache,
"HELM_REPOSITORY_CONFIG": s.RepositoryConfig,
"HELM_NAMESPACE": s.Namespace(),
"HELM_MAX_HISTORY": strconv.Itoa(s.MaxHistory),
"HELM_BURST_LIMIT": strconv.Itoa(s.BurstLimit),
"HELM_QPS": strconv.FormatFloat(float64(s.QPS), 'f', 2, 32),
// broken, these are populated from helm flags and not kubeconfig.
"HELM_KUBECONTEXT": s.KubeContext,

@ -0,0 +1,42 @@
/*
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 cmd
import (
"io"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/action"
)
const aliasHelp = `
This command consists of multiple subcommands to interact with OCI aliases.
`
func newAliasCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "alias",
Short: "manage OCI aliases and substitutions",
Long: aliasHelp,
}
cmd.AddCommand(
newAliasListCmd(cfg, out),
newAliasSetCmd(cfg, out),
newAliasSubstituteCmd(cfg, out),
)
return cmd
}

@ -0,0 +1,75 @@
/*
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 cmd
import (
"io"
"github.com/gosuri/uitable"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cli/output"
"helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/registry"
)
const aliasListDesc = `
List registry aliases and substitutions.
`
func newAliasListCmd(_ *action.Configuration, out io.Writer) *cobra.Command {
var aliasesOpt, substitutionsOpt bool
cmd := &cobra.Command{
Use: "list",
Short: "list aliases and substitutions",
Long: aliasListDesc,
Args: require.NoArgs,
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(_ *cobra.Command, _ []string) error {
var err error
a, _ := registry.LoadAliasesFile(settings.RegistryAliasConfig)
if aliasesOpt || !substitutionsOpt {
table := uitable.New()
table.AddRow("ALIAS", "URL")
for a, url := range a.Aliases {
table.AddRow(a, url)
}
err = output.EncodeTable(out, table)
}
if substitutionsOpt || !aliasesOpt {
table := uitable.New()
table.AddRow("SUBSTITUTION", "REPLACEMENT")
for s, r := range a.Substitutions {
table.AddRow(s, r)
}
err = output.EncodeTable(out, table)
}
return err
},
}
f := cmd.Flags()
f.BoolVarP(&aliasesOpt, "aliases", "a", false, "list aliases")
f.BoolVarP(&substitutionsOpt, "substitutions", "s", false, "list substitutions")
return cmd
}

@ -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 cmd
import (
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/registry"
)
const aliasSetDesc = `
Set or remove an alias for an OCI registry.
`
func newAliasSetCmd(_ *action.Configuration, _ io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "set NAME [URL]",
Short: "configure the named alias",
Long: aliasSetDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(_ *cobra.Command, args []string) error {
alias := args[0]
var value *string
if len(args) > 1 {
value = &args[1]
}
err := setAlias(settings.RegistryAliasConfig, alias, value)
return err
},
}
return cmd
}
func setAlias(aliasesFile, alias string, value *string) error {
if strings.Contains(alias, "/") {
return fmt.Errorf("alias name (%s) contains '/', please specify a different name without '/'", alias)
}
a, err := registry.LoadAliasesFile(aliasesFile)
if err != nil && !isNotExist(err) {
return fmt.Errorf("failed to load aliases: %w", err)
}
if value != nil {
a.SetAlias(alias, *value)
} else {
a.RemoveAlias(alias)
}
if err := a.WriteAliasesFile(aliasesFile, 0o644); err != nil {
return err
}
return nil
}

@ -0,0 +1,74 @@
/*
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 cmd
import (
"fmt"
"io"
"github.com/spf13/cobra"
"helm.sh/helm/v4/pkg/action"
"helm.sh/helm/v4/pkg/cmd/require"
"helm.sh/helm/v4/pkg/registry"
)
const aliasSubstituteDesc = `
Set or remove a registry substitution.
`
func newAliasSubstituteCmd(_ *action.Configuration, _ io.Writer) *cobra.Command {
cmd := &cobra.Command{
Use: "substitute URL [URL]",
Short: "configure a OCI registry URL substitution",
Long: aliasSubstituteDesc,
Args: require.MinimumNArgs(1),
ValidArgsFunction: noMoreArgsCompFunc,
RunE: func(_ *cobra.Command, args []string) error {
substitution := args[0]
var replacement *string
if len(args) > 1 {
replacement = &args[1]
}
err := setSubstitution(settings.RegistryAliasConfig, substitution, replacement)
return err
},
}
return cmd
}
func setSubstitution(aliasesFile, substitution string, replacement *string) error {
a, err := registry.LoadAliasesFile(aliasesFile)
if err != nil && !isNotExist(err) {
return fmt.Errorf("failed to load aliases: %w", err)
}
if replacement != nil {
a.SetSubstitution(substitution, *replacement)
} else {
a.RemoveSubstitution(substitution)
}
if err := a.WriteAliasesFile(aliasesFile, 0o644); err != nil {
return err
}
return nil
}

@ -61,16 +61,17 @@ func newDependencyBuildCmd(out io.Writer) *cobra.Command {
}
man := &downloader.Manager{
Out: out,
ChartPath: chartpath,
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings),
RegistryClient: registryClient,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
Debug: settings.Debug,
Out: out,
ChartPath: chartpath,
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings),
RegistryClient: registryClient,
RegistryAliasConfig: settings.RegistryAliasConfig,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
Debug: settings.Debug,
}
if client.Verify {
man.Verify = downloader.VerifyIfPossible

@ -65,16 +65,17 @@ func newDependencyUpdateCmd(_ *action.Configuration, out io.Writer) *cobra.Comma
}
man := &downloader.Manager{
Out: out,
ChartPath: chartpath,
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings),
RegistryClient: registryClient,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
Debug: settings.Debug,
Out: out,
ChartPath: chartpath,
Keyring: client.Keyring,
SkipUpdate: client.SkipRefresh,
Getters: getter.All(settings),
RegistryClient: registryClient,
RegistryAliasConfig: settings.RegistryAliasConfig,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
Debug: settings.Debug,
}
if client.Verify {
man.Verify = downloader.VerifyAlways

@ -290,16 +290,17 @@ func runInstall(args []string, client *action.Install, valueOpts *values.Options
if err := action.CheckDependencies(chartRequested, req); err != nil {
if client.DependencyUpdate {
man := &downloader.Manager{
Out: out,
ChartPath: cp,
Keyring: client.Keyring,
SkipUpdate: false,
Getters: p,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
Debug: settings.Debug,
RegistryClient: client.GetRegistryClient(),
Out: out,
ChartPath: cp,
Keyring: client.Keyring,
SkipUpdate: false,
Getters: p,
RegistryAliasConfig: settings.RegistryAliasConfig,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
Debug: settings.Debug,
RegistryClient: client.GetRegistryClient(),
}
if err := man.Update(); err != nil {
return nil, err

@ -92,15 +92,16 @@ func newPackageCmd(out io.Writer) *cobra.Command {
if client.DependencyUpdate {
downloadManager := &downloader.Manager{
Out: io.Discard,
ChartPath: path,
Keyring: client.Keyring,
Getters: p,
Debug: settings.Debug,
RegistryClient: registryClient,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
Out: io.Discard,
ChartPath: path,
Keyring: client.Keyring,
Getters: p,
Debug: settings.Debug,
RegistryClient: registryClient,
RegistryAliasConfig: settings.RegistryAliasConfig,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
}
if err := downloadManager.Update(); err != nil {

@ -287,6 +287,7 @@ func newRootCmdWithConfig(actionConfig *action.Configuration, out io.Writer, arg
)
cmd.AddCommand(
newAliasCmd(actionConfig, out),
newRegistryCmd(actionConfig, out),
newPushCmd(actionConfig, out),
)

@ -17,6 +17,7 @@ HELM_MAX_HISTORY
HELM_NAMESPACE
HELM_PLUGINS
HELM_QPS
HELM_REGISTRY_ALIAS_CONFIG
HELM_REGISTRY_CONFIG
HELM_REPOSITORY_CACHE
HELM_REPOSITORY_CONFIG

@ -209,15 +209,16 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
err = fmt.Errorf("an error occurred while checking for chart dependencies. You may need to run `helm dependency build` to fetch missing dependencies: %w", err)
if client.DependencyUpdate {
man := &downloader.Manager{
Out: out,
ChartPath: chartPath,
Keyring: client.Keyring,
SkipUpdate: false,
Getters: p,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
Debug: settings.Debug,
Out: out,
ChartPath: chartPath,
Keyring: client.Keyring,
SkipUpdate: false,
Getters: p,
RegistryAliasConfig: settings.RegistryAliasConfig,
RepositoryConfig: settings.RepositoryConfig,
RepositoryCache: settings.RepositoryCache,
ContentCache: settings.ContentCache,
Debug: settings.Debug,
}
if err := man.Update(); err != nil {
return err

@ -71,10 +71,11 @@ type Manager struct {
// SkipUpdate indicates that the repository should not be updated first.
SkipUpdate bool
// Getter collection for the operation
Getters []getter.Provider
RegistryClient *registry.Client
RepositoryConfig string
RepositoryCache string
Getters []getter.Provider
RegistryClient *registry.Client
RegistryAliasConfig string
RepositoryConfig string
RepositoryCache string
// ContentCache is a location where a cache of charts can be stored
ContentCache string
@ -564,11 +565,20 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
rf, err := loadRepoConfig(m.RepositoryConfig)
if err != nil {
if errors.Is(err, stdfs.ErrNotExist) {
return make(map[string]string), nil
rf = repo.NewFile()
} else {
return nil, err
}
}
aliases, err := registry.LoadAliasesFile(m.RegistryAliasConfig)
if err != nil {
if errors.Is(err, stdfs.ErrNotExist) {
aliases = registry.NewAliasesFile()
} else {
return nil, err
}
return nil, err
}
repos := rf.Repositories
reposMap := make(map[string]string)
@ -593,6 +603,8 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
continue
}
dd.Repository = aliases.Expand(dd.Repository)
if registry.IsOCI(dd.Repository) {
reposMap[dd.Name] = dd.Repository
continue
@ -600,7 +612,7 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
found := false
for _, repo := range repos {
for _, repo := range rf.Repositories {
if (strings.HasPrefix(dd.Repository, "@") && strings.TrimPrefix(dd.Repository, "@") == repo.Name) ||
(strings.HasPrefix(dd.Repository, "alias:") && strings.TrimPrefix(dd.Repository, "alias:") == repo.Name) {
found = true

@ -0,0 +1,153 @@
/*
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 (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"sigs.k8s.io/yaml"
)
// Aliases represents the registry/aliases.yaml file
type Aliases struct {
APIVersion string `json:"apiVersion"`
Aliases map[string]string `json:"aliases"`
Substitutions map[string]string `json:"substitutions"`
}
// NewAliasesFile generates an empty aliases file.
//
// APIVersion is automatically set.
func NewAliasesFile() *Aliases {
return &Aliases{
APIVersion: APIVersionV1,
Aliases: map[string]string{},
Substitutions: map[string]string{},
}
}
// LoadAliasesFile takes a file at the given path and returns an Aliases object
func LoadAliasesFile(path string) (*Aliases, error) {
a := NewAliasesFile()
b, err := os.ReadFile(path)
if err != nil {
return a, fmt.Errorf("couldn't load aliases file (%s): %w", path, err)
}
err = yaml.Unmarshal(b, a)
return a, err
}
// SetAlias adds or updates an alias.
func (a *Aliases) SetAlias(alias, url string) {
a.Aliases[alias] = url
}
// RemoveAlias removes the entry from the list of repository aliases.
// RemoveAlias returns true if the alias existed before it was deleted.
func (a *Aliases) RemoveAlias(alias string) bool {
_, existing := a.Aliases[alias]
delete(a.Aliases, alias)
return existing
}
// SetSubstitution adds or updates a substitution.
func (a *Aliases) SetSubstitution(substitution, replacement string) {
a.Substitutions[substitution] = replacement
}
// RemoveSubstitution removes the substitution and returns true if the
// substitution existed before it was deleted.
func (a *Aliases) RemoveSubstitution(substitution string) bool {
_, existing := a.Substitutions[substitution]
delete(a.Substitutions, substitution)
return existing
}
// Expand first expands aliases to their mapped value end then performs
// prefix substitutions until no substitution matches or each substitution
// was used at most once.
func (a *Aliases) Expand(source string) string {
return a.performSubstitutions(a.expandAlias(source))
}
func (a *Aliases) expandAlias(source string) string {
isAtAlias := strings.HasPrefix(source, "@")
isLongAlias := strings.HasPrefix(source, "alias:")
if isAtAlias || isLongAlias {
var alias string
if isAtAlias {
alias = strings.TrimPrefix(source, "@")
} else if isLongAlias {
alias = strings.TrimPrefix(source, "alias:")
}
if v, existing := a.Aliases[alias]; existing {
return v
}
}
return source
}
func (a *Aliases) performSubstitutions(source string) string {
current := source
// no recursions
used := make(map[string]bool, len(a.Substitutions))
orderedSubstitutions := make([]string, 0, len(a.Substitutions))
for k := range a.Substitutions {
orderedSubstitutions = append(orderedSubstitutions, k)
}
sort.SliceStable(orderedSubstitutions, func(i, j int) bool {
return len(orderedSubstitutions[i]) < len(orderedSubstitutions[j])
})
var changed bool
for {
changed = false
for i := range orderedSubstitutions {
k := orderedSubstitutions[i]
if !used[k] && strings.HasPrefix(current, k) {
used[k] = true
current = a.Substitutions[k] + strings.TrimPrefix(current, k)
changed = true
}
}
if !changed {
break
}
}
return current
}
// WriteAliasesFile writes an aliases file to the given path.
func (a *Aliases) WriteAliasesFile(path string, perm os.FileMode) error {
data, err := yaml.Marshal(a)
if err != nil {
return err
}
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return err
}
return os.WriteFile(path, data, perm)
}

@ -0,0 +1,176 @@
/*
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"
"reflect"
"strings"
"testing"
)
const testAliasesFile = "testdata/aliases.yaml"
func TestAliasesFile(t *testing.T) {
a := NewAliasesFile()
a.SetAlias("staging", "oci://example.com/charts/staging")
a.SetAlias("production", "oci://example.com/charts/production")
a.SetSubstitution("oci://example.com/charts/production", "oci://example.com/qa-environment/charts/production")
if len(a.Aliases) != 2 {
t.Fatal("Expected 2 aliases")
}
if len(a.Substitutions) != 1 {
t.Fatal("Expected 1 substitution")
}
if !a.RemoveAlias("staging") {
t.Fatal("Expected staging alias to exist")
}
if a.RemoveAlias("staging") {
t.Fatal("Expected staging alias to not exist")
}
if len(a.Aliases) != 1 {
t.Fatal("Expected 1 alias")
}
if !a.RemoveSubstitution("oci://example.com/charts/production") {
t.Fatal("Expected 'oci://example.com/charts/production' substitution to exist")
}
if a.RemoveSubstitution("oci://example.com/charts/production") {
t.Fatal("Expected 'oci://example.com/charts/production' substitution to not exist")
}
if len(a.Substitutions) != 0 {
t.Fatal("Expected 0 substitutions")
}
}
func TestNewAliasesFile(t *testing.T) {
expects := NewAliasesFile()
expects.SetAlias("staging", "oci://example.com/charts/staging")
expects.SetAlias("production", "oci://example.com/charts/production")
expects.SetAlias("dev", "oci://example.com/charts/dev")
expects.SetSubstitution("oci://example.com/charts/dev", "oci://dev.example.com/charts")
expects.SetSubstitution("oci://example.com/charts/staging", "oci://staging.example.com/charts")
expects.SetSubstitution("https://example.com/stable/charts", "oci://stable.example.com/charts")
file, err := LoadAliasesFile(testAliasesFile)
if err != nil {
t.Errorf("%q could not be loaded: %s", testAliasesFile, err)
}
if !reflect.DeepEqual(expects.APIVersion, file.APIVersion) {
t.Fatalf("Unexpected apiVersion: %#v", file.APIVersion)
}
if !reflect.DeepEqual(expects.Aliases, file.Aliases) {
t.Fatalf("Unexpected aliases: %#v", file.Aliases)
}
if !reflect.DeepEqual(expects.Substitutions, file.Substitutions) {
t.Fatalf("Unexpected substitutions: %#v", file.Substitutions)
}
}
func TestWriteAliasesFile(t *testing.T) {
expects := NewAliasesFile()
expects.SetAlias("dev", "oci://example.com/charts/dev")
expects.SetSubstitution("oci://example.com/charts/dev", "oci://dev.example.com/charts")
file, err := os.CreateTemp(t.TempDir(), "helm-aliases")
if err != nil {
t.Errorf("failed to create test-file (%v)", err)
}
defer os.Remove(file.Name())
if err := expects.WriteAliasesFile(file.Name(), 0o644); err != nil {
t.Errorf("failed to write file (%v)", err)
}
aliases, err := LoadAliasesFile(file.Name())
if err != nil {
t.Errorf("failed to load file (%v)", err)
}
if !reflect.DeepEqual(expects, aliases) {
t.Errorf("aliases inconsistent after saving and reloading:\nexpected: %#v\nactual: %#v", expects, aliases)
}
}
func TestAliasNotExists(t *testing.T) {
if _, err := LoadAliasesFile("/this/path/does/not/exist.yaml"); err == nil {
t.Errorf("expected err to be non-nil when path does not exist")
} else if !strings.Contains(err.Error(), "couldn't load aliases file") {
t.Errorf("expected prompt `couldn't load aliases file`")
}
}
func TestAliases_performSubstitutions(t *testing.T) {
substitutions := NewAliasesFile()
substitutions.SetSubstitution("oci://example.com/charts", "oci://example.com/charts/dev")
substitutions.SetSubstitution("oci://length.example.com", "oci://shorter.length.example.com")
substitutions.SetSubstitution("oci://length.example.com/charts", "oci://longer.length.example.com/charts")
substitutions.SetSubstitution("oci://multiple.example.com", "oci://example.com/charts")
substitutions.SetSubstitution("oci://localhost:5000/", "oci://staging.example.com/charts/")
substitutions.SetSubstitution("https://example.com/vendor", "oci://vendor.example.com/charts/")
substitutions.SetSubstitution("oci://one.example.com", "oci://two.example.com")
substitutions.SetSubstitution("oci://two.example.com", "oci://one.example.com")
tests := []struct {
name string
source string
want string
}{
{
name: "basicOCIReplacement",
source: "oci://localhost:5000/myrepo",
want: "oci://staging.example.com/charts/myrepo",
},
{
name: "exacltyAsRequested",
source: "https://example.com/vendor-dev/some-chart-repo",
want: "oci://vendor.example.com/charts/-dev/some-chart-repo",
},
{
name: "multipleReplacements",
source: "oci://multiple.example.com/myrepo",
want: "oci://example.com/charts/dev/myrepo",
},
{
name: "norecursion",
source: "oci://one.example.com/myrepo",
want: "oci://one.example.com/myrepo",
},
{
name: "usedOnlyOnce",
source: "oci://example.com/charts/myrepo",
want: "oci://example.com/charts/dev/myrepo",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := substitutions.performSubstitutions(tt.source); got != tt.want {
t.Errorf("Aliases.performSubstitutions() = %v, want %v", got, tt.want)
}
})
}
}

@ -34,4 +34,7 @@ const (
// LegacyChartLayerMediaType is the legacy reserved media type for Helm chart package content.
LegacyChartLayerMediaType = "application/tar+gzip"
// APIVersionV1 is the v1 API version for the aliases and substitutions file.
APIVersionV1 = "v1"
)

@ -0,0 +1,9 @@
apiVersion: v1
aliases:
staging: oci://example.com/charts/staging
production: oci://example.com/charts/production
dev: oci://example.com/charts/dev
substitutions:
oci://example.com/charts/dev: oci://dev.example.com/charts
oci://example.com/charts/staging: oci://staging.example.com/charts
https://example.com/stable/charts: oci://stable.example.com/charts
Loading…
Cancel
Save