Path: blob/main/install/installer/pkg/common/common.go
2500 views
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.1// Licensed under the GNU Affero General Public License (AGPL).2// See License.AGPL.txt in the project root for license information.34package common56import (7"crypto/sha256"8"encoding/json"9"fmt"10"io"11"math/rand"12"sort"13"strconv"14"strings"1516"github.com/gitpod-io/gitpod/common-go/baseserver"17config "github.com/gitpod-io/gitpod/installer/pkg/config/v1"18"github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental"1920appsv1 "k8s.io/api/apps/v1"21corev1 "k8s.io/api/core/v1"22"k8s.io/apimachinery/pkg/api/resource"23metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"24"k8s.io/apimachinery/pkg/runtime"25"k8s.io/apimachinery/pkg/util/intstr"26"k8s.io/utils/pointer"27"sigs.k8s.io/yaml"28)2930// getProxyServerEnvvar get the proxy server envvars in both upper and lowercase form for maximum compatiblity31func getProxyServerEnvvar(cfg *config.Config, envvarName string, key string) []corev1.EnvVar {32env := corev1.EnvVar{33Name: strings.ToUpper(envvarName),34ValueFrom: &corev1.EnvVarSource{35SecretKeyRef: &corev1.SecretKeySelector{36LocalObjectReference: corev1.LocalObjectReference{37Name: cfg.HTTPProxy.Name,38},39Key: key,40Optional: pointer.Bool(true),41},42},43}4445return []corev1.EnvVar{46env,47func() corev1.EnvVar {48envLower := env.DeepCopy()49envLower.Name = strings.ToLower(envvarName)5051return *envLower52}(),53}54}5556func DefaultLabels(component string) map[string]string {57return map[string]string{58"app": "gitpod",59"component": component,60}61}6263func DefaultLabelSelector(component string) string {64labels := DefaultLabels(component)65labelKeys := []string{}66// get keys of label and sort them67for k := range labels {68labelKeys = append(labelKeys, k)69}70results := []string{}71sort.Strings(labelKeys)72for _, key := range labelKeys {73results = append(results, fmt.Sprintf("%s=%s", key, labels[key]))74}75return strings.Join(results, ",")76}7778func MergeEnv(envs ...[]corev1.EnvVar) (res []corev1.EnvVar) {79for _, e := range envs {80res = append(res, e...)81}82return83}8485func ProxyEnv(cfg *config.Config) []corev1.EnvVar {86if cfg.HTTPProxy == nil {87return []corev1.EnvVar{}88}8990// The hard-coded values are the gRPC service names and the licence server91noProxyValue := "ws-manager,wsdaemon,$(CUSTOM_NO_PROXY)"9293return MergeEnv(94getProxyServerEnvvar(cfg, "HTTP_PROXY", "httpProxy"),95getProxyServerEnvvar(cfg, "HTTPS_PROXY", "httpsProxy"),96getProxyServerEnvvar(cfg, "CUSTOM_NO_PROXY", "noProxy"),97[]corev1.EnvVar{98// This must come after the CUSTOM_NO_PROXY definition99{Name: "NO_PROXY", Value: noProxyValue},100{Name: "no_proxy", Value: noProxyValue},101},102)103}104105func DefaultEnv(cfg *config.Config) []corev1.EnvVar {106logLevel := "info"107if cfg.Observability.LogLevel != "" {108logLevel = string(cfg.Observability.LogLevel)109}110111return MergeEnv(112[]corev1.EnvVar{113{Name: "GITPOD_DOMAIN", Value: cfg.Domain},114{Name: "GITPOD_INSTALLATION_SHORTNAME", Value: cfg.Metadata.InstallationShortname},115{Name: "GITPOD_REGION", Value: cfg.Metadata.Region},116{Name: "HOST_URL", Value: "https://" + cfg.Domain},117{Name: "KUBE_NAMESPACE", ValueFrom: &corev1.EnvVarSource{118FieldRef: &corev1.ObjectFieldSelector{119FieldPath: "metadata.namespace",120},121}},122{Name: "KUBE_DOMAIN", Value: "svc.cluster.local"},123{Name: "LOG_LEVEL", Value: strings.ToLower(logLevel)},124// TODO(gpl): This is our bandaid for https:://tldr.fail125// See these issues for details:126// - https://linear.app/gitpod/issue/CLC-1264/investigate-public-api-server-connectivity-issues-during-sso-login#comment-f2daa302127// - https://linear.app/gitpod/issue/CLC-1067/go-upgrade-from-123x-to-124x-once-available for details128{Name: "GODEBUG", Value: "tlsmlkem=0"},129},130ProxyEnv(cfg),131)132}133134func WorkspaceTracingEnv(context *RenderContext, component string) (res []corev1.EnvVar) {135var tracing *experimental.Tracing136137_ = context.WithExperimental(func(cfg *experimental.Config) error {138if cfg.Workspace != nil {139tracing = cfg.Workspace.Tracing140}141return nil142})143144return tracingEnv(context, component, tracing)145}146147func WebappTracingEnv(context *RenderContext, component string) (res []corev1.EnvVar) {148var tracing *experimental.Tracing149150_ = context.WithExperimental(func(cfg *experimental.Config) error {151if cfg.WebApp != nil {152tracing = cfg.WebApp.Tracing153}154return nil155})156157return tracingEnv(context, component, tracing)158}159160func tracingEnv(context *RenderContext, component string, tracing *experimental.Tracing) (res []corev1.EnvVar) {161// For OpenTelemetry (OTEL) environment variable specification, see https://opentelemetry.io/docs/reference/specification/protocol/exporter/162163if context.Config.Observability.Tracing == nil {164res = append(res, corev1.EnvVar{Name: "JAEGER_DISABLED", Value: "true"})165res = append(res, corev1.EnvVar{Name: "OTEL_SDK_DISABLED", Value: "true"})166return167}168169if ep := context.Config.Observability.Tracing.Endpoint; ep != nil {170res = append(res, corev1.EnvVar{Name: "JAEGER_ENDPOINT", Value: *ep})171res = append(res, corev1.EnvVar{Name: "OTEL_EXPORTER_OTLP_ENDPOINT", Value: *ep})172} else if v := context.Config.Observability.Tracing.AgentHost; v != nil {173res = append(res, corev1.EnvVar{Name: "JAEGER_AGENT_HOST", Value: *v})174} else {175// TODO(cw): think about proper error handling here.176// Returning an error would be the appropriate thing to do,177// but would make env var composition more cumbersome.178}179180if context.Config.Observability.Tracing.SecretName != nil {181res = append(res, corev1.EnvVar{182Name: "JAEGER_USER",183ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{184LocalObjectReference: corev1.LocalObjectReference{Name: *context.Config.Observability.Tracing.SecretName},185Key: "JAEGER_USER",186}},187})188189res = append(res, corev1.EnvVar{190Name: "JAEGER_PASSWORD",191ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{192LocalObjectReference: corev1.LocalObjectReference{Name: *context.Config.Observability.Tracing.SecretName},193Key: "JAEGER_PASSWORD",194}},195})196}197198res = append(res, corev1.EnvVar{Name: "JAEGER_SERVICE_NAME", Value: component})199res = append(res, corev1.EnvVar{Name: "OTEL_SERVICE_NAME", Value: component})200201jaegerTags := []string{}202if context.Config.Metadata.InstallationShortname != "" {203jaegerTags = append(jaegerTags, fmt.Sprintf("cluster=%v", context.Config.Metadata.InstallationShortname))204}205206if context.Config.Metadata.Region != "" {207jaegerTags = append(jaegerTags, fmt.Sprintf("region=%v", context.Config.Metadata.Region))208}209210if len(jaegerTags) > 0 {211res = append(res,212corev1.EnvVar{Name: "JAEGER_TAGS", Value: strings.Join(jaegerTags, ",")},213// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable214corev1.EnvVar{Name: "OTEL_RESOURCE_ATTRIBUTES", Value: strings.Join(jaegerTags, ",")},215)216}217218samplerType := experimental.TracingSampleTypeConst219samplerParam := "1"220221if tracing != nil {222if tracing.SamplerType != nil {223samplerType = *tracing.SamplerType224}225if tracing.SamplerParam != nil {226samplerParam = strconv.FormatFloat(*tracing.SamplerParam, 'f', -1, 64)227}228}229230res = append(res,231corev1.EnvVar{Name: "JAEGER_SAMPLER_TYPE", Value: string(samplerType)},232corev1.EnvVar{Name: "JAEGER_SAMPLER_PARAM", Value: samplerParam},233234corev1.EnvVar{Name: "OTEL_TRACES_SAMPLER", Value: string(samplerType)},235corev1.EnvVar{Name: "OTEL_TRACES_SAMPLER_ARG", Value: samplerParam},236)237238return239}240241func AnalyticsEnv(cfg *config.Config) (res []corev1.EnvVar) {242if cfg.Analytics == nil {243return244}245246return []corev1.EnvVar{{247Name: "GITPOD_ANALYTICS_WRITER",248Value: cfg.Analytics.Writer,249}, {250Name: "GITPOD_ANALYTICS_SEGMENT_KEY",251Value: cfg.Analytics.SegmentKey,252}, {253Name: "GITPOD_ANALYTICS_SEGMENT_ENDPOINT",254Value: cfg.Analytics.SegmentEndpoint,255}}256}257258func DatabaseEnv(cfg *config.Config) (res []corev1.EnvVar) {259var (260secretRef corev1.LocalObjectReference261envvars []corev1.EnvVar262)263264if pointer.BoolDeref(cfg.Database.InCluster, false) {265secretRef = corev1.LocalObjectReference{Name: InClusterDbSecret}266envvars = append(envvars,267corev1.EnvVar{268Name: "DB_HOST",269ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{270LocalObjectReference: secretRef,271Key: "host",272}},273},274corev1.EnvVar{275Name: "DB_PORT",276ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{277LocalObjectReference: secretRef,278Key: "port",279}},280},281)282} else if cfg.Database.External != nil && cfg.Database.External.Certificate.Name != "" {283// External DB284secretRef = corev1.LocalObjectReference{Name: cfg.Database.External.Certificate.Name}285envvars = append(envvars,286corev1.EnvVar{287Name: "DB_HOST",288ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{289LocalObjectReference: secretRef,290Key: "host",291}},292},293corev1.EnvVar{294Name: "DB_PORT",295ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{296LocalObjectReference: secretRef,297Key: "port",298}},299},300)301} else if cfg.Database.CloudSQL != nil && cfg.Database.CloudSQL.ServiceAccount.Name != "" {302// GCP303secretRef = corev1.LocalObjectReference{Name: cfg.Database.CloudSQL.ServiceAccount.Name}304envvars = append(envvars,305corev1.EnvVar{306Name: "DB_HOST",307Value: "cloudsqlproxy",308},309corev1.EnvVar{310Name: "DB_PORT",311Value: "3306",312},313)314} else {315panic("invalid database configuration")316}317318envvars = append(envvars,319corev1.EnvVar{320Name: "DB_PASSWORD",321ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{322LocalObjectReference: secretRef,323Key: "password",324}},325},326corev1.EnvVar{327Name: "DB_USERNAME",328ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{329LocalObjectReference: secretRef,330Key: "username",331}},332},333corev1.EnvVar{334Name: "DB_ENCRYPTION_KEYS",335ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{336LocalObjectReference: secretRef,337Key: "encryptionKeys",338}},339},340)341342if cfg.Database.SSL != nil && cfg.Database.SSL.CaCert != nil {343secretRef = corev1.LocalObjectReference{Name: cfg.Database.SSL.CaCert.Name}344envvars = append(envvars, corev1.EnvVar{345Name: DBCaCertEnvVarName,346ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{347LocalObjectReference: secretRef,348Key: DBCaFileName,349}},350})351}352353return envvars354}355356func DatabaseEnvSecret(cfg config.Config) (corev1.Volume, corev1.VolumeMount, string) {357var secretName string358359if pointer.BoolDeref(cfg.Database.InCluster, false) {360secretName = InClusterDbSecret361} else if cfg.Database.External != nil && cfg.Database.External.Certificate.Name != "" {362// External DB363secretName = cfg.Database.External.Certificate.Name364365} else if cfg.Database.CloudSQL != nil && cfg.Database.CloudSQL.ServiceAccount.Name != "" {366// GCP367secretName = cfg.Database.CloudSQL.ServiceAccount.Name368369} else {370panic("invalid database configuration")371}372373volume := corev1.Volume{374Name: "database-config",375VolumeSource: corev1.VolumeSource{376Secret: &corev1.SecretVolumeSource{377SecretName: secretName,378},379},380}381382mount := corev1.VolumeMount{383Name: "database-config",384MountPath: DatabaseConfigMountPath,385ReadOnly: true,386}387388return volume, mount, DatabaseConfigMountPath389}390391func ConfigcatEnv(ctx *RenderContext) []corev1.EnvVar {392var sdkKey string393_ = ctx.WithExperimental(func(cfg *experimental.Config) error {394if cfg.WebApp != nil && cfg.WebApp.ConfigcatKey != "" {395sdkKey = cfg.WebApp.ConfigcatKey396}397return nil398})399400if sdkKey == "" {401return nil402}403404return []corev1.EnvVar{405{406Name: "CONFIGCAT_SDK_KEY",407Value: "gitpod",408},409{410Name: "CONFIGCAT_BASE_URL",411Value: ClusterURL("http", ProxyComponent, ctx.Namespace, ProxyConfigcatPort) + "/configcat",412},413}414}415416func ConfigcatEnvOutOfCluster(ctx *RenderContext) []corev1.EnvVar {417return []corev1.EnvVar{418{419Name: "CONFIGCAT_SDK_KEY",420Value: "gitpod",421},422{423Name: "CONFIGCAT_BASE_URL",424Value: fmt.Sprintf("https://%s/configcat", ctx.Config.Domain),425},426}427}428429func ConfigcatProxyEnv(ctx *RenderContext) []corev1.EnvVar {430var (431sdkKey string432baseUrl string433pollInterval string434fromConfigMap string435)436_ = ctx.WithExperimental(func(cfg *experimental.Config) error {437if cfg.WebApp != nil && cfg.WebApp.ConfigcatKey != "" {438sdkKey = cfg.WebApp.ConfigcatKey439}440if cfg.WebApp != nil && cfg.WebApp.ProxyConfig != nil && cfg.WebApp.ProxyConfig.Configcat != nil {441baseUrl = cfg.WebApp.ProxyConfig.Configcat.BaseUrl442pollInterval = cfg.WebApp.ProxyConfig.Configcat.PollInterval443fromConfigMap = cfg.WebApp.ProxyConfig.Configcat.FromConfigMap444}445return nil446})447448if sdkKey == "" {449return nil450}451envs := []corev1.EnvVar{452{453Name: "CONFIGCAT_SDK_KEY",454Value: sdkKey,455},456}457458if fromConfigMap != "" {459envs = append(envs,460corev1.EnvVar{461Name: "CONFIGCAT_DIR",462Value: "/data/configcat/",463},464)465} else {466envs = append(envs,467corev1.EnvVar{468Name: "CONFIGCAT_BASE_URL",469Value: baseUrl,470},471corev1.EnvVar{472Name: "CONFIGCAT_POLL_INTERVAL",473Value: pollInterval,474},475)476}477478return envs479}480481func DatabaseWaiterContainer(ctx *RenderContext) *corev1.Container {482return databaseWaiterContainer(ctx, false)483}484485func DatabaseMigrationWaiterContainer(ctx *RenderContext) *corev1.Container {486return databaseWaiterContainer(ctx, true)487}488489func databaseWaiterContainer(ctx *RenderContext, doMigrationCheck bool) *corev1.Container {490args := []string{491"-v",492"database",493}494if doMigrationCheck {495args = append(args, "--migration-check", "true")496}497return &corev1.Container{498Name: "database-waiter",499Image: ctx.ImageName(ctx.Config.Repository, "service-waiter", ctx.VersionManifest.Components.ServiceWaiter.Version),500Args: args,501SecurityContext: &corev1.SecurityContext{502Privileged: pointer.Bool(false),503AllowPrivilegeEscalation: pointer.Bool(false),504RunAsUser: pointer.Int64(31001),505},506Env: MergeEnv(507DatabaseEnv(&ctx.Config),508ProxyEnv(&ctx.Config),509),510}511}512513func RedisWaiterContainer(ctx *RenderContext) *corev1.Container {514return &corev1.Container{515Name: "redis-waiter",516Image: ctx.ImageName(ctx.Config.Repository, "service-waiter", ctx.VersionManifest.Components.ServiceWaiter.Version),517Args: []string{518"-v",519"redis",520},521SecurityContext: &corev1.SecurityContext{522Privileged: pointer.Bool(false),523AllowPrivilegeEscalation: pointer.Bool(false),524RunAsUser: pointer.Int64(31001),525},526}527}528529// ServerComponentWaiterContainer is the container used to wait for the deployment/server to be ready530// it requires531// - pods list access to the cluster532func ServerComponentWaiterContainer(ctx *RenderContext) *corev1.Container {533image := ctx.ImageName(ctx.Config.Repository, ServerComponent, ctx.VersionManifest.Components.Server.Version)534return componentWaiterContainer(ctx, ServerComponent, DefaultLabelSelector(ServerComponent), image)535}536537// PublicApiServerComponentWaiterContainer is the container used to wait for the deployment/public-api-server to be ready538// it requires539// - pods list access to the cluster540func PublicApiServerComponentWaiterContainer(ctx *RenderContext) *corev1.Container {541image := ctx.ImageName(ctx.Config.Repository, PublicApiComponent, ctx.VersionManifest.Components.PublicAPIServer.Version)542return componentWaiterContainer(ctx, PublicApiComponent, DefaultLabelSelector(PublicApiComponent), image)543}544545func componentWaiterContainer(ctx *RenderContext, component, labels, image string) *corev1.Container {546return &corev1.Container{547Name: component + "-waiter",548Image: ctx.ImageName(ctx.Config.Repository, "service-waiter", ctx.VersionManifest.Components.ServiceWaiter.Version),549Args: []string{550"-v",551"component",552"--namespace",553ctx.Namespace,554"--component",555component,556"--labels",557labels,558"--image",559image,560},561SecurityContext: &corev1.SecurityContext{562Privileged: pointer.Bool(false),563AllowPrivilegeEscalation: pointer.Bool(false),564RunAsUser: pointer.Int64(31001),565},566Env: ConfigcatEnv(ctx),567}568}569570func KubeRBACProxyContainer(ctx *RenderContext) *corev1.Container {571return KubeRBACProxyContainerWithConfig(ctx)572}573574func KubeRBACProxyContainerWithConfig(ctx *RenderContext) *corev1.Container {575return &corev1.Container{576Name: "kube-rbac-proxy",577Image: ctx.ImageName(ThirdPartyContainerRepo(ctx.Config.Repository, KubeRBACProxyRepo), KubeRBACProxyImage, KubeRBACProxyTag),578Args: []string{579"--logtostderr",580fmt.Sprintf("--insecure-listen-address=[$(IP)]:%d", baseserver.BuiltinMetricsPort),581fmt.Sprintf("--upstream=http://127.0.0.1:%d/", baseserver.BuiltinMetricsPort),582"--http2-disable",583},584Ports: []corev1.ContainerPort{585{Name: baseserver.BuiltinMetricsPortName, ContainerPort: baseserver.BuiltinMetricsPort},586},587Env: MergeEnv(588[]corev1.EnvVar{589{590Name: "IP",591ValueFrom: &corev1.EnvVarSource{592FieldRef: &corev1.ObjectFieldSelector{593FieldPath: "status.podIP",594},595},596},597},598ProxyEnv(&ctx.Config),599),600Resources: corev1.ResourceRequirements{Requests: corev1.ResourceList{601corev1.ResourceCPU: resource.MustParse("1m"),602corev1.ResourceMemory: resource.MustParse("30Mi"),603}},604TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,605SecurityContext: &corev1.SecurityContext{606AllowPrivilegeEscalation: pointer.Bool(false),607RunAsUser: pointer.Int64(65532),608RunAsGroup: pointer.Int64(65532),609RunAsNonRoot: pointer.Bool(true),610},611}612}613614func IsDatabaseMigrationDisabled(ctx *RenderContext) bool {615disableMigration := false616_ = ctx.WithExperimental(func(cfg *experimental.Config) error {617if cfg.WebApp != nil {618disableMigration = cfg.WebApp.DisableMigration619}620return nil621})622return disableMigration623}624625func Replicas(ctx *RenderContext, component string) *int32 {626replicas := int32(1)627628if ctx.Config.Components != nil && ctx.Config.Components.PodConfig[component] != nil {629if ctx.Config.Components.PodConfig[component].Replicas != nil {630replicas = *ctx.Config.Components.PodConfig[component].Replicas631}632}633634return &replicas635}636637func ResourceRequirements(ctx *RenderContext, component, containerName string, defaults corev1.ResourceRequirements) corev1.ResourceRequirements {638resources := defaults639640if ctx.Config.Components != nil && ctx.Config.Components.PodConfig[component] != nil {641if ctx.Config.Components.PodConfig[component].Resources[containerName] != nil {642resources = *ctx.Config.Components.PodConfig[component].Resources[containerName]643}644}645646return resources647}648649// ObjectHash marshals the objects to YAML and produces a sha256 hash of the output.650// This function is useful for restarting pods when the config changes.651// Takes an error as argument to make calling it more conventient. If that error is not nil,652// it's passed right through653func ObjectHash(objs []runtime.Object, err error) (string, error) {654if err != nil {655return "", err656}657658hash := sha256.New()659for _, o := range objs {660b, err := yaml.Marshal(o)661if err != nil {662return "", err663}664_, _ = hash.Write(b)665}666return fmt.Sprintf("%x", hash.Sum(nil)), nil667}668669var (670TCPProtocol = func() *corev1.Protocol {671tcpProtocol := corev1.ProtocolTCP672return &tcpProtocol673}()674)675676var DeploymentStrategy = appsv1.DeploymentStrategy{677Type: appsv1.RollingUpdateDeploymentStrategyType,678RollingUpdate: &appsv1.RollingUpdateDeployment{679MaxSurge: &intstr.IntOrString{IntVal: 1},680MaxUnavailable: &intstr.IntOrString{IntVal: 0},681},682}683684// TODO(cw): find a better way to do this. Those values must exist in the appropriate places already.685var (686TypeMetaNamespace = metav1.TypeMeta{687APIVersion: "v1",688Kind: "Namespace",689}690TypeMetaStatefulSet = metav1.TypeMeta{691APIVersion: "apps/v1",692Kind: "StatefulSet",693}694TypeMetaConfigmap = metav1.TypeMeta{695APIVersion: "v1",696Kind: "ConfigMap",697}698TypeMetaServiceAccount = metav1.TypeMeta{699APIVersion: "v1",700Kind: "ServiceAccount",701}702TypeMetaPod = metav1.TypeMeta{703APIVersion: "v1",704Kind: "Pod",705}706TypeMetaDaemonset = metav1.TypeMeta{707APIVersion: "apps/v1",708Kind: "DaemonSet",709}710TypeMetaService = metav1.TypeMeta{711APIVersion: "v1",712Kind: "Service",713}714TypeMetaClusterRole = metav1.TypeMeta{715APIVersion: "rbac.authorization.k8s.io/v1",716Kind: "ClusterRole",717}718TypeMetaClusterRoleBinding = metav1.TypeMeta{719APIVersion: "rbac.authorization.k8s.io/v1",720Kind: "ClusterRoleBinding",721}722TypeMetaRoleBinding = metav1.TypeMeta{723APIVersion: "rbac.authorization.k8s.io/v1",724Kind: "RoleBinding",725}726TypeMetaRole = metav1.TypeMeta{727APIVersion: "rbac.authorization.k8s.io/v1",728Kind: "Role",729}730TypeMetaNetworkPolicy = metav1.TypeMeta{731APIVersion: "networking.k8s.io/v1",732Kind: "NetworkPolicy",733}734TypeMetaDeployment = metav1.TypeMeta{735APIVersion: "apps/v1",736Kind: "Deployment",737}738TypeMetaCertificate = metav1.TypeMeta{739APIVersion: "cert-manager.io/v1",740Kind: "Certificate",741}742TypeMetaCertificateIssuer = metav1.TypeMeta{743APIVersion: "cert-manager.io/v1",744Kind: "Issuer",745}746TypeMetaSecret = metav1.TypeMeta{747APIVersion: "v1",748Kind: "Secret",749}750TypeMetaResourceQuota = metav1.TypeMeta{751APIVersion: "v1",752Kind: "ResourceQuota",753}754TypeMetaBatchJob = metav1.TypeMeta{755APIVersion: "batch/v1",756Kind: "Job",757}758TypeMetaBatchCronJob = metav1.TypeMeta{759APIVersion: "batch/v1",760Kind: "CronJob",761}762TypeMetaCertificateClusterIssuer = metav1.TypeMeta{763APIVersion: "cert-manager.io/v1",764Kind: "ClusterIssuer",765}766TypeMetaBundle = metav1.TypeMeta{767APIVersion: "trust.cert-manager.io/v1alpha1",768Kind: "Bundle",769}770TypePodDisruptionBudget = metav1.TypeMeta{771APIVersion: "policy/v1",772Kind: "PodDisruptionBudget",773}774)775776// validCookieChars contains all characters which may occur in an HTTP Cookie value (unicode \u0021 through \u007E),777// without the characters , ; and / ... I did not find more details about permissible characters in RFC2965, so I took778// this list of permissible chars from Wikipedia.779//780// The tokens we produce here (e.g. owner token or CLI API token) are likely placed in cookies or transmitted via HTTP.781// To make the lifes of downstream users easier we'll try and play nice here w.r.t. to the characters used.782var validCookieChars = []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_-.")783784// RandomString produces a cryptographically secure random string of length N.785// The string contains alphanumeric characters and _ (underscore), - (dash) and . (dot)786func RandomString(length int) (string, error) {787b := make([]byte, length)788n, err := rand.Read(b)789if err != nil {790return "", err791}792if n != length {793return "", io.ErrShortWrite794}795796lrsc := len(validCookieChars)797for i, c := range b {798b[i] = validCookieChars[int(c)%lrsc]799}800return string(b), nil801}802803// ThirdPartyContainerRepo returns the container registry to use for third-party containers.804// If config registry is set to the Gitpod registry, the third-party registry is returned. If805// config registry is different, that repository is returned and deployment expected to mirror806// the images to their registry807func ThirdPartyContainerRepo(configRegistry string, thirdPartyRegistry string) string {808configRegistry = strings.TrimSuffix(configRegistry, "/")809810if configRegistry == GitpodContainerRegistry {811return thirdPartyRegistry812}813814return configRegistry815}816817// ToJSONString returns the serialized JSON string of an object818func ToJSONString(input interface{}) ([]byte, error) {819return json.MarshalIndent(input, "", " ")820}821822func NodeNameEnv(context *RenderContext) []corev1.EnvVar {823return []corev1.EnvVar{{824Name: "NODENAME",825ValueFrom: &corev1.EnvVarSource{826FieldRef: &corev1.ObjectFieldSelector{FieldPath: "spec.nodeName"},827},828}}829}830831func NodeIPEnv(context *RenderContext) []corev1.EnvVar {832return []corev1.EnvVar{{833Name: "NODE_IP",834ValueFrom: &corev1.EnvVarSource{835FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.hostIP"},836},837}}838}839840// ExperimentalWebappConfig extracts webapp experimental config from the render context.841// When the experimental config is not defined, the result will be nil.842func ExperimentalWebappConfig(ctx *RenderContext) *experimental.WebAppConfig {843var experimentalCfg *experimental.Config844_ = ctx.WithExperimental(func(ucfg *experimental.Config) error {845experimentalCfg = ucfg846return nil847})848849if experimentalCfg == nil || experimentalCfg.WebApp == nil {850return nil851}852853return experimentalCfg.WebApp854}855856// WithLocalWsManager returns true if the installed application cluster should connect to a local ws-manager857func WithLocalWsManager(ctx *RenderContext) bool {858return ctx.Config.Kind == config.InstallationFull859}860861func DaemonSetRolloutStrategy() appsv1.DaemonSetUpdateStrategy {862maxUnavailable := intstr.Parse("20%")863864return appsv1.DaemonSetUpdateStrategy{865Type: appsv1.RollingUpdateDaemonSetStrategyType,866RollingUpdate: &appsv1.RollingUpdateDaemonSet{867MaxUnavailable: &maxUnavailable,868},869}870}871872873