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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 81 additions & 42 deletions
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)
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