Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: use wildcard Host entry in config-ssh#16096

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
bcpeinhardt merged 1 commit intocoder:mainfromaaronlehmann:config-ssh-wildcard
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
233 changes: 75 additions & 158 deletionscli/configssh.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,7 +3,6 @@ package cli
import (
"bufio"
"bytes"
"context"
"errors"
"fmt"
"io"
Expand All@@ -12,7 +11,6 @@ import (
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"

Expand All@@ -22,11 +20,9 @@ import (
"github.com/pkg/diff/write"
"golang.org/x/exp/constraints"
"golang.org/x/exp/slices"
"golang.org/x/sync/errgroup"
"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/coderd/util/slice"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)
Expand DownExpand Up@@ -139,74 +135,6 @@ func (o sshConfigOptions) asList() (list []string) {
return list
}

type sshWorkspaceConfig struct {
Name string
Hosts []string
}

func sshFetchWorkspaceConfigs(ctx context.Context, client *codersdk.Client) ([]sshWorkspaceConfig, error) {
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
Owner: codersdk.Me,
})
if err != nil {
return nil, err
}

var errGroup errgroup.Group
workspaceConfigs := make([]sshWorkspaceConfig, len(res.Workspaces))
for i, workspace := range res.Workspaces {
i := i
workspace := workspace
errGroup.Go(func() error {
resources, err := client.TemplateVersionResources(ctx, workspace.LatestBuild.TemplateVersionID)
if err != nil {
return err
}

wc := sshWorkspaceConfig{Name: workspace.Name}
var agents []codersdk.WorkspaceAgent
for _, resource := range resources {
if resource.Transition != codersdk.WorkspaceTransitionStart {
continue
}
agents = append(agents, resource.Agents...)
}

// handle both WORKSPACE and WORKSPACE.AGENT syntax
if len(agents) == 1 {
wc.Hosts = append(wc.Hosts, workspace.Name)
}
for _, agent := range agents {
hostname := workspace.Name + "." + agent.Name
wc.Hosts = append(wc.Hosts, hostname)
}

workspaceConfigs[i] = wc

return nil
})
}
err = errGroup.Wait()
if err != nil {
return nil, err
}

return workspaceConfigs, nil
}

func sshPrepareWorkspaceConfigs(ctx context.Context, client *codersdk.Client) (receive func() ([]sshWorkspaceConfig, error)) {
wcC := make(chan []sshWorkspaceConfig, 1)
errC := make(chan error, 1)
go func() {
wc, err := sshFetchWorkspaceConfigs(ctx, client)
wcC <- wc
errC <- err
}()
return func() ([]sshWorkspaceConfig, error) {
return <-wcC, <-errC
}
}

