| 116 | } |
| 117 | |
| 118 | func (a *AppsAPI) UpdateAppStatus(ctx context.Context, req *agentproto.UpdateAppStatusRequest) (*agentproto.UpdateAppStatusResponse, error) { |
| 119 | if len(req.Message) > 160 { |
| 120 | return nil, codersdk.NewError(http.StatusBadRequest, codersdk.Response{ |
| 121 | Message: "Message is too long.", |
| 122 | Detail: "Message must be less than 160 characters.", |
| 123 | Validations: []codersdk.ValidationError{ |
| 124 | {Field: "message", Detail: "Message must be less than 160 characters."}, |
| 125 | }, |
| 126 | }) |
| 127 | } |
| 128 | |
| 129 | var dbState database.WorkspaceAppStatusState |
| 130 | switch req.State { |
| 131 | case agentproto.UpdateAppStatusRequest_COMPLETE: |
| 132 | dbState = database.WorkspaceAppStatusStateComplete |
| 133 | case agentproto.UpdateAppStatusRequest_FAILURE: |
| 134 | dbState = database.WorkspaceAppStatusStateFailure |
| 135 | case agentproto.UpdateAppStatusRequest_WORKING: |
| 136 | dbState = database.WorkspaceAppStatusStateWorking |
| 137 | case agentproto.UpdateAppStatusRequest_IDLE: |
| 138 | dbState = database.WorkspaceAppStatusStateIdle |
| 139 | default: |
| 140 | return nil, codersdk.NewError(http.StatusBadRequest, codersdk.Response{ |
| 141 | Message: "Invalid state provided.", |
| 142 | Detail: fmt.Sprintf("invalid state: %q", req.State), |
| 143 | Validations: []codersdk.ValidationError{ |
| 144 | {Field: "state", Detail: "State must be one of: complete, failure, working, idle."}, |
| 145 | }, |
| 146 | }) |
| 147 | } |
| 148 | |
| 149 | app, err := a.Database.GetWorkspaceAppByAgentIDAndSlug(ctx, database.GetWorkspaceAppByAgentIDAndSlugParams{ |
| 150 | AgentID: a.AgentID, |
| 151 | Slug: req.Slug, |
| 152 | }) |
| 153 | if err != nil { |
| 154 | return nil, codersdk.NewError(http.StatusBadRequest, codersdk.Response{ |
| 155 | Message: "Failed to get workspace app.", |
| 156 | Detail: fmt.Sprintf("No app found with slug %q", req.Slug), |
| 157 | }) |
| 158 | } |
| 159 | |
| 160 | ws, ok := a.Workspace.AsWorkspaceIdentity() |
| 161 | if !ok { |
| 162 | return nil, codersdk.NewError(http.StatusInternalServerError, codersdk.Response{ |
| 163 | Message: "Workspace identity not cached.", |
| 164 | }) |
| 165 | } |
| 166 | |
| 167 | // Treat the message as untrusted input. |
| 168 | cleaned := strutil.UISanitize(req.Message) |
| 169 | |
| 170 | // Get the latest status for the workspace app to detect no-op updates |
| 171 | // nolint:gocritic // This is a system restricted operation. |
| 172 | latestAppStatus, err := a.Database.GetLatestWorkspaceAppStatusByAppID(dbauthz.AsSystemRestricted(ctx), app.ID) |
| 173 | if err != nil && !xerrors.Is(err, sql.ErrNoRows) { |
| 174 | return nil, codersdk.NewError(http.StatusInternalServerError, codersdk.Response{ |
| 175 | Message: "Failed to get latest workspace app status.", |