2021-10-14 09:52:03 +03:00
|
|
|
package metadatautil
|
2021-02-16 09:25:26 +03:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2022-06-15 15:38:39 +03:00
|
|
|
"cuelang.org/go/cue/cuecontext"
|
|
|
|
"cuelang.org/go/cue/format"
|
|
|
|
cueyaml "cuelang.org/go/encoding/yaml"
|
|
|
|
cuejson "cuelang.org/go/pkg/encoding/json"
|
2021-02-16 09:25:26 +03:00
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
)
|
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
const baseDirectoryKey = "base_directory"
|
2021-02-16 09:25:26 +03:00
|
|
|
const includeTag = "!include"
|
|
|
|
|
2021-10-14 09:52:03 +03:00
|
|
|
type YamlDecoderOpts struct {
|
2021-02-16 09:25:26 +03:00
|
|
|
// directory which is to be used as the parent directory to look for filenames
|
|
|
|
// specified in !include tag
|
|
|
|
IncludeTagBaseDirectory string
|
|
|
|
}
|
2021-12-09 20:25:54 +03:00
|
|
|
|
2021-10-14 09:52:03 +03:00
|
|
|
type yamlDecoder struct {
|
2021-02-16 09:25:26 +03:00
|
|
|
destination interface{}
|
2021-10-14 09:52:03 +03:00
|
|
|
opts YamlDecoderOpts
|
2021-02-16 09:25:26 +03:00
|
|
|
}
|
|
|
|
|
2021-10-14 09:52:03 +03:00
|
|
|
func NewYamlDecoder(opts YamlDecoderOpts, destination interface{}) *yamlDecoder {
|
|
|
|
return &yamlDecoder{destination, opts}
|
2021-02-16 09:25:26 +03:00
|
|
|
}
|
|
|
|
|
2021-10-14 09:52:03 +03:00
|
|
|
func (s *yamlDecoder) UnmarshalYAML(value *yaml.Node) error {
|
2021-02-16 09:25:26 +03:00
|
|
|
ctx := map[string]string{}
|
2021-12-09 20:25:54 +03:00
|
|
|
ctx[baseDirectoryKey] = s.opts.IncludeTagBaseDirectory
|
2021-02-16 09:25:26 +03:00
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
resolved, err := resolveTags(ctx, value, nil)
|
2021-02-16 09:25:26 +03:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return resolved.Decode(s.destination)
|
|
|
|
}
|
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
type fragment struct {
|
2021-02-16 09:25:26 +03:00
|
|
|
ctx map[string]string
|
2021-12-09 20:25:54 +03:00
|
|
|
files *[]string
|
2021-02-16 09:25:26 +03:00
|
|
|
content *yaml.Node
|
|
|
|
}
|
|
|
|
|
2021-12-09 20:25:54 +03:00
|
|
|
func newFragment(ctx map[string]string, files *[]string) *fragment {
|
|
|
|
f := new(fragment)
|
2021-02-16 09:25:26 +03:00
|
|
|
f.ctx = ctx
|
2021-12-09 20:25:54 +03:00
|
|
|
f.files = files
|
2021-02-16 09:25:26 +03:00
|
|
|
return f
|
|
|
|
}
|
2021-12-09 20:25:54 +03:00
|
|
|
func (f *fragment) UnmarshalYAML(value *yaml.Node) error {
|
2021-02-16 09:25:26 +03:00
|
|
|
var err error
|
|
|
|
// process includes in fragments
|
2021-12-09 20:25:54 +03:00
|
|
|
f.content, err = resolveTags(f.ctx, value, f.files)
|
2021-02-16 09:25:26 +03:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-06-15 18:44:36 +03:00
|
|
|
type YamlTagResolverError struct {
|
|
|
|
tag string
|
|
|
|
file string
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *YamlTagResolverError) Error() string {
|
|
|
|
if e.tag == "" {
|
|
|
|
return fmt.Sprintf("yaml tag resolver error: %s", e.err.Error())
|
2021-12-09 20:25:54 +03:00
|
|
|
}
|
2022-06-15 18:44:36 +03:00
|
|
|
|
|
|
|
return fmt.Sprintf(
|
|
|
|
"yaml tag resolver error:\ntag: %s\nfile: %s\nerror: %s",
|
|
|
|
e.tag,
|
|
|
|
e.file,
|
|
|
|
e.err.Error(),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *YamlTagResolverError) Unwrap() error {
|
|
|
|
return e.err
|
|
|
|
}
|
|
|
|
|
|
|
|
var resolver = func(ctx map[string]string, node *yaml.Node, files *[]string) (*yaml.Node, error) {
|
2021-12-09 20:25:54 +03:00
|
|
|
baseDir, ok := ctx[baseDirectoryKey]
|
|
|
|
if !ok {
|
2022-06-15 18:44:36 +03:00
|
|
|
return nil, &YamlTagResolverError{"", "", fmt.Errorf("parser error: base directory for !include tag not specified")}
|
2021-12-09 20:25:54 +03:00
|
|
|
}
|
|
|
|
fileLocation := filepath.Join(baseDir, node.Value)
|
|
|
|
file, err := ioutil.ReadFile(fileLocation)
|
|
|
|
if err != nil {
|
2022-06-15 18:44:36 +03:00
|
|
|
return nil, &YamlTagResolverError{node.Tag, fileLocation, err}
|
2021-02-16 09:25:26 +03:00
|
|
|
}
|
2021-12-09 20:25:54 +03:00
|
|
|
if files != nil {
|
|
|
|
*files = append(*files, fileLocation)
|
|
|
|
}
|
|
|
|
if filepath.Ext(fileLocation) != ".yaml" {
|
|
|
|
node.Value = string(file)
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
newctx := map[string]string{}
|
|
|
|
for k, v := range ctx {
|
|
|
|
newctx[k] = v
|
|
|
|
}
|
|
|
|
newctx[baseDirectoryKey] = filepath.Dir(filepath.Join(baseDir, node.Value))
|
|
|
|
var f = newFragment(newctx, files)
|
|
|
|
err = yaml.Unmarshal(file, f)
|
|
|
|
if err != nil {
|
2022-06-15 18:44:36 +03:00
|
|
|
return nil, &YamlTagResolverError{node.Tag, fileLocation, err}
|
2021-12-09 20:25:54 +03:00
|
|
|
}
|
|
|
|
return f.content, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func resolveTags(ctx map[string]string, node *yaml.Node, files *[]string) (*yaml.Node, error) {
|
2022-06-15 18:44:36 +03:00
|
|
|
switch node.Kind {
|
|
|
|
case yaml.DocumentNode, yaml.SequenceNode, yaml.MappingNode:
|
|
|
|
var err error
|
|
|
|
for idx := range node.Content {
|
|
|
|
node.Content[idx], err = resolveTags(ctx, node.Content[idx], files)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-16 09:25:26 +03:00
|
|
|
switch node.Tag {
|
|
|
|
case includeTag:
|
2021-12-09 20:25:54 +03:00
|
|
|
return resolver(ctx, node, files)
|
2021-02-16 09:25:26 +03:00
|
|
|
case "!!str":
|
|
|
|
if strings.Contains(node.Value, includeTag) {
|
|
|
|
node.Tag = includeTag
|
|
|
|
parts := strings.Split(node.Value, " ")
|
2021-05-18 13:42:46 +03:00
|
|
|
node.Value = strings.Trim(strings.Join(parts[1:], " "), "\"")
|
2021-12-09 20:25:54 +03:00
|
|
|
return resolver(ctx, node, files)
|
2021-02-16 09:25:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return node, nil
|
|
|
|
}
|
2021-12-09 20:25:54 +03:00
|
|
|
|
|
|
|
// GetIncludeTagFiles files will return file paths of all child !include tag values
|
|
|
|
// eg:
|
|
|
|
// for example say the following are contents rootfile.yaml
|
|
|
|
// somekey: somevalue
|
|
|
|
// foos: !include foo.yaml
|
|
|
|
//
|
|
|
|
// let foo.yaml contain the following contents
|
|
|
|
// someother: key
|
|
|
|
// bar: !include bar.yaml
|
|
|
|
//
|
|
|
|
// contents of bar.yaml
|
|
|
|
// baz: baz
|
|
|
|
//
|
|
|
|
// On execution of GetIncludeTagFiles(rootfile), It is expected to return
|
|
|
|
// rootfileparent/foo.yaml, fooparent/bar.yaml
|
|
|
|
func GetIncludeTagFiles(node *yaml.Node, baseDirectory string) ([]string, error) {
|
|
|
|
var filenames []string
|
|
|
|
_, err := resolveTags(map[string]string{baseDirectoryKey: baseDirectory}, node, &filenames)
|
|
|
|
return filenames, err
|
|
|
|
}
|
2022-06-15 15:38:39 +03:00
|
|
|
|
|
|
|
func YAMLToJSON(yamlbs []byte) ([]byte, error) {
|
|
|
|
cueExpr, err := cueyaml.Extract("", yamlbs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cue extraction error: %w", err)
|
|
|
|
}
|
|
|
|
cueNode, err := format.Node(cueExpr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cue formatting error: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cueValue := cuecontext.New().CompileBytes(cueNode)
|
|
|
|
jsonString, err := cuejson.Marshal(cueValue)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return []byte(jsonString), nil
|
|
|
|
}
|