Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 44f4a04

Browse files
authored
Use env.ssh_available field in config-ssh (#197)
1 parent db06ecc commit 44f4a04

File tree

2 files changed

+82
-42
lines changed

2 files changed

+82
-42
lines changed

coder-sdk/env.go

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type Environment struct {
3232
LastOpenedAt time.Time `json:"last_opened_at" table:"-"`
3333
LastConnectionAt time.Time `json:"last_connection_at" table:"-"`
3434
AutoOffThreshold Duration `json:"auto_off_threshold" table:"-"`
35+
SSHAvailable bool `json:"ssh_available" table:"-"`
3536
}
3637

3738
// RebuildMessage defines the message shown when an Environment requires a rebuild for it can be accessed.

internal/cmd/configssh.go

+81-42
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,17 @@ import (
2020
"golang.org/x/xerrors"
2121
)
2222

23+
const sshStartToken = "# ------------START-CODER-ENTERPRISE-----------"
24+
const sshStartMessage = `# The following has been auto-generated by "coder config-ssh"
25+
# to make accessing your Coder Enterprise environments easier.
26+
#
27+
# To remove this blob, run:
28+
#
29+
# coder config-ssh --remove
30+
#
31+
# You should not hand-edit this section, unless you are deleting it.`
32+
const sshEndToken = "# ------------END-CODER-ENTERPRISE------------"
33+
2334
func configSSHCmd() *cobra.Command {
2435
var (
2536
configpath string
@@ -39,17 +50,6 @@ func configSSHCmd() *cobra.Command {
3950
}
4051

4152
func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []string) error {
42-
const startToken = "# ------------START-CODER-ENTERPRISE-----------"
43-
startMessage := `# The following has been auto-generated by "coder config-ssh"
44-
# to make accessing your Coder Enterprise environments easier.
45-
#
46-
# To remove this blob, run:
47-
#
48-
# coder config-ssh --remove
49-
#
50-
# You should not hand-edit this section, unless you are deleting it.`
51-
const endToken = "# ------------END-CODER-ENTERPRISE------------"
52-
5353
return func(cmd *cobra.Command, _ []string) error {
5454
ctx := cmd.Context()
5555
usr, err := user.Current()
@@ -71,14 +71,11 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
7171
return xerrors.Errorf("read ssh config file %q: %w", *configpath, err)
7272
}
7373

74-
startIndex := strings.Index(currentConfig, startToken)
75-
endIndex := strings.Index(currentConfig, endToken)
76-
74+
currentConfig, didRemoveConfig := removeOldConfig(currentConfig)
7775
if *remove {
78-
if startIndex == -1 || endIndex == -1 {
76+
if !didRemoveConfig {
7977
return xerrors.Errorf("the Coder Enterprise ssh configuration section could not be safely deleted or does not exist")
8078
}
81-
currentConfig = currentConfig[:startIndex-1] + currentConfig[endIndex+len(endToken)+1:]
8279

8380
err = writeStr(*configpath, currentConfig)
8481
if err != nil {
@@ -93,10 +90,6 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
9390
return err
9491
}
9592

96-
if !isSSHAvailable(ctx) {
97-
return xerrors.New("SSH is disabled or not available for your Coder Enterprise deployment.")
98-
}
99-
10093
user, err := client.Me(ctx)
10194
if err != nil {
10295
return xerrors.Errorf("fetch username: %w", err)
@@ -109,14 +102,19 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
109102
if len(envs) < 1 {
110103
return xerrors.New("no environments found")
111104
}
112-
newConfig, err := makeNewConfigs(user.Username, envs, startToken, startMessage, endToken, privateKeyFilepath)
105+
106+
if !sshAvailable(envs) {
107+
return xerrors.New("SSH is disabled or not available for any environments in your Coder Enterprise deployment.")
108+
}
109+
110+
err = canConnectSSH(ctx)
113111
if err != nil {
114-
return xerrors.Errorf("make new ssh configurations: %w", err)
112+
return xerrors.Errorf("check if SSH is available: unable to connect to SSH endpoint: %w", err)
115113
}
116114

117-
// if we find the old config, remove those chars from the string
118-
if startIndex != -1 && endIndex != -1 {
119-
currentConfig = currentConfig[:startIndex-1] + currentConfig[endIndex+len(endToken)+1:]
115+
newConfig, err := makeNewConfigs(user.Username, envs, privateKeyFilepath)
116+
if err != nil {
117+
return xerrors.Errorf("make new ssh configurations: %w", err)
120118
}
121119

122120
err = os.MkdirAll(filepath.Dir(*configpath), os.ModePerm)
@@ -145,6 +143,57 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
145143
}
146144
}
147145

146+
// removeOldConfig removes the old ssh configuration from the user's sshconfig.
147+
// Returns true if the config was modified.
148+
func removeOldConfig(config string) (string, bool) {
149+
startIndex := strings.Index(config, sshStartToken)
150+
endIndex := strings.Index(config, sshEndToken)
151+
152+
if startIndex == -1 || endIndex == -1 {
153+
return config, false
154+
}
155+
config = config[:startIndex-1] + config[endIndex+len(sshEndToken)+1:]
156+
157+
return config, true
158+
}
159+
160+
// sshAvailable returns true if SSH is available for at least one environment.
161+
func sshAvailable(envs []coder.Environment) bool {
162+
for _, env := range envs {
163+
if env.SSHAvailable {
164+
return true
165+
}
166+
}
167+
168+
return false
169+
}
170+
171+
// canConnectSSH returns an error if we cannot dial the SSH port.
172+
func canConnectSSH(ctx context.Context) error {
173+
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
174+
defer cancel()
175+
176+
host, err := configuredHostname()
177+
if err != nil {
178+
return xerrors.Errorf("get configured manager hostname: %w", err)
179+
}
180+
181+
var (
182+
dialer net.Dialer
183+
hostPort = net.JoinHostPort(host, "22")
184+
)
185+
conn, err := dialer.DialContext(ctx, "tcp", hostPort)
186+
if err != nil {
187+
if err == context.DeadlineExceeded {
188+
err = xerrors.New("timed out after 3 seconds")
189+
}
190+
return xerrors.Errorf("dial tcp://%v: %w", hostPort, err)
191+
}
192+
conn.Close()
193+
194+
return nil
195+
}
196+
148197
func writeSSHKey(ctx context.Context, client *coder.Client, privateKeyPath string) error {
149198
key, err := client.SSHKey(ctx)
150199
if err != nil {
@@ -153,17 +202,21 @@ func writeSSHKey(ctx context.Context, client *coder.Client, privateKeyPath strin
153202
return ioutil.WriteFile(privateKeyPath, []byte(key.PrivateKey), 0400)
154203
}
155204

156-
func makeNewConfigs(userName string, envs []coder.Environment, startToken, startMsg, endToken, privateKeyFilepath string) (string, error) {
205+
func makeNewConfigs(userName string, envs []coder.Environment, privateKeyFilepath string) (string, error) {
157206
hostname, err := configuredHostname()
158207
if err != nil {
159208
return "", err
160209
}
161210

162-
newConfig := fmt.Sprintf("\n%s\n%s\n\n", startToken, startMsg)
211+
newConfig := fmt.Sprintf("\n%s\n%s\n\n", sshStartToken, sshStartMessage)
163212
for _, env := range envs {
213+
if !env.SSHAvailable {
214+
continue
215+
}
216+
164217
newConfig += makeSSHConfig(hostname, userName, env.Name, privateKeyFilepath)
165218
}
166-
newConfig += fmt.Sprintf("\n%s\n", endToken)
219+
newConfig += fmt.Sprintf("\n%s\n", sshEndToken)
167220

168221
return newConfig, nil
169222
}
@@ -181,20 +234,6 @@ func makeSSHConfig(host, userName, envName, privateKeyFilepath string) string {
181234
`, envName, host, userName, envName, privateKeyFilepath)
182235
}
183236

184-
func isSSHAvailable(ctx context.Context) bool {
185-
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
186-
defer cancel()
187-
188-
host, err := configuredHostname()
189-
if err != nil {
190-
return false
191-
}
192-
193-
var dialer net.Dialer
194-
_, err = dialer.DialContext(ctx, "tcp", net.JoinHostPort(host, "22"))
195-
return err == nil
196-
}
197-
198237
func configuredHostname() (string, error) {
199238
u, err := config.URL.Read()
200239
if err != nil {

0 commit comments

Comments
 (0)