mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
Refactor GetPack to new endpoint pattern (#2409)
This commit is contained in:
parent
7f3d3ad96c
commit
fce3e42abb
8 changed files with 173 additions and 75 deletions
|
|
@ -47,41 +47,6 @@ func packResponseForPack(ctx context.Context, svc fleet.Service, pack fleet.Pack
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Get Pack
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
type getPackRequest struct {
|
|
||||||
ID uint
|
|
||||||
}
|
|
||||||
|
|
||||||
type getPackResponse struct {
|
|
||||||
Pack packResponse `json:"pack,omitempty"`
|
|
||||||
Err error `json:"error,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r getPackResponse) error() error { return r.Err }
|
|
||||||
|
|
||||||
func makeGetPackEndpoint(svc fleet.Service) endpoint.Endpoint {
|
|
||||||
return func(ctx context.Context, request interface{}) (interface{}, error) {
|
|
||||||
req := request.(getPackRequest)
|
|
||||||
|
|
||||||
pack, err := svc.GetPack(ctx, req.ID)
|
|
||||||
if err != nil {
|
|
||||||
return getPackResponse{Err: err}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := packResponseForPack(ctx, svc, *pack)
|
|
||||||
if err != nil {
|
|
||||||
return getPackResponse{Err: err}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return getPackResponse{
|
|
||||||
Pack: *resp,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// List Packs
|
// List Packs
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,6 @@ type FleetEndpoints struct {
|
||||||
CreateDistributedQueryCampaignByNames endpoint.Endpoint
|
CreateDistributedQueryCampaignByNames endpoint.Endpoint
|
||||||
CreatePack endpoint.Endpoint
|
CreatePack endpoint.Endpoint
|
||||||
ModifyPack endpoint.Endpoint
|
ModifyPack endpoint.Endpoint
|
||||||
GetPack endpoint.Endpoint
|
|
||||||
ListPacks endpoint.Endpoint
|
ListPacks endpoint.Endpoint
|
||||||
DeletePack endpoint.Endpoint
|
DeletePack endpoint.Endpoint
|
||||||
DeletePackByID endpoint.Endpoint
|
DeletePackByID endpoint.Endpoint
|
||||||
|
|
@ -184,7 +183,6 @@ func MakeFleetServerEndpoints(svc fleet.Service, urlPrefix string, limitStore th
|
||||||
CreateDistributedQueryCampaignByNames: authenticatedUser(svc, makeCreateDistributedQueryCampaignByNamesEndpoint(svc)),
|
CreateDistributedQueryCampaignByNames: authenticatedUser(svc, makeCreateDistributedQueryCampaignByNamesEndpoint(svc)),
|
||||||
CreatePack: authenticatedUser(svc, makeCreatePackEndpoint(svc)),
|
CreatePack: authenticatedUser(svc, makeCreatePackEndpoint(svc)),
|
||||||
ModifyPack: authenticatedUser(svc, makeModifyPackEndpoint(svc)),
|
ModifyPack: authenticatedUser(svc, makeModifyPackEndpoint(svc)),
|
||||||
GetPack: authenticatedUser(svc, makeGetPackEndpoint(svc)),
|
|
||||||
ListPacks: authenticatedUser(svc, makeListPacksEndpoint(svc)),
|
ListPacks: authenticatedUser(svc, makeListPacksEndpoint(svc)),
|
||||||
DeletePack: authenticatedUser(svc, makeDeletePackEndpoint(svc)),
|
DeletePack: authenticatedUser(svc, makeDeletePackEndpoint(svc)),
|
||||||
DeletePackByID: authenticatedUser(svc, makeDeletePackByIDEndpoint(svc)),
|
DeletePackByID: authenticatedUser(svc, makeDeletePackByIDEndpoint(svc)),
|
||||||
|
|
@ -295,7 +293,6 @@ type fleetHandlers struct {
|
||||||
CreateDistributedQueryCampaignByNames http.Handler
|
CreateDistributedQueryCampaignByNames http.Handler
|
||||||
CreatePack http.Handler
|
CreatePack http.Handler
|
||||||
ModifyPack http.Handler
|
ModifyPack http.Handler
|
||||||
GetPack http.Handler
|
|
||||||
ListPacks http.Handler
|
ListPacks http.Handler
|
||||||
DeletePack http.Handler
|
DeletePack http.Handler
|
||||||
DeletePackByID http.Handler
|
DeletePackByID http.Handler
|
||||||
|
|
@ -405,7 +402,6 @@ func makeKitHandlers(e FleetEndpoints, opts []kithttp.ServerOption) *fleetHandle
|
||||||
CreateDistributedQueryCampaignByNames: newServer(e.CreateDistributedQueryCampaignByNames, decodeCreateDistributedQueryCampaignByNamesRequest),
|
CreateDistributedQueryCampaignByNames: newServer(e.CreateDistributedQueryCampaignByNames, decodeCreateDistributedQueryCampaignByNamesRequest),
|
||||||
CreatePack: newServer(e.CreatePack, decodeCreatePackRequest),
|
CreatePack: newServer(e.CreatePack, decodeCreatePackRequest),
|
||||||
ModifyPack: newServer(e.ModifyPack, decodeModifyPackRequest),
|
ModifyPack: newServer(e.ModifyPack, decodeModifyPackRequest),
|
||||||
GetPack: newServer(e.GetPack, decodeGetPackRequest),
|
|
||||||
ListPacks: newServer(e.ListPacks, decodeListPacksRequest),
|
ListPacks: newServer(e.ListPacks, decodeListPacksRequest),
|
||||||
DeletePack: newServer(e.DeletePack, decodeDeletePackRequest),
|
DeletePack: newServer(e.DeletePack, decodeDeletePackRequest),
|
||||||
DeletePackByID: newServer(e.DeletePackByID, decodeDeletePackByIDRequest),
|
DeletePackByID: newServer(e.DeletePackByID, decodeDeletePackByIDRequest),
|
||||||
|
|
@ -615,7 +611,6 @@ func attachFleetAPIRoutes(r *mux.Router, h *fleetHandlers) {
|
||||||
|
|
||||||
r.Handle("/api/v1/fleet/packs", h.CreatePack).Methods("POST").Name("create_pack")
|
r.Handle("/api/v1/fleet/packs", h.CreatePack).Methods("POST").Name("create_pack")
|
||||||
r.Handle("/api/v1/fleet/packs/{id}", h.ModifyPack).Methods("PATCH").Name("modify_pack")
|
r.Handle("/api/v1/fleet/packs/{id}", h.ModifyPack).Methods("PATCH").Name("modify_pack")
|
||||||
r.Handle("/api/v1/fleet/packs/{id}", h.GetPack).Methods("GET").Name("get_pack")
|
|
||||||
r.Handle("/api/v1/fleet/packs", h.ListPacks).Methods("GET").Name("list_packs")
|
r.Handle("/api/v1/fleet/packs", h.ListPacks).Methods("GET").Name("list_packs")
|
||||||
r.Handle("/api/v1/fleet/packs/{name}", h.DeletePack).Methods("DELETE").Name("delete_pack")
|
r.Handle("/api/v1/fleet/packs/{name}", h.DeletePack).Methods("DELETE").Name("delete_pack")
|
||||||
r.Handle("/api/v1/fleet/packs/id/{id}", h.DeletePackByID).Methods("DELETE").Name("delete_pack_by_id")
|
r.Handle("/api/v1/fleet/packs/id/{id}", h.DeletePackByID).Methods("DELETE").Name("delete_pack_by_id")
|
||||||
|
|
@ -719,6 +714,8 @@ func attachNewStyleFleetAPIRoutes(r *mux.Router, svc fleet.Service, opts []kitht
|
||||||
e.GET("/api/v1/fleet/teams/{team_id}/policies/{policy_id}", getTeamPolicyByIDEndpoint, getTeamPolicyByIDRequest{})
|
e.GET("/api/v1/fleet/teams/{team_id}/policies/{policy_id}", getTeamPolicyByIDEndpoint, getTeamPolicyByIDRequest{})
|
||||||
e.POST("/api/v1/fleet/teams/{team_id}/policies/delete", deleteTeamPoliciesEndpoint, deleteTeamPoliciesRequest{})
|
e.POST("/api/v1/fleet/teams/{team_id}/policies/delete", deleteTeamPoliciesEndpoint, deleteTeamPoliciesRequest{})
|
||||||
|
|
||||||
|
e.GET("/api/v1/fleet/packs/{id:[0-9]+}", getPackEndpoint, getPackRequest{})
|
||||||
|
|
||||||
e.GET("/api/v1/fleet/software", listSoftwareEndpoint, listSoftwareRequest{})
|
e.GET("/api/v1/fleet/software", listSoftwareEndpoint, listSoftwareRequest{})
|
||||||
|
|
||||||
e.POST("/api/v1/fleet/hosts/delete", deleteHostsEndpoint, deleteHostsRequest{})
|
e.POST("/api/v1/fleet/hosts/delete", deleteHostsEndpoint, deleteHostsRequest{})
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,18 @@ package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fleetdm/fleet/v4/server/config"
|
||||||
"github.com/fleetdm/fleet/v4/server/mock"
|
"github.com/fleetdm/fleet/v4/server/mock"
|
||||||
kitlog "github.com/go-kit/kit/log"
|
kitlog "github.com/go-kit/kit/log"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/throttled/throttled/v2/store/memstore"
|
"github.com/throttled/throttled/v2/store/memstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -109,10 +114,6 @@ func TestAPIRoutes(t *testing.T) {
|
||||||
verb: "POST",
|
verb: "POST",
|
||||||
uri: "/api/v1/fleet/queries/run",
|
uri: "/api/v1/fleet/queries/run",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
verb: "GET",
|
|
||||||
uri: "/api/v1/fleet/packs/1",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
verb: "GET",
|
verb: "GET",
|
||||||
uri: "/api/v1/fleet/packs",
|
uri: "/api/v1/fleet/packs",
|
||||||
|
|
@ -180,10 +181,6 @@ func TestAPIRoutes(t *testing.T) {
|
||||||
verb: "DELETE",
|
verb: "DELETE",
|
||||||
uri: "/api/v1/fleet/labels/1",
|
uri: "/api/v1/fleet/labels/1",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
verb: "GET",
|
|
||||||
uri: "/api/v1/fleet/hosts/1",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
verb: "GET",
|
verb: "GET",
|
||||||
uri: "/api/v1/fleet/hosts",
|
uri: "/api/v1/fleet/hosts",
|
||||||
|
|
@ -206,6 +203,77 @@ func TestAPIRoutes(t *testing.T) {
|
||||||
httptest.NewRequest(route.verb, route.uri, nil),
|
httptest.NewRequest(route.verb, route.uri, nil),
|
||||||
)
|
)
|
||||||
assert.NotEqual(st, 404, recorder.Code)
|
assert.NotEqual(st, 404, recorder.Code)
|
||||||
|
assert.NotEqual(st, 405, recorder.Code, route.verb) // if it matches a path but with wrong verb
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIRoutesConflicts(t *testing.T) {
|
||||||
|
ds := new(mock.Store)
|
||||||
|
|
||||||
|
svc := newTestService(ds, nil, nil)
|
||||||
|
limitStore, _ := memstore.New(0)
|
||||||
|
h := MakeHandler(svc, config.TestConfig(), kitlog.NewNopLogger(), limitStore)
|
||||||
|
router := h.(*mux.Router)
|
||||||
|
|
||||||
|
type testCase struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
verb string
|
||||||
|
want int
|
||||||
|
}
|
||||||
|
var cases []testCase
|
||||||
|
|
||||||
|
// build the test cases: for each route, generate a request designed to match
|
||||||
|
// it, and override its handler to return a unique status code. If the
|
||||||
|
// request doesn't result in that status code, then some other route
|
||||||
|
// conflicts with it and took precedence - a route conflict. The route's name
|
||||||
|
// is used to name the sub-test for that route.
|
||||||
|
status := 200
|
||||||
|
reSimpleVar, reNumVar := regexp.MustCompile(`\{(\w+)\}`), regexp.MustCompile(`\{\w+:[^\}]+\}`)
|
||||||
|
err := router.Walk(func(route *mux.Route, router *mux.Router, ancestores []*mux.Route) error {
|
||||||
|
name := route.GetName()
|
||||||
|
path, err := route.GetPathTemplate()
|
||||||
|
if err != nil {
|
||||||
|
// all our routes should have paths
|
||||||
|
return errors.Wrap(err, name)
|
||||||
|
}
|
||||||
|
meths, err := route.GetMethods()
|
||||||
|
if err != nil || len(meths) == 0 {
|
||||||
|
// only route without method is distributed_query_results (websocket)
|
||||||
|
if name != "distributed_query_results" {
|
||||||
|
return errors.Wrap(err, name+" "+path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
path = reSimpleVar.ReplaceAllString(path, "$1")
|
||||||
|
// for now at least, the only times we use regexp-constrained vars is
|
||||||
|
// for numeric arguments.
|
||||||
|
path = reNumVar.ReplaceAllString(path, "1")
|
||||||
|
|
||||||
|
routeStatus := status
|
||||||
|
route.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(routeStatus) })
|
||||||
|
for _, meth := range meths {
|
||||||
|
cases = append(cases, testCase{
|
||||||
|
name: name,
|
||||||
|
path: path,
|
||||||
|
verb: meth,
|
||||||
|
want: status,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
status++
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
t.Log(c.verb, c.path)
|
||||||
|
req := httptest.NewRequest(c.verb, c.path, nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(rr, req)
|
||||||
|
require.Equal(t, c.want, rr.Code)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -563,3 +563,19 @@ func (s *integrationTestSuite) TestCountSoftware() {
|
||||||
)
|
)
|
||||||
assert.Equal(t, 1, resp.Count)
|
assert.Equal(t, 1, resp.Count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *integrationTestSuite) TestGetPack() {
|
||||||
|
t := s.T()
|
||||||
|
|
||||||
|
pack := &fleet.Pack{
|
||||||
|
Name: t.Name(),
|
||||||
|
}
|
||||||
|
pack, err := s.ds.NewPack(context.Background(), pack)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var packResp getPackResponse
|
||||||
|
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/packs/%d", pack.ID), nil, http.StatusOK, &packResp)
|
||||||
|
require.Equal(t, packResp.Pack.ID, pack.ID)
|
||||||
|
|
||||||
|
s.Do("GET", fmt.Sprintf("/api/v1/fleet/packs/%d", pack.ID+1), nil, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
|
||||||
47
server/service/packs.go
Normal file
47
server/service/packs.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||||
|
)
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Get Pack
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
type getPackRequest struct {
|
||||||
|
ID uint `url:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type getPackResponse struct {
|
||||||
|
Pack packResponse `json:"pack,omitempty"`
|
||||||
|
Err error `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r getPackResponse) error() error { return r.Err }
|
||||||
|
|
||||||
|
func getPackEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (interface{}, error) {
|
||||||
|
req := request.(*getPackRequest)
|
||||||
|
pack, err := svc.GetPack(ctx, req.ID)
|
||||||
|
if err != nil {
|
||||||
|
return getPackResponse{Err: err}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := packResponseForPack(ctx, svc, *pack)
|
||||||
|
if err != nil {
|
||||||
|
return getPackResponse{Err: err}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPackResponse{
|
||||||
|
Pack: *resp,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (svc *Service) GetPack(ctx context.Context, id uint) (*fleet.Pack, error) {
|
||||||
|
if err := svc.authz.Authorize(ctx, &fleet.Pack{}, fleet.ActionRead); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc.ds.Pack(ctx, id)
|
||||||
|
}
|
||||||
32
server/service/packs_test.go
Normal file
32
server/service/packs_test.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fleetdm/fleet/v4/server/authz"
|
||||||
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||||
|
"github.com/fleetdm/fleet/v4/server/mock"
|
||||||
|
"github.com/fleetdm/fleet/v4/server/test"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetPack(t *testing.T) {
|
||||||
|
ds := new(mock.Store)
|
||||||
|
svc := newTestService(ds, nil, nil)
|
||||||
|
|
||||||
|
ds.PackFunc = func(ctx context.Context, id uint) (*fleet.Pack, error) {
|
||||||
|
return &fleet.Pack{
|
||||||
|
ID: 1,
|
||||||
|
TeamIDs: []uint{1},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pack, err := svc.GetPack(test.UserContext(test.UserAdmin), 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, uint(1), pack.ID)
|
||||||
|
|
||||||
|
_, err = svc.GetPack(test.UserContext(test.UserNoRoles), 1)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), authz.ForbiddenErrorMessage)
|
||||||
|
}
|
||||||
|
|
@ -76,14 +76,6 @@ func (svc *Service) ListPacks(ctx context.Context, opt fleet.PackListOptions) ([
|
||||||
return svc.ds.ListPacks(ctx, opt)
|
return svc.ds.ListPacks(ctx, opt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *Service) GetPack(ctx context.Context, id uint) (*fleet.Pack, error) {
|
|
||||||
if err := svc.authz.Authorize(ctx, &fleet.Pack{}, fleet.ActionRead); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return svc.ds.Pack(ctx, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (svc *Service) NewPack(ctx context.Context, p fleet.PackPayload) (*fleet.Pack, error) {
|
func (svc *Service) NewPack(ctx context.Context, p fleet.PackPayload) (*fleet.Pack, error) {
|
||||||
if err := svc.authz.Authorize(ctx, &fleet.Pack{}, fleet.ActionWrite); err != nil {
|
if err := svc.authz.Authorize(ctx, &fleet.Pack{}, fleet.ActionWrite); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -33,25 +33,6 @@ func TestServiceListPacks(t *testing.T) {
|
||||||
assert.Len(t, queries, 1)
|
assert.Len(t, queries, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetPack(t *testing.T) {
|
|
||||||
ds := mysql.CreateMySQLDS(t)
|
|
||||||
defer ds.Close()
|
|
||||||
|
|
||||||
svc := newTestService(ds, nil, nil)
|
|
||||||
|
|
||||||
pack := &fleet.Pack{
|
|
||||||
Name: "foo",
|
|
||||||
}
|
|
||||||
_, err := ds.NewPack(context.Background(), pack)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
assert.NotZero(t, pack.ID)
|
|
||||||
|
|
||||||
packVerify, err := svc.GetPack(test.UserContext(test.UserAdmin), pack.ID)
|
|
||||||
assert.Nil(t, err)
|
|
||||||
|
|
||||||
assert.Equal(t, pack.ID, packVerify.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewSavesTargets(t *testing.T) {
|
func TestNewSavesTargets(t *testing.T) {
|
||||||
ds := new(mock.Store)
|
ds := new(mock.Store)
|
||||||
svc := newTestService(ds, nil, nil)
|
svc := newTestService(ds, nil, nil)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue