Skip to content

Commit 8e4f304

Browse files
committed
Add 'quote' option to dotenv format
Signed-off-by: Ilia Choly <ilia.choly@gmail.com>
1 parent 54deaac commit 8e4f304

4 files changed

Lines changed: 81 additions & 6 deletions

File tree

README.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2099,7 +2099,10 @@ Stores configuration object
20992099
21002100
The store configuration object can have the following keys:
21012101
2102-
* ``dotenv``: this is an object. Right now no keys are supported.
2102+
* ``dotenv``: this is an object, supporting the following keys:
2103+
2104+
* ``quote`` (boolean; default ``false``): when ``true``, values are
2105+
double-quoted on emit and must be double-quoted on load.
21032106
21042107
* ``ini``: this is an object. Right now no keys are supported.
21052108

config/config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,9 @@ func FindConfigFile(start string) (string, error) {
9999
return result.Path, err
100100
}
101101

102-
type DotenvStoreConfig struct{}
102+
type DotenvStoreConfig struct {
103+
Quote bool `yaml:"quote"`
104+
}
103105

104106
type INIStoreConfig struct{}
105107

stores/dotenv/store.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"fmt"
66
"sort"
7+
"strconv"
78
"strings"
89

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

58-
stores.DecodeNewLines(mdMap)
59+
if !store.config.Quote {
60+
stores.DecodeNewLines(mdMap)
61+
}
5962
err = stores.DecodeNonStrings(mdMap)
6063
if err != nil {
6164
return sops.Tree{}, err
@@ -97,9 +100,19 @@ func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) {
97100
if pos == -1 {
98101
return nil, fmt.Errorf("invalid dotenv input line: %s", line)
99102
}
103+
var value string
104+
if store.config.Quote {
105+
var err error
106+
value, err = strconv.Unquote(string(line[pos+1:]))
107+
if err != nil {
108+
return nil, fmt.Errorf("invalid quoted dotenv value for key %q: %w", line[:pos], err)
109+
}
110+
} else {
111+
value = strings.Replace(string(line[pos+1:]), "\\n", "\n", -1)
112+
}
100113
branch = append(branch, sops.TreeItem{
101114
Key: string(line[:pos]),
102-
Value: strings.Replace(string(line[pos+1:]), "\\n", "\n", -1),
115+
Value: value,
103116
})
104117
}
105118
}
@@ -118,7 +131,9 @@ func (store *Store) EmitEncryptedFile(in sops.Tree) ([]byte, error) {
118131
}
119132

120133
stores.EncodeNonStrings(mdItems)
121-
stores.EncodeNewLines(mdItems)
134+
if !store.config.Quote {
135+
stores.EncodeNewLines(mdItems)
136+
}
122137

123138
var keys []string
124139
for k := range mdItems {
@@ -151,7 +166,10 @@ func (store *Store) EmitPlainFile(in sops.TreeBranches) ([]byte, error) {
151166
value, ok := item.Value.(string)
152167
if !ok {
153168
value = stores.ValToString(item.Value)
154-
} else {
169+
}
170+
if store.config.Quote {
171+
value = strconv.Quote(value)
172+
} else if ok {
155173
value = strings.ReplaceAll(value, "\n", "\\n")
156174
}
157175

stores/dotenv/store_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/getsops/sops/v3"
8+
"github.com/getsops/sops/v3/config"
89
"github.com/stretchr/testify/assert"
910
)
1011

@@ -88,6 +89,57 @@ func TestEmitEncryptedFileStability(t *testing.T) {
8889
}
8990
}
9091

92+
var QUOTED_PLAIN = []byte(strings.TrimLeft(`
93+
VAR1="val1"
94+
VAR2="val2"
95+
#comment
96+
VAR3_unencrypted="val3"
97+
VAR4="val4\nval4"
98+
JSON="{ \"app_id\": \"123\" }"
99+
`, "\n"))
100+
101+
var QUOTED_BRANCH = sops.TreeBranch{
102+
sops.TreeItem{
103+
Key: "VAR1",
104+
Value: "val1",
105+
},
106+
sops.TreeItem{
107+
Key: "VAR2",
108+
Value: "val2",
109+
},
110+
sops.TreeItem{
111+
Key: sops.Comment{Value: "comment"},
112+
Value: nil,
113+
},
114+
sops.TreeItem{
115+
Key: "VAR3_unencrypted",
116+
Value: "val3",
117+
},
118+
sops.TreeItem{
119+
Key: "VAR4",
120+
Value: "val4\nval4",
121+
},
122+
sops.TreeItem{
123+
Key: "JSON",
124+
Value: `{ "app_id": "123" }`,
125+
},
126+
}
127+
128+
func TestQuotedLoadPlainFile(t *testing.T) {
129+
branches, err := (&Store{config: config.DotenvStoreConfig{Quote: true}}).LoadPlainFile(QUOTED_PLAIN)
130+
assert.Nil(t, err)
131+
assert.Equal(t, QUOTED_BRANCH, branches[0])
132+
}
133+
134+
func TestQuotedEmitPlainFile(t *testing.T) {
135+
branches := sops.TreeBranches{
136+
QUOTED_BRANCH,
137+
}
138+
bytes, err := (&Store{config: config.DotenvStoreConfig{Quote: true}}).EmitPlainFile(branches)
139+
assert.Nil(t, err)
140+
assert.Equal(t, QUOTED_PLAIN, bytes)
141+
}
142+
91143
func TestHasSopsTopLevelKey(t *testing.T) {
92144
ok := (&Store{}).HasSopsTopLevelKey(sops.TreeBranch{
93145
sops.TreeItem{

0 commit comments

Comments
 (0)