2022-10-11 13:51:26 +02:00

460 lines
15 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 config
import (
"time"
"github.com/gitpod-io/gitpod/common-go/util"
"github.com/gitpod-io/gitpod/installer/pkg/config"
"github.com/gitpod-io/gitpod/installer/pkg/config/v1/experimental"
"github.com/gitpod-io/gitpod/installer/pkg/containerd"
"github.com/gitpod-io/gitpod/ws-daemon/pkg/cpulimit"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/pointer"
)
func init() {
config.AddVersion("v1", version{})
}
type version struct{}
func (v version) Factory() interface{} {
return &Config{
AuthProviders: []ObjectRef{},
BlockNewUsers: BlockNewUsers{
Enabled: false,
Passlist: []string{},
},
}
}
const (
defaultRepositoryUrl = "eu.gcr.io/gitpod-core-dev/build"
defaultOpenVSXURL = "https://open-vsx.org"
defaultMetadataRegion = "local"
)
func (v version) Defaults(in interface{}) error {
cfg, ok := in.(*Config)
if !ok {
return config.ErrInvalidType
}
cfg.Kind = InstallationFull
cfg.Repository = defaultRepositoryUrl
cfg.Observability = Observability{
LogLevel: LogLevelInfo,
}
cfg.Certificate.Kind = ObjectRefSecret
cfg.Certificate.Name = "https-certificates"
cfg.Database.InCluster = pointer.Bool(true)
cfg.Metadata.Region = defaultMetadataRegion
cfg.Metadata.InstallationShortname = InstallationShortNameOldDefault // TODO(gpl): we're tied to "default" here because that's what we put into static bridges in the past
cfg.ObjectStorage.InCluster = pointer.Bool(true)
cfg.ObjectStorage.Resources = &Resources{
Requests: corev1.ResourceList{
corev1.ResourceMemory: resource.MustParse("2Gi"),
},
}
cfg.ContainerRegistry.InCluster = pointer.Bool(true)
cfg.ContainerRegistry.PrivateBaseImageAllowList = []string{}
cfg.Workspace.Resources.Requests = corev1.ResourceList{
corev1.ResourceCPU: resource.MustParse("1000m"),
corev1.ResourceMemory: resource.MustParse("2Gi"),
}
cfg.Workspace.Runtime.FSShiftMethod = FSShiftFuseFS
cfg.Workspace.Runtime.ContainerDSocket = containerd.ContainerdSocketLocationDefault.String()
cfg.Workspace.Runtime.ContainerDRuntimeDir = containerd.ContainerdLocationDefault.String()
cfg.Workspace.MaxLifetime = util.Duration(36 * time.Hour)
cfg.Workspace.PVC.Size = resource.MustParse("30Gi")
cfg.Workspace.PVC.StorageClass = ""
cfg.Workspace.PVC.SnapshotClass = ""
cfg.OpenVSX.URL = defaultOpenVSXURL
cfg.DisableDefinitelyGP = true
return nil
}
func (v version) CheckDeprecated(rawCfg interface{}) (map[string]interface{}, []string) {
warnings := make(map[string]interface{}, 0)
conflicts := make([]string, 0)
cfg := rawCfg.(*Config)
if cfg.Experimental != nil {
if cfg.Experimental.Common != nil && cfg.Experimental.Common.UsePodSecurityPolicies {
warnings["experimental.common.usePodSecurityPolicies"] = "true"
}
if cfg.Experimental.WebApp != nil {
// service type of proxy is now configurable from main config
if cfg.Experimental.WebApp.ProxyConfig != nil && cfg.Experimental.WebApp.ProxyConfig.ServiceType != nil {
warnings["experimental.webapp.proxy.serviceType"] = *cfg.Experimental.WebApp.ProxyConfig.ServiceType
if cfg.Components != nil && cfg.Components.Proxy != nil && cfg.Components.Proxy.Service != nil && cfg.Components.Proxy.Service.ServiceType != nil {
conflicts = append(conflicts, "Cannot set proxy service type in both components and experimental")
} else {
// Promote the experimental value to the components
if cfg.Components == nil {
cfg.Components = &Components{}
}
if cfg.Components.Proxy == nil {
cfg.Components.Proxy = &ProxyComponent{}
}
if cfg.Components.Proxy.Service == nil {
cfg.Components.Proxy.Service = &ComponentTypeService{}
}
cfg.Components.Proxy.Service.ServiceType = cfg.Experimental.WebApp.ProxyConfig.ServiceType
}
}
// default workspace base image is now configurable from main config
if cfg.Experimental.WebApp.Server != nil {
workspaceImage := cfg.Experimental.WebApp.Server.WorkspaceDefaults.WorkspaceImage
if workspaceImage != "" {
warnings["experimental.webapp.server.workspaceDefaults.workspaceImage"] = workspaceImage
if cfg.Workspace.WorkspaceImage != "" {
conflicts = append(conflicts, "Cannot set default workspace image in both workspaces and experimental")
} else {
cfg.Workspace.WorkspaceImage = workspaceImage
}
}
registryAllowList := cfg.Experimental.WebApp.Server.DefaultBaseImageRegistryWhiteList
if registryAllowList != nil {
warnings["experimental.webapp.server.defaultBaseImageRegistryWhitelist"] = registryAllowList
if len(cfg.ContainerRegistry.PrivateBaseImageAllowList) > 0 {
conflicts = append(conflicts, "Cannot set allow list for private base image in both containerRegistry and experimental")
} else {
cfg.ContainerRegistry.PrivateBaseImageAllowList = registryAllowList
}
}
}
}
}
if cfg.ObjectStorage.MaximumBackupCount != nil {
warnings["objectStorage.maximumBackupCount"] = cfg.ObjectStorage.MaximumBackupCount
}
return warnings, conflicts
}
// Config defines the v1 version structure of the gitpod config file
type Config struct {
// Installation type to run - for most users, this will be Full
Kind InstallationKind `json:"kind" validate:"required,installation_kind"`
// The domain to deploy to
Domain string `json:"domain" validate:"required,fqdn"`
Metadata Metadata `json:"metadata"`
Repository string `json:"repository" validate:"required,ascii"`
Observability Observability `json:"observability"`
Analytics *Analytics `json:"analytics,omitempty"`
Database Database `json:"database" validate:"required"`
ObjectStorage ObjectStorage `json:"objectStorage" validate:"required"`
ContainerRegistry ContainerRegistry `json:"containerRegistry" validate:"required"`
Certificate ObjectRef `json:"certificate" validate:"required"`
HTTPProxy *ObjectRef `json:"httpProxy,omitempty"`
ImagePullSecrets []ObjectRef `json:"imagePullSecrets,omitempty"`
Workspace Workspace `json:"workspace" validate:"required"`
OpenVSX OpenVSX `json:"openVSX"`
AuthProviders []ObjectRef `json:"authProviders" validate:"dive"`
BlockNewUsers BlockNewUsers `json:"blockNewUsers"`
License *ObjectRef `json:"license,omitempty"`
SSHGatewayHostKey *ObjectRef `json:"sshGatewayHostKey,omitempty"`
DisableDefinitelyGP bool `json:"disableDefinitelyGp"`
CustomCACert *ObjectRef `json:"customCACert,omitempty"`
DropImageRepo *bool `json:"dropImageRepo,omitempty"`
Customization *[]Customization `json:"customization,omitempty"`
Components *Components `json:"components,omitempty"`
Experimental *experimental.Config `json:"experimental,omitempty"`
}
type Metadata struct {
// Location for your objectStorage provider
Region string `json:"region" validate:"required"`
// InstallationShortname establishes the "identity" of the (application) cluster.
InstallationShortname string `json:"shortname"`
}
const (
InstallationShortNameOldDefault string = "default"
)
type Observability struct {
LogLevel LogLevel `json:"logLevel" validate:"required,log_level"`
Tracing *Tracing `json:"tracing,omitempty"`
}
type Analytics struct {
SegmentKey string `json:"segmentKey"`
Writer string `json:"writer"`
}
type Tracing struct {
Endpoint *string `json:"endpoint,omitempty"`
AgentHost *string `json:"agentHost,omitempty"`
// Name of the kubernetes secret to use for Jaeger authentication
// The secret should contains two definitions: JAEGER_USER and JAEGER_PASSWORD
SecretName *string `json:"secretName,omitempty"`
}
type Database struct {
InCluster *bool `json:"inCluster,omitempty"`
External *DatabaseExternal `json:"external,omitempty"`
CloudSQL *DatabaseCloudSQL `json:"cloudSQL,omitempty"`
}
type DatabaseExternal struct {
Certificate ObjectRef `json:"certificate"`
}
type DatabaseCloudSQL struct {
ServiceAccount ObjectRef `json:"serviceAccount"`
Instance string `json:"instance" validate:"required"`
}
type ObjectStorage struct {
InCluster *bool `json:"inCluster,omitempty"`
S3 *ObjectStorageS3 `json:"s3,omitempty"`
CloudStorage *ObjectStorageCloudStorage `json:"cloudStorage,omitempty"`
Azure *ObjectStorageAzure `json:"azure,omitempty"`
// DEPRECATED
MaximumBackupCount *int `json:"maximumBackupCount,omitempty"`
BlobQuota *int64 `json:"blobQuota,omitempty"`
Resources *Resources `json:"resources,omitempty"`
}
type ObjectStorageS3 struct {
Endpoint string `json:"endpoint" validate:"required"`
Credentials ObjectRef `json:"credentials" validate:"required"`
// BucketName sets the name of an existing bucket to enable the "single bucket mode"
// If no name is configured, the old "one bucket per user" behaviour kicks in.
BucketName string `json:"bucket"`
AllowInsecureConnection bool `json:"allowInsecureConnection"`
}
type ObjectStorageCloudStorage struct {
ServiceAccount ObjectRef `json:"serviceAccount" validate:"required"`
Project string `json:"project" validate:"required"`
}
type ObjectStorageAzure struct {
Credentials ObjectRef `json:"credentials" validate:"required"`
}
type InstallationKind string
const (
InstallationMeta InstallationKind = "Meta"
InstallationWorkspace InstallationKind = "Workspace"
InstallationFull InstallationKind = "Full"
)
type ObjectRef struct {
Kind ObjectRefKind `json:"kind" validate:"required,objectref_kind"`
Name string `json:"name" validate:"required"`
}
type ObjectRefKind string
const (
ObjectRefSecret ObjectRefKind = "secret"
)
type ContainerRegistry struct {
InCluster *bool `json:"inCluster,omitempty" validate:"required"`
External *ContainerRegistryExternal `json:"external,omitempty" validate:"required_if=InCluster false"`
S3Storage *S3Storage `json:"s3storage,omitempty"`
PrivateBaseImageAllowList []string `json:"privateBaseImageAllowList"`
}
type ContainerRegistryExternal struct {
URL string `json:"url" validate:"required"`
Certificate ObjectRef `json:"certificate" validate:"required"`
}
type S3Storage struct {
Bucket string `json:"bucket" validate:"required"`
Region string `json:"region" validate:"required"`
Endpoint string `json:"endpoint" validate:"required"`
Certificate ObjectRef `json:"certificate" validate:"required"`
}
type LogLevel string
// Taken from github.com/gitpod-io/gitpod/components/gitpod-protocol/src/util/logging.ts
const (
LogLevelTrace LogLevel = "trace"
LogLevelDebug LogLevel = "debug"
LogLevelInfo LogLevel = "info"
LogLevelWarning LogLevel = "warning"
LogLevelError LogLevel = "error"
LogLevelFatal LogLevel = "fatal"
LogLevelPanic LogLevel = "panic"
)
type Resources struct {
// todo(sje): add custom validation to corev1.ResourceList
Requests corev1.ResourceList `json:"requests" validate:"required"`
Limits corev1.ResourceList `json:"limits,omitempty"`
}
type WorkspaceRuntime struct {
// File system
FSShiftMethod FSShiftMethod `json:"fsShiftMethod" validate:"required,fs_shift_method"`
// The location of containerd socket on the host machine
ContainerDRuntimeDir string `json:"containerdRuntimeDir" validate:"required,startswith=/"`
// The location of containerd socket on the host machine
ContainerDSocket string `json:"containerdSocket" validate:"required,startswith=/"`
}
type WorkspaceResources struct {
Requests corev1.ResourceList `json:"requests" validate:"required"`
Limits WorkspaceLimits `json:"limits,omitempty"`
}
type WorkspaceLimits struct {
Cpu WorkspaceCpuLimits `json:"cpu"`
Memory string `json:"memory"`
Storage string `json:"storage"`
EphemeralStorage string `json:"ephemeral-storage"`
}
type WorkspaceCpuLimits struct {
Buckets []cpulimit.Bucket `json:"buckets"`
MinLimit string `json:"min"`
BurstLimit string `json:"burst"`
}
type WorkspaceTemplates struct {
Default *corev1.Pod `json:"default"`
Prebuild *corev1.Pod `json:"prebuild"`
ImageBuild *corev1.Pod `json:"imagebuild"`
Regular *corev1.Pod `json:"regular"`
}
type PersistentVolumeClaim struct {
// Size is a size of persistent volume claim to use
Size resource.Quantity `json:"size" validate:"required"`
// StorageClass is a storage class of persistent volume claim to use
StorageClass string `json:"storageClass"`
// SnapshotClass is a snapshot class name that is used to create volume snapshot
SnapshotClass string `json:"snapshotClass"`
}
type Workspace struct {
Runtime WorkspaceRuntime `json:"runtime" validate:"required"`
Resources Resources `json:"resources" validate:"required"`
Templates *WorkspaceTemplates `json:"templates,omitempty"`
// PVC is the struct that describes how to setup persistent volume claim for workspace
PVC PersistentVolumeClaim `json:"pvc" validate:"required"`
// MaxLifetime is the maximum time a workspace is allowed to run. After that, the workspace times out despite activity
MaxLifetime util.Duration `json:"maxLifetime" validate:"required"`
// TimeoutDefault is the default timeout of a regular workspace
TimeoutDefault *util.Duration `json:"timeoutDefault,omitempty"`
// TimeoutExtended is the workspace timeout that a user can extend to for one workspace
TimeoutExtended *util.Duration `json:"timeoutExtended,omitempty"`
// TimeoutAfterClose is the time a workspace timed out after it has been closed (“closed” means that it does not get a heartbeat from an IDE anymore)
TimeoutAfterClose *util.Duration `json:"timeoutAfterClose,omitempty"`
WorkspaceImage string `json:"workspaceImage,omitempty"`
}
type OpenVSX struct {
URL string `json:"url" validate:"url"`
}
type LicensorType string
const (
LicensorTypeGitpod LicensorType = "gitpod"
LicensorTypeReplicated LicensorType = "replicated"
)
type FSShiftMethod string
const (
FSShiftFuseFS FSShiftMethod = "fuse"
FSShiftShiftFS FSShiftMethod = "shiftfs"
)
type BlockNewUsers struct {
Enabled bool `json:"enabled"`
// Passlist []string `json:"passlist" validate:"min=1,unique,dive,fqdn"`
Passlist []string `json:"passlist" validate:"block_new_users_passlist"`
}
// AuthProviderConfigs this only contains what is necessary for validation
type AuthProviderConfigs struct {
ID string `json:"id" validate:"required"`
Host string `json:"host" validate:"required"`
Type string `json:"type" validate:"required"`
OAuth OAuth `json:"oauth" validate:"required"`
}
// OAuth this only contains what is necessary for validation
type OAuth struct {
ClientId string `json:"clientId" validate:"required"`
ClientSecret string `json:"clientSecret" validate:"required"`
CallBackUrl string `json:"callBackUrl" validate:"required"`
}
// Customization is a stripped-down version of the Kubernetes YAML
type Customization struct {
metav1.TypeMeta `json:",inline"`
Metadata metav1.ObjectMeta `json:"metadata"`
Spec CustomizationSpec `json:"spec,omitempty"`
}
type CustomizationSpec struct {
Env []corev1.EnvVar `json:"env"`
}
type Components struct {
Proxy *ProxyComponent `json:"proxy,omitempty"`
}
type ProxyComponent struct {
Service *ComponentTypeService `json:"service,omitempty"`
}
type ComponentTypeService struct {
ServiceType *corev1.ServiceType `json:"serviceType,omitempty" validate:"omitempty,service_config_type"`
}