pull/31524/merge
Alexandr D. 1 day ago committed by GitHub
commit f4c201135f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -76,7 +76,7 @@ func (r *Resolver) Resolve(reqs []*chart.Dependency, repoNames map[string]string
}
continue
}
if strings.HasPrefix(d.Repository, "file://") {
if IsLocalDependency(d.Repository) {
chartpath, err := GetLocalPath(d.Repository, r.chartpath)
if err != nil {
return nil, err
@ -259,3 +259,9 @@ func GetLocalPath(repo, chartpath string) (string, error) {
return depPath, nil
}
// IsLocalDependency checks if the repository path is a local file dependency.
// Local dependencies use the "file://" protocol prefix.
func IsLocalDependency(repositoryPath string) bool {
return strings.HasPrefix(repositoryPath, "file://")
}

@ -310,3 +310,138 @@ func createTestingChart(t *testing.T, dest, name, baseURL string) {
t.Fatal(err)
}
}
func TestDependencyUpdateCmd_NestedLocalDependencies(t *testing.T) {
srv := repotest.NewTempServer(
t,
repotest.WithChartSourceGlob("testdata/testcharts/*.tgz"),
)
defer srv.Stop()
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
dir := func(p ...string) string {
return filepath.Join(append([]string{srv.Root()}, p...)...)
}
// Create leaf dependency (has remote dependency)
leafChart := &chart.Chart{
Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV2,
Name: "leaf-chart",
Version: "0.1.0",
Dependencies: []*chart.Dependency{
{Name: "reqtest", Version: "0.1.0", Repository: srv.URL()},
},
},
}
if err := chartutil.SaveDir(leafChart, dir()); err != nil {
t.Fatal(err)
}
// Create middle dependency (has local dependency on leaf)
middleChart := &chart.Chart{
Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV2,
Name: "middle-chart",
Version: "0.1.0",
Dependencies: []*chart.Dependency{
{Name: "leaf-chart", Version: "0.1.0", Repository: "file://../leaf-chart"},
},
},
}
if err := chartutil.SaveDir(middleChart, dir()); err != nil {
t.Fatal(err)
}
rootChart := &chart.Chart{
Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV2,
Name: "root-chart",
Version: "0.1.0",
Dependencies: []*chart.Dependency{
{Name: "middle-chart", Version: "0.1.0", Repository: "file://../middle-chart"},
},
},
}
if err := chartutil.SaveDir(rootChart, dir()); err != nil {
t.Fatal(err)
}
contentCache := t.TempDir()
_, out, err := executeActionCommand(
fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --content-cache %s --plain-http",
dir("root-chart"), dir("repositories.yaml"), dir(), contentCache),
)
if err != nil {
t.Logf("Output: %s", out)
t.Fatal(err)
}
expectMiddle := dir("root-chart", "charts/middle-chart-0.1.0.tgz")
if _, err := os.Stat(expectMiddle); err != nil {
t.Fatalf("Expected middle-chart dependency: %s", err)
}
expectLeaf := dir("middle-chart", "charts/leaf-chart-0.1.0.tgz")
if _, err := os.Stat(expectLeaf); err != nil {
t.Fatalf("Expected leaf-chart dependency: %s", err)
}
expectReqtest := dir("leaf-chart", "charts/reqtest-0.1.0.tgz")
if _, err := os.Stat(expectReqtest); err != nil {
t.Fatalf("Expected reqtest dependency: %s", err)
}
}
func TestDependencyUpdateCmd_CircularDependency(t *testing.T) {
srv := repotest.NewTempServer(t)
defer srv.Stop()
dir := func(p ...string) string {
return filepath.Join(append([]string{srv.Root()}, p...)...)
}
// Create chart A that depends on B
chartA := &chart.Chart{
Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV2,
Name: "chart-a",
Version: "1.0.0",
Dependencies: []*chart.Dependency{
{Name: "chart-b", Version: "1.0.0", Repository: "file://../chart-b"},
},
},
}
if err := chartutil.SaveDir(chartA, dir()); err != nil {
t.Fatal(err)
}
// Create chart B that depends on A (circular)
chartB := &chart.Chart{
Metadata: &chart.Metadata{
APIVersion: chart.APIVersionV2,
Name: "chart-b",
Version: "1.0.0",
Dependencies: []*chart.Dependency{
{Name: "chart-a", Version: "1.0.0", Repository: "file://../chart-a"},
},
},
}
if err := chartutil.SaveDir(chartB, dir()); err != nil {
t.Fatal(err)
}
_, out, err := executeActionCommand(
fmt.Sprintf("dependency update '%s'", dir("chart-a")),
)
if err == nil {
t.Fatal("Expected circular dependency error")
}
if !strings.Contains(out, "circular dependency detected") {
t.Fatalf("Expected 'circular dependency detected' in output, got: %s", out)
}
}

