@@ -2,10 +2,10 @@ package charts
2
2
3
3
import (
4
4
"context"
5
- "encoding/base64"
6
5
"encoding/json"
7
6
"fmt"
8
7
"path"
8
+ "sort"
9
9
"time"
10
10
11
11
"helm.sh/helm/v3/pkg/action"
@@ -18,12 +18,22 @@ import (
18
18
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
19
19
"k8s.io/apimachinery/pkg/types"
20
20
"sigs.k8s.io/controller-runtime/pkg/client"
21
- "sigs.k8s.io/yaml"
22
21
23
22
helmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1"
23
+ "github.com/fluxcd/pkg/runtime/dependency"
24
24
sourcev1beta1 "github.com/fluxcd/source-controller/api/v1beta1"
25
25
)
26
26
27
+ const (
28
+ // LayerAnnotation is the annotation that Helm charts can have to indicate which
29
+ // layer they should be in, the HelmRelease DependsOn is calculated from this.
30
+ LayerAnnotation = "weave.works/layer"
31
+
32
+ // LayerLabel is applied to created HelmReleases which makes it possible to
33
+ // query for HelmReleases that are applied in a layer.
34
+ LayerLabel = "weave.works/applied-layer"
35
+ )
36
+
27
37
// ChartReference is a Helm chart reference, the SourceRef is a Flux
28
38
// SourceReference for the Helm chart.
29
39
type ChartReference struct {
@@ -171,44 +181,120 @@ func (h HelmChartClient) envSettings() *cli.EnvSettings {
171
181
return conf
172
182
}
173
183
174
- func ParseValues (chart string , version string , values string , clusterName string , helmRepo * sourcev1beta1.HelmRepository ) (* helmv2beta1.HelmRelease , error ) {
175
- decoded , err := base64 .StdEncoding .DecodeString (values )
176
- if err != nil {
177
- return nil , fmt .Errorf ("failed to base64 decode values: %w" , err )
184
+ // MakeHelmReleasesInLayers accepts a set of ChartInstall requests and
185
+ // returns a set of HelmReleases that are configured with appropriate
186
+ // dependencies.
187
+ //
188
+ // If the Charts are annotated with a layer, the charts will be installed in the
189
+ // layer order.
190
+ //
191
+ // For charts without a layer, these will be configured to depend on the highest
192
+ // layer.
193
+ func MakeHelmReleasesInLayers (clusterName , namespace string , installs []ChartInstall ) ([]* helmv2beta1.HelmRelease , error ) {
194
+ layerInstalls := map [string ][]ChartInstall {}
195
+ for _ , v := range installs {
196
+ current , ok := layerInstalls [v .Layer ]
197
+ if ! ok {
198
+ current = []ChartInstall {}
199
+ }
200
+ current = append (current , v )
201
+ layerInstalls [v .Layer ] = current
178
202
}
179
- vals := map [string ]interface {}{}
180
- yaml .Unmarshal (decoded , & vals )
181
- jsonValues , err := json .Marshal (vals )
182
- if err != nil {
183
- return nil , fmt .Errorf ("failed to marshal YAML values into JSON: %w" , err )
184
- }
185
-
186
- hr := helmv2beta1.HelmRelease {
187
- ObjectMeta : metav1.ObjectMeta {
188
- Name : fmt .Sprintf ("%s-%s" , clusterName , chart ),
189
- Namespace : "wego-system" ,
190
- },
191
- TypeMeta : metav1.TypeMeta {
192
- APIVersion : helmv2beta1 .GroupVersion .Identifier (),
193
- Kind : helmv2beta1 .HelmReleaseKind ,
194
- },
195
- Spec : helmv2beta1.HelmReleaseSpec {
196
- Chart : helmv2beta1.HelmChartTemplate {
197
- Spec : helmv2beta1.HelmChartTemplateSpec {
198
- Chart : chart ,
199
- Version : version ,
200
- SourceRef : helmv2beta1.CrossNamespaceObjectReference {
201
- APIVersion : sourcev1beta1 .GroupVersion .Identifier (),
202
- Kind : sourcev1beta1 .HelmRepositoryKind ,
203
- Name : helmRepo .ObjectMeta .Name ,
204
- Namespace : helmRepo .ObjectMeta .Namespace ,
203
+
204
+ var layerNames []string
205
+ for k := range layerInstalls {
206
+ layerNames = append (layerNames , k )
207
+ }
208
+
209
+ makeHelmReleaseName := func (clusterName , installName string ) string {
210
+ return clusterName + "-" + installName
211
+ }
212
+
213
+ layerDependencies := pairLayers (layerNames )
214
+ var releases []* helmv2beta1.HelmRelease
215
+ for _ , layer := range layerDependencies {
216
+ for _ , install := range layerInstalls [layer .name ] {
217
+ jsonValues , err := json .Marshal (install .Values )
218
+ if err != nil {
219
+ return nil , fmt .Errorf ("failed to marshal values for chart %s: %w" , install .Ref .Chart , err )
220
+ }
221
+ hr := helmv2beta1.HelmRelease {
222
+ ObjectMeta : metav1.ObjectMeta {
223
+ Name : makeHelmReleaseName (clusterName , install .Ref .Chart ),
224
+ Namespace : namespace ,
225
+ },
226
+ TypeMeta : metav1.TypeMeta {
227
+ APIVersion : helmv2beta1 .GroupVersion .Identifier (),
228
+ Kind : helmv2beta1 .HelmReleaseKind ,
229
+ },
230
+ Spec : helmv2beta1.HelmReleaseSpec {
231
+ Chart : helmv2beta1.HelmChartTemplate {
232
+ Spec : helmv2beta1.HelmChartTemplateSpec {
233
+ Chart : install .Ref .Chart ,
234
+ Version : install .Ref .Version ,
235
+ SourceRef : helmv2beta1.CrossNamespaceObjectReference {
236
+ APIVersion : sourcev1beta1 .GroupVersion .Identifier (),
237
+ Kind : sourcev1beta1 .HelmRepositoryKind ,
238
+ Name : install .Ref .SourceRef .Name ,
239
+ Namespace : install .Ref .SourceRef .Namespace ,
240
+ },
241
+ },
205
242
},
243
+ Interval : metav1.Duration {Duration : time .Minute },
244
+ Values : & apiextensionsv1.JSON {Raw : jsonValues },
206
245
},
207
- },
208
- Interval : metav1.Duration {Duration : time .Minute },
209
- Values : & apiextensionsv1.JSON {Raw : jsonValues },
210
- },
246
+ }
247
+ if layer .dependsOn != "" {
248
+ for _ , v := range layerInstalls [layer .dependsOn ] {
249
+ hr .Spec .DependsOn = append (hr .Spec .DependsOn ,
250
+ dependency.CrossNamespaceDependencyReference {
251
+ Name : makeHelmReleaseName (clusterName , v .Ref .Chart ),
252
+ })
253
+ }
254
+ }
255
+ if layer .name != "" {
256
+ hr .Labels = map [string ]string {
257
+ LayerLabel : layer .name ,
258
+ }
259
+ }
260
+ releases = append (releases , & hr )
261
+ }
211
262
}
212
263
213
- return & hr , nil
264
+ sort .Slice (releases , func (i , j int ) bool { return releases [i ].GetName () < releases [j ].GetName () })
265
+ return releases , nil
266
+ }
267
+
268
+ type layerDependency struct {
269
+ name string
270
+ dependsOn string
271
+ }
272
+
273
+ // iterate over a slice returning slice where element 1 will be configured to
274
+ // depend on layer 0.
275
+ //
276
+ // The sorting is determined lexicographically.
277
+ func pairLayers (names []string ) []layerDependency {
278
+ sort .Sort (sort .Reverse (sort .StringSlice (names )))
279
+ deps := []layerDependency {}
280
+ for i := range names {
281
+ if i < len (names )- 1 {
282
+ deps = append (deps , layerDependency {name : names [i ], dependsOn : names [i + 1 ]})
283
+ continue
284
+ }
285
+ dep := layerDependency {name : names [i ]}
286
+ if names [i ] == "" && len (names ) > 0 {
287
+ dep .dependsOn = names [0 ]
288
+ }
289
+ deps = append (deps , dep )
290
+ }
291
+ return deps
292
+ }
293
+
294
+ // ChartInstall configures the installation of a specific chart into a
295
+ // cluster.
296
+ type ChartInstall struct {
297
+ Ref ChartReference
298
+ Layer string
299
+ Values map [string ]interface {}
214
300
}
0 commit comments