Skip to content

Commit 3437757

Browse files
committed
feat: use wildcard Host entry in config-ssh
Rather than create a separate Host entry for every workspace, configure a wildcard such as `coder.*` which can accomodate all of a user's workspaces.
1 parent 20c36a6 commit 3437757

File tree

2 files changed

+192
-431
lines changed

2 files changed

+192
-431
lines changed

cli/configssh.go

Lines changed: 75 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package cli
33
import (
44
"bufio"
55
"bytes"
6-
"context"
76
"errors"
87
"fmt"
98
"io"
@@ -12,7 +11,6 @@ import (
1211
"os"
1312
"path/filepath"
1413
"runtime"
15-
"sort"
1614
"strconv"
1715
"strings"
1816

@@ -22,11 +20,9 @@ import (
2220
"github.com/pkg/diff/write"
2321
"golang.org/x/exp/constraints"
2422
"golang.org/x/exp/slices"
25-
"golang.org/x/sync/errgroup"
2623
"golang.org/x/xerrors"
2724

2825
"github.com/coder/coder/v2/cli/cliui"
29-
"github.com/coder/coder/v2/coderd/util/slice"
3026
"github.com/coder/coder/v2/codersdk"
3127
"github.com/coder/serpent"
3228
)
@@ -139,74 +135,6 @@ func (o sshConfigOptions) asList() (list []string) {
139135
return list
140136
}
141137

142-
type sshWorkspaceConfig struct {
143-
Name string
144-
Hosts []string
145-
}
146-
147-
func sshFetchWorkspaceConfigs(ctx context.Context, client *codersdk.Client) ([]sshWorkspaceConfig, error) {
148-
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
149-
Owner: codersdk.Me,
150-
})
151-
if err != nil {
152-
return nil, err
153-
}
154-
155-
var errGroup errgroup.Group
156-
workspaceConfigs := make([]sshWorkspaceConfig, len(res.Workspaces))
157-
for i, workspace := range res.Workspaces {
158-
i := i
159-
workspace := workspace
160-
errGroup.Go(func() error {
161-
resources, err := client.TemplateVersionResources(ctx, workspace.LatestBuild.TemplateVersionID)
162-
if err != nil {
163-
return err
164-
}
165-
166-
wc := sshWorkspaceConfig{Name: workspace.Name}
167-
var agents []codersdk.WorkspaceAgent
168-
for _, resource := range resources {
169-
if resource.Transition != codersdk.WorkspaceTransitionStart {
170-
continue
171-
}
172-
agents = append(agents, resource.Agents...)
173-
}
174-
175-
// handle both WORKSPACE and WORKSPACE.AGENT syntax
176-
if len(agents) == 1 {
177-
wc.Hosts = append(wc.Hosts, workspace.Name)
178-
}
179-
for _, agent := range agents {
180-
hostname := workspace.Name + "." + agent.Name
181-
wc.Hosts = append(wc.Hosts, hostname)
182-
}
183-
184-
workspaceConfigs[i] = wc
185-
186-
return nil
187-
})
188-
}
189-
err = errGroup.Wait()
190-
if err != nil {
191-
return nil, err
192-
}
193-
194-
return workspaceConfigs, nil
195-
}
196-
197-
func sshPrepareWorkspaceConfigs(ctx context.Context, client *codersdk.Client) (receive func() ([]sshWorkspaceConfig, error)) {
198-
wcC := make(chan []sshWorkspaceConfig, 1)
199-
errC := make(chan error, 1)
200-
go func() {
201-
wc, err := sshFetchWorkspaceConfigs(ctx, client)
202-
wcC <- wc
203-
errC <- err
204-
}()
205-
return func() ([]sshWorkspaceConfig, error) {
206-
return <-wcC, <-errC
207-
}
208-
}
209-
210138
func (r *RootCmd) configSSH() *serpent.Command {
211139
var (
212140
sshConfigFile string
@@ -254,8 +182,6 @@ func (r *RootCmd) configSSH() *serpent.Command {
254182
// warning at any time.
255183
_, _ = client.BuildInfo(ctx)
256184

257-
recvWorkspaceConfigs := sshPrepareWorkspaceConfigs(ctx, client)
258-
259185
out := inv.Stdout
260186
if dryRun {
261187
// Print everything except diff to stderr so
@@ -371,11 +297,6 @@ func (r *RootCmd) configSSH() *serpent.Command {
371297
newline := len(before) > 0
372298
sshConfigWriteSectionHeader(buf, newline, sshConfigOpts)
373299

374-
workspaceConfigs, err := recvWorkspaceConfigs()
375-
if err != nil {
376-
return xerrors.Errorf("fetch workspace configs failed: %w", err)
377-
}
378-
379300
coderdConfig, err := client.SSHConfiguration(ctx)
380301
if err != nil {
381302
// If the error is 404, this deployment does not support
@@ -394,91 +315,79 @@ func (r *RootCmd) configSSH() *serpent.Command {
394315
coderdConfig.HostnamePrefix = sshConfigOpts.userHostPrefix
395316
}
396317

397-
// Ensure stable sorting of output.
398-
slices.SortFunc(workspaceConfigs, func(a, b sshWorkspaceConfig) int {
399-
return slice.Ascending(a.Name, b.Name)
400-
})
401-
for _, wc := range workspaceConfigs {
402-
sort.Strings(wc.Hosts)
403-
// Write agent configuration.
404-
for _, workspaceHostname := range wc.Hosts {
405-
sshHostname := fmt.Sprintf("%s%s", coderdConfig.HostnamePrefix, workspaceHostname)
406-
defaultOptions := []string{
407-
"HostName " + sshHostname,
408-
"ConnectTimeout=0",
409-
"StrictHostKeyChecking=no",
410-
// Without this, the "REMOTE HOST IDENTITY CHANGED"
411-
// message will appear.
412-
"UserKnownHostsFile=/dev/null",
413-
// This disables the "Warning: Permanently added 'hostname' (RSA) to the list of known hosts."
414-
// message from appearing on every SSH. This happens because we ignore the known hosts.
415-
"LogLevel ERROR",
416-
}
417-
418-
if !skipProxyCommand {
419-
rootFlags := fmt.Sprintf("--global-config %s", escapedGlobalConfig)
420-
for _, h := range sshConfigOpts.header {
421-
rootFlags += fmt.Sprintf(" --header %q", h)
422-
}
423-
if sshConfigOpts.headerCommand != "" {
424-
rootFlags += fmt.Sprintf(" --header-command %q", sshConfigOpts.headerCommand)
425-
}
426-
427-
flags := ""
428-
if sshConfigOpts.waitEnum != "auto" {
429-
flags += " --wait=" + sshConfigOpts.waitEnum
430-
}
431-
if sshConfigOpts.disableAutostart {
432-
flags += " --disable-autostart=true"
433-
}
434-
defaultOptions = append(defaultOptions, fmt.Sprintf(
435-
"ProxyCommand %s %s ssh --stdio%s %s",
436-
escapedCoderBinary, rootFlags, flags, workspaceHostname,
437-
))
438-
}
318+
// Write agent configuration.
319+
defaultOptions := []string{
320+
"ConnectTimeout=0",
321+
"StrictHostKeyChecking=no",
322+
// Without this, the "REMOTE HOST IDENTITY CHANGED"
323+
// message will appear.
324+
"UserKnownHostsFile=/dev/null",
325+
// This disables the "Warning: Permanently added 'hostname' (RSA) to the list of known hosts."
326+
// message from appearing on every SSH. This happens because we ignore the known hosts.
327+
"LogLevel ERROR",
328+
}
439329

440-
// Create a copy of the options so we can modify them.
441-
configOptions := sshConfigOpts
442-
configOptions.sshOptions = nil
443-
444-
// User options first (SSH only uses the first
445-
// option unless it can be given multiple times)
446-
for _, opt := range sshConfigOpts.sshOptions {
447-
err := configOptions.addOptions(opt)
448-
if err != nil {
449-
return xerrors.Errorf("add flag config option %q: %w", opt, err)
450-
}
451-
}
330+
if !skipProxyCommand {
331+
rootFlags := fmt.Sprintf("--global-config %s", escapedGlobalConfig)
332+
for _, h := range sshConfigOpts.header {
333+
rootFlags += fmt.Sprintf(" --header %q", h)
334+
}
335+
if sshConfigOpts.headerCommand != "" {
336+
rootFlags += fmt.Sprintf(" --header-command %q", sshConfigOpts.headerCommand)
337+
}
452338

453-
// Deployment options second, allow them to
454-
// override standard options.
455-
for k, v := range coderdConfig.SSHConfigOptions {
456-
opt := fmt.Sprintf("%s %s", k, v)
457-
err := configOptions.addOptions(opt)
458-
if err != nil {
459-
return xerrors.Errorf("add coderd config option %q: %w", opt, err)
460-
}
461-
}
339+
flags := ""
340+
if sshConfigOpts.waitEnum != "auto" {
341+
flags += " --wait=" + sshConfigOpts.waitEnum
342+
}
343+
if sshConfigOpts.disableAutostart {
344+
flags += " --disable-autostart=true"
345+
}
346+
defaultOptions = append(defaultOptions, fmt.Sprintf(
347+
"ProxyCommand %s %s ssh --stdio%s --ssh-host-prefix %s %%h",
348+
escapedCoderBinary, rootFlags, flags, coderdConfig.HostnamePrefix,
349+
))
350+
}
462351

463-
// Finally, add the standard options.
464-
err := configOptions.addOptions(defaultOptions...)
465-
if err != nil {
466-
return err
467-
}
352+
// Create a copy of the options so we can modify them.
353+
configOptions := sshConfigOpts
354+
configOptions.sshOptions = nil
468355

469-
hostBlock := []string{
470-
"Host " + sshHostname,
471-
}
472-
// Prefix with '\t'
473-
for _, v := range configOptions.sshOptions {
474-
hostBlock = append(hostBlock, "\t"+v)
475-
}
356+
// User options first (SSH only uses the first
357+
// option unless it can be given multiple times)
358+
for _, opt := range sshConfigOpts.sshOptions {
359+
err := configOptions.addOptions(opt)
360+
if err != nil {
361+
return xerrors.Errorf("add flag config option %q: %w", opt, err)
362+
}
363+
}
476364

477-
_, _ = buf.WriteString(strings.Join(hostBlock, "\n"))
478-
_ = buf.WriteByte('\n')
365+
// Deployment options second, allow them to
366+
// override standard options.
367+
for k, v := range coderdConfig.SSHConfigOptions {
368+
opt := fmt.Sprintf("%s %s", k, v)
369+
err := configOptions.addOptions(opt)
370+
if err != nil {
371+
return xerrors.Errorf("add coderd config option %q: %w", opt, err)
479372
}
480373
}
481374

375+
// Finally, add the standard options.
376+
if err := configOptions.addOptions(defaultOptions...); err != nil {
377+
return err
378+
}
379+
380+
hostBlock := []string{
381+
"Host " + coderdConfig.HostnamePrefix + "*",
382+
}
383+
// Prefix with '\t'
384+
for _, v := range configOptions.sshOptions {
385+
hostBlock = append(hostBlock, "\t"+v)
386+
}
387+
388+
_, _ = buf.WriteString(strings.Join(hostBlock, "\n"))
389+
_ = buf.WriteByte('\n')
390+
482391
sshConfigWriteSectionEnd(buf)
483392

484393
// Write the remainder of the users config file to buf.
@@ -532,9 +441,17 @@ func (r *RootCmd) configSSH() *serpent.Command {
532441
_, _ = fmt.Fprintf(out, "Updated %q\n", sshConfigFile)
533442
}
534443

535-
if len(workspaceConfigs) > 0 {
444+
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
445+
Owner: codersdk.Me,
446+
Limit: 1,
447+
})
448+
if err != nil {
449+
return xerrors.Errorf("fetch workspaces failed: %w", err)
450+
}
451+
452+
if len(res.Workspaces) > 0 {
536453
_, _ = fmt.Fprintln(out, "You should now be able to ssh into your workspace.")
537-
_, _ = fmt.Fprintf(out, "For example, try running:\n\n\t$ ssh %s%s\n", coderdConfig.HostnamePrefix, workspaceConfigs[0].Name)
454+
_, _ = fmt.Fprintf(out, "For example, try running:\n\n\t$ ssh %s%s\n", coderdConfig.HostnamePrefix, res.Workspaces[0].Name)
538455
} else {
539456
_, _ = fmt.Fprint(out, "You don't have any workspaces yet, try creating one with:\n\n\t$ coder create <workspace>\n")
540457
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy