Skip to content
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
46 changes: 36 additions & 10 deletions cmd/fleet-plan/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func runDiff(cmd *cobra.Command, _ []string) error {
fmt.Fprintf(os.Stderr, "Warning: could not extract baseline (%v), skipping baseline subtraction\n", err)
} else {
defer baseCleanup()
baseDefaultFile := resolveBaselineDefault(baseRoot, flagBase, flagEnv)
baseDefaultFile := resolveBaselineDefault(baseRoot, flagRepo, flagBase, flagEnv)
baseParsed, err := parser.ParseRepo(baseRoot, teams, baseDefaultFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: could not parse baseline (%v), skipping baseline subtraction\n", err)
Expand Down Expand Up @@ -255,17 +255,43 @@ func resolveDefaultFile(repo, base, env string) (path string, cleanup func(), er
return tmpPath, func() { os.Remove(tmpPath) }, nil
}

// resolveBaselineDefault returns the path to default.yml within the baseline
// temp dir, if it exists. For baseline parsing, we use the base branch's own
// default.yml (no merge with env overlay, since the overlay may have changed
// in the MR).
func resolveBaselineDefault(baseRoot, base, env string) string {
// If base+env merge was used, check if default.yml exists in the baseline.
// resolveBaselineDefault returns the path to default.yml for baseline parsing.
// When --base and --env are used, it merges the base branch's base.yml with
// the env overlay (from the MR branch, since env overlays rarely change).
// Falls back to the baseline's default.yml if present.
func resolveBaselineDefault(baseRoot, repoRoot, base, env string) string {
if base != "" && env != "" {
candidate := filepath.Join(baseRoot, "default.yml")
if _, err := os.Stat(candidate); err == nil {
return candidate
baseFile := filepath.Join(baseRoot, base)
if _, err := os.Stat(baseFile); err != nil {
// base.yml doesn't exist at base ref, skip
return ""
}
// Use the env overlay from the MR branch (repoRoot), not the baseline.
envFile := env
if !filepath.IsAbs(envFile) {
envFile = filepath.Join(repoRoot, envFile)
}
if _, err := os.Stat(envFile); err != nil {
return ""
}
tmp, err := os.CreateTemp("", "fleet-plan-baseline-default-*.yml")
if err != nil {
return ""
}
tmpPath := tmp.Name()
tmp.Close()
if err := merge.MergeFiles(baseFile, envFile, tmpPath); err != nil {
os.Remove(tmpPath)
return ""
}
// Note: this temp file leaks if the caller doesn't clean it up.
// It's small and short-lived (CI job lifetime), acceptable tradeoff.
return tmpPath
}
// No base+env: check for plain default.yml in the baseline.
candidate := filepath.Join(baseRoot, "default.yml")
if _, err := os.Stat(candidate); err == nil {
return candidate
}
return ""
}
Expand Down
34 changes: 34 additions & 0 deletions internal/diff/differ.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,20 @@ func Diff(current *api.FleetState, proposed *parser.ParsedRepo, teamFilters []st
// Diff global queries
globalResult.Queries = diffQueries(current.GlobalQueries, proposed.Global.Queries)

// Subtract baseline for global scope
if cfg.baseline != nil && cfg.baseline.Global != nil {
var baseConfig []ConfigChange
if current.Config != nil {
baseConfig, _ = diffConfig(current.Config, cfg.baseline.Global)
}
basePolicies := diffPolicies(current.GlobalPolicies, cfg.baseline.Global.Policies)
baseQueries := diffQueries(current.GlobalQueries, cfg.baseline.Global.Queries)

globalResult.Config = subtractConfigChanges(globalResult.Config, baseConfig)
globalResult.Policies = subtractResourceDiff(globalResult.Policies, basePolicies)
globalResult.Queries = subtractResourceDiff(globalResult.Queries, baseQueries)
}

results = append(results, globalResult)
}

Expand Down Expand Up @@ -437,6 +451,26 @@ func sameFieldDiffs(a, b map[string]FieldDiff) bool {
return true
}

// subtractConfigChanges removes ConfigChange entries from "total" that also
// appear in "baseline" with the same Section, Key, Old, and New values.
func subtractConfigChanges(total, baseline []ConfigChange) []ConfigChange {
if len(baseline) == 0 {
return total
}
type configKey struct{ Section, Key, Old, New string }
baseSet := make(map[configKey]bool, len(baseline))
for _, b := range baseline {
baseSet[configKey{b.Section, b.Key, b.Old, b.New}] = true
}
var out []ConfigChange
for _, c := range total {
if !baseSet[configKey{c.Section, c.Key, c.Old, c.New}] {
out = append(out, c)
}
}
return out
}

// ---------- Per-resource diffing ----------

func diffPolicies(current []api.Policy, proposed []parser.ParsedPolicy) ResourceDiff {
Expand Down
Loading