github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/tiltfile/encoding/yaml.go (about)

     1  package encoding
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/pkg/errors"
    11  	"go.starlark.net/starlark"
    12  	k8syaml "k8s.io/apimachinery/pkg/util/yaml"
    13  	"sigs.k8s.io/yaml"
    14  
    15  	tiltfile_io "github.com/tilt-dev/tilt/internal/tiltfile/io"
    16  	"github.com/tilt-dev/tilt/internal/tiltfile/starkit"
    17  	"github.com/tilt-dev/tilt/internal/tiltfile/value"
    18  )
    19  
    20  // takes a list of objects that came from deserializing a potential starlark stream
    21  // ensures there's only one, and returns it
    22  func singleYAMLDoc(l *starlark.List) (starlark.Value, error) {
    23  	switch l.Len() {
    24  	case 0:
    25  		// if there are zero documents in the stream, that's actually an empty yaml document, which is a yaml
    26  		// document with a scalar value of NULL
    27  		return starlark.None, nil
    28  	case 1:
    29  		return l.Index(0), nil
    30  	default:
    31  		return nil, fmt.Errorf("expected a yaml document but found a yaml stream (documents separated by `---`). use %s instead to decode a yaml stream", decodeYAMLStreamN)
    32  	}
    33  }
    34  
    35  func readYAMLStreamAsStarlarkList(thread *starlark.Thread, path starlark.String, defaultValue *starlark.List) (*starlark.List, error) {
    36  	localPath, err := value.ValueToAbsPath(thread, path)
    37  	if err != nil {
    38  		return nil, fmt.Errorf("Argument 0 (paths): %v", err)
    39  	}
    40  
    41  	contents, err := tiltfile_io.ReadFile(thread, localPath)
    42  	if err != nil {
    43  		// Return the default value if the file doesn't exist AND a default value was given
    44  		if os.IsNotExist(err) && defaultValue != nil {
    45  			return defaultValue, nil
    46  		}
    47  		return nil, err
    48  	}
    49  
    50  	return yamlStreamToStarlark(string(contents), path.GoString())
    51  }
    52  
    53  func readYAMLStream(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    54  	var path starlark.String
    55  	var defaultValue *starlark.List
    56  	if err := starkit.UnpackArgs(thread, fn.Name(), args, kwargs, "paths", &path, "default?", &defaultValue); err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	return readYAMLStreamAsStarlarkList(thread, path, defaultValue)
    61  }
    62  
    63  func readYAML(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    64  	var path starlark.String
    65  	var defaultValue starlark.Value
    66  	if err := starkit.UnpackArgs(thread, fn.Name(), args, kwargs, "paths", &path, "default?", &defaultValue); err != nil {
    67  		return nil, err
    68  	}
    69  
    70  	var defaultValueList *starlark.List
    71  	if defaultValue != nil {
    72  		defaultValueList = starlark.NewList([]starlark.Value{defaultValue})
    73  	}
    74  
    75  	l, err := readYAMLStreamAsStarlarkList(thread, path, defaultValueList)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	return singleYAMLDoc(l)
    81  }
    82  
    83  func decodeYAMLStreamAsStarlarkList(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (*starlark.List, error) {
    84  	var contents value.Stringable
    85  	if err := starkit.UnpackArgs(thread, fn.Name(), args, kwargs, "yaml", &contents); err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	return yamlStreamToStarlark(contents.Value, "")
    90  }
    91  
    92  func decodeYAMLStream(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    93  	return decodeYAMLStreamAsStarlarkList(thread, fn, args, kwargs)
    94  }
    95  
    96  func decodeYAML(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
    97  	l, err := decodeYAMLStreamAsStarlarkList(thread, fn, args, kwargs)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	return singleYAMLDoc(l)
   103  }
   104  
   105  func yamlStreamToStarlark(s string, source string) (*starlark.List, error) {
   106  	var ret []starlark.Value
   107  	r := k8syaml.NewYAMLReader(bufio.NewReader(strings.NewReader(s)))
   108  
   109  	for {
   110  		bytes, err := r.Read()
   111  		if err == io.EOF {
   112  			break
   113  		}
   114  		if err != nil {
   115  			return nil, wrapError(err, "error reading YAML stream", source)
   116  		}
   117  
   118  		var decodedYAML interface{}
   119  		err = k8syaml.Unmarshal(bytes, &decodedYAML)
   120  		if err != nil {
   121  			return nil, wrapError(err, "error parsing YAML", source)
   122  		}
   123  
   124  		v, err := ConvertStructuredDataToStarlark(decodedYAML)
   125  		if err != nil {
   126  			return nil, wrapError(err, "error converting YAML to Starlark", source)
   127  		}
   128  		if v == starlark.None {
   129  			continue // ignore empty entries
   130  		}
   131  
   132  		ret = append(ret, v)
   133  	}
   134  
   135  	return starlark.NewList(ret), nil
   136  }
   137  
   138  // dumps yaml to a string
   139  func encodeYAML(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   140  	var obj starlark.Value
   141  	if err := starkit.UnpackArgs(thread, fn.Name(), args, kwargs, "obj", &obj); err != nil {
   142  		return nil, err
   143  	}
   144  
   145  	ret, err := starlarkToYAMLString(obj)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  
   150  	return tiltfile_io.NewBlob(ret, "encode_yaml"), nil
   151  }
   152  
   153  func encodeYAMLStream(thread *starlark.Thread, fn *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) {
   154  	var objs *starlark.List
   155  	if err := starkit.UnpackArgs(thread, fn.Name(), args, kwargs, "objs", &objs); err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	var yamlDocs []string
   160  
   161  	it := objs.Iterate()
   162  	defer it.Done()
   163  	var v starlark.Value
   164  	for it.Next(&v) {
   165  		s, err := starlarkToYAMLString(v)
   166  		if err != nil {
   167  			return nil, err
   168  		}
   169  		yamlDocs = append(yamlDocs, s)
   170  	}
   171  
   172  	return tiltfile_io.NewBlob(strings.Join(yamlDocs, "---\n"), "encode_yaml_stream"), nil
   173  }
   174  
   175  func starlarkToYAMLString(obj starlark.Value) (string, error) {
   176  	v, err := convertStarlarkToStructuredData(obj)
   177  	if err != nil {
   178  		return "", errors.Wrap(err, "error converting object from starlark")
   179  	}
   180  
   181  	b, err := yaml.Marshal(v)
   182  	if err != nil {
   183  		return "", errors.Wrap(err, "error serializing object to yaml")
   184  	}
   185  
   186  	return string(b), nil
   187  }
   188  
   189  func wrapError(err error, errmsg string, source string) error {
   190  	if source != "" {
   191  		errmsg += fmt.Sprintf(" from %s", source)
   192  	}
   193  	return errors.Wrap(err, errmsg)
   194  }