Skip to content

Commit

Permalink
Change get resources webapi response (#10598) (#10683)
Browse files Browse the repository at this point in the history
* Created a general listResourcesGetResponse as a consistent return value
* Wrapped response in ^ object for resources that were not wrapped to 
   accommodate future changes (dbs, desktops, kubes)
  • Loading branch information
kimlisa authored Mar 1, 2022
1 parent 726ed4a commit 5b93afc
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 74 deletions.
25 changes: 7 additions & 18 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) {
h.GET("/webapi/sites/:site/namespaces", h.WithClusterAuth(h.getSiteNamespaces))

// get nodes
h.GET("/webapi/sites/:site/nodes", h.WithClusterAuth(h.siteNodesGet))
h.GET("/webapi/sites/:site/nodes", h.WithClusterAuth(h.clusterNodesGet))

// Get applications.
h.GET("/webapi/sites/:site/apps", h.WithClusterAuth(h.clusterAppsGet))
Expand Down Expand Up @@ -412,7 +412,7 @@ func NewHandler(cfg Config, opts ...HandlerOption) (*APIHandler, error) {
h.GET("/webapi/apps/:fqdnHint/:clusterName/:publicAddr", h.WithAuth(h.getAppFQDN))

// Desktop access endpoints.
h.GET("/webapi/sites/:site/desktops", h.WithClusterAuth(h.getDesktopsHandle))
h.GET("/webapi/sites/:site/desktops", h.WithClusterAuth(h.clusterDesktopsGet))
h.GET("/webapi/sites/:site/desktops/:desktopName", h.WithClusterAuth(h.getDesktopHandle))
// GET /webapi/sites/:site/desktops/:desktopName/connect?access_token=<bearer_token>&username=<username>&width=<width>&height=<height>
h.GET("/webapi/sites/:site/desktops/:desktopName/connect", h.WithClusterAuth(h.desktopConnectHandle))
Expand Down Expand Up @@ -1821,12 +1821,8 @@ func (h *Handler) getSiteNamespaces(w http.ResponseWriter, r *http.Request, _ ht
}, nil
}

/* siteNodesGet returns a list of nodes for a given site and namespace
GET /v1/webapi/sites/:site/namespaces/:namespace/nodes
*/
func (h *Handler) siteNodesGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
// clusterNodesGet returns a list of nodes for a given cluster site.
func (h *Handler) clusterNodesGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
// Get a client to the Auth Server with the logged in user's identity. The
// identity of the logged in user is used to fetch the list of nodes.
clt, err := ctx.GetUserClient(site)
Expand All @@ -1838,8 +1834,9 @@ func (h *Handler) siteNodesGet(w http.ResponseWriter, r *http.Request, p httprou
return nil, trace.Wrap(err)
}

uiServers := ui.MakeServers(site.GetName(), servers)
return makeResponse(uiServers)
return listResourcesGetResponse{
Items: ui.MakeServers(site.GetName(), servers),
}, nil
}

// siteNodeConnect connect to the site node
Expand Down Expand Up @@ -2621,14 +2618,6 @@ func OK() interface{} {
return message("ok")
}

type responseData struct {
Items interface{} `json:"items"`
}

func makeResponse(items interface{}) (interface{}, error) {
return responseData{Items: items}, nil
}

// makeTeleportClientConfig creates default teleport client configuration
// that is used to initiate an SSH terminal session or SCP file transfer
func makeTeleportClientConfig(ctx context.Context, sesCtx *SessionContext) (*client.Config, error) {
Expand Down
108 changes: 64 additions & 44 deletions lib/web/apiserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -751,29 +751,37 @@ func (s *WebSuite) TestWebSessionsBadInput(c *C) {
}
}

type getSiteNodeResponse struct {
Items []ui.Server `json:"items"`
type clusterNodesGetResponse struct {
Items []ui.Server `json:"items"`
StartKey string `json:"startKey"`
TotalCount int `json:"totalCount"`
}

func (s *WebSuite) TestGetSiteNodes(c *C) {
pack := s.authPack(c, "foo")
func TestClusterNodesGet(t *testing.T) {
t.Parallel()
env := newWebPack(t, 1)
proxy := env.proxies[0]
pack := proxy.authPack(t, "test-user@example.com")
clusterName := env.server.ClusterName()

// get site nodes
re, err := pack.clt.Get(context.Background(), pack.clt.Endpoint("webapi", "sites", s.server.ClusterName(), "nodes"), url.Values{})
c.Assert(err, IsNil)
endpoint := pack.clt.Endpoint("webapi", "sites", clusterName, "nodes")

nodes := getSiteNodeResponse{}
c.Assert(json.Unmarshal(re.Bytes(), &nodes), IsNil)
c.Assert(len(nodes.Items), Equals, 1)
// Get nodes.
re, err := pack.clt.Get(context.Background(), endpoint, url.Values{})
require.NoError(t, err)

nodes := clusterNodesGetResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &nodes))
require.Len(t, nodes.Items, 1)

// get site nodes using shortcut
// Get nodes using shortcut.
re, err = pack.clt.Get(context.Background(), pack.clt.Endpoint("webapi", "sites", currentSiteShortcut, "nodes"), url.Values{})
c.Assert(err, IsNil)
require.NoError(t, err)

nodes2 := getSiteNodeResponse{}
c.Assert(json.Unmarshal(re.Bytes(), &nodes2), IsNil)
c.Assert(len(nodes.Items), Equals, 1)
c.Assert(nodes2, DeepEquals, nodes)
nodes2 := clusterNodesGetResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &nodes2))
require.Len(t, nodes.Items, 1)
require.Equal(t, nodes, nodes2)
}

