Skip to content

Commit

Permalink
Add KindWindowsDesktops to ListResources (#10769)
Browse files Browse the repository at this point in the history
* Also add windows desktops sorter and its type converters
* Use forked vulcand/predicate library: allows traversing
  by embedded fields

Part of RFD 55
  • Loading branch information
kimlisa authored Mar 7, 2022
1 parent f314e59 commit 632d851
Show file tree
Hide file tree
Showing 30 changed files with 1,358 additions and 474 deletions.
2 changes: 2 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2409,6 +2409,8 @@ func (c *Client) ListResources(ctx context.Context, req proto.ListResourcesReque
resources[i] = respResource.GetNode()
case types.KindKubeService:
resources[i] = respResource.GetKubeService()
case types.KindWindowsDesktop:
resources[i] = respResource.GetWindowsDesktop()
default:
return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType)
}
Expand Down
29 changes: 29 additions & 0 deletions api/client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@ func (m *mockServer) ListResources(ctx context.Context, req *proto.ListResources
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_KubeService{KubeService: srv}}
case types.KindWindowsDesktop:
desktop, ok := resource.(*types.WindowsDesktopV3)
if !ok {
return nil, trace.Errorf("windows desktop has invalid type %T", resource)
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_WindowsDesktop{WindowsDesktop: desktop}}
}

resp.Resources = append(resp.Resources, protoResource)
Expand Down Expand Up @@ -226,6 +233,21 @@ func testResources(resourceType, namespace string) ([]types.ResourceWithLabels,
return nil, trace.Wrap(err)
}
}
case types.KindWindowsDesktop:
for i := 0; i < size; i++ {
var err error
name := fmt.Sprintf("windows-desktop-%d", i)
resources[i], err = types.NewWindowsDesktopV3(
name,
map[string]string{"label": string(make([]byte, labelSize))},
types.WindowsDesktopSpecV3{
Addr: "_",
HostID: "_",
})
if err != nil {
return nil, trace.Wrap(err)
}
}

default:
return nil, trace.Errorf("unsupported resource type %s", resourceType)
Expand Down Expand Up @@ -479,6 +501,10 @@ func TestListResources(t *testing.T) {
resourceType: types.KindKubeService,
resourceStruct: &types.ServerV2{},
},
"WindowsDesktop": {
resourceType: types.KindWindowsDesktop,
resourceStruct: &types.WindowsDesktopV3{},
},
}

// Create client
Expand Down Expand Up @@ -558,6 +584,9 @@ func TestGetResources(t *testing.T) {
"KubeService": {
resourceType: types.KindKubeService,
},
"WindowsDesktop": {
resourceType: types.KindWindowsDesktop,
},
}

for name, test := range testCases {
Expand Down
1,029 changes: 583 additions & 446 deletions api/client/proto/authservice.pb.go

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions api/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1457,6 +1457,9 @@ message PaginatedResource {
types.ServerV2 Node = 3 [ (gogoproto.jsontag) = "node,omitempty" ];
// KubeService represents a KubernetesService resource.
types.ServerV2 KubeService = 4 [ (gogoproto.jsontag) = "kube_service,omitempty" ];
// WindowsDesktop represents a WindowsDesktop resource.
types.WindowsDesktopV3 WindowsDesktop = 5
[ (gogoproto.jsontag) = "windows_desktop,omitempty" ];
}
}

Expand Down Expand Up @@ -1485,6 +1488,9 @@ message ListResourcesRequest {
// NeedTotalCount indicates whether or not the caller also wants the total number of resources
// after filtering.
bool NeedTotalCount = 9 [ (gogoproto.jsontag) = "need_total_count,omitempty" ];
// WindowsDesktopFilter specifies windows desktop specific filters.
types.WindowsDesktopFilter WindowsDesktopFilter = 10
[ (gogoproto.nullable) = false, (gogoproto.jsontag) = "windows_desktop_filter,omitempty" ];
}

