Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement calculate diffs and rendering diffs of k8s #5674

Merged
merged 2 commits into from
Mar 17, 2025
Merged
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
144 changes: 144 additions & 0 deletions pkg/app/pipedv1/plugin/kubernetes/provider/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
package provider

import (
"fmt"
"sort"
"strings"

"go.uber.org/zap"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -81,3 +85,143 @@

return &unstructured.Unstructured{Object: newO}, nil
}

type DiffListResult struct {
Adds []Manifest
Deletes []Manifest
Changes []DiffListChange
}

type DiffListChange struct {
Old Manifest
New Manifest
Diff *diff.Result
}

func (r *DiffListResult) NoChanges() bool {
return len(r.Adds)+len(r.Deletes)+len(r.Changes) == 0
}

func (r *DiffListResult) TotalOutOfSync() int {
return len(r.Adds) + len(r.Deletes) + len(r.Changes)
}

func DiffList(liveManifests, desiredManifests []Manifest, logger *zap.Logger, opts ...diff.Option) (*DiffListResult, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref;

func DiffList(olds, news []Manifest, logger *zap.Logger, opts ...diff.Option) (*DiffListResult, error) {
adds, deletes, newChanges, oldChanges := groupManifests(olds, news)
cr := &DiffListResult{
Adds: adds,
Deletes: deletes,
Changes: make([]DiffListChange, 0, len(newChanges)),
}
for i := 0; i < len(newChanges); i++ {
result, err := Diff(oldChanges[i], newChanges[i], logger, opts...)
if err != nil {
return nil, err
}
if !result.HasDiff() {
continue
}
cr.Changes = append(cr.Changes, DiffListChange{
Old: oldChanges[i],
New: newChanges[i],
Diff: result,
})
}
return cr, nil
}

adds, deletes, newChanges, oldChanges := groupManifests(liveManifests, desiredManifests)
result := &DiffListResult{
Adds: adds,
Deletes: deletes,
Changes: make([]DiffListChange, 0, len(newChanges)),
}

for i := 0; i < len(newChanges); i++ {
diffResult, err := Diff(oldChanges[i], newChanges[i], logger, opts...)
if err != nil {
logger.Error("Failed to diff manifests", zap.Error(err))
continue

Check warning on line 121 in pkg/app/pipedv1/plugin/kubernetes/provider/diff.go

View check run for this annotation

Codecov / codecov/patch

pkg/app/pipedv1/plugin/kubernetes/provider/diff.go#L120-L121

Added lines #L120 - L121 were not covered by tests
}
if !diffResult.HasDiff() {
continue
}
result.Changes = append(result.Changes, DiffListChange{
Old: oldChanges[i],
New: newChanges[i],
Diff: diffResult,
})
}

return result, nil
}

func groupManifests(olds, news []Manifest) (adds, deletes, newChanges, oldChanges []Manifest) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref;

func groupManifests(olds, news []Manifest) (adds, deletes, newChanges, oldChanges []Manifest) {
// Sort the manifests before comparing.
sort.Slice(news, func(i, j int) bool {
return news[i].Key.IsLessWithIgnoringNamespace(news[j].Key)
})
sort.Slice(olds, func(i, j int) bool {
return olds[i].Key.IsLessWithIgnoringNamespace(olds[j].Key)
})
var n, o int
for {
if n >= len(news) || o >= len(olds) {
break
}
if news[n].Key.IsEqualWithIgnoringNamespace(olds[o].Key) {
newChanges = append(newChanges, news[n])
oldChanges = append(oldChanges, olds[o])
n++
o++
continue
}
// Has in news but not in olds so this should be a added one.
if news[n].Key.IsLessWithIgnoringNamespace(olds[o].Key) {
adds = append(adds, news[n])
n++
continue
}
// Has in olds but not in news so this should be an deleted one.
deletes = append(deletes, olds[o])
o++
}
if len(news) > n {
adds = append(adds, news[n:]...)
}
if len(olds) > o {
deletes = append(deletes, olds[o:]...)
}
return
}

I changed it to compare with the namespace because the plugin's implementation sets the namespace when loading and parsing manifests, so I think it's ok.

// Sort the manifests before comparing.
sort.Slice(news, func(i, j int) bool {
return news[i].Key().String() < news[j].Key().String()
})
sort.Slice(olds, func(i, j int) bool {
return olds[i].Key().String() < olds[j].Key().String()
})

