381 lines
13 KiB
Go

// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
// Licensed under the GNU Affero General Public License (AGPL).
// See License.AGPL.txt in the project root for license information.
package server
import (
"fmt"
"net"
"path/filepath"
"strconv"
"strings"
"github.com/gitpod-io/gitpod/installer/pkg/common"
contentservice "github.com/gitpod-io/gitpod/installer/pkg/components/content-service"
ideservice "github.com/gitpod-io/gitpod/installer/pkg/components/ide-service"
"github.com/gitpod-io/gitpod/installer/pkg/components/usage"
"github.com/gitpod-io/gitpod/installer/pkg/components/workspace"
"github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental"
"github.com/gitpod-io/gitpod/ws-manager/api/config"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/utils/pointer"
)
func configmap(ctx *common.RenderContext) ([]runtime.Object, error) {
jwtSecret, err := common.RandomString(20)
if err != nil {
return nil, err
}
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil && cfg.WebApp.Server.OAuthServer.JWTSecret != "" {
jwtSecret = cfg.WebApp.Server.OAuthServer.JWTSecret
}
return nil
})
license := ""
if ctx.Config.License != nil {
license = licenseFilePath
}
workspaceImage := ctx.Config.Workspace.WorkspaceImage
if workspaceImage == "" {
workspaceImage = ctx.ImageName(common.ThirdPartyContainerRepo(ctx.Config.Repository, ""), workspace.DefaultWorkspaceImage, workspace.DefaultWorkspaceImageVersion)
}
sessionSecret := "Important!Really-Change-This-Key!"
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil && cfg.WebApp.Server.Session.Secret != "" {
sessionSecret = cfg.WebApp.Server.Session.Secret
}
return nil
})
disableDynamicAuthProviderLogin := false
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil {
disableDynamicAuthProviderLogin = cfg.WebApp.Server.DisableDynamicAuthProviderLogin
}
return nil
})
enableLocalApp := true
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil && cfg.WebApp.Server.EnableLocalApp != nil {
enableLocalApp = *cfg.WebApp.Server.EnableLocalApp
}
return nil
})
runDbDeleter := true
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil && cfg.WebApp.Server.RunDbDeleter != nil {
runDbDeleter = *cfg.WebApp.Server.RunDbDeleter
}
return nil
})
defaultBaseImageRegistryWhitelist := []string{}
allowList := ctx.Config.ContainerRegistry.PrivateBaseImageAllowList
if len(allowList) > 0 {
defaultBaseImageRegistryWhitelist = allowList
}
chargebeeSecret := ""
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil {
chargebeeSecret = cfg.WebApp.Server.ChargebeeSecret
}
return nil
})
stripeSecret := ""
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil {
stripeSecret = cfg.WebApp.Server.StripeSecret
}
return nil
})
stripeConfig := ""
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil {
stripeConfig = cfg.WebApp.Server.StripeConfig
}
return nil
})
disableWsGarbageCollection := false
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil {
disableWsGarbageCollection = cfg.WebApp.Server.DisableWorkspaceGarbageCollection
}
return nil
})
disableLongRunningMigrationsJob := false
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil {
disableLongRunningMigrationsJob = cfg.WebApp.Server.DisableLongRunningMigrationJob
}
return nil
})
disableCompleteSnapshotJob := false
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil {
disableCompleteSnapshotJob = cfg.WebApp.Server.DisableCompleteSnapshotJob
}
return nil
})
githubApp := GitHubApp{}
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil && cfg.WebApp.Server.GithubApp != nil {
githubApp.AppId = cfg.WebApp.Server.GithubApp.AppId
githubApp.AuthProviderId = cfg.WebApp.Server.GithubApp.AuthProviderId
githubApp.BaseUrl = cfg.WebApp.Server.GithubApp.BaseUrl
githubApp.CertPath = cfg.WebApp.Server.GithubApp.CertPath
githubApp.Enabled = cfg.WebApp.Server.GithubApp.Enabled
githubApp.LogLevel = cfg.WebApp.Server.GithubApp.LogLevel
githubApp.MarketplaceName = cfg.WebApp.Server.GithubApp.MarketplaceName
githubApp.WebhookSecret = cfg.WebApp.Server.GithubApp.WebhookSecret
githubApp.CertSecretName = cfg.WebApp.Server.GithubApp.CertSecretName
}
return nil
})
workspaceClasses := []WorkspaceClass{
{
Id: config.DefaultWorkspaceClass,
Category: GeneralPurpose,
DisplayName: strings.Title(config.DefaultWorkspaceClass),
Description: "Default workspace class",
PowerUps: 1,
IsDefault: true,
},
}
ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.WorkspaceClasses != nil && len(cfg.WebApp.WorkspaceClasses) > 0 {
workspaceClasses = nil
for _, cl := range cfg.WebApp.WorkspaceClasses {
class := WorkspaceClass{
Id: cl.Id,
Category: WorkspaceClassCategory(cl.Category),
DisplayName: cl.DisplayName,
Description: cl.Description,
PowerUps: cl.PowerUps,
IsDefault: cl.IsDefault,
Marker: cl.Marker,
}
workspaceClasses = append(workspaceClasses, class)
}
}
return nil
})
inactivityPeriodForReposInDays := 0
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil && cfg.WebApp.Server.InactivityPeriodForReposInDays != nil {
inactivityPeriodForReposInDays = *cfg.WebApp.Server.InactivityPeriodForReposInDays
}
return nil
})
var personalAccessTokenSigningKeyPath string
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
_, _, personalAccessTokenSigningKeyPath, _ = getPersonalAccessTokenSigningKey(cfg)
return nil
})
showSetupModal := true // old default to make self-hosted continue to work!
_ = ctx.WithExperimental(func(cfg *experimental.Config) error {
if cfg.WebApp != nil && cfg.WebApp.Server != nil && cfg.WebApp.Server.ShowSetupModal != nil {
showSetupModal = *cfg.WebApp.Server.ShowSetupModal
}
return nil
})
_, _, adminCredentialsPath := getAdminCredentials()
// todo(sje): all these values are configurable
scfg := ConfigSerialized{
Version: ctx.VersionManifest.Version,
HostURL: fmt.Sprintf("https://%s", ctx.Config.Domain),
InstallationShortname: ctx.Config.Metadata.InstallationShortname,
LicenseFile: license,
WorkspaceHeartbeat: WorkspaceHeartbeat{
IntervalSeconds: 60,
TimeoutSeconds: 300,
},
WorkspaceDefaults: WorkspaceDefaults{
WorkspaceImage: workspaceImage,
PreviewFeatureFlags: []NamedWorkspaceFeatureFlag{},
DefaultFeatureFlags: []NamedWorkspaceFeatureFlag{},
TimeoutDefault: ctx.Config.Workspace.TimeoutDefault,
TimeoutExtended: ctx.Config.Workspace.TimeoutExtended,
},
Session: Session{
MaxAgeMs: 259200000,
Secret: sessionSecret,
},
DefinitelyGpDisabled: ctx.Config.DisableDefinitelyGP,
GitHubApp: githubApp,
WorkspaceGarbageCollection: WorkspaceGarbageCollection{
Disabled: disableWsGarbageCollection,
IntervalSeconds: 5 * 60,
MinAgeDays: 14,
MinAgePrebuildDays: 7,
ChunkLimit: 1000,
ContentRetentionPeriodDays: 21,
ContentChunkLimit: 100,
PurgeRetentionPeriodDays: 365,
PurgeChunkLimit: 5000,
},
LongRunningMigrationsJob: JobConfig{
Disabled: disableLongRunningMigrationsJob,
},
CompleteSnapshotJob: JobConfig{
Disabled: disableCompleteSnapshotJob,
},
EnableLocalApp: enableLocalApp,
AuthProviderConfigFiles: func() []string {
providers := make([]string, 0)
// Appending "/provider" here in order to play nicely with the telepresence hack
for _, provider := range ctx.Config.AuthProviders {
providers = append(providers, fmt.Sprintf("%s/%s/provider", authProviderFilePath, provider.Name))
}
return providers
}(),
DisableDynamicAuthProviderLogin: disableDynamicAuthProviderLogin,
MaxEnvvarPerUserCount: 4048,
MaxConcurrentPrebuildsPerRef: 10,
IncrementalPrebuilds: IncrementalPrebuilds{CommitHistory: 100, RepositoryPasslist: []string{}},
BlockNewUsers: ctx.Config.BlockNewUsers,
DefaultBaseImageRegistryWhitelist: defaultBaseImageRegistryWhitelist,
RunDbDeleter: runDbDeleter,
OAuthServer: OAuthServer{
Enabled: true,
JWTSecret: jwtSecret,
},
RateLimiter: RateLimiter{
Groups: map[string]GroupsConfig{
"inWorkspaceUserAction": {
Points: 10,
DurationsSec: 2,
},
},
Functions: map[string]FunctionsConfig{
"openPort": {Group: "inWorkspaceUserAction"},
"closePort": {Group: "inWorkspaceUserAction"},
"controlAdmission": {Group: "inWorkspaceUserAction"},
"shareSnapshot": {Group: "inWorkspaceUserAction"},
},
},
ContentServiceAddr: net.JoinHostPort(fmt.Sprintf("%s.%s.svc.cluster.local", contentservice.Component, ctx.Namespace), strconv.Itoa(contentservice.RPCPort)),
UsageServiceAddr: net.JoinHostPort(fmt.Sprintf("%s.%s.svc.cluster.local", usage.Component, ctx.Namespace), strconv.Itoa(usage.GRPCServicePort)),
IDEServiceAddr: net.JoinHostPort(fmt.Sprintf("%s.%s.svc.cluster.local", ideservice.Component, ctx.Namespace), strconv.Itoa(ideservice.GRPCServicePort)),
MaximumEventLoopLag: 0.35,
CodeSync: CodeSync{},
VSXRegistryUrl: fmt.Sprintf("https://open-vsx.%s", ctx.Config.Domain), // todo(sje): or "https://{{ .Values.vsxRegistry.host | default "open-vsx.org" }}" if not using OpenVSX proxy
EnablePayment: chargebeeSecret != "" || stripeSecret != "" || stripeConfig != "",
ChargebeeProviderOptionsFile: fmt.Sprintf("%s/providerOptions", chargebeeMountPath),
StripeSecretsFile: fmt.Sprintf("%s/apikeys", stripeSecretMountPath),
InsecureNoDomain: false,
PrebuildLimiter: PrebuildRateLimiters{
// default limit for all cloneURLs
"*": PrebuildRateLimiterConfig{
Limit: 100,
Period: 600,
},
},
WorkspaceClasses: workspaceClasses,
InactivityPeriodForReposInDays: inactivityPeriodForReposInDays,
PATSigningKeyFile: personalAccessTokenSigningKeyPath,
Admin: AdminConfig{
GrantFirstUserAdminRole: true, // existing default
CredentialsPath: adminCredentialsPath,
},
ShowSetupModal: showSetupModal,
}
fc, err := common.ToJSONString(scfg)
if err != nil {
return nil, fmt.Errorf("failed to marshal server config: %w", err)
}
return []runtime.Object{
&corev1.ConfigMap{
TypeMeta: common.TypeMetaConfigmap,
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-config", Component),
Namespace: ctx.Namespace,
Labels: common.CustomizeLabel(ctx, Component, common.TypeMetaConfigmap),
Annotations: common.CustomizeAnnotation(ctx, Component, common.TypeMetaConfigmap),
},
Data: map[string]string{
"config.json": string(fc),
},
},
}, nil
}
func getPersonalAccessTokenSigningKey(cfg *experimental.Config) (corev1.Volume, corev1.VolumeMount, string, bool) {
var volume corev1.Volume
var mount corev1.VolumeMount
var path string
if cfg == nil || cfg.WebApp == nil || cfg.WebApp.PublicAPI == nil || cfg.WebApp.PublicAPI.PersonalAccessTokenSigningKeySecretName == "" {
return volume, mount, path, false
}
personalAccessTokenSecretname := cfg.WebApp.PublicAPI.PersonalAccessTokenSigningKeySecretName
path = personalAccessTokenSigningKeyMountPath
volume = corev1.Volume{
Name: "personal-access-token-signing-key",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: personalAccessTokenSecretname,
Optional: pointer.Bool(true),
},
},
}
mount = corev1.VolumeMount{
Name: "personal-access-token-signing-key",
MountPath: personalAccessTokenSigningKeyMountPath,
SubPath: "personal-access-token-signing-key",
ReadOnly: true,
}
return volume, mount, path, true
}
func getAdminCredentials() (corev1.Volume, corev1.VolumeMount, string) {
volume := corev1.Volume{
Name: "admin-credentials",
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: AdminCredentialsSecretName,
Optional: pointer.Bool(true),
},
},
}
mount := corev1.VolumeMount{
Name: "admin-credentials",
MountPath: AdminCredentialsSecretMountPath,
ReadOnly: true,
}
return volume, mount, filepath.Join(AdminCredentialsSecretMountPath, AdminCredentialsSecretKey)
}