@@ -20,6 +20,17 @@ import (
20
20
"golang.org/x/xerrors"
21
21
)
22
22
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
+
23
34
func configSSHCmd () * cobra.Command {
24
35
var (
25
36
configpath string
@@ -39,17 +50,6 @@ func configSSHCmd() *cobra.Command {
39
50
}
40
51
41
52
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
-
53
53
return func (cmd * cobra.Command , _ []string ) error {
54
54
ctx := cmd .Context ()
55
55
usr , err := user .Current ()
@@ -71,14 +71,11 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
71
71
return xerrors .Errorf ("read ssh config file %q: %w" , * configpath , err )
72
72
}
73
73
74
- startIndex := strings .Index (currentConfig , startToken )
75
- endIndex := strings .Index (currentConfig , endToken )
76
-
74
+ currentConfig , didRemoveConfig := removeOldConfig (currentConfig )
77
75
if * remove {
78
- if startIndex == - 1 || endIndex == - 1 {
76
+ if ! didRemoveConfig {
79
77
return xerrors .Errorf ("the Coder Enterprise ssh configuration section could not be safely deleted or does not exist" )
80
78
}
81
- currentConfig = currentConfig [:startIndex - 1 ] + currentConfig [endIndex + len (endToken )+ 1 :]
82
79
83
80
err = writeStr (* configpath , currentConfig )
84
81
if err != nil {
@@ -93,10 +90,6 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
93
90
return err
94
91
}
95
92
96
- if ! isSSHAvailable (ctx ) {
97
- return xerrors .New ("SSH is disabled or not available for your Coder Enterprise deployment." )
98
- }
99
-
100
93
user , err := client .Me (ctx )
101
94
if err != nil {
102
95
return xerrors .Errorf ("fetch username: %w" , err )
@@ -109,14 +102,19 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
109
102
if len (envs ) < 1 {
110
103
return xerrors .New ("no environments found" )
111
104
}
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 )
113
111
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 )
115
113
}
116
114
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 )
120
118
}
121
119
122
120
err = os .MkdirAll (filepath .Dir (* configpath ), os .ModePerm )
@@ -145,6 +143,57 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
145
143
}
146
144
}
147
145
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
+
148
197
func writeSSHKey (ctx context.Context , client * coder.Client , privateKeyPath string ) error {
149
198
key , err := client .SSHKey (ctx )
150
199
if err != nil {
@@ -153,17 +202,21 @@ func writeSSHKey(ctx context.Context, client *coder.Client, privateKeyPath strin
153
202
return ioutil .WriteFile (privateKeyPath , []byte (key .PrivateKey ), 0400 )
154
203
}
155
204
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 ) {
157
206
hostname , err := configuredHostname ()
158
207
if err != nil {
159
208
return "" , err
160
209
}
161
210
162
- newConfig := fmt .Sprintf ("\n %s\n %s\n \n " , startToken , startMsg )
211
+ newConfig := fmt .Sprintf ("\n %s\n %s\n \n " , sshStartToken , sshStartMessage )
163
212
for _ , env := range envs {
213
+ if ! env .SSHAvailable {
214
+ continue
215
+ }
216
+
164
217
newConfig += makeSSHConfig (hostname , userName , env .Name , privateKeyFilepath )
165
218
}
166
- newConfig += fmt .Sprintf ("\n %s\n " , endToken )
219
+ newConfig += fmt .Sprintf ("\n %s\n " , sshEndToken )
167
220
168
221
return newConfig , nil
169
222
}
@@ -181,20 +234,6 @@ func makeSSHConfig(host, userName, envName, privateKeyFilepath string) string {
181
234
` , envName , host , userName , envName , privateKeyFilepath )
182
235
}
183
236
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
-
198
237
func configuredHostname () (string , error ) {
199
238
u , err := config .URL .Read ()
200
239
if err != nil {
0 commit comments