@@ -25,13 +25,18 @@ type AgentOptions struct {
25
25
Fetch func (ctx context.Context , agentID uuid.UUID ) (codersdk.WorkspaceAgent , error )
26
26
FetchLogs func (ctx context.Context , agentID uuid.UUID , after int64 , follow bool ) (<- chan []codersdk.WorkspaceAgentLog , io.Closer , error )
27
27
Wait bool // If true, wait for the agent to be ready (startup script).
28
+ DocsURL string
28
29
}
29
30
30
31
// Agent displays a spinning indicator that waits for a workspace agent to connect.
31
32
func Agent (ctx context.Context , writer io.Writer , agentID uuid.UUID , opts AgentOptions ) error {
32
33
ctx , cancel := context .WithCancel (ctx )
33
34
defer cancel ()
34
35
36
+ if opts .DocsURL == "" {
37
+ opts .DocsURL = "https://coder.com/docs"
38
+ }
39
+
35
40
if opts .FetchInterval == 0 {
36
41
opts .FetchInterval = 500 * time .Millisecond
37
42
}
@@ -119,7 +124,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
119
124
if agent .Status == codersdk .WorkspaceAgentTimeout {
120
125
now := time .Now ()
121
126
sw .Log (now , codersdk .LogLevelInfo , "The workspace agent is having trouble connecting, wait for it to connect or restart your workspace." )
122
- sw .Log (now , codersdk .LogLevelInfo , troubleshootingMessage (agent , "https://coder.com/docs/ templates#agent-connection-issues" ))
127
+ sw .Log (now , codersdk .LogLevelInfo , troubleshootingMessage (agent , fmt . Sprintf ( "%s/ templates#agent-connection-issues", opts . DocsURL ) ))
123
128
for agent .Status == codersdk .WorkspaceAgentTimeout {
124
129
if agent , err = fetch (); err != nil {
125
130
return xerrors .Errorf ("fetch: %w" , err )
@@ -224,13 +229,13 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
224
229
sw .Fail (stage , safeDuration (sw , agent .ReadyAt , agent .StartedAt ))
225
230
// Use zero time (omitted) to separate these from the startup logs.
226
231
sw .Log (time.Time {}, codersdk .LogLevelWarn , "Warning: A startup script exited with an error and your workspace may be incomplete." )
227
- sw .Log (time.Time {}, codersdk .LogLevelWarn , troubleshootingMessage (agent , "https://coder.com/docs/ templates/troubleshooting #startup-script-exited-with-an-error" ))
232
+ sw .Log (time.Time {}, codersdk .LogLevelWarn , troubleshootingMessage (agent , fmt . Sprintf ( "%s/ templates#startup-script-exited-with-an-error", opts . DocsURL ) ))
228
233
default :
229
234
switch {
230
235
case agent .LifecycleState .Starting ():
231
236
// Use zero time (omitted) to separate these from the startup logs.
232
237
sw .Log (time.Time {}, codersdk .LogLevelWarn , "Notice: The startup scripts are still running and your workspace may be incomplete." )
233
- sw .Log (time.Time {}, codersdk .LogLevelWarn , troubleshootingMessage (agent , "https://coder.com/docs/ templates/troubleshooting #your-workspace-may-be-incomplete" ))
238
+ sw .Log (time.Time {}, codersdk .LogLevelWarn , troubleshootingMessage (agent , fmt . Sprintf ( "%s/ templates#your-workspace-may-be-incomplete", opts . DocsURL ) ))
234
239
// Note: We don't complete or fail the stage here, it's
235
240
// intentionally left open to indicate this stage didn't
236
241
// complete.
@@ -252,7 +257,7 @@ func Agent(ctx context.Context, writer io.Writer, agentID uuid.UUID, opts AgentO
252
257
stage := "The workspace agent lost connection"
253
258
sw .Start (stage )
254
259
sw .Log (time .Now (), codersdk .LogLevelWarn , "Wait for it to reconnect or restart your workspace." )
255
- sw .Log (time .Now (), codersdk .LogLevelWarn , troubleshootingMessage (agent , "https://coder.com/docs/ templates/troubleshooting #agent-connection-issues" ))
260
+ sw .Log (time .Now (), codersdk .LogLevelWarn , troubleshootingMessage (agent , fmt . Sprintf ( "%s/ templates#agent-connection-issues", opts . DocsURL ) ))
256
261
257
262
disconnectedAt := agent .DisconnectedAt
258
263
for agent .Status == codersdk .WorkspaceAgentDisconnected {
@@ -351,16 +356,16 @@ func PeerDiagnostics(w io.Writer, d tailnet.PeerDiagnostics) {
351
356
}
352
357
353
358
type ConnDiags struct {
354
- ConnInfo workspacesdk.AgentConnectionInfo
355
- PingP2P bool
356
- DisableDirect bool
357
- LocalNetInfo * tailcfg.NetInfo
358
- LocalInterfaces * healthsdk.InterfacesReport
359
- AgentNetcheck * healthsdk.AgentNetcheckReport
360
- ClientIPIsAWS bool
361
- AgentIPIsAWS bool
362
- Verbose bool
363
- // TODO: More diagnostics
359
+ ConnInfo workspacesdk.AgentConnectionInfo
360
+ PingP2P bool
361
+ DisableDirect bool
362
+ LocalNetInfo * tailcfg.NetInfo
363
+ LocalInterfaces * healthsdk.InterfacesReport
364
+ AgentNetcheck * healthsdk.AgentNetcheckReport
365
+ ClientIPIsAWS bool
366
+ AgentIPIsAWS bool
367
+ Verbose bool
368
+ TroubleshootingURL string
364
369
}
365
370
366
371
func (d ConnDiags ) Write (w io.Writer ) {
@@ -395,7 +400,7 @@ func (d ConnDiags) splitDiagnostics() (general, client, agent []string) {
395
400
agent = append (agent , msg .Message )
396
401
}
397
402
if len (d .AgentNetcheck .Interfaces .Warnings ) > 0 {
398
- agent [len (agent )- 1 ] += " \n https://coder.com/docs/networking/troubleshooting #low-mtu"
403
+ agent [len (agent )- 1 ] += fmt . Sprintf ( " \n %s #low-mtu", d . TroubleshootingURL )
399
404
}
400
405
}
401
406
@@ -404,7 +409,7 @@ func (d ConnDiags) splitDiagnostics() (general, client, agent []string) {
404
409
client = append (client , msg .Message )
405
410
}
406
411
if len (d .LocalInterfaces .Warnings ) > 0 {
407
- client [len (client )- 1 ] += " \n https://coder.com/docs/networking/troubleshooting #low-mtu"
412
+ client [len (client )- 1 ] += fmt . Sprintf ( " \n %s #low-mtu", d . TroubleshootingURL )
408
413
}
409
414
}
410
415
@@ -420,45 +425,45 @@ func (d ConnDiags) splitDiagnostics() (general, client, agent []string) {
420
425
}
421
426
422
427
if d .ConnInfo .DisableDirectConnections {
423
- general = append (general , "❗ Your Coder administrator has blocked direct connections \n " +
424
- " https://coder.com/docs/networking/troubleshooting #disabled-deployment-wide" )
428
+ general = append (general ,
429
+ fmt . Sprintf ( "❗ Your Coder administrator has blocked direct connections \n %s #disabled-deployment-wide", d . TroubleshootingURL ) )
425
430
if ! d .Verbose {
426
431
return general , client , agent
427
432
}
428
433
}
429
434
430
435
if ! d .ConnInfo .DERPMap .HasSTUN () {
431
- general = append (general , "❗ The DERP map is not configured to use STUN \n " +
432
- " https://coder.com/docs/networking/troubleshooting #no-stun-servers" )
436
+ general = append (general ,
437
+ fmt . Sprintf ( "❗ The DERP map is not configured to use STUN \n %s #no-stun-servers", d . TroubleshootingURL ) )
433
438
} else if d .LocalNetInfo != nil && ! d .LocalNetInfo .UDP {
434
- client = append (client , "Client could not connect to STUN over UDP \n " +
435
- " https://coder.com/docs/networking/troubleshooting #udp-blocked" )
439
+ client = append (client ,
440
+ fmt . Sprintf ( "Client could not connect to STUN over UDP \n %s #udp-blocked", d . TroubleshootingURL ) )
436
441
}
437
442
438
443
if d .LocalNetInfo != nil && d .LocalNetInfo .MappingVariesByDestIP .EqualBool (true ) {
439
- client = append (client , "Client is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers \n " +
440
- " https://coder.com/docs/networking/troubleshooting#Endpoint-Dependent-Nat-Hard-NAT" )
444
+ client = append (client ,
445
+ fmt . Sprintf ( "Client is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers \n %s#endpoint-dependent-nat-hard-nat" , d . TroubleshootingURL ) )
441
446
}
442
447
443
448
if d .AgentNetcheck != nil && d .AgentNetcheck .NetInfo != nil {
444
449
if d .AgentNetcheck .NetInfo .MappingVariesByDestIP .EqualBool (true ) {
445
- agent = append (agent , "Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers \n " +
446
- " https://coder.com/docs/networking/troubleshooting#Endpoint-Dependent-Nat-Hard-NAT" )
450
+ agent = append (agent ,
451
+ fmt . Sprintf ( "Agent is potentially behind a hard NAT, as multiple endpoints were retrieved from different STUN servers \n %s#endpoint-dependent-nat-hard-nat" , d . TroubleshootingURL ) )
447
452
}
448
453
if ! d .AgentNetcheck .NetInfo .UDP {
449
- agent = append (agent , "Agent could not connect to STUN over UDP \n " +
450
- " https://coder.com/docs/networking/troubleshooting #udp-blocked" )
454
+ agent = append (agent ,
455
+ fmt . Sprintf ( "Agent could not connect to STUN over UDP \n %s #udp-blocked", d . TroubleshootingURL ) )
451
456
}
452
457
}
453
458
454
459
if d .ClientIPIsAWS {
455
- client = append (client , "Client IP address is within an AWS range (AWS uses hard NAT) \n " +
456
- " https://coder.com/docs/networking/troubleshooting#Endpoint-Dependent-Nat-Hard-NAT" )
460
+ client = append (client ,
461
+ fmt . Sprintf ( "Client IP address is within an AWS range (AWS uses hard NAT) \n %s#endpoint-dependent-nat-hard-nat" , d . TroubleshootingURL ) )
457
462
}
458
463
459
464
if d .AgentIPIsAWS {
460
- agent = append (agent , "Agent IP address is within an AWS range (AWS uses hard NAT) \n " +
461
- " https://coder.com/docs/networking/troubleshooting#Endpoint-Dependent-Nat-Hard-NAT" )
465
+ agent = append (agent ,
466
+ fmt . Sprintf ( "Agent IP address is within an AWS range (AWS uses hard NAT) \n %s#endpoint-dependent-nat-hard-nat" , d . TroubleshootingURL ) )
462
467
}
463
468
return general , client , agent
464
469
}
0 commit comments