// ListResourceResponse response of ListResources.
Expand Down
4 changes: 0 additions & 4 deletions api/types/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,6 @@ func TestAppServerSorter(t *testing.T) {
for _, c := range cases {
c := c
t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) {
t.Parallel()

sortBy := SortBy{Field: c.fieldName, IsDesc: true}
servers := AppServers(makeServers(testValsUnordered, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy))
Expand All @@ -152,8 +150,6 @@ func TestAppServerSorter(t *testing.T) {
})

t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) {
t.Parallel()

sortBy := SortBy{Field: c.fieldName}
servers := AppServers(makeServers(testValsUnordered, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy))
Expand Down
9 changes: 8 additions & 1 deletion api/types/appserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,14 @@ func (s AppServers) Len() int { return len(s) }

// Less compares app servers by name and host ID.
func (s AppServers) Less(i, j int) bool {
return s[i].GetName() < s[j].GetName() && s[i].GetHostID() < s[j].GetHostID()
switch {
case s[i].GetName() < s[j].GetName():
return true
case s[i].GetName() > s[j].GetName():
return false
default:
return s[i].GetHostID() < s[j].GetHostID()
}
}

// Swap swaps two app servers.
Expand Down
9 changes: 8 additions & 1 deletion api/types/databaseserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,14 @@ func (s DatabaseServers) Len() int { return len(s) }

// Less compares database servers by name and host ID.
func (s DatabaseServers) Less(i, j int) bool {
return s[i].GetName() < s[j].GetName() && s[i].GetHostID() < s[j].GetHostID()
switch {
case s[i].GetName() < s[j].GetName():
return true
case s[i].GetName() > s[j].GetName():
return false
default:
return s[i].GetHostID() < s[j].GetHostID()
}
}

// Swap swaps two database servers.
Expand Down
4 changes: 0 additions & 4 deletions api/types/databaseserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,6 @@ func TestDatabaseServerSorter(t *testing.T) {
for _, c := range cases {
c := c
t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) {
t.Parallel()

sortBy := SortBy{Field: c.fieldName, IsDesc: true}
servers := DatabaseServers(makeServers(testValsUnordered, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy))
Expand All @@ -165,8 +163,6 @@ func TestDatabaseServerSorter(t *testing.T) {
})

t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) {
t.Parallel()

sortBy := SortBy{Field: c.fieldName}
servers := DatabaseServers(makeServers(testValsUnordered, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy))
Expand Down
90 changes: 90 additions & 0 deletions api/types/desktop.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ limitations under the License.
package types

import (
"sort"

"github.com/gravitational/teleport/api/utils"
"github.com/gravitational/trace"
)
Expand Down Expand Up @@ -218,3 +220,91 @@ func (f *WindowsDesktopFilter) Match(req WindowsDesktop) bool {
}
return true
}

// WindowsDesktops represents a list of windows desktops.
type WindowsDesktops []WindowsDesktop

// Len returns the slice length.
func (s WindowsDesktops) Len() int { return len(s) }

// Less compares desktops by name and host ID.
func (s WindowsDesktops) Less(i, j int) bool {
switch {
case s[i].GetName() < s[j].GetName():
return true
case s[i].GetName() > s[j].GetName():
return false
default:
return s[i].GetHostID() < s[j].GetHostID()
}
}

// Swap swaps two windows desktops.
func (s WindowsDesktops) Swap(i, j int) { s[i], s[j] = s[j], s[i] }

// SortByCustom custom sorts by given sort criteria.
func (s WindowsDesktops) SortByCustom(sortBy SortBy) error {
if sortBy.Field == "" {
return nil
}

isDesc := sortBy.IsDesc
switch sortBy.Field {
case ResourceMetadataName:
sort.SliceStable(s, func(i, j int) bool {
return stringCompare(s[i].GetName(), s[j].GetName(), isDesc)
})
case ResourceSpecAddr:
sort.SliceStable(s, func(i, j int) bool {
return stringCompare(s[i].GetAddr(), s[j].GetAddr(), isDesc)
})
default:
return trace.NotImplemented("sorting by field %q for resource %q is not supported", sortBy.Field, KindWindowsDesktop)
}

return nil
}

// AsResources returns windows desktops as type resources with labels.
func (s WindowsDesktops) AsResources() []ResourceWithLabels {
resources := make([]ResourceWithLabels, 0, len(s))
for _, server := range s {
resources = append(resources, ResourceWithLabels(server))
}
return resources
}

// GetFieldVals returns list of select field values.
func (s WindowsDesktops) GetFieldVals(field string) ([]string, error) {
vals := make([]string, 0, len(s))
switch field {
case ResourceMetadataName:
for _, server := range s {
vals = append(vals, server.GetName())
}
case ResourceSpecAddr:
for _, server := range s {
vals = append(vals, server.GetAddr())
}
default:
return nil, trace.NotImplemented("getting field %q for resource %q is not supported", field, KindWindowsDesktop)
}

return vals, nil
}

// ListWindowsDesktopsResponse is a response type to ListWindowsDesktops.
type ListWindowsDesktopsResponse struct {
Desktops []WindowsDesktop
NextKey string
}

// ListWindowsDesktopsRequest is a request type to ListWindowsDesktops.
type ListWindowsDesktopsRequest struct {
WindowsDesktopFilter
Limit int
StartKey, PredicateExpression string
Labels map[string]string
SearchKeywords []string
SortBy SortBy
}
88 changes: 88 additions & 0 deletions api/types/desktop_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
Copyright 2022 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package types

import (
"fmt"
"testing"

"github.com/gravitational/trace"
"github.com/stretchr/testify/require"
)

func TestWindowsDesktopsSorter(t *testing.T) {
t.Parallel()

testValsUnordered := []string{"d", "b", "a", "c"}

makeDesktops := func(testVals []string, testField string) []WindowsDesktop {
desktops := make([]WindowsDesktop, len(testVals))
for i := 0; i < len(testVals); i++ {
testVal := testVals[i]
var err error
desktops[i], err = NewWindowsDesktopV3(
getTestVal(testField == ResourceMetadataName, testVal),
nil,
WindowsDesktopSpecV3{
Addr: getTestVal(testField == ResourceSpecAddr, testVal),
})

require.NoError(t, err)
}
return desktops
}

cases := []struct {
name string
fieldName string
}{
{
name: "by name",
fieldName: ResourceMetadataName,
},
{
name: "by addr",
fieldName: ResourceSpecAddr,
},
}

for _, c := range cases {
c := c
t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) {
sortBy := SortBy{Field: c.fieldName, IsDesc: true}
servers := WindowsDesktops(makeDesktops(testValsUnordered, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy))
targetVals, err := servers.GetFieldVals(c.fieldName)
require.NoError(t, err)
require.IsDecreasing(t, targetVals)
})

t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) {
sortBy := SortBy{Field: c.fieldName}
servers := WindowsDesktops(makeDesktops(testValsUnordered, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy))
targetVals, err := servers.GetFieldVals(c.fieldName)
require.NoError(t, err)
require.IsIncreasing(t, targetVals)
})
}

