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 }