Skip to content

Commit e8833e0

Browse files
authored
Merge pull request #364 from weaveworks/layer-ordering
Layer ordering. Profile helm charts can be annotated with weave.works/layer: layer-name. Charts with metadata annotations are ordered based on a sorted version of the layers. For example, if two Profile charts are being installed in layer-0 and layer-1, the charts with declared layer-1 will be configured as dependsOn the chart in layer-0.
2 parents 62ba809 + 83dc5eb commit e8833e0

File tree

10 files changed

+526
-197
lines changed

10 files changed

+526
-197
lines changed

cmd/clusters-service/api/capi_server.proto

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,4 +260,5 @@ message ProfileValues {
260260
string name = 1;
261261
string version = 2;
262262
string values = 3;
263+
string layer = 4;
263264
}

cmd/clusters-service/api/capi_server.swagger.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@
181181
},
182182
"/v1/profiles/{profileName}/{profileVersion}/values": {
183183
"get": {
184-
"summary": "GetProfileVersionValues returns a list of profiles\nfrom the cluster.",
184+
"summary": "GetProfileValues returns a list of values for \na given version of a profile from the cluster.",
185185
"operationId": "ClustersService_GetProfileValues",
186186
"responses": {
187187
"200": {
@@ -725,6 +725,9 @@
725725
},
726726
"values": {
727727
"type": "string"
728+
},
729+
"layer": {
730+
"type": "string"
728731
}
729732
}
730733
},

cmd/clusters-service/pkg/charts/values.go

Lines changed: 123 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package charts
22

33
import (
44
"context"
5-
"encoding/base64"
65
"encoding/json"
76
"fmt"
87
"path"
8+
"sort"
99
"time"
1010

1111
"helm.sh/helm/v3/pkg/action"
@@ -18,12 +18,22 @@ import (
1818
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1919
"k8s.io/apimachinery/pkg/types"
2020
"sigs.k8s.io/controller-runtime/pkg/client"
21-
"sigs.k8s.io/yaml"
2221

2322
helmv2beta1 "github.com/fluxcd/helm-controller/api/v2beta1"
23+
"github.com/fluxcd/pkg/runtime/dependency"
2424
sourcev1beta1 "github.com/fluxcd/source-controller/api/v1beta1"
2525
)
2626

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+
2737
// ChartReference is a Helm chart reference, the SourceRef is a Flux
2838
// SourceReference for the Helm chart.
2939
type ChartReference struct {
@@ -171,44 +181,120 @@ func (h HelmChartClient) envSettings() *cli.EnvSettings {
171181
return conf
172182
}
173183

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
178202
}
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+
},
205242
},
243+
Interval: metav1.Duration{Duration: time.Minute},
244+
Values: &apiextensionsv1.JSON{Raw: jsonValues},
206245
},
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+
}
211262
}
212263

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{}
214300
}

0 commit comments

Comments
 (0)