...

Source file src/sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil/functiontypes.go

Documentation: sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package runtimeutil
     5  
     6  import (
     7  	"fmt"
     8  	"os"
     9  	"sort"
    10  	"strings"
    11  
    12  	"sigs.k8s.io/kustomize/kyaml/yaml"
    13  	k8syaml "sigs.k8s.io/yaml"
    14  )
    15  
    16  const (
    17  	FunctionAnnotationKey    = "config.kubernetes.io/function"
    18  	oldFunctionAnnotationKey = "config.k8s.io/function"
    19  )
    20  
    21  var functionAnnotationKeys = []string{FunctionAnnotationKey, oldFunctionAnnotationKey}
    22  
    23  // ContainerNetworkName is a type for network name used in container
    24  type ContainerNetworkName string
    25  
    26  const (
    27  	NetworkNameNone ContainerNetworkName = "none"
    28  	NetworkNameHost ContainerNetworkName = "host"
    29  )
    30  const defaultEnvValue string = "true"
    31  
    32  // ContainerEnv defines the environment present in a container.
    33  type ContainerEnv struct {
    34  	// EnvVars is a key-value map that will be set as env in container
    35  	EnvVars map[string]string
    36  
    37  	// VarsToExport are only env key. Value will be the value in the host system
    38  	VarsToExport []string
    39  }
    40  
    41  // GetDockerFlags returns docker run style env flags
    42  func (ce *ContainerEnv) GetDockerFlags() []string {
    43  	envs := ce.EnvVars
    44  	if envs == nil {
    45  		envs = make(map[string]string)
    46  	}
    47  
    48  	flags := []string{}
    49  	// return in order to keep consistent among different runs
    50  	keys := []string{}
    51  	for k := range envs {
    52  		keys = append(keys, k)
    53  	}
    54  	sort.Strings(keys)
    55  	for _, key := range keys {
    56  		flags = append(flags, "-e", key+"="+envs[key])
    57  	}
    58  
    59  	for _, key := range ce.VarsToExport {
    60  		flags = append(flags, "-e", key)
    61  	}
    62  
    63  	return flags
    64  }
    65  
    66  // AddKeyValue adds a key-value pair into the envs
    67  func (ce *ContainerEnv) AddKeyValue(key, value string) {
    68  	if ce.EnvVars == nil {
    69  		ce.EnvVars = make(map[string]string)
    70  	}
    71  	ce.EnvVars[key] = value
    72  }
    73  
    74  // HasExportedKey returns true if the key is a exported key
    75  func (ce *ContainerEnv) HasExportedKey(key string) bool {
    76  	for _, k := range ce.VarsToExport {
    77  		if k == key {
    78  			return true
    79  		}
    80  	}
    81  	return false
    82  }
    83  
    84  // AddKey adds a key into the envs
    85  func (ce *ContainerEnv) AddKey(key string) {
    86  	if !ce.HasExportedKey(key) {
    87  		ce.VarsToExport = append(ce.VarsToExport, key)
    88  	}
    89  }
    90  
    91  // Raw returns a slice of string which represents the envs.
    92  // Example: [foo=bar, baz]
    93  func (ce *ContainerEnv) Raw() []string {
    94  	var ret []string
    95  	for k, v := range ce.EnvVars {
    96  		ret = append(ret, k+"="+v)
    97  	}
    98  
    99  	ret = append(ret, ce.VarsToExport...)
   100  	return ret
   101  }
   102  
   103  // NewContainerEnv returns a pointer to a new ContainerEnv
   104  func NewContainerEnv() *ContainerEnv {
   105  	var ce ContainerEnv
   106  	ce.EnvVars = make(map[string]string)
   107  	// default envs
   108  	ce.EnvVars["LOG_TO_STDERR"] = defaultEnvValue
   109  	ce.EnvVars["STRUCTURED_RESULTS"] = defaultEnvValue
   110  	return &ce
   111  }
   112  
   113  // NewContainerEnvFromStringSlice returns a new ContainerEnv pointer with parsing
   114  // input envStr. envStr example: ["foo=bar", "baz"]
   115  func NewContainerEnvFromStringSlice(envStr []string) *ContainerEnv {
   116  	ce := NewContainerEnv()
   117  	for _, e := range envStr {
   118  		parts := strings.SplitN(e, "=", 2)
   119  		if len(parts) == 1 {
   120  			ce.AddKey(e)
   121  		} else {
   122  			ce.AddKeyValue(parts[0], parts[1])
   123  		}
   124  	}
   125  	return ce
   126  }
   127  
   128  // FunctionSpec defines a spec for running a function
   129  type FunctionSpec struct {
   130  	DeferFailure bool `json:"deferFailure,omitempty" yaml:"deferFailure,omitempty"`
   131  
   132  	// Container is the spec for running a function as a container
   133  	Container ContainerSpec `json:"container,omitempty" yaml:"container,omitempty"`
   134  
   135  	// Starlark is the spec for running a function as a starlark script
   136  	Starlark StarlarkSpec `json:"starlark,omitempty" yaml:"starlark,omitempty"`
   137  
   138  	// ExecSpec is the spec for running a function as an executable
   139  	Exec ExecSpec `json:"exec,omitempty" yaml:"exec,omitempty"`
   140  }
   141  
   142  type ExecSpec struct {
   143  	Path string `json:"path,omitempty" yaml:"path,omitempty"`
   144  }
   145  
   146  // ContainerSpec defines a spec for running a function as a container
   147  type ContainerSpec struct {
   148  	// Image is the container image to run
   149  	Image string `json:"image,omitempty" yaml:"image,omitempty"`
   150  
   151  	// Network defines network specific configuration
   152  	Network bool `json:"network,omitempty" yaml:"network,omitempty"`
   153  
   154  	// Mounts are the storage or directories to mount into the container
   155  	StorageMounts []StorageMount `json:"mounts,omitempty" yaml:"mounts,omitempty"`
   156  
   157  	// Env is a slice of env string that will be exposed to container
   158  	Env []string `json:"envs,omitempty" yaml:"envs,omitempty"`
   159  }
   160  
   161  // StarlarkSpec defines how to run a function as a starlark program
   162  type StarlarkSpec struct {
   163  	Name string `json:"name,omitempty" yaml:"name,omitempty"`
   164  
   165  	// Path specifies a path to a starlark script
   166  	Path string `json:"path,omitempty" yaml:"path,omitempty"`
   167  
   168  	// URL specifies a url containing a starlark script
   169  	URL string `json:"url,omitempty" yaml:"url,omitempty"`
   170  }
   171  
   172  // StorageMount represents a container's mounted storage option(s)
   173  type StorageMount struct {
   174  	// Type of mount e.g. bind mount, local volume, etc.
   175  	MountType string `json:"type,omitempty" yaml:"type,omitempty"`
   176  
   177  	// Source for the storage to be mounted.
   178  	// For named volumes, this is the name of the volume.
   179  	// For anonymous volumes, this field is omitted (empty string).
   180  	// For bind mounts, this is the path to the file or directory on the host.
   181  	Src string `json:"src,omitempty" yaml:"src,omitempty"`
   182  
   183  	// The path where the file or directory is mounted in the container.
   184  	DstPath string `json:"dst,omitempty" yaml:"dst,omitempty"`
   185  
   186  	// Mount in ReadWrite mode if it's explicitly configured
   187  	// See https://docs.docker.com/storage/bind-mounts/#use-a-read-only-bind-mount
   188  	ReadWriteMode bool `json:"rw,omitempty" yaml:"rw,omitempty"`
   189  }
   190  
   191  func (s *StorageMount) String() string {
   192  	mode := ""
   193  	if !s.ReadWriteMode {
   194  		mode = ",readonly"
   195  	}
   196  	return fmt.Sprintf("type=%s,source=%s,target=%s%s", s.MountType, s.Src, s.DstPath, mode)
   197  }
   198  
   199  // GetFunctionSpec returns the FunctionSpec for a resource.  Returns
   200  // nil if the resource does not have a FunctionSpec.
   201  //
   202  // The FunctionSpec is read from the resource metadata.annotation
   203  // "config.kubernetes.io/function"
   204  func GetFunctionSpec(n *yaml.RNode) (*FunctionSpec, error) {
   205  	meta, err := n.GetMeta()
   206  	if err != nil {
   207  		return nil, fmt.Errorf("failed to get ResourceMeta: %w", err)
   208  	}
   209  
   210  	fn, err := getFunctionSpecFromAnnotation(n, meta)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  	if fn != nil {
   215  		return fn, nil
   216  	}
   217  
   218  	// legacy function specification for backwards compatibility
   219  	container := meta.Annotations["config.kubernetes.io/container"]
   220  	if container != "" {
   221  		return &FunctionSpec{Container: ContainerSpec{Image: container}}, nil
   222  	}
   223  	return nil, nil
   224  }
   225  
   226  // getFunctionSpecFromAnnotation parses the config function from an annotation
   227  // if it is found
   228  func getFunctionSpecFromAnnotation(n *yaml.RNode, meta yaml.ResourceMeta) (*FunctionSpec, error) {
   229  	var fs FunctionSpec
   230  	for _, s := range functionAnnotationKeys {
   231  		fn := meta.Annotations[s]
   232  		if fn != "" {
   233  			if err := k8syaml.UnmarshalStrict([]byte(fn), &fs); err != nil {
   234  				return nil, fmt.Errorf("%s unmarshal error: %w", s, err)
   235  			}
   236  			return &fs, nil
   237  		}
   238  	}
   239  	n, err := n.Pipe(yaml.Lookup("metadata", "configFn"))
   240  	if err != nil {
   241  		return nil, fmt.Errorf("failed to look up metadata.configFn: %w", err)
   242  	}
   243  	if yaml.IsMissingOrNull(n) {
   244  		return nil, nil
   245  	}
   246  	s, err := n.String()
   247  	if err != nil {
   248  		fmt.Fprintf(os.Stderr, "configFn parse error: %v\n", err)
   249  		return nil, fmt.Errorf("configFn parse error: %w", err)
   250  	}
   251  	if err := k8syaml.UnmarshalStrict([]byte(s), &fs); err != nil {
   252  		return nil, fmt.Errorf("%s unmarshal error: %w", "configFn", err)
   253  	}
   254  	return &fs, nil
   255  }
   256  
   257  func StringToStorageMount(s string) StorageMount {
   258  	m := make(map[string]string)
   259  	options := strings.Split(s, ",")
   260  	for _, option := range options {
   261  		keyVal := strings.SplitN(option, "=", 2)
   262  		if len(keyVal) == 2 {
   263  			m[keyVal[0]] = keyVal[1]
   264  		}
   265  	}
   266  	var sm StorageMount
   267  	for key, value := range m {
   268  		switch {
   269  		case key == "type":
   270  			sm.MountType = value
   271  		case key == "src" || key == "source":
   272  			sm.Src = value
   273  		case key == "dst" || key == "target":
   274  			sm.DstPath = value
   275  		case key == "rw" && value == "true":
   276  			sm.ReadWriteMode = true
   277  		}
   278  	}
   279  	return sm
   280  }
   281  
   282  // IsReconcilerFilter filters Resources based on whether or not they are Reconciler Resource.
   283  // Resources with an apiVersion starting with '*.gcr.io', 'gcr.io' or 'docker.io' are considered
   284  // Reconciler Resources.
   285  type IsReconcilerFilter struct {
   286  	// ExcludeReconcilers if set to true, then Reconcilers will be excluded -- e.g.
   287  	// Resources with a reconcile container through the apiVersion (gcr.io prefix) or
   288  	// through the annotations
   289  	ExcludeReconcilers bool `yaml:"excludeReconcilers,omitempty"`
   290  
   291  	// IncludeNonReconcilers if set to true, the NonReconciler will be included.
   292  	IncludeNonReconcilers bool `yaml:"includeNonReconcilers,omitempty"`
   293  }
   294  
   295  // Filter implements kio.Filter
   296  func (c *IsReconcilerFilter) Filter(inputs []*yaml.RNode) ([]*yaml.RNode, error) {
   297  	var out []*yaml.RNode
   298  	for i := range inputs {
   299  		functionSpec, err := GetFunctionSpec(inputs[i])
   300  		if err != nil {
   301  			return nil, err
   302  		}
   303  		isFnResource := functionSpec != nil
   304  		if isFnResource && !c.ExcludeReconcilers {
   305  			out = append(out, inputs[i])
   306  		}
   307  		if !isFnResource && c.IncludeNonReconcilers {
   308  			out = append(out, inputs[i])
   309  		}
   310  	}
   311  	return out, nil
   312  }
   313  

View as plain text