mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 01:18:42 +00:00
Add host details in API responses (#223)
Add label and pack information for the returned hosts in the single-host API endpoints. Example: ``` curl -k 'https://localhost:8080/api/v1/kolide/hosts/7' -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZXNzaW9uX2tleSI6Ii9oNEZ4MUpEVmlvQWhtMC8wNUJKbzZpdldsUDZpMDhjQVBuZXRLeFIvWjNOUGgvMW9VdCsxQnFlNU1CVDVsMlU3ckVGMm5Sb1VxS3ZSUllzSmJJR2lBPT0ifQ.GQQsJgBU3JA1H1o4Y8fPjyfF78F_VY4c9AbrP5k0sCg' { "host": { "created_at": "2021-01-16T00:22:33Z", "updated_at": "2021-01-16T00:22:51Z", "id": 7, "detail_updated_at": "1970-01-02T00:00:00Z", "label_updated_at": "1970-01-02T00:00:00Z", "last_enrolled_at": "2021-01-16T00:22:33Z", "seen_time": "2021-01-16T00:22:51Z", "hostname": "55d91fc9c303", "uuid": "853a4588-0000-0000-a061-7d494d04e9c4", "platform": "ubuntu", "osquery_version": "4.6.0", "os_version": "Ubuntu 20.04.0", "build": "", "platform_like": "debian", "code_name": "", "uptime": 0, "memory": 16794206208, "cpu_type": "x86_64", "cpu_subtype": "158", "cpu_brand": "Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz\u0000\u0000\u0000\u0000\u0000\u0000\u0000", "cpu_physical_cores": 8, "cpu_logical_cores": 8, "hardware_vendor": "", "hardware_model": "", "hardware_version": "", "hardware_serial": "", "computer_name": "55d91fc9c303", "primary_ip": "", "primary_mac": "", "distributed_interval": 10, "config_tls_refresh": 0, "logger_tls_period": 10, "enroll_secret_name": "default", "labels": [ { "created_at": "2020-12-22T01:22:47Z", "updated_at": "2020-12-22T01:22:47Z", "id": 6, "name": "All Hosts", "description": "All hosts which have enrolled in Fleet", "query": "select 1;", "label_type": "builtin", "label_membership_type": "dynamic" } ], "packs": [ { "created_at": "2021-01-20T16:36:42Z", "updated_at": "2021-01-20T16:36:42Z", "id": 2, "name": "test" } ], "status": "offline", "display_text": "55d91fc9c303" } } ```
This commit is contained in:
parent
3fc6412e67
commit
6215acdd1b
8 changed files with 116 additions and 56 deletions
|
|
@ -13,13 +13,10 @@ type HostStatus string
|
|||
const (
|
||||
// StatusOnline host is active.
|
||||
StatusOnline = HostStatus("online")
|
||||
|
||||
// StatusOffline no communication with host for OfflineDuration.
|
||||
StatusOffline = HostStatus("offline")
|
||||
|
||||
// StatusMIA no communication with host for MIADuration.
|
||||
StatusMIA = HostStatus("mia")
|
||||
|
||||
// StatusNew means the host has enrolled in the interval defined by
|
||||
// NewDuration. It is independent of offline and online.
|
||||
StatusNew = HostStatus("new")
|
||||
|
|
@ -28,7 +25,7 @@ const (
|
|||
// considered new.
|
||||
NewDuration = 24 * time.Hour
|
||||
|
||||
// OfflineDuration if a host hasn't been in communication for this period it
|
||||
// MIADuration if a host hasn't been in communication for this period it
|
||||
// is considered MIA.
|
||||
MIADuration = 30 * 24 * time.Hour
|
||||
|
||||
|
|
@ -84,13 +81,13 @@ type HostStore interface {
|
|||
|
||||
type HostService interface {
|
||||
ListHosts(ctx context.Context, opt HostListOptions) (hosts []*Host, err error)
|
||||
GetHost(ctx context.Context, id uint) (host *Host, err error)
|
||||
GetHost(ctx context.Context, id uint) (host *HostDetail, 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)
|
||||
HostByIdentifier(ctx context.Context, identifier string) (*HostDetail, error)
|
||||
}
|
||||
|
||||
type HostListOptions struct {
|
||||
|
|
@ -146,6 +143,16 @@ type Host struct {
|
|||
EnrollSecretName string `json:"enroll_secret_name" db:"enroll_secret_name"`
|
||||
}
|
||||
|
||||
// HostDetail provides the full host metadata along with associated labels and
|
||||
// packs.
|
||||
type HostDetail struct {
|
||||
Host
|
||||
// Labels is the list of labels the host is a member of.
|
||||
Labels []Label `json:"labels"`
|
||||
// Packs is the list of packs the host is a member of.
|
||||
Packs []Pack `json:"packs"`
|
||||
}
|
||||
|
||||
const (
|
||||
HostKind = "host"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -169,12 +169,12 @@ type Label struct {
|
|||
UpdateCreateTimestamps
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Query string `json:"query"`
|
||||
Platform string `json:"platform"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
LabelType LabelType `json:"label_type" db:"label_type"`
|
||||
LabelMembershipType LabelMembershipType `json:"label_membership_type" db:"label_membership_type"`
|
||||
HostCount int `json:"host_count" db:"host_count"`
|
||||
HostCount int `json:"host_count,omitempty" db:"host_count"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -124,9 +124,9 @@ type Pack struct {
|
|||
UpdateCreateTimestamps
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Platform string `json:"platform"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
Disabled bool `json:"disabled,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func (c *Client) GetHosts() ([]HostResponse, error) {
|
|||
|
||||
// HostByIdentifier retrieves a host by the uuid, osquery_host_id, hostname, or
|
||||
// node_key.
|
||||
func (c *Client) HostByIdentifier(identifier string) (*HostResponse, error) {
|
||||
func (c *Client) HostByIdentifier(identifier string) (*HostDetailResponse, 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")
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ import (
|
|||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/go-kit/kit/endpoint"
|
||||
)
|
||||
|
||||
// HostResponse is the response struct that contains the full host information
|
||||
|
|
@ -27,18 +26,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,
|
||||
// HostDetailresponse is the response struct that contains the full host information
|
||||
// with the HostDetail details.
|
||||
type HostDetailResponse struct {
|
||||
kolide.HostDetail
|
||||
Status kolide.HostStatus `json:"status"`
|
||||
DisplayText string `json:"display_text"`
|
||||
}
|
||||
|
||||
func hostDetailResponseForHost(ctx context.Context, svc kolide.Service, host *kolide.HostDetail) (*HostDetailResponse, error) {
|
||||
return &HostDetailResponse{
|
||||
HostDetail: *host,
|
||||
Status: host.Status(time.Now()),
|
||||
DisplayText: host.HostName,
|
||||
Labels: labels,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
@ -50,8 +51,8 @@ type getHostRequest struct {
|
|||
}
|
||||
|
||||
type getHostResponse struct {
|
||||
Host *HostResponse `json:"host"`
|
||||
Err error `json:"error,omitempty"`
|
||||
Host *HostDetailResponse `json:"host"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r getHostResponse) error() error { return r.Err }
|
||||
|
|
@ -64,7 +65,7 @@ func makeGetHostEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
return getHostResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
resp, err := hostResponseForHost(ctx, svc, host)
|
||||
resp, err := hostDetailResponseForHost(ctx, svc, host)
|
||||
if err != nil {
|
||||
return getHostResponse{Err: err}, nil
|
||||
}
|
||||
|
|
@ -76,7 +77,7 @@ func makeGetHostEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Get Host
|
||||
// Get Host By Identifier
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
type hostByIdentifierRequest struct {
|
||||
|
|
@ -91,7 +92,7 @@ func makeHostByIdentifierEndpoint(svc kolide.Service) endpoint.Endpoint {
|
|||
return getHostResponse{Err: err}, nil
|
||||
}
|
||||
|
||||
resp, err := addLabelsToHost(ctx, svc, host)
|
||||
resp, err := hostDetailResponseForHost(ctx, svc, host)
|
||||
if err != nil {
|
||||
return getHostResponse{Err: err}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,9 +25,9 @@ func (mw loggingMiddleware) ListHosts(ctx context.Context, opt kolide.HostListOp
|
|||
return hosts, err
|
||||
}
|
||||
|
||||
func (mw loggingMiddleware) GetHost(ctx context.Context, id uint) (*kolide.Host, error) {
|
||||
func (mw loggingMiddleware) GetHost(ctx context.Context, id uint) (*kolide.HostDetail, error) {
|
||||
var (
|
||||
host *kolide.Host
|
||||
host *kolide.HostDetail
|
||||
err error
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,18 +4,50 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (svc service) ListHosts(ctx context.Context, opt kolide.HostListOptions) ([]*kolide.Host, error) {
|
||||
return svc.ds.ListHosts(opt)
|
||||
}
|
||||
|
||||
func (svc service) GetHost(ctx context.Context, id uint) (*kolide.Host, error) {
|
||||
return svc.ds.Host(id)
|
||||
func (svc service) GetHost(ctx context.Context, id uint) (*kolide.HostDetail, error) {
|
||||
host, err := svc.ds.Host(id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get host")
|
||||
}
|
||||
|
||||
return svc.getHostDetails(ctx, host)
|
||||
}
|
||||
|
||||
func (svc service) HostByIdentifier(ctx context.Context, identifier string) (*kolide.Host, error) {
|
||||
return svc.ds.HostByIdentifier(identifier)
|
||||
func (svc service) HostByIdentifier(ctx context.Context, identifier string) (*kolide.HostDetail, error) {
|
||||
host, err := svc.ds.HostByIdentifier(identifier)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get host by identifier")
|
||||
}
|
||||
|
||||
return svc.getHostDetails(ctx, host)
|
||||
}
|
||||
|
||||
func (svc service) getHostDetails(ctx context.Context, host *kolide.Host) (*kolide.HostDetail, error) {
|
||||
labels, err := svc.ds.ListLabelsForHost(host.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get labels for host")
|
||||
}
|
||||
|
||||
packPtrs, err := svc.ds.ListPacksForHost(host.ID)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get packs for host")
|
||||
}
|
||||
|
||||
// TODO refactor List* APIs to be consistent so we don't have to do this
|
||||
// transformation
|
||||
packs := make([]kolide.Pack, 0, len(packPtrs))
|
||||
for _, p := range packPtrs {
|
||||
packs = append(packs, *p)
|
||||
}
|
||||
|
||||
return &kolide.HostDetail{Host: *host, Labels: labels, Packs: packs}, nil
|
||||
}
|
||||
|
||||
func (svc service) GetHostSummary(ctx context.Context) (*kolide.HostSummary, error) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import (
|
|||
"github.com/fleetdm/fleet/server/config"
|
||||
"github.com/fleetdm/fleet/server/datastore/inmem"
|
||||
"github.com/fleetdm/fleet/server/kolide"
|
||||
"github.com/fleetdm/fleet/server/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestListHosts(t *testing.T) {
|
||||
|
|
@ -33,27 +35,6 @@ func TestListHosts(t *testing.T) {
|
|||
assert.Len(t, hosts, 1)
|
||||
}
|
||||
|
||||
func TestGetHost(t *testing.T) {
|
||||
ds, err := inmem.New(config.TestConfig())
|
||||
assert.Nil(t, err)
|
||||
|
||||
svc, err := newTestService(ds, nil, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
host, err := ds.NewHost(&kolide.Host{
|
||||
HostName: "foo",
|
||||
})
|
||||
assert.Nil(t, err)
|
||||
assert.NotZero(t, host.ID)
|
||||
|
||||
hostVerify, err := svc.GetHost(ctx, host.ID)
|
||||
assert.Nil(t, err)
|
||||
|
||||
assert.Equal(t, host.ID, hostVerify.ID)
|
||||
}
|
||||
|
||||
func TestDeleteHost(t *testing.T) {
|
||||
ds, err := inmem.New(config.TestConfig())
|
||||
assert.Nil(t, err)
|
||||
|
|
@ -77,3 +58,42 @@ func TestDeleteHost(t *testing.T) {
|
|||
assert.Len(t, hosts, 0)
|
||||
|
||||
}
|
||||
|
||||
func TestHostDetails(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
svc := service{ds: ds}
|
||||
|
||||
host := &kolide.Host{ID: 3}
|
||||
ctx := context.Background()
|
||||
expectedLabels := []kolide.Label{
|
||||
{
|
||||
Name: "foobar",
|
||||
Description: "the foobar label",
|
||||
},
|
||||
}
|
||||
ds.ListLabelsForHostFunc = func(hid uint) ([]kolide.Label, error) {
|
||||
return expectedLabels, nil
|
||||
}
|
||||
expectedPacks := []kolide.Pack{
|
||||
{
|
||||
Name: "pack1",
|
||||
},
|
||||
{
|
||||
Name: "pack2",
|
||||
},
|
||||
}
|
||||
ds.ListPacksForHostFunc = func(hid uint) ([]*kolide.Pack, error) {
|
||||
packs := []*kolide.Pack{}
|
||||
for _, p := range expectedPacks {
|
||||
// Make pointer in inner scope
|
||||
p2 := p
|
||||
packs = append(packs, &p2)
|
||||
}
|
||||
return packs, nil
|
||||
}
|
||||
|
||||
hostDetail, err := svc.getHostDetails(ctx, host)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedLabels, hostDetail.Labels)
|
||||
assert.Equal(t, expectedPacks, hostDetail.Packs)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue