Skip to content

Commit 6308c55

Browse files
authored
Merge pull request #6990 from chrischdi/pr-topology-plan-diff-go
🌱 Create unified yaml diff in clusterctl alpha topology plan for having a human readable output
2 parents 1a19e06 + 637a76b commit 6308c55

File tree

1 file changed

+58
-3
lines changed

1 file changed

+58
-3
lines changed

cmd/clusterctl/cmd/topology_plan.go

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@ package cmd
1818

1919
import (
2020
"fmt"
21+
"io"
2122
"os"
2223
"path"
24+
"path/filepath"
25+
"regexp"
2326
"sort"
27+
"strings"
2428

25-
"github.com/google/go-cmp/cmp"
2629
"github.com/olekukonko/tablewriter"
2730
"github.com/pkg/errors"
2831
"github.com/spf13/cobra"
2932
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
33+
"k8s.io/utils/exec"
3034
crclient "sigs.k8s.io/controller-runtime/pkg/client"
3135

3236
"sigs.k8s.io/cluster-api/cmd/clusterctl/client"
@@ -276,10 +280,13 @@ func writeOutputFiles(out *cluster.TopologyPlanOutput, outDir string) error {
276280
}
277281

278282
// Calculate the diff and write to a file.
279-
diff := cmp.Diff(m.Before, m.After)
280283
diffFileName := fmt.Sprintf("%s_%s_%s.diff", m.After.GetKind(), m.After.GetNamespace(), m.After.GetName())
281284
diffFilePath := path.Join(modifiedDir, diffFileName)
282-
if err := os.WriteFile(diffFilePath, []byte(diff), 0600); err != nil {
285+
diffFile, err := os.OpenFile(filepath.Clean(diffFilePath), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
286+
if err != nil {
287+
return errors.Wrapf(err, "unable to open file %q", diffFilePath)
288+
}
289+
if err := writeDiffToFile(filePathOriginal, filePathModified, diffFile); err != nil {
283290
return errors.Wrapf(err, "failed to write diff to file %q", diffFilePath)
284291
}
285292
}
@@ -329,3 +336,51 @@ func addRow(table *tablewriter.Table, o *unstructured.Unstructured, action strin
329336
},
330337
)
331338
}
339+
340+
// writeDiffToFile runs the detected diff program. `from` and `to` are the files to diff.
341+
// The implementation is highly inspired by kubectl's DiffProgram implementation:
342+
// ref: https://github.com/kubernetes/kubectl/blob/v0.24.3/pkg/cmd/diff/diff.go#L218
343+
func writeDiffToFile(from, to string, out io.Writer) error {
344+
diff, cmd := getDiffCommand(from, to)
345+
cmd.SetStdout(out)
346+
347+
if err := cmd.Run(); err != nil && !isDiffError(err) {
348+
return errors.Wrapf(err, "failed to run %q", diff)
349+
}
350+
return nil
351+
}
352+
353+
func getDiffCommand(args ...string) (string, exec.Cmd) {
354+
diff := ""
355+
if envDiff := os.Getenv("KUBECTL_EXTERNAL_DIFF"); envDiff != "" {
356+
diffCommand := strings.Split(envDiff, " ")
357+
diff = diffCommand[0]
358+
359+
if len(diffCommand) > 1 {
360+
// Regex accepts: Alphanumeric (case-insensitive), dash and equal
361+
isValidChar := regexp.MustCompile(`^[a-zA-Z0-9-=]+$`).MatchString
362+
for i := 1; i < len(diffCommand); i++ {
363+
if isValidChar(diffCommand[i]) {
364+
args = append(args, diffCommand[i])
365+
}
366+
}
367+
}
368+
} else {
369+
diff = "diff"
370+
args = append([]string{"-u", "-N"}, args...)
371+
}
372+
373+
cmd := exec.New().Command(diff, args...)
374+
375+
return diff, cmd
376+
}
377+
378+
// diffError returns true if the status code is lower or equal to 1, false otherwise.
379+
// This makes use of the exit code of diff programs which is 0 for no diff, 1 for
380+
// modified and 2 for other errors.
381+
func isDiffError(err error) bool {
382+
if err, ok := err.(exec.ExitError); ok && err.ExitStatus() <= 1 {
383+
return true
384+
}
385+
return false
386+
}

0 commit comments

Comments
 (0)