mirror of https://github.com/coder/coder.git
fix: avoid returning 500 on apps when workspace stopped (#11656)
This commit is contained in:
parent
1be119b08f
commit
552e9fe22f
|
@ -26,6 +26,7 @@ import (
|
|||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/coderdtest"
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/rbac"
|
||||
"github.com/coder/coder/v2/coderd/workspaceapps"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
|
@ -1484,6 +1485,24 @@ func Run(t *testing.T, appHostIsPrimary bool, factory DeploymentFactory) {
|
|||
assert.Equal(t, "test-app-owner", stats[0].SlugOrPort)
|
||||
assert.Equal(t, 1, stats[0].Requests)
|
||||
})
|
||||
|
||||
t.Run("WorkspaceOffline", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
appDetails := setupProxyTest(t, nil)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
|
||||
defer cancel()
|
||||
|
||||
_ = coderdtest.MustTransitionWorkspace(t, appDetails.SDKClient, appDetails.Workspace.ID, database.WorkspaceTransitionStart, database.WorkspaceTransitionStop)
|
||||
|
||||
u := appDetails.PathAppURL(appDetails.Apps.Owner)
|
||||
resp, err := appDetails.AppClient(t).Request(ctx, http.MethodGet, u.String(), nil)
|
||||
require.NoError(t, err)
|
||||
_ = resp.Body.Close()
|
||||
require.Equal(t, http.StatusBadRequest, resp.StatusCode)
|
||||
require.Equal(t, "text/html; charset=utf-8", resp.Header.Get("Content-Type"))
|
||||
})
|
||||
}
|
||||
|
||||
type fakeStatsReporter struct {
|
||||
|
|
|
@ -103,6 +103,9 @@ func (p *DBTokenProvider) Issue(ctx context.Context, rw http.ResponseWriter, r *
|
|||
if xerrors.Is(err, sql.ErrNoRows) {
|
||||
WriteWorkspaceApp404(p.Logger, p.DashboardURL, rw, r, &appReq, nil, err.Error())
|
||||
return nil, "", false
|
||||
} else if xerrors.Is(err, errWorkspaceStopped) {
|
||||
WriteWorkspaceOffline(p.Logger, p.DashboardURL, rw, r, &appReq)
|
||||
return nil, "", false
|
||||
} else if err != nil {
|
||||
WriteWorkspaceApp500(p.Logger, p.DashboardURL, rw, r, &appReq, err, "get app details from database")
|
||||
return nil, "", false
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
package workspaceapps
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/site"
|
||||
)
|
||||
|
||||
|
@ -90,3 +92,28 @@ func WriteWorkspaceAppOffline(log slog.Logger, accessURL *url.URL, rw http.Respo
|
|||
DashboardURL: accessURL.String(),
|
||||
})
|
||||
}
|
||||
|
||||
// WriteWorkspaceOffline writes a HTML 400 error page for a workspace app. If
|
||||
// appReq is not nil, it will be used to log the request details at debug level.
|
||||
func WriteWorkspaceOffline(log slog.Logger, accessURL *url.URL, rw http.ResponseWriter, r *http.Request, appReq *Request) {
|
||||
if appReq != nil {
|
||||
slog.Helper()
|
||||
log.Debug(r.Context(),
|
||||
"workspace app unavailable: workspace stopped",
|
||||
slog.F("username_or_id", appReq.UsernameOrID),
|
||||
slog.F("workspace_and_agent", appReq.WorkspaceAndAgent),
|
||||
slog.F("workspace_name_or_id", appReq.WorkspaceNameOrID),
|
||||
slog.F("agent_name_or_id", appReq.AgentNameOrID),
|
||||
slog.F("app_slug_or_port", appReq.AppSlugOrPort),
|
||||
slog.F("hostname_prefix", appReq.Prefix),
|
||||
)
|
||||
}
|
||||
|
||||
site.RenderStaticErrorPage(rw, r, site.ErrorPageData{
|
||||
Status: http.StatusBadRequest,
|
||||
Title: "Workspace Offline",
|
||||
Description: fmt.Sprintf("Last workspace transition was to the %q state. Start the workspace to access its applications.", codersdk.WorkspaceTransitionStop),
|
||||
RetryEnabled: false,
|
||||
DashboardURL: accessURL.String(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@ import (
|
|||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
var errWorkspaceStopped = xerrors.New("stopped workspace")
|
||||
|
||||
type AccessMethod string
|
||||
|
||||
const (
|
||||
|
@ -260,10 +262,17 @@ func (r Request) getDatabase(ctx context.Context, db database.Store) (*databaseR
|
|||
if err != nil {
|
||||
return nil, xerrors.Errorf("get workspace agents: %w", err)
|
||||
}
|
||||
build, err := db.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("get latest workspace build: %w", err)
|
||||
}
|
||||
if build.Transition == database.WorkspaceTransitionStop {
|
||||
return nil, errWorkspaceStopped
|
||||
}
|
||||
if len(agents) == 0 {
|
||||
// TODO(@deansheather): return a 404 if there are no agents in the
|
||||
// workspace, requires a different error type.
|
||||
return nil, xerrors.New("no agents in workspace")
|
||||
return nil, xerrors.Errorf("no agents in workspace: %w", sql.ErrNoRows)
|
||||
}
|
||||
|
||||
// Get workspace apps.
|
||||
|
|
|
@ -191,7 +191,8 @@ const TerminalPage: FC = () => {
|
|||
return;
|
||||
} else if (!workspaceAgent) {
|
||||
terminal.writeln(
|
||||
Language.workspaceAgentErrorMessagePrefix + "no agent found with ID",
|
||||
Language.workspaceAgentErrorMessagePrefix +
|
||||
"no agent found with ID, is the workspace started?",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue