Skip to content

Commit 48a2111

Browse files
committed
fixes
Signed-off-by: Suleiman Dibirov <[email protected]>
1 parent 6506f15 commit 48a2111

File tree

3 files changed

+83
-123
lines changed

3 files changed

+83
-123
lines changed

pkg/skaffold/deploy/helm/dependencygraph.go

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,46 @@ func NewDependencyGraph(releases []latest.HelmRelease) (*DependencyGraph, error)
5353
graph[r.Name] = r.DependsOn
5454
}
5555

56-
return &DependencyGraph{
56+
g := &DependencyGraph{
5757
graph: graph,
5858
releases: releases,
5959
hasDependencies: hasDependencies,
60-
}, nil
60+
}
61+
62+
if err := g.hasCycles(); err != nil {
63+
return nil, err
64+
}
65+
66+
return g, nil
6167
}
6268

63-
// HasCycles checks if there are any cycles in the dependency graph
64-
func (g *DependencyGraph) HasCycles() error {
69+
// GetReleasesByLevel returns releases grouped by their dependency level while preserving
70+
// the original order within each level. Level 0 contains releases with no dependencies,
71+
// level 1 contains releases that depend only on level 0 releases, and so on.
72+
func (g *DependencyGraph) GetReleasesByLevel() (map[int][]string, error) {
73+
if len(g.releases) == 0 {
74+
// For empty releases, return empty map to avoid nil
75+
return map[int][]string{}, nil
76+
}
77+
78+
if !g.hasDependencies {
79+
// Fast path: if no dependencies, all releases are at level 0
80+
// Preserve original order from releases slice
81+
return map[int][]string{
82+
0: g.getNames(),
83+
}, nil
84+
}
85+
86+
order, err := g.calculateDeploymentOrder()
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
return g.groupReleasesByLevel(order), nil
92+
}
93+
94+
// hasCycles checks if there are any cycles in the dependency graph
95+
func (g *DependencyGraph) hasCycles() error {
6596
if !g.hasDependencies {
6697
return nil
6798
}
@@ -81,7 +112,7 @@ func (g *DependencyGraph) HasCycles() error {
81112
return err
82113
}
83114
} else if recStack[dep] {
84-
return fmt.Errorf("cycle detected involving release %s", node)
115+
return fmt.Errorf("cycle detected involving release %q", node)
85116
}
86117
}
87118
}
@@ -99,36 +130,6 @@ func (g *DependencyGraph) HasCycles() error {
99130
return nil
100131
}
101132

