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.
This commit is contained in:
Juan Fernandez 2023-07-27 09:29:09 -04:00 committed by GitHub
parent 6f77911ffe
commit e4cc0c3098
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 211 additions and 57 deletions

View file

@ -18,6 +18,7 @@ import (
"gopkg.in/guregu/null.v3" "gopkg.in/guregu/null.v3"
"github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
"github.com/fleetdm/fleet/v4/server/service" "github.com/fleetdm/fleet/v4/server/service"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/olekukonko/tablewriter" "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 { func getQueriesCommand() *cli.Command {
return &cli.Command{ return &cli.Command{
Name: "queries", Name: "queries",
@ -323,11 +368,24 @@ func getQueriesCommand() *cli.Command {
name := c.Args().First() name := c.Args().First()
var teamID *uint var teamID *uint
var teamName string
if tid := c.Uint(teamFlagName); tid != 0 { if tid := c.Uint(teamFlagName); tid != 0 {
teamID = &tid teamID = &tid
team, err := client.GetTeam(*teamID)
if err != nil {
var notFoundErr service.NotFoundErr
if errors.As(err, &notFoundErr) {
// 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 == "" { if name == "" {
queries, err := client.GetQueries(teamID) queries, err := client.GetQueries(teamID)
if err != nil { if err != nil {
@ -359,17 +417,12 @@ func getQueriesCommand() *cli.Command {
} }
if len(queries) == 0 { if len(queries) == 0 {
fmt.Println("No queries found") scope := "global"
return nil if teamID != nil {
} scope = "team"
var teamName string
if teamID != nil {
team, err := client.GetTeam(*teamID)
if err != nil {
return fmt.Errorf("get team: %w", err)
} }
teamName = team.Name fmt.Printf("No %s queries found.\n", scope)
return nil
} }
if c.Bool(yamlFlagName) || c.Bool(jsonFlagName) { if c.Bool(yamlFlagName) || c.Bool(jsonFlagName) {
@ -392,18 +445,21 @@ func getQueriesCommand() *cli.Command {
} }
} else { } else {
// Default to printing as a table // Default to printing as a table
data := [][]string{} rows := [][]string{}
columns := []string{"name", "description", "query", "team", "schedule"}
for _, query := range queries { for _, query := range queries {
data = append(data, []string{ rows = append(rows, queryToTableRow(query, teamName))
query.Name,
query.Description,
query.Query,
})
} }
columns := []string{"name", "description", "query"} // Need to determine the number of inherited queries if we are viewing the
printTable(c, columns, data) // queries for a team
nInheritedQueries, err := numberInheritedQueries(client, teamID)
if err != nil {
return err
}
printQueryTable(c, columns, rows, nInheritedQueries)
} }
return nil return nil
} }
@ -520,10 +576,8 @@ func getPacksCommand() *cli.Command {
if err := printPack(c, pack); err != nil { if err := printPack(c, pack); err != nil {
return fmt.Errorf("unable to print pack: %w", err) return fmt.Errorf("unable to print pack: %w", err)
} }
addQueries(pack) addQueries(pack)
} }
return printQueries() 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) { func printTable(c *cli.Context, columns []string, data [][]string) {
table := defaultTable(c.App.Writer) table := defaultTable(c.App.Writer)
table.SetHeader(columns) table.SetHeader(columns)

View file

@ -1053,16 +1053,42 @@ func TestGetQueries(t *testing.T) {
return nil, errors.New("invalid team ID") return nil, errors.New("invalid team ID")
} }
expectedGlobal := `+--------+-------------+-----------+ expectedGlobal := `+--------+-------------+-----------+-----------+--------------------------------+
| NAME | DESCRIPTION | QUERY | | NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE |
+--------+-------------+-----------+ +--------+-------------+-----------+-----------+--------------------------------+
| query1 | some desc | select 1; | | query1 | some desc | select 1; | All teams | interval: 0 |
+--------+-------------+-----------+ | | | | | |
| query2 | some desc 2 | select 2; | | | | | | platform: all |
+--------+-------------+-----------+ | | | | | |
| query4 | some desc 4 | select 4; | | | | | | 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 := `--- expectedYAMLGlobal := `---
apiVersion: v1 apiVersion: v1
kind: query 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"}} {"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 := `+--------+-------------+-----------+ expectedTeam := `+--------+-------------+-----------+--------+----------------------------+
| NAME | DESCRIPTION | QUERY | | NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE |
+--------+-------------+-----------+ +--------+-------------+-----------+--------+----------------------------+
| query3 | some desc 3 | select 3; | | query3 | some desc 3 | select 3; | Foobar | interval: 3600 |
+--------+-------------+-----------+ | | | | | |
| | | | | platform: darwin |
| | | | | |
| | | | | min_osquery_version: 5.4.0 |
| | | | | |
| | | | | automations_enabled: false |
| | | | | |
| | | | | logging: snapshot |
+--------+-------------+-----------+--------+----------------------------+
` `
expectedYAMLTeam := `--- expectedYAMLTeam := `---
apiVersion: v1 apiVersion: v1
kind: query kind: query
@ -1149,7 +1184,12 @@ spec:
} }
func TestGetQuery(t *testing.T) { 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) { ds.TeamFunc = func(ctx context.Context, tid uint) (*fleet.Team, error) {
if tid == 1 { if tid == 1 {
@ -1339,11 +1379,19 @@ func TestGetQueriesAsObserver(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
setCurrentUserSession(tc.user) setCurrentUserSession(tc.user)
expected := `+--------+-------------+-----------+ expected := `+--------+-------------+-----------+-----------+----------------------------+
| NAME | DESCRIPTION | QUERY | | NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE |
+--------+-------------+-----------+ +--------+-------------+-----------+-----------+----------------------------+
| query2 | some desc 2 | select 2; | | query2 | some desc 2 | select 2; | All teams | interval: 0 |
+--------+-------------+-----------+ | | | | | |
| | | | | platform: all |
| | | | | |
| | | | | min_osquery_version: all |
| | | | | |
| | | | | automations_enabled: false |
| | | | | |
| | | | | logging: |
+--------+-------------+-----------+-----------+----------------------------+
` `
expectedYaml := `--- expectedYaml := `---
apiVersion: v1 apiVersion: v1
@ -1388,15 +1436,39 @@ spec:
}, },
}) })
expected := `+--------+-------------+-----------+ expected := `+--------+-------------+-----------+-----------+----------------------------+
| NAME | DESCRIPTION | QUERY | | NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE |
+--------+-------------+-----------+ +--------+-------------+-----------+-----------+----------------------------+
| query1 | some desc | select 1; | | query1 | some desc | select 1; | All teams | interval: 0 |
+--------+-------------+-----------+ | | | | | |
| query2 | some desc 2 | select 2; | | | | | | platform: all |
+--------+-------------+-----------+ | | | | | |
| query3 | some desc 3 | select 3; | | | | | | 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 := `--- expectedYaml := `---
apiVersion: v1 apiVersion: v1
@ -1498,13 +1570,29 @@ spec:
}, },
}, nil }, nil
} }
expected = `+--------+-------------+-----------+ expected = `+--------+-------------+-----------+-----------+----------------------------+
| NAME | DESCRIPTION | QUERY | | NAME | DESCRIPTION | QUERY | TEAM | SCHEDULE |
+--------+-------------+-----------+ +--------+-------------+-----------+-----------+----------------------------+
| query1 | some desc | select 1; | | query1 | some desc | select 1; | All teams | interval: 0 |
+--------+-------------+-----------+ | | | | | |
| query2 | some desc 2 | select 2; | | | | | | 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"})) assert.Equal(t, expected, runAppForTest(t, []string{"get", "queries"}))
} }