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
22 changes: 21 additions & 1 deletion cmd/grlx/cmd/jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ var (
jobsLimit int
jobsLocal bool
jobsUser string
jobsCohort string
watchTimeout int
purgeOlderH int
)
Expand All @@ -36,6 +37,7 @@ var cmdJobsList = &cobra.Command{
Long: `List recent jobs. By default queries the farmer.
Use --local to list jobs from local CLI-side storage.
Use --user to filter jobs by the invoking user's pubkey (use 'me' for current user).
Use -C/--cohort to filter jobs to sprouts in a cohort.
When using --local, an optional sproutID argument filters jobs for that sprout.`,
Run: func(cmd *cobra.Command, args []string) {
var summaries []jobs.JobSummary
Expand All @@ -52,7 +54,24 @@ When using --local, an optional sproutID argument filters jobs for that sprout.`
}
userFilter = key
}
if len(args) > 0 {
if len(args) > 0 && jobsCohort != "" {
log.Fatalf("Cannot use both a sproutID argument and --cohort (-C)")
}
if jobsCohort != "" {
// Resolve cohort to sprout list, then fetch jobs for each.
members, cohortErr := client.ResolveCohort(jobsCohort)
if cohortErr != nil {
log.Fatalf("Failed to resolve cohort %q: %v", jobsCohort, cohortErr)
}
for _, sproutID := range members {
sproutJobs, listErr := client.ListJobsForSprout(sproutID)
if listErr != nil {
log.Errorf("Failed to list jobs for %s: %v", sproutID, listErr)
continue
}
summaries = append(summaries, sproutJobs...)
}
} else if len(args) > 0 {
summaries, err = client.ListJobsForSprout(args[0])
} else {
summaries, err = client.ListJobs(jobsLimit, userFilter)
Expand Down Expand Up @@ -503,6 +522,7 @@ func init() {
cmdJobsList.Flags().IntVar(&jobsLimit, "limit", 50, "Maximum number of jobs to return")
cmdJobsList.Flags().BoolVar(&jobsLocal, "local", false, "List jobs from local CLI-side storage instead of the farmer")
cmdJobsList.Flags().StringVar(&jobsUser, "user", "", "Filter jobs by invoking user's pubkey (use 'me' for current user)")
cmdJobsList.Flags().StringVarP(&jobsCohort, "cohort", "C", "", "Filter jobs to sprouts in a cohort")
cmdJobsShow.Flags().BoolVar(&jobsLocal, "local", false, "Show job from local CLI-side storage instead of the farmer")
cmdJobsWatch.Flags().IntVar(&watchTimeout, "timeout", 120, "Watch timeout in seconds")
cmdJobsPurge.Flags().IntVar(&purgeOlderH, "older-than", 720, "Remove jobs older than this many hours (default 720 = 30 days)")
Expand Down
19 changes: 19 additions & 0 deletions cmd/grlx/cmd/sprouts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
var (
sproutsStateFilter string
sproutsOnlineOnly bool
sproutsCohort string
)

var cmdSprouts = &cobra.Command{
Expand All @@ -36,9 +37,26 @@ var cmdSproutsList = &cobra.Command{
log.Fatalf("Failed to list sprouts: %v", err)
}

// If -C/--cohort is given, resolve the cohort and restrict the list
// to only the sprouts in that cohort.
var cohortMembers map[string]bool
if sproutsCohort != "" {
members, cohortErr := client.ResolveCohort(sproutsCohort)
if cohortErr != nil {
log.Fatalf("Failed to resolve cohort %q: %v", sproutsCohort, cohortErr)
}
cohortMembers = make(map[string]bool, len(members))
for _, m := range members {
cohortMembers[m] = true
}
}

// Apply filters.
filtered := sprouts[:0]
for _, s := range sprouts {
if cohortMembers != nil && !cohortMembers[s.ID] {
continue
}
if sproutsStateFilter != "" && s.KeyState != sproutsStateFilter {
continue
}
Expand Down Expand Up @@ -181,6 +199,7 @@ var cmdSproutsShow = &cobra.Command{
func init() {
cmdSproutsList.Flags().StringVar(&sproutsStateFilter, "state", "", "Filter by key state (accepted, unaccepted, denied, rejected)")
cmdSproutsList.Flags().BoolVar(&sproutsOnlineOnly, "online", false, "Show only online (connected) sprouts")
cmdSproutsList.Flags().StringVarP(&sproutsCohort, "cohort", "C", "", "Filter sprouts to members of a cohort")
cmdSprouts.AddCommand(cmdSproutsList)
cmdSprouts.AddCommand(cmdSproutsShow)
rootCmd.AddCommand(cmdSprouts)
Expand Down
178 changes: 178 additions & 0 deletions cmd/grlx/cmd/targeting_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package cmd

import (
"strings"
"testing"

"github.com/spf13/cobra"
)

// --- resolveEffectiveTarget tests ---

func TestResolveEffectiveTarget_TargetOnly_Simple(t *testing.T) {
oldS, oldC := sproutTarget, cohortTarget
defer func() { sproutTarget = oldS; cohortTarget = oldC }()

sproutTarget = "web-1"
cohortTarget = ""
got, err := resolveEffectiveTarget()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "web-1" {
t.Errorf("got %q, want %q", got, "web-1")
}
}

func TestResolveEffectiveTarget_TargetOnly_Regex(t *testing.T) {
oldS, oldC := sproutTarget, cohortTarget
defer func() { sproutTarget = oldS; cohortTarget = oldC }()

sproutTarget = "web-.*"
cohortTarget = ""
got, err := resolveEffectiveTarget()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "web-.*" {
t.Errorf("got %q, want %q", got, "web-.*")
}
}

func TestResolveEffectiveTarget_TargetOnly_CommaSeparated(t *testing.T) {
oldS, oldC := sproutTarget, cohortTarget
defer func() { sproutTarget = oldS; cohortTarget = oldC }()

sproutTarget = "web-1,web-2,db-1"
cohortTarget = ""
got, err := resolveEffectiveTarget()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != "web-1,web-2,db-1" {
t.Errorf("got %q, want %q", got, "web-1,web-2,db-1")
}
}

func TestResolveEffectiveTarget_NeitherSet_Error(t *testing.T) {
oldS, oldC := sproutTarget, cohortTarget
defer func() { sproutTarget = oldS; cohortTarget = oldC }()

sproutTarget = ""
cohortTarget = ""
_, err := resolveEffectiveTarget()
if err == nil {
t.Fatal("expected error when neither flag is set")
}
if !strings.Contains(err.Error(), "required") {
t.Errorf("error should mention 'required': %v", err)
}
}

func TestResolveEffectiveTarget_BothSet_Error(t *testing.T) {
oldS, oldC := sproutTarget, cohortTarget
defer func() { sproutTarget = oldS; cohortTarget = oldC }()

sproutTarget = "web-1"
cohortTarget = "web-servers"
_, err := resolveEffectiveTarget()
if err == nil {
t.Fatal("expected error when both flags are set")
}
if !strings.Contains(err.Error(), "cannot use both") {
t.Errorf("error should mention 'cannot use both': %v", err)
}
}

// --- addTargetFlags tests ---

func TestAddTargetFlags_RegistersFlags(t *testing.T) {
// cook, cmd, and test all use addTargetFlags — verify the flags exist.
cmds := map[string]*cobra.Command{
"cook": cmdCook,
"cmd": cmdCmd,
"test": testCmd,
}
for name, c := range cmds {
flags := c.PersistentFlags()
if f := flags.Lookup("target"); f == nil {
t.Errorf("%s missing --target (-T) persistent flag", name)
}
if f := flags.Lookup("cohort"); f == nil {
t.Errorf("%s missing --cohort (-C) persistent flag", name)
}
}
}

func TestCookCommand_HasCohortFlag(t *testing.T) {
f := cmdCook.PersistentFlags().Lookup("cohort")
if f == nil {
t.Fatal("cook command should have --cohort flag")
}
if f.Shorthand != "C" {
t.Errorf("cook --cohort shorthand = %q, want 'C'", f.Shorthand)
}
}

func TestCmdCommand_HasCohortFlag(t *testing.T) {
f := cmdCmd.PersistentFlags().Lookup("cohort")
if f == nil {
t.Fatal("cmd command should have --cohort flag")
}
if f.Shorthand != "C" {
t.Errorf("cmd --cohort shorthand = %q, want 'C'", f.Shorthand)
}
}

func TestTestCommand_HasCohortFlag(t *testing.T) {
f := testCmd.PersistentFlags().Lookup("cohort")
if f == nil {
t.Fatal("test command should have --cohort flag")
}
if f.Shorthand != "C" {
t.Errorf("test --cohort shorthand = %q, want 'C'", f.Shorthand)
}
}

func TestSSHCommand_HasCohortFlag(t *testing.T) {
f := sshCmd.Flags().Lookup("cohort")
if f == nil {
t.Fatal("ssh command should have --cohort flag")
}
if f.Shorthand != "C" {
t.Errorf("ssh --cohort shorthand = %q, want 'C'", f.Shorthand)
}
}

func TestSproutsListCommand_HasCohortFlag(t *testing.T) {
f := cmdSproutsList.Flags().Lookup("cohort")
if f == nil {
t.Fatal("sprouts list command should have --cohort flag")
}
if f.Shorthand != "C" {
t.Errorf("sprouts list --cohort shorthand = %q, want 'C'", f.Shorthand)
}
}

func TestJobsListCommand_HasCohortFlag(t *testing.T) {
f := cmdJobsList.Flags().Lookup("cohort")
if f == nil {
t.Fatal("jobs list command should have --cohort flag")
}
if f.Shorthand != "C" {
t.Errorf("jobs list --cohort shorthand = %q, want 'C'", f.Shorthand)
}
}

// --- cook Use string documents -C ---

func TestCookUseString_DocumentsCohort(t *testing.T) {
if !strings.Contains(cmdCook.Use, "-C") {
t.Error("cook Use string should mention -C flag")
}
if !strings.Contains(cmdCook.Use, "cohort") {
t.Error("cook Use string should mention 'cohort'")
}
}

// SSH targeting tests are in ssh_test.go to avoid redeclaration.
Loading