1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
package file
import (
"bufio"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
ghodssyaml "github.com/ghodss/yaml"
"github.com/imdario/mergo"
"github.com/pkg/errors"
)
// getContent reads all the YAML and JSON files in the directory or the
// file, depending on the type of each item in filenames, merges the content of
// these files and renders a Content.
func getContent(filenames []string) (*Content, error) {
var allReaders []io.Reader
for _, fileOrDir := range filenames {
readers, err := getReaders(fileOrDir)
if err != nil {
return nil, err
}
allReaders = append(allReaders, readers...)
}
var res Content
for _, r := range allReaders {
content, err := readContent(r)
if err != nil {
return nil, errors.Wrap(err, "reading file")
}
err = mergo.Merge(&res, content, mergo.WithAppendSlice)
if err != nil {
return nil, errors.Wrap(err, "merging file contents")
}
}
return &res, nil
}
// getReaders returns back io.Readers representing all the YAML and JSON
// files in a directory. If fileOrDir is a single file, then it
// returns back the reader for the file.
// If fileOrDir is equal to "-" string, then it returns back a io.Reader
// for the os.Stdin file descriptor.
func getReaders(fileOrDir string) ([]io.Reader, error) {
// special case where `-` means stdin
if fileOrDir == "-" {
return []io.Reader{os.Stdin}, nil
}
finfo, err := os.Stat(fileOrDir)
if err != nil {
return nil, errors.Wrap(err, "reading state file")
}
var files []string
if finfo.IsDir() {
files, err = configFilesInDir(fileOrDir)
if err != nil {
return nil,
errors.Wrap(err, "getting files from directory")
}
} else {
files = append(files, fileOrDir)
}
var res []io.Reader
for _, file := range files {
f, err := os.Open(file)
if err != nil {
return nil, errors.Wrap(err, "opening file")
}
res = append(res, bufio.NewReader(f))
}
return res, nil
}
// readContent reads all the byes until io.EOF and unmarshals the read
// bytes into Content.
func readContent(reader io.Reader) (*Content, error) {
var content Content
var bytes []byte
var err error
bytes, err = ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
err = validate(bytes)
if err != nil {
return nil, errors.Wrap(err, "validating file content")
}
err = yamlUnmarshal(bytes, &content)
if err != nil {
return nil, err
}
return &content, nil
}
// yamlUnmarshal is a wrapper around yaml.Unmarshal to ensure that the right
// yaml package is in use. Using ghodss/yaml ensures that no
// `map[interface{}]interface{}` is present in go-kong.Plugin.Configuration.
// If it is present, then it leads to a silent error. See Github Issue #144.
// The verification for this is done using a test.
func yamlUnmarshal(bytes []byte, v interface{}) error {
return ghodssyaml.Unmarshal(bytes, v)
}
// configFilesInDir traverses the directory rooted at dir and
// returns all the files with a case-insensitive extension of `yml` or `yaml`.
func configFilesInDir(dir string) ([]string, error) {
var res []string
err := filepath.Walk(
dir,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
switch strings.ToLower(filepath.Ext(path)) {
case ".yaml", ".yml", ".json":
res = append(res, path)
}
return nil
},
)
if err != nil {
return nil, errors.Wrap(err, "reading state directory")
}
return res, nil
}
|