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
8 changes: 5 additions & 3 deletions cmd/fleet-plan/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import (
"github.com/TsekNet/fleet-plan/internal/api"
"github.com/TsekNet/fleet-plan/internal/config"
"github.com/TsekNet/fleet-plan/internal/diff"
"github.com/TsekNet/fleet-plan/internal/merge"
"github.com/TsekNet/fleet-plan/internal/git"
"github.com/TsekNet/fleet-plan/internal/merge"
"github.com/TsekNet/fleet-plan/internal/output"
"github.com/TsekNet/fleet-plan/internal/parser"
"github.com/TsekNet/fleet-plan/internal/teamdir"
)

// Set via -ldflags at build time.
Expand Down Expand Up @@ -132,10 +133,11 @@ func runDiff(cmd *cobra.Command, _ []string) error {
}

if len(repo.Teams) == 0 && len(repo.Errors) == 0 {
dirName := teamdir.Resolve(flagRepo)
if len(teams) > 0 {
return fmt.Errorf("no teams matching %v found in %s/teams/", teams, flagRepo)
return fmt.Errorf("no teams matching %v found in %s/%s/", teams, flagRepo, dirName)
}
return fmt.Errorf("no teams found in %s/teams/\nAre you in a fleet-gitops repo? Try --repo /path/to/repo", flagRepo)
return fmt.Errorf("no teams found in %s/%s/\nAre you in a fleet-gitops repo? Try --repo /path/to/repo", flagRepo, dirName)
}

// Parse baseline (base branch) for subtraction when in --git mode.
Expand Down
9 changes: 6 additions & 3 deletions internal/git/baseline.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"os/exec"
"path/filepath"
"strings"

"github.com/TsekNet/fleet-plan/internal/teamdir"
)

// CheckoutBaseline extracts the base-branch versions of the given files into a
Expand All @@ -26,8 +28,9 @@ func CheckoutBaseline(repoRoot string, baseRef string, files []string) (tmpRoot
}
cleanup = func() { os.RemoveAll(tmpRoot) }

// Ensure teams/ directory exists so ParseRepo doesn't bail early.
os.MkdirAll(filepath.Join(tmpRoot, "teams"), 0o755)
// Ensure the team directory exists so ParseRepo doesn't bail early.
// Mirror whichever name the source repo uses (fleets/ or teams/).
os.MkdirAll(filepath.Join(tmpRoot, teamdir.Resolve(repoRoot)), 0o755)

