mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Add fleetctl get host capability to get single host with labels
Getting a single host with `fleetctl get host foobar` will look up the host with the matching hostname, uuid, osquery identifier, or node key, and provide the full host details along with the labels the host is a member of.
This commit is contained in:
parent
7f757d3144
commit
fcb8418b2f
@ -575,43 +575,57 @@ func getHostsCommand() cli.Command {
|
||||
return err
|
||||
}
|
||||
|
||||
hosts, err := fleet.GetHosts()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not list hosts")
|
||||
}
|
||||
identifier := c.Args().First()
|
||||
|
||||
if len(hosts) == 0 {
|
||||
fmt.Println("no hosts found")
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Bool(jsonFlagName) || c.Bool(yamlFlagName) {
|
||||
for _, host := range hosts {
|
||||
err = printHost(c, &host.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if identifier == "" {
|
||||
hosts, err := fleet.GetHosts()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not list hosts")
|
||||
}
|
||||
return nil
|
||||
|
||||
if len(hosts) == 0 {
|
||||
fmt.Println("no hosts found")
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.Bool(jsonFlagName) || c.Bool(yamlFlagName) {
|
||||
for _, host := range hosts {
|
||||
err = printHost(c, &host.Host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Default to printing as table
|
||||
data := [][]string{}
|
||||
|
||||
for _, host := range hosts {
|
||||
data = append(data, []string{
|
||||
host.Host.UUID,
|
||||
host.DisplayText,
|
||||
host.Host.Platform,
|
||||
string(host.Status),
|
||||
})
|
||||
}
|
||||
|
||||
table := defaultTable()
|
||||
table.SetHeader([]string{"uuid", "hostname", "platform", "status"})
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
} else {
|
||||
host, err := fleet.HostByIdentifier(identifier)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not get host")
|
||||
}
|
||||
b, err := yaml.Marshal(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Print(string(b))
|
||||
}
|
||||
|
||||
// Default to printing as table
|
||||
data := [][]string{}
|
||||
|
||||
for _, host := range hosts {
|
||||
data = append(data, []string{
|
||||
host.Host.UUID,
|
||||
host.DisplayText,
|
||||
host.Host.Platform,
|
||||
string(host.Status),
|
||||
})
|
||||
}
|
||||
|
||||
table := defaultTable()
|
||||
table.SetHeader([]string{"uuid", "hostname", "platform", "status"})
|
||||
table.AppendBulk(data)
|
||||
table.Render()
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
@ -500,6 +500,7 @@ func testHostIDsByName(t *testing.T, ds kolide.Datastore) {
|
||||
func testHostAdditional(t *testing.T, ds kolide.Datastore) {
|
||||
_, err := ds.NewHost(&kolide.Host{
|
||||
DetailUpdateTime: time.Now(),
|
||||
LabelUpdateTime: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
LabelUpdateTime: time.Now(),
|
||||
OsqueryHostID: "foobar",
|
||||
@ -567,3 +568,41 @@ func testHostAdditional(t *testing.T, ds kolide.Datastore) {
|
||||
require.Nil(t, err)
|
||||
assert.Equal(t, additional, *h.Additional)
|
||||
}
|
||||
|
||||
func testHostByIdentifier(t *testing.T, ds kolide.Datastore) {
|
||||
for i := 1; i <= 10; i++ {
|
||||
_, err := ds.NewHost(&kolide.Host{
|
||||
DetailUpdateTime: time.Now(),
|
||||
LabelUpdateTime: time.Now(),
|
||||
SeenTime: time.Now(),
|
||||
OsqueryHostID: fmt.Sprintf("osquery_host_id_%d", i),
|
||||
NodeKey: fmt.Sprintf("node_key_%d", i),
|
||||
UUID: fmt.Sprintf("uuid_%d", i),
|
||||
HostName: fmt.Sprintf("hostname_%d", i),
|
||||
})
|
||||
require.Nil(t, err)
|
||||
}
|
||||
|
||||
var (
|
||||
h *kolide.Host
|
||||
err error
|
||||
)
|
||||
h, err = ds.HostByIdentifier("uuid_1")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint(1), h.ID)
|
||||
|
||||
h, err = ds.HostByIdentifier("osquery_host_id_2")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint(2), h.ID)
|
||||
|
||||
h, err = ds.HostByIdentifier("node_key_4")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint(4), h.ID)
|
||||
|
||||
h, err = ds.HostByIdentifier("hostname_7")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, uint(7), h.ID)
|
||||
|
||||
h, err = ds.HostByIdentifier("foobar")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ var testFunctions = [...]func(*testing.T, kolide.Datastore){
|
||||
testListHostsInPack,
|
||||
testListPacksForHost,
|
||||
testHostIDsByName,
|
||||
testHostByIdentifier,
|
||||
testListPacks,
|
||||
testDistributedQueryCampaign,
|
||||
testCleanupDistributedQueryCampaigns,
|
||||
|
@ -284,6 +284,7 @@ func (d *Datastore) AuthenticateHost(nodeKey string) (*kolide.Host, error) {
|
||||
deleted_at,
|
||||
deleted,
|
||||
detail_update_time,
|
||||
label_update_time,
|
||||
node_key,
|
||||
host_name,
|
||||
uuid,
|
||||
@ -480,3 +481,18 @@ func (d *Datastore) HostIDsByName(hostnames []string) ([]uint, error) {
|
||||
return hostIDs, nil
|
||||
|
||||
}
|
||||
|
||||
func (d *Datastore) HostByIdentifier(identifier string) (*kolide.Host, error) {
|
||||
sql := `
|
||||
SELECT * FROM hosts
|
||||
WHERE ? IN (host_name, osquery_host_id, node_key, uuid)
|
||||
LIMIT 1
|
||||
`
|
||||
host := &kolide.Host{}
|
||||
err := d.db.Get(host, sql, identifier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get host by identifier")
|
||||
}
|
||||
|
||||
return host, nil
|
||||
}
|
||||
|
@ -67,6 +67,10 @@ type HostStore interface {
|
||||
GenerateHostStatusStatistics(now time.Time) (online, offline, mia, new uint, err error)
|
||||
// HostIDsByName Retrieve the IDs associated with the given hostnames
|
||||
HostIDsByName(hostnames []string) ([]uint, error)
|
||||
// HostByIdentifier returns one host matching the provided identifier.
|
||||
// Possible matches can be on osquery_host_identifier, node_key, UUID, or
|
||||
// hostname.
|
||||
HostByIdentifier(identifier string) (*Host, error)
|
||||
}
|
||||
|
||||
type HostService interface {
|
||||
@ -74,6 +78,10 @@ type HostService interface {
|
||||
GetHost(ctx context.Context, id uint) (host *Host, err error)
|
||||
GetHostSummary(ctx context.Context) (summary *HostSummary, err error)
|
||||
DeleteHost(ctx context.Context, id uint) (err error)
|
||||
// HostByIdentifier returns one host matching the provided identifier.
|
||||
// Possible matches can be on osquery_host_identifier, node_key, UUID, or
|
||||
// hostname.
|
||||
HostByIdentifier(ctx context.Context, identifier string) (*Host, error)
|
||||
}
|
||||
|
||||
type HostListOptions struct {
|
||||
|
@ -76,6 +76,9 @@ type LabelService interface {
|
||||
// given ID.
|
||||
ListHostsInLabel(ctx context.Context, lid uint, opt ListOptions) ([]Host, error)
|
||||
|
||||
// LabelsForHost returns the labels that the given host is in.
|
||||
ListLabelsForHost(ctx context.Context, hid uint) ([]Label, error)
|
||||
|
||||
// HostIDsForLabel returns ids of hosts that belong to the label identified
|
||||
// by lid
|
||||
HostIDsForLabel(lid uint) ([]uint, error)
|
||||
|
@ -18,6 +18,8 @@ type DeleteHostFunc func(hid uint) error
|
||||
|
||||
type HostFunc func(id uint) (*kolide.Host, error)
|
||||
|
||||
type HostByIdentifierFunc func(identifier string) (*kolide.Host, error)
|
||||
|
||||
type ListHostsFunc func(opt kolide.HostListOptions) ([]*kolide.Host, error)
|
||||
|
||||
type EnrollHostFunc func(osqueryHostId, nodeKey, secretName string) (*kolide.Host, error)
|
||||
@ -49,6 +51,9 @@ type HostStore struct {
|
||||
HostFunc HostFunc
|
||||
HostFuncInvoked bool
|
||||
|
||||
HostByIdentifierFunc HostByIdentifierFunc
|
||||
HostByIdentifierFuncInvoked bool
|
||||
|
||||
ListHostsFunc ListHostsFunc
|
||||
ListHostsFuncInvoked bool
|
||||
|
||||
@ -97,6 +102,11 @@ func (s *HostStore) Host(id uint) (*kolide.Host, error) {
|
||||
return s.HostFunc(id)
|
||||
}
|
||||
|
||||
func (s *HostStore) HostByIdentifier(identifier string) (*kolide.Host, error) {
|
||||
s.HostByIdentifierFuncInvoked = true
|
||||
return s.HostByIdentifierFunc(identifier)
|
||||
}
|
||||
|
||||
func (s *HostStore) ListHosts(opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
s.ListHostsFuncInvoked = true
|
||||
return s.ListHostsFunc(opt)
|
||||
|
@ -33,3 +33,31 @@ func (c *Client) GetHosts() ([]HostResponse, error) {
|
||||
|
||||
return responseBody.Hosts, nil
|
||||
}
|
||||
|
||||
// HostByIdentifier retrieves a host by the uuid, osquery_host_id, hostname, or
|
||||
// node_key.
|
||||
func (c *Client) HostByIdentifier(identifier string) (*HostResponse, error) {
|
||||
response, err := c.AuthenticatedDo("GET", "/api/v1/kolide/hosts/identifier/"+identifier, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GET /api/v1/kolide/hosts/identifier")
|
||||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
if response.StatusCode != http.StatusOK {
|
||||
return nil, errors.Errorf(
|
||||
"get host by identifier received status %d %s",
|
||||
response.StatusCode,
|
||||
extractServerErrorText(response.Body),
|
||||
)
|
||||
}
|
||||
var responseBody getHostResponse
|
||||
err = json.NewDecoder(response.Body).Decode(&responseBody)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decode host response")
|
||||
}
|
||||
if responseBody.Err != nil {
|
||||
return nil, errors.Errorf("get host by identifier: %s", responseBody.Err)
|
||||
}
|
||||
|
||||
return responseBody.Host, nil
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/kolide/fleet/server/kolide"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HostResponse is the response struct that contains the full host information
|
||||
@ -15,6 +16,7 @@ type HostResponse struct {
|
||||
kolide.Host
|
||||
Status kolide.HostStatus `json:"status"`
|
||||
DisplayText string `json:"display_text"`
|
||||
Labels []kolide.Label `json:"labels,omitempty"`
|
||||
}
|
||||
|
||||
func hostResponseForHost(ctx context.Context, svc kolide.Service, host *kolide.Host) (*HostResponse, error) {
|
||||
@ -25,6 +27,20 @@ func hostResponseForHost(ctx context.Context, svc kolide.Service, host *kolide.H
|
||||
}, nil
|
||||
}
|
||||
|
||||
func addLabelsToHost(ctx context.Context, svc kolide.Service, host *kolide.Host) (*HostResponse, error) {
|
||||
labels, err := svc.ListLabelsForHost(ctx, host.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "list labels for host")
|
||||
}
|
||||
return &HostResponse{
|
||||
Host: *host,
|
||||
Status: host.Status(time.Now()),
|
||||
DisplayText: host.HostName,
|
||||
Labels: labels,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Get Host
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@ -59,6 +75,33 @@ func makeGetHostEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Get Host
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type hostByIdentifierRequest struct {
|
||||
Identifier string `json:"identifier"`
|
||||
}
|
||||
|
||||
func makeHostByIdentifierEndpoint(svc kolide.Service) endpoint.Endpoint {
|
||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
||||
req := request.(hostByIdentifierRequest)
|
||||
host, err := svc.HostByIdentifier(ctx, req.Identifier)
|
||||
if err != nil {
|
||||
return getHostResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
resp, err := addLabelsToHost(ctx, svc, host)
|
||||
if err != nil {
|
||||
return getHostResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
return getHostResponse{
|
||||
Host: resp,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// List Hosts
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -84,6 +84,7 @@ type KolideEndpoints struct {
|
||||
GetLabelSpecs endpoint.Endpoint
|
||||
GetLabelSpec endpoint.Endpoint
|
||||
GetHost endpoint.Endpoint
|
||||
HostByIdentifier endpoint.Endpoint
|
||||
DeleteHost endpoint.Endpoint
|
||||
ListHosts endpoint.Endpoint
|
||||
GetHostSummary endpoint.Endpoint
|
||||
@ -168,6 +169,7 @@ func MakeKolideServerEndpoints(svc kolide.Service, jwtKey, urlPrefix string) Kol
|
||||
GetPackSpecs: authenticatedUser(jwtKey, svc, makeGetPackSpecsEndpoint(svc)),
|
||||
GetPackSpec: authenticatedUser(jwtKey, svc, makeGetPackSpecEndpoint(svc)),
|
||||
GetHost: authenticatedUser(jwtKey, svc, makeGetHostEndpoint(svc)),
|
||||
HostByIdentifier: authenticatedUser(jwtKey, svc, makeHostByIdentifierEndpoint(svc)),
|
||||
ListHosts: authenticatedUser(jwtKey, svc, makeListHostsEndpoint(svc)),
|
||||
GetHostSummary: authenticatedUser(jwtKey, svc, makeGetHostSummaryEndpoint(svc)),
|
||||
DeleteHost: authenticatedUser(jwtKey, svc, makeDeleteHostEndpoint(svc)),
|
||||
@ -269,6 +271,7 @@ type kolideHandlers struct {
|
||||
GetLabelSpecs http.Handler
|
||||
GetLabelSpec http.Handler
|
||||
GetHost http.Handler
|
||||
HostByIdentifier http.Handler
|
||||
DeleteHost http.Handler
|
||||
ListHosts http.Handler
|
||||
GetHostSummary http.Handler
|
||||
@ -357,6 +360,7 @@ func makeKolideKitHandlers(e KolideEndpoints, opts []kithttp.ServerOption) *koli
|
||||
GetLabelSpecs: newServer(e.GetLabelSpecs, decodeNoParamsRequest),
|
||||
GetLabelSpec: newServer(e.GetLabelSpec, decodeGetGenericSpecRequest),
|
||||
GetHost: newServer(e.GetHost, decodeGetHostRequest),
|
||||
HostByIdentifier: newServer(e.HostByIdentifier, decodeHostByIdentifierRequest),
|
||||
DeleteHost: newServer(e.DeleteHost, decodeDeleteHostRequest),
|
||||
ListHosts: newServer(e.ListHosts, decodeListHostsRequest),
|
||||
GetHostSummary: newServer(e.GetHostSummary, decodeNoParamsRequest),
|
||||
@ -489,6 +493,7 @@ func attachKolideAPIRoutes(r *mux.Router, h *kolideHandlers) {
|
||||
r.Handle("/api/v1/kolide/hosts", h.ListHosts).Methods("GET").Name("list_hosts")
|
||||
r.Handle("/api/v1/kolide/host_summary", h.GetHostSummary).Methods("GET").Name("get_host_summary")
|
||||
r.Handle("/api/v1/kolide/hosts/{id}", h.GetHost).Methods("GET").Name("get_host")
|
||||
r.Handle("/api/v1/kolide/hosts/identifier/{identifier}", h.HostByIdentifier).Methods("GET").Name("host_by_identifier")
|
||||
r.Handle("/api/v1/kolide/hosts/{id}", h.DeleteHost).Methods("DELETE").Name("delete_host")
|
||||
|
||||
r.Handle("/api/v1/kolide/spec/osquery_options", h.ApplyOsqueryOptionsSpec).Methods("POST").Name("apply_osquery_options_spec")
|
||||
|
@ -14,6 +14,10 @@ func (svc service) GetHost(ctx context.Context, id uint) (*kolide.Host, error) {
|
||||
return svc.ds.Host(id)
|
||||
}
|
||||
|
||||
func (svc service) HostByIdentifier(ctx context.Context, identifier string) (*kolide.Host, error) {
|
||||
return svc.ds.HostByIdentifier(identifier)
|
||||
}
|
||||
|
||||
func (svc service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, error) {
|
||||
online, offline, mia, new, err := svc.ds.GenerateHostStatusStatistics(svc.clock.Now())
|
||||
if err != nil {
|
||||
|
@ -94,6 +94,10 @@ func (svc service) ListHostsInLabel(ctx context.Context, lid uint, opt kolide.Li
|
||||
return svc.ds.ListHostsInLabel(lid, opt)
|
||||
}
|
||||
|
||||
func (svc service) ListLabelsForHost(ctx context.Context, hid uint) ([]kolide.Label, error) {
|
||||
return svc.ds.ListLabelsForHost(hid)
|
||||
}
|
||||
|
||||
func (svc service) HostIDsForLabel(lid uint) ([]uint, error) {
|
||||
hosts, err := svc.ds.ListHostsInLabel(lid, kolide.ListOptions{})
|
||||
if err != nil {
|
||||
|
@ -16,6 +16,14 @@ func decodeGetHostRequest(ctx context.Context, r *http.Request) (interface{}, er
|
||||
return getHostRequest{ID: id}, nil
|
||||
}
|
||||
|
||||
func decodeHostByIdentifierRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
identifier, err := nameFromRequest(r, "identifier")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hostByIdentifierRequest{Identifier: identifier}, nil
|
||||
}
|
||||
|
||||
func decodeDeleteHostRequest(ctx context.Context, r *http.Request) (interface{}, error) {
|
||||
id, err := idFromRequest(r, "id")
|
||||
if err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user