@@ -19,123 +19,145 @@ package helm
1919import (
2020 "context"
2121 "fmt"
22+ "net/url"
2223 "os"
23- "path"
2424 "path/filepath"
2525 "strings"
2626
2727 "github.com/Masterminds/semver/v3"
28+ securejoin "github.com/cyphar/filepath-securejoin"
2829 "golang.org/x/sync/errgroup"
2930 helmchart "helm.sh/helm/v3/pkg/chart"
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.
3536type 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.
4146type DependencyManager struct {
42- Chart * helmchart.Chart
43- ChartPath string
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.
4459 Dependencies []* DependencyWithRepository
4560}
4661
47- // Build compiles and builds the chart dependencies
48- func (dm * DependencyManager ) Build () error {
49- if dm .Dependencies == nil {
62+ // Build compiles and builds the dependencies of the Chart.
63+ func (dm * DependencyManager ) Build (ctx context. Context ) error {
64+ if len ( dm .Dependencies ) == 0 {
5065 return nil
5166 }
5267
53- ctx := context .Background ()
5468 errs , ctx := errgroup .WithContext (ctx )
55-
5669 for _ , item := range dm .Dependencies {
57- dep := item .Dependency
58- chartRepo := item .Repo
5970 errs .Go (func () error {
60- var (
61- ch * helmchart.Chart
62- err error
63- )
64- if strings .HasPrefix (dep .Repository , "file://" ) {
65- ch , err = chartForLocalDependency (dep , dm .ChartPath )
66- } else {
67- ch , err = chartForRemoteDependency (dep , chartRepo )
71+ select {
72+ case <- ctx .Done ():
73+ return ctx .Err ()
74+ default :
6875 }
69- if err != nil {
70- return err
76+
77+ var err error
78+ switch item .Repository {
79+ case nil :
80+ err = dm .addLocalDependency (item )
81+ default :
82+ err = dm .addRemoteDependency (item )
7183 }
72- dm .Chart .AddDependency (ch )
73- return nil
84+ return err
7485 })
7586 }
7687
7788 return errs .Wait ()
7889}
7990
80- func chartForLocalDependency ( dep * helmchart. Dependency , cp string ) ( * helmchart. Chart , error ) {
81- origPath , err := filepath . Abs ( path . Join ( cp , strings . TrimPrefix ( dep . Repository , "file://" )) )
91+ func ( dm * DependencyManager ) addLocalDependency ( dpr * DependencyWithRepository ) error {
92+ sLocalChartPath , err := dm . secureLocalChartPath ( dpr )
8293 if err != nil {
83- return nil , err
94+ return err
8495 }
8596
86- if _ , err := os .Stat (origPath ); os .IsNotExist (err ) {
87- err := fmt .Errorf ("chart path %s not found: %w" , origPath , err )
88- return nil , err
89- } else if err != nil {
90- 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
91103 }
92104
93- ch , err := loader .Load (origPath )
105+ ch , err := loader .Load (sLocalChartPath )
94106 if err != nil {
95- return nil , err
107+ return err
96108 }
97109
98- constraint , err := semver .NewConstraint (dep .Version )
110+ constraint , err := semver .NewConstraint (dpr . Dependency .Version )
99111 if err != nil {
100- err := fmt .Errorf ("dependency %s has an invalid version/constraint format: %w" , dep .Name , err )
101- return nil , err
112+ err := fmt .Errorf ("dependency '%s' has an invalid version/constraint format: %w" , dpr . Dependency .Name , err )
113+ return err
102114 }
103115
104116 v , err := semver .NewVersion (ch .Metadata .Version )
105117 if err != nil {
106- return nil , err
118+ return err
107119 }
108120
109121 if ! constraint .Check (v ) {
110- err = fmt .Errorf ("can't get a valid version for dependency %s " , dep .Name )
111- return nil , err
122+ err = fmt .Errorf ("can't get a valid version for dependency '%s' " , dpr . Dependency .Name )
123+ return err
112124 }
113125
114- return ch , nil
126+ dm .Chart .AddDependency (ch )
127+ return nil
115128}
116129
117- func chartForRemoteDependency (dep * helmchart.Dependency , chartrepo * ChartRepository ) (* helmchart.Chart , error ) {
118- if chartrepo == nil {
119- err := fmt .Errorf ("chartrepo should not be nil" )
120- return nil , err
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 )
121133 }
122134
123- // Lookup the chart version in the chart repository index
124- chartVer , err := chartrepo .Get (dep .Name , dep .Version )
135+ chartVer , err := dpr .Repository .Get (dpr .Dependency .Name , dpr .Dependency .Version )
125136 if err != nil {
126- return nil , err
137+ return err
127138 }
128139
129- // Download chart
130- res , err := chartrepo .DownloadChart (chartVer )
140+ res , err := dpr .Repository .DownloadChart (chartVer )
131141 if err != nil {
132- return nil , err
142+ return err
133143 }
134144
135145 ch , err := loader .LoadArchive (res )
136146 if err != nil {
137- return nil , err
147+ return err
138148 }
139149
140- 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 ))
141163}
0 commit comments