func (s *WebSuite) TestSiteNodeConnectInvalidSessionID(c *C) {
Expand Down Expand Up @@ -1553,44 +1561,48 @@ func (s *WebSuite) TestCloseConnectionsOnLogout(c *C) {
}
}

func (s *WebSuite) TestCreateSession(c *C) {
pack := s.authPack(c, "foo")
func TestCreateSession(t *testing.T) {
t.Parallel()
env := newWebPack(t, 1)
proxy := env.proxies[0]
user := "test-user@example.com"
pack := proxy.authPack(t, user)

// get site nodes
re, err := pack.clt.Get(context.Background(), pack.clt.Endpoint("webapi", "sites", s.server.ClusterName(), "nodes"), url.Values{})
c.Assert(err, IsNil)
re, err := pack.clt.Get(context.Background(), pack.clt.Endpoint("webapi", "sites", env.server.ClusterName(), "nodes"), url.Values{})
require.NoError(t, err)

nodes := getSiteNodeResponse{}
c.Assert(json.Unmarshal(re.Bytes(), &nodes), IsNil)
nodes := clusterNodesGetResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &nodes))
node := nodes.Items[0]

sess := session.Session{
TerminalParams: session.TerminalParams{W: 300, H: 120},
Login: s.user,
Login: user,
}

// test using node UUID
sess.ServerID = node.Name
re, err = pack.clt.PostJSON(
context.Background(),
pack.clt.Endpoint("webapi", "sites", s.server.ClusterName(), "sessions"),
pack.clt.Endpoint("webapi", "sites", env.server.ClusterName(), "sessions"),
siteSessionGenerateReq{Session: sess},
)
c.Assert(err, IsNil)
require.NoError(t, err)

var created *siteSessionGenerateResponse
c.Assert(json.Unmarshal(re.Bytes(), &created), IsNil)
c.Assert(created.Session.ID, Not(Equals), "")
c.Assert(created.Session.ServerHostname, Equals, node.Hostname)
require.NoError(t, json.Unmarshal(re.Bytes(), &created))
require.NotEmpty(t, created.Session.ID)
require.Equal(t, node.Hostname, created.Session.ServerHostname)

// test empty serverID (older version does not supply serverID)
sess.ServerID = ""
_, err = pack.clt.PostJSON(
context.Background(),
pack.clt.Endpoint("webapi", "sites", s.server.ClusterName(), "sessions"),
pack.clt.Endpoint("webapi", "sites", env.server.ClusterName(), "sessions"),
siteSessionGenerateReq{Session: sess},
)
c.Assert(err, IsNil)
require.NoError(t, err)
}

func (s *WebSuite) TestPlayback(c *C) {
Expand Down Expand Up @@ -2170,10 +2182,14 @@ func TestClusterDatabasesGet(t *testing.T) {
re, err := pack.clt.Get(context.Background(), endpoint, url.Values{})
require.NoError(t, err)

type testResponse struct {
Items []ui.Database `json:"items"`
}

// No db registered.
dbs := []ui.Database{}
require.NoError(t, json.Unmarshal(re.Bytes(), &dbs))
require.Len(t, dbs, 0)
resp := testResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &resp))
require.Len(t, resp.Items, 0)

// Register a database.
db, err := types.NewDatabaseServerV3(types.Metadata{
Expand All @@ -2194,16 +2210,16 @@ func TestClusterDatabasesGet(t *testing.T) {
re, err = pack.clt.Get(context.Background(), endpoint, url.Values{})
require.NoError(t, err)

dbs = []ui.Database{}
require.NoError(t, json.Unmarshal(re.Bytes(), &dbs))
require.Len(t, dbs, 1)
resp = testResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &resp))
require.Len(t, resp.Items, 1)
require.EqualValues(t, ui.Database{
Name: "test-db-name",
Desc: "test-description",
Protocol: "test-protocol",
Type: types.DatabaseTypeSelfHosted,
Labels: []ui.Label{{Name: "test-field", Value: "test-value"}},
}, dbs[0])
}, resp.Items[0])
}