// Resolve which files we need: the explicitly changed files, plus any
// files they reference (path: directives in team YAML). We start with
Expand Down Expand Up @@ -84,7 +87,7 @@ func collectBaselineFiles(repoRoot, baseRef string, changedFiles []string) []str
// references. Also extract any referenced resource files so the parser can
// resolve them.
for _, f := range changedFiles {
if !strings.HasPrefix(f, "teams/") && f != "base.yml" && f != "default.yml" {
if !teamdir.HasPrefix(f) && f != "base.yml" && f != "default.yml" {
continue
}
content, err := gitShow(repoRoot, baseRef, f)
Expand Down
29 changes: 22 additions & 7 deletions internal/git/scope.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"path/filepath"
"strings"

"github.com/TsekNet/fleet-plan/internal/teamdir"
"gopkg.in/yaml.v3"
)

Expand Down Expand Up @@ -40,7 +41,7 @@ func ResolveScope(root string, changedFiles []string, envFile string) Scope {
case f == "base.yml", f == envFile, strings.HasPrefix(f, "labels/") && !strings.HasSuffix(f, ".md"):
scope.IncludeGlobal = true

case strings.HasPrefix(f, "teams/") && (strings.HasSuffix(f, ".yml") || strings.HasSuffix(f, ".yaml")):
case isTeamYAML(f):
name := readTeamName(filepath.Join(root, f))
if name != "" && !teamsSeen[name] {
teamsSeen[name] = true
Expand Down Expand Up @@ -80,7 +81,16 @@ func isFleetResource(f string) bool {
}

func isFleetResourceOrTeam(f string) bool {
return isFleetResource(f) || (strings.HasPrefix(f, "teams/") && (strings.HasSuffix(f, ".yml") || strings.HasSuffix(f, ".yaml")))
return isFleetResource(f) || isTeamYAML(f)
}

// isTeamYAML reports whether a repo-relative path is a per-team YAML file
// under any recognized team directory (fleets/ or teams/).
func isTeamYAML(f string) bool {
if !strings.HasSuffix(f, ".yml") && !strings.HasSuffix(f, ".yaml") {
return false
}
return teamdir.HasPrefix(f)
}

// buildSearchPatterns returns the set of path strings to grep for in team YAMLs.
Expand All @@ -103,12 +113,17 @@ func buildSearchPatterns(root, f string) []string {
return patterns
}

// teamsReferencingAny reads teams/*.yml and returns team names whose file
// content contains any of the given patterns (plain string search).
// teamsReferencingAny reads every team YAML in the repo (fleets/ or teams/)
// and returns team names whose file content contains any of the given patterns
// (plain string search).
func teamsReferencingAny(root string, patterns []string) []string {
ymlMatches, _ := filepath.Glob(filepath.Join(root, "teams", "*.yml"))
yamlMatches, _ := filepath.Glob(filepath.Join(root, "teams", "*.yaml"))
matches := append(ymlMatches, yamlMatches...)
var matches []string
for _, dir := range teamdir.Names() {
for _, ext := range []string{"*.yml", "*.yaml"} {
m, _ := filepath.Glob(filepath.Join(root, dir, ext))
matches = append(matches, m...)
}
}
seen := map[string]bool{}
var names []string
for _, teamFile := range matches {
Expand Down
57 changes: 55 additions & 2 deletions internal/git/scope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ import (
)

// writeTeamFile creates a team YAML file in root/teams/ with the given name
// and optional body content referencing resources.
// and optional body content referencing resources. The legacy teams/ name
// is kept here so existing test cases continue exercising backwards compat.
func writeTeamFile(t *testing.T, root, filename, teamName, body string) {
t.Helper()
dir := filepath.Join(root, "teams")
writeTeamFileIn(t, root, "teams", filename, teamName, body)
}

// writeTeamFileIn is the same as writeTeamFile but lets the caller pick the
// directory name (used to exercise the canonical fleets/ layout).
func writeTeamFileIn(t *testing.T, root, dirName, filename, teamName, body string) {
t.Helper()
dir := filepath.Join(root, dirName)
if err := os.MkdirAll(dir, 0o755); err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -130,6 +138,29 @@ func TestResolveScope(t *testing.T) {
wantTeams: []string{"Workstations"},
wantTeamCount: 1,
},
{
name: "fleets/ team YAML change resolves team name",
setup: func(t *testing.T, root string) {
writeTeamFileIn(t, root, "fleets", "infra.yml", "Infrastructure", "")
},
changedFiles: []string{"fleets/infra.yml"},
wantGlobal: false,
wantTeams: []string{"Infrastructure"},
wantChanged: []string{"fleets/infra.yml"},
wantTeamCount: 1,
},
{
name: "resource change finds referencing fleets/ team",
setup: func(t *testing.T, root string) {
writeTeamFileIn(t, root, "fleets", "alpha.yml", "Alpha",
"controls:\n scripts:\n - path: ../scripts/setup.ps1\n")
},
changedFiles: []string{"scripts/setup.ps1"},
wantGlobal: false,
wantTeams: []string{"Alpha"},
wantChanged: []string{"scripts/setup.ps1"},
wantTeamCount: 1,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -196,6 +227,28 @@ func TestIsFleetResource(t *testing.T) {
}
}

func TestIsTeamYAML(t *testing.T) {
t.Parallel()
cases := []struct {
path string
want bool
}{
{"fleets/workstations.yml", true},
{"teams/workstations.yml", true},
{"fleets/infra.yaml", true},
{"teams/infra.yaml", true},
{"fleets/workstations.md", false},
{"policies/disk.yml", false},
{"fleets-extra/x.yml", false},
{"", false},
}
for _, c := range cases {
if got := isTeamYAML(c.path); got != c.want {
t.Errorf("isTeamYAML(%q) = %v, want %v", c.path, got, c.want)
}
}
}

func contains(ss []string, s string) bool {
for _, v := range ss {
if v == s {
Expand Down
Loading