diff --git a/sops.go b/sops.go index 718f51bf8..7352161d9 100644 --- a/sops.go +++ b/sops.go @@ -114,6 +114,11 @@ type Comment struct { Value string } +// EmptyLines represents empty lines in the sops tree +type EmptyLines struct { + Count int // Must be positive +} + // TreeItem is an item inside sops's tree type TreeItem struct { Key interface{} @@ -316,6 +321,9 @@ func (branch TreeBranch) walkSlice(in []interface{}, path []string, commentsStac // Because append returns a new slice, the original stack is not changed. commentsStack = append(commentsStack, []string{}) for i, v := range in { + if _, ok := v.(EmptyLines); ok { + continue + } c, vIsComment := v.(Comment) if vIsComment { // If v is a comment, we add it to the slice of active comments. @@ -339,6 +347,9 @@ func (branch TreeBranch) walkBranch(in TreeBranch, path []string, commentsStack // Because append returns a new slice, the original stack is not changed. commentsStack = append(commentsStack, []string{}) for i, item := range in { + if _, ok := item.Key.(EmptyLines); ok { + continue + } if c, ok := item.Key.(Comment); ok { // If key is a comment, we add it to the slice of active comments. // This allows us to also encrypt comments themselves by enabling encryption in a prior comment. diff --git a/stores/dotenv/store.go b/stores/dotenv/store.go index 1e533341e..57bdc425a 100644 --- a/stores/dotenv/store.go +++ b/stores/dotenv/store.go @@ -43,6 +43,8 @@ func (store *Store) LoadEncryptedFile(in []byte) (sops.Tree, error) { } case sops.Comment: resultBranch = append(resultBranch, item) + case sops.EmptyLines: + resultBranch = append(resultBranch, item) default: panic(fmt.Sprintf("Unexpected type: %T (value %#v)", key, key)) } @@ -76,10 +78,19 @@ func (store *Store) LoadPlainFile(in []byte) (sops.TreeBranches, error) { var branches sops.TreeBranches var branch sops.TreeBranch + emptyLines := 0 for _, line := range bytes.Split(in, []byte("\n")) { if len(line) == 0 { + emptyLines += 1 continue } + if emptyLines > 0 { + branch = append(branch, sops.TreeItem{ + Key: sops.EmptyLines{Count: emptyLines}, + Value: nil, + }) + emptyLines = 0 + } if line[0] == '#' { branch = append(branch, sops.TreeItem{ Key: sops.Comment{Value: string(line[1:])}, @@ -138,7 +149,9 @@ func (store *Store) EmitPlainFile(in sops.TreeBranches) ([]byte, error) { return nil, fmt.Errorf("cannot use complex value in dotenv file: %s", item.Value) } var line string - if comment, ok := item.Key.(sops.Comment); ok { + if emptyLines, ok := item.Key.(sops.EmptyLines); ok { + line = strings.Repeat("\n", emptyLines.Count) + } else if comment, ok := item.Key.(sops.Comment); ok { line = fmt.Sprintf("#%s\n", comment.Value) } else { value := strings.Replace(item.Value.(string), "\n", "\\n", -1) diff --git a/stores/ini/store.go b/stores/ini/store.go index 83605000e..07773c3a1 100644 --- a/stores/ini/store.go +++ b/stores/ini/store.go @@ -28,6 +28,9 @@ func (store Store) encodeTree(branches sops.TreeBranches) ([]byte, error) { iniFile.DeleteSection(ini.DefaultSection) for _, branch := range branches { for _, item := range branch { + if _, ok := item.Key.(sops.EmptyLines); ok { + continue + } if _, ok := item.Key.(sops.Comment); ok { continue } diff --git a/stores/json/store.go b/stores/json/store.go index 7b8bf3da5..66e373509 100644 --- a/stores/json/store.go +++ b/stores/json/store.go @@ -188,6 +188,9 @@ func (store Store) encodeArray(array []interface{}) ([]byte, error) { out := "[" empty := true for _, item := range array { + if _, ok := item.(sops.EmptyLines); ok { + continue + } if _, ok := item.(sops.Comment); ok { continue } @@ -209,6 +212,9 @@ func (store Store) encodeTree(tree sops.TreeBranch) ([]byte, error) { out := "{" empty := true for _, item := range tree { + if _, ok := item.Key.(sops.EmptyLines); ok { + continue + } if _, ok := item.Key.(sops.Comment); ok { continue } diff --git a/stores/yaml/store.go b/stores/yaml/store.go index 5d5c41d8d..291ece252 100644 --- a/stores/yaml/store.go +++ b/stores/yaml/store.go @@ -26,29 +26,63 @@ func NewStore(c *config.YAMLStoreConfig) *Store { func (store Store) appendCommentToList(comment string, list []interface{}) []interface{} { if comment != "" { + emptyLines := 0 for _, commentLine := range strings.Split(comment, "\n") { if commentLine != "" { + if emptyLines > 0 { + list = append(list, sops.EmptyLines{ + Count: emptyLines, + }) + emptyLines = 0 + } list = append(list, sops.Comment{ Value: commentLine[1:], }) + } else { + emptyLines += 1 } } + if emptyLines > 0 { + list = append(list, sops.EmptyLines{ + Count: emptyLines, + }) + } } return list } func (store Store) appendCommentToMap(comment string, branch sops.TreeBranch) sops.TreeBranch { if comment != "" { + emptyLines := 0 for _, commentLine := range strings.Split(comment, "\n") { if commentLine != "" { + if emptyLines > 0 { + branch = append(branch, sops.TreeItem{ + Key: sops.EmptyLines{ + Count: emptyLines, + }, + Value: nil, + }) + emptyLines = 0 + } branch = append(branch, sops.TreeItem{ Key: sops.Comment{ Value: commentLine[1:], }, Value: nil, }) + } else { + emptyLines += 1 } } + if emptyLines > 0 { + branch = append(branch, sops.TreeItem{ + Key: sops.EmptyLines{ + Count: emptyLines, + }, + Value: nil, + }) + } } return branch } @@ -158,7 +192,7 @@ func (store Store) yamlDocumentNodeToTreeBranch(in yaml.Node) (sops.TreeBranch, func (store *Store) addCommentsHead(node *yaml.Node, comments []string) []string { if len(comments) > 0 { - comment := "#" + strings.Join(comments, "\n#") + comment := strings.Join(comments, "\n") if len(node.HeadComment) > 0 { node.HeadComment = comment + "\n" + node.HeadComment } else { @@ -170,7 +204,7 @@ func (store *Store) addCommentsHead(node *yaml.Node, comments []string) []string func (store *Store) addCommentsFoot(node *yaml.Node, comments []string) []string { if len(comments) > 0 { - comment := "#" + strings.Join(comments, "\n#") + comment := strings.Join(comments, "\n") if len(node.FootComment) > 0 { node.FootComment += "\n" + comment } else { @@ -203,8 +237,12 @@ func (store *Store) appendSequence(in []interface{}, sequence *yaml.Node) { var comments []string var beginning bool = true for _, item := range in { - if comment, ok := item.(sops.Comment); ok { - comments = append(comments, comment.Value) + if emptyLines, ok := item.(sops.EmptyLines); ok { + for _ = range emptyLines.Count { + comments = append(comments, "") + } + } else if comment, ok := item.(sops.Comment); ok { + comments = append(comments, "#" + comment.Value) } else { if beginning { comments = store.addCommentsHead(sequence, comments) @@ -228,8 +266,12 @@ func (store *Store) appendTreeBranch(branch sops.TreeBranch, mapping *yaml.Node) var comments []string var beginning bool = true for _, item := range branch { - if comment, ok := item.Key.(sops.Comment); ok { - comments = append(comments, comment.Value) + if emptyLines, ok := item.Key.(sops.EmptyLines); ok { + for _ = range emptyLines.Count { + comments = append(comments, "") + } + } else if comment, ok := item.Key.(sops.Comment); ok { + comments = append(comments, "#" + comment.Value) } else { if beginning { comments = store.addCommentsHead(mapping, comments)