Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2099,7 +2099,10 @@ Stores configuration object

The store configuration object can have the following keys:

* ``dotenv``: this is an object. Right now no keys are supported.
* ``dotenv``: this is an object, supporting the following keys:

* ``quote`` (boolean; default ``false``): when ``true``, values are
double-quoted on emit and must be double-quoted on load.

* ``ini``: this is an object. Right now no keys are supported.

Expand Down
4 changes: 3 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ func FindConfigFile(start string) (string, error) {
return result.Path, err
}

type DotenvStoreConfig struct{}
type DotenvStoreConfig struct {
Quote bool `yaml:"quote"`
}

type INIStoreConfig struct{}

Expand Down
26 changes: 22 additions & 4 deletions stores/dotenv/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"

"github.com/getsops/sops/v3"
Expand Down Expand Up @@ -55,7 +56,9 @@ func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) {
return sops.Tree{}, sops.MetadataNotFound
}

stores.DecodeNewLines(mdMap)
if !store.config.Quote {
stores.DecodeNewLines(mdMap)
}
err = stores.DecodeNonStrings(mdMap)
if err != nil {
return sops.Tree{}, err
Expand Down Expand Up @@ -97,9 +100,19 @@ func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) {
if pos == -1 {
return nil, fmt.Errorf("invalid dotenv input line: %s", line)
}
var value string
if store.config.Quote {
var err error
value, err = strconv.Unquote(string(line[pos+1:]))
if err != nil {
return nil, fmt.Errorf("invalid quoted dotenv value for key %q: %w", line[:pos], err)
}
} else {
value = strings.Replace(string(line[pos+1:]), "\\n", "\n", -1)
}
branch = append(branch, sops.TreeItem{
Key: string(line[:pos]),
Value: strings.Replace(string(line[pos+1:]), "\\n", "\n", -1),
Value: value,
})
}
}
Expand All @@ -118,7 +131,9 @@ func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) {
}

stores.EncodeNonStrings(mdItems)
stores.EncodeNewLines(mdItems)
if !store.config.Quote {
stores.EncodeNewLines(mdItems)
}

var keys []string
for k := range mdItems {
Expand Down Expand Up @@ -151,7 +166,10 @@ func (store *Store) EmitPlainFile(in sops.TreeBranches) ([]byte, error) {
value, ok := item.Value.(string)
if !ok {
value = stores.ValToString(item.Value)
} else {
}
if store.config.Quote {
value = strconv.Quote(value)
} else if ok {
value = strings.ReplaceAll(value, "\n", "\\n")
}

Expand Down
52 changes: 52 additions & 0 deletions stores/dotenv/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

"github.com/getsops/sops/v3"
"github.com/getsops/sops/v3/config"
"github.com/stretchr/testify/assert"
)

Expand Down Expand Up @@ -88,6 +89,57 @@ func TestEmitEncryptedFileStability(t *testing.T) {
}
}

var QUOTED_PLAIN = []byte(strings.TrimLeft(`
VAR1="val1"
VAR2="val2"
#comment
VAR3_unencrypted="val3"
VAR4="val4\nval4"
JSON="{ \"app_id\": \"123\" }"
`, "\n"))

var QUOTED_BRANCH = sops.TreeBranch{
sops.TreeItem{
Key: "VAR1",
Value: "val1",
},
sops.TreeItem{
Key: "VAR2",
Value: "val2",
},
sops.TreeItem{
Key: sops.Comment{Value: "comment"},
Value: nil,
},
sops.TreeItem{
Key: "VAR3_unencrypted",
Value: "val3",
},
sops.TreeItem{
Key: "VAR4",
Value: "val4\nval4",
},
sops.TreeItem{
Key: "JSON",
Value: `{ "app_id": "123" }`,
},
}

func TestQuotedLoadPlainFile(t *testing.T) {
branches, err := (&Store{config: config.DotenvStoreConfig{Quote: true}}).LoadPlainFile(QUOTED_PLAIN)
assert.Nil(t, err)
assert.Equal(t, QUOTED_BRANCH, branches[0])
}

func TestQuotedEmitPlainFile(t *testing.T) {
branches := sops.TreeBranches{
QUOTED_BRANCH,
}
bytes, err := (&Store{config: config.DotenvStoreConfig{Quote: true}}).EmitPlainFile(branches)
assert.Nil(t, err)
assert.Equal(t, QUOTED_PLAIN, bytes)
}

func TestHasSopsTopLevelKey(t *testing.T) {
ok := (&Store{}).HasSopsTopLevelKey(sops.TreeBranch{
sops.TreeItem{
Expand Down