var n, o int
for {
if n >= len(news) || o >= len(olds) {
break
}
if news[n].Key().String() == olds[o].Key().String() {
newChanges = append(newChanges, news[n])
oldChanges = append(oldChanges, olds[o])
n++
o++
continue
}
// Has in news but not in olds so this should be a added one.
if news[n].Key().String() < olds[o].Key().String() {
adds = append(adds, news[n])
n++
continue
}
// Has in olds but not in news so this should be an deleted one.
deletes = append(deletes, olds[o])
o++
}

if len(news) > n {
adds = append(adds, news[n:]...)
}
if len(olds) > o {
deletes = append(deletes, olds[o:]...)
}
return adds, deletes, newChanges, oldChanges
}

type DiffRenderOptions struct {
MaskSecret bool
MaskConfigMap bool
// Maximum number of changed manifests should be shown.
// Zero means rendering all.
MaxChangedManifests int
}

func (r *DiffListResult) Render(opt DiffRenderOptions) string {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ref;

func (r *DiffListResult) Render(opt DiffRenderOptions) string {
var b strings.Builder
index := 0
for _, delete := range r.Deletes {
index++
b.WriteString(fmt.Sprintf("- %d. %s\n\n", index, delete.Key.ReadableString()))
}
for _, add := range r.Adds {
index++
b.WriteString(fmt.Sprintf("+ %d. %s\n\n", index, add.Key.ReadableString()))
}
maxPrintDiffs := len(r.Changes)
if opt.MaxChangedManifests != 0 && opt.MaxChangedManifests < maxPrintDiffs {
maxPrintDiffs = opt.MaxChangedManifests
}
var prints = 0
for _, change := range r.Changes {
key := change.Old.Key
opts := []diff.RenderOption{
diff.WithLeftPadding(1),
}
needMaskValue := false
if opt.MaskSecret && key.IsSecret() {
opts = append(opts, diff.WithMaskPath("data"))
needMaskValue = true
} else if opt.MaskConfigMap && key.IsConfigMap() {
opts = append(opts, diff.WithMaskPath("data"))
needMaskValue = true
}
renderer := diff.NewRenderer(opts...)
index++
b.WriteString(fmt.Sprintf("# %d. %s\n\n", index, key.ReadableString()))
// Use our diff check in one of the following cases:
// - not explicit set useDiffCommand option.
// - requires masking secret or configmap value.
if !opt.UseDiffCommand || needMaskValue {
b.WriteString(renderer.Render(change.Diff.Nodes()))
} else {
// TODO: Find a way to mask values in case of using unix `diff` command.
d, err := diffByCommand(diffCommand, change.Old, change.New)
if err != nil {
b.WriteString(fmt.Sprintf("An error occurred while rendering diff (%v)", err))
} else {
b.Write(d)
}
}
b.WriteString("\n")
prints++
if prints >= maxPrintDiffs {
break
}
}
if prints < len(r.Changes) {
b.WriteString(fmt.Sprintf("... (omitted %d other changed manifests\n", len(r.Changes)-prints))
}
return b.String()
}

I omit the option to use diff command to calculate diff.

var b strings.Builder
index := 0
for _, delete := range r.Deletes {
index++
b.WriteString(fmt.Sprintf("- %d. %s\n\n", index, delete.Key().ReadableString()))
}
for _, add := range r.Adds {
index++
b.WriteString(fmt.Sprintf("+ %d. %s\n\n", index, add.Key().ReadableString()))
}

maxPrintDiffs := len(r.Changes)
if opt.MaxChangedManifests != 0 && opt.MaxChangedManifests < maxPrintDiffs {
maxPrintDiffs = opt.MaxChangedManifests
}

for _, change := range r.Changes[:maxPrintDiffs] {
key := change.Old.Key()
opts := []diff.RenderOption{
diff.WithLeftPadding(1),
}

if opt.MaskSecret && change.Old.IsSecret() {
opts = append(opts, diff.WithMaskPath("data"))
} else if opt.MaskConfigMap && change.Old.IsConfigMap() {
opts = append(opts, diff.WithMaskPath("data"))
}
renderer := diff.NewRenderer(opts...)

index++
b.WriteString(fmt.Sprintf("# %d. %s\n\n", index, key.ReadableString()))

b.WriteString(renderer.Render(change.Diff.Nodes()))
b.WriteString("\n")
}

if maxPrintDiffs < len(r.Changes) {
b.WriteString(fmt.Sprintf("... (omitted %d other changed manifests)\n", len(r.Changes)-maxPrintDiffs))
}

return b.String()
}
Loading