flux diff artifact: Add support for the `--ignore-paths` flag.

Signed-off-by: Florian Forster <fforster@gitlab.com>
pull/4916/head
Florian Forster 9 months ago
parent 72d32a248b
commit 672b759f2e
No known key found for this signature in database

@ -33,6 +33,7 @@ import (
oci "github.com/fluxcd/pkg/oci/client" oci "github.com/fluxcd/pkg/oci/client"
"github.com/fluxcd/pkg/tar" "github.com/fluxcd/pkg/tar"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" sourcev1 "github.com/fluxcd/source-controller/api/v1beta2"
"github.com/go-git/go-git/v5/plumbing/format/gitignore"
"github.com/gonvenience/ytbx" "github.com/gonvenience/ytbx"
"github.com/google/shlex" "github.com/google/shlex"
"github.com/hexops/gotextdiff" "github.com/hexops/gotextdiff"
@ -177,6 +178,16 @@ func diffArtifactCmdRun(cmd *cobra.Command, args []string) error {
return fmt.Errorf("%q and %q: %w", from, to, ErrDiffArtifactChanged) return fmt.Errorf("%q and %q: %w", from, to, ErrDiffArtifactChanged)
} }
func newMatcher(ignorePaths []string) gitignore.Matcher {
var patterns []gitignore.Pattern
for _, path := range ignorePaths {
patterns = append(patterns, gitignore.ParsePattern(path, nil))
}
return gitignore.NewMatcher(patterns)
}
func diffArtifact(ctx context.Context, client *oci.Client, from, to string, flags diffArtifactFlags) (string, error) { func diffArtifact(ctx context.Context, client *oci.Client, from, to string, flags diffArtifactFlags) (string, error) {
fromDir, fromCleanup, err := loadArtifact(ctx, client, from) fromDir, fromCleanup, err := loadArtifact(ctx, client, from)
if err != nil { if err != nil {
@ -190,7 +201,7 @@ func diffArtifact(ctx context.Context, client *oci.Client, from, to string, flag
} }
defer toCleanup() defer toCleanup()
return flags.differ.Diff(ctx, fromDir, toDir) return flags.differ.Diff(ctx, fromDir, toDir, flags.ignorePaths)
} }
// loadArtifact ensures that the artifact is in a local directory that can be // loadArtifact ensures that the artifact is in a local directory that can be
@ -279,6 +290,7 @@ func extractTo(archivePath, destDir string) error {
if err != nil { if err != nil {
return err return err
} }
defer archiveFH.Close()
if err := tar.Untar(archiveFH, destDir); err != nil { if err := tar.Untar(archiveFH, destDir); err != nil {
return fmt.Errorf("Untar(%q, %q): %w", archivePath, destDir, err) return fmt.Errorf("Untar(%q, %q): %w", archivePath, destDir, err)
@ -303,21 +315,25 @@ func copyFile(from io.Reader, to string) error {
type differ interface { type differ interface {
// Diff compares the two local directories "to" and "from" and returns their differences, or an empty string if they are equal. // Diff compares the two local directories "to" and "from" and returns their differences, or an empty string if they are equal.
Diff(ctx context.Context, from, to string) (string, error) Diff(ctx context.Context, from, to string, ignorePaths []string) (string, error)
} }
type unifiedDiff struct{} type unifiedDiff struct{}
func (d unifiedDiff) Diff(_ context.Context, fromDir, toDir string) (string, error) { func (d unifiedDiff) Diff(_ context.Context, fromDir, toDir string, ignorePaths []string) (string, error) {
fromFiles, err := filesInDir(fromDir) matcher := newMatcher(ignorePaths)
fromFiles, err := filesInDir(fromDir, matcher)
if err != nil { if err != nil {
return "", err return "", err
} }
fmt.Fprintf(os.Stderr, "fromFiles = %v\n", fromFiles)
toFiles, err := filesInDir(toDir) toFiles, err := filesInDir(toDir, matcher)
if err != nil { if err != nil {
return "", err return "", err
} }
fmt.Fprintf(os.Stderr, "toFiles = %v\n", toFiles)
allFiles := fromFiles.Union(toFiles) allFiles := fromFiles.Union(toFiles)
@ -338,13 +354,19 @@ func (d unifiedDiff) Diff(_ context.Context, fromDir, toDir string) (string, err
func (d unifiedDiff) diffFiles(fromDir, toDir, relPath string) (string, error) { func (d unifiedDiff) diffFiles(fromDir, toDir, relPath string) (string, error) {
fromPath := filepath.Join(fromDir, relPath) fromPath := filepath.Join(fromDir, relPath)
fromData, err := d.readFile(fromPath) fromData, err := d.readFile(fromPath)
if err != nil { switch {
case errors.Is(err, fs.ErrNotExist):
return fmt.Sprintf("Only in %s: %s\n", toDir, relPath), nil
case err != nil:
return "", fmt.Errorf("readFile(%q): %w", fromPath, err) return "", fmt.Errorf("readFile(%q): %w", fromPath, err)
} }
toPath := filepath.Join(toDir, relPath) toPath := filepath.Join(toDir, relPath)
toData, err := d.readFile(toPath) toData, err := d.readFile(toPath)
if err != nil { switch {
case errors.Is(err, fs.ErrNotExist):
return fmt.Sprintf("Only in %s: %s\n", fromDir, relPath), nil
case err != nil:
return "", fmt.Errorf("readFile(%q): %w", toPath, err) return "", fmt.Errorf("readFile(%q): %w", toPath, err)
} }
@ -355,14 +377,18 @@ func (d unifiedDiff) diffFiles(fromDir, toDir, relPath string) (string, error) {
func (d unifiedDiff) readFile(path string) ([]byte, error) { func (d unifiedDiff) readFile(path string) ([]byte, error) {
file, err := os.Open(path) file, err := os.Open(path)
if err != nil { if err != nil {
return nil, fmt.Errorf("os.Open(%q): %w", path, err) return nil, err
} }
defer file.Close() defer file.Close()
return io.ReadAll(file) return io.ReadAll(file)
} }
func filesInDir(root string) (stringset.Set, error) { func splitPath(path string) []string {
return strings.Split(path, string([]rune{filepath.Separator}))
}
func filesInDir(root string, matcher gitignore.Matcher) (stringset.Set, error) {
var files stringset.Set var files stringset.Set
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error { err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
@ -370,15 +396,22 @@ func filesInDir(root string) (stringset.Set, error) {
return err return err
} }
if !d.Type().IsRegular() {
return nil
}
relPath, err := filepath.Rel(root, path) relPath, err := filepath.Rel(root, path)
if err != nil { if err != nil {
return fmt.Errorf("filepath.Rel(%q, %q): %w", root, path, err) return fmt.Errorf("filepath.Rel(%q, %q): %w", root, path, err)
} }
if matcher.Match(splitPath(relPath), d.IsDir()) {
if d.IsDir() {
return fs.SkipDir
}
return nil
}
if !d.Type().IsRegular() {
return nil
}
files.Add(relPath) files.Add(relPath)
return nil return nil
}) })
@ -395,7 +428,7 @@ type externalDiff struct{}
// externalDiffVar is the environment variable users can use to overwrite the external diff command. // externalDiffVar is the environment variable users can use to overwrite the external diff command.
const externalDiffVar = "FLUX_EXTERNAL_DIFF" const externalDiffVar = "FLUX_EXTERNAL_DIFF"
func (externalDiff) Diff(ctx context.Context, fromDir, toDir string) (string, error) { func (externalDiff) Diff(ctx context.Context, fromDir, toDir string, ignorePaths []string) (string, error) {
cmdline := os.Getenv(externalDiffVar) cmdline := os.Getenv(externalDiffVar)
if cmdline == "" { if cmdline == "" {
return "", fmt.Errorf("the required %q environment variable is unset", externalDiffVar) return "", fmt.Errorf("the required %q environment variable is unset", externalDiffVar)
@ -409,6 +442,10 @@ func (externalDiff) Diff(ctx context.Context, fromDir, toDir string) (string, er
var executable string var executable string
executable, args = args[0], args[1:] executable, args = args[0], args[1:]
for _, path := range ignorePaths {
args = append(args, "--exclude", path)
}
args = append(args, fromDir, toDir) args = append(args, fromDir, toDir)
cmd := exec.CommandContext(ctx, executable, args...) cmd := exec.CommandContext(ctx, executable, args...)
@ -435,7 +472,7 @@ type dyffBuiltin struct {
opts []dyff.CompareOption opts []dyff.CompareOption
} }
func (d dyffBuiltin) Diff(ctx context.Context, fromDir, toDir string) (string, error) { func (d dyffBuiltin) Diff(ctx context.Context, fromDir, toDir string, _ []string) (string, error) {
fromFile, err := ytbx.LoadDirectory(fromDir) fromFile, err := ytbx.LoadDirectory(fromDir)
if err != nil { if err != nil {
return "", fmt.Errorf("ytbx.LoadDirectory(%q): %w", fromDir, err) return "", fmt.Errorf("ytbx.LoadDirectory(%q): %w", fromDir, err)

Loading…
Cancel
Save