123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- package yamlpatch
- import (
- "bytes"
- "io"
- "os"
- "github.com/pkg/errors"
- log "github.com/sirupsen/logrus"
- "gopkg.in/yaml.v2"
- )
- type Patcher struct {
- BaseFilePath string
- PatchFilePath string
- quiet bool
- }
- func NewPatcher(filePath string, suffix string) *Patcher {
- return &Patcher{
- BaseFilePath: filePath,
- PatchFilePath: filePath + suffix,
- quiet: false,
- }
- }
- // SetQuiet sets the quiet flag, which will log as DEBUG_LEVEL instead of INFO
- func (p *Patcher) SetQuiet(quiet bool) {
- p.quiet = quiet
- }
- // read a single YAML file, check for errors (the merge package doesn't) then return the content as bytes.
- func readYAML(filePath string) ([]byte, error) {
- var content []byte
- var err error
- if content, err = os.ReadFile(filePath); err != nil {
- return nil, errors.Wrap(err, "while reading yaml file")
- }
- var yamlMap map[interface{}]interface{}
- if err = yaml.Unmarshal(content, &yamlMap); err != nil {
- return nil, errors.Wrap(err, filePath)
- }
- return content, nil
- }
- // MergedPatchContent reads a YAML file and, if it exists, its patch file,
- // then merges them and returns it serialized.
- func (p *Patcher) MergedPatchContent() ([]byte, error) {
- var err error
- var base []byte
- base, err = readYAML(p.BaseFilePath)
- if err != nil {
- return nil, err
- }
- var over []byte
- over, err = readYAML(p.PatchFilePath)
- if errors.Is(err, os.ErrNotExist) {
- return base, nil
- }
- if err != nil {
- return nil, err
- }
- logf := log.Infof
- if p.quiet {
- logf = log.Debugf
- }
- logf("Patching yaml: '%s' with '%s'", p.BaseFilePath, p.PatchFilePath)
- var patched *bytes.Buffer
- // strict mode true, will raise errors for duplicate map keys and
- // overriding with a different type
- patched, err = YAML([][]byte{base, over}, true)
- if err != nil {
- return nil, err
- }
- return patched.Bytes(), nil
- }
- // read multiple YAML documents inside a file, and writes them to a buffer
- // separated by the appropriate '---' terminators.
- func decodeDocuments(file *os.File, buf *bytes.Buffer, finalDashes bool) error {
- var (
- err error
- docBytes []byte
- )
- dec := yaml.NewDecoder(file)
- dec.SetStrict(true)
- dashTerminator := false
- for {
- yml := make(map[interface{}]interface{})
- err = dec.Decode(&yml)
- if err != nil {
- if errors.Is(err, io.EOF) {
- break
- }
- return errors.Wrapf(err, "while decoding %s", file.Name())
- }
- docBytes, err = yaml.Marshal(&yml)
- if err != nil {
- return errors.Wrapf(err, "while marshaling %s", file.Name())
- }
- if dashTerminator {
- buf.Write([]byte("---\n"))
- }
- buf.Write(docBytes)
- dashTerminator = true
- }
- if dashTerminator && finalDashes {
- buf.Write([]byte("---\n"))
- }
- return nil
- }
- // PrependedPatchContent collates the base .yaml file with the .yaml.patch, by putting
- // the content of the patch BEFORE the base document. The result is a multi-document
- // YAML in all cases, even if the base and patch files are single documents.
- func (p *Patcher) PrependedPatchContent() ([]byte, error) {
- var (
- result bytes.Buffer
- patchFile *os.File
- baseFile *os.File
- err error
- )
- patchFile, err = os.Open(p.PatchFilePath)
- // optional file, ignore if it does not exist
- if err != nil && !errors.Is(err, os.ErrNotExist) {
- return nil, errors.Wrapf(err, "while opening %s", p.PatchFilePath)
- }
- if patchFile != nil {
- if err = decodeDocuments(patchFile, &result, true); err != nil {
- return nil, err
- }
- logf := log.Infof
- if p.quiet {
- logf = log.Debugf
- }
- logf("Prepending yaml: '%s' with '%s'", p.BaseFilePath, p.PatchFilePath)
- }
- baseFile, err = os.Open(p.BaseFilePath)
- if err != nil {
- return nil, errors.Wrapf(err, "while opening %s", p.BaseFilePath)
- }
- if err = decodeDocuments(baseFile, &result, false); err != nil {
- return nil, err
- }
- return result.Bytes(), nil
- }
|