Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/install/installer/pkg/helm/helm.go
2500 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package helm
6
7
import (
8
"bytes"
9
"context"
10
"fmt"
11
"os"
12
"os/signal"
13
"path/filepath"
14
"strings"
15
"syscall"
16
17
"sigs.k8s.io/yaml"
18
19
"github.com/gitpod-io/gitpod/installer/pkg/common"
20
"github.com/gitpod-io/gitpod/installer/third_party/charts"
21
"helm.sh/helm/v3/pkg/action"
22
"helm.sh/helm/v3/pkg/chart/loader"
23
"helm.sh/helm/v3/pkg/downloader"
24
"helm.sh/helm/v3/pkg/getter"
25
"helm.sh/helm/v3/pkg/release"
26
corev1 "k8s.io/api/core/v1"
27
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
)
29
30
// TemplateConfig
31
type TemplateConfig struct {
32
Namespace string
33
}
34
35
func getContext(settings Settings) context.Context {
36
// Create context and prepare the handle of SIGTERM
37
ctx := context.Background()
38
ctx, cancel := context.WithCancel(ctx)
39
40
// Handle SIGTERM
41
cSignal := make(chan os.Signal)
42
signal.Notify(cSignal, os.Interrupt, syscall.SIGTERM)
43
go func() {
44
<-cSignal
45
settings.Write("Release of Gitpod has been cancelled.")
46
cancel()
47
}()
48
49
return ctx
50
}
51
52
func installDependencies(settings Settings) error {
53
client := action.NewDependency()
54
55
man := &downloader.Manager{
56
Out: &bytes.Buffer{},
57
ChartPath: settings.Chart,
58
Keyring: client.Keyring,
59
SkipUpdate: client.SkipRefresh,
60
Verify: downloader.VerifyNever,
61
RegistryClient: settings.ActionConfig.RegistryClient,
62
Getters: getter.All(settings.Env),
63
RepositoryConfig: settings.Env.RepositoryConfig,
64
RepositoryCache: settings.Env.RepositoryCache,
65
Debug: false,
66
}
67
68
err := man.Update()
69
if err != nil {
70
return fmt.Errorf("error pulling Helm dependency for %s: %w", settings.Chart, err)
71
}
72
73
return nil
74
}
75
76
// runInstall emulates this function in Helm with simplified error handling
77
// https://github.com/helm/helm/blob/9fafb4ad6811afb017cc464b630be2ff8390ac63/cmd/helm/install.go#L177
78
func runInstall(settings Settings, client *action.Install) (*release.Release, error) {
79
name, _, err := client.NameAndChart([]string{
80
settings.Config.Name,
81
settings.Chart,
82
})
83
if err != nil {
84
return nil, err
85
}
86
client.ReleaseName = name
87
88
p := getter.All(settings.Env)
89
vals, err := settings.Values.MergeValues(p)
90
if err != nil {
91
return nil, err
92
}
93
94
chartRequested, err := loader.Load(settings.Chart)
95
if err != nil {
96
return nil, err
97
}
98
99
return client.RunWithContext(getContext(settings), chartRequested, vals)
100
}
101
102
func writeCharts(chart *charts.Chart) (string, error) {
103
dir, err := os.MkdirTemp("", chart.Name)
104
if err != nil {
105
return "", err
106
}
107
108
err = chart.Export(dir)
109
if err != nil {
110
return "", err
111
}
112
113
return dir, nil
114
}
115
116
// AffinityYaml convert an affinity into a YAML byte array
117
func AffinityYaml(orLabels ...string) ([]byte, error) {
118
affinities := nodeAffinity(orLabels...)
119
120
marshal, err := yaml.Marshal(affinities)
121
if err != nil {
122
return nil, err
123
}
124
125
return marshal, nil
126
}
127
128
func WithTolerationWorkspaceComponentNotReadyYaml(ctx *common.RenderContext) ([]byte, error) {
129
tolerations := common.WithTolerationWorkspaceComponentNotReady(ctx)
130
131
marshal, err := yaml.Marshal(tolerations)
132
if err != nil {
133
return nil, err
134
}
135
136
return marshal, nil
137
}
138
139
func nodeAffinity(orLabels ...string) *corev1.Affinity {
140
var terms []corev1.NodeSelectorTerm
141
for _, lbl := range orLabels {
142
terms = append(terms, corev1.NodeSelectorTerm{
143
MatchExpressions: []corev1.NodeSelectorRequirement{
144
{
145
Key: lbl,
146
Operator: corev1.NodeSelectorOpExists,
147
},
148
},
149
})
150
}
151
152
return &corev1.Affinity{
153
NodeAffinity: &corev1.NodeAffinity{
154
RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
155
NodeSelectorTerms: terms,
156
},
157
},
158
}
159
}
160
161
func ImagePullSecrets(key string, ctx *common.RenderContext) string {
162
if len(ctx.Config.ImagePullSecrets) > 0 {
163
var pullSecrets []string
164
for _, i := range ctx.Config.ImagePullSecrets {
165
pullSecrets = append(pullSecrets, i.Name)
166
}
167
168
return KeyValueArray(key, pullSecrets)
169
}
170
171
// Nothing to be set
172
return ""
173
}
174
175
// ImportTemplate allows for Helm charts to be imported into the installer manifest
176
func ImportTemplate(chart *charts.Chart, templateCfg TemplateConfig, pkgConfig PkgConfig) common.HelmFunc {
177
return func(cfg *common.RenderContext) (r []string, err error) {
178
defer func() {
179
if err != nil {
180
err = fmt.Errorf("cannot import template %s: %w", chart.Name, err)
181
}
182
}()
183
184
helmConfig, err := pkgConfig(cfg)
185
if err != nil {
186
return nil, err
187
}
188
189
if !helmConfig.Enabled {
190
return nil, nil
191
}
192
193
dir, err := writeCharts(chart)
194
if err != nil {
195
return nil, err
196
}
197
198
settings := SettingsFactory(
199
&Config{
200
Debug: false,
201
Name: chart.Name,
202
Namespace: cfg.Namespace,
203
},
204
dir,
205
helmConfig.Values,
206
)
207
208
if _, err := os.Stat(filepath.Join(dir, "charts")); err != nil {
209
err = installDependencies(settings)
210
if err != nil {
211
return nil, err
212
}
213
}
214
215
client := action.NewInstall(settings.ActionConfig)
216
client.DryRun = true
217
client.ReleaseName = "RELEASE-NAME"
218
client.Replace = true // Skip the name check
219
client.ClientOnly = true
220
if templateCfg.Namespace == "" {
221
client.Namespace = cfg.Namespace
222
} else {
223
client.Namespace = templateCfg.Namespace
224
}
225
226
rel, err := runInstall(settings, client)
227
if err != nil {
228
return nil, err
229
}
230
if rel == nil {
231
return nil, fmt.Errorf("release for %s generated an empty value", settings.Config.Name)
232
}
233
234
// Fetch any additional Kubernetes files that need applying
235
var templates []string
236
for _, obj := range chart.AdditionalFiles {
237
b, err := chart.Content.ReadFile(obj)
238
if err != nil {
239
return nil, err
240
}
241
templates = append(templates, string(b))
242
}
243
244
return append(templates, rel.Manifest), nil
245
}
246
}
247
248
// CustomizeAnnotation check for customized annotations and output in Helm format
249
func CustomizeAnnotation(registryValues []string, prefix string, ctx *common.RenderContext, component string, typeMeta metav1.TypeMeta, existingAnnotations ...func() map[string]string) []string {
250
annotations := common.CustomizeAnnotation(ctx, component, typeMeta, existingAnnotations...)
251
if len(annotations) > 0 {
252
for k, v := range annotations {
253
// Escape any dots in the keys so they're not expanded
254
key := strings.Replace(k, ".", "\\.", -1)
255
registryValues = append(registryValues, KeyValue(fmt.Sprintf("%s.%s", prefix, key), v))
256
}
257
}
258
259
return registryValues
260
}
261
262
// CustomizeLabel check for customized labels and output in Helm format - also removes the default labels, which conflict with Helm
263
func CustomizeLabel(registryValues []string, prefix string, ctx *common.RenderContext, component string, typeMeta metav1.TypeMeta, existingLabels ...func() map[string]string) []string {
264
labels := common.CustomizeLabel(ctx, component, typeMeta, existingLabels...)
265
266
// Remove the default labels
267
for k := range common.DefaultLabels(component) {
268
delete(labels, k)
269
}
270
271
if len(labels) > 0 {
272
for k, v := range labels {
273
// Escape any dots in the keys so they're not expanded
274
key := strings.Replace(k, ".", "\\.", -1)
275
registryValues = append(registryValues, KeyValue(fmt.Sprintf("%s.%s", prefix, key), v))
276
}
277
}
278
279
return registryValues
280
}
281
282
// CustomizeEnvvar check for customized envvars and output in Helm format - assumes name/value only
283
func CustomizeEnvvar(registryValues []string, prefix string, ctx *common.RenderContext, component string, existingEnvvars ...[]corev1.EnvVar) []string {
284
// Helm is unlikely to have any existing envvars, so treat them as optional
285
envvars := common.CustomizeEnvvar(ctx, component, func() []corev1.EnvVar {
286
envs := make([]corev1.EnvVar, 0)
287
288
for _, e := range existingEnvvars {
289
envs = append(envs, e...)
290
}
291
292
return envs
293
}())
294
295
if len(envvars) > 0 {
296
for k, v := range envvars {
297
registryValues = append(registryValues, KeyValue(fmt.Sprintf("%s[%d].name", prefix, k), v.Name))
298
registryValues = append(registryValues, KeyValue(fmt.Sprintf("%s[%d].value", prefix, k), v.Value))
299
}
300
}
301
302
return registryValues
303
}
304
305