...

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

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

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package starlark
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io"
    10  	"net/http"
    11  	"os"
    12  
    13  	"go.starlark.net/starlark"
    14  	"sigs.k8s.io/kustomize/kyaml/errors"
    15  	"sigs.k8s.io/kustomize/kyaml/fn/runtime/runtimeutil"
    16  	"sigs.k8s.io/kustomize/kyaml/internal/forked/github.com/qri-io/starlib/util"
    17  	"sigs.k8s.io/kustomize/kyaml/kio/filters"
    18  	"sigs.k8s.io/kustomize/kyaml/yaml"
    19  )
    20  
    21  // Filter transforms a set of resources through the provided program
    22  type Filter struct {
    23  	Name string
    24  
    25  	// Program is a starlark script which will be run against the resources
    26  	Program string
    27  
    28  	// URL is the url of a starlark program to fetch and run
    29  	URL string
    30  
    31  	// Path is the path to a starlark program to read and run
    32  	Path string
    33  
    34  	runtimeutil.FunctionFilter
    35  }
    36  
    37  func (sf *Filter) String() string {
    38  	return fmt.Sprintf(
    39  		"name: %v path: %v url: %v program: %v", sf.Name, sf.Path, sf.URL, sf.Program)
    40  }
    41  
    42  func (sf *Filter) Filter(nodes []*yaml.RNode) ([]*yaml.RNode, error) {
    43  	if err := sf.setup(); err != nil {
    44  		return nil, err
    45  	}
    46  	sf.FunctionFilter.Run = sf.Run
    47  
    48  	return sf.FunctionFilter.Filter(nodes)
    49  }
    50  
    51  func (sf *Filter) setup() error {
    52  	if (sf.URL != "" && sf.Path != "") ||
    53  		(sf.URL != "" && sf.Program != "") ||
    54  		(sf.Path != "" && sf.Program != "") {
    55  		return errors.Errorf("Filter Path, Program and URL are mutually exclusive")
    56  	}
    57  
    58  	// read the program from a file
    59  	if sf.Path != "" {
    60  		b, err := os.ReadFile(sf.Path)
    61  		if err != nil {
    62  			return err
    63  		}
    64  		sf.Program = string(b)
    65  	}
    66  
    67  	// read the program from a URL
    68  	if sf.URL != "" {
    69  		err := func() error {
    70  			resp, err := http.Get(sf.URL)
    71  			if err != nil {
    72  				return err
    73  			}
    74  			defer resp.Body.Close()
    75  			b, err := io.ReadAll(resp.Body)
    76  			if err != nil {
    77  				return err
    78  			}
    79  			sf.Program = string(b)
    80  			return nil
    81  		}()
    82  		if err != nil {
    83  			return err
    84  		}
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  func (sf *Filter) Run(reader io.Reader, writer io.Writer) error {
    91  	// retain map of inputs to outputs by id so if the name is changed by the
    92  	// starlark program, we are able to match the same resources
    93  	value, err := sf.readResourceList(reader)
    94  	if err != nil {
    95  		return errors.Wrap(err)
    96  	}
    97  
    98  	// run the starlark as program as transformation function
    99  	thread := &starlark.Thread{Name: sf.Name}
   100  
   101  	ctx := &Context{resourceList: value}
   102  	pd, err := ctx.predeclared()
   103  	if err != nil {
   104  		return errors.Wrap(err)
   105  	}
   106  	_, err = starlark.ExecFile(thread, sf.Name, sf.Program, pd)
   107  	if err != nil {
   108  		return errors.Wrap(err)
   109  	}
   110  
   111  	return sf.writeResourceList(value, writer)
   112  }
   113  
   114  // inputToResourceList transforms input into a starlark.Value
   115  func (sf *Filter) readResourceList(reader io.Reader) (starlark.Value, error) {
   116  	// read and parse the inputs
   117  	rl := bytes.Buffer{}
   118  	_, err := rl.ReadFrom(reader)
   119  	if err != nil {
   120  		return nil, errors.Wrap(err)
   121  	}
   122  	rn, err := yaml.Parse(rl.String())
   123  	if err != nil {
   124  		return nil, errors.Wrap(err)
   125  	}
   126  
   127  	// convert to a starlark value
   128  	b, err := yaml.Marshal(rn.Document()) // convert to bytes
   129  	if err != nil {
   130  		return nil, errors.Wrap(err)
   131  	}
   132  	var in map[string]interface{}
   133  	err = yaml.Unmarshal(b, &in) // convert to map[string]interface{}
   134  	if err != nil {
   135  		return nil, errors.Wrap(err)
   136  	}
   137  	return util.Marshal(in) // convert to starlark value
   138  }
   139  
   140  // resourceListToOutput converts the output of the starlark program to the filter output
   141  func (sf *Filter) writeResourceList(value starlark.Value, writer io.Writer) error {
   142  	// convert the modified resourceList back into a slice of RNodes
   143  	// by first converting to a map[string]interface{}
   144  	out, err := util.Unmarshal(value)
   145  	if err != nil {
   146  		return errors.Wrap(err)
   147  	}
   148  	b, err := yaml.Marshal(out)
   149  	if err != nil {
   150  		return errors.Wrap(err)
   151  	}
   152  
   153  	rl, err := yaml.Parse(string(b))
   154  	if err != nil {
   155  		return errors.Wrap(err)
   156  	}
   157  
   158  	// preserve the comments from the input
   159  	items, err := rl.Pipe(yaml.Lookup("items"))
   160  	if err != nil {
   161  		return errors.Wrap(err)
   162  	}
   163  	err = items.VisitElements(func(node *yaml.RNode) error {
   164  		// starlark will serialize the resources sorting the fields alphabetically,
   165  		// format them to have a better ordering
   166  		_, err := filters.FormatFilter{}.Filter([]*yaml.RNode{node})
   167  		return err
   168  	})
   169  	if err != nil {
   170  		return errors.Wrap(err)
   171  	}
   172  
   173  	s, err := rl.String()
   174  	if err != nil {
   175  		return errors.Wrap(err)
   176  	}
   177  
   178  	_, err = writer.Write([]byte(s))
   179  	return err
   180  }
   181  

View as plain text