// Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 package runtimeutil import ( "fmt" "os" "sort" "strings" "sigs.k8s.io/kustomize/kyaml/yaml" ) const ( FunctionAnnotationKey = "config.kubernetes.io/function" oldFunctionAnnotationKey = "config.k8s.io/function" ) var functionAnnotationKeys = []string{FunctionAnnotationKey, oldFunctionAnnotationKey} // ContainerNetworkName is a type for network name used in container type ContainerNetworkName string const ( NetworkNameNone ContainerNetworkName = "none" NetworkNameHost ContainerNetworkName = "host" ) const defaultEnvValue string = "true" // ContainerEnv defines the environment present in a container. type ContainerEnv struct { // EnvVars is a key-value map that will be set as env in container EnvVars map[string]string // VarsToExport are only env key. Value will be the value in the host system VarsToExport []string } // GetDockerFlags returns docker run style env flags func (ce *ContainerEnv) GetDockerFlags() []string { envs := ce.EnvVars if envs == nil { envs = make(map[string]string) } flags := []string{} // return in order to keep consistent among different runs keys := []string{} for k := range envs { keys = append(keys, k) } sort.Strings(keys) for _, key := range keys { flags = append(flags, "-e", key+"="+envs[key]) } for _, key := range ce.VarsToExport { flags = append(flags, "-e", key) } return flags } // AddKeyValue adds a key-value pair into the envs func (ce *ContainerEnv) AddKeyValue(key, value string) { if ce.EnvVars == nil { ce.EnvVars = make(map[string]string) } ce.EnvVars[key] = value } // HasExportedKey returns true if the key is a exported key func (ce *ContainerEnv) HasExportedKey(key string) bool { for _, k := range ce.VarsToExport { if k == key { return true } } return false } // AddKey adds a key into the envs func (ce *ContainerEnv) AddKey(key string) { if !ce.HasExportedKey(key) { ce.VarsToExport = append(ce.VarsToExport, key) } } // Raw returns a slice of string which represents the envs. // Example: [foo=bar, baz] func (ce *ContainerEnv) Raw() []string { var ret []string for k, v := range ce.EnvVars { ret = append(ret, k+"="+v) } ret = append(ret, ce.VarsToExport...) return ret } // NewContainerEnv returns a pointer to a new ContainerEnv func NewContainerEnv() *ContainerEnv { var ce ContainerEnv ce.EnvVars = make(map[string]string) // default envs ce.EnvVars["LOG_TO_STDERR"] = defaultEnvValue ce.EnvVars["STRUCTURED_RESULTS"] = defaultEnvValue return &ce } // NewContainerEnvFromStringSlice returns a new ContainerEnv pointer with parsing // input envStr. envStr example: ["foo=bar", "baz"] func NewContainerEnvFromStringSlice(envStr []string) *ContainerEnv { ce := NewContainerEnv() for _, e := range envStr { parts := strings.SplitN(e, "=", 2) if len(parts) == 1 { ce.AddKey(e) } else { ce.AddKeyValue(parts[0], parts[1]) } } return ce } // FunctionSpec defines a spec for running a function type FunctionSpec struct { DeferFailure bool `json:"deferFailure,omitempty" yaml:"deferFailure,omitempty"` // Container is the spec for running a function as a container Container ContainerSpec `json:"container,omitempty" yaml:"container,omitempty"` // Starlark is the spec for running a function as a starlark script Starlark StarlarkSpec `json:"starlark,omitempty" yaml:"starlark,omitempty"` // ExecSpec is the spec for running a function as an executable Exec ExecSpec `json:"exec,omitempty" yaml:"exec,omitempty"` } type ExecSpec struct { Path string `json:"path,omitempty" yaml:"path,omitempty"` } // ContainerSpec defines a spec for running a function as a container type ContainerSpec struct { // Image is the container image to run Image string `json:"image,omitempty" yaml:"image,omitempty"` // Network defines network specific configuration Network bool `json:"network,omitempty" yaml:"network,omitempty"` // Mounts are the storage or directories to mount into the container StorageMounts []StorageMount `json:"mounts,omitempty" yaml:"mounts,omitempty"` // Env is a slice of env string that will be exposed to container Env []string `json:"envs,omitempty" yaml:"envs,omitempty"` } // StarlarkSpec defines how to run a function as a starlark program type StarlarkSpec struct { Name string `json:"name,omitempty" yaml:"name,omitempty"` // Path specifies a path to a starlark script Path string `json:"path,omitempty" yaml:"path,omitempty"` // URL specifies a url containing a starlark script URL string `json:"url,omitempty" yaml:"url,omitempty"` } // StorageMount represents a container's mounted storage option(s) type StorageMount struct { // Type of mount e.g. bind mount, local volume, etc. MountType string `json:"type,omitempty" yaml:"type,omitempty"` // Source for the storage to be mounted. // For named volumes, this is the name of the volume. // For anonymous volumes, this field is omitted (empty string). // For bind mounts, this is the path to the file or directory on the host. Src string `json:"src,omitempty" yaml:"src,omitempty"` // The path where the file or directory is mounted in the container. DstPath string `json:"dst,omitempty" yaml:"dst,omitempty"` // Mount in ReadWrite mode if it's explicitly configured // See https://docs.docker.com/storage/bind-mounts/#use-a-read-only-bind-mount ReadWriteMode bool `json:"rw,omitempty" yaml:"rw,omitempty"` } func (s *StorageMount) String() string { mode := "" if !s.ReadWriteMode { mode = ",readonly" } return fmt.Sprintf("type=%s,source=%s,target=%s%s", s.MountType, s.Src, s.DstPath, mode) } // GetFunctionSpec returns the FunctionSpec for a resource. Returns // nil if the resource does not have a FunctionSpec. // // The FunctionSpec is read from the resource metadata.annotation // "config.kubernetes.io/function" func GetFunctionSpec(n *yaml.RNode) *FunctionSpec { meta, err := n.GetMeta() if err != nil { return nil } if fn := getFunctionSpecFromAnnotation(n, meta); fn != nil { return fn } // legacy function specification for backwards compatibility container := meta.Annotations["config.kubernetes.io/container"] if container != "" { return &FunctionSpec{Container: ContainerSpec{Image: container}} } return nil } // getFunctionSpecFromAnnotation parses the config function from an annotation // if it is found func getFunctionSpecFromAnnotation(n *yaml.RNode, meta yaml.ResourceMeta) *FunctionSpec { var fs FunctionSpec for _, s := range functionAnnotationKeys { fn := meta.Annotations[s] if fn != "" { err := yaml.Unmarshal([]byte(fn), &fs) if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) } return &fs } } n, err := n.Pipe(yaml.Lookup("metadata", "configFn")) if err != nil || yaml.IsMissingOrNull(n) { return nil } s, err := n.String() if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) } err = yaml.Unmarshal([]byte(s), &fs) if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) } return &fs } func StringToStorageMount(s string) StorageMount { m := make(map[string]string) options := strings.Split(s, ",") for _, option := range options { keyVal := strings.SplitN(option, "=", 2) if len(keyVal) == 2 { m[keyVal[0]] = keyVal[1] } } var sm StorageMount for key, value := range m { switch { case key == "type": sm.MountType = value case key == "src" || key == "source": sm.Src = value case key == "dst" || key == "target": sm.DstPath = value case key == "rw" && value == "true": sm.ReadWriteMode = true } } return sm } // IsReconcilerFilter filters Resources based on whether or not they are Reconciler Resource. // Resources with an apiVersion starting with '*.gcr.io', 'gcr.io' or 'docker.io' are considered // Reconciler Resources. type IsReconcilerFilter struct { // ExcludeReconcilers if set to true, then Reconcilers will be excluded -- e.g. // Resources with a reconcile container through the apiVersion (gcr.io prefix) or // through the annotations ExcludeReconcilers bool `yaml:"excludeReconcilers,omitempty"` // IncludeNonReconcilers if set to true, the NonReconciler will be included. IncludeNonReconcilers bool `yaml:"includeNonReconcilers,omitempty"` } // Filter implements kio.Filter func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) { var out []*yaml.RNode for i := range inputs { isFnResource := GetFunctionSpec(inputs[i]) != nil if isFnResource && !c.ExcludeReconcilers { out = append(out, inputs[i]) } if !isFnResource && c.IncludeNonReconcilers { out = append(out, inputs[i]) } } return out, nil }