mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
Update fleetctl get software
to list titles and versions. (#15444)
This commit is contained in:
parent
6b128dd455
commit
e3d225ade7
1
changes/issue-15406-fleetctl-get-software
Normal file
1
changes/issue-15406-fleetctl-get-software
Normal file
@ -0,0 +1 @@
|
||||
* Updated `fleetctl get software` to list software titles, and add optional `--versions` flag to list software versions.
|
@ -1213,12 +1213,16 @@ func getSoftwareCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "software",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "List software",
|
||||
Usage: "List software titles",
|
||||
Flags: []cli.Flag{
|
||||
&cli.UintFlag{
|
||||
Name: teamFlagName,
|
||||
Usage: "Only list software of hosts that belong to the specified team",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "versions",
|
||||
Usage: "List all software versions",
|
||||
},
|
||||
jsonFlag(),
|
||||
yamlFlag(),
|
||||
configFlag(),
|
||||
@ -1242,13 +1246,22 @@ func getSoftwareCommand() *cli.Command {
|
||||
query.Set("team_id", strconv.FormatUint(uint64(teamID), 10))
|
||||
}
|
||||
|
||||
software, err := client.ListSoftware(query.Encode())
|
||||
if c.Bool("versions") {
|
||||
return printSoftwareVersions(c, client, query)
|
||||
}
|
||||
return printSoftwareTitles(c, client, query)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func printSoftwareVersions(c *cli.Context, client *service.Client, query url.Values) error {
|
||||
software, err := client.ListSoftwareVersions(query.Encode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not list software: %w", err)
|
||||
return fmt.Errorf("could not list software versions: %w", err)
|
||||
}
|
||||
|
||||
if len(software) == 0 {
|
||||
log(c, "No software found")
|
||||
log(c, "No software versions found")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1273,16 +1286,62 @@ func getSoftwareCommand() *cli.Command {
|
||||
s.Name,
|
||||
s.Version,
|
||||
s.Source,
|
||||
s.GenerateCPE,
|
||||
fmt.Sprint(len(s.Vulnerabilities)),
|
||||
fmt.Sprintf("%d vulnerabilities", len(s.Vulnerabilities)),
|
||||
fmt.Sprint(s.HostsCount),
|
||||
})
|
||||
}
|
||||
columns := []string{"Name", "Version", "Source", "CPE", "# of CVEs"}
|
||||
columns := []string{"Name", "Version", "Type", "Vulnerabilities", "Hosts"}
|
||||
printTable(c, columns, data)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func printSoftwareTitles(c *cli.Context, client *service.Client, query url.Values) error {
|
||||
software, err := client.ListSoftwareTitles(query.Encode())
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not list software titles: %w", err)
|
||||
}
|
||||
|
||||
if len(software) == 0 {
|
||||
log(c, "No software titles found")
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Bool(jsonFlagName) || c.Bool(yamlFlagName) {
|
||||
spec := specGeneric{
|
||||
Kind: "software_title",
|
||||
Version: "1",
|
||||
Spec: software,
|
||||
}
|
||||
err = printSpec(c, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Default to printing as table
|
||||
data := [][]string{}
|
||||
|
||||
for _, s := range software {
|
||||
vulns := make(map[string]bool)
|
||||
for _, ver := range s.Versions {
|
||||
if ver.Vulnerabilities != nil {
|
||||
for _, vuln := range *ver.Vulnerabilities {
|
||||
vulns[vuln] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
data = append(data, []string{
|
||||
s.Name,
|
||||
fmt.Sprintf("%d versions", s.VersionsCount),
|
||||
s.Source,
|
||||
fmt.Sprintf("%d vulnerabilities", len(vulns)),
|
||||
fmt.Sprint(s.HostsCount),
|
||||
})
|
||||
}
|
||||
columns := []string{"Name", "Versions", "Type", "Vulnerabilities", "Hosts"}
|
||||
printTable(c, columns, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func getMDMAppleCommand() *cli.Command {
|
||||
|
@ -602,7 +602,159 @@ func TestGetConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSoftware(t *testing.T) {
|
||||
func TestGetSoftwareTitles(t *testing.T) {
|
||||
_, ds := runServerWithMockedDS(t, &service.TestServerOpts{
|
||||
License: &fleet.LicenseInfo{
|
||||
Tier: fleet.TierPremium,
|
||||
Expiration: time.Now().Add(24 * time.Hour),
|
||||
},
|
||||
})
|
||||
|
||||
var gotTeamID *uint
|
||||
|
||||
ds.ListSoftwareTitlesFunc = func(ctx context.Context, opt fleet.SoftwareTitleListOptions) ([]fleet.SoftwareTitle, int, *fleet.PaginationMetadata, error) {
|
||||
gotTeamID = opt.TeamID
|
||||
return []fleet.SoftwareTitle{
|
||||
{
|
||||
Name: "foo",
|
||||
Source: "chrome_extensions",
|
||||
HostsCount: 2,
|
||||
VersionsCount: 3,
|
||||
Versions: []fleet.SoftwareVersion{
|
||||
{
|
||||
Version: "0.0.1",
|
||||
Vulnerabilities: &fleet.SliceString{"cve-123-456-001", "cve-123-456-002"},
|
||||
},
|
||||
{
|
||||
Version: "0.0.2",
|
||||
Vulnerabilities: &fleet.SliceString{"cve-123-456-001"},
|
||||
},
|
||||
{
|
||||
Version: "0.0.3",
|
||||
Vulnerabilities: &fleet.SliceString{"cve-123-456-003"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "bar",
|
||||
Source: "deb_packages",
|
||||
HostsCount: 0,
|
||||
VersionsCount: 1,
|
||||
Versions: []fleet.SoftwareVersion{
|
||||
{
|
||||
Version: "0.0.3",
|
||||
Vulnerabilities: nil,
|
||||
},
|
||||
},
|
||||
},
|
||||
}, 0, nil, nil
|
||||
}
|
||||
|
||||
expected := `+------+------------+-------------------+-------------------+-------+
|
||||
| NAME | VERSIONS | TYPE | VULNERABILITIES | HOSTS |
|
||||
+------+------------+-------------------+-------------------+-------+
|
||||
| foo | 3 versions | chrome_extensions | 3 vulnerabilities | 2 |
|
||||
+------+------------+-------------------+-------------------+-------+
|
||||
| bar | 1 versions | deb_packages | 0 vulnerabilities | 0 |
|
||||
+------+------------+-------------------+-------------------+-------+
|
||||
`
|
||||
|
||||
expectedYaml := `---
|
||||
apiVersion: "1"
|
||||
kind: software_title
|
||||
spec:
|
||||
- hosts_count: 2
|
||||
id: 0
|
||||
name: foo
|
||||
source: chrome_extensions
|
||||
versions:
|
||||
- id: 0
|
||||
version: 0.0.1
|
||||
vulnerabilities:
|
||||
- cve-123-456-001
|
||||
- cve-123-456-002
|
||||
- id: 0
|
||||
version: 0.0.2
|
||||
vulnerabilities:
|
||||
- cve-123-456-001
|
||||
- id: 0
|
||||
version: 0.0.3
|
||||
vulnerabilities:
|
||||
- cve-123-456-003
|
||||
versions_count: 3
|
||||
- hosts_count: 0
|
||||
id: 0
|
||||
name: bar
|
||||
source: deb_packages
|
||||
versions:
|
||||
- id: 0
|
||||
version: 0.0.3
|
||||
versions_count: 1
|
||||
`
|
||||
|
||||
expectedJson := `
|
||||
{
|
||||
"kind": "software_title",
|
||||
"apiVersion": "1",
|
||||
"spec": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "foo",
|
||||
"source": "chrome_extensions",
|
||||
"hosts_count": 2,
|
||||
"versions_count": 3,
|
||||
"versions": [
|
||||
{
|
||||
"id": 0,
|
||||
"version": "0.0.1",
|
||||
"vulnerabilities": [
|
||||
"cve-123-456-001",
|
||||
"cve-123-456-002"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0,
|
||||
"version": "0.0.2",
|
||||
"vulnerabilities": [
|
||||
"cve-123-456-001"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0,
|
||||
"version": "0.0.3",
|
||||
"vulnerabilities": [
|
||||
"cve-123-456-003"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0,
|
||||
"name": "bar",
|
||||
"source": "deb_packages",
|
||||
"hosts_count": 0,
|
||||
"versions_count": 1,
|
||||
"versions": [
|
||||
{
|
||||
"id": 0,
|
||||
"version": "0.0.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
|
||||
assert.Equal(t, expected, runAppForTest(t, []string{"get", "software"}))
|
||||
assert.YAMLEq(t, expectedYaml, runAppForTest(t, []string{"get", "software", "--yaml"}))
|
||||
assert.JSONEq(t, expectedJson, runAppForTest(t, []string{"get", "software", "--json"}))
|
||||
|
||||
runAppForTest(t, []string{"get", "software", "--json", "--team", "999"})
|
||||
require.NotNil(t, gotTeamID)
|
||||
assert.Equal(t, uint(999), *gotTeamID)
|
||||
}
|
||||
|
||||
func TestGetSoftwareVersions(t *testing.T) {
|
||||
_, ds := runServerWithMockedDS(t)
|
||||
|
||||
foo001 := fleet.Software{
|
||||
@ -627,17 +779,17 @@ func TestGetSoftware(t *testing.T) {
|
||||
return 4, nil
|
||||
}
|
||||
|
||||
expected := `+------+---------+-------------------+--------------------------+-----------+
|
||||
| NAME | VERSION | SOURCE | CPE | # OF CVES |
|
||||
+------+---------+-------------------+--------------------------+-----------+
|
||||
| foo | 0.0.1 | chrome_extensions | somecpe | 2 |
|
||||
+------+---------+-------------------+--------------------------+-----------+
|
||||
| foo | 0.0.2 | chrome_extensions | | 0 |
|
||||
+------+---------+-------------------+--------------------------+-----------+
|
||||
| foo | 0.0.3 | chrome_extensions | someothercpewithoutvulns | 0 |
|
||||
+------+---------+-------------------+--------------------------+-----------+
|
||||
| bar | 0.0.3 | deb_packages | | 0 |
|
||||
+------+---------+-------------------+--------------------------+-----------+
|
||||
expected := `+------+---------+-------------------+-------------------+-------+
|
||||
| NAME | VERSION | TYPE | VULNERABILITIES | HOSTS |
|
||||
+------+---------+-------------------+-------------------+-------+
|
||||
| foo | 0.0.1 | chrome_extensions | 2 vulnerabilities | 0 |
|
||||
+------+---------+-------------------+-------------------+-------+
|
||||
| foo | 0.0.2 | chrome_extensions | 0 vulnerabilities | 0 |
|
||||
+------+---------+-------------------+-------------------+-------+
|
||||
| foo | 0.0.3 | chrome_extensions | 0 vulnerabilities | 0 |
|
||||
+------+---------+-------------------+-------------------+-------+
|
||||
| bar | 0.0.3 | deb_packages | 0 vulnerabilities | 0 |
|
||||
+------+---------+-------------------+-------------------+-------+
|
||||
`
|
||||
|
||||
expectedYaml := `---
|
||||
@ -736,11 +888,11 @@ spec:
|
||||
}
|
||||
`
|
||||
|
||||
assert.Equal(t, expected, runAppForTest(t, []string{"get", "software"}))
|
||||
assert.YAMLEq(t, expectedYaml, runAppForTest(t, []string{"get", "software", "--yaml"}))
|
||||
assert.JSONEq(t, expectedJson, runAppForTest(t, []string{"get", "software", "--json"}))
|
||||
assert.Equal(t, expected, runAppForTest(t, []string{"get", "software", "--versions"}))
|
||||
assert.YAMLEq(t, expectedYaml, runAppForTest(t, []string{"get", "software", "--versions", "--yaml"}))
|
||||
assert.JSONEq(t, expectedJson, runAppForTest(t, []string{"get", "software", "--versions", "--json"}))
|
||||
|
||||
runAppForTest(t, []string{"get", "software", "--json", "--team", "999"})
|
||||
runAppForTest(t, []string{"get", "software", "--versions", "--json", "--team", "999"})
|
||||
require.NotNil(t, gotTeamID)
|
||||
assert.Equal(t, uint(999), *gotTeamID)
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
)
|
||||
|
||||
// ListSoftware retrieves the software running across hosts.
|
||||
func (c *Client) ListSoftware(query string) ([]fleet.Software, error) {
|
||||
// ListSoftwareVersions retrieves the software versions installed on hosts.
|
||||
func (c *Client) ListSoftwareVersions(query string) ([]fleet.Software, error) {
|
||||
verb, path := "GET", "/api/latest/fleet/software/versions"
|
||||
var responseBody listSoftwareVersionsResponse
|
||||
err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query)
|
||||
@ -14,3 +14,14 @@ func (c *Client) ListSoftware(query string) ([]fleet.Software, error) {
|
||||
}
|
||||
return responseBody.Software, nil
|
||||
}
|
||||
|
||||
// ListSoftwareTitles retrieves the software titles installed on hosts.
|
||||
func (c *Client) ListSoftwareTitles(query string) ([]fleet.SoftwareTitle, error) {
|
||||
verb, path := "GET", "/api/latest/fleet/software/titles"
|
||||
var responseBody listSoftwareTitlesResponse
|
||||
err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return responseBody.SoftwareTitles, nil
|
||||
}
|
||||
|
@ -310,7 +310,7 @@ func getSoftwareFromURL(url, apiToken string, debug bool) []fleet.Software {
|
||||
}
|
||||
apiClient.SetToken(apiToken)
|
||||
|
||||
software, err := apiClient.ListSoftware("")
|
||||
software, err := apiClient.ListSoftwareVersions("")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user