Skip to content

Commit 29a051c

Browse files
committed
Refactor and document DependencyManager
Mostly to re-use the fields of the structure instead of copying things around. Signed-off-by: Hidde Beydals <[email protected]>
1 parent 8d0b54e commit 29a051c

File tree

3 files changed

+101
-63
lines changed

3 files changed

+101
-63
lines changed

controllers/helmchart_controller.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
515515
if strings.HasPrefix(dep.Repository, "file://") {
516516
dwr = append(dwr, &helm.DependencyWithRepository{
517517
Dependency: dep,
518-
Repo: nil,
518+
Repository: nil,
519519
})
520520
continue
521521
}
@@ -577,15 +577,15 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
577577

578578
dwr = append(dwr, &helm.DependencyWithRepository{
579579
Dependency: dep,
580-
Repo: chartRepo,
580+
Repository: chartRepo,
581581
})
582582
}
583583

584584
// Construct dependencies for chart if any
585585
if len(dwr) > 0 {
586586
dm := &helm.DependencyManager{
587-
BaseDir: tmpDir,
588-
ChartPath: chartPath,
587+
WorkingDir: tmpDir,
588+
ChartPath: chart.Spec.Chart,
589589
Chart: helmChart,
590590
Dependencies: dwr,
591591
}

internal/helm/dependency_manager.go

Lines changed: 69 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package helm
1919
import (
2020
"context"
2121
"fmt"
22+
"net/url"
2223
"os"
2324
"path/filepath"
2425
"strings"
@@ -30,22 +31,35 @@ import (
3031
"helm.sh/helm/v3/pkg/chart/loader"
3132
)
3233

33-
// DependencyWithRepository is a container for a dependency and its respective
34-
// repository
34+
// DependencyWithRepository is a container for a Helm chart dependency
35+
// and its respective repository.
3536
type DependencyWithRepository struct {
37+
// Dependency holds the reference to a chart.Chart dependency.
3638
Dependency *helmchart.Dependency
37-
Repo *ChartRepository
39+
// Repository is the ChartRepository the dependency should be
40+
// available at and can be downloaded from. If there is none,
41+
// a local ('file://') dependency is assumed.
42+
Repository *ChartRepository
3843
}
3944

40-
// DependencyManager manages dependencies for helm charts
45+
// DependencyManager manages dependencies for a Helm chart.
4146
type DependencyManager struct {
42-
BaseDir string
43-
ChartPath string
44-
Chart *helmchart.Chart
47+
// WorkingDir is the chroot path for dependency manager operations,
48+
// Dependencies that hold a local (relative) path reference are not
49+
// allowed to traverse outside this directory.
50+
WorkingDir string
51+
// ChartPath is the path of the Chart relative to the WorkingDir,
52+
// the combination of the WorkingDir and ChartPath is used to
53+
// determine the absolute path of a local dependency.
54+
ChartPath string
55+
// Chart holds the loaded chart.Chart from the ChartPath.
56+
Chart *helmchart.Chart
57+
// Dependencies contains a list of dependencies, and the respective
58+
// repository the dependency can be found at.
4559
Dependencies []*DependencyWithRepository
4660
}
4761

48-
// Build compiles and builds the chart dependencies
62+
// Build compiles and builds the dependencies of the Chart.
4963
func (dm *DependencyManager) Build(ctx context.Context) error {
5064
if len(dm.Dependencies) == 0 {
5165
return nil
@@ -60,85 +74,90 @@ func (dm *DependencyManager) Build(ctx context.Context) error {
6074
default:
6175
}
6276

63-
var (
64-
ch *helmchart.Chart
65-
err error
66-
)
67-
if strings.HasPrefix(item.Dependency.Repository, "file://") {
68-
ch, err = chartForLocalDependency(item.Dependency, dm.BaseDir, dm.ChartPath)
69-
} else {
70-
ch, err = chartForRemoteDependency(item.Dependency, item.Repo)
71-
}
72-
if err != nil {
73-
return err
77+
var err error
78+
switch item.Repository {
79+
case nil:
80+
err = dm.addLocalDependency(item)
81+
default:
82+
err = dm.addRemoteDependency(item)
7483
}
75-
dm.Chart.AddDependency(ch)
76-
return nil
84+
return err
7785
})
7886
}
7987

8088
return errs.Wait()
8189
}
8290

83-
func chartForLocalDependency(dep *helmchart.Dependency, baseDir, chartPath string) (*helmchart.Chart, error) {
84-
origPath, err := securejoin.SecureJoin(baseDir,
85-
filepath.Join(strings.TrimPrefix(chartPath, baseDir), strings.TrimPrefix(dep.Repository, "file://")))
91+
func (dm *DependencyManager) addLocalDependency(dpr *DependencyWithRepository) error {
92+
sLocalChartPath, err := dm.secureLocalChartPath(dpr)
8693
if err != nil {
87-
return nil, err
94+
return err
8895
}
8996

90-
if _, err := os.Stat(origPath); os.IsNotExist(err) {
91-
err := fmt.Errorf("chart path %s not found: %w", origPath, err)
92-
return nil, err
93-
} else if err != nil {
94-
return nil, err
97+
if _, err := os.Stat(sLocalChartPath); err != nil {
98+
if os.IsNotExist(err) {
99+
return fmt.Errorf("no chart found at '%s' (reference '%s') for dependency '%s'",
100+
strings.TrimPrefix(sLocalChartPath, dm.WorkingDir), dpr.Dependency.Repository, dpr.Dependency.Name)
101+
}
102+
return err
95103
}
96104

97-
ch, err := loader.Load(origPath)
105+
ch, err := loader.Load(sLocalChartPath)
98106
if err != nil {
99-
return nil, err
107+
return err
100108
}
101109

102-
constraint, err := semver.NewConstraint(dep.Version)
110+
constraint, err := semver.NewConstraint(dpr.Dependency.Version)
103111
if err != nil {
104-
err := fmt.Errorf("dependency %s has an invalid version/constraint format: %w", dep.Name, err)
105-
return nil, err
112+
err := fmt.Errorf("dependency '%s' has an invalid version/constraint format: %w", dpr.Dependency.Name, err)
113+
return err
106114
}
107115

108116
v, err := semver.NewVersion(ch.Metadata.Version)
109117
if err != nil {
110-
return nil, err
118+
return err
111119
}
112120

113121
if !constraint.Check(v) {
114-
err = fmt.Errorf("can't get a valid version for dependency %s", dep.Name)
115-
return nil, err
122+
err = fmt.Errorf("can't get a valid version for dependency '%s'", dpr.Dependency.Name)
123+
return err
116124
}
117125

118-
return ch, nil
126+
dm.Chart.AddDependency(ch)
127+
return nil
119128
}
120129

121-
func chartForRemoteDependency(dep *helmchart.Dependency, chartRepo *ChartRepository) (*helmchart.Chart, error) {
122-
if chartRepo == nil {
123-
return nil, fmt.Errorf("chartrepo should not be nil")
130+
func (dm *DependencyManager) addRemoteDependency(dpr *DependencyWithRepository) error {
131+
if dpr.Repository == nil {
132+
return fmt.Errorf("no ChartRepository given for '%s' dependency", dpr.Dependency.Name)
124133
}
125134

126-
// Lookup the chart version in the chart repository index
127-
chartVer, err := chartRepo.Get(dep.Name, dep.Version)
135+
chartVer, err := dpr.Repository.Get(dpr.Dependency.Name, dpr.Dependency.Version)
128136
if err != nil {
129-
return nil, err
137+
return err
130138
}
131139

132-
// Download chart
133-
res, err := chartRepo.DownloadChart(chartVer)
140+
res, err := dpr.Repository.DownloadChart(chartVer)
134141
if err != nil {
135-
return nil, err
142+
return err
136143
}
137144

138145
ch, err := loader.LoadArchive(res)
139146
if err != nil {
140-
return nil, err
147+
return err
141148
}
142149

143-
return ch, nil
150+
dm.Chart.AddDependency(ch)
151+
return nil
152+
}
153+
154+
func (dm *DependencyManager) secureLocalChartPath(dep *DependencyWithRepository) (string, error) {
155+
localUrl, err := url.Parse(dep.Dependency.Repository)
156+
if err != nil {
157+
return "", fmt.Errorf("failed to parse alleged local chart reference: %w", err)
158+
}
159+
if localUrl.Scheme != "file" {
160+
return "", fmt.Errorf("'%s' is not a local chart reference", dep.Dependency.Repository)
161+
}
162+
return securejoin.SecureJoin(dm.WorkingDir, filepath.Join(dm.ChartPath, localUrl.Host, localUrl.Path))
144163
}

internal/helm/dependency_manager_test.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,15 @@ func TestBuild_WithLocalChart(t *testing.T) {
7373
Repository: chartLocalRepository,
7474
},
7575
},
76+
{
77+
name: "allowed traversing path",
78+
dep: helmchart.Dependency{
79+
Name: chartName,
80+
Alias: "aliased",
81+
Version: chartVersion,
82+
Repository: "file://../../../testdata/charts/helmchartwithdeps/../helmchart",
83+
},
84+
},
7685
{
7786
name: "invalid path",
7887
dep: helmchart.Dependency{
@@ -81,7 +90,17 @@ func TestBuild_WithLocalChart(t *testing.T) {
8190
Repository: "file://../invalid",
8291
},
8392
wantErr: true,
84-
errMsg: "no such file or directory",
93+
errMsg: "no chart found at",
94+
},
95+
{
96+
name: "illegal traversing path",
97+
dep: helmchart.Dependency{
98+
Name: chartName,
99+
Version: chartVersion,
100+
Repository: "file://../../../../../controllers/testdata/charts/helmchart",
101+
},
102+
wantErr: true,
103+
errMsg: "no chart found at",
85104
},
86105
{
87106
name: "invalid version constraint format",
@@ -109,13 +128,13 @@ func TestBuild_WithLocalChart(t *testing.T) {
109128
t.Run(tt.name, func(t *testing.T) {
110129
c := chartFixture
111130
dm := DependencyManager{
112-
BaseDir: "./",
113-
ChartPath: "testdata/charts/helmchart",
114-
Chart: &c,
131+
WorkingDir: "./",
132+
ChartPath: "testdata/charts/helmchart",
133+
Chart: &c,
115134
Dependencies: []*DependencyWithRepository{
116135
{
117136
Dependency: &tt.dep,
118-
Repo: nil,
137+
Repository: nil,
119138
},
120139
},
121140
}
@@ -132,7 +151,7 @@ func TestBuild_WithLocalChart(t *testing.T) {
132151
}
133152
return
134153
} else if err != nil {
135-
t.Errorf("Build() expected to return error")
154+
t.Errorf("Build() not expected to return an error: %s", err)
136155
return
137156
}
138157

@@ -168,7 +187,7 @@ func TestBuild_WithRemoteChart(t *testing.T) {
168187
Dependencies: []*DependencyWithRepository{
169188
{
170189
Dependency: &remoteDepFixture,
171-
Repo: cr,
190+
Repository: cr,
172191
},
173192
},
174193
}
@@ -189,10 +208,10 @@ func TestBuild_WithRemoteChart(t *testing.T) {
189208
}
190209

191210
// When repo is not set
192-
dm.Dependencies[0].Repo = nil
211+
dm.Dependencies[0].Repository = nil
193212
if err := dm.Build(context.TODO()); err == nil {
194213
t.Errorf("Build() expected to return error")
195-
} else if !strings.Contains(err.Error(), "chartrepo should not be nil") {
214+
} else if !strings.Contains(err.Error(), "is not a local chart reference") {
196215
t.Errorf("Build() expected to return different error, got: %s", err)
197216
}
198217
}

0 commit comments

Comments
 (0)