mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
Fix cyclic import cycle Update altnames Update golden testdata, todo Update grpc opts Testing Remove blocking dial Only add TLS in ws cluster Conditional TLS Add comments
563 lines
19 KiB
Go
563 lines
19 KiB
Go
// Copyright (c) 2020 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 (
|
|
"bytes"
|
|
"html/template"
|
|
iofs "io/fs"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
ozzo "github.com/go-ozzo/ozzo-validation"
|
|
"github.com/go-ozzo/ozzo-validation/is"
|
|
"golang.org/x/xerrors"
|
|
corev1 "k8s.io/api/core/v1"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
"k8s.io/apimachinery/pkg/util/validation"
|
|
"k8s.io/apimachinery/pkg/util/yaml"
|
|
|
|
"github.com/gitpod-io/gitpod/common-go/grpc"
|
|
"github.com/gitpod-io/gitpod/common-go/util"
|
|
cntntcfg "github.com/gitpod-io/gitpod/content-service/api/config"
|
|
)
|
|
|
|
// DefaultWorkspaceClass is the name of the default workspace class
|
|
const DefaultWorkspaceClass = "default"
|
|
|
|
type osFS struct{}
|
|
|
|
func (*osFS) Open(name string) (iofs.File, error) {
|
|
return os.Open(name)
|
|
}
|
|
|
|
// FS is used to load files referred to by this configuration.
|
|
// We use a library here to be able to test things properly.
|
|
var FS iofs.FS = &osFS{}
|
|
|
|
// ServiceConfiguration configures the ws-manager configuration
|
|
type ServiceConfiguration struct {
|
|
Manager Configuration `json:"manager"`
|
|
Content struct {
|
|
Storage cntntcfg.StorageConfig `json:"storage"`
|
|
} `json:"content"`
|
|
RPCServer struct {
|
|
Addr string `json:"addr"`
|
|
TLS struct {
|
|
CA string `json:"ca"`
|
|
Certificate string `json:"crt"`
|
|
PrivateKey string `json:"key"`
|
|
} `json:"tls"`
|
|
RateLimits map[string]grpc.RateLimit `json:"ratelimits"`
|
|
} `json:"rpcServer"`
|
|
ImageBuilderProxy struct {
|
|
TargetAddr string `json:"targetAddr"`
|
|
TLS struct {
|
|
CA string `json:"ca"`
|
|
Certificate string `json:"crt"`
|
|
PrivateKey string `json:"key"`
|
|
} `json:"tls"`
|
|
} `json:"imageBuilderProxy"`
|
|
|
|
PProf struct {
|
|
Addr string `json:"addr"`
|
|
} `json:"pprof"`
|
|
Prometheus struct {
|
|
Addr string `json:"addr"`
|
|
} `json:"prometheus"`
|
|
}
|
|
|
|
// Configuration is the configuration of the ws-manager
|
|
type Configuration struct {
|
|
// Namespace is the kubernetes namespace the workspace manager operates in
|
|
Namespace string `json:"namespace"`
|
|
// SchedulerName is the name of the workspace scheduler all pods are created with
|
|
SchedulerName string `json:"schedulerName"`
|
|
// SeccompProfile names the seccomp profile workspaces will use
|
|
SeccompProfile string `json:"seccompProfile"`
|
|
// Timeouts configures how long workspaces can be without activity before they're shut down.
|
|
// All values in here must be valid time.Duration
|
|
Timeouts WorkspaceTimeoutConfiguration `json:"timeouts"`
|
|
// InitProbe configures the ready-probe of workspaces which signal when the initialization is finished
|
|
InitProbe InitProbeConfiguration `json:"initProbe"`
|
|
// WorkspaceCACertSecret optionally names a secret which is mounted in `/etc/ssl/certs/gp-custom.crt`
|
|
// in all workspace pods.
|
|
WorkspaceCACertSecret string `json:"caCertSecret,omitempty"`
|
|
// WorkspaceURLTemplate is a Go template which resolves to the external URL of the
|
|
// workspace. Available fields are:
|
|
// - `ID` which is the workspace ID,
|
|
// - `Prefix` which is the workspace's service prefix
|
|
// - `Host` which is the GitpodHostURL
|
|
WorkspaceURLTemplate string `json:"urlTemplate"`
|
|
// WorkspaceURLTemplate is a Go template which resolves to the external URL of the
|
|
// workspace port. Available fields are:
|
|
// - `ID` which is the workspace ID,
|
|
// - `Prefix` which is the workspace's service prefix
|
|
// - `Host` which is the GitpodHostURL
|
|
// - `WorkspacePort` which is the workspace port
|
|
// - `IngressPort` which is the publicly accessile port
|
|
WorkspacePortURLTemplate string `json:"portUrlTemplate"`
|
|
// HostPath is the path on the node where workspace data resides (ideally this is an SSD)
|
|
WorkspaceHostPath string `json:"workspaceHostPath"`
|
|
// HeartbeatInterval is the time in seconds in which Theia sends a heartbeat if the user is active
|
|
HeartbeatInterval util.Duration `json:"heartbeatInterval"`
|
|
// Is the URL under which Gitpod is installed (e.g. https://gitpod.io)
|
|
GitpodHostURL string `json:"hostURL"`
|
|
// EventTraceLog is a path to file where we'll write the monitor event trace log to
|
|
EventTraceLog string `json:"eventTraceLog,omitempty"`
|
|
// ReconnectionInterval configures the time we wait until we reconnect to the various other services
|
|
ReconnectionInterval util.Duration `json:"reconnectionInterval"`
|
|
// DryRun prevents us from ever stopping a pod. It is considered equivalent to a listener mode
|
|
DryRun bool `json:"dryRun,omitempty"`
|
|
// WorkspaceDaemon configures our connection to the workspace sync daemons runnin on the nodes
|
|
WorkspaceDaemon WorkspaceDaemonConfiguration `json:"wsdaemon"`
|
|
// RegistryFacadeHost is the host (possibly including port) on which the registry facade resolves
|
|
RegistryFacadeHost string `json:"registryFacadeHost"`
|
|
// Cluster host under which workspaces are served, e.g. ws-eu11.gitpod.io
|
|
WorkspaceClusterHost string `json:"workspaceClusterHost"`
|
|
// WorkspaceClasses provide different resource classes for workspaces
|
|
WorkspaceClasses map[string]*WorkspaceClass `json:"workspaceClass"`
|
|
// DebugWorkspacePod adds extra finalizer to workspace to prevent it from shutting down. Helps to debug.
|
|
DebugWorkspacePod bool `json:"debugWorkspacePod,omitempty"`
|
|
}
|
|
|
|
type WorkspaceClass struct {
|
|
Name string `json:"name"`
|
|
Container ContainerConfiguration `json:"container"`
|
|
Templates WorkspacePodTemplateConfiguration `json:"templates"`
|
|
PrebuildPVC PVCConfiguration `json:"prebuildPVC"`
|
|
PVC PVCConfiguration `json:"pvc"`
|
|
}
|
|
|
|
// WorkspaceTimeoutConfiguration configures the timeout behaviour of workspaces
|
|
type WorkspaceTimeoutConfiguration struct {
|
|
// TotalStartup is the total time a workspace can take until we expect the first activity
|
|
TotalStartup util.Duration `json:"startup"`
|
|
// Initialization is the time the initialization phase alone can take
|
|
Initialization util.Duration `json:"initialization"`
|
|
// RegularWorkspace is the time a regular workspace can be without activity before it's shutdown
|
|
RegularWorkspace util.Duration `json:"regularWorkspace"`
|
|
// MaxLifetime is the maximum lifetime of a regular workspace
|
|
MaxLifetime util.Duration `json:"maxLifetime"`
|
|
// HeadlessWorkspace is the maximum runtime a headless workspace can have (including startup)
|
|
HeadlessWorkspace util.Duration `json:"headlessWorkspace"`
|
|
// AfterClose is the time a workspace lives after it has been marked closed
|
|
AfterClose util.Duration `json:"afterClose"`
|
|
// ContentFinalization is the time in which the workspace's content needs to be backed up and removed from the node
|
|
ContentFinalization util.Duration `json:"contentFinalization"`
|
|
// Stopping is the time a workspace has until it has to be stopped. This time includes finalization, hence must be greater than
|
|
// the ContentFinalization timeout.
|
|
Stopping util.Duration `json:"stopping"`
|
|
// Interrupted is the time a workspace may be interrupted (since it last saw activity or since it was created if it never saw any)
|
|
Interrupted util.Duration `json:"interrupted"`
|
|
}
|
|
|
|
// InitProbeConfiguration configures the behaviour of the workspace ready probe
|
|
type InitProbeConfiguration struct {
|
|
// Disabled disables the workspace init probe - this is only neccesary during tests and in noDomain environments.
|
|
Disabled bool `json:"disabled,omitempty"`
|
|
|
|
// Timeout is the HTTP GET timeout during each probe attempt. Defaults to 5 seconds.
|
|
Timeout string `json:"timeout,omitempty"`
|
|
}
|
|
|
|
// WorkspacePodTemplateConfiguration configures the paths to workspace pod templates
|
|
type WorkspacePodTemplateConfiguration struct {
|
|
// DefaultPath is a path to a workspace pod template YAML file that's used for
|
|
// all workspaces irregardles of their type. If a type-specific template is configured
|
|
// as well, that template is merged in, too.
|
|
DefaultPath string `json:"defaultPath,omitempty"`
|
|
// RegularPath is a path to an additional workspace pod template YAML file for regular workspaces
|
|
RegularPath string `json:"regularPath,omitempty"`
|
|
// PrebuildPath is a path to an additional workspace pod template YAML file for prebuild workspaces
|
|
PrebuildPath string `json:"prebuildPath,omitempty"`
|
|
// ProbePath is a path to an additional workspace pod template YAML file for probe workspaces
|
|
// Deprecated
|
|
ProbePath string `json:"probePath,omitempty"`
|
|
// ImagebuildPath is a path to an additional workspace pod template YAML file for imagebuild workspaces
|
|
ImagebuildPath string `json:"imagebuildPath,omitempty"`
|
|
}
|
|
|
|
// WorkspaceDaemonConfiguration configures our connection to the workspace sync daemons runnin on the nodes
|
|
type WorkspaceDaemonConfiguration struct {
|
|
// Port is the port on the node on which the ws-daemon is listening
|
|
Port int `json:"port"`
|
|
// TLS is the certificate/key config to connect to ws-daemon
|
|
TLS struct {
|
|
// Authority is the root certificate that was used to sign the certificate itself
|
|
Authority string `json:"ca"`
|
|
// Certificate is the crt file, the actual certificate
|
|
Certificate string `json:"crt"`
|
|
// PrivateKey is the private key in order to use the certificate
|
|
PrivateKey string `json:"key"`
|
|
} `json:"tls"`
|
|
}
|
|
|
|
// Validate validates the configuration to catch issues during startup and not at runtime
|
|
func (c *Configuration) Validate() error {
|
|
err := ozzo.ValidateStruct(&c.Timeouts,
|
|
ozzo.Field(&c.Timeouts.AfterClose, ozzo.Required),
|
|
ozzo.Field(&c.Timeouts.HeadlessWorkspace, ozzo.Required),
|
|
ozzo.Field(&c.Timeouts.Initialization, ozzo.Required),
|
|
ozzo.Field(&c.Timeouts.RegularWorkspace, ozzo.Required),
|
|
ozzo.Field(&c.Timeouts.MaxLifetime, ozzo.Required),
|
|
ozzo.Field(&c.Timeouts.TotalStartup, ozzo.Required),
|
|
ozzo.Field(&c.Timeouts.ContentFinalization, ozzo.Required),
|
|
ozzo.Field(&c.Timeouts.Stopping, ozzo.Required),
|
|
)
|
|
if err != nil {
|
|
return xerrors.Errorf("timeouts: %w", err)
|
|
}
|
|
if c.Timeouts.Stopping < c.Timeouts.ContentFinalization {
|
|
return xerrors.Errorf("stopping timeout must be greater than content finalization timeout")
|
|
}
|
|
|
|
err = ozzo.ValidateStruct(c,
|
|
ozzo.Field(&c.WorkspaceURLTemplate, ozzo.Required, validWorkspaceURLTemplate),
|
|
ozzo.Field(&c.WorkspaceHostPath, ozzo.Required),
|
|
ozzo.Field(&c.HeartbeatInterval, ozzo.Required),
|
|
ozzo.Field(&c.GitpodHostURL, ozzo.Required, is.URL),
|
|
ozzo.Field(&c.ReconnectionInterval, ozzo.Required),
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, ok := c.WorkspaceClasses[DefaultWorkspaceClass]; !ok {
|
|
return xerrors.Errorf("missing \"%s\" workspace class", DefaultWorkspaceClass)
|
|
}
|
|
for name, class := range c.WorkspaceClasses {
|
|
if errs := validation.IsValidLabelValue(name); len(errs) > 0 {
|
|
return xerrors.Errorf("workspace class name \"%s\" is invalid: %v", name, errs)
|
|
}
|
|
if err := class.Container.Validate(); err != nil {
|
|
return xerrors.Errorf("workspace class %s: %w", name, err)
|
|
}
|
|
|
|
err = ozzo.ValidateStruct(&class.Templates,
|
|
ozzo.Field(&class.Templates.DefaultPath, validPodTemplate),
|
|
ozzo.Field(&class.Templates.PrebuildPath, validPodTemplate),
|
|
ozzo.Field(&class.Templates.ProbePath, validPodTemplate),
|
|
ozzo.Field(&class.Templates.RegularPath, validPodTemplate),
|
|
)
|
|
if err != nil {
|
|
return xerrors.Errorf("workspace class %s: %w", name, err)
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
var validPodTemplate = ozzo.By(func(o interface{}) error {
|
|
s, ok := o.(string)
|
|
if !ok {
|
|
return xerrors.Errorf("field should be string")
|
|
}
|
|
|
|
_, err := GetWorkspacePodTemplate(s)
|
|
return err
|
|
})
|
|
|
|
var validWorkspaceURLTemplate = ozzo.By(func(o interface{}) error {
|
|
s, ok := o.(string)
|
|
if !ok {
|
|
return xerrors.Errorf("field should be string")
|
|
}
|
|
|
|
wsurl, err := RenderWorkspaceURL(s, "foo", "bar", "gitpod.io")
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot render URL: %w", err)
|
|
}
|
|
_, err = url.Parse(wsurl)
|
|
if err != nil {
|
|
return xerrors.Errorf("not a valid URL: %w", err)
|
|
}
|
|
|
|
return err
|
|
})
|
|
|
|
// PVCConfiguration configures properties of persistent volume claim to use for workspace containers
|
|
type PVCConfiguration struct {
|
|
Size resource.Quantity `json:"size"`
|
|
StorageClass string `json:"storageClass"`
|
|
SnapshotClass string `json:"snapshotClass"`
|
|
}
|
|
|
|
// Validate validates a PVC configuration
|
|
func (c *PVCConfiguration) Validate() error {
|
|
return ozzo.ValidateStruct(c,
|
|
ozzo.Field(&c.Size, ozzo.Required),
|
|
ozzo.Field(&c.StorageClass, ozzo.Required),
|
|
ozzo.Field(&c.SnapshotClass, ozzo.Required),
|
|
)
|
|
}
|
|
|
|
// ContainerConfiguration configures properties of workspace pod container
|
|
type ContainerConfiguration struct {
|
|
Requests *ResourceRequestConfiguration `json:"requests,omitempty"`
|
|
Limits *ResourceLimitConfiguration `json:"limits,omitempty"`
|
|
}
|
|
|
|
// Validate validates a container configuration
|
|
func (c *ContainerConfiguration) Validate() error {
|
|
return ozzo.ValidateStruct(c,
|
|
ozzo.Field(&c.Requests, validResourceRequestConfig),
|
|
ozzo.Field(&c.Limits, validResourceLimitConfig),
|
|
)
|
|
}
|
|
|
|
var validResourceRequestConfig = ozzo.By(func(o interface{}) error {
|
|
rc, ok := o.(*ResourceRequestConfiguration)
|
|
if !ok {
|
|
return xerrors.Errorf("can only validate ResourceRequestConfiguration")
|
|
}
|
|
if rc == nil {
|
|
return nil
|
|
}
|
|
if rc.CPU != "" {
|
|
_, err := resource.ParseQuantity(rc.CPU)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot parse CPU quantity: %w", err)
|
|
}
|
|
}
|
|
if rc.Memory != "" {
|
|
_, err := resource.ParseQuantity(rc.Memory)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot parse Memory quantity: %w", err)
|
|
}
|
|
}
|
|
if rc.EphemeralStorage != "" {
|
|
_, err := resource.ParseQuantity(rc.EphemeralStorage)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot parse EphemeralStorage quantity: %w", err)
|
|
}
|
|
}
|
|
if rc.Storage != "" {
|
|
_, err := resource.ParseQuantity(rc.Storage)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot parse Storage quantity: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
var validResourceLimitConfig = ozzo.By(func(o interface{}) error {
|
|
rc, ok := o.(*ResourceLimitConfiguration)
|
|
if !ok {
|
|
return xerrors.Errorf("can only validate ResourceLimitConfiguration")
|
|
}
|
|
if rc == nil {
|
|
return nil
|
|
}
|
|
if rc.CPU.MinLimit != "" {
|
|
_, err := resource.ParseQuantity(rc.CPU.MinLimit)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot parse low limit CPU quantity: %w", err)
|
|
}
|
|
}
|
|
if rc.CPU.BurstLimit != "" {
|
|
_, err := resource.ParseQuantity(rc.CPU.BurstLimit)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot parse burst limit CPU quantity: %w", err)
|
|
}
|
|
}
|
|
if rc.Memory != "" {
|
|
_, err := resource.ParseQuantity(rc.Memory)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot parse Memory quantity: %w", err)
|
|
}
|
|
}
|
|
if rc.EphemeralStorage != "" {
|
|
_, err := resource.ParseQuantity(rc.EphemeralStorage)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot parse EphemeralStorage quantity: %w", err)
|
|
}
|
|
}
|
|
if rc.Storage != "" {
|
|
_, err := resource.ParseQuantity(rc.Storage)
|
|
if err != nil {
|
|
return xerrors.Errorf("cannot parse Storage quantity: %w", err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
func (r *ResourceRequestConfiguration) StorageQuantity() (resource.Quantity, error) {
|
|
if r.Storage == "" {
|
|
res := resource.NewQuantity(0, resource.BinarySI)
|
|
return *res, nil
|
|
}
|
|
return resource.ParseQuantity(r.Storage)
|
|
}
|
|
|
|
// ResourceList parses the quantities in the resource config
|
|
func (r *ResourceRequestConfiguration) ResourceList() (corev1.ResourceList, error) {
|
|
if r == nil {
|
|
return corev1.ResourceList{}, nil
|
|
}
|
|
res := map[corev1.ResourceName]string{
|
|
corev1.ResourceCPU: r.CPU,
|
|
corev1.ResourceMemory: r.Memory,
|
|
corev1.ResourceEphemeralStorage: r.EphemeralStorage,
|
|
}
|
|
|
|
var l = make(corev1.ResourceList)
|
|
for k, v := range res {
|
|
if v == "" {
|
|
continue
|
|
}
|
|
|
|
q, err := resource.ParseQuantity(v)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
}
|
|
if q.Value() == 0 {
|
|
continue
|
|
}
|
|
|
|
l[k] = q
|
|
}
|
|
return l, nil
|
|
}
|
|
|
|
// GetWorkspacePodTemplate parses a pod template YAML file. Returns nil if path is empty.
|
|
func GetWorkspacePodTemplate(filename string) (*corev1.Pod, error) {
|
|
if filename == "" {
|
|
return nil, nil
|
|
}
|
|
|
|
tpr := os.Getenv("TELEPRESENCE_ROOT")
|
|
if tpr != "" {
|
|
filename = filepath.Join(tpr, filename)
|
|
}
|
|
|
|
tpl, err := FS.Open(filename)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("cannot read pod template: %w", err)
|
|
}
|
|
defer tpl.Close()
|
|
|
|
var res corev1.Pod
|
|
decoder := yaml.NewYAMLOrJSONDecoder(tpl, 4096)
|
|
err = decoder.Decode(&res)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("cannot unmarshal pod template: %w", err)
|
|
}
|
|
|
|
return &res, nil
|
|
}
|
|
|
|
// RenderWorkspaceURL takes a workspace URL template and renders it
|
|
func RenderWorkspaceURL(urltpl, id, servicePrefix, host string) (string, error) {
|
|
tpl, err := template.New("url").Parse(urltpl)
|
|
if err != nil {
|
|
return "", xerrors.Errorf("cannot compute workspace URL: %w", err)
|
|
}
|
|
|
|
type data struct {
|
|
ID string
|
|
Prefix string
|
|
Host string
|
|
}
|
|
d := data{
|
|
ID: id,
|
|
Prefix: servicePrefix,
|
|
Host: host,
|
|
}
|
|
|
|
var b bytes.Buffer
|
|
err = tpl.Execute(&b, d)
|
|
if err != nil {
|
|
return "", xerrors.Errorf("cannot compute workspace URL: %w", err)
|
|
}
|
|
|
|
return b.String(), nil
|
|
}
|
|
|
|
type PortURLContext struct {
|
|
ID string
|
|
Prefix string
|
|
Host string
|
|
WorkspacePort string
|
|
IngressPort string
|
|
}
|
|
|
|
// RenderWorkspacePortURL takes a workspace port URL template and renders it
|
|
func RenderWorkspacePortURL(urltpl string, ctx PortURLContext) (string, error) {
|
|
tpl, err := template.New("url").Parse(urltpl)
|
|
if err != nil {
|
|
return "", xerrors.Errorf("cannot compute workspace URL: %w", err)
|
|
}
|
|
|
|
var b bytes.Buffer
|
|
err = tpl.Execute(&b, ctx)
|
|
if err != nil {
|
|
return "", xerrors.Errorf("cannot compute workspace port URL: %w", err)
|
|
}
|
|
|
|
return b.String(), nil
|
|
}
|
|
|
|
// ResourceRequestConfiguration configures resources of a pod/container
|
|
type ResourceRequestConfiguration struct {
|
|
CPU string `json:"cpu"`
|
|
Memory string `json:"memory"`
|
|
EphemeralStorage string `json:"ephemeral-storage"`
|
|
Storage string `json:"storage,omitempty"`
|
|
}
|
|
|
|
type ResourceLimitConfiguration struct {
|
|
CPU *CpuResourceLimit `json:"cpu"`
|
|
Memory string `json:"memory"`
|
|
EphemeralStorage string `json:"ephemeral-storage"`
|
|
Storage string `json:"storage,omitempty"`
|
|
}
|
|
|
|
func (r *ResourceLimitConfiguration) ResourceList() (corev1.ResourceList, error) {
|
|
if r == nil {
|
|
return corev1.ResourceList{}, nil
|
|
}
|
|
res := map[corev1.ResourceName]string{
|
|
corev1.ResourceMemory: r.Memory,
|
|
corev1.ResourceEphemeralStorage: r.EphemeralStorage,
|
|
}
|
|
|
|
if r.CPU != nil {
|
|
res[corev1.ResourceCPU] = r.CPU.BurstLimit
|
|
}
|
|
|
|
var l = make(corev1.ResourceList)
|
|
for k, v := range res {
|
|
if v == "" {
|
|
continue
|
|
}
|
|
|
|
q, err := resource.ParseQuantity(v)
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("%s: %w", k, err)
|
|
}
|
|
if q.Value() == 0 {
|
|
continue
|
|
}
|
|
|
|
l[k] = q
|
|
}
|
|
return l, nil
|
|
}
|
|
|
|
func (r *ResourceLimitConfiguration) StorageQuantity() (resource.Quantity, error) {
|
|
if r.Storage == "" {
|
|
res := resource.NewQuantity(0, resource.BinarySI)
|
|
return *res, nil
|
|
}
|
|
return resource.ParseQuantity(r.Storage)
|
|
}
|
|
|
|
type CpuResourceLimit struct {
|
|
MinLimit string `json:"min"`
|
|
BurstLimit string `json:"burst"`
|
|
}
|