102-
// GetReleasesByLevel returns releases grouped by their dependency level while preserving
103-
// the original order within each level. Level 0 contains releases with no dependencies,
104-
// level 1 contains releases that depend only on level 0 releases, and so on.
105-
func (g *DependencyGraph) GetReleasesByLevel() (map[int][]string, error) {
106-
if len(g.releases) == 0 {
107-
// For empty releases, return empty map to avoid nil
108-
return map[int][]string{}, nil
109-
}
110-
111-
if !g.hasDependencies {
112-
// Fast path: if no dependencies, all releases are at level 0
113-
// Preserve original order from releases slice
114-
return map[int][]string{
115-
0: g.getNames(),
116-
}, nil
117-
}
118-
119-
// Check for cycles before calculating deployment order
120-
if err := g.HasCycles(); err != nil {
121-
return nil, err
122-
}
123-
124-
order, err := g.calculateDeploymentOrder()
125-
if err != nil {
126-
return nil, err
127-
}
128-
129-
return g.groupReleasesByLevel(order), nil
130-
}
131-
132133
// getNames returns a slice of release names in their original order
133134
func (g *DependencyGraph) getNames() []string {
134135
names := make([]string, len(g.releases))
@@ -143,7 +144,13 @@ func (g *DependencyGraph) getNames() []string {
143144
// the original order where possible
144145
func (g *DependencyGraph) calculateDeploymentOrder() ([]string, error) {
145146
visited := make(map[string]bool)
146-
order := make([]string, 0)
147+
order := make([]string, 0, len(g.releases))
148+
149+
// Create a mapping of release name to its index in original order
150+
originalOrder := make(map[string]int, len(g.releases))
151+
for i, release := range g.releases {
152+
originalOrder[release.Name] = i
153+
}
147154

148155
var visit func(node string) error
149156
visit = func(node string) error {
@@ -152,7 +159,22 @@ func (g *DependencyGraph) calculateDeploymentOrder() ([]string, error) {
152159
}
153160
visited[node] = true
154161

155-
for _, dep := range g.graph[node] {
162+
// Sort dependencies based on original order
163+
deps := make([]string, len(g.graph[node]))
164+
copy(deps, g.graph[node])
165+
if len(deps) > 1 {
166+
// Sort dependencies by their original position
167+
for i := 0; i < len(deps)-1; i++ {
168+
for j := i + 1; j < len(deps); j++ {
169+
if originalOrder[deps[i]] > originalOrder[deps[j]] {
170+
deps[i], deps[j] = deps[j], deps[i]
171+
}
172+
}
173+
}
174+
}
175+
176+
// Visit dependencies in original order
177+
for _, dep := range deps {
156178
if err := visit(dep); err != nil {
157179
return err
158180
}

pkg/skaffold/deploy/helm/dependencygraph_test.go

Lines changed: 23 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
2424
"github.com/GoogleContainerTools/skaffold/v2/testutil"
25+
"github.com/google/go-cmp/cmp"
2526
)
2627

2728
func TestNewDependencyGraph(t *testing.T) {
@@ -36,7 +37,7 @@ func TestNewDependencyGraph(t *testing.T) {
3637
description: "simple dependency graph",
3738
releases: []latest.HelmRelease{
3839
{Name: "release1", DependsOn: []string{"release2"}},
39-
{Name: "release2", DependsOn: []string{}},
40+
{Name: "release2"},
4041
},
4142
expected: map[string][]string{
4243
"release1": {"release2"},
@@ -85,6 +86,15 @@ func TestNewDependencyGraph(t *testing.T) {
8586
shouldErr: true,
8687
errorMessage: "duplicate release name release1",
8788
},
89+
{
90+
description: "has cycle",
91+
releases: []latest.HelmRelease{
92+
{Name: "a", DependsOn: []string{"b"}},
93+
{Name: "b", DependsOn: []string{"a"}},
94+
},
95+
shouldErr: true,
96+
errorMessage: "cycle detected",
97+
},
8898
}
8999

90100
for _, test := range tests {
@@ -97,24 +107,11 @@ func TestNewDependencyGraph(t *testing.T) {
97107
}
98108
t.CheckDeepEqual(len(test.expected), len(graph.graph))
99109

100-
for release, deps := range test.expected {
101-
actualDeps, exists := graph.graph[release]
102-
if !exists {
103-
t.Errorf("missing release %s in graph", release)
104-
continue
105-
}
106-
107-
if len(deps) != len(actualDeps) {
108-
t.Errorf("expected %d dependencies for %s, got %d", len(deps), release, len(actualDeps))
109-
continue
110-
}
111-
112-
// Check all expected dependencies exist
113-
for _, dep := range deps {
114-
if !slices.Contains(actualDeps, dep) {
115-
t.Errorf("missing dependency %s for release %s", dep, release)
116-
}
117-
}
110+
opt := cmp.Comparer(func(x, y []string) bool {
111+
return slices.Equal(x, y)
112+
})
113+
if diff := cmp.Diff(test.expected, graph.graph, opt); diff != "" {
114+
t.Errorf("%s:got unexpected diff: %s", test.description, diff)
118115
}
119116
})
120117
}
@@ -172,9 +169,7 @@ func TestHasCycles(t *testing.T) {
172169

173170
for _, test := range tests {
174171
testutil.Run(t, test.description, func(t *testutil.T) {
175-
graph, err := NewDependencyGraph(test.releases)
176-
t.CheckError(false, err)
177-
err = graph.HasCycles()
172+
_, err := NewDependencyGraph(test.releases)
178173

179174
if test.shouldErr {
180175
t.CheckErrorContains("cycle detected", err)
@@ -207,7 +202,7 @@ func TestGetReleasesByLevel(t *testing.T) {
207202
{
208203
description: "multiple dependencies at same level",
209204
releases: []latest.HelmRelease{
210-
{Name: "a", DependsOn: []string{"b", "c"}},
205+
{Name: "a", DependsOn: []string{"c", "b"}},
211206
{Name: "b", DependsOn: []string{"d"}},
212207
{Name: "c", DependsOn: []string{"d"}},
213208
{Name: "d"},
@@ -276,8 +271,11 @@ func TestGetReleasesByLevel(t *testing.T) {
276271
t.CheckDeepEqual(len(test.expected), len(levels))
277272

278273
// Check that each level contains expected releases
279-
for level, releases := range test.expected {
280-
t.CheckDeepEqual(releases, levels[level])
274+
opt := cmp.Comparer(func(x, y []string) bool {
275+
return slices.Equal(x, y)
276+
})
277+
if diff := cmp.Diff(test.expected, levels, opt); diff != "" {
278+
t.Errorf("%s: got unexpected diff (-want +got):\n%s", test.description, diff)
281279
}
282280

283281
// Verify level assignments are correct
@@ -317,59 +315,3 @@ func TestGetReleasesByLevel(t *testing.T) {
317315
})
318316
}
319317
}
320-
321-
func TestOrderPreservationWithinLevels(t *testing.T) {
322-
tests := []struct {
323-
description string
324-
releases []latest.HelmRelease
325-
expected map[int][]string
326-
}{
327-
{
328-
description: "preserve order within same level",
329-
releases: []latest.HelmRelease{
330-
{Name: "a3"},
331-
{Name: "a2"},
332-
{Name: "a1"},
333-
{Name: "b3", DependsOn: []string{"a3", "a2", "a1"}},
334-
{Name: "b2", DependsOn: []string{"a1", "a2", "a3"}},
335-
{Name: "b1", DependsOn: []string{"a1", "a2", "a3"}},
336-
},
337-
expected: map[int][]string{
338-
0: {"a3", "a2", "a1"},
339-
1: {"b3", "b2", "b1"},
340-
},
341-
},
342-
{
343-
description: "preserve order with mixed dependencies",
344-
releases: []latest.HelmRelease{
345-
{Name: "c2", DependsOn: []string{"a1"}},
346-
{Name: "a1"},
347-
{Name: "c1", DependsOn: []string{"a1"}},
348-
{Name: "c3", DependsOn: []string{"a1"}},
349-
{Name: "b1", DependsOn: []string{"c1", "c2", "c3"}},
350-
},
351-
expected: map[int][]string{
352-
0: {"a1"},
353-
1: {"c2", "c1", "c3"},
354-
2: {"b1"},
355-
},
356-
},
357-
}
358-
359-
for _, test := range tests {
360-
testutil.Run(t, test.description, func(t *testutil.T) {
361-
graph, err := NewDependencyGraph(test.releases)
362-
t.CheckNoError(err)
363-
364-
levels, err := graph.GetReleasesByLevel()
365-
t.CheckNoError(err)
366-
367-
// Verify exact ordering within each level
368-
for level, expectedReleases := range test.expected {
369-
actualReleases, exists := levels[level]
370-
t.CheckTrue(exists)
371-
t.CheckDeepEqual(expectedReleases, actualReleases)
372-
}
373-
})
374-
}
375-
}

pkg/skaffold/schema/defaults/defaults.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,10 @@ func setHelmDefaults(c *latest.SkaffoldConfig) error {
129129
}
130130

131131
if len(c.Deploy.LegacyHelmDeploy.Releases) > 1 {
132-
dependencyGraph, err := helm.NewDependencyGraph(c.Deploy.LegacyHelmDeploy.Releases)
132+
_, err := helm.NewDependencyGraph(c.Deploy.LegacyHelmDeploy.Releases)
133133
if err != nil {
134134
return err
135135
}
136-
137-
if err := dependencyGraph.HasCycles(); err != nil {
138-
return fmt.Errorf("dependency cycle detected in helm releases: %w", err)
139-
}
140136
}
141137

142138
return nil

0 commit comments

Comments
 (0)