@ -23,6 +23,7 @@ import (
"io"
stdfs "io/fs"
"log"
"log/slog"
"net/url"
"os"
"path/filepath"
@ -192,6 +193,12 @@ func (m *Manager) Update() error {
}
}
// Do resolution for each local dependency first. Local dependencies may
// have their own dependencies which must be resolved.
if err := m.updateLocalDeps(req, nil); err != nil {
return err
}
// Now we need to find out which version of a chart best satisfies the
// dependencies in the Chart.yaml
lock, err := m.resolve(req, repoNames)
@ -300,7 +307,7 @@ func (m *Manager) downloadAll(deps []*chart.Dependency) error {
}
continue
}
if strings.HasPrefix(dep.Repository, "file://") {
if resolver.IsLocalDependency(dep.Repository) {
if m.Debug {
fmt.Fprintf(m.Out, "Archiving %s from repo %s\n", dep.Name, dep.Repository)
}
@ -476,7 +483,7 @@ func (m *Manager) hasAllRepos(deps []*chart.Dependency) error {
Loop:
for _, dd := range deps {
// If repo is from local path or OCI, continue
if strings.HasPrefix(dd.Repository, "file://") || registry.IsOCI(dd.Repository) {
if resolver.IsLocalDependency(dd.Repository) || registry.IsOCI(dd.Repository) {
continue
}
@ -579,7 +586,7 @@ func (m *Manager) resolveRepoNames(deps []*chart.Dependency) (map[string]string,
continue
}
// if dep chart is from local path, verify the path is valid
if strings.HasPrefix(dd.Repository, "file://") {
if resolver.IsLocalDependency(dd.Repository) {
if _, err := resolver.GetLocalPath(dd.Repository, m.ChartPath); err != nil {
return nil, err
}
@ -871,7 +878,7 @@ func writeLock(chartpath string, lock *chart.Lock, legacyLockfile bool) error {
// archive a dep chart from local directory and save it into destPath
func tarFromLocalDir(chartpath, name, repo, version, destPath string) (string, error) {
if !strings.HasPrefix(repo, "file://") {
if !resolver.IsLocalDependency(repo) {
return "", fmt.Errorf("wrong format: chart %s repository %s", name, repo)
}
@ -917,3 +924,51 @@ func key(name string) (string, error) {
}
return hex.EncodeToString(hash.Sum(nil)), nil
}
// updateLocalDeps recursively updates local dependencies and detects circular dependencies.
func (m *Manager) updateLocalDeps(deps []*chart.Dependency, chain []string) error {
absPath, err := filepath.Abs(m.ChartPath)
if err != nil {
return err
}
// Check for circular dependency
for i, visited := range chain {
if visited == absPath {
cycle := append(chain[i:], absPath)
return fmt.Errorf("circular dependency detected:\n%s", strings.Join(cycle, "\n -> "))
}
}
chain = append(chain, absPath)
for _, dep := range deps {
if !resolver.IsLocalDependency(dep.Repository) {
continue
}
chartpath, err := resolver.GetLocalPath(dep.Repository, m.ChartPath)
if err != nil {
return err
}
c, err := loader.LoadDir(chartpath)
if err != nil {
return err
}
man := *m
man.SkipUpdate = true
man.ChartPath = chartpath
slog.Debug("Recursively updating dependencies for local chart", "chart", dep.Name, "path", chartpath)
if err := man.updateLocalDeps(c.Metadata.Dependencies, chain); err != nil {
return err
}
if err := man.Update(); err != nil {
return err
}
}
return nil
}

Loading…
Cancel
Save