func TestClusterKubesGet(t *testing.T) {
Expand All @@ -2216,10 +2232,14 @@ func TestClusterKubesGet(t *testing.T) {
re, err := pack.clt.Get(context.Background(), endpoint, url.Values{})
require.NoError(t, err)

type testResponse struct {
Items []ui.Kube `json:"items"`
}

// No kube registered.
kbs := []ui.Kube{}
require.NoError(t, json.Unmarshal(re.Bytes(), &kbs))
require.Len(t, kbs, 0)
resp := testResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &resp))
require.Len(t, resp.Items, 0)

// Register a kube service.
_, err = env.server.Auth().UpsertKubeServiceV2(context.Background(), &types.ServerV2{
Expand All @@ -2245,13 +2265,13 @@ func TestClusterKubesGet(t *testing.T) {
re, err = pack.clt.Get(context.Background(), endpoint, url.Values{})
require.NoError(t, err)

kbs = []ui.Kube{}
require.NoError(t, json.Unmarshal(re.Bytes(), &kbs))
require.Len(t, kbs, 1)
resp = testResponse{}
require.NoError(t, json.Unmarshal(re.Bytes(), &resp))
require.Len(t, resp.Items, 1)
require.EqualValues(t, ui.Kube{
Name: "test-kube-name",
Labels: []ui.Label{{Name: "test-field", Value: "test-value"}},
}, kbs[0])
}, resp.Items[0])
}

// TestApplicationAccessDisabled makes sure application access can be disabled
Expand Down
16 changes: 9 additions & 7 deletions lib/web/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,15 @@ func (h *Handler) clusterAppsGet(w http.ResponseWriter, r *http.Request, p httpr
apps = append(apps, server.GetApp())
}

return makeResponse(ui.MakeApps(ui.MakeAppsConfig{
LocalClusterName: h.auth.clusterName,
LocalProxyDNSName: h.proxyDNSName(),
AppClusterName: appClusterName,
Identity: identity,
Apps: types.DeduplicateApps(apps),
}))
return listResourcesGetResponse{
Items: ui.MakeApps(ui.MakeAppsConfig{
LocalClusterName: h.auth.clusterName,
LocalProxyDNSName: h.proxyDNSName(),
AppClusterName: appClusterName,
Identity: identity,
Apps: types.DeduplicateApps(apps),
}),
}, nil
}

type GetAppFQDNRequest resolveAppParams
Expand Down
10 changes: 10 additions & 0 deletions lib/web/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,16 @@ func ExtractResourceAndValidate(yaml string) (*services.UnknownResource, error)
return &unknownRes, nil
}

type listResourcesGetResponse struct {
// Items is a list of resources retrieved.
Items interface{} `json:"items"`
// StartKey is the position to resume search events.
StartKey string `json:"startKey"`
// TotalCount is the total count of resources available
// after filter.
TotalCount int `json:"totalCount"`
}

type resourcesAPIGetter interface {
// GetRole returns role by name
GetRole(ctx context.Context, name string) (types.Role, error)
Expand Down
16 changes: 11 additions & 5 deletions lib/web/servers.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ func (h *Handler) clusterKubesGet(w http.ResponseWriter, r *http.Request, p http
return nil, trace.Wrap(err)
}

return ui.MakeKubes(h.auth.clusterName, kubeServers), nil
return listResourcesGetResponse{
Items: ui.MakeKubes(h.auth.clusterName, kubeServers),
}, nil
}

// clusterDatabasesGet returns a list of db servers in a form the UI can present.
Expand All @@ -63,11 +65,13 @@ func (h *Handler) clusterDatabasesGet(w http.ResponseWriter, r *http.Request, p
databases = append(databases, server.GetDatabase())
}

return ui.MakeDatabases(h.auth.clusterName, types.DeduplicateDatabases(databases)), nil
return listResourcesGetResponse{
Items: ui.MakeDatabases(h.auth.clusterName, types.DeduplicateDatabases(databases)),
}, nil
}

// getDesktopsHandle returns a list of desktops in a form the UI can present.
func (h *Handler) getDesktopsHandle(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
// clusterDesktopsGet returns a list of desktops in a form the UI can present.
func (h *Handler) clusterDesktopsGet(w http.ResponseWriter, r *http.Request, p httprouter.Params, ctx *SessionContext, site reversetunnel.RemoteSite) (interface{}, error) {
clt, err := ctx.GetUserClient(site)
if err != nil {
return nil, trace.Wrap(err)
Expand All @@ -79,7 +83,9 @@ func (h *Handler) getDesktopsHandle(w http.ResponseWriter, r *http.Request, p ht
}
windowsDesktops = types.DeduplicateDesktops(windowsDesktops)

return ui.MakeDesktops(windowsDesktops), nil
return listResourcesGetResponse{
Items: ui.MakeDesktops(windowsDesktops),
}, nil
}

// getDesktopHandle returns a desktop.
Expand Down

0 comments on commit 5b93afc

Please sign in to comment.