fix get hosts command to properly output json/yaml (#1774)

* fix get hosts command to properly output json/yaml based on command line flag

* add changes file

* added tests for get hosts when specifiying host

* added additional hosts to be returned in test cases

* go fmt
This commit is contained in:
Benjamin Edwards
2021-08-31 11:37:03 -04:00
committed by GitHub
parent 6b437b8526
commit 047dc5ac62
8 changed files with 274 additions and 58 deletions
+1
View File
@@ -0,0 +1 @@
* fix `fleetctl get host <hostname>` to properly output JSON when the command line flag is supplied i.e `fleetctl get host --json foobar`
+12 -4
View File
@@ -132,6 +132,16 @@ func printHost(c *cli.Context, host *service.HostResponse) error {
return printSpec(c, spec)
}
func printHostDetail(c *cli.Context, host *service.HostDetailResponse) error {
spec := specGeneric{
Kind: fleet.HostKind,
Version: fleet.ApiVersion,
Spec: host,
}
return printSpec(c, spec)
}
func printConfig(c *cli.Context, config *fleet.AppConfig) error {
spec := specGeneric{
Kind: fleet.AppConfigKind,
@@ -368,7 +378,7 @@ func getPacksCommand() *cli.Command {
return errors.Wrap(err, "could not list packs")
}
if c.Bool(yamlFlagName) {
if c.Bool(yamlFlagName) || c.Bool(jsonFlagName) {
for _, pack := range packs {
if err := printPack(c, pack); err != nil {
return errors.Wrap(err, "unable to print pack")
@@ -625,12 +635,10 @@ func getHostsCommand() *cli.Command {
if err != nil {
return errors.Wrap(err, "could not get host")
}
b, err := yaml.Marshal(host)
err = printHostDetail(c, host)
if err != nil {
return err
}
fmt.Print(string(b))
}
return nil
},
+123 -53
View File
@@ -1,7 +1,12 @@
package main
import (
"bufio"
"bytes"
"encoding/json"
"github.com/ghodss/yaml"
"io/ioutil"
"path/filepath"
"testing"
"time"
@@ -192,6 +197,7 @@ func TestGetHosts(t *testing.T) {
server, ds := runServerWithMockedDS(t)
defer server.Close()
// this func is called when no host is specified i.e. `fleetctl get hosts --json`
ds.ListHostsFunc = func(filter fleet.TeamFilter, opt fleet.HostListOptions) ([]*fleet.Host, error) {
hosts := []*fleet.Host{
{
@@ -207,67 +213,131 @@ func TestGetHosts(t *testing.T) {
ComputerName: "test_host",
Hostname: "test_host",
},
{
UpdateCreateTimestamps: fleet.UpdateCreateTimestamps{
CreateTimestamp: fleet.CreateTimestamp{CreatedAt: time.Time{}},
UpdateTimestamp: fleet.UpdateTimestamp{UpdatedAt: time.Time{}},
},
HostSoftware: fleet.HostSoftware{},
DetailUpdatedAt: time.Time{},
LabelUpdatedAt: time.Time{},
LastEnrolledAt: time.Time{},
SeenTime: time.Time{},
ComputerName: "test_host2",
Hostname: "test_host2",
},
}
return hosts, nil
}
expectedText := `+------+-----------+----------+-----------------+--------+
| UUID | HOSTNAME | PLATFORM | OSQUERY VERSION | STATUS |
+------+-----------+----------+-----------------+--------+
| | test_host | | | mia |
+------+-----------+----------+-----------------+--------+
// these are run when host is specified `fleetctl get hosts --json test_host`
ds.HostByIdentifierFunc = func(identifier string) (*fleet.Host, error) {
require.NotEmpty(t, identifier)
return &fleet.Host{
UpdateCreateTimestamps: fleet.UpdateCreateTimestamps{
CreateTimestamp: fleet.CreateTimestamp{CreatedAt: time.Time{}},
UpdateTimestamp: fleet.UpdateTimestamp{UpdatedAt: time.Time{}},
},
HostSoftware: fleet.HostSoftware{},
DetailUpdatedAt: time.Time{},
LabelUpdatedAt: time.Time{},
LastEnrolledAt: time.Time{},
SeenTime: time.Time{},
ComputerName: "test_host",
Hostname: "test_host"}, nil
}
ds.LoadHostSoftwareFunc = func(host *fleet.Host) error {
return nil
}
ds.ListLabelsForHostFunc = func(hid uint) ([]*fleet.Label, error) {
return make([]*fleet.Label, 0), nil
}
ds.ListPacksForHostFunc = func(hid uint) (packs []*fleet.Pack, err error) {
return make([]*fleet.Pack, 0), nil
}
expectedText := `+------+------------+----------+-----------------+--------+
| UUID | HOSTNAME | PLATFORM | OSQUERY VERSION | STATUS |
+------+------------+----------+-----------------+--------+
| | test_host | | | mia |
+------+------------+----------+-----------------+--------+
| | test_host2 | | | mia |
+------+------------+----------+-----------------+--------+
`
expectedYaml := `---
apiVersion: v1
kind: host
spec:
build: ""
code_name: ""
computer_name: test_host
config_tls_refresh: 0
cpu_brand: ""
cpu_logical_cores: 0
cpu_physical_cores: 0
cpu_subtype: ""
cpu_type: ""
created_at: "0001-01-01T00:00:00Z"
detail_updated_at: "0001-01-01T00:00:00Z"
display_text: test_host
distributed_interval: 0
gigs_disk_space_available: 0
hardware_model: ""
hardware_serial: ""
hardware_vendor: ""
hardware_version: ""
hostname: test_host
id: 0
label_updated_at: "0001-01-01T00:00:00Z"
last_enrolled_at: "0001-01-01T00:00:00Z"
logger_tls_period: 0
memory: 0
os_version: ""
osquery_version: ""
pack_stats: null
percent_disk_space_available: 0
platform: ""
platform_like: ""
primary_ip: ""
primary_mac: ""
refetch_requested: false
seen_time: "0001-01-01T00:00:00Z"
status: mia
team_id: null
team_name: null
updated_at: "0001-01-01T00:00:00Z"
uptime: 0
uuid: ""
`
expectedJson := "{\"kind\":\"host\",\"apiVersion\":\"v1\",\"spec\":{\"created_at\":\"0001-01-01T00:00:00Z\",\"updated_at\":\"0001-01-01T00:00:00Z\",\"id\":0,\"detail_updated_at\":\"0001-01-01T00:00:00Z\",\"label_updated_at\":\"0001-01-01T00:00:00Z\",\"last_enrolled_at\":\"0001-01-01T00:00:00Z\",\"seen_time\":\"0001-01-01T00:00:00Z\",\"refetch_requested\":false,\"hostname\":\"test_host\",\"uuid\":\"\",\"platform\":\"\",\"osquery_version\":\"\",\"os_version\":\"\",\"build\":\"\",\"platform_like\":\"\",\"code_name\":\"\",\"uptime\":0,\"memory\":0,\"cpu_type\":\"\",\"cpu_subtype\":\"\",\"cpu_brand\":\"\",\"cpu_physical_cores\":0,\"cpu_logical_cores\":0,\"hardware_vendor\":\"\",\"hardware_model\":\"\",\"hardware_version\":\"\",\"hardware_serial\":\"\",\"computer_name\":\"test_host\",\"primary_ip\":\"\",\"primary_mac\":\"\",\"distributed_interval\":0,\"config_tls_refresh\":0,\"logger_tls_period\":0,\"team_id\":null,\"pack_stats\":null,\"team_name\":null,\"gigs_disk_space_available\":0,\"percent_disk_space_available\":0,\"status\":\"mia\",\"display_text\":\"test_host\"}}\n"
tests := []struct {
name string
goldenFile string
unmarshaler func(data []byte, v interface{}) error
scanner func(s string) []string
args []string
}{
{
name: "get hosts --json",
goldenFile: "expectedListHostsJson.json",
unmarshaler: json.Unmarshal,
scanner: func(s string) []string {
var parts []string
scanner := bufio.NewScanner(bytes.NewBufferString(s))
for scanner.Scan() {
parts = append(parts, scanner.Text())
}
return parts
},
args: []string{"get", "hosts", "--json"},
},
{
name: "get hosts --json test_host",
goldenFile: "expectedHostDetailResponseJson.json",
unmarshaler: json.Unmarshal,
scanner: func(s string) []string {
return []string{s}
},
args: []string{"get", "hosts", "--json", "test_host"},
},
{
name: "get hosts --yaml",
goldenFile: "expectedListHostsYaml.yml",
unmarshaler: yaml.Unmarshal,
scanner: func(s string) []string {
return []string{s}
},
args: []string{"get", "hosts", "--yaml"},
},
{
name: "get hosts --yaml test_host",
goldenFile: "expectedHostDetailResponseYaml.yml",
unmarshaler: yaml.Unmarshal,
scanner: func(s string) []string {
return splitYaml(s)
},
args: []string{"get", "hosts", "--yaml", "test_host"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expected, err := ioutil.ReadFile(filepath.Join("testdata", tt.goldenFile))
require.NoError(t, err)
expectedResults := tt.scanner(string(expected))
expectedSpecs := make([]specGeneric, len(expectedResults))
for i, result := range expectedResults {
var got specGeneric
require.NoError(t, tt.unmarshaler([]byte(result), &got))
expectedSpecs[i] = got
}
actualResult := tt.scanner(runAppForTest(t, tt.args))
actualSpecs := make([]specGeneric, len(actualResult))
for i, result := range actualResult {
var spec specGeneric
require.NoError(t, tt.unmarshaler([]byte(result), &spec))
actualSpecs[i] = spec
}
require.Equal(t, expectedSpecs, actualSpecs)
})
}
assert.Equal(t, expectedText, runAppForTest(t, []string{"get", "hosts"}))
assert.Equal(t, expectedYaml, runAppForTest(t, []string{"get", "hosts", "--yaml"}))
assert.Equal(t, expectedJson, runAppForTest(t, []string{"get", "hosts", "--json"}))
}
func TestGetConfig(t *testing.T) {
@@ -0,0 +1 @@
{"kind":"host","apiVersion":"v1","spec":{"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","id":0,"detail_updated_at":"0001-01-01T00:00:00Z","label_updated_at":"0001-01-01T00:00:00Z","last_enrolled_at":"0001-01-01T00:00:00Z","seen_time":"0001-01-01T00:00:00Z","refetch_requested":false,"hostname":"test_host","uuid":"","platform":"","osquery_version":"","os_version":"","build":"","platform_like":"","code_name":"","uptime":0,"memory":0,"cpu_type":"","cpu_subtype":"","cpu_brand":"","cpu_physical_cores":0,"cpu_logical_cores":0,"hardware_vendor":"","hardware_model":"","hardware_version":"","hardware_serial":"","computer_name":"test_host","primary_ip":"","primary_mac":"","distributed_interval":0,"config_tls_refresh":0,"logger_tls_period":0,"team_id":null,"pack_stats":null,"team_name":null,"gigs_disk_space_available":0,"percent_disk_space_available":0,"labels":[],"packs":[],"status":"mia","display_text":"test_host"}}
@@ -0,0 +1,46 @@
---
apiVersion: v1
kind: host
spec:
build: ""
code_name: ""
computer_name: test_host
config_tls_refresh: 0
cpu_brand: ""
cpu_logical_cores: 0
cpu_physical_cores: 0
cpu_subtype: ""
cpu_type: ""
created_at: "0001-01-01T00:00:00Z"
detail_updated_at: "0001-01-01T00:00:00Z"
display_text: test_host
distributed_interval: 0
gigs_disk_space_available: 0
hardware_model: ""
hardware_serial: ""
hardware_vendor: ""
hardware_version: ""
hostname: test_host
id: 0
label_updated_at: "0001-01-01T00:00:00Z"
labels: []
last_enrolled_at: "0001-01-01T00:00:00Z"
logger_tls_period: 0
memory: 0
os_version: ""
osquery_version: ""
pack_stats: null
packs: []
percent_disk_space_available: 0
platform: ""
platform_like: ""
primary_ip: ""
primary_mac: ""
refetch_requested: false
seen_time: "0001-01-01T00:00:00Z"
status: mia
team_id: null
team_name: null
updated_at: "0001-01-01T00:00:00Z"
uptime: 0
uuid: ""
+2
View File
@@ -0,0 +1,2 @@
{"kind":"host","apiVersion":"v1","spec":{"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","id":0,"detail_updated_at":"0001-01-01T00:00:00Z","label_updated_at":"0001-01-01T00:00:00Z","last_enrolled_at":"0001-01-01T00:00:00Z","seen_time":"0001-01-01T00:00:00Z","refetch_requested":false,"hostname":"test_host","uuid":"","platform":"","osquery_version":"","os_version":"","build":"","platform_like":"","code_name":"","uptime":0,"memory":0,"cpu_type":"","cpu_subtype":"","cpu_brand":"","cpu_physical_cores":0,"cpu_logical_cores":0,"hardware_vendor":"","hardware_model":"","hardware_version":"","hardware_serial":"","computer_name":"test_host","primary_ip":"","primary_mac":"","distributed_interval":0,"config_tls_refresh":0,"logger_tls_period":0,"team_id":null,"pack_stats":null,"team_name":null,"gigs_disk_space_available":0,"percent_disk_space_available":0,"status":"mia","display_text":"test_host"}}
{"kind":"host","apiVersion":"v1","spec":{"created_at":"0001-01-01T00:00:00Z","updated_at":"0001-01-01T00:00:00Z","id":0,"detail_updated_at":"0001-01-01T00:00:00Z","label_updated_at":"0001-01-01T00:00:00Z","last_enrolled_at":"0001-01-01T00:00:00Z","seen_time":"0001-01-01T00:00:00Z","refetch_requested":false,"hostname":"test_host2","uuid":"","platform":"","osquery_version":"","os_version":"","build":"","platform_like":"","code_name":"","uptime":0,"memory":0,"cpu_type":"","cpu_subtype":"","cpu_brand":"","cpu_physical_cores":0,"cpu_logical_cores":0,"hardware_vendor":"","hardware_model":"","hardware_version":"","hardware_serial":"","computer_name":"test_host2","primary_ip":"","primary_mac":"","distributed_interval":0,"config_tls_refresh":0,"logger_tls_period":0,"team_id":null,"pack_stats":null,"team_name":null,"gigs_disk_space_available":0,"percent_disk_space_available":0,"status":"mia","display_text":"test_host2"}}
+88
View File
@@ -0,0 +1,88 @@
---
apiVersion: v1
kind: host
spec:
build: ""
code_name: ""
computer_name: test_host
config_tls_refresh: 0
cpu_brand: ""
cpu_logical_cores: 0
cpu_physical_cores: 0
cpu_subtype: ""
cpu_type: ""
created_at: "0001-01-01T00:00:00Z"
detail_updated_at: "0001-01-01T00:00:00Z"
display_text: test_host
distributed_interval: 0
gigs_disk_space_available: 0
hardware_model: ""
hardware_serial: ""
hardware_vendor: ""
hardware_version: ""
hostname: test_host
id: 0
label_updated_at: "0001-01-01T00:00:00Z"
last_enrolled_at: "0001-01-01T00:00:00Z"
logger_tls_period: 0
memory: 0
os_version: ""
osquery_version: ""
pack_stats: null
percent_disk_space_available: 0
platform: ""
platform_like: ""
primary_ip: ""
primary_mac: ""
refetch_requested: false
seen_time: "0001-01-01T00:00:00Z"
status: mia
team_id: null
team_name: null
updated_at: "0001-01-01T00:00:00Z"
uptime: 0
uuid: ""
---
apiVersion: v1
kind: host
spec:
build: ""
code_name: ""
computer_name: test_host2
config_tls_refresh: 0
cpu_brand: ""
cpu_logical_cores: 0
cpu_physical_cores: 0
cpu_subtype: ""
cpu_type: ""
created_at: "0001-01-01T00:00:00Z"
detail_updated_at: "0001-01-01T00:00:00Z"
display_text: test_host2
distributed_interval: 0
gigs_disk_space_available: 0
hardware_model: ""
hardware_serial: ""
hardware_vendor: ""
hardware_version: ""
hostname: test_host2
id: 0
label_updated_at: "0001-01-01T00:00:00Z"
last_enrolled_at: "0001-01-01T00:00:00Z"
logger_tls_period: 0
memory: 0
os_version: ""
osquery_version: ""
pack_stats: null
percent_disk_space_available: 0
platform: ""
platform_like: ""
primary_ip: ""
primary_mac: ""
refetch_requested: false
seen_time: "0001-01-01T00:00:00Z"
status: mia
team_id: null
team_name: null
updated_at: "0001-01-01T00:00:00Z"
uptime: 0
uuid: ""
+1 -1
View File
@@ -26,7 +26,7 @@ func hostResponseForHost(ctx context.Context, svc fleet.Service, host *fleet.Hos
}, nil
}
// HostDetailresponse is the response struct that contains the full host information
// HostDetailResponse is the response struct that contains the full host information
// with the HostDetail details.
type HostDetailResponse struct {
fleet.HostDetail