From e4cc0c309879778337f7dfaa9c8a853cd6b4e149 Mon Sep 17 00:00:00 2001 From: Juan Fernandez Date: Thu, 27 Jul 2023 09:29:09 -0400 Subject: [PATCH] Fixed format issues with fleetctl get queries (#12983) Output from `fleetctl get queries` should include the team the query is in and also the scheduling information. --- cmd/fleetctl/get.go | 108 +++++++++++++++++++++----- cmd/fleetctl/get_test.go | 160 ++++++++++++++++++++++++++++++--------- 2 files changed, 211 insertions(+), 57 deletions(-) diff --git a/cmd/fleetctl/get.go b/cmd/fleetctl/get.go index f512ea26cb..54083b927b 100644 --- a/cmd/fleetctl/get.go +++ b/cmd/fleetctl/get.go @@ -18,6 +18,7 @@ import ( "gopkg.in/guregu/null.v3" "github.com/fleetdm/fleet/v4/server/fleet" + "github.com/fleetdm/fleet/v4/server/ptr" "github.com/fleetdm/fleet/v4/server/service" "github.com/ghodss/yaml" "github.com/olekukonko/tablewriter" @@ -298,6 +299,50 @@ func getCommand() *cli.Command { } } +func queryToTableRow(query fleet.Query, teamName string) []string { + platform := "all" + if query.Platform != "" { + platform = query.Platform + } + + minOsqueryVersion := "all" + if query.MinOsqueryVersion != "" { + minOsqueryVersion = query.MinOsqueryVersion + } + + scheduleInfo := fmt.Sprintf("interval: %d\nplatform: %s\nmin_osquery_version: %s\nautomations_enabled: %t\nlogging: %s", + query.Interval, + platform, + minOsqueryVersion, + query.AutomationsEnabled, + query.Logging, + ) + + teamNameOut := teamName + if teamName == "" { + teamNameOut = "All teams" + } + + return []string{ + query.Name, + query.Description, + query.Query, + teamNameOut, + scheduleInfo, + } +} + +func numberInheritedQueries(client *service.Client, teamID *uint) (*int, error) { + if teamID != nil { + globalQueries, err := client.GetQueries(nil) + if err != nil { + return nil, fmt.Errorf("could not list global queries: %w", err) + } + return ptr.Int(len(globalQueries)), nil + } + return nil, nil +} + func getQueriesCommand() *cli.Command { return &cli.Command{ Name: "queries", @@ -323,11 +368,24 @@ func getQueriesCommand() *cli.Command { name := c.Args().First() var teamID *uint + var teamName string + if tid := c.Uint(teamFlagName); tid != 0 { teamID = &tid + team, err := client.GetTeam(*teamID) + if err != nil { + var notFoundErr service.NotFoundErr + if errors.As(err, ¬FoundErr) { + // Do not error out, just inform the user and 'gracefully' exit. + fmt.Println("Team not found.") + return nil + } + return fmt.Errorf("get team: %w", err) + } + teamName = team.Name } - // if name wasn't provided, list all queries + // if name wasn't provided, list either all global queries or all team queries... if name == "" { queries, err := client.GetQueries(teamID) if err != nil { @@ -359,17 +417,12 @@ func getQueriesCommand() *cli.Command { } if len(queries) == 0 { - fmt.Println("No queries found") - return nil - } - - var teamName string - if teamID != nil { - team, err := client.GetTeam(*teamID) - if err != nil { - return fmt.Errorf("get team: %w", err) + scope := "global" + if teamID != nil { + scope = "team" } - teamName = team.Name + fmt.Printf("No %s queries found.\n", scope) + return nil } if c.Bool(yamlFlagName) || c.Bool(jsonFlagName) { @@ -392,18 +445,21 @@ func getQueriesCommand() *cli.Command { } } else { // Default to printing as a table - data := [][]string{} + rows := [][]string{} + columns := []string{"name", "description", "query", "team", "schedule"} for _, query := range queries { - data = append(data, []string{ - query.Name, - query.Description, - query.Query, - }) + rows = append(rows, queryToTableRow(query, teamName)) } - columns := []string{"name", "description", "query"} - printTable(c, columns, data) + // Need to determine the number of inherited queries if we are viewing the + // queries for a team + nInheritedQueries, err := numberInheritedQueries(client, teamID) + if err != nil { + return err + } + + printQueryTable(c, columns, rows, nInheritedQueries) } return nil } @@ -520,10 +576,8 @@ func getPacksCommand() *cli.Command { if err := printPack(c, pack); err != nil { return fmt.Errorf("unable to print pack: %w", err) } - addQueries(pack) } - return printQueries() } @@ -1021,6 +1075,18 @@ func getUserRolesCommand() *cli.Command { } } +func printQueryTable(c *cli.Context, columns []string, data [][]string, nInheritedQueries *int) { + table := defaultTable(c.App.Writer) + table.SetHeader(columns) + table.SetReflowDuringAutoWrap(false) + table.AppendBulk(data) + table.Render() + + if nInheritedQueries != nil { + fmt.Printf("Not showing %d inherited queries. To see global queries, run this command without the `--team` flag.\n", *nInheritedQueries) + } +} + func printTable(c *cli.Context, columns []string, data [][]string) { table := defaultTable(c.App.Writer) table.SetHeader(columns) diff --git a/cmd/fleetctl/get_test.go b/cmd/fleetctl/get_test.go index 7a1f0afe23..5f0f7a3a68 100644 --- a/cmd/fleetctl/get_test.go +++ b/cmd/fleetctl/get_test.go @@ -1053,16 +1053,42 @@ func TestGetQueries(t *testing.T) { return nil, errors.New("invalid team ID") } - expectedGlobal := `+--------+-------------+-----------+ -| NAME | DESCRIPTION | QUERY | -+--------+-------------+-----------+ -| query1 | some desc | select 1; | -+--------+-------------+-----------+ -| query2 | some desc 2 | select 2; | -+--------+-------------+-----------+ -| query4 | some desc 4 | select 4; | -+--------+-------------+-----------+ + expectedGlobal := `+--------+-------------+-----------+-----------+--------------------------------+ +| NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE | ++--------+-------------+-----------+-----------+--------------------------------+ +| query1 | some desc | select 1; | All teams | interval: 0 | +| | | | | | +| | | | | platform: all | +| | | | | | +| | | | | min_osquery_version: all | +| | | | | | +| | | | | automations_enabled: false | +| | | | | | +| | | | | logging: | ++--------+-------------+-----------+-----------+--------------------------------+ +| query2 | some desc 2 | select 2; | All teams | interval: 0 | +| | | | | | +| | | | | platform: all | +| | | | | | +| | | | | min_osquery_version: all | +| | | | | | +| | | | | automations_enabled: false | +| | | | | | +| | | | | logging: | ++--------+-------------+-----------+-----------+--------------------------------+ +| query4 | some desc 4 | select 4; | All teams | interval: 60 | +| | | | | | +| | | | | platform: darwin,windows | +| | | | | | +| | | | | min_osquery_version: 5.3.0 | +| | | | | | +| | | | | automations_enabled: true | +| | | | | | +| | | | | logging: | +| | | | | differential_ignore_removals | ++--------+-------------+-----------+-----------+--------------------------------+ ` + expectedYAMLGlobal := `--- apiVersion: v1 kind: query @@ -1111,12 +1137,21 @@ spec: {"kind":"query","apiVersion":"v1","spec":{"name":"query4","description":"some desc 4","query":"select 4;","team":"","interval":60,"observer_can_run":true,"platform":"darwin,windows","min_osquery_version":"5.3.0","automations_enabled":true,"logging":"differential_ignore_removals"}} ` - expectedTeam := `+--------+-------------+-----------+ -| NAME | DESCRIPTION | QUERY | -+--------+-------------+-----------+ -| query3 | some desc 3 | select 3; | -+--------+-------------+-----------+ + expectedTeam := `+--------+-------------+-----------+--------+----------------------------+ +| NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE | ++--------+-------------+-----------+--------+----------------------------+ +| query3 | some desc 3 | select 3; | Foobar | interval: 3600 | +| | | | | | +| | | | | platform: darwin | +| | | | | | +| | | | | min_osquery_version: 5.4.0 | +| | | | | | +| | | | | automations_enabled: false | +| | | | | | +| | | | | logging: snapshot | ++--------+-------------+-----------+--------+----------------------------+ ` + expectedYAMLTeam := `--- apiVersion: v1 kind: query @@ -1149,7 +1184,12 @@ spec: } func TestGetQuery(t *testing.T) { - _, ds := runServerWithMockedDS(t) + _, ds := runServerWithMockedDS(t, &service.TestServerOpts{ + License: &fleet.LicenseInfo{ + Tier: fleet.TierPremium, + Expiration: time.Now().Add(24 * time.Hour), + }, + }) ds.TeamFunc = func(ctx context.Context, tid uint) (*fleet.Team, error) { if tid == 1 { @@ -1339,11 +1379,19 @@ func TestGetQueriesAsObserver(t *testing.T) { t.Run(tc.name, func(t *testing.T) { setCurrentUserSession(tc.user) - expected := `+--------+-------------+-----------+ -| NAME | DESCRIPTION | QUERY | -+--------+-------------+-----------+ -| query2 | some desc 2 | select 2; | -+--------+-------------+-----------+ + expected := `+--------+-------------+-----------+-----------+----------------------------+ +| NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE | ++--------+-------------+-----------+-----------+----------------------------+ +| query2 | some desc 2 | select 2; | All teams | interval: 0 | +| | | | | | +| | | | | platform: all | +| | | | | | +| | | | | min_osquery_version: all | +| | | | | | +| | | | | automations_enabled: false | +| | | | | | +| | | | | logging: | ++--------+-------------+-----------+-----------+----------------------------+ ` expectedYaml := `--- apiVersion: v1 @@ -1388,15 +1436,39 @@ spec: }, }) - expected := `+--------+-------------+-----------+ -| NAME | DESCRIPTION | QUERY | -+--------+-------------+-----------+ -| query1 | some desc | select 1; | -+--------+-------------+-----------+ -| query2 | some desc 2 | select 2; | -+--------+-------------+-----------+ -| query3 | some desc 3 | select 3; | -+--------+-------------+-----------+ + expected := `+--------+-------------+-----------+-----------+----------------------------+ +| NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE | ++--------+-------------+-----------+-----------+----------------------------+ +| query1 | some desc | select 1; | All teams | interval: 0 | +| | | | | | +| | | | | platform: all | +| | | | | | +| | | | | min_osquery_version: all | +| | | | | | +| | | | | automations_enabled: false | +| | | | | | +| | | | | logging: | ++--------+-------------+-----------+-----------+----------------------------+ +| query2 | some desc 2 | select 2; | All teams | interval: 0 | +| | | | | | +| | | | | platform: all | +| | | | | | +| | | | | min_osquery_version: all | +| | | | | | +| | | | | automations_enabled: false | +| | | | | | +| | | | | logging: | ++--------+-------------+-----------+-----------+----------------------------+ +| query3 | some desc 3 | select 3; | All teams | interval: 0 | +| | | | | | +| | | | | platform: all | +| | | | | | +| | | | | min_osquery_version: all | +| | | | | | +| | | | | automations_enabled: false | +| | | | | | +| | | | | logging: | ++--------+-------------+-----------+-----------+----------------------------+ ` expectedYaml := `--- apiVersion: v1 @@ -1498,13 +1570,29 @@ spec: }, }, nil } - expected = `+--------+-------------+-----------+ -| NAME | DESCRIPTION | QUERY | -+--------+-------------+-----------+ -| query1 | some desc | select 1; | -+--------+-------------+-----------+ -| query2 | some desc 2 | select 2; | -+--------+-------------+-----------+ + expected = `+--------+-------------+-----------+-----------+----------------------------+ +| NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE | ++--------+-------------+-----------+-----------+----------------------------+ +| query1 | some desc | select 1; | All teams | interval: 0 | +| | | | | | +| | | | | platform: all | +| | | | | | +| | | | | min_osquery_version: all | +| | | | | | +| | | | | automations_enabled: false | +| | | | | | +| | | | | logging: | ++--------+-------------+-----------+-----------+----------------------------+ +| query2 | some desc 2 | select 2; | All teams | interval: 0 | +| | | | | | +| | | | | platform: all | +| | | | | | +| | | | | min_osquery_version: all | +| | | | | | +| | | | | automations_enabled: false | +| | | | | | +| | | | | logging: | ++--------+-------------+-----------+-----------+----------------------------+ ` assert.Equal(t, expected, runAppForTest(t, []string{"get", "queries"})) }