@@ -33,6 +33,7 @@ import (
33
33
"golang.org/x/mod/semver"
34
34
"golang.org/x/oauth2"
35
35
xgithub "golang.org/x/oauth2/github"
36
+ "golang.org/x/sync/errgroup"
36
37
"golang.org/x/xerrors"
37
38
"google.golang.org/api/idtoken"
38
39
"google.golang.org/api/option"
@@ -169,8 +170,9 @@ func server() *cobra.Command {
169
170
}
170
171
171
172
var (
172
- tunnelErrChan <- chan error
173
173
ctxTunnel , closeTunnel = context .WithCancel (cmd .Context ())
174
+ devTunnel = (* devtunnel .Tunnel )(nil )
175
+ devTunnelErrChan = make (<- chan error , 1 )
174
176
)
175
177
defer closeTunnel ()
176
178
@@ -195,14 +197,37 @@ func server() *cobra.Command {
195
197
}
196
198
}
197
199
if err == nil {
198
- accessURL , tunnelErrChan , err = devtunnel .New (ctxTunnel , localURL )
200
+ devTunnel , devTunnelErrChan , err = devtunnel .New (ctxTunnel , logger . Named ( "devtunnel" ) )
199
201
if err != nil {
200
202
return xerrors .Errorf ("create tunnel: %w" , err )
201
203
}
204
+ accessURL = devTunnel .URL
202
205
}
203
206
_ , _ = fmt .Fprintln (cmd .ErrOrStderr ())
204
207
}
205
208
209
+ // Warn the user if the access URL appears to be a loopback address.
210
+ isLocal , err := isLocalURL (cmd .Context (), accessURL )
211
+ if isLocal || err != nil {
212
+ var reason string
213
+ if isLocal {
214
+ reason = "appears to be a loopback address"
215
+ } else {
216
+ reason = "could not be resolved"
217
+ }
218
+ _ , _ = fmt .Fprintf (cmd .ErrOrStderr (), cliui .Styles .Wrap .Render (
219
+ cliui .Styles .Warn .Render ("Warning:" )+ " The current access URL:" )+ "\n \n " )
220
+ _ , _ = fmt .Fprintf (cmd .ErrOrStderr (), " " + cliui .Styles .Field .Render (accessURL )+ "\n \n " )
221
+ _ , _ = fmt .Fprintf (cmd .ErrOrStderr (), cliui .Styles .Wrap .Render (
222
+ reason + ". Provisioned workspaces are unlikely to be able to " +
223
+ "connect to Coder. Please consider changing your " +
224
+ "access URL using the --access-url option, or directly " +
225
+ "specifying access URLs on templates." ,
226
+ )+ "\n \n " )
227
+ _ , _ = fmt .Fprintf (cmd .ErrOrStderr (), "For more information, see " +
228
+ "https://github.com/coder/coder/issues/1528\n \n " )
229
+ }
230
+
206
231
validator , err := idtoken .NewValidator (cmd .Context (), option .WithoutAuthentication ())
207
232
if err != nil {
208
233
return err
@@ -327,7 +352,27 @@ func server() *cobra.Command {
327
352
return shutdownConnsCtx
328
353
},
329
354
}
330
- errCh <- server .Serve (listener )
355
+
356
+ wg := errgroup.Group {}
357
+ wg .Go (func () error {
358
+ // Make sure to close the tunnel listener if we exit so the
359
+ // errgroup doesn't wait forever!
360
+ if dev && tunnel {
361
+ defer devTunnel .Listener .Close ()
362
+ }
363
+
364
+ return server .Serve (listener )
365
+ })
366
+
367
+ if dev && tunnel {
368
+ wg .Go (func () error {
369
+ defer listener .Close ()
370
+
371
+ return server .Serve (devTunnel .Listener )
372
+ })
373
+ }
374
+
375
+ errCh <- wg .Wait ()
331
376
}()
332
377
333
378
config := createConfig (cmd )
@@ -393,7 +438,7 @@ func server() *cobra.Command {
393
438
case <- cmd .Context ().Done ():
394
439
coderAPI .Close ()
395
440
return cmd .Context ().Err ()
396
- case err := <- tunnelErrChan :
441
+ case err := <- devTunnelErrChan :
397
442
if err != nil {
398
443
return err
399
444
}
@@ -456,7 +501,7 @@ func server() *cobra.Command {
456
501
if dev && tunnel {
457
502
_ , _ = fmt .Fprintf (cmd .OutOrStdout (), cliui .Styles .Prompt .String ()+ "Waiting for dev tunnel to close...\n " )
458
503
closeTunnel ()
459
- <- tunnelErrChan
504
+ <- devTunnelErrChan
460
505
}
461
506
462
507
_ , _ = fmt .Fprintf (cmd .OutOrStdout (), cliui .Styles .Prompt .String ()+ "Waiting for WebSocket connections to close...\n " )
@@ -803,3 +848,24 @@ func serveHandler(ctx context.Context, logger slog.Logger, handler http.Handler,
803
848
804
849
return func () { _ = srv .Close () }
805
850
}
851
+
852
+ // isLocalURL returns true if the hostname of the provided URL appears to
853
+ // resolve to a loopback address.
854
+ func isLocalURL (ctx context.Context , urlString string ) (bool , error ) {
855
+ parsedURL , err := url .Parse (urlString )
856
+ if err != nil {
857
+ return false , err
858
+ }
859
+ resolver := & net.Resolver {}
860
+ ips , err := resolver .LookupIPAddr (ctx , parsedURL .Hostname ())
861
+ if err != nil {
862
+ return false , err
863
+ }
864
+
865
+ for _ , ip := range ips {
866
+ if ip .IP .IsLoopback () {
867
+ return true , nil
868
+ }
869
+ }
870
+ return false , nil
871
+ }
0 commit comments