diff --git a/cmd/sops/main.go b/cmd/sops/main.go index 330c3bc8e..3a7216619 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -1863,6 +1863,14 @@ func main() { Name: "indent", Usage: "the number of spaces to indent YAML or JSON encoded file", }, + cli.BoolFlag{ + Name: "compact-array-indent", + Usage: "use compact YAML array indentation where '- ' is considered part of the indentation", + }, + cli.BoolFlag{ + Name: "document-start-marker", + Usage: "prepend the YAML document start marker '---' to the output", + }, cli.BoolFlag{ Name: "verbose", Usage: "Enable verbose logging output", @@ -2394,6 +2402,12 @@ func outputStore(context *cli.Context, path string) (common.Store, error) { storesConf.JSON.Indent = indent storesConf.JSONBinary.Indent = indent } + if context.GlobalBool("compact-array-indent") { + storesConf.YAML.CompactArrayIndent = true + } + if context.GlobalBool("document-start-marker") { + storesConf.YAML.DocumentStartMarker = true + } return common.DefaultStoreForPathOrFormat(storesConf, path, context.String("output-type")), nil } diff --git a/config/config.go b/config/config.go index 511df1bc1..2977357a1 100644 --- a/config/config.go +++ b/config/config.go @@ -112,7 +112,9 @@ type JSONBinaryStoreConfig struct { } type YAMLStoreConfig struct { - Indent int `yaml:"indent"` + Indent int `yaml:"indent"` + CompactArrayIndent bool `yaml:"compact_array_indent"` + DocumentStartMarker bool `yaml:"document_start_marker"` } type StoresConfig struct { diff --git a/stores/yaml/store.go b/stores/yaml/store.go index 1589fdcdc..74e32937b 100644 --- a/stores/yaml/store.go +++ b/stores/yaml/store.go @@ -380,6 +380,9 @@ func (store *Store) EmitPlainFile(branches sops.TreeBranches) ([]byte, error) { return nil, err } e.SetIndent(indent) + if store.config.CompactArrayIndent { + e.CompactSeqIndent() + } for _, branch := range branches { // Document root var doc = yaml.Node{} @@ -397,6 +400,9 @@ func (store *Store) EmitPlainFile(branches sops.TreeBranches) ([]byte, error) { } } e.Close() + if store.config.DocumentStartMarker && !bytes.HasPrefix(b.Bytes(), []byte("---")) { + return append([]byte("---\n"), b.Bytes()...), nil + } return b.Bytes(), nil } diff --git a/stores/yaml/store_test.go b/stores/yaml/store_test.go index 42445ae80..fd5ef0058 100644 --- a/stores/yaml/store_test.go +++ b/stores/yaml/store_test.go @@ -440,6 +440,165 @@ func TestIndent1(t *testing.T) { assert.Equal(t, INDENT_1_OUT, bytes) } +func TestCompactArrayIndent(t *testing.T) { + in := []byte(`spec: + rules: + - host: example.com + http: + paths: + - path: /api + backend: + serviceName: api + - path: /web + backend: + serviceName: web + tags: + - production + - web +`) + // With default indent (4) and CompactSeqIndent, '- ' is considered + // part of the indentation, so arrays are indented by (indent - 2) spaces. + expected := []byte(`spec: + rules: + - host: example.com + http: + paths: + - path: /api + backend: + serviceName: api + - path: /web + backend: + serviceName: web + tags: + - production + - web +`) + branches, err := (&Store{}).LoadPlainFile(in) + assert.Nil(t, err) + bytes, err := (&Store{ + config: config.YAMLStoreConfig{ + CompactArrayIndent: true, + }, + }).EmitPlainFile(branches) + assert.Nil(t, err) + assert.Equal(t, string(expected), string(bytes)) +} + +func TestCompactArrayIndentWithIndent2(t *testing.T) { + // With indent 2, compact array indent produces arrays flush with the parent key. + in := []byte(`spec: + rules: + - host: example.com + http: + paths: + - path: /api + backend: + serviceName: api + tags: + - production + - web +`) + expected := []byte(`spec: + rules: + - host: example.com + http: + paths: + - path: /api + backend: + serviceName: api + tags: + - production + - web +`) + branches, err := (&Store{}).LoadPlainFile(in) + assert.Nil(t, err) + bytes, err := (&Store{ + config: config.YAMLStoreConfig{ + Indent: 2, + CompactArrayIndent: true, + }, + }).EmitPlainFile(branches) + assert.Nil(t, err) + assert.Equal(t, string(expected), string(bytes)) +} + +func TestCompactArrayIndentDisabled(t *testing.T) { + in := []byte(`spec: + rules: + - host: example.com + http: + paths: + - path: /api +`) + branches, err := (&Store{}).LoadPlainFile(in) + assert.Nil(t, err) + bytes, err := (&Store{ + config: config.YAMLStoreConfig{ + CompactArrayIndent: false, + }, + }).EmitPlainFile(branches) + assert.Nil(t, err) + assert.Equal(t, string(in), string(bytes)) +} + +func TestDocumentStartMarker(t *testing.T) { + in := []byte(`key: value +nested: + list: + - a + - b +`) + expected := []byte(`--- +key: value +nested: + list: + - a + - b +`) + branches, err := (&Store{}).LoadPlainFile(in) + assert.Nil(t, err) + bytes, err := (&Store{ + config: config.YAMLStoreConfig{ + DocumentStartMarker: true, + }, + }).EmitPlainFile(branches) + assert.Nil(t, err) + assert.Equal(t, string(expected), string(bytes)) +} + +func TestDocumentStartMarkerMultiDoc(t *testing.T) { + in := []byte(`--- +key1: value1 +--- +key2: value2`) + expected := []byte(`--- +key1: value1 +--- +key2: value2 +`) + branches, err := (&Store{}).LoadPlainFile(in) + assert.Nil(t, err) + bytes, err := (&Store{ + config: config.YAMLStoreConfig{ + DocumentStartMarker: true, + }, + }).EmitPlainFile(branches) + assert.Nil(t, err) + assert.Equal(t, string(expected), string(bytes)) +} + +func TestDocumentStartMarkerDisabled(t *testing.T) { + in := []byte(`key: value +`) + branches, err := (&Store{}).LoadPlainFile(in) + assert.Nil(t, err) + bytes, err := (&Store{}).EmitPlainFile(branches) + assert.Nil(t, err) + assert.Equal(t, string(in), string(bytes)) + // Verify no '---' was prepended + assert.False(t, len(bytes) > 3 && string(bytes[:3]) == "---") +} + func TestHasSopsTopLevelKey(t *testing.T) { ok := (&Store{}).HasSopsTopLevelKey(sops.TreeBranch{ sops.TreeItem{