func (r *RootCmd) configSSH() *serpent.Command {
var (
sshConfigFile string
Expand DownExpand Up@@ -254,8 +182,6 @@ func (r *RootCmd) configSSH() *serpent.Command {
// warning at any time.
_, _ = client.BuildInfo(ctx)

recvWorkspaceConfigs := sshPrepareWorkspaceConfigs(ctx, client)

out := inv.Stdout
if dryRun {
// Print everything except diff to stderr so
Expand DownExpand Up@@ -371,11 +297,6 @@ func (r *RootCmd) configSSH() *serpent.Command {
newline := len(before) > 0
sshConfigWriteSectionHeader(buf, newline, sshConfigOpts)

workspaceConfigs, err := recvWorkspaceConfigs()
if err != nil {
return xerrors.Errorf("fetch workspace configs failed: %w", err)
}

coderdConfig, err := client.SSHConfiguration(ctx)
if err != nil {
// If the error is 404, this deployment does not support
Expand All@@ -394,91 +315,79 @@ func (r *RootCmd) configSSH() *serpent.Command {
coderdConfig.HostnamePrefix = sshConfigOpts.userHostPrefix
}

// Ensure stable sorting of output.
slices.SortFunc(workspaceConfigs, func(a, b sshWorkspaceConfig) int {
return slice.Ascending(a.Name, b.Name)
})
for _, wc := range workspaceConfigs {
sort.Strings(wc.Hosts)
// Write agent configuration.
for _, workspaceHostname := range wc.Hosts {
sshHostname := fmt.Sprintf("%s%s", coderdConfig.HostnamePrefix, workspaceHostname)
defaultOptions := []string{
"HostName " + sshHostname,
"ConnectTimeout=0",
"StrictHostKeyChecking=no",
// Without this, the "REMOTE HOST IDENTITY CHANGED"
// message will appear.
"UserKnownHostsFile=/dev/null",
// This disables the "Warning: Permanently added 'hostname' (RSA) to the list of known hosts."
// message from appearing on every SSH. This happens because we ignore the known hosts.
"LogLevel ERROR",
}

if !skipProxyCommand {
rootFlags := fmt.Sprintf("--global-config %s", escapedGlobalConfig)
for _, h := range sshConfigOpts.header {
rootFlags += fmt.Sprintf(" --header %q", h)
}
if sshConfigOpts.headerCommand != "" {
rootFlags += fmt.Sprintf(" --header-command %q", sshConfigOpts.headerCommand)
}

flags := ""
if sshConfigOpts.waitEnum != "auto" {
flags += " --wait=" + sshConfigOpts.waitEnum
}
if sshConfigOpts.disableAutostart {
flags += " --disable-autostart=true"
}
defaultOptions = append(defaultOptions, fmt.Sprintf(
"ProxyCommand %s %s ssh --stdio%s %s",
escapedCoderBinary, rootFlags, flags, workspaceHostname,
))
}
// Write agent configuration.
defaultOptions := []string{
"ConnectTimeout=0",
"StrictHostKeyChecking=no",
// Without this, the "REMOTE HOST IDENTITY CHANGED"
// message will appear.
"UserKnownHostsFile=/dev/null",
// This disables the "Warning: Permanently added 'hostname' (RSA) to the list of known hosts."
// message from appearing on every SSH. This happens because we ignore the known hosts.
"LogLevel ERROR",
}

// Create a copy of the options so we can modify them.
configOptions := sshConfigOpts
configOptions.sshOptions = nil

// User options first (SSH only uses the first
// option unless it can be given multiple times)
for _, opt := range sshConfigOpts.sshOptions {
err := configOptions.addOptions(opt)
if err != nil {
return xerrors.Errorf("add flag config option %q: %w", opt, err)
}
}
if !skipProxyCommand {
rootFlags := fmt.Sprintf("--global-config %s", escapedGlobalConfig)
for _, h := range sshConfigOpts.header {
rootFlags += fmt.Sprintf(" --header %q", h)
}
if sshConfigOpts.headerCommand != "" {
rootFlags += fmt.Sprintf(" --header-command %q", sshConfigOpts.headerCommand)
}

// Deployment options second, allow them to
// override standard options.
for k, v := range coderdConfig.SSHConfigOptions {
opt := fmt.Sprintf("%s %s", k, v)
err := configOptions.addOptions(opt)
if err != nil {
return xerrors.Errorf("add coderd config option %q: %w", opt, err)
}
}
flags := ""
if sshConfigOpts.waitEnum != "auto" {
flags += " --wait=" + sshConfigOpts.waitEnum
}
if sshConfigOpts.disableAutostart {
flags += " --disable-autostart=true"
}
defaultOptions = append(defaultOptions, fmt.Sprintf(
"ProxyCommand %s %s ssh --stdio%s --ssh-host-prefix %s %%h",
escapedCoderBinary, rootFlags, flags, coderdConfig.HostnamePrefix,
))
}

// Finally, add the standard options.
err := configOptions.addOptions(defaultOptions...)
if err != nil {
return err
}
// Create a copy of the options so we can modify them.
configOptions := sshConfigOpts
configOptions.sshOptions = nil

hostBlock := []string{
"Host " + sshHostname,
}
// Prefix with '\t'
for _, v := range configOptions.sshOptions {
hostBlock = append(hostBlock, "\t"+v)
}
// User options first (SSH only uses the first
// option unless it can be given multiple times)
for _, opt := range sshConfigOpts.sshOptions {
err := configOptions.addOptions(opt)
if err != nil {
return xerrors.Errorf("add flag config option %q: %w", opt, err)
}
}

_, _ = buf.WriteString(strings.Join(hostBlock, "\n"))
_ = buf.WriteByte('\n')
// Deployment options second, allow them to
// override standard options.
for k, v := range coderdConfig.SSHConfigOptions {
opt := fmt.Sprintf("%s %s", k, v)
err := configOptions.addOptions(opt)
if err != nil {
return xerrors.Errorf("add coderd config option %q: %w", opt, err)
}
}

// Finally, add the standard options.
if err := configOptions.addOptions(defaultOptions...); err != nil {
return err
}

hostBlock := []string{
"Host " + coderdConfig.HostnamePrefix + "*",
}
// Prefix with '\t'
for _, v := range configOptions.sshOptions {
hostBlock = append(hostBlock, "\t"+v)
}

_, _ = buf.WriteString(strings.Join(hostBlock, "\n"))
_ = buf.WriteByte('\n')

sshConfigWriteSectionEnd(buf)

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

if len(workspaceConfigs) > 0 {
res, err := client.Workspaces(ctx, codersdk.WorkspaceFilter{
Owner: codersdk.Me,
Limit: 1,
})
if err != nil {
return xerrors.Errorf("fetch workspaces failed: %w", err)
}

if len(res.Workspaces) > 0 {
_, _ = fmt.Fprintln(out, "You should now be able to ssh into your workspace.")
_, _ = fmt.Fprintf(out, "For example, try running:\n\n\t$ ssh %s%s\n", coderdConfig.HostnamePrefix,workspaceConfigs[0].Name)
_, _ = fmt.Fprintf(out, "For example, try running:\n\n\t$ ssh %s%s\n", coderdConfig.HostnamePrefix,res.Workspaces[0].Name)
} else {
_, _ = fmt.Fprint(out, "You don't have any workspaces yet, try creating one with:\n\n\t$ coder create <workspace>\n")
}
Expand Down
Loading
Loading

[8]ページ先頭

©2009-2025 Movatter.jp