mirror of https://github.com/knative/func.git
567 lines
21 KiB
Go
567 lines
21 KiB
Go
package function
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v2"
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
|
"knative.dev/kn-plugin-func/utils"
|
|
)
|
|
|
|
// ConfigFile is the name of the config's serialized form.
|
|
const ConfigFile = "func.yaml"
|
|
|
|
var (
|
|
regWholeSecret = regexp.MustCompile(`^{{\s*secret:((?:\w|['-]\w)+)\s*}}$`)
|
|
regKeyFromSecret = regexp.MustCompile(`^{{\s*secret:((?:\w|['-]\w)+):(\w+)\s*}}$`)
|
|
regWholeConfigMap = regexp.MustCompile(`^{{\s*configMap:((?:\w|['-]\w)+)\s*}}$`)
|
|
regKeyFromConfigMap = regexp.MustCompile(`^{{\s*configMap:((?:\w|['-]\w)+):(\w+)\s*}}$`)
|
|
regLocalEnv = regexp.MustCompile(`^{{\s*env:(\w+)\s*}}$`)
|
|
)
|
|
|
|
type Volumes []Volume
|
|
type Volume struct {
|
|
Secret *string `yaml:"secret,omitempty" jsonschema:"oneof_required=secret"`
|
|
ConfigMap *string `yaml:"configMap,omitempty" jsonschema:"oneof_required=configmap"`
|
|
Path *string `yaml:"path"`
|
|
}
|
|
|
|
func (v Volume) String() string {
|
|
if v.ConfigMap != nil {
|
|
return fmt.Sprintf("ConfigMap \"%s\" mounted at path: \"%s\"", *v.ConfigMap, *v.Path)
|
|
} else if v.Secret != nil {
|
|
return fmt.Sprintf("Secret \"%s\" mounted at path: \"%s\"", *v.Secret, *v.Path)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
type Envs []Env
|
|
type Env struct {
|
|
Name *string `yaml:"name,omitempty" jsonschema:"pattern=^[-._a-zA-Z][-._a-zA-Z0-9]*$"`
|
|
Value *string `yaml:"value"`
|
|
}
|
|
|
|
func (e Env) String() string {
|
|
if e.Name == nil && e.Value != nil {
|
|
match := regWholeSecret.FindStringSubmatch(*e.Value)
|
|
if len(match) == 2 {
|
|
return fmt.Sprintf("All key=value pairs from Secret \"%s\"", match[1])
|
|
}
|
|
match = regWholeConfigMap.FindStringSubmatch(*e.Value)
|
|
if len(match) == 2 {
|
|
return fmt.Sprintf("All key=value pairs from ConfigMap \"%s\"", match[1])
|
|
}
|
|
} else if e.Name != nil && e.Value != nil {
|
|
match := regKeyFromSecret.FindStringSubmatch(*e.Value)
|
|
if len(match) == 3 {
|
|
return fmt.Sprintf("Env \"%s\" with value set from key \"%s\" from Secret \"%s\"", *e.Name, match[2], match[1])
|
|
}
|
|
match = regKeyFromConfigMap.FindStringSubmatch(*e.Value)
|
|
if len(match) == 3 {
|
|
return fmt.Sprintf("Env \"%s\" with value set from key \"%s\" from ConfigMap \"%s\"", *e.Name, match[2], match[1])
|
|
}
|
|
match = regLocalEnv.FindStringSubmatch(*e.Value)
|
|
if len(match) == 2 {
|
|
return fmt.Sprintf("Env \"%s\" with value set from local env variable \"%s\"", *e.Name, match[1])
|
|
}
|
|
|
|
return fmt.Sprintf("Env \"%s\" with value \"%s\"", *e.Name, *e.Value)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Labels []Label
|
|
type Label struct {
|
|
// Key consist of optional prefix part (ended by '/') and name part
|
|
// Prefix part validation pattern: [a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*
|
|
// Name part validation pattern: ([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]
|
|
Key *string `yaml:"key" jsonschema:"pattern=^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\\/)?([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$"`
|
|
Value *string `yaml:"value,omitempty" jsonschema:"pattern=^(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$"`
|
|
}
|
|
|
|
func (l Label) String() string {
|
|
if l.Key != nil && l.Value == nil {
|
|
return fmt.Sprintf("Label with key \"%s\"", *l.Key)
|
|
} else if l.Key != nil && l.Value != nil {
|
|
match := regLocalEnv.FindStringSubmatch(*l.Value)
|
|
if len(match) == 2 {
|
|
return fmt.Sprintf("Label with key \"%s\" and value set from local env variable \"%s\"", *l.Key, match[1])
|
|
}
|
|
return fmt.Sprintf("Label with key \"%s\" and value \"%s\"", *l.Key, *l.Value)
|
|
}
|
|
return ""
|
|
}
|
|
|
|
type Options struct {
|
|
Scale *ScaleOptions `yaml:"scale,omitempty"`
|
|
Resources *ResourcesOptions `yaml:"resources,omitempty"`
|
|
}
|
|
|
|
type ScaleOptions struct {
|
|
Min *int64 `yaml:"min,omitempty" jsonschema_extras:"minimum=0"`
|
|
Max *int64 `yaml:"max,omitempty" jsonschema_extras:"minimum=0"`
|
|
Metric *string `yaml:"metric,omitempty" jsonschema:"enum=concurrency,enum=rps"`
|
|
Target *float64 `yaml:"target,omitempty" jsonschema_extras:"minimum=0.01"`
|
|
Utilization *float64 `yaml:"utilization,omitempty" jsonschema:"minimum=1,maximum=100"`
|
|
}
|
|
|
|
type ResourcesOptions struct {
|
|
Requests *ResourcesRequestsOptions `yaml:"requests,omitempty"`
|
|
Limits *ResourcesLimitsOptions `yaml:"limits,omitempty"`
|
|
}
|
|
|
|
type ResourcesLimitsOptions struct {
|
|
CPU *string `yaml:"cpu,omitempty" jsonschema:"pattern=^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$"`
|
|
Memory *string `yaml:"memory,omitempty" jsonschema:"pattern=^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$"`
|
|
Concurrency *int64 `yaml:"concurrency,omitempty" jsonschema_extras:"minimum=0"`
|
|
}
|
|
|
|
type ResourcesRequestsOptions struct {
|
|
CPU *string `yaml:"cpu,omitempty" jsonschema:"pattern=^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$"`
|
|
Memory *string `yaml:"memory,omitempty" jsonschema:"pattern=^([+-]?[0-9.]+)([eEinumkKMGTP]*[-+]?[0-9]*)$"`
|
|
}
|
|
|
|
// Config represents the serialized state of a Function's metadata.
|
|
// See the Function struct for attribute documentation.
|
|
type Config struct {
|
|
Name string `yaml:"name" jsonschema:"pattern=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"`
|
|
Namespace string `yaml:"namespace"`
|
|
Runtime string `yaml:"runtime"`
|
|
Image string `yaml:"image"`
|
|
ImageDigest string `yaml:"imageDigest"`
|
|
Builder string `yaml:"builder"`
|
|
Builders map[string]string `yaml:"builders"`
|
|
Buildpacks []string `yaml:"buildpacks"`
|
|
HealthEndpoints map[string]string `yaml:"healthEndpoints"`
|
|
Volumes Volumes `yaml:"volumes"`
|
|
BuildEnvs Envs `yaml:"buildEnvs"`
|
|
Envs Envs `yaml:"envs"`
|
|
Annotations map[string]string `yaml:"annotations"`
|
|
Options Options `yaml:"options"`
|
|
Labels Labels `yaml:"labels"`
|
|
// Add new values to the toConfig/fromConfig functions.
|
|
}
|
|
|
|
// newConfig returns a Config populated from data serialized to disk if it is
|
|
// available. Errors are returned if the path is not valid, if there are
|
|
// errors accessing an extant config file, or the contents of the file do not
|
|
// unmarshall. A missing file at a valid path does not error but returns the
|
|
// empty value of Config.
|
|
func newConfig(root string) (c Config, err error) {
|
|
filename := filepath.Join(root, ConfigFile)
|
|
if _, err = os.Stat(filename); err != nil {
|
|
// do not consider a missing config file an error. Just return.
|
|
if os.IsNotExist(err) {
|
|
err = nil
|
|
}
|
|
return
|
|
}
|
|
bb, err := ioutil.ReadFile(filename)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
errMsg := ""
|
|
errMsgHeader := "'func.yaml' config file is not valid:\n"
|
|
errMsgReg := regexp.MustCompile("not found in type .*")
|
|
|
|
// Let's try to unmarshal the config file, any fields that are found
|
|
// in the data that do not have corresponding struct members, or mapping
|
|
// keys that are duplicates, will result in an error.
|
|
err = yaml.UnmarshalStrict(bb, &c)
|
|
if err != nil {
|
|
errMsg = err.Error()
|
|
|
|
if strings.HasPrefix(errMsg, "yaml: unmarshal errors:") {
|
|
errMsg = errMsgReg.ReplaceAllString(errMsg, "is not valid")
|
|
errMsg = strings.Replace(errMsg, "yaml: unmarshal errors:\n", errMsgHeader, 1)
|
|
} else if strings.HasPrefix(errMsg, "yaml:") {
|
|
errMsg = errMsgReg.ReplaceAllString(errMsg, "is not valid")
|
|
errMsg = strings.Replace(errMsg, "yaml: ", errMsgHeader+" ", 1)
|
|
}
|
|
}
|
|
|
|
// Let's check that all entries in `volumes`, `buildEnvs`, `envs` and `options` contain all required fields
|
|
volumesErrors := validateVolumes(c.Volumes)
|
|
buildEnvsErrors := ValidateBuildEnvs((c.BuildEnvs))
|
|
envsErrors := ValidateEnvs(c.Envs)
|
|
optionsErrors := validateOptions(c.Options)
|
|
labelsErrors := ValidateLabels(c.Labels)
|
|
if len(volumesErrors) > 0 || len(buildEnvsErrors) > 0 || len(envsErrors) > 0 || len(optionsErrors) > 0 || len(labelsErrors) > 0 {
|
|
// if there aren't any previously reported errors, we need to set the error message header first
|
|
if errMsg == "" {
|
|
errMsg = errMsgHeader
|
|
} else {
|
|
// if there are some previously reporeted errors, we need to indent them
|
|
errMsg = errMsg + "\n"
|
|
}
|
|
|
|
// lets make the error message a little bit nice -> indent each error message
|
|
for i := range volumesErrors {
|
|
volumesErrors[i] = " " + volumesErrors[i]
|
|
}
|
|
for i := range buildEnvsErrors {
|
|
buildEnvsErrors[i] = " " + buildEnvsErrors[i]
|
|
}
|
|
for i := range envsErrors {
|
|
envsErrors[i] = " " + envsErrors[i]
|
|
}
|
|
for i := range optionsErrors {
|
|
optionsErrors[i] = " " + optionsErrors[i]
|
|
}
|
|
for i := range labelsErrors {
|
|
labelsErrors[i] = " " + labelsErrors[i]
|
|
}
|
|
errMsg = errMsg + strings.Join(volumesErrors, "\n")
|
|
// we have errors from both volumes and buildEnvs sections -> let's make sure they are both indented
|
|
if len(buildEnvsErrors) > 0 && len(volumesErrors) > 0 {
|
|
errMsg = errMsg + "\n"
|
|
}
|
|
errMsg = errMsg + strings.Join(buildEnvsErrors, "\n")
|
|
// we have errors from volumes, buildEnvs and envs sections -> let's make sure they are indented
|
|
if len(envsErrors) > 0 && (len(volumesErrors) > 0 || len(buildEnvsErrors) > 0) {
|
|
errMsg = errMsg + "\n"
|
|
}
|
|
errMsg = errMsg + strings.Join(envsErrors, "\n")
|
|
// lets indent options related errors if there are already some set
|
|
if len(optionsErrors) > 0 && (len(volumesErrors) > 0 || len(buildEnvsErrors) > 0 || len(envsErrors) > 0) {
|
|
errMsg = errMsg + "\n"
|
|
}
|
|
errMsg = errMsg + strings.Join(optionsErrors, "\n")
|
|
// now also handle labels related errors
|
|
if len(labelsErrors) > 0 && (len(optionsErrors) > 0 || len(volumesErrors) > 0 || len(buildEnvsErrors) > 0 || len(envsErrors) > 0) {
|
|
errMsg = errMsg + "\n"
|
|
}
|
|
errMsg = errMsg + strings.Join(labelsErrors, "\n")
|
|
}
|
|
|
|
if errMsg != "" {
|
|
err = errors.New(errMsg)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// fromConfig returns a Function populated from config.
|
|
// Note that config does not include ancillary fields not serialized, such as Root.
|
|
func fromConfig(c Config) (f Function) {
|
|
return Function{
|
|
Name: c.Name,
|
|
Namespace: c.Namespace,
|
|
Runtime: c.Runtime,
|
|
Image: c.Image,
|
|
ImageDigest: c.ImageDigest,
|
|
Builder: c.Builder,
|
|
Builders: c.Builders,
|
|
Buildpacks: c.Buildpacks,
|
|
HealthEndpoints: c.HealthEndpoints,
|
|
Volumes: c.Volumes,
|
|
BuildEnvs: c.BuildEnvs,
|
|
Envs: c.Envs,
|
|
Annotations: c.Annotations,
|
|
Options: c.Options,
|
|
Labels: c.Labels,
|
|
}
|
|
}
|
|
|
|
// toConfig serializes a Function to a config object.
|
|
func toConfig(f Function) Config {
|
|
return Config{
|
|
Name: f.Name,
|
|
Namespace: f.Namespace,
|
|
Runtime: f.Runtime,
|
|
Image: f.Image,
|
|
ImageDigest: f.ImageDigest,
|
|
Builder: f.Builder,
|
|
Builders: f.Builders,
|
|
Buildpacks: f.Buildpacks,
|
|
HealthEndpoints: f.HealthEndpoints,
|
|
Volumes: f.Volumes,
|
|
BuildEnvs: f.BuildEnvs,
|
|
Envs: f.Envs,
|
|
Annotations: f.Annotations,
|
|
Options: f.Options,
|
|
Labels: f.Labels,
|
|
}
|
|
}
|
|
|
|
// writeConfig for the given Function out to disk at root.
|
|
func writeConfig(f Function) (err error) {
|
|
path := filepath.Join(f.Root, ConfigFile)
|
|
c := toConfig(f)
|
|
var bb []byte
|
|
if bb, err = yaml.Marshal(&c); err != nil {
|
|
return
|
|
}
|
|
return ioutil.WriteFile(path, bb, 0644)
|
|
}
|
|
|
|
// validateVolumes checks that input Volumes are correct and contain all necessary fields.
|
|
// Returns array of error messages, empty if no errors are found
|
|
//
|
|
// Allowed settings:
|
|
// - secret: example-secret # mount Secret as Volume
|
|
// path: /etc/secret-volume
|
|
// - configMap: example-configMap # mount ConfigMap as Volume
|
|
// path: /etc/configMap-volume
|
|
func validateVolumes(volumes Volumes) (errors []string) {
|
|
|
|
for i, vol := range volumes {
|
|
if vol.Secret != nil && vol.ConfigMap != nil {
|
|
errors = append(errors, fmt.Sprintf("volume entry #%d is not properly set, both secret '%s' and configMap '%s' can not be set at the same time",
|
|
i, *vol.Secret, *vol.ConfigMap))
|
|
} else if vol.Path == nil && vol.Secret == nil && vol.ConfigMap == nil {
|
|
errors = append(errors, fmt.Sprintf("volume entry #%d is not properly set", i))
|
|
} else if vol.Path == nil {
|
|
if vol.Secret != nil {
|
|
errors = append(errors, fmt.Sprintf("volume entry #%d is missing path field, only secret '%s' is set", i, *vol.Secret))
|
|
} else if vol.ConfigMap != nil {
|
|
errors = append(errors, fmt.Sprintf("volume entry #%d is missing path field, only configMap '%s' is set", i, *vol.ConfigMap))
|
|
}
|
|
} else if vol.Path != nil && vol.Secret == nil && vol.ConfigMap == nil {
|
|
errors = append(errors, fmt.Sprintf("volume entry #%d is missing secret or configMap field, only path '%s' is set", i, *vol.Path))
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// ValidateBuildEnvs checks that input BuildEnvs are correct and contain all necessary fields.
|
|
// Returns array of error messages, empty if no errors are found
|
|
//
|
|
// Allowed settings:
|
|
// - name: EXAMPLE1 # ENV directly from a value
|
|
// value: value1
|
|
// - name: EXAMPLE2 # ENV from the local ENV var
|
|
// value: {{ env:MY_ENV }}
|
|
func ValidateBuildEnvs(envs Envs) (errors []string) {
|
|
for i, env := range envs {
|
|
if env.Name == nil && env.Value == nil {
|
|
errors = append(errors, fmt.Sprintf("env entry #%d is not properly set", i))
|
|
} else if env.Value == nil {
|
|
errors = append(errors, fmt.Sprintf("env entry #%d is missing value field, only name '%s' is set", i, *env.Name))
|
|
} else {
|
|
|
|
if err := utils.ValidateEnvVarName(*env.Name); err != nil {
|
|
errors = append(errors, fmt.Sprintf("env entry #%d has invalid name set: %q; %s", i, *env.Name, err.Error()))
|
|
}
|
|
|
|
if strings.HasPrefix(*env.Value, "{{") {
|
|
// ENV from the local ENV var; {{ env:MY_ENV }}
|
|
if !regLocalEnv.MatchString(*env.Value) {
|
|
errors = append(errors,
|
|
fmt.Sprintf(
|
|
"env entry #%d with name '%s' has invalid value field set, it has '%s', but allowed is only '{{ env:MY_ENV }}'",
|
|
i, *env.Name, *env.Value))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// ValidateEnvs checks that input Envs are correct and contain all necessary fields.
|
|
// Returns array of error messages, empty if no errors are found
|
|
//
|
|
// Allowed settings:
|
|
// - name: EXAMPLE1 # ENV directly from a value
|
|
// value: value1
|
|
// - name: EXAMPLE2 # ENV from the local ENV var
|
|
// value: {{ env:MY_ENV }}
|
|
// - name: EXAMPLE3
|
|
// value: {{ secret:secretName:key }} # ENV from a key in secret
|
|
// - value: {{ secret:secretName }} # all key-pair values from secret are set as ENV
|
|
// - name: EXAMPLE4
|
|
// value: {{ configMap:configMapName:key }} # ENV from a key in configMap
|
|
// - value: {{ configMap:configMapName }} # all key-pair values from configMap are set as ENV
|
|
func ValidateEnvs(envs Envs) (errors []string) {
|
|
for i, env := range envs {
|
|
if env.Name == nil && env.Value == nil {
|
|
errors = append(errors, fmt.Sprintf("env entry #%d is not properly set", i))
|
|
} else if env.Value == nil {
|
|
errors = append(errors, fmt.Sprintf("env entry #%d is missing value field, only name '%s' is set", i, *env.Name))
|
|
} else if env.Name == nil {
|
|
// all key-pair values from secret are set as ENV; {{ secret:secretName }} or {{ configMap:configMapName }}
|
|
if !regWholeSecret.MatchString(*env.Value) && !regWholeConfigMap.MatchString(*env.Value) {
|
|
errors = append(errors, fmt.Sprintf("env entry #%d has invalid value field set, it has '%s', but allowed is only '{{ secret:secretName }}' or '{{ configMap:configMapName }}'",
|
|
i, *env.Value))
|
|
}
|
|
} else {
|
|
|
|
if err := utils.ValidateEnvVarName(*env.Name); err != nil {
|
|
errors = append(errors, fmt.Sprintf("env entry #%d has invalid name set: %q; %s", i, *env.Name, err.Error()))
|
|
}
|
|
|
|
if strings.HasPrefix(*env.Value, "{{") {
|
|
// ENV from the local ENV var; {{ env:MY_ENV }}
|
|
// or
|
|
// ENV from a key in secret/configMap; {{ secret:secretName:key }} or {{ configMap:configMapName:key }}
|
|
if !regLocalEnv.MatchString(*env.Value) && !regKeyFromSecret.MatchString(*env.Value) && !regKeyFromConfigMap.MatchString(*env.Value) {
|
|
errors = append(errors,
|
|
fmt.Sprintf(
|
|
"env entry #%d with name '%s' has invalid value field set, it has '%s', but allowed is only '{{ env:MY_ENV }}', '{{ secret:secretName:key }}' or '{{ configMap:configMapName:key }}'",
|
|
i, *env.Name, *env.Value))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// ValidateLabels checks that input labels are correct and contain all necessary fields.
|
|
// Returns array of error messages, empty if no errors are found
|
|
//
|
|
// Allowed settings:
|
|
// - key: EXAMPLE1 # label directly from a value
|
|
// value: value1
|
|
// - key: EXAMPLE2 # label from the local ENV var
|
|
// value: {{ env:MY_ENV }}
|
|
func ValidateLabels(labels Labels) (errors []string) {
|
|
for i, label := range labels {
|
|
if label.Key == nil && label.Value == nil {
|
|
errors = append(errors, fmt.Sprintf("label entry #%d is not properly set", i))
|
|
} else if label.Key == nil && label.Value != nil {
|
|
errors = append(errors, fmt.Sprintf("label entry #%d is missing key field, only value '%s' is set", i, *label.Value))
|
|
} else {
|
|
if err := utils.ValidateLabelKey(*label.Key); err != nil {
|
|
errors = append(errors, fmt.Sprintf("label entry #%d has invalid key set: %q; %s", i, *label.Key, err.Error()))
|
|
}
|
|
if label.Value != nil {
|
|
if err := utils.ValidateLabelValue(*label.Value); err != nil {
|
|
errors = append(errors, fmt.Sprintf("label entry #%d has invalid value set: %q; %s", i, *label.Value, err.Error()))
|
|
}
|
|
|
|
if strings.HasPrefix(*label.Value, "{{") {
|
|
// ENV from the local ENV var; {{ env:MY_ENV }}
|
|
if !regLocalEnv.MatchString(*label.Value) {
|
|
errors = append(errors,
|
|
fmt.Sprintf(
|
|
"label entry #%d with key '%s' has invalid value field set, it has '%s', but allowed is only '{{ env:MY_ENV }}'",
|
|
i, *label.Key, *label.Value))
|
|
} else {
|
|
match := regLocalEnv.FindStringSubmatch(*label.Value)
|
|
value := os.Getenv(match[1])
|
|
if err := utils.ValidateLabelValue(value); err != nil {
|
|
errors = append(errors, fmt.Sprintf("label entry #%d with key '%s' has invalid value when the environment is evaluated: '%s': %s", i, *label.Key, value, err.Error()))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// validateOptions checks that input Options are correctly set.
|
|
// Returns array of error messages, empty if no errors are found
|
|
func validateOptions(options Options) (errors []string) {
|
|
|
|
// options.scale
|
|
if options.Scale != nil {
|
|
if options.Scale.Min != nil {
|
|
if *options.Scale.Min < 0 {
|
|
errors = append(errors, fmt.Sprintf("options field \"scale.min\" has invalid value set: %d, the value must be greater than \"0\"",
|
|
*options.Scale.Min))
|
|
}
|
|
}
|
|
|
|
if options.Scale.Max != nil {
|
|
if *options.Scale.Max < 0 {
|
|
errors = append(errors, fmt.Sprintf("options field \"scale.max\" has invalid value set: %d, the value must be greater than \"0\"",
|
|
*options.Scale.Max))
|
|
}
|
|
}
|
|
|
|
if options.Scale.Min != nil && options.Scale.Max != nil {
|
|
if *options.Scale.Max < *options.Scale.Min {
|
|
errors = append(errors, "options field \"scale.max\" value must be greater or equal to \"scale.min\"")
|
|
}
|
|
}
|
|
|
|
if options.Scale.Metric != nil {
|
|
if *options.Scale.Metric != "concurrency" && *options.Scale.Metric != "rps" {
|
|
errors = append(errors, fmt.Sprintf("options field \"scale.metric\" has invalid value set: %s, allowed is only \"concurrency\" or \"rps\"",
|
|
*options.Scale.Metric))
|
|
}
|
|
}
|
|
|
|
if options.Scale.Target != nil {
|
|
if *options.Scale.Target < 0.01 {
|
|
errors = append(errors, fmt.Sprintf("options field \"scale.target\" has value set to \"%f\", but it must not be less than 0.01",
|
|
*options.Scale.Target))
|
|
}
|
|
}
|
|
|
|
if options.Scale.Utilization != nil {
|
|
if *options.Scale.Utilization < 1 || *options.Scale.Utilization > 100 {
|
|
errors = append(errors,
|
|
fmt.Sprintf("options field \"scale.utilization\" has value set to \"%f\", but it must not be less than 1 or greater than 100",
|
|
*options.Scale.Utilization))
|
|
}
|
|
}
|
|
}
|
|
|
|
// options.resource
|
|
if options.Resources != nil {
|
|
|
|
// options.resource.requests
|
|
if options.Resources.Requests != nil {
|
|
|
|
if options.Resources.Requests.CPU != nil {
|
|
_, err := resource.ParseQuantity(*options.Resources.Requests.CPU)
|
|
if err != nil {
|
|
errors = append(errors, fmt.Sprintf("options field \"resources.requests.cpu\" has invalid value set: \"%s\"; \"%s\"",
|
|
*options.Resources.Requests.CPU, err.Error()))
|
|
}
|
|
}
|
|
|
|
if options.Resources.Requests.Memory != nil {
|
|
_, err := resource.ParseQuantity(*options.Resources.Requests.Memory)
|
|
if err != nil {
|
|
errors = append(errors, fmt.Sprintf("options field \"resources.requests.memory\" has invalid value set: \"%s\"; \"%s\"",
|
|
*options.Resources.Requests.Memory, err.Error()))
|
|
}
|
|
}
|
|
}
|
|
|
|
// options.resource.limits
|
|
if options.Resources.Limits != nil {
|
|
|
|
if options.Resources.Limits.CPU != nil {
|
|
_, err := resource.ParseQuantity(*options.Resources.Limits.CPU)
|
|
if err != nil {
|
|
errors = append(errors, fmt.Sprintf("options field \"resources.limits.cpu\" has invalid value set: \"%s\"; \"%s\"",
|
|
*options.Resources.Limits.CPU, err.Error()))
|
|
}
|
|
}
|
|
|
|
if options.Resources.Limits.Memory != nil {
|
|
_, err := resource.ParseQuantity(*options.Resources.Limits.Memory)
|
|
if err != nil {
|
|
errors = append(errors, fmt.Sprintf("options field \"resources.limits.memory\" has invalid value set: \"%s\"; \"%s\"",
|
|
*options.Resources.Limits.Memory, err.Error()))
|
|
}
|
|
}
|
|
|
|
if options.Resources.Limits.Concurrency != nil {
|
|
if *options.Resources.Limits.Concurrency < 0 {
|
|
errors = append(errors, fmt.Sprintf("options field \"resources.limits.concurrency\" has value set to \"%d\", but it must not be less than 0",
|
|
*options.Resources.Limits.Concurrency))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|