// Test error.
sortBy := SortBy{Field: "unsupported"}
desktops := makeDesktops(testValsUnordered, "does-not-matter")
require.True(t, trace.IsNotImplemented(WindowsDesktops(desktops).SortByCustom(sortBy)))
}
13 changes: 13 additions & 0 deletions api/types/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,19 @@ func (r ResourcesWithLabels) AsDatabaseServers() ([]DatabaseServer, error) {
return dbs, nil
}

// AsWindowsDesktops converts each resource into type WindowsDesktop.
func (r ResourcesWithLabels) AsWindowsDesktops() ([]WindowsDesktop, error) {
desktops := make([]WindowsDesktop, 0, len(r))
for _, resource := range r {
desktop, ok := resource.(WindowsDesktop)
if !ok {
return nil, trace.BadParameter("expected types.WindowsDesktop, got: %T", resource)
}
desktops = append(desktops, desktop)
}
return desktops, nil
}

// GetVersion returns resource version
func (h *ResourceHeader) GetVersion() string {
return h.Version
Expand Down
4 changes: 0 additions & 4 deletions api/types/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@ func TestServerSorter(t *testing.T) {
for _, c := range cases {
c := c
t.Run(fmt.Sprintf("%s desc", c.name), func(t *testing.T) {
t.Parallel()

sortBy := SortBy{Field: c.fieldName, IsDesc: true}
servers := Servers(makeServers(testValsUnordered, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy))
Expand All @@ -87,8 +85,6 @@ func TestServerSorter(t *testing.T) {
})

t.Run(fmt.Sprintf("%s asc", c.name), func(t *testing.T) {
t.Parallel()

sortBy := SortBy{Field: c.fieldName}
servers := Servers(makeServers(testValsUnordered, c.fieldName))
require.NoError(t, servers.SortByCustom(sortBy))
Expand Down
5 changes: 4 additions & 1 deletion api/types/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 632d851

Please sign in to comment.