File: patcher.go

package info (click to toggle)
crowdsec 1.4.6-10.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 18,500 kB
  • sloc: sh: 2,870; makefile: 386; python: 74
file content (154 lines) | stat: -rw-r--r-- 3,560 bytes parent folder | download | duplicates (3)
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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
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
}

func NewPatcher(filePath string, suffix string) *Patcher {
	return &Patcher{
		BaseFilePath:  filePath,
		PatchFilePath: filePath + suffix,
	}
}

// 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)
	// optional file, ignore if it does not exist
	if err != nil && !errors.Is(err, os.ErrNotExist) {
		return nil, err
	}
	if err == nil {
		log.Debugf("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
		}
		log.Infof("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
}