Skip to content

Commit

Permalink
tctl support for DynamicWindowsDesktop (#46988)
Browse files Browse the repository at this point in the history
* Add DynamicWindowsDesktop to proto

* Add resource matchers to Windows desktop service config

* Implement API and backend for DynamicWindowsDesktop

* tctl support for DynamicWindowsDesktop

* Fix imports

* Remove unused methods

* move rpc to separate server

* rework api and grpc more towards 153-style

* e

* remove dynamic windows from paginated resource

* add tests

* add tests

* update client

* Update api/proto/teleport/legacy/types/types.proto

Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com>

* lint

* gci

* cleanup

* cleanup

* use generic service

* cleanup

* cleanup

* cleanup

* cleanup

* cleanup

* gci

* add admin action checks

* move service

* add service test

* add tests

* gci

* review comments

* review comments

* review comments

* review comments

* review comments

* review comments

---------

Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com>
  • Loading branch information
probakowski and rosstimothy authored Oct 23, 2024
1 parent 5de0660 commit d391ccc
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 0 deletions.
29 changes: 29 additions & 0 deletions tool/tctl/common/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,35 @@ func (c *windowsDesktopCollection) writeJSON(w io.Writer) error {
return utils.WriteJSONArray(w, c.desktops)
}

type dynamicWindowsDesktopCollection struct {
desktops []types.DynamicWindowsDesktop
}

func (c *dynamicWindowsDesktopCollection) resources() (r []types.Resource) {
r = make([]types.Resource, 0, len(c.desktops))
for _, resource := range c.desktops {
r = append(r, resource)
}
return r
}

func (c *dynamicWindowsDesktopCollection) writeText(w io.Writer, verbose bool) error {
var rows [][]string
for _, d := range c.desktops {
labels := common.FormatLabels(d.GetAllLabels(), verbose)
rows = append(rows, []string{d.GetName(), d.GetAddr(), d.GetDomain(), labels})
}
headers := []string{"Name", "Address", "AD Domain", "Labels"}
var t asciitable.Table
if verbose {
t = asciitable.MakeTable(headers, rows...)
} else {
t = asciitable.MakeTableWithTruncatedColumn(headers, rows, "Labels")
}
_, err := t.AsBuffer().WriteTo(w)
return trace.Wrap(err)
}

type tokenCollection struct {
tokens []types.ProvisionToken
}
Expand Down
36 changes: 36 additions & 0 deletions tool/tctl/common/edit_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ func TestEditResources(t *testing.T) {
kind: types.KindAutoUpdateVersion,
edit: testEditAutoUpdateVersion,
},
{
kind: types.KindDynamicWindowsDesktop,
edit: testEditDynamicWindowsDesktop,
},
}

for _, test := range tests {
Expand Down Expand Up @@ -635,3 +639,35 @@ func testEditAutoUpdateVersion(t *testing.T, clt *authclient.Client) {
"tools_autoupdate should have been modified by edit")
assert.Equal(t, expected.GetSpec().GetTools().GetTargetVersion(), actual.GetSpec().GetTools().GetTargetVersion())
}

func testEditDynamicWindowsDesktop(t *testing.T, clt *authclient.Client) {
ctx := context.Background()

expected, err := types.NewDynamicWindowsDesktopV1("test", nil, types.DynamicWindowsDesktopSpecV1{
Addr: "test",
})
require.NoError(t, err)
created, err := clt.DynamicDesktopClient().CreateDynamicWindowsDesktop(ctx, expected)
require.NoError(t, err)

editor := func(name string) error {
f, err := os.Create(name)
if err != nil {
return trace.Wrap(err, "opening file to edit")
}

expected.SetRevision(created.GetRevision())
expected.Spec.Addr = "test2"

collection := &dynamicWindowsDesktopCollection{desktops: []types.DynamicWindowsDesktop{expected}}
return trace.NewAggregate(writeYAML(collection, f), f.Close())
}

_, err = runEditCommand(t, clt, []string{"edit", "dynamic_windows_desktop/test"}, withEditor(editor))
require.NoError(t, err)

actual, err := clt.DynamicDesktopClient().GetDynamicWindowsDesktop(ctx, expected.GetName())
require.NoError(t, err)
expected.SetRevision(actual.GetRevision())
require.Empty(t, cmp.Diff(expected, actual, protocmp.Transform()))
}
81 changes: 81 additions & 0 deletions tool/tctl/common/resource_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *servicec
types.KindOktaImportRule: rc.createOktaImportRule,
types.KindIntegration: rc.createIntegration,
types.KindWindowsDesktop: rc.createWindowsDesktop,
types.KindDynamicWindowsDesktop: rc.createDynamicWindowsDesktop,
types.KindAccessList: rc.createAccessList,
types.KindDiscoveryConfig: rc.createDiscoveryConfig,
types.KindAuditQuery: rc.createAuditQuery,
Expand Down Expand Up @@ -193,6 +194,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *servicec
types.KindUserTask: rc.updateUserTask,
types.KindAutoUpdateConfig: rc.updateAutoUpdateConfig,
types.KindAutoUpdateVersion: rc.updateAutoUpdateVersion,
types.KindDynamicWindowsDesktop: rc.updateDynamicWindowsDesktop,
}
rc.config = config

Expand Down Expand Up @@ -891,6 +893,45 @@ func (rc *ResourceCommand) createWindowsDesktop(ctx context.Context, client *aut
return nil
}

func (rc *ResourceCommand) createDynamicWindowsDesktop(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error {
wd, err := services.UnmarshalDynamicWindowsDesktop(raw.Raw)
if err != nil {
return trace.Wrap(err)
}
dynamicDesktopClient := client.DynamicDesktopClient()
if _, err := dynamicDesktopClient.CreateDynamicWindowsDesktop(ctx, wd); err != nil {
if trace.IsAlreadyExists(err) {
if !rc.force {
return trace.AlreadyExists("application %q already exists", wd.GetName())
}
if _, err := dynamicDesktopClient.UpdateDynamicWindowsDesktop(ctx, wd); err != nil {
return trace.Wrap(err)
}
fmt.Printf("dynamic windows desktop %q has been updated\n", wd.GetName())
return nil
}
return trace.Wrap(err)
}

fmt.Printf("dynamic windows desktop %q has been updated\n", wd.GetName())
return nil
}

func (rc *ResourceCommand) updateDynamicWindowsDesktop(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error {
wd, err := services.UnmarshalDynamicWindowsDesktop(raw.Raw)
if err != nil {
return trace.Wrap(err)
}

dynamicDesktopClient := client.DynamicDesktopClient()
if _, err := dynamicDesktopClient.UpdateDynamicWindowsDesktop(ctx, wd); err != nil {
return trace.Wrap(err)
}

fmt.Printf("dynamic windows desktop %q has been updated\n", wd.GetName())
return nil
}

func (rc *ResourceCommand) createAppServer(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error {
appServer, err := services.UnmarshalAppServer(raw.Raw)
if err != nil {
Expand Down Expand Up @@ -1688,6 +1729,11 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client *authclient.Client
return trace.Wrap(err)
}
fmt.Printf("windows desktop service %q has been deleted\n", rc.ref.Name)
case types.KindDynamicWindowsDesktop:
if err = client.DynamicDesktopClient().DeleteDynamicWindowsDesktop(ctx, rc.ref.Name); err != nil {
return trace.Wrap(err)
}
fmt.Printf("dynamic windows desktop %q has been deleted\n", rc.ref.Name)
case types.KindWindowsDesktop:
desktops, err := client.GetWindowsDesktops(ctx,
types.WindowsDesktopFilter{Name: rc.ref.Name})
Expand Down Expand Up @@ -2461,6 +2507,41 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *authclient
return nil, trace.NotFound("Windows desktop %q not found", rc.ref.Name)
}
return &windowsDesktopCollection{desktops: out}, nil
case types.KindDynamicWindowsDesktop:
dynamicDesktopClient := client.DynamicDesktopClient()
if rc.ref.Name != "" {
desktop, err := dynamicDesktopClient.GetDynamicWindowsDesktop(ctx, rc.ref.Name)
if err != nil {
return nil, trace.Wrap(err)
}
return &dynamicWindowsDesktopCollection{
desktops: []types.DynamicWindowsDesktop{desktop},
}, nil
}

pageToken := ""
desktops := make([]types.DynamicWindowsDesktop, 0, 100)
for {
d, next, err := dynamicDesktopClient.ListDynamicWindowsDesktop(ctx, 100, pageToken)
if err != nil {
return nil, trace.Wrap(err)
}
if rc.ref.Name == "" {
desktops = append(desktops, d...)
} else {
for _, desktop := range desktops {
if desktop.GetName() == rc.ref.Name {
desktops = append(desktops, desktop)
}
}
}
pageToken = next
if next == "" {
break
}
}

return &dynamicWindowsDesktopCollection{desktops}, nil
case types.KindToken:
if rc.ref.Name == "" {
tokens, err := client.GetTokens(ctx)
Expand Down
33 changes: 33 additions & 0 deletions tool/tctl/common/resource_command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1419,6 +1419,10 @@ func TestCreateResources(t *testing.T) {
kind: types.KindAutoUpdateVersion,
create: testCreateAutoUpdateVersion,
},
{
kind: types.KindDynamicWindowsDesktop,
create: testCreateDynamicWindowsDesktop,
},
}

for _, test := range tests {
Expand Down Expand Up @@ -2359,6 +2363,35 @@ version: v1
))
}

func testCreateDynamicWindowsDesktop(t *testing.T, clt *authclient.Client) {
const resourceYAML = `kind: dynamic_windows_desktop
metadata:
name: test
revision: 3a43b44a-201e-4d7f-aef1-ae2f6d9811ed
spec:
addr: test
version: v1
`

// Create the resource.
resourceYAMLPath := filepath.Join(t.TempDir(), "resource.yaml")
require.NoError(t, os.WriteFile(resourceYAMLPath, []byte(resourceYAML), 0644))
_, err := runResourceCommand(t, clt, []string{"create", resourceYAMLPath})
require.NoError(t, err)

// Get the resource
buf, err := runResourceCommand(t, clt, []string{"get", types.KindDynamicWindowsDesktop, "--format=json"})
require.NoError(t, err)
resources := mustDecodeJSON[[]types.DynamicWindowsDesktopV1](t, buf)
require.Len(t, resources, 1)

var expected types.DynamicWindowsDesktopV1
require.NoError(t, yaml.Unmarshal([]byte(resourceYAML), &expected))
expected.SetRevision(resources[0].GetRevision())

require.Empty(t, cmp.Diff([]types.DynamicWindowsDesktopV1{expected}, resources, protocmp.Transform()))
}

func TestPluginResourceWrapper(t *testing.T) {
tests := []struct {
name string
Expand Down

0 comments on commit d391ccc

Please sign in to comment.