Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/install/installer/pkg/common/common.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 common
6
7
import (
8
"crypto/sha256"
9
"encoding/json"
10
"fmt"
11
"io"
12
"math/rand"
13
"sort"
14
"strconv"
15
"strings"
16
17
"github.com/gitpod-io/gitpod/common-go/baseserver"
18
config "github.com/gitpod-io/gitpod/installer/pkg/config/v1"
19
"github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental"
20
21
appsv1 "k8s.io/api/apps/v1"
22
corev1 "k8s.io/api/core/v1"
23
"k8s.io/apimachinery/pkg/api/resource"
24
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25
"k8s.io/apimachinery/pkg/runtime"
26
"k8s.io/apimachinery/pkg/util/intstr"
27
"k8s.io/utils/pointer"
28
"sigs.k8s.io/yaml"
29
)
30
31
// getProxyServerEnvvar get the proxy server envvars in both upper and lowercase form for maximum compatiblity
32
func getProxyServerEnvvar(cfg *config.Config, envvarName string, key string) []corev1.EnvVar {
33
env := corev1.EnvVar{
34
Name: strings.ToUpper(envvarName),
35
ValueFrom: &corev1.EnvVarSource{
36
SecretKeyRef: &corev1.SecretKeySelector{
37
LocalObjectReference: corev1.LocalObjectReference{
38
Name: cfg.HTTPProxy.Name,
39
},
40
Key: key,
41
Optional: pointer.Bool(true),
42
},
43
},
44
}
45
46
return []corev1.EnvVar{
47
env,
48
func() corev1.EnvVar {
49
envLower := env.DeepCopy()
50
envLower.Name = strings.ToLower(envvarName)
51
52
return *envLower
53
}(),
54
}
55
}
56
57
func DefaultLabels(component string) map[string]string {
58
return map[string]string{
59
"app": "gitpod",
60
"component": component,
61
}
62
}
63
64
func DefaultLabelSelector(component string) string {
65
labels := DefaultLabels(component)
66
labelKeys := []string{}
67
// get keys of label and sort them
68
for k := range labels {
69
labelKeys = append(labelKeys, k)
70
}
71
results := []string{}
72
sort.Strings(labelKeys)
73
for _, key := range labelKeys {
74
results = append(results, fmt.Sprintf("%s=%s", key, labels[key]))
75
}
76
return strings.Join(results, ",")
77
}
78
79
func MergeEnv(envs ...[]corev1.EnvVar) (res []corev1.EnvVar) {
80
for _, e := range envs {
81
res = append(res, e...)
82
}
83
return
84
}
85
86
func ProxyEnv(cfg *config.Config) []corev1.EnvVar {
87
if cfg.HTTPProxy == nil {
88
return []corev1.EnvVar{}
89
}
90
91
// The hard-coded values are the gRPC service names and the licence server
92
noProxyValue := "ws-manager,wsdaemon,$(CUSTOM_NO_PROXY)"
93
94
return MergeEnv(
95
getProxyServerEnvvar(cfg, "HTTP_PROXY", "httpProxy"),
96
getProxyServerEnvvar(cfg, "HTTPS_PROXY", "httpsProxy"),
97
getProxyServerEnvvar(cfg, "CUSTOM_NO_PROXY", "noProxy"),
98
[]corev1.EnvVar{
99
// This must come after the CUSTOM_NO_PROXY definition
100
{Name: "NO_PROXY", Value: noProxyValue},
101
{Name: "no_proxy", Value: noProxyValue},
102
},
103
)
104
}
105
106
func DefaultEnv(cfg *config.Config) []corev1.EnvVar {
107
logLevel := "info"
108
if cfg.Observability.LogLevel != "" {
109
logLevel = string(cfg.Observability.LogLevel)
110
}
111
112
return MergeEnv(
113
[]corev1.EnvVar{
114
{Name: "GITPOD_DOMAIN", Value: cfg.Domain},
115
{Name: "GITPOD_INSTALLATION_SHORTNAME", Value: cfg.Metadata.InstallationShortname},
116
{Name: "GITPOD_REGION", Value: cfg.Metadata.Region},
117
{Name: "HOST_URL", Value: "https://" + cfg.Domain},
118
{Name: "KUBE_NAMESPACE", ValueFrom: &corev1.EnvVarSource{
119
FieldRef: &corev1.ObjectFieldSelector{
120
FieldPath: "metadata.namespace",
121
},
122
}},
123
{Name: "KUBE_DOMAIN", Value: "svc.cluster.local"},
124
{Name: "LOG_LEVEL", Value: strings.ToLower(logLevel)},
125
// TODO(gpl): This is our bandaid for https:://tldr.fail
126
// See these issues for details:
127
// - https://linear.app/gitpod/issue/CLC-1264/investigate-public-api-server-connectivity-issues-during-sso-login#comment-f2daa302
128
// - https://linear.app/gitpod/issue/CLC-1067/go-upgrade-from-123x-to-124x-once-available for details
129
{Name: "GODEBUG", Value: "tlsmlkem=0"},
130
},
131
ProxyEnv(cfg),
132
)
133
}
134
135
func WorkspaceTracingEnv(context *RenderContext, component string) (res []corev1.EnvVar) {
136
var tracing *experimental.Tracing
137
138
_ = context.WithExperimental(func(cfg *experimental.Config) error {
139
if cfg.Workspace != nil {
140
tracing = cfg.Workspace.Tracing
141
}
142
return nil
143
})
144
145
return tracingEnv(context, component, tracing)
146
}
147
148
func WebappTracingEnv(context *RenderContext, component string) (res []corev1.EnvVar) {
149
var tracing *experimental.Tracing
150
151
_ = context.WithExperimental(func(cfg *experimental.Config) error {
152
if cfg.WebApp != nil {
153
tracing = cfg.WebApp.Tracing
154
}
155
return nil
156
})
157
158
return tracingEnv(context, component, tracing)
159
}
160
161
func tracingEnv(context *RenderContext, component string, tracing *experimental.Tracing) (res []corev1.EnvVar) {
162
// For OpenTelemetry (OTEL) environment variable specification, see https://opentelemetry.io/docs/reference/specification/protocol/exporter/
163
164
if context.Config.Observability.Tracing == nil {
165
res = append(res, corev1.EnvVar{Name: "JAEGER_DISABLED", Value: "true"})
166
res = append(res, corev1.EnvVar{Name: "OTEL_SDK_DISABLED", Value: "true"})
167
return
168
}
169
170
if ep := context.Config.Observability.Tracing.Endpoint; ep != nil {
171
res = append(res, corev1.EnvVar{Name: "JAEGER_ENDPOINT", Value: *ep})
172
res = append(res, corev1.EnvVar{Name: "OTEL_EXPORTER_OTLP_ENDPOINT", Value: *ep})
173
} else if v := context.Config.Observability.Tracing.AgentHost; v != nil {
174
res = append(res, corev1.EnvVar{Name: "JAEGER_AGENT_HOST", Value: *v})
175
} else {
176
// TODO(cw): think about proper error handling here.
177
// Returning an error would be the appropriate thing to do,
178
// but would make env var composition more cumbersome.
179
}
180
181
if context.Config.Observability.Tracing.SecretName != nil {
182
res = append(res, corev1.EnvVar{
183
Name: "JAEGER_USER",
184
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
185
LocalObjectReference: corev1.LocalObjectReference{Name: *context.Config.Observability.Tracing.SecretName},
186
Key: "JAEGER_USER",
187
}},
188
})
189
190
res = append(res, corev1.EnvVar{
191
Name: "JAEGER_PASSWORD",
192
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
193
LocalObjectReference: corev1.LocalObjectReference{Name: *context.Config.Observability.Tracing.SecretName},
194
Key: "JAEGER_PASSWORD",
195
}},
196
})
197
}
198
199
res = append(res, corev1.EnvVar{Name: "JAEGER_SERVICE_NAME", Value: component})
200
res = append(res, corev1.EnvVar{Name: "OTEL_SERVICE_NAME", Value: component})
201
202
jaegerTags := []string{}
203
if context.Config.Metadata.InstallationShortname != "" {
204
jaegerTags = append(jaegerTags, fmt.Sprintf("cluster=%v", context.Config.Metadata.InstallationShortname))
205
}
206
207
if context.Config.Metadata.Region != "" {
208
jaegerTags = append(jaegerTags, fmt.Sprintf("region=%v", context.Config.Metadata.Region))
209
}
210
211
if len(jaegerTags) > 0 {
212
res = append(res,
213
corev1.EnvVar{Name: "JAEGER_TAGS", Value: strings.Join(jaegerTags, ",")},
214
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable
215
corev1.EnvVar{Name: "OTEL_RESOURCE_ATTRIBUTES", Value: strings.Join(jaegerTags, ",")},
216
)
217
}
218
219
samplerType := experimental.TracingSampleTypeConst
220
samplerParam := "1"
221
222
if tracing != nil {
223
if tracing.SamplerType != nil {
224
samplerType = *tracing.SamplerType
225
}
226
if tracing.SamplerParam != nil {
227
samplerParam = strconv.FormatFloat(*tracing.SamplerParam, 'f', -1, 64)
228
}
229
}
230
231
res = append(res,
232
corev1.EnvVar{Name: "JAEGER_SAMPLER_TYPE", Value: string(samplerType)},
233
corev1.EnvVar{Name: "JAEGER_SAMPLER_PARAM", Value: samplerParam},
234
235
corev1.EnvVar{Name: "OTEL_TRACES_SAMPLER", Value: string(samplerType)},
236
corev1.EnvVar{Name: "OTEL_TRACES_SAMPLER_ARG", Value: samplerParam},
237
)
238
239
return
240
}
241
242
func AnalyticsEnv(cfg *config.Config) (res []corev1.EnvVar) {
243
if cfg.Analytics == nil {
244
return
245
}
246
247
return []corev1.EnvVar{{
248
Name: "GITPOD_ANALYTICS_WRITER",
249
Value: cfg.Analytics.Writer,
250
}, {
251
Name: "GITPOD_ANALYTICS_SEGMENT_KEY",
252
Value: cfg.Analytics.SegmentKey,
253
}, {
254
Name: "GITPOD_ANALYTICS_SEGMENT_ENDPOINT",
255
Value: cfg.Analytics.SegmentEndpoint,
256
}}
257
}
258
259
func DatabaseEnv(cfg *config.Config) (res []corev1.EnvVar) {
260
var (
261
secretRef corev1.LocalObjectReference
262
envvars []corev1.EnvVar
263
)
264
265
if pointer.BoolDeref(cfg.Database.InCluster, false) {
266
secretRef = corev1.LocalObjectReference{Name: InClusterDbSecret}
267
envvars = append(envvars,
268
corev1.EnvVar{
269
Name: "DB_HOST",
270
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
271
LocalObjectReference: secretRef,
272
Key: "host",
273
}},
274
},
275
corev1.EnvVar{
276
Name: "DB_PORT",
277
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
278
LocalObjectReference: secretRef,
279
Key: "port",
280
}},
281
},
282
)
283
} else if cfg.Database.External != nil && cfg.Database.External.Certificate.Name != "" {
284
// External DB
285
secretRef = corev1.LocalObjectReference{Name: cfg.Database.External.Certificate.Name}
286
envvars = append(envvars,
287
corev1.EnvVar{
288
Name: "DB_HOST",
289
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
290
LocalObjectReference: secretRef,
291
Key: "host",
292
}},
293
},
294
corev1.EnvVar{
295
Name: "DB_PORT",
296
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
297
LocalObjectReference: secretRef,
298
Key: "port",
299
}},
300
},
301
)
302
} else if cfg.Database.CloudSQL != nil && cfg.Database.CloudSQL.ServiceAccount.Name != "" {
303
// GCP
304
secretRef = corev1.LocalObjectReference{Name: cfg.Database.CloudSQL.ServiceAccount.Name}
305
envvars = append(envvars,
306
corev1.EnvVar{
307
Name: "DB_HOST",
308
Value: "cloudsqlproxy",
309
},
310
corev1.EnvVar{
311
Name: "DB_PORT",
312
Value: "3306",
313
},
314
)
315
} else {
316
panic("invalid database configuration")
317
}
318
319
envvars = append(envvars,
320
corev1.EnvVar{
321
Name: "DB_PASSWORD",
322
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
323
LocalObjectReference: secretRef,
324
Key: "password",
325
}},
326
},
327
corev1.EnvVar{
328
Name: "DB_USERNAME",
329
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
330
LocalObjectReference: secretRef,
331
Key: "username",
332
}},
333
},
334
corev1.EnvVar{
335
Name: "DB_ENCRYPTION_KEYS",
336
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
337
LocalObjectReference: secretRef,
338
Key: "encryptionKeys",
339
}},
340
},
341
)
342
343
if cfg.Database.SSL != nil && cfg.Database.SSL.CaCert != nil {
344
secretRef = corev1.LocalObjectReference{Name: cfg.Database.SSL.CaCert.Name}
345
envvars = append(envvars, corev1.EnvVar{
346
Name: DBCaCertEnvVarName,
347
ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{
348
LocalObjectReference: secretRef,
349
Key: DBCaFileName,
350
}},
351
})
352
}
353
354
return envvars
355
}
356
357
func DatabaseEnvSecret(cfg config.Config) (corev1.Volume, corev1.VolumeMount, string) {
358
var secretName string
359
360
if pointer.BoolDeref(cfg.Database.InCluster, false) {
361
secretName = InClusterDbSecret
362
} else if cfg.Database.External != nil && cfg.Database.External.Certificate.Name != "" {
363
// External DB
364
secretName = cfg.Database.External.Certificate.Name
365
366
} else if cfg.Database.CloudSQL != nil && cfg.Database.CloudSQL.ServiceAccount.Name != "" {
367
// GCP
368
secretName = cfg.Database.CloudSQL.ServiceAccount.Name
369
370
} else {
371
panic("invalid database configuration")
372
}
373
374
volume := corev1.Volume{
375
Name: "database-config",
376
VolumeSource: corev1.VolumeSource{
377
Secret: &corev1.SecretVolumeSource{
378
SecretName: secretName,
379
},
380
},
381
}
382
383
mount := corev1.VolumeMount{
384
Name: "database-config",
385
MountPath: DatabaseConfigMountPath,
386
ReadOnly: true,
387
}
388
389
return volume, mount, DatabaseConfigMountPath
390
}
391
392
func ConfigcatEnv(ctx *RenderContext) []corev1.EnvVar {
393
var sdkKey string
394
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
395
if cfg.WebApp != nil && cfg.WebApp.ConfigcatKey != "" {
396
sdkKey = cfg.WebApp.ConfigcatKey
397
}
398
return nil
399
})
400
401
if sdkKey == "" {
402
return nil
403
}
404
405
return []corev1.EnvVar{
406
{
407
Name: "CONFIGCAT_SDK_KEY",
408
Value: "gitpod",
409
},
410
{
411
Name: "CONFIGCAT_BASE_URL",
412
Value: ClusterURL("http", ProxyComponent, ctx.Namespace, ProxyConfigcatPort) + "/configcat",
413
},
414
}
415
}
416
417
func ConfigcatEnvOutOfCluster(ctx *RenderContext) []corev1.EnvVar {
418
return []corev1.EnvVar{
419
{
420
Name: "CONFIGCAT_SDK_KEY",
421
Value: "gitpod",
422
},
423
{
424
Name: "CONFIGCAT_BASE_URL",
425
Value: fmt.Sprintf("https://%s/configcat", ctx.Config.Domain),
426
},
427
}
428
}
429
430
func ConfigcatProxyEnv(ctx *RenderContext) []corev1.EnvVar {
431
var (
432
sdkKey string
433
baseUrl string
434
pollInterval string
435
fromConfigMap string
436
)
437
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
438
if cfg.WebApp != nil && cfg.WebApp.ConfigcatKey != "" {
439
sdkKey = cfg.WebApp.ConfigcatKey
440
}
441
if cfg.WebApp != nil && cfg.WebApp.ProxyConfig != nil && cfg.WebApp.ProxyConfig.Configcat != nil {
442
baseUrl = cfg.WebApp.ProxyConfig.Configcat.BaseUrl
443
pollInterval = cfg.WebApp.ProxyConfig.Configcat.PollInterval
444
fromConfigMap = cfg.WebApp.ProxyConfig.Configcat.FromConfigMap
445
}
446
return nil
447
})
448
449
if sdkKey == "" {
450
return nil
451
}
452
envs := []corev1.EnvVar{
453
{
454
Name: "CONFIGCAT_SDK_KEY",
455
Value: sdkKey,
456
},
457
}
458
459
if fromConfigMap != "" {
460
envs = append(envs,
461
corev1.EnvVar{
462
Name: "CONFIGCAT_DIR",
463
Value: "/data/configcat/",
464
},
465
)
466
} else {
467
envs = append(envs,
468
corev1.EnvVar{
469
Name: "CONFIGCAT_BASE_URL",
470
Value: baseUrl,
471
},
472
corev1.EnvVar{
473
Name: "CONFIGCAT_POLL_INTERVAL",
474
Value: pollInterval,
475
},
476
)
477
}
478
479
return envs
480
}
481
482
func DatabaseWaiterContainer(ctx *RenderContext) *corev1.Container {
483
return databaseWaiterContainer(ctx, false)
484
}
485
486
func DatabaseMigrationWaiterContainer(ctx *RenderContext) *corev1.Container {
487
return databaseWaiterContainer(ctx, true)
488
}
489
490
func databaseWaiterContainer(ctx *RenderContext, doMigrationCheck bool) *corev1.Container {
491
args := []string{
492
"-v",
493
"database",
494
}
495
if doMigrationCheck {
496
args = append(args, "--migration-check", "true")
497
}
498
return &corev1.Container{
499
Name: "database-waiter",
500
Image: ctx.ImageName(ctx.Config.Repository, "service-waiter", ctx.VersionManifest.Components.ServiceWaiter.Version),
501
Args: args,
502
SecurityContext: &corev1.SecurityContext{
503
Privileged: pointer.Bool(false),
504
AllowPrivilegeEscalation: pointer.Bool(false),
505
RunAsUser: pointer.Int64(31001),
506
},
507
Env: MergeEnv(
508
DatabaseEnv(&ctx.Config),
509
ProxyEnv(&ctx.Config),
510
),
511
}
512
}
513
514
func RedisWaiterContainer(ctx *RenderContext) *corev1.Container {
515
return &corev1.Container{
516
Name: "redis-waiter",
517
Image: ctx.ImageName(ctx.Config.Repository, "service-waiter", ctx.VersionManifest.Components.ServiceWaiter.Version),
518
Args: []string{
519
"-v",
520
"redis",
521
},
522
SecurityContext: &corev1.SecurityContext{
523
Privileged: pointer.Bool(false),
524
AllowPrivilegeEscalation: pointer.Bool(false),
525
RunAsUser: pointer.Int64(31001),
526
},
527
}
528
}
529
530
// ServerComponentWaiterContainer is the container used to wait for the deployment/server to be ready
531
// it requires
532
// - pods list access to the cluster
533
func ServerComponentWaiterContainer(ctx *RenderContext) *corev1.Container {
534
image := ctx.ImageName(ctx.Config.Repository, ServerComponent, ctx.VersionManifest.Components.Server.Version)
535
return componentWaiterContainer(ctx, ServerComponent, DefaultLabelSelector(ServerComponent), image)
536
}
537
538
// PublicApiServerComponentWaiterContainer is the container used to wait for the deployment/public-api-server to be ready
539
// it requires
540
// - pods list access to the cluster
541
func PublicApiServerComponentWaiterContainer(ctx *RenderContext) *corev1.Container {
542
image := ctx.ImageName(ctx.Config.Repository, PublicApiComponent, ctx.VersionManifest.Components.PublicAPIServer.Version)
543
return componentWaiterContainer(ctx, PublicApiComponent, DefaultLabelSelector(PublicApiComponent), image)
544
}
545
546
func componentWaiterContainer(ctx *RenderContext, component, labels, image string) *corev1.Container {
547
return &corev1.Container{
548
Name: component + "-waiter",
549
Image: ctx.ImageName(ctx.Config.Repository, "service-waiter", ctx.VersionManifest.Components.ServiceWaiter.Version),
550
Args: []string{
551
"-v",
552
"component",
553
"--namespace",
554
ctx.Namespace,
555
"--component",
556
component,
557
"--labels",
558
labels,
559
"--image",
560
image,
561
},
562
SecurityContext: &corev1.SecurityContext{
563
Privileged: pointer.Bool(false),
564
AllowPrivilegeEscalation: pointer.Bool(false),
565
RunAsUser: pointer.Int64(31001),
566
},
567
Env: ConfigcatEnv(ctx),
568
}
569
}
570
571
func KubeRBACProxyContainer(ctx *RenderContext) *corev1.Container {
572
return KubeRBACProxyContainerWithConfig(ctx)
573
}
574
575
func KubeRBACProxyContainerWithConfig(ctx *RenderContext) *corev1.Container {
576
return &corev1.Container{
577
Name: "kube-rbac-proxy",
578
Image: ctx.ImageName(ThirdPartyContainerRepo(ctx.Config.Repository, KubeRBACProxyRepo), KubeRBACProxyImage, KubeRBACProxyTag),
579
Args: []string{
580
"--logtostderr",
581
fmt.Sprintf("--insecure-listen-address=[$(IP)]:%d", baseserver.BuiltinMetricsPort),
582
fmt.Sprintf("--upstream=http://127.0.0.1:%d/", baseserver.BuiltinMetricsPort),
583
"--http2-disable",
584
},
585
Ports: []corev1.ContainerPort{
586
{Name: baseserver.BuiltinMetricsPortName, ContainerPort: baseserver.BuiltinMetricsPort},
587
},
588
Env: MergeEnv(
589
[]corev1.EnvVar{
590
{
591
Name: "IP",
592
ValueFrom: &corev1.EnvVarSource{
593
FieldRef: &corev1.ObjectFieldSelector{
594
FieldPath: "status.podIP",
595
},
596
},
597
},
598
},
599
ProxyEnv(&ctx.Config),
600
),
601
Resources: corev1.ResourceRequirements{Requests: corev1.ResourceList{
602
corev1.ResourceCPU: resource.MustParse("1m"),
603
corev1.ResourceMemory: resource.MustParse("30Mi"),
604
}},
605
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
606
SecurityContext: &corev1.SecurityContext{
607
AllowPrivilegeEscalation: pointer.Bool(false),
608
RunAsUser: pointer.Int64(65532),
609
RunAsGroup: pointer.Int64(65532),
610
RunAsNonRoot: pointer.Bool(true),
611
},
612
}
613
}
614
615
func IsDatabaseMigrationDisabled(ctx *RenderContext) bool {
616
disableMigration := false
617
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
618
if cfg.WebApp != nil {
619
disableMigration = cfg.WebApp.DisableMigration
620
}
621
return nil
622
})
623
return disableMigration
624
}
625
626
func Replicas(ctx *RenderContext, component string) *int32 {
627
replicas := int32(1)
628
629
if ctx.Config.Components != nil && ctx.Config.Components.PodConfig[component] != nil {
630
if ctx.Config.Components.PodConfig[component].Replicas != nil {
631
replicas = *ctx.Config.Components.PodConfig[component].Replicas
632
}
633
}
634
635
return &replicas
636
}
637
638
func ResourceRequirements(ctx *RenderContext, component, containerName string, defaults corev1.ResourceRequirements) corev1.ResourceRequirements {
639
resources := defaults
640
641
if ctx.Config.Components != nil && ctx.Config.Components.PodConfig[component] != nil {
642
if ctx.Config.Components.PodConfig[component].Resources[containerName] != nil {
643
resources = *ctx.Config.Components.PodConfig[component].Resources[containerName]
644
}
645
}
646
647
return resources
648
}
649
650
// ObjectHash marshals the objects to YAML and produces a sha256 hash of the output.
651
// This function is useful for restarting pods when the config changes.
652
// Takes an error as argument to make calling it more conventient. If that error is not nil,
653
// it's passed right through
654
func ObjectHash(objs []runtime.Object, err error) (string, error) {
655
if err != nil {
656
return "", err
657
}
658
659
hash := sha256.New()
660
for _, o := range objs {
661
b, err := yaml.Marshal(o)
662
if err != nil {
663
return "", err
664
}
665
_, _ = hash.Write(b)
666
}
667
return fmt.Sprintf("%x", hash.Sum(nil)), nil
668
}
669
670
var (
671
TCPProtocol = func() *corev1.Protocol {
672
tcpProtocol := corev1.ProtocolTCP
673
return &tcpProtocol
674
}()
675
)
676
677
var DeploymentStrategy = appsv1.DeploymentStrategy{
678
Type: appsv1.RollingUpdateDeploymentStrategyType,
679
RollingUpdate: &appsv1.RollingUpdateDeployment{
680
MaxSurge: &intstr.IntOrString{IntVal: 1},
681
MaxUnavailable: &intstr.IntOrString{IntVal: 0},
682
},
683
}
684
685
// TODO(cw): find a better way to do this. Those values must exist in the appropriate places already.
686
var (
687
TypeMetaNamespace = metav1.TypeMeta{
688
APIVersion: "v1",
689
Kind: "Namespace",
690
}
691
TypeMetaStatefulSet = metav1.TypeMeta{
692
APIVersion: "apps/v1",
693
Kind: "StatefulSet",
694
}
695
TypeMetaConfigmap = metav1.TypeMeta{
696
APIVersion: "v1",
697
Kind: "ConfigMap",
698
}
699
TypeMetaServiceAccount = metav1.TypeMeta{
700
APIVersion: "v1",
701
Kind: "ServiceAccount",
702
}
703
TypeMetaPod = metav1.TypeMeta{
704
APIVersion: "v1",
705
Kind: "Pod",
706
}
707
TypeMetaDaemonset = metav1.TypeMeta{
708
APIVersion: "apps/v1",
709
Kind: "DaemonSet",
710
}
711
TypeMetaService = metav1.TypeMeta{
712
APIVersion: "v1",
713
Kind: "Service",
714
}
715
TypeMetaClusterRole = metav1.TypeMeta{
716
APIVersion: "rbac.authorization.k8s.io/v1",
717
Kind: "ClusterRole",
718
}
719
TypeMetaClusterRoleBinding = metav1.TypeMeta{
720
APIVersion: "rbac.authorization.k8s.io/v1",
721
Kind: "ClusterRoleBinding",
722
}
723
TypeMetaRoleBinding = metav1.TypeMeta{
724
APIVersion: "rbac.authorization.k8s.io/v1",
725
Kind: "RoleBinding",
726
}
727
TypeMetaRole = metav1.TypeMeta{
728
APIVersion: "rbac.authorization.k8s.io/v1",
729
Kind: "Role",
730
}
731
TypeMetaNetworkPolicy = metav1.TypeMeta{
732
APIVersion: "networking.k8s.io/v1",
733
Kind: "NetworkPolicy",
734
}
735
TypeMetaDeployment = metav1.TypeMeta{
736
APIVersion: "apps/v1",
737
Kind: "Deployment",
738
}
739
TypeMetaCertificate = metav1.TypeMeta{
740
APIVersion: "cert-manager.io/v1",
741
Kind: "Certificate",
742
}
743
TypeMetaCertificateIssuer = metav1.TypeMeta{
744
APIVersion: "cert-manager.io/v1",
745
Kind: "Issuer",
746
}
747
TypeMetaSecret = metav1.TypeMeta{
748
APIVersion: "v1",
749
Kind: "Secret",
750
}
751
TypeMetaResourceQuota = metav1.TypeMeta{
752
APIVersion: "v1",
753
Kind: "ResourceQuota",
754
}
755
TypeMetaBatchJob = metav1.TypeMeta{
756
APIVersion: "batch/v1",
757
Kind: "Job",
758
}
759
TypeMetaBatchCronJob = metav1.TypeMeta{
760
APIVersion: "batch/v1",
761
Kind: "CronJob",
762
}
763
TypeMetaCertificateClusterIssuer = metav1.TypeMeta{
764
APIVersion: "cert-manager.io/v1",
765
Kind: "ClusterIssuer",
766
}
767
TypeMetaBundle = metav1.TypeMeta{
768
APIVersion: "trust.cert-manager.io/v1alpha1",
769
Kind: "Bundle",
770
}
771
TypePodDisruptionBudget = metav1.TypeMeta{
772
APIVersion: "policy/v1",
773
Kind: "PodDisruptionBudget",
774
}
775
)
776
777
// validCookieChars contains all characters which may occur in an HTTP Cookie value (unicode \u0021 through \u007E),
778
// without the characters , ; and / ... I did not find more details about permissible characters in RFC2965, so I took
779
// this list of permissible chars from Wikipedia.
780
//
781
// The tokens we produce here (e.g. owner token or CLI API token) are likely placed in cookies or transmitted via HTTP.
782
// To make the lifes of downstream users easier we'll try and play nice here w.r.t. to the characters used.
783
var validCookieChars = []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.")
784
785
// RandomString produces a cryptographically secure random string of length N.
786
// The string contains alphanumeric characters and _ (underscore), - (dash) and . (dot)
787
func RandomString(length int) (string, error) {
788
b := make([]byte, length)
789
n, err := rand.Read(b)
790
if err != nil {
791
return "", err
792
}
793
if n != length {
794
return "", io.ErrShortWrite
795
}
796
797
lrsc := len(validCookieChars)
798
for i, c := range b {
799
b[i] = validCookieChars[int(c)%lrsc]
800
}
801
return string(b), nil
802
}
803
804
// ThirdPartyContainerRepo returns the container registry to use for third-party containers.
805
// If config registry is set to the Gitpod registry, the third-party registry is returned. If
806
// config registry is different, that repository is returned and deployment expected to mirror
807
// the images to their registry
808
func ThirdPartyContainerRepo(configRegistry string, thirdPartyRegistry string) string {
809
configRegistry = strings.TrimSuffix(configRegistry, "/")
810
811
if configRegistry == GitpodContainerRegistry {
812
return thirdPartyRegistry
813
}
814
815
return configRegistry
816
}
817
818
// ToJSONString returns the serialized JSON string of an object
819
func ToJSONString(input interface{}) ([]byte, error) {
820
return json.MarshalIndent(input, "", " ")
821
}
822
823
func NodeNameEnv(context *RenderContext) []corev1.EnvVar {
824
return []corev1.EnvVar{{
825
Name: "NODENAME",
826
ValueFrom: &corev1.EnvVarSource{
827
FieldRef: &corev1.ObjectFieldSelector{FieldPath: "spec.nodeName"},
828
},
829
}}
830
}
831
832
func NodeIPEnv(context *RenderContext) []corev1.EnvVar {
833
return []corev1.EnvVar{{
834
Name: "NODE_IP",
835
ValueFrom: &corev1.EnvVarSource{
836
FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.hostIP"},
837
},
838
}}
839
}
840
841
// ExperimentalWebappConfig extracts webapp experimental config from the render context.
842
// When the experimental config is not defined, the result will be nil.
843
func ExperimentalWebappConfig(ctx *RenderContext) *experimental.WebAppConfig {
844
var experimentalCfg *experimental.Config
845
_ = ctx.WithExperimental(func(ucfg *experimental.Config) error {
846
experimentalCfg = ucfg
847
return nil
848
})
849
850
if experimentalCfg == nil || experimentalCfg.WebApp == nil {
851
return nil
852
}
853
854
return experimentalCfg.WebApp
855
}
856
857
// WithLocalWsManager returns true if the installed application cluster should connect to a local ws-manager
858
func WithLocalWsManager(ctx *RenderContext) bool {
859
return ctx.Config.Kind == config.InstallationFull
860
}
861
862
func DaemonSetRolloutStrategy() appsv1.DaemonSetUpdateStrategy {
863
maxUnavailable := intstr.Parse("20%")
864
865
return appsv1.DaemonSetUpdateStrategy{
866
Type: appsv1.RollingUpdateDaemonSetStrategyType,
867
RollingUpdate: &appsv1.RollingUpdateDaemonSet{
868
MaxUnavailable: &maxUnavailable,
869
},
870
}
871
}
872
873