252 lines
5.4 KiB
Go
252 lines
5.4 KiB
Go
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/compose-spec/compose-go/template"
|
|
"github.com/compose-spec/compose-go/utils"
|
|
)
|
|
|
|
type trimmer struct {
|
|
input *bytes.Reader
|
|
output bytes.Buffer
|
|
}
|
|
|
|
func removeComments(buf []byte) ([]byte, error) {
|
|
t := &trimmer{
|
|
input: bytes.NewReader(buf),
|
|
output: bytes.Buffer{},
|
|
}
|
|
err := t.process()
|
|
return t.output.Bytes(), err
|
|
}
|
|
|
|
func (t *trimmer) process() error {
|
|
for {
|
|
// Read the next byte until EOF
|
|
c, err := t.input.ReadByte()
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Switch states if we need to
|
|
switch c {
|
|
case '\\':
|
|
//nolint:errcheck // next byte is known
|
|
t.input.UnreadByte()
|
|
err = t.escape()
|
|
case '\'':
|
|
//nolint:errcheck // next byte is known
|
|
t.input.UnreadByte()
|
|
if t.hasNQuotes(c, 3) {
|
|
err = t.tripleSingleQuote()
|
|
} else {
|
|
err = t.singleQuote()
|
|
}
|
|
case '"':
|
|
//nolint:errcheck // next byte is known
|
|
t.input.UnreadByte()
|
|
if t.hasNQuotes(c, 3) {
|
|
err = t.tripleDoubleQuote()
|
|
} else {
|
|
err = t.doubleQuote()
|
|
}
|
|
case '#':
|
|
err = t.comment()
|
|
default:
|
|
t.output.WriteByte(c)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (t *trimmer) hasNQuotes(ref byte, limit int64) bool {
|
|
var count int64
|
|
// Look ahead check if the next characters are what we expect
|
|
for count = 0; count < limit; count++ {
|
|
c, err := t.input.ReadByte()
|
|
if err != nil || c != ref {
|
|
break
|
|
}
|
|
}
|
|
// We also need to unread the non-matching character
|
|
offset := -count
|
|
if count < limit {
|
|
offset--
|
|
}
|
|
//nolint:errcheck // Unread the already matched characters
|
|
t.input.Seek(offset, io.SeekCurrent)
|
|
return count >= limit
|
|
}
|
|
|
|
func (t *trimmer) readWriteByte() (byte, error) {
|
|
c, err := t.input.ReadByte()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return c, t.output.WriteByte(c)
|
|
}
|
|
|
|
func (t *trimmer) escape() error {
|
|
//nolint:errcheck // Consume the known starting backslash and quote
|
|
t.readWriteByte()
|
|
|
|
// Read the next character which is the escaped one and exit
|
|
_, err := t.readWriteByte()
|
|
return err
|
|
}
|
|
|
|
func (t *trimmer) singleQuote() error {
|
|
//nolint:errcheck // Consume the known starting quote
|
|
t.readWriteByte()
|
|
|
|
// Read bytes until EOF, line end or another single quote
|
|
for {
|
|
if c, err := t.readWriteByte(); err != nil || c == '\'' || c == '\n' {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *trimmer) tripleSingleQuote() error {
|
|
for i := 0; i < 3; i++ {
|
|
//nolint:errcheck // Consume the known starting quotes
|
|
t.readWriteByte()
|
|
}
|
|
|
|
// Read bytes until EOF or another set of triple single quotes
|
|
for {
|
|
c, err := t.readWriteByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if c == '\'' && t.hasNQuotes('\'', 2) {
|
|
//nolint:errcheck // Consume the two additional ending quotes
|
|
t.readWriteByte()
|
|
//nolint:errcheck // Consume the two additional ending quotes
|
|
t.readWriteByte()
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func (t *trimmer) doubleQuote() error {
|
|
//nolint:errcheck // Consume the known starting quote
|
|
t.readWriteByte()
|
|
|
|
// Read bytes until EOF, line end or another double quote
|
|
for {
|
|
c, err := t.input.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch c {
|
|
case '\\':
|
|
//nolint:errcheck // Consume the found escaped character
|
|
t.input.UnreadByte()
|
|
if err := t.escape(); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
case '"', '\n':
|
|
// Found terminator
|
|
return t.output.WriteByte(c)
|
|
}
|
|
t.output.WriteByte(c)
|
|
}
|
|
}
|
|
|
|
func (t *trimmer) tripleDoubleQuote() error {
|
|
for i := 0; i < 3; i++ {
|
|
//nolint:errcheck // Consume the known starting quotes
|
|
t.readWriteByte()
|
|
}
|
|
|
|
// Read bytes until EOF or another set of triple double quotes
|
|
for {
|
|
c, err := t.input.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
switch c {
|
|
case '\\':
|
|
//nolint:errcheck // Consume the found escape character
|
|
t.input.UnreadByte()
|
|
if err := t.escape(); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
case '"':
|
|
t.output.WriteByte(c)
|
|
if t.hasNQuotes('"', 2) {
|
|
//nolint:errcheck // Consume the two additional ending quotes
|
|
t.readWriteByte()
|
|
//nolint:errcheck // Consume the two additional ending quotes
|
|
t.readWriteByte()
|
|
return nil
|
|
}
|
|
continue
|
|
}
|
|
t.output.WriteByte(c)
|
|
}
|
|
}
|
|
|
|
func (t *trimmer) comment() error {
|
|
// Read bytes until EOF or a line break
|
|
for {
|
|
c, err := t.input.ReadByte()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if c == '\n' {
|
|
return t.output.WriteByte(c)
|
|
}
|
|
}
|
|
}
|
|
|
|
func substituteEnvironment(contents []byte, oldReplacementBehavior bool) ([]byte, error) {
|
|
options := []template.Option{
|
|
template.WithReplacementFunction(func(s string, m template.Mapping, cfg *template.Config) (string, error) {
|
|
result, applied, err := template.DefaultReplacementAppliedFunc(s, m, cfg)
|
|
if err == nil && !applied {
|
|
// Keep undeclared environment-variable patterns to reproduce
|
|
// pre-v1.27 behavior
|
|
return s, nil
|
|
}
|
|
if err != nil && strings.HasPrefix(err.Error(), "Invalid template:") {
|
|
// Keep invalid template patterns to ignore regexp substitutions
|
|
// like ${1}
|
|
return s, nil
|
|
}
|
|
return result, err
|
|
}),
|
|
template.WithoutLogging,
|
|
}
|
|
if oldReplacementBehavior {
|
|
options = append(options, template.WithPattern(oldVarRe))
|
|
}
|
|
|
|
envMap := utils.GetAsEqualsMap(os.Environ())
|
|
retVal, err := template.SubstituteWithOptions(string(contents), func(k string) (string, bool) {
|
|
if v, ok := envMap[k]; ok {
|
|
return v, ok
|
|
}
|
|
return "", false
|
|
}, options...)
|
|
return []byte(retVal), err
|
|
}
|