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
7 changes: 6 additions & 1 deletion internal/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ func applyReportOptionsFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&reportOptions.detectRenames, "detect-renames", defaults.detectRenames, "enable detection for renames (document level for Kubernetes resources)")

// Main output preferences
cmd.Flags().StringVarP(&reportOptions.style, "output", "o", defaults.style, "specify the output style, supported styles: human, brief, github, gitlab, gitea")
cmd.Flags().StringVarP(&reportOptions.style, "output", "o", defaults.style, "specify the output style, supported styles: human, brief, github, gitlab, gitea, yaml")
cmd.Flags().BoolVarP(&reportOptions.omitHeader, "omit-header", "b", defaults.omitHeader, "omit the dyff summary header")
cmd.Flags().BoolVarP(&reportOptions.exitWithCode, "set-exit-code", "s", defaults.exitWithCode, "set program exit code, with 0 meaning no difference, 1 for differences detected, and 255 for program error")

Expand Down Expand Up @@ -280,6 +280,11 @@ func writeReport(cmd *cobra.Command, report dyff.Report) error {
},
}

case "yaml", "yml":
reportWriter = &dyff.YAMLReport{
Report: report,
}

case "brief", "short", "summary":
reportWriter = &dyff.BriefReport{
Report: report,
Expand Down
54 changes: 53 additions & 1 deletion pkg/dyff/core_identifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package dyff

import (
"fmt"
"regexp"
"strings"

yamlv3 "gopkg.in/yaml.v3"
Expand Down Expand Up @@ -136,6 +137,57 @@ func (i *k8sItemIdentifier) Name(node *yamlv3.Node) (string, error) {
return strings.Join(elem, "/"), nil
}

func (lf *k8sItemIdentifier) String() string {
func (i *k8sItemIdentifier) String() string {
return "resource"
}

func K8sMetaFromName(name string) (*K8sMetadata, error) {
parts := strings.Split(name, "/")

switch len(parts) {
case 3:
// Minimum case. Must be APIVersion/Kind/Name
// where APIVersion has no group
return &K8sMetadata{
APIVersion: parts[0],
Kind: parts[1],
Metadata: map[string]string{
"name": parts[2],
},
}, nil
case 4:
// Could be APIVersion/Kind/Namespace/Name or APIVersion/Kind/Name
// if APIVersion has a group i.e. apps
if regexp.MustCompile(`^v\d+([a-z]+\d+)?$`).MatchString(parts[0]) {
return &K8sMetadata{
APIVersion: parts[0],
Kind: parts[1],
Metadata: map[string]string{
"namespace": parts[2],
"name": parts[3],
},
}, nil
}
return &K8sMetadata{
APIVersion: parts[0] + "/" + parts[1],
Kind: parts[2],
Metadata: map[string]string{
"name": parts[3],
},
}, nil

case 5:
// Maximum case. Must be APIVersion/Group/Kind/Namespace/Name
// where APIVersion has a group i.e. apps
return &K8sMetadata{
APIVersion: parts[0] + "/" + parts[1],
Kind: parts[2],
Metadata: map[string]string{
"namespace": parts[3],
"name": parts[4],
},
}, nil
}

return nil, fmt.Errorf("invalid resource name %q", name)
}
6 changes: 6 additions & 0 deletions pkg/dyff/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ type Detail struct {
Kind rune
}

type K8sMetadata struct {
APIVersion string
Kind string
Metadata map[string]string
}

// Diff encapsulates everything noteworthy about a difference
type Diff struct {
Path *ytbx.Path
Expand Down
146 changes: 146 additions & 0 deletions pkg/dyff/output_yaml.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package dyff

import (
"bufio"
"github.com/gonvenience/neat"
"github.com/gonvenience/ytbx"
"io"
)

type YAMLReport struct {
Report
}

type YAMLReportDiff struct {
Details map[string]string
Path string
}

type YAMLReportOutput struct {
APIVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
Metadata map[string]string `yaml:"metadata"`
Diffs []YAMLReportDiff `yaml:"diffs"`
}

// TODO: Support non-Kubernetes yaml documents
func (report *YAMLReport) WriteReport(out io.Writer) error {
writer := bufio.NewWriter(out)
defer writer.Flush()
consolidatedDiff, err := report.consolidateDiff()
if err != nil {
return err
}
for file, diffs := range consolidatedDiff {
meta, err := K8sMetaFromName(file)
if err != nil {
return err
}
var d []YAMLReportDiff
for _, diff := range diffs {
d = append(d, YAMLReportDiff{
Path: diff.Path,
Details: diff.Details,
})
}
data := YAMLReportOutput{
APIVersion: meta.APIVersion,
Kind: meta.Kind,
Metadata: meta.Metadata,
Diffs: d,
}

// Use neat to format the YAML output
yamlData, err := neat.NewOutputProcessor(false, true, nil).ToYAML(data)
if err != nil {
return err
}

if _, err := writer.WriteString(yamlData); err != nil {
return err
}
}

_, _ = writer.WriteString("\n") // Ensure a newline at the end of the report
return nil
}

func (report *YAMLReport) consolidateDiff() (map[string][]YAMLReportDiff, error) {
fileDiffs := make(map[string][]YAMLReportDiff)

for _, diff := range report.Diffs {
deet := make(map[string]string)
switch len(diff.Details) {
case 1:
switch diff.Details[0].Kind {
case ADDITION:
ytbx.RestructureObject(diff.Details[0].To)
output, err := neat.NewOutputProcessor(false, true, nil).ToYAML(diff.Details[0].To)
if err != nil {
return nil, err
}
deet["to"] = output
deet["from"] = ""
deet["kind"] = "addition"
case REMOVAL:
ytbx.RestructureObject(diff.Details[0].From)
output, err := neat.NewOutputProcessor(false, true, nil).ToYAML(diff.Details[0].From)
if err != nil {
return nil, err
}
deet["to"] = ""
deet["from"] = output
deet["kind"] = "removal"
case MODIFICATION:
ytbx.RestructureObject(diff.Details[0].To)
outputTo, err := neat.NewOutputProcessor(false, true, nil).ToYAML(diff.Details[0].To)
ytbx.RestructureObject(diff.Details[0].From)
outputFrom, err := neat.NewOutputProcessor(false, true, nil).ToYAML(diff.Details[0].From)
if err != nil {
return nil, err
}
deet["to"] = outputTo
deet["from"] = outputFrom
deet["kind"] = "modification"
case ORDERCHANGE:
ytbx.RestructureObject(diff.Details[0].To)
outputTo, err := neat.NewOutputProcessor(false, true, nil).ToYAML(diff.Details[0].To)
ytbx.RestructureObject(diff.Details[0].From)
outputFrom, err := neat.NewOutputProcessor(false, true, nil).ToYAML(diff.Details[0].From)
if err != nil {
return nil, err
}
deet["to"] = outputTo
deet["from"] = outputFrom
deet["kind"] = "orderchange"
}
case 2:
for _, detail := range diff.Details {
switch detail.Kind {
case ADDITION:
ytbx.RestructureObject(detail.To)
output, err := neat.NewOutputProcessor(false, true, nil).ToYAML(detail.To)
if err != nil {
return nil, err
}
deet["to"] = output
case REMOVAL:
ytbx.RestructureObject(detail.From)
output, err := neat.NewOutputProcessor(false, true, nil).ToYAML(detail.From)
if err != nil {
return nil, err
}
deet["from"] = output
}
}
deet["kind"] = "modification"
}

fileDiffs[diff.Path.RootDescription()] = append(fileDiffs[diff.Path.RootDescription()], YAMLReportDiff{
Path: diff.Path.String(),
Details: deet,
})
}

return fileDiffs, nil
}