From 607bed49d85f91c6478a2f48f6b82473cf228cd7 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 10 Nov 2022 18:39:36 +0000 Subject: [PATCH 1/8] feat: Add fallback troubleshooting URL to SSH connection timeouts --- cli/cliui/agent.go | 19 ++++++--- cli/cliui/agent_test.go | 95 +++++++++++++++++++++++++++++++++++++++++ cli/ssh.go | 3 ++ 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/cli/cliui/agent.go b/cli/cliui/agent.go index 023891f678fc3..ece70fc672805 100644 --- a/cli/cliui/agent.go +++ b/cli/cliui/agent.go @@ -16,10 +16,11 @@ import ( ) type AgentOptions struct { - WorkspaceName string - Fetch func(context.Context) (codersdk.WorkspaceAgent, error) - FetchInterval time.Duration - WarnInterval time.Duration + WorkspaceName string + Fetch func(context.Context) (codersdk.WorkspaceAgent, error) + FetchInterval time.Duration + WarnInterval time.Duration + FallbackTroubleshootingURL string } // Agent displays a spinning indicator that waits for a workspace agent to connect. @@ -72,7 +73,7 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error { resourceMutex.Lock() defer resourceMutex.Unlock() - m := waitingMessage(agent) + m := waitingMessage(agent, opts.FallbackTroubleshootingURL) if m == waitMessage { return } @@ -128,7 +129,7 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error { } } -func waitingMessage(agent codersdk.WorkspaceAgent) string { +func waitingMessage(agent codersdk.WorkspaceAgent, fallbackTroubleshootingURL string) string { var m string switch agent.Status { case codersdk.WorkspaceAgentTimeout: @@ -139,8 +140,12 @@ func waitingMessage(agent codersdk.WorkspaceAgent) string { // Not a failure state, no troubleshooting necessary. return "Don't panic, your workspace is booting up!" } + troubleshootingURL := fallbackTroubleshootingURL if agent.TroubleshootingURL != "" { - return fmt.Sprintf("%s See troubleshooting instructions at: %s", m, agent.TroubleshootingURL) + troubleshootingURL = agent.TroubleshootingURL + } + if troubleshootingURL != "" { + return fmt.Sprintf("%s See troubleshooting instructions at: %s", m, troubleshootingURL) } return fmt.Sprintf("%s Wait for it to (re)connect or restart your workspace.", m) } diff --git a/cli/cliui/agent_test.go b/cli/cliui/agent_test.go index 9a43eeb701dce..618b8ad153880 100644 --- a/cli/cliui/agent_test.go +++ b/cli/cliui/agent_test.go @@ -12,6 +12,7 @@ import ( "github.com/coder/coder/cli/cliui" "github.com/coder/coder/codersdk" "github.com/coder/coder/pty/ptytest" + "github.com/coder/coder/testutil" ) func TestAgent(t *testing.T) { @@ -49,3 +50,97 @@ func TestAgent(t *testing.T) { disconnected.Store(true) <-done } + +func TestAgentTimeoutWithTroubleshootingURL(t *testing.T) { + t.Parallel() + + ctx, _ := testutil.Context(t) + + wantURL := "https://coder.com/troubleshoot" + + var connected, timeout atomic.Bool + cmd := &cobra.Command{ + RunE: func(cmd *cobra.Command, args []string) error { + err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{ + WorkspaceName: "example", + Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) { + agent := codersdk.WorkspaceAgent{ + Status: codersdk.WorkspaceAgentConnecting, + TroubleshootingURL: "https://coder.com/troubleshoot", + } + switch { + case connected.Load(): + agent.Status = codersdk.WorkspaceAgentConnected + case timeout.Load(): + agent.Status = codersdk.WorkspaceAgentTimeout + } + return agent, nil + }, + FetchInterval: time.Millisecond, + WarnInterval: 5 * time.Millisecond, + }) + return err + }, + } + ptty := ptytest.New(t) + cmd.SetOutput(ptty.Output()) + cmd.SetIn(ptty.Input()) + done := make(chan struct{}) + go func() { + defer close(done) + err := cmd.ExecuteContext(ctx) + assert.NoError(t, err) + }() + ptty.ExpectMatch("Don't panic") + timeout.Store(true) + ptty.ExpectMatch(wantURL) + connected.Store(true) + <-done +} + +func TestAgentTimeoutWithFallbackTroubleshootingURL(t *testing.T) { + t.Parallel() + + ctx, _ := testutil.Context(t) + + wantURL := "https://coder.com/troubleshoot" + + var connected, timeout atomic.Bool + cmd := &cobra.Command{ + RunE: func(cmd *cobra.Command, args []string) error { + err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{ + WorkspaceName: "example", + Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) { + agent := codersdk.WorkspaceAgent{ + Status: codersdk.WorkspaceAgentConnecting, + } + switch { + case connected.Load(): + agent.Status = codersdk.WorkspaceAgentConnected + case timeout.Load(): + agent.Status = codersdk.WorkspaceAgentTimeout + } + return agent, nil + }, + FetchInterval: time.Millisecond, + WarnInterval: 5 * time.Millisecond, + FallbackTroubleshootingURL: "https://coder.com/troubleshoot", + }) + return err + }, + } + ptty := ptytest.New(t) + cmd.SetOutput(ptty.Output()) + cmd.SetIn(ptty.Input()) + done := make(chan struct{}) + go func() { + defer close(done) + err := cmd.ExecuteContext(ctx) + assert.NoError(t, err) + }() + ptty.ExpectMatch("Don't panic") + timeout.Store(true) + ptty.ExpectMatch(wantURL) + connected.Store(true) + <-done +} diff --git a/cli/ssh.go b/cli/ssh.go index b72ebd398d60f..c504bf1c3c144 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -85,6 +85,9 @@ func ssh() *cobra.Command { Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) { return client.WorkspaceAgent(ctx, workspaceAgent.ID) }, + FallbackTroubleshootingURL: client.URL.ResolveReference(&url.URL{ + Path: fmt.Sprintf("/templates/%s#readme", workspace.TemplateName), + }).String(), }) if err != nil { return xerrors.Errorf("await agent: %w", err) From da8fbb2fe9bca38b251c2c3996d6790199b6b150 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Thu, 10 Nov 2022 18:43:10 +0000 Subject: [PATCH 2/8] Fix fragment --- cli/ssh.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cli/ssh.go b/cli/ssh.go index c504bf1c3c144..9b2e58aa1286f 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -86,7 +86,8 @@ func ssh() *cobra.Command { return client.WorkspaceAgent(ctx, workspaceAgent.ID) }, FallbackTroubleshootingURL: client.URL.ResolveReference(&url.URL{ - Path: fmt.Sprintf("/templates/%s#readme", workspace.TemplateName), + Path: fmt.Sprintf("/templates/%s", workspace.TemplateName), + Fragment: "readme", }).String(), }) if err != nil { From 37a46ace02b3487c120383a26a05016c8de201d6 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 14 Nov 2022 16:37:21 +0000 Subject: [PATCH 3/8] feat: Add fallback troubleshooting URL to deployment config (hidden) --- cli/cliui/agent.go | 19 ++++------- cli/cliui/agent_test.go | 47 --------------------------- cli/deployment/config.go | 7 ++++ cli/ssh.go | 4 --- coderd/provisionerjobs.go | 2 +- coderd/workspaceagents.go | 18 +++++++---- coderd/workspaceagents_test.go | 52 ++++++++++++++++++++++++++++-- coderd/workspacebuilds.go | 2 +- codersdk/deploymentconfig.go | 59 +++++++++++++++++----------------- 9 files changed, 107 insertions(+), 103 deletions(-) diff --git a/cli/cliui/agent.go b/cli/cliui/agent.go index ece70fc672805..023891f678fc3 100644 --- a/cli/cliui/agent.go +++ b/cli/cliui/agent.go @@ -16,11 +16,10 @@ import ( ) type AgentOptions struct { - WorkspaceName string - Fetch func(context.Context) (codersdk.WorkspaceAgent, error) - FetchInterval time.Duration - WarnInterval time.Duration - FallbackTroubleshootingURL string + WorkspaceName string + Fetch func(context.Context) (codersdk.WorkspaceAgent, error) + FetchInterval time.Duration + WarnInterval time.Duration } // Agent displays a spinning indicator that waits for a workspace agent to connect. @@ -73,7 +72,7 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error { resourceMutex.Lock() defer resourceMutex.Unlock() - m := waitingMessage(agent, opts.FallbackTroubleshootingURL) + m := waitingMessage(agent) if m == waitMessage { return } @@ -129,7 +128,7 @@ func Agent(ctx context.Context, writer io.Writer, opts AgentOptions) error { } } -func waitingMessage(agent codersdk.WorkspaceAgent, fallbackTroubleshootingURL string) string { +func waitingMessage(agent codersdk.WorkspaceAgent) string { var m string switch agent.Status { case codersdk.WorkspaceAgentTimeout: @@ -140,12 +139,8 @@ func waitingMessage(agent codersdk.WorkspaceAgent, fallbackTroubleshootingURL st // Not a failure state, no troubleshooting necessary. return "Don't panic, your workspace is booting up!" } - troubleshootingURL := fallbackTroubleshootingURL if agent.TroubleshootingURL != "" { - troubleshootingURL = agent.TroubleshootingURL - } - if troubleshootingURL != "" { - return fmt.Sprintf("%s See troubleshooting instructions at: %s", m, troubleshootingURL) + return fmt.Sprintf("%s See troubleshooting instructions at: %s", m, agent.TroubleshootingURL) } return fmt.Sprintf("%s Wait for it to (re)connect or restart your workspace.", m) } diff --git a/cli/cliui/agent_test.go b/cli/cliui/agent_test.go index 618b8ad153880..4f549f39f055e 100644 --- a/cli/cliui/agent_test.go +++ b/cli/cliui/agent_test.go @@ -97,50 +97,3 @@ func TestAgentTimeoutWithTroubleshootingURL(t *testing.T) { connected.Store(true) <-done } - -func TestAgentTimeoutWithFallbackTroubleshootingURL(t *testing.T) { - t.Parallel() - - ctx, _ := testutil.Context(t) - - wantURL := "https://coder.com/troubleshoot" - - var connected, timeout atomic.Bool - cmd := &cobra.Command{ - RunE: func(cmd *cobra.Command, args []string) error { - err := cliui.Agent(cmd.Context(), cmd.OutOrStdout(), cliui.AgentOptions{ - WorkspaceName: "example", - Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) { - agent := codersdk.WorkspaceAgent{ - Status: codersdk.WorkspaceAgentConnecting, - } - switch { - case connected.Load(): - agent.Status = codersdk.WorkspaceAgentConnected - case timeout.Load(): - agent.Status = codersdk.WorkspaceAgentTimeout - } - return agent, nil - }, - FetchInterval: time.Millisecond, - WarnInterval: 5 * time.Millisecond, - FallbackTroubleshootingURL: "https://coder.com/troubleshoot", - }) - return err - }, - } - ptty := ptytest.New(t) - cmd.SetOutput(ptty.Output()) - cmd.SetIn(ptty.Input()) - done := make(chan struct{}) - go func() { - defer close(done) - err := cmd.ExecuteContext(ctx) - assert.NoError(t, err) - }() - ptty.ExpectMatch("Don't panic") - timeout.Store(true) - ptty.ExpectMatch(wantURL) - connected.Store(true) - <-done -} diff --git a/cli/deployment/config.go b/cli/deployment/config.go index 4c8c72c135000..51803d45d612a 100644 --- a/cli/deployment/config.go +++ b/cli/deployment/config.go @@ -332,6 +332,13 @@ func newConfig() *codersdk.DeploymentConfig { Hidden: true, Default: 10 * time.Minute, }, + AgentFallbackTroubleshootingURL: &codersdk.DeploymentConfigField[string]{ + Name: "Agent Fallback Troubleshooting URL", + Usage: "URL to use for agent fallback troubleshooting", + Flag: "agent-fallback-troubleshooting-url", + Hidden: true, + Default: "https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates", + }, AuditLogging: &codersdk.DeploymentConfigField[bool]{ Name: "Audit Logging", Usage: "Specifies whether audit logging is enabled.", diff --git a/cli/ssh.go b/cli/ssh.go index 9b2e58aa1286f..b72ebd398d60f 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -85,10 +85,6 @@ func ssh() *cobra.Command { Fetch: func(ctx context.Context) (codersdk.WorkspaceAgent, error) { return client.WorkspaceAgent(ctx, workspaceAgent.ID) }, - FallbackTroubleshootingURL: client.URL.ResolveReference(&url.URL{ - Path: fmt.Sprintf("/templates/%s", workspace.TemplateName), - Fragment: "readme", - }).String(), }) if err != nil { return xerrors.Errorf("await agent: %w", err) diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index 9111f9ca287a2..c9eedf8dd68cc 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -262,7 +262,7 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, } } - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), agent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout) + apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), agent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading job agent.", diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index f8d10d6ff33b8..665211fe0cc97 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -52,7 +52,7 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { }) return } - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout) + apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", @@ -67,7 +67,7 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout) + apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", @@ -138,7 +138,7 @@ func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) func (api *API) postWorkspaceAgentVersion(rw http.ResponseWriter, r *http.Request) { ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout) + apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", @@ -192,7 +192,7 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { httpapi.ResourceNotFound(rw) return } - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout) + apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", @@ -269,7 +269,7 @@ func (api *API) workspaceAgentListeningPorts(rw http.ResponseWriter, r *http.Req return } - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout) + apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), workspaceAgent, nil, api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) if err != nil { httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", @@ -660,7 +660,7 @@ func convertApps(dbApps []database.WorkspaceApp) []codersdk.WorkspaceApp { return apps } -func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordinator, dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, agentInactiveDisconnectTimeout time.Duration) (codersdk.WorkspaceAgent, error) { +func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordinator, dbAgent database.WorkspaceAgent, apps []codersdk.WorkspaceApp, agentInactiveDisconnectTimeout time.Duration, agentFallbackTroubleshootingURL string) (codersdk.WorkspaceAgent, error) { var envs map[string]string if dbAgent.EnvironmentVariables.Valid { err := json.Unmarshal(dbAgent.EnvironmentVariables.RawMessage, &envs) @@ -668,6 +668,10 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordin return codersdk.WorkspaceAgent{}, xerrors.Errorf("unmarshal env vars: %w", err) } } + troubleshootingURL := agentFallbackTroubleshootingURL + if dbAgent.TroubleshootingURL != "" { + troubleshootingURL = dbAgent.TroubleshootingURL + } workspaceAgent := codersdk.WorkspaceAgent{ ID: dbAgent.ID, CreatedAt: dbAgent.CreatedAt, @@ -683,7 +687,7 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator tailnet.Coordin Directory: dbAgent.Directory, Apps: apps, ConnectionTimeoutSeconds: dbAgent.ConnectionTimeoutSeconds, - TroubleshootingURL: dbAgent.TroubleshootingURL, + TroubleshootingURL: troubleshootingURL, } node := coordinator.Node(dbAgent.ID) if node != nil { diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index fcd13635ba772..efedc9c2ed16d 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -74,6 +74,48 @@ func TestWorkspaceAgent(t *testing.T) { _, err = client.WorkspaceAgent(ctx, workspace.LatestBuild.Resources[0].Agents[0].ID) require.NoError(t, err) }) + t.Run("HasFallbackTroubleshootingURL", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }) + user := coderdtest.CreateFirstUser(t, client) + authToken := uuid.NewString() + tmpDir := t.TempDir() + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionDryRun: echo.ProvisionComplete, + Provision: []*proto.Provision_Response{{ + Type: &proto.Provision_Response_Complete{ + Complete: &proto.Provision_Complete{ + Resources: []*proto.Resource{{ + Name: "example", + Type: "aws_instance", + Agents: []*proto.Agent{{ + Id: uuid.NewString(), + Directory: tmpDir, + Auth: &proto.Agent_Token{ + Token: authToken, + }, + }}, + }}, + }, + }, + }}, + }) + template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) + coderdtest.AwaitTemplateVersionJob(t, client, version.ID) + workspace := coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID) + coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) + defer cancel() + + workspace, err := client.Workspace(ctx, workspace.ID) + require.NoError(t, err) + require.NotEmpty(t, workspace.LatestBuild.Resources[0].Agents[0].TroubleshootingURL) + t.Log(workspace.LatestBuild.Resources[0].Agents[0].TroubleshootingURL) + }) t.Run("Timeout", func(t *testing.T) { t.Parallel() client := coderdtest.New(t, &coderdtest.Options{ @@ -82,6 +124,9 @@ func TestWorkspaceAgent(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) authToken := uuid.NewString() tmpDir := t.TempDir() + + wantTroubleshootingURL := "https://example.com/troubleshoot" + version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ Parse: echo.ParseComplete, ProvisionDryRun: echo.ProvisionComplete, @@ -98,7 +143,7 @@ func TestWorkspaceAgent(t *testing.T) { Token: authToken, }, ConnectionTimeoutSeconds: 1, - TroubleshootingUrl: "https://example.com/troubleshoot", + TroubleshootingUrl: wantTroubleshootingURL, }}, }}, }, @@ -113,13 +158,16 @@ func TestWorkspaceAgent(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitMedium) defer cancel() + var err error testutil.Eventually(ctx, t, func(ctx context.Context) (done bool) { - workspace, err := client.Workspace(ctx, workspace.ID) + workspace, err = client.Workspace(ctx, workspace.ID) if !assert.NoError(t, err) { return false } return workspace.LatestBuild.Resources[0].Agents[0].Status == codersdk.WorkspaceAgentTimeout }, testutil.IntervalMedium, "agent status timeout") + + require.Equal(t, wantTroubleshootingURL, workspace.LatestBuild.Resources[0].Agents[0].TroubleshootingURL) }) } diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index 54f8d2a9affaa..4e1cff9147030 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -902,7 +902,7 @@ func (api *API) convertWorkspaceBuild( apiAgents := make([]codersdk.WorkspaceAgent, 0) for _, agent := range agents { apps := appsByAgentID[agent.ID] - apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), agent, convertApps(apps), api.AgentInactiveDisconnectTimeout) + apiAgent, err := convertWorkspaceAgent(api.DERPMap, *api.TailnetCoordinator.Load(), agent, convertApps(apps), api.AgentInactiveDisconnectTimeout, api.DeploymentConfig.AgentFallbackTroubleshootingURL.Value) if err != nil { return codersdk.WorkspaceBuild{}, xerrors.Errorf("converting workspace agent: %w", err) } diff --git a/codersdk/deploymentconfig.go b/codersdk/deploymentconfig.go index bc51fd806a447..0df85805f89bc 100644 --- a/codersdk/deploymentconfig.go +++ b/codersdk/deploymentconfig.go @@ -11,35 +11,36 @@ import ( // DeploymentConfig is the central configuration for the coder server. type DeploymentConfig struct { - AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"` - WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"` - Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` - AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"` - DERP *DERP `json:"derp" typescript:",notnull"` - GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"` - Prometheus *PrometheusConfig `json:"prometheus" typescript:",notnull"` - Pprof *PprofConfig `json:"pprof" typescript:",notnull"` - ProxyTrustedHeaders *DeploymentConfigField[[]string] `json:"proxy_trusted_headers" typescript:",notnull"` - ProxyTrustedOrigins *DeploymentConfigField[[]string] `json:"proxy_trusted_origins" typescript:",notnull"` - CacheDirectory *DeploymentConfigField[string] `json:"cache_directory" typescript:",notnull"` - InMemoryDatabase *DeploymentConfigField[bool] `json:"in_memory_database" typescript:",notnull"` - PostgresURL *DeploymentConfigField[string] `json:"pg_connection_url" typescript:",notnull"` - OAuth2 *OAuth2Config `json:"oauth2" typescript:",notnull"` - OIDC *OIDCConfig `json:"oidc" typescript:",notnull"` - Telemetry *TelemetryConfig `json:"telemetry" typescript:",notnull"` - TLS *TLSConfig `json:"tls" typescript:",notnull"` - Trace *TraceConfig `json:"trace" typescript:",notnull"` - SecureAuthCookie *DeploymentConfigField[bool] `json:"secure_auth_cookie" typescript:",notnull"` - SSHKeygenAlgorithm *DeploymentConfigField[string] `json:"ssh_keygen_algorithm" typescript:",notnull"` - AutoImportTemplates *DeploymentConfigField[[]string] `json:"auto_import_templates" typescript:",notnull"` - MetricsCacheRefreshInterval *DeploymentConfigField[time.Duration] `json:"metrics_cache_refresh_interval" typescript:",notnull"` - AgentStatRefreshInterval *DeploymentConfigField[time.Duration] `json:"agent_stat_refresh_interval" typescript:",notnull"` - AuditLogging *DeploymentConfigField[bool] `json:"audit_logging" typescript:",notnull"` - BrowserOnly *DeploymentConfigField[bool] `json:"browser_only" typescript:",notnull"` - SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"` - UserWorkspaceQuota *DeploymentConfigField[int] `json:"user_workspace_quota" typescript:",notnull"` - Provisioner *ProvisionerConfig `json:"provisioner" typescript:",notnull"` - Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"` + AccessURL *DeploymentConfigField[string] `json:"access_url" typescript:",notnull"` + WildcardAccessURL *DeploymentConfigField[string] `json:"wildcard_access_url" typescript:",notnull"` + Address *DeploymentConfigField[string] `json:"address" typescript:",notnull"` + AutobuildPollInterval *DeploymentConfigField[time.Duration] `json:"autobuild_poll_interval" typescript:",notnull"` + DERP *DERP `json:"derp" typescript:",notnull"` + GitAuth *DeploymentConfigField[[]GitAuthConfig] `json:"gitauth" typescript:",notnull"` + Prometheus *PrometheusConfig `json:"prometheus" typescript:",notnull"` + Pprof *PprofConfig `json:"pprof" typescript:",notnull"` + ProxyTrustedHeaders *DeploymentConfigField[[]string] `json:"proxy_trusted_headers" typescript:",notnull"` + ProxyTrustedOrigins *DeploymentConfigField[[]string] `json:"proxy_trusted_origins" typescript:",notnull"` + CacheDirectory *DeploymentConfigField[string] `json:"cache_directory" typescript:",notnull"` + InMemoryDatabase *DeploymentConfigField[bool] `json:"in_memory_database" typescript:",notnull"` + PostgresURL *DeploymentConfigField[string] `json:"pg_connection_url" typescript:",notnull"` + OAuth2 *OAuth2Config `json:"oauth2" typescript:",notnull"` + OIDC *OIDCConfig `json:"oidc" typescript:",notnull"` + Telemetry *TelemetryConfig `json:"telemetry" typescript:",notnull"` + TLS *TLSConfig `json:"tls" typescript:",notnull"` + Trace *TraceConfig `json:"trace" typescript:",notnull"` + SecureAuthCookie *DeploymentConfigField[bool] `json:"secure_auth_cookie" typescript:",notnull"` + SSHKeygenAlgorithm *DeploymentConfigField[string] `json:"ssh_keygen_algorithm" typescript:",notnull"` + AutoImportTemplates *DeploymentConfigField[[]string] `json:"auto_import_templates" typescript:",notnull"` + MetricsCacheRefreshInterval *DeploymentConfigField[time.Duration] `json:"metrics_cache_refresh_interval" typescript:",notnull"` + AgentStatRefreshInterval *DeploymentConfigField[time.Duration] `json:"agent_stat_refresh_interval" typescript:",notnull"` + AgentFallbackTroubleshootingURL *DeploymentConfigField[string] `json:"agent_fallback_troubleshooting_url" typescript:",notnull"` + AuditLogging *DeploymentConfigField[bool] `json:"audit_logging" typescript:",notnull"` + BrowserOnly *DeploymentConfigField[bool] `json:"browser_only" typescript:",notnull"` + SCIMAPIKey *DeploymentConfigField[string] `json:"scim_api_key" typescript:",notnull"` + UserWorkspaceQuota *DeploymentConfigField[int] `json:"user_workspace_quota" typescript:",notnull"` + Provisioner *ProvisionerConfig `json:"provisioner" typescript:",notnull"` + Experimental *DeploymentConfigField[bool] `json:"experimental" typescript:",notnull"` } type DERP struct { From 2cb3d8aaf6513eb565def20881428fb34d4508e4 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 14 Nov 2022 16:39:49 +0000 Subject: [PATCH 4/8] Fix usage --- cli/deployment/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/deployment/config.go b/cli/deployment/config.go index 51803d45d612a..eb36d821e5866 100644 --- a/cli/deployment/config.go +++ b/cli/deployment/config.go @@ -334,7 +334,7 @@ func newConfig() *codersdk.DeploymentConfig { }, AgentFallbackTroubleshootingURL: &codersdk.DeploymentConfigField[string]{ Name: "Agent Fallback Troubleshooting URL", - Usage: "URL to use for agent fallback troubleshooting", + Usage: "URL to use for agent troubleshooting when not set in the template", Flag: "agent-fallback-troubleshooting-url", Hidden: true, Default: "https://coder.com/docs/coder-oss/latest/templates#troubleshooting-templates", From 54d2246bacec3cadb30e4a7a6c914715246e0fe3 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 14 Nov 2022 16:41:28 +0000 Subject: [PATCH 5/8] Update troubleshooting URL optionality --- codersdk/workspaceagents.go | 2 +- site/src/api/typesGenerated.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/codersdk/workspaceagents.go b/codersdk/workspaceagents.go index 8f6d43f6eaf4e..089cded561dc1 100644 --- a/codersdk/workspaceagents.go +++ b/codersdk/workspaceagents.go @@ -56,7 +56,7 @@ type WorkspaceAgent struct { // DERPLatency is mapped by region name (e.g. "New York City", "Seattle"). DERPLatency map[string]DERPRegion `json:"latency,omitempty"` ConnectionTimeoutSeconds int32 `json:"connection_timeout_seconds"` - TroubleshootingURL string `json:"troubleshooting_url,omitempty"` + TroubleshootingURL string `json:"troubleshooting_url"` } type WorkspaceAgentResourceMetadata struct { diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index bb8d4fa38c16f..fc805cc2ac400 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -297,6 +297,7 @@ export interface DeploymentConfig { readonly auto_import_templates: DeploymentConfigField readonly metrics_cache_refresh_interval: DeploymentConfigField readonly agent_stat_refresh_interval: DeploymentConfigField + readonly agent_fallback_troubleshooting_url: DeploymentConfigField readonly audit_logging: DeploymentConfigField readonly browser_only: DeploymentConfigField readonly scim_api_key: DeploymentConfigField @@ -811,7 +812,7 @@ export interface WorkspaceAgent { readonly apps: WorkspaceApp[] readonly latency?: Record readonly connection_timeout_seconds: number - readonly troubleshooting_url?: string + readonly troubleshooting_url: string } // From codersdk/workspaceagents.go From ec410af98df548980b74ed253d5d2e25ba56d102 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 14 Nov 2022 16:50:38 +0000 Subject: [PATCH 6/8] Fix build, update site --- coderd/workspaceagents_test.go | 6 +++--- site/src/components/Resources/AgentStatus.tsx | 12 ++++-------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/coderd/workspaceagents_test.go b/coderd/workspaceagents_test.go index 3a875b76e48cd..25e0010cab057 100644 --- a/coderd/workspaceagents_test.go +++ b/coderd/workspaceagents_test.go @@ -128,9 +128,9 @@ func TestWorkspaceAgent(t *testing.T) { wantTroubleshootingURL := "https://example.com/troubleshoot" version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{ - Parse: echo.ParseComplete, - ProvisionDryRun: echo.ProvisionComplete, - Provision: []*proto.Provision_Response{{ + Parse: echo.ParseComplete, + ProvisionPlan: echo.ProvisionComplete, + ProvisionApply: []*proto.Provision_Response{{ Type: &proto.Provision_Response_Complete{ Complete: &proto.Provision_Complete{ Resources: []*proto.Resource{{ diff --git a/site/src/components/Resources/AgentStatus.tsx b/site/src/components/Resources/AgentStatus.tsx index 203f721845f30..b4bc60c438a2c 100644 --- a/site/src/components/Resources/AgentStatus.tsx +++ b/site/src/components/Resources/AgentStatus.tsx @@ -58,15 +58,12 @@ const ConnectingStatus: React.FC = () => { const TimeoutStatus: React.FC<{ agent: WorkspaceAgent - workspace: Workspace -}> = ({ agent, workspace }) => { +}> = ({ agent }) => { const { t } = useTranslation("agent") const styles = useStyles() const anchorRef = useRef(null) const [isOpen, setIsOpen] = useState(false) const id = isOpen ? "timeout-popover" : undefined - const troubleshootLink = - agent.troubleshooting_url ?? `/templates/${workspace.template_name}#readme` return ( <> @@ -88,7 +85,7 @@ const TimeoutStatus: React.FC<{ {t("timeoutTooltip.title")} {t("timeoutTooltip.message")}{" "} - + {t("timeoutTooltip.link")} . @@ -100,8 +97,7 @@ const TimeoutStatus: React.FC<{ export const AgentStatus: React.FC<{ agent: WorkspaceAgent - workspace: Workspace -}> = ({ agent, workspace }) => { +}> = ({ agent }) => { return ( @@ -111,7 +107,7 @@ export const AgentStatus: React.FC<{ - + From 2d7586a9edfa666aa1a5106dccfd61a9d8079926 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 14 Nov 2022 17:59:23 +0000 Subject: [PATCH 7/8] Fix js lint --- site/src/components/Resources/AgentRow.tsx | 2 +- site/src/components/Resources/AgentStatus.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/site/src/components/Resources/AgentRow.tsx b/site/src/components/Resources/AgentRow.tsx index e53c2678a55b7..d0fbc112fbb44 100644 --- a/site/src/components/Resources/AgentRow.tsx +++ b/site/src/components/Resources/AgentRow.tsx @@ -45,7 +45,7 @@ export const AgentRow: FC = ({ >
- +
{agent.name}
diff --git a/site/src/components/Resources/AgentStatus.tsx b/site/src/components/Resources/AgentStatus.tsx index b4bc60c438a2c..0ecb923ea2752 100644 --- a/site/src/components/Resources/AgentStatus.tsx +++ b/site/src/components/Resources/AgentStatus.tsx @@ -1,7 +1,7 @@ import Tooltip from "@material-ui/core/Tooltip" import { makeStyles } from "@material-ui/core/styles" import { combineClasses } from "util/combineClasses" -import { Workspace, WorkspaceAgent } from "api/typesGenerated" +import { WorkspaceAgent } from "api/typesGenerated" import { ChooseOne, Cond } from "components/Conditionals/ChooseOne" import { useTranslation } from "react-i18next" import WarningRounded from "@material-ui/icons/WarningRounded" From 7e2898242f427601bf1bc2aaa45d589839c778d0 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 14 Nov 2022 18:32:06 +0000 Subject: [PATCH 8/8] Lint --- site/src/components/Resources/AgentStatus.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/site/src/components/Resources/AgentStatus.tsx b/site/src/components/Resources/AgentStatus.tsx index 0ecb923ea2752..48de1597c8ca3 100644 --- a/site/src/components/Resources/AgentStatus.tsx +++ b/site/src/components/Resources/AgentStatus.tsx @@ -85,7 +85,11 @@ const TimeoutStatus: React.FC<{ {t("timeoutTooltip.title")} {t("timeoutTooltip.message")}{" "} - + {t("timeoutTooltip.link")} .