github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/thirdparty/cmdconfig/commands/cmdeval/cmdeval.go (about)

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package cmdeval
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"os"
    12  	"path/filepath"
    13  	"strings"
    14  
    15  	docs "github.com/GoogleContainerTools/kpt/internal/docs/generated/fndocs"
    16  	"github.com/GoogleContainerTools/kpt/internal/fnruntime"
    17  	"github.com/GoogleContainerTools/kpt/internal/pkg"
    18  	"github.com/GoogleContainerTools/kpt/internal/printer"
    19  	"github.com/GoogleContainerTools/kpt/internal/util/argutil"
    20  	"github.com/GoogleContainerTools/kpt/internal/util/cmdutil"
    21  	"github.com/GoogleContainerTools/kpt/internal/util/pathutil"
    22  	kptfile "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
    23  	"github.com/GoogleContainerTools/kpt/pkg/kptfile/kptfileutil"
    24  	"github.com/GoogleContainerTools/kpt/thirdparty/cmdconfig/commands/runner"
    25  	"github.com/GoogleContainerTools/kpt/thirdparty/kyaml/runfn"
    26  	"github.com/google/shlex"
    27  	"github.com/spf13/cobra"
    28  	"sigs.k8s.io/kustomize/kyaml/comments"
    29  	"sigs.k8s.io/kustomize/kyaml/errors"
    30  	"sigs.k8s.io/kustomize/kyaml/filesys"
    31  	"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
    32  	"sigs.k8s.io/kustomize/kyaml/order"
    33  	"sigs.k8s.io/kustomize/kyaml/yaml"
    34  )
    35  
    36  // GetEvalFnRunner returns a EvalFnRunner.
    37  func GetEvalFnRunner(ctx context.Context, parent string) *EvalFnRunner {
    38  	r := &EvalFnRunner{Ctx: ctx}
    39  	r.InitDefaults()
    40  
    41  	c := &cobra.Command{
    42  		Use:     "eval [DIR | -] [flags] [--fn-args]",
    43  		Short:   docs.EvalShort,
    44  		Long:    docs.EvalShort + "\n" + docs.EvalLong,
    45  		Example: docs.EvalExamples,
    46  		RunE:    r.runE,
    47  		PreRunE: r.preRunE,
    48  	}
    49  	r.Command = c
    50  	r.Command.Flags().StringVarP(&r.Dest, "output", "o", "",
    51  		fmt.Sprintf("output resources are written to provided location. Allowed values: %s|%s|<OUT_DIR_PATH>", cmdutil.Stdout, cmdutil.Unwrap))
    52  	r.Command.Flags().StringVarP(
    53  		&r.Image, "image", "i", "", "run this image as a function")
    54  	_ = r.Command.RegisterFlagCompletionFunc("image", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    55  		return cmdutil.SuggestFunctions(cmd), cobra.ShellCompDirectiveDefault
    56  	})
    57  	r.Command.Flags().StringArrayVarP(
    58  		&r.Keywords, "keywords", "k", nil, "filter functions that match one or more keywords")
    59  	_ = r.Command.RegisterFlagCompletionFunc("keywords", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    60  		return cmdutil.SuggestKeywords(cmd), cobra.ShellCompDirectiveDefault
    61  	})
    62  	r.Command.Flags().StringVarP(&r.FnType, "type", "t", "",
    63  		"`mutator` (default) or `validator`. tell the function type for autocompletion and `--save` flag")
    64  	_ = r.Command.RegisterFlagCompletionFunc("type", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    65  		return []string{"mutator", "validator"}, cobra.ShellCompDirectiveDefault
    66  	})
    67  	r.Command.Flags().BoolVarP(
    68  		&r.SaveFn, "save", "s", false,
    69  		"save the function and its arguments to Kptfile")
    70  	r.Command.Flags().StringVar(
    71  		&r.Exec, "exec", "", "run an executable as a function")
    72  	r.Command.Flags().StringVar(
    73  		&r.FnConfigPath, "fn-config", "", "path to the function config file")
    74  	r.Command.Flags().BoolVarP(
    75  		&r.IncludeMetaResources, "include-meta-resources", "m", false, "include package meta resources in function input")
    76  	r.Command.Flags().StringVar(
    77  		&r.ResultsDir, "results-dir", "", "write function results to this dir")
    78  	r.Command.Flags().BoolVar(
    79  		&r.Network, "network", false, "enable network access for functions that declare it")
    80  	r.Command.Flags().StringArrayVar(
    81  		&r.Mounts, "mount", []string{},
    82  		"a list of storage options read from the filesystem")
    83  	r.Command.Flags().StringArrayVarP(
    84  		&r.Env, "env", "e", []string{},
    85  		"a list of environment variables to be used by functions")
    86  	r.Command.Flags().BoolVar(
    87  		&r.AsCurrentUser, "as-current-user", false, "use the uid and gid that kpt is running with to run the function in the container")
    88  
    89  	r.Command.Flags().Var(&r.RunnerOptions.ImagePullPolicy, "image-pull-policy",
    90  		"pull image before running the container "+r.RunnerOptions.ImagePullPolicy.HelpAllowedValues())
    91  	_ = r.Command.RegisterFlagCompletionFunc("image-pull-policy", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
    92  		return r.RunnerOptions.ImagePullPolicy.AllStrings(), cobra.ShellCompDirectiveDefault
    93  	})
    94  
    95  	r.Command.Flags().BoolVar(
    96  		&r.RunnerOptions.AllowWasm, "allow-alpha-wasm", false, "allow alpha wasm functions to be run. If true, you can specify a wasm image with --image flag or a path to a wasm file (must have the .wasm file extension) with --exec flag.")
    97  
    98  	// selector flags
    99  	r.Command.Flags().StringVar(
   100  		&r.Selector.APIVersion, "match-api-version", "", "select resources matching the given apiVersion")
   101  	r.Command.Flags().StringVar(
   102  		&r.Selector.Kind, "match-kind", "", "select resources matching the given kind")
   103  	r.Command.Flags().StringVar(
   104  		&r.Selector.Name, "match-name", "", "select resources matching the given name")
   105  	r.Command.Flags().StringVar(
   106  		&r.Selector.Namespace, "match-namespace", "", "select resources matching the given namespace")
   107  	r.Command.Flags().StringArrayVar(
   108  		&r.selectorAnnotations, "match-annotations", []string{}, "select resources matching the given annotations")
   109  	r.Command.Flags().StringArrayVar(
   110  		&r.selectorLabels, "match-labels", []string{}, "select resources matching the given labels")
   111  
   112  	// exclusion flags
   113  	r.Command.Flags().StringVar(
   114  		&r.Exclusion.APIVersion, "exclude-api-version", "", "exclude resources matching the given apiVersion")
   115  	r.Command.Flags().StringVar(
   116  		&r.Exclusion.Kind, "exclude-kind", "", "exclude resources matching the given kind")
   117  	r.Command.Flags().StringVar(
   118  		&r.Exclusion.Name, "exclude-name", "", "exclude resources matching the given name")
   119  	r.Command.Flags().StringVar(
   120  		&r.Exclusion.Namespace, "exclude-namespace", "", "exclude resources matching the given namespace")
   121  	r.Command.Flags().StringArrayVar(
   122  		&r.excludeAnnotations, "exclude-annotations", []string{}, "exclude resources matching the given annotations")
   123  	r.Command.Flags().StringArrayVar(
   124  		&r.excludeLabels, "exclude-labels", []string{}, "exclude resources matching the given labels")
   125  
   126  	if err := r.Command.Flags().MarkHidden("include-meta-resources"); err != nil {
   127  		panic(err)
   128  	}
   129  	cmdutil.FixDocs("kpt", parent, c)
   130  	return r
   131  }
   132  
   133  func EvalCommand(ctx context.Context, name string) *cobra.Command {
   134  	return GetEvalFnRunner(ctx, name).Command
   135  }
   136  
   137  // EvalFnRunner contains the run function
   138  type EvalFnRunner struct {
   139  	Command              *cobra.Command
   140  	Dest                 string
   141  	OutContent           bytes.Buffer
   142  	FromStdin            bool
   143  	Image                string
   144  	SaveFn               bool
   145  	Keywords             []string
   146  	FnType               string
   147  	Exec                 string
   148  	FnConfigPath         string
   149  	ResultsDir           string
   150  	Network              bool
   151  	Mounts               []string
   152  	Env                  []string
   153  	AsCurrentUser        bool
   154  	IncludeMetaResources bool
   155  	Ctx                  context.Context
   156  	Selector             kptfile.Selector
   157  	Exclusion            kptfile.Selector
   158  	dataItems            []string
   159  
   160  	RunnerOptions fnruntime.RunnerOptions
   161  
   162  	// we will need to parse these values into Selector and Exclusion
   163  	selectorLabels      []string
   164  	selectorAnnotations []string
   165  	excludeLabels       []string
   166  	excludeAnnotations  []string
   167  
   168  	runFns runfn.RunFns
   169  }
   170  
   171  func (r *EvalFnRunner) InitDefaults() {
   172  	r.RunnerOptions.InitDefaults()
   173  }
   174  
   175  func (r *EvalFnRunner) runE(c *cobra.Command, _ []string) error {
   176  	err := runner.HandleError(r.Ctx, r.runFns.Execute())
   177  	if err != nil {
   178  		return err
   179  	}
   180  	if err = cmdutil.WriteFnOutput(r.Dest, r.OutContent.String(), r.FromStdin,
   181  		printer.FromContextOrDie(r.Ctx).OutStream()); err != nil {
   182  		return err
   183  	}
   184  	if r.SaveFn {
   185  		r.SaveFnToKptfile()
   186  	}
   187  	return nil
   188  }
   189  
   190  // NewFunction creates a Kptfile.Function object which has the evaluated fn configurations.
   191  // This object can be written to Kptfile `pipeline.mutators`.
   192  func (r *EvalFnRunner) NewFunction() *kptfile.Function {
   193  	newFn := &kptfile.Function{}
   194  	if r.Image != "" {
   195  		newFn.Image = r.Image
   196  	} else {
   197  		newFn.Exec = r.Exec
   198  	}
   199  	if !r.Selector.IsEmpty() {
   200  		newFn.Selectors = []kptfile.Selector{r.Selector}
   201  	}
   202  	if !r.Exclusion.IsEmpty() {
   203  		newFn.Exclusions = []kptfile.Selector{r.Exclusion}
   204  	}
   205  	if r.FnConfigPath != "" {
   206  		fnConfigAbsPath, _, _ := pathutil.ResolveAbsAndRelPaths(r.FnConfigPath)
   207  		pkgAbsPath, _, _ := pathutil.ResolveAbsAndRelPaths(r.runFns.Path)
   208  		newFn.ConfigPath, _ = filepath.Rel(pkgAbsPath, fnConfigAbsPath)
   209  	} else {
   210  		data := map[string]string{}
   211  		for i, s := range r.dataItems {
   212  			kv := strings.SplitN(s, "=", 2)
   213  			if i == 0 && len(kv) == 1 {
   214  				continue
   215  			}
   216  			data[kv[0]] = kv[1]
   217  		}
   218  		if len(data) != 0 {
   219  			newFn.ConfigMap = data
   220  		}
   221  	}
   222  	return newFn
   223  }
   224  
   225  // Add the evaluated function to the kptfile.Function list, this Function can either be
   226  // `pipeline.mutators` or `pipeline.validators`
   227  func (r *EvalFnRunner) updateFnList(oldFNs []kptfile.Function) ([]kptfile.Function, string) {
   228  	var newFns []kptfile.Function
   229  	found := false
   230  	newFn := r.NewFunction()
   231  	var message string
   232  	for _, m := range oldFNs {
   233  		switch {
   234  		case m.Image != "" && m.Image == r.Image:
   235  			newFns = append(newFns, *newFn)
   236  			found = true
   237  			message = fmt.Sprintf("Updated %q as %v in the Kptfile.\n", r.Image, r.FnType)
   238  		case m.Exec != "" && m.Exec == r.Exec:
   239  			newFns = append(newFns, *newFn)
   240  			found = true
   241  			message = fmt.Sprintf("Updated %q as %v in the Kptfile.\n", r.Exec, r.FnType)
   242  		default:
   243  			newFns = append(newFns, m)
   244  		}
   245  	}
   246  	if !found {
   247  		newFns = append(newFns, *newFn)
   248  		if newFn.Image != "" {
   249  			message = fmt.Sprintf("Added %q as %v in the Kptfile.\n", r.Image, r.FnType)
   250  		} else if newFn.Exec != "" {
   251  			message = fmt.Sprintf("Added %q as %v in the Kptfile.\n", r.Exec, r.FnType)
   252  		}
   253  	}
   254  	return newFns, message
   255  }
   256  
   257  // SaveFnToKptfile adds the evaluated function and its arguments to Kptfile `pipeline.mutators` or `pipeline.validators` .
   258  func (r *EvalFnRunner) SaveFnToKptfile() {
   259  	pr := printer.FromContextOrDie(r.Ctx)
   260  	kf, err := pkg.ReadKptfile(filesys.FileSystemOrOnDisk{}, r.runFns.Path)
   261  	if err != nil {
   262  		pr.Printf("function not added: Kptfile not exists\n")
   263  		return
   264  	}
   265  
   266  	if kf.Pipeline == nil {
   267  		kf.Pipeline = &kptfile.Pipeline{}
   268  	}
   269  	var usrMsg string
   270  	switch r.FnType {
   271  	case "mutator":
   272  		kf.Pipeline.Mutators, usrMsg = r.updateFnList(kf.Pipeline.Mutators)
   273  	case "validator":
   274  		kf.Pipeline.Validators, usrMsg = r.updateFnList(kf.Pipeline.Validators)
   275  	}
   276  
   277  	mutatedKfAsYNode, err := r.preserveCommentsAndFieldOrder(kf)
   278  	if err != nil {
   279  		pr.Printf("function is not added to Kptfile: %v\n", err)
   280  	}
   281  
   282  	// When saving function to Kptfile, the functionConfig should be the relative path
   283  	// to the kpt package, not the relative path to the current working dir.
   284  	// error handling are ignored since they have been validated in preRunE.
   285  	if err := kptfileutil.WriteFile(r.runFns.Path, mutatedKfAsYNode); err != nil {
   286  		pr.Printf("function is not added to Kptfile: %v\n", err)
   287  		return
   288  	}
   289  	pr.Printf(usrMsg)
   290  }
   291  
   292  // preserveCommentsAndFieldOrder syncs the mutated Kptfile with the original to preserve
   293  // comments and field order, and returns the result as a yaml Node
   294  func (r *EvalFnRunner) preserveCommentsAndFieldOrder(kf *kptfile.KptFile) (*yaml.Node, error) {
   295  	kfAsRNode, err := yaml.ReadFile(filepath.Join(r.runFns.Path, kptfile.KptFileName))
   296  	if err != nil {
   297  		return nil, fmt.Errorf("could not read Kptfile: %v", err)
   298  	}
   299  	mutatedKfAsBytes, err := yaml.Marshal(kf)
   300  	if err != nil {
   301  		return nil, fmt.Errorf("could not Marshal Kptfile into bytes: %v", err)
   302  	}
   303  	mutatedKfAsRNode, err := yaml.Parse(string(mutatedKfAsBytes))
   304  	if err != nil {
   305  		return nil, fmt.Errorf("could not parse Kptfile: %v", err)
   306  	}
   307  	// preserve comments and sync field order
   308  	if err := comments.CopyComments(kfAsRNode, mutatedKfAsRNode); err != nil {
   309  		return nil, fmt.Errorf("could not preserve Kptfile comments: %v", err)
   310  	}
   311  	if err := order.SyncOrder(kfAsRNode, mutatedKfAsRNode); err != nil {
   312  		return nil, fmt.Errorf("could not preserve Kptfile field order %v", err)
   313  	}
   314  	return mutatedKfAsRNode.YNode(), nil
   315  }
   316  
   317  // getCLIFunctionConfig parses the commandline flags and arguments into explicit
   318  // function config
   319  func (r *EvalFnRunner) getCLIFunctionConfig(ctx context.Context, dataItems []string) (*yaml.RNode, error) {
   320  	if r.Image == "" && r.Exec == "" {
   321  		return nil, nil
   322  	}
   323  
   324  	// TODO: This probably doesn't belong here, but moving it changes the test output
   325  	if r.Image != "" {
   326  		img, err := r.RunnerOptions.ResolveToImage(ctx, r.Image)
   327  		if err != nil {
   328  			return nil, err
   329  		}
   330  		r.Image = img
   331  	}
   332  
   333  	var err error
   334  
   335  	// create the function config
   336  	rc, err := yaml.Parse(`
   337  metadata:
   338    name: function-input
   339  data: {}
   340  `)
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  
   345  	// default the function config kind to ConfigMap, this may be overridden
   346  	var kind = "ConfigMap"
   347  	var version = "v1"
   348  
   349  	// populate the function config with data.  this is a convention for functions
   350  	// to be more commandline friendly
   351  	if len(dataItems) > 0 {
   352  		dataField, err := rc.Pipe(yaml.Lookup("data"))
   353  		if err != nil {
   354  			return nil, err
   355  		}
   356  		for i, s := range dataItems {
   357  			kv := strings.SplitN(s, "=", 2)
   358  			if i == 0 && len(kv) == 1 {
   359  				// first argument may be the kind
   360  				kind = s
   361  				continue
   362  			}
   363  			if len(kv) != 2 {
   364  				return nil, fmt.Errorf("args must have keys and values separated by =")
   365  			}
   366  			// When we are using a ConfigMap as the functionConfig, we should create
   367  			// the node with type string instead of creating a scalar node. Because
   368  			// a scalar node might be parsed as int, float or bool later.
   369  			err := dataField.PipeE(yaml.SetField(kv[0], yaml.NewStringRNode(kv[1])))
   370  			if err != nil {
   371  				return nil, err
   372  			}
   373  		}
   374  	}
   375  	err = rc.PipeE(yaml.SetField("kind", yaml.NewScalarRNode(kind)))
   376  	if err != nil {
   377  		return nil, err
   378  	}
   379  	err = rc.PipeE(yaml.SetField("apiVersion", yaml.NewScalarRNode(version)))
   380  	if err != nil {
   381  		return nil, err
   382  	}
   383  	return rc, nil
   384  }
   385  
   386  func (r *EvalFnRunner) getFunctionSpec() (*runtimeutil.FunctionSpec, []string, error) {
   387  	fn := &runtimeutil.FunctionSpec{}
   388  	var execArgs []string
   389  	if r.Image != "" {
   390  		if err := kptfile.ValidateFunctionImageURL(r.Image); err != nil {
   391  			return nil, nil, err
   392  		}
   393  		fn.Container.Image = r.Image
   394  	} else if r.Exec != "" {
   395  		// check the flags that doesn't make sense with exec function
   396  		// --mount, --as-current-user, --network and --env are
   397  		// only used with container functions
   398  		if r.AsCurrentUser || r.Network ||
   399  			len(r.Mounts) != 0 || len(r.Env) != 0 {
   400  			return nil, nil, fmt.Errorf("--mount, --as-current-user, --network and --env can only be used with container functions")
   401  		}
   402  		s, err := shlex.Split(r.Exec)
   403  		if err != nil {
   404  			return nil, nil, fmt.Errorf("exec command %q must be valid: %w", r.Exec, err)
   405  		}
   406  		if len(s) > 0 {
   407  			fn.Exec.Path = s[0]
   408  			execArgs = s[1:]
   409  		}
   410  	}
   411  	return fn, execArgs, nil
   412  }
   413  
   414  func toStorageMounts(mounts []string) []runtimeutil.StorageMount {
   415  	var sms []runtimeutil.StorageMount
   416  	for _, mount := range mounts {
   417  		sms = append(sms, runtimeutil.StringToStorageMount(mount))
   418  	}
   419  	return sms
   420  }
   421  
   422  func checkFnConfigPathExistence(path string) error {
   423  	// check does fn config file exist
   424  	if _, err := os.Stat(path); os.IsNotExist(err) {
   425  		return fmt.Errorf("missing function config file: %s", path)
   426  	}
   427  	return nil
   428  }
   429  
   430  func (r *EvalFnRunner) validateOptionalFlags() error {
   431  	// Let users know that --include-meta-resources is no longer necessary
   432  	// since meta resources are included by default.
   433  	if r.IncludeMetaResources {
   434  		return fmt.Errorf("--include-meta-resources is no longer necessary because meta resources are now included by default")
   435  	}
   436  	// SaveFn stores function to Kptfile. If not enabled, only make in-place changes.
   437  	if r.SaveFn {
   438  		if r.FnType == "" {
   439  			return fmt.Errorf("--type must be specified if saving functions to Kptfile (--save=true)")
   440  		}
   441  		if r.FnType != "mutator" && r.FnType != "validator" {
   442  			return fmt.Errorf("--type must be either `mutator` or `validator`")
   443  		}
   444  	}
   445  	// ResultsDir stores the hydrated output in a structured format to result dir. If not specified, only make
   446  	// in-place changes.
   447  	if r.ResultsDir != "" {
   448  		err := os.MkdirAll(r.ResultsDir, 0755)
   449  		if err != nil {
   450  			return fmt.Errorf("cannot read or create results dir %q: %w", r.ResultsDir, err)
   451  		}
   452  	}
   453  
   454  	return nil
   455  }
   456  
   457  func (r *EvalFnRunner) preRunE(c *cobra.Command, args []string) error {
   458  	// separate the optional flag validation to fix linter issue: cyclomatic complexity
   459  	if err := r.validateOptionalFlags(); err != nil {
   460  		return err
   461  	}
   462  	if r.Dest != "" && r.Dest != cmdutil.Stdout && r.Dest != cmdutil.Unwrap {
   463  		if err := cmdutil.CheckDirectoryNotPresent(r.Dest); err != nil {
   464  			return err
   465  		}
   466  	}
   467  	if r.Image == "" && r.Exec == "" {
   468  		return errors.Errorf("must specify --image or --exec")
   469  	}
   470  	var dataItems []string
   471  	if c.ArgsLenAtDash() >= 0 {
   472  		dataItems = append(dataItems, args[c.ArgsLenAtDash():]...)
   473  		args = args[:c.ArgsLenAtDash()]
   474  	}
   475  	if len(args) == 0 {
   476  		// default to current working directory
   477  		args = append(args, ".")
   478  	}
   479  	if len(args) > 1 {
   480  		return errors.Errorf("0 or 1 arguments supported, function arguments go after '--'")
   481  	}
   482  	if len(dataItems) > 0 && r.FnConfigPath != "" {
   483  		return fmt.Errorf("function arguments can only be specified without function config file")
   484  	}
   485  	fnConfig, err := r.getCLIFunctionConfig(c.Context(), dataItems)
   486  	if err != nil {
   487  		return err
   488  	}
   489  	r.dataItems = dataItems
   490  	fnSpec, execArgs, err := r.getFunctionSpec()
   491  	if err != nil {
   492  		return err
   493  	}
   494  
   495  	// set the output to stdout if in dry-run mode or no arguments are specified
   496  	var output io.Writer
   497  	var input io.Reader
   498  	r.OutContent = bytes.Buffer{}
   499  	if args[0] == "-" {
   500  		output = &r.OutContent
   501  		input = c.InOrStdin()
   502  		r.FromStdin = true
   503  
   504  		// clear args as it indicates stdin and not path
   505  		args = []string{}
   506  	} else if r.Dest != "" {
   507  		output = &r.OutContent
   508  	}
   509  
   510  	// set the path if specified as an argument
   511  	var path string
   512  	if len(args) == 1 {
   513  		// argument is the directory
   514  		path = args[0]
   515  	}
   516  
   517  	// parse mounts to set storageMounts
   518  	storageMounts := toStorageMounts(r.Mounts)
   519  
   520  	if r.FnConfigPath != "" {
   521  		err = checkFnConfigPathExistence(r.FnConfigPath)
   522  		if err != nil {
   523  			return err
   524  		}
   525  	}
   526  
   527  	if path != "" {
   528  		path, err = argutil.ResolveSymlink(r.Ctx, path)
   529  		if err != nil {
   530  			return err
   531  		}
   532  	}
   533  	if r.SaveFn && r.FnConfigPath != "" {
   534  		fnConfigAbsPath, _, _ := pathutil.ResolveAbsAndRelPaths(r.FnConfigPath)
   535  		pkgAbsPath, _, _ := pathutil.ResolveAbsAndRelPaths(path)
   536  		if !strings.HasPrefix(fnConfigAbsPath, pkgAbsPath) {
   537  			return fmt.Errorf("--fn-config must be under %v if saving functions to Kptfile (--save=true)",
   538  				pkgAbsPath)
   539  		}
   540  	}
   541  	r.parseSelectors()
   542  	r.runFns = runfn.RunFns{
   543  		Ctx:           r.Ctx,
   544  		Function:      fnSpec,
   545  		ExecArgs:      execArgs,
   546  		OriginalExec:  r.Exec,
   547  		Output:        output,
   548  		Input:         input,
   549  		Path:          path,
   550  		Network:       r.Network,
   551  		StorageMounts: storageMounts,
   552  		ResultsDir:    r.ResultsDir,
   553  		Env:           r.Env,
   554  		AsCurrentUser: r.AsCurrentUser,
   555  		FnConfig:      fnConfig,
   556  		FnConfigPath:  r.FnConfigPath,
   557  		// fn eval should remove all files when all resources
   558  		// are deleted.
   559  		ContinueOnEmptyResult: true,
   560  		Selector:              r.Selector,
   561  		Exclusion:             r.Exclusion,
   562  		RunnerOptions:         r.RunnerOptions,
   563  	}
   564  
   565  	return nil
   566  }
   567  
   568  // parses annotation and label based selectors and exclusion from the command line input
   569  func (r *EvalFnRunner) parseSelectors() {
   570  	r.Selector.Annotations = parseSelectorMap(r.selectorAnnotations)
   571  	r.Selector.Labels = parseSelectorMap(r.selectorLabels)
   572  	r.Exclusion.Annotations = parseSelectorMap(r.excludeAnnotations)
   573  	r.Exclusion.Labels = parseSelectorMap(r.excludeLabels)
   574  }
   575  
   576  func parseSelectorMap(selectors []string) map[string]string {
   577  	if len(selectors) == 0 {
   578  		return nil
   579  	}
   580  	result := make(map[string]string)
   581  	for _, s := range selectors {
   582  		parts := strings.Split(s, "=")
   583  		key, value := parts[0], parts[1]
   584  		result[key] = value
   585  	}
   586  	return result
   587  }