@@ -19,123 +19,145 @@ package helm
19
19
import (
20
20
"context"
21
21
"fmt"
22
+ "net/url"
22
23
"os"
23
- "path"
24
24
"path/filepath"
25
25
"strings"
26
26
27
27
"github.com/Masterminds/semver/v3"
28
+ securejoin "github.com/cyphar/filepath-securejoin"
28
29
"golang.org/x/sync/errgroup"
29
30
helmchart "helm.sh/helm/v3/pkg/chart"
30
31
"helm.sh/helm/v3/pkg/chart/loader"
31
32
)
32
33
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.
35
36
type DependencyWithRepository struct {
37
+ // Dependency holds the reference to a chart.Chart dependency.
36
38
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
38
43
}
39
44
40
- // DependencyManager manages dependencies for helm charts
45
+ // DependencyManager manages dependencies for a Helm chart.
41
46
type 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.
44
59
Dependencies []* DependencyWithRepository
45
60
}
46
61
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 {
50
65
return nil
51
66
}
52
67
53
- ctx := context .Background ()
54
68
errs , ctx := errgroup .WithContext (ctx )
55
-
56
69
for _ , item := range dm .Dependencies {
57
- dep := item .Dependency
58
- chartRepo := item .Repo
59
70
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 :
68
75
}
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 )
71
83
}
72
- dm .Chart .AddDependency (ch )
73
- return nil
84
+ return err
74
85
})
75
86
}
76
87
77
88
return errs .Wait ()
78
89
}
79
90
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 )
82
93
if err != nil {
83
- return nil , err
94
+ return err
84
95
}
85
96
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
91
103
}
92
104
93
- ch , err := loader .Load (origPath )
105
+ ch , err := loader .Load (sLocalChartPath )
94
106
if err != nil {
95
- return nil , err
107
+ return err
96
108
}
97
109
98
- constraint , err := semver .NewConstraint (dep .Version )
110
+ constraint , err := semver .NewConstraint (dpr . Dependency .Version )
99
111
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
102
114
}
103
115
104
116
v , err := semver .NewVersion (ch .Metadata .Version )
105
117
if err != nil {
106
- return nil , err
118
+ return err
107
119
}
108
120
109
121
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
112
124
}
113
125
114
- return ch , nil
126
+ dm .Chart .AddDependency (ch )
127
+ return nil
115
128
}
116
129
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 )
121
133
}
122
134
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 )
125
136
if err != nil {
126
- return nil , err
137
+ return err
127
138
}
128
139
129
- // Download chart
130
- res , err := chartrepo .DownloadChart (chartVer )
140
+ res , err := dpr .Repository .DownloadChart (chartVer )
131
141
if err != nil {
132
- return nil , err
142
+ return err
133
143
}
134
144
135
145
ch , err := loader .LoadArchive (res )
136
146
if err != nil {
137
- return nil , err
147
+ return err
138
148
}
139
149
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 ))
141
163
}
0 commit comments