diff --git a/coderd/audit.go b/coderd/audit.go index 8a87b1297d..c9fbb3a9a8 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -21,12 +21,12 @@ import ( ) func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() if !api.Authorize(r, rbac.ActionRead, rbac.ResourceAuditLog) { httpapi.Forbidden(rw) return } - ctx := r.Context() page, ok := parsePagination(rw, r) if !ok { return @@ -35,7 +35,7 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) { queryStr := r.URL.Query().Get("q") filter, errs := auditSearchQuery(queryStr) if len(errs) > 0 { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid audit search query.", Validations: errs, }) @@ -56,7 +56,7 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, codersdk.AuditLogResponse{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.AuditLogResponse{ AuditLogs: convertAuditLogs(dblogs), }) } @@ -71,7 +71,7 @@ func (api *API) auditLogCount(rw http.ResponseWriter, r *http.Request) { queryStr := r.URL.Query().Get("q") filter, errs := auditSearchQuery(queryStr) if len(errs) > 0 { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid audit search query.", Validations: errs, }) @@ -90,7 +90,7 @@ func (api *API) auditLogCount(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, codersdk.AuditLogCountResponse{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.AuditLogCountResponse{ Count: count, }) } @@ -131,7 +131,7 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) { } var params codersdk.CreateTestAuditLogRequest - if !httpapi.Read(rw, r, ¶ms) { + if !httpapi.Read(ctx, rw, r, ¶ms) { return } if params.Action == "" { diff --git a/coderd/coderd.go b/coderd/coderd.go index 571086a6c2..a04b36a631 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -188,7 +188,7 @@ func New(options *Options) *API { // Build-Version is helpful for debugging. func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Add("Build-Version", buildinfo.Version()) + w.Header().Add("X-Coder-Build-Version", buildinfo.Version()) next.ServeHTTP(w, r) }) }, @@ -229,7 +229,7 @@ func New(options *Options) *API { httpmw.RateLimitPerMinute(options.APIRateLimit), ) r.Get("/", func(w http.ResponseWriter, r *http.Request) { - httpapi.Write(w, http.StatusOK, codersdk.Response{ + httpapi.Write(r.Context(), w, http.StatusOK, codersdk.Response{ //nolint:gocritic Message: "👋", }) @@ -239,7 +239,7 @@ func New(options *Options) *API { r.Route("/buildinfo", func(r chi.Router) { r.Get("/", func(rw http.ResponseWriter, r *http.Request) { - httpapi.Write(rw, http.StatusOK, codersdk.BuildInfoResponse{ + httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.BuildInfoResponse{ ExternalURL: buildinfo.ExternalURL(), Version: buildinfo.Version(), }) @@ -430,7 +430,7 @@ func New(options *Options) *API { // error message when transitioning from WebRTC to Tailscale. See: // https://github.com/coder/coder/issues/4126 r.Get("/dial", func(w http.ResponseWriter, r *http.Request) { - httpapi.Write(w, http.StatusGone, codersdk.Response{ + httpapi.Write(r.Context(), w, http.StatusGone, codersdk.Response{ Message: "Your Coder CLI is out of date, and requires v0.8.15+ to connect!", }) }) diff --git a/coderd/csp.go b/coderd/csp.go index f815bc48bd..473cf6fd60 100644 --- a/coderd/csp.go +++ b/coderd/csp.go @@ -23,7 +23,7 @@ func (api *API) logReportCSPViolations(rw http.ResponseWriter, r *http.Request) err := dec.Decode(&v) if err != nil { api.Logger.Warn(ctx, "csp violation", slog.Error(err)) - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to read body, invalid json.", Detail: err.Error(), }) @@ -36,5 +36,5 @@ func (api *API) logReportCSPViolations(rw http.ResponseWriter, r *http.Request) } api.Logger.Warn(ctx, "csp violation", fields...) - httpapi.Write(rw, http.StatusOK, "ok") + httpapi.Write(ctx, rw, http.StatusOK, "ok") } diff --git a/coderd/files.go b/coderd/files.go index 1b2728f9cc..8f8c39fd96 100644 --- a/coderd/files.go +++ b/coderd/files.go @@ -19,6 +19,7 @@ import ( ) func (api *API) postFile(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() apiKey := httpmw.APIKey(r) // This requires the site wide action to create files. // Once created, a user can read their own files uploaded @@ -32,7 +33,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) { switch contentType { case "application/x-tar": default: - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Unsupported content type header %q.", contentType), }) return @@ -41,7 +42,7 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) { r.Body = http.MaxBytesReader(rw, r.Body, 10*(10<<20)) data, err := io.ReadAll(r.Body) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to read file from request.", Detail: err.Error(), }) @@ -49,15 +50,15 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) { } hashBytes := sha256.Sum256(data) hash := hex.EncodeToString(hashBytes[:]) - file, err := api.Database.GetFileByHash(r.Context(), hash) + file, err := api.Database.GetFileByHash(ctx, hash) if err == nil { // The file already exists! - httpapi.Write(rw, http.StatusOK, codersdk.UploadResponse{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.UploadResponse{ Hash: file.Hash, }) return } - file, err = api.Database.InsertFile(r.Context(), database.InsertFileParams{ + file, err = api.Database.InsertFile(ctx, database.InsertFileParams{ Hash: hash, CreatedBy: apiKey.UserID, CreatedAt: database.Now(), @@ -65,33 +66,34 @@ func (api *API) postFile(rw http.ResponseWriter, r *http.Request) { Data: data, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error saving file.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusCreated, codersdk.UploadResponse{ + httpapi.Write(ctx, rw, http.StatusCreated, codersdk.UploadResponse{ Hash: file.Hash, }) } func (api *API) fileByHash(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() hash := chi.URLParam(r, "hash") if hash == "" { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "File hash must be provided in url.", }) return } - file, err := api.Database.GetFileByHash(r.Context(), hash) + file, err := api.Database.GetFileByHash(ctx, hash) if errors.Is(err, sql.ErrNoRows) { httpapi.ResourceNotFound(rw) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching file.", Detail: err.Error(), }) diff --git a/coderd/gitsshkey.go b/coderd/gitsshkey.go index d4b035e654..c65600e0d3 100644 --- a/coderd/gitsshkey.go +++ b/coderd/gitsshkey.go @@ -12,6 +12,7 @@ import ( ) func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() user := httpmw.UserParam(r) if !api.Authorize(r, rbac.ActionUpdate, rbac.ResourceUserData.WithOwner(user.ID.String())) { @@ -21,37 +22,37 @@ func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) { privateKey, publicKey, err := gitsshkey.Generate(api.SSHKeygenAlgorithm) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error generating a new SSH keypair.", Detail: err.Error(), }) return } - err = api.Database.UpdateGitSSHKey(r.Context(), database.UpdateGitSSHKeyParams{ + err = api.Database.UpdateGitSSHKey(ctx, database.UpdateGitSSHKeyParams{ UserID: user.ID, UpdatedAt: database.Now(), PrivateKey: privateKey, PublicKey: publicKey, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating user's git SSH key.", Detail: err.Error(), }) return } - newKey, err := api.Database.GetGitSSHKey(r.Context(), user.ID) + newKey, err := api.Database.GetGitSSHKey(ctx, user.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user's git SSH key.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.GitSSHKey{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.GitSSHKey{ UserID: newKey.UserID, CreatedAt: newKey.CreatedAt, UpdatedAt: newKey.UpdatedAt, @@ -61,6 +62,7 @@ func (api *API) regenerateGitSSHKey(rw http.ResponseWriter, r *http.Request) { } func (api *API) gitSSHKey(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() user := httpmw.UserParam(r) if !api.Authorize(r, rbac.ActionRead, rbac.ResourceUserData.WithOwner(user.ID.String())) { @@ -68,16 +70,16 @@ func (api *API) gitSSHKey(rw http.ResponseWriter, r *http.Request) { return } - gitSSHKey, err := api.Database.GetGitSSHKey(r.Context(), user.ID) + gitSSHKey, err := api.Database.GetGitSSHKey(ctx, user.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user's SSH key.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.GitSSHKey{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.GitSSHKey{ UserID: gitSSHKey.UserID, CreatedAt: gitSSHKey.CreatedAt, UpdatedAt: gitSSHKey.UpdatedAt, @@ -87,44 +89,45 @@ func (api *API) gitSSHKey(rw http.ResponseWriter, r *http.Request) { } func (api *API) agentGitSSHKey(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() agent := httpmw.WorkspaceAgent(r) - resource, err := api.Database.GetWorkspaceResourceByID(r.Context(), agent.ResourceID) + resource, err := api.Database.GetWorkspaceResourceByID(ctx, agent.ResourceID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace resource.", Detail: err.Error(), }) return } - job, err := api.Database.GetWorkspaceBuildByJobID(r.Context(), resource.JobID) + job, err := api.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace build.", Detail: err.Error(), }) return } - workspace, err := api.Database.GetWorkspaceByID(r.Context(), job.WorkspaceID) + workspace, err := api.Database.GetWorkspaceByID(ctx, job.WorkspaceID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace.", Detail: err.Error(), }) return } - gitSSHKey, err := api.Database.GetGitSSHKey(r.Context(), workspace.OwnerID) + gitSSHKey, err := api.Database.GetGitSSHKey(ctx, workspace.OwnerID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching git SSH key.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.AgentGitSSHKey{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.AgentGitSSHKey{ PublicKey: gitSSHKey.PublicKey, PrivateKey: gitSSHKey.PrivateKey, }) diff --git a/coderd/httpapi/httpapi.go b/coderd/httpapi/httpapi.go index 56bdfcaa6a..3d3def7ee4 100644 --- a/coderd/httpapi/httpapi.go +++ b/coderd/httpapi/httpapi.go @@ -15,6 +15,7 @@ import ( "github.com/go-playground/validator/v10" + "github.com/coder/coder/coderd/tracing" "github.com/coder/coder/codersdk" ) @@ -49,16 +50,19 @@ func init() { } } +// Convenience error functions don't take contexts since their responses are +// static, it doesn't make much sense to trace them. + // ResourceNotFound is intentionally vague. All 404 responses should be identical // to prevent leaking existence of resources. func ResourceNotFound(rw http.ResponseWriter) { - Write(rw, http.StatusNotFound, codersdk.Response{ + Write(context.Background(), rw, http.StatusNotFound, codersdk.Response{ Message: "Resource not found or you do not have access to this resource", }) } func Forbidden(rw http.ResponseWriter) { - Write(rw, http.StatusForbidden, codersdk.Response{ + Write(context.Background(), rw, http.StatusForbidden, codersdk.Response{ Message: "Forbidden.", }) } @@ -69,20 +73,29 @@ func InternalServerError(rw http.ResponseWriter, err error) { details = err.Error() } - Write(rw, http.StatusInternalServerError, codersdk.Response{ + Write(context.Background(), rw, http.StatusInternalServerError, codersdk.Response{ Message: "An internal server error occurred.", Detail: details, }) } func RouteNotFound(rw http.ResponseWriter) { - Write(rw, http.StatusNotFound, codersdk.Response{ + Write(context.Background(), rw, http.StatusNotFound, codersdk.Response{ Message: "Route not found.", }) } -// Write outputs a standardized format to an HTTP response body. -func Write(rw http.ResponseWriter, status int, response interface{}) { +// Write outputs a standardized format to an HTTP response body. ctx is used for +// tracing and can be nil for tracing to be disabled. Tracing this function is +// helpful because JSON marshaling can sometimes take a non-insignificant amount +// of time, and could help us catch outliers. Additionally, we can enrich span +// data a bit more since we have access to the actual interface{} we're +// marshaling, such as the number of elements in an array, which could help us +// spot routes that need to be paginated. +func Write(ctx context.Context, rw http.ResponseWriter, status int, response interface{}) { + _, span := tracing.StartSpan(ctx) + defer span.End() + buf := &bytes.Buffer{} enc := json.NewEncoder(buf) enc.SetEscapeHTML(true) @@ -100,12 +113,17 @@ func Write(rw http.ResponseWriter, status int, response interface{}) { } } -// Read decodes JSON from the HTTP request into the value provided. -// It uses go-validator to validate the incoming request body. -func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool { +// Read decodes JSON from the HTTP request into the value provided. It uses +// go-validator to validate the incoming request body. ctx is used for tracing +// and can be nil. Although tracing this function isn't likely too helpful, it +// was done to be consistent with Write. +func Read(ctx context.Context, rw http.ResponseWriter, r *http.Request, value interface{}) bool { + ctx, span := tracing.StartSpan(ctx) + defer span.End() + err := json.NewDecoder(r.Body).Decode(value) if err != nil { - Write(rw, http.StatusBadRequest, codersdk.Response{ + Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Request body must be valid JSON.", Detail: err.Error(), }) @@ -121,14 +139,14 @@ func Read(rw http.ResponseWriter, r *http.Request, value interface{}) bool { Detail: fmt.Sprintf("Validation failed for tag %q with value: \"%v\"", validationError.Tag(), validationError.Value()), }) } - Write(rw, http.StatusBadRequest, codersdk.Response{ + Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Validation failed.", Validations: apiErrors, }) return false } if err != nil { - Write(rw, http.StatusInternalServerError, codersdk.Response{ + Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error validating request body payload.", Detail: err.Error(), }) diff --git a/coderd/httpapi/httpapi_test.go b/coderd/httpapi/httpapi_test.go index 79a26d54a2..77ff9cdca6 100644 --- a/coderd/httpapi/httpapi_test.go +++ b/coderd/httpapi/httpapi_test.go @@ -2,6 +2,7 @@ package httpapi_test import ( "bytes" + "context" "encoding/json" "net/http" "net/http/httptest" @@ -54,8 +55,9 @@ func TestWrite(t *testing.T) { t.Parallel() t.Run("NoErrors", func(t *testing.T) { t.Parallel() + ctx := context.Background() rw := httptest.NewRecorder() - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "Wow.", }) var m map[string]interface{} @@ -70,18 +72,20 @@ func TestRead(t *testing.T) { t.Parallel() t.Run("EmptyStruct", func(t *testing.T) { t.Parallel() + ctx := context.Background() rw := httptest.NewRecorder() r := httptest.NewRequest("POST", "/", bytes.NewBufferString("{}")) v := struct{}{} - require.True(t, httpapi.Read(rw, r, &v)) + require.True(t, httpapi.Read(ctx, rw, r, &v)) }) t.Run("NoBody", func(t *testing.T) { t.Parallel() + ctx := context.Background() rw := httptest.NewRecorder() r := httptest.NewRequest("POST", "/", nil) var v json.RawMessage - require.False(t, httpapi.Read(rw, r, v)) + require.False(t, httpapi.Read(ctx, rw, r, v)) }) t.Run("Validate", func(t *testing.T) { @@ -89,11 +93,12 @@ func TestRead(t *testing.T) { type toValidate struct { Value string `json:"value" validate:"required"` } + ctx := context.Background() rw := httptest.NewRecorder() r := httptest.NewRequest("POST", "/", bytes.NewBufferString(`{"value":"hi"}`)) var validate toValidate - require.True(t, httpapi.Read(rw, r, &validate)) + require.True(t, httpapi.Read(ctx, rw, r, &validate)) require.Equal(t, "hi", validate.Value) }) @@ -102,11 +107,12 @@ func TestRead(t *testing.T) { type toValidate struct { Value string `json:"value" validate:"required"` } + ctx := context.Background() rw := httptest.NewRecorder() r := httptest.NewRequest("POST", "/", bytes.NewBufferString("{}")) var validate toValidate - require.False(t, httpapi.Read(rw, r, &validate)) + require.False(t, httpapi.Read(ctx, rw, r, &validate)) var v codersdk.Response err := json.NewDecoder(rw.Body).Decode(&v) require.NoError(t, err) diff --git a/coderd/httpmw/apikey.go b/coderd/httpmw/apikey.go index 3d11ba9849..ddec7d092b 100644 --- a/coderd/httpmw/apikey.go +++ b/coderd/httpmw/apikey.go @@ -91,6 +91,7 @@ func UseLoginURL(loginURL *url.URL) func(http.Handler) http.Handler { func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() // Write wraps writing a response to redirect if the handler // specified it should. This redirect is used for user-facing // pages like workspace applications. @@ -127,7 +128,7 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool return } - httpapi.Write(rw, code, response) + httpapi.Write(ctx, rw, code, response) } cookieValue := apiTokenFromRequest(r) @@ -329,7 +330,6 @@ func ExtractAPIKey(db database.Store, oauth *OAuth2Configs, redirectToLogin bool return } - ctx := r.Context() ctx = context.WithValue(ctx, apiKeyContextKey{}, key) ctx = context.WithValue(ctx, userAuthKey{}, Authorization{ ID: key.UserID, diff --git a/coderd/httpmw/apikey_test.go b/coderd/httpmw/apikey_test.go index e59065db66..e7844d4549 100644 --- a/coderd/httpmw/apikey_test.go +++ b/coderd/httpmw/apikey_test.go @@ -34,7 +34,7 @@ func TestAPIKey(t *testing.T) { successHandler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { // Only called if the API key passes through the handler. - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(context.Background(), rw, http.StatusOK, codersdk.Response{ Message: "It worked!", }) }) @@ -205,7 +205,7 @@ func TestAPIKey(t *testing.T) { httpmw.ExtractAPIKey(db, nil, false)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { // Checks that it exists on the context! _ = httpmw.APIKey(r) - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.Response{ Message: "It worked!", }) })).ServeHTTP(rw, r) @@ -249,7 +249,7 @@ func TestAPIKey(t *testing.T) { apiKey := httpmw.APIKey(r) assert.Equal(t, database.APIKeyScopeApplicationConnect, apiKey.Scope) - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.Response{ Message: "it worked!", }) })).ServeHTTP(rw, r) @@ -285,7 +285,7 @@ func TestAPIKey(t *testing.T) { httpmw.ExtractAPIKey(db, nil, false)(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { // Checks that it exists on the context! _ = httpmw.APIKey(r) - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.Response{ Message: "It worked!", }) })).ServeHTTP(rw, r) diff --git a/coderd/httpmw/httpmw.go b/coderd/httpmw/httpmw.go index 1dde571b87..11f363e7ea 100644 --- a/coderd/httpmw/httpmw.go +++ b/coderd/httpmw/httpmw.go @@ -15,7 +15,7 @@ import ( func parseUUID(rw http.ResponseWriter, r *http.Request, param string) (uuid.UUID, bool) { rawID := chi.URLParam(r, param) if rawID == "" { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusBadRequest, codersdk.Response{ Message: "Missing UUID in URL.", // Url params mean nothing to a user Detail: fmt.Sprintf("%q URL param missing", param), @@ -25,7 +25,7 @@ func parseUUID(rw http.ResponseWriter, r *http.Request, param string) (uuid.UUID parsed, err := uuid.Parse(rawID) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(r.Context(), rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Invalid UUID %q.", param), Detail: err.Error(), }) diff --git a/coderd/httpmw/oauth2.go b/coderd/httpmw/oauth2.go index 75f839ed76..aeebb544e3 100644 --- a/coderd/httpmw/oauth2.go +++ b/coderd/httpmw/oauth2.go @@ -43,9 +43,10 @@ func OAuth2(r *http.Request) OAuth2State { func ExtractOAuth2(config OAuth2Config) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() // Interfaces can hold a nil value if config == nil || reflect.ValueOf(config).IsNil() { - httpapi.Write(rw, http.StatusPreconditionRequired, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionRequired, codersdk.Response{ Message: "The oauth2 method requested is not configured!", }) return @@ -58,7 +59,7 @@ func ExtractOAuth2(config OAuth2Config) func(http.Handler) http.Handler { // If the code isn't provided, we'll redirect! state, err := cryptorand.String(32) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error generating state string.", Detail: err.Error(), }) @@ -87,7 +88,7 @@ func ExtractOAuth2(config OAuth2Config) func(http.Handler) http.Handler { } if state == "" { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "State must be provided.", }) return @@ -95,13 +96,13 @@ func ExtractOAuth2(config OAuth2Config) func(http.Handler) http.Handler { stateCookie, err := r.Cookie(codersdk.OAuth2StateKey) if err != nil { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: fmt.Sprintf("Cookie %q must be provided.", codersdk.OAuth2StateKey), }) return } if stateCookie.Value != state { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: "State mismatched.", }) return @@ -113,16 +114,16 @@ func ExtractOAuth2(config OAuth2Config) func(http.Handler) http.Handler { redirect = stateRedirect.Value } - oauthToken, err := config.Exchange(r.Context(), code) + oauthToken, err := config.Exchange(ctx, code) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error exchanging Oauth code.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), oauth2StateKey{}, OAuth2State{ + ctx = context.WithValue(ctx, oauth2StateKey{}, OAuth2State{ Token: oauthToken, Redirect: redirect, }) diff --git a/coderd/httpmw/organizationparam.go b/coderd/httpmw/organizationparam.go index f3baacd42b..da27ac577c 100644 --- a/coderd/httpmw/organizationparam.go +++ b/coderd/httpmw/organizationparam.go @@ -38,24 +38,25 @@ func OrganizationMemberParam(r *http.Request) database.OrganizationMember { func ExtractOrganizationParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() orgID, ok := parseUUID(rw, r, "organization") if !ok { return } - organization, err := db.GetOrganizationByID(r.Context(), orgID) + organization, err := db.GetOrganizationByID(ctx, orgID) if errors.Is(err, sql.ErrNoRows) { httpapi.ResourceNotFound(rw) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching organization.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), organizationParamContextKey{}, organization) + ctx = context.WithValue(ctx, organizationParamContextKey{}, organization) next.ServeHTTP(rw, r.WithContext(ctx)) }) } @@ -66,10 +67,11 @@ func ExtractOrganizationParam(db database.Store) func(http.Handler) http.Handler func ExtractOrganizationMemberParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() organization := OrganizationParam(r) user := UserParam(r) - organizationMember, err := db.GetOrganizationMemberByUserID(r.Context(), database.GetOrganizationMemberByUserIDParams{ + organizationMember, err := db.GetOrganizationMemberByUserID(ctx, database.GetOrganizationMemberByUserIDParams{ OrganizationID: organization.ID, UserID: user.ID, }) @@ -78,14 +80,14 @@ func ExtractOrganizationMemberParam(db database.Store) func(http.Handler) http.H return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching organization member.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), organizationMemberParamContextKey{}, organizationMember) + ctx = context.WithValue(ctx, organizationMemberParamContextKey{}, organizationMember) next.ServeHTTP(rw, r.WithContext(ctx)) }) } diff --git a/coderd/httpmw/ratelimit.go b/coderd/httpmw/ratelimit.go index 366e110eb4..5fbe929847 100644 --- a/coderd/httpmw/ratelimit.go +++ b/coderd/httpmw/ratelimit.go @@ -32,7 +32,7 @@ func RateLimitPerMinute(count int) func(http.Handler) http.Handler { return httprate.KeyByIP(r) }, httprate.KeyByEndpoint), httprate.WithLimitHandler(func(w http.ResponseWriter, r *http.Request) { - httpapi.Write(w, http.StatusTooManyRequests, codersdk.Response{ + httpapi.Write(r.Context(), w, http.StatusTooManyRequests, codersdk.Response{ Message: "You've been rate limited for sending too many requests!", }) }), diff --git a/coderd/httpmw/requestid.go b/coderd/httpmw/requestid.go index f2d7b2234d..bd232c75a4 100644 --- a/coderd/httpmw/requestid.go +++ b/coderd/httpmw/requestid.go @@ -24,11 +24,12 @@ func RequestID(r *http.Request) uuid.UUID { func AttachRequestID(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rid := uuid.New() + ridString := rid.String() ctx := context.WithValue(r.Context(), requestIDContextKey{}, rid) ctx = slog.With(ctx, slog.F("request_id", rid)) - rw.Header().Set("X-Coder-Request-Id", rid.String()) + rw.Header().Set("X-Coder-Request-Id", ridString) next.ServeHTTP(rw, r.WithContext(ctx)) }) } diff --git a/coderd/httpmw/templateparam.go b/coderd/httpmw/templateparam.go index cc9cb0b542..9400a47f77 100644 --- a/coderd/httpmw/templateparam.go +++ b/coderd/httpmw/templateparam.go @@ -28,6 +28,7 @@ func TemplateParam(r *http.Request) database.Template { func ExtractTemplateParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() templateID, parsed := parseUUID(rw, r, "template") if !parsed { return @@ -38,14 +39,14 @@ func ExtractTemplateParam(db database.Store) func(http.Handler) http.Handler { return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), templateParamContextKey{}, template) + ctx = context.WithValue(ctx, templateParamContextKey{}, template) chi.RouteContext(ctx).URLParams.Add("organization", template.OrganizationID.String()) next.ServeHTTP(rw, r.WithContext(ctx)) }) diff --git a/coderd/httpmw/templateversionparam.go b/coderd/httpmw/templateversionparam.go index 762f5dae44..91b2cbf362 100644 --- a/coderd/httpmw/templateversionparam.go +++ b/coderd/httpmw/templateversionparam.go @@ -28,24 +28,25 @@ func TemplateVersionParam(r *http.Request) database.TemplateVersion { func ExtractTemplateVersionParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() templateVersionID, parsed := parseUUID(rw, r, "templateversion") if !parsed { return } - templateVersion, err := db.GetTemplateVersionByID(r.Context(), templateVersionID) + templateVersion, err := db.GetTemplateVersionByID(ctx, templateVersionID) if errors.Is(err, sql.ErrNoRows) { httpapi.ResourceNotFound(rw) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), templateVersionParamContextKey{}, templateVersion) + ctx = context.WithValue(ctx, templateVersionParamContextKey{}, templateVersion) chi.RouteContext(ctx).URLParams.Add("organization", templateVersion.OrganizationID.String()) next.ServeHTTP(rw, r.WithContext(ctx)) }) diff --git a/coderd/httpmw/userparam.go b/coderd/httpmw/userparam.go index 7f16ac82c9..6b852408c2 100644 --- a/coderd/httpmw/userparam.go +++ b/coderd/httpmw/userparam.go @@ -37,26 +37,29 @@ func UserParam(r *http.Request) database.User { func ExtractUserParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - var user database.User - var err error + var ( + ctx = r.Context() + user database.User + err error + ) // userQuery is either a uuid, a username, or 'me' userQuery := chi.URLParam(r, "user") if userQuery == "" { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "\"user\" must be provided.", }) return } if userQuery == "me" { - user, err = db.GetUserByID(r.Context(), APIKey(r).UserID) + user, err = db.GetUserByID(ctx, APIKey(r).UserID) if xerrors.Is(err, sql.ErrNoRows) { httpapi.ResourceNotFound(rw) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user.", Detail: err.Error(), }) @@ -64,27 +67,27 @@ func ExtractUserParam(db database.Store) func(http.Handler) http.Handler { } } else if userID, err := uuid.Parse(userQuery); err == nil { // If the userQuery is a valid uuid - user, err = db.GetUserByID(r.Context(), userID) + user, err = db.GetUserByID(ctx, userID) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: userErrorMessage, }) return } } else { // Try as a username last - user, err = db.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{ + user, err = db.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{ Username: userQuery, }) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: userErrorMessage, }) return } } - ctx := context.WithValue(r.Context(), userParamContextKey{}, user) + ctx = context.WithValue(ctx, userParamContextKey{}, user) next.ServeHTTP(rw, r.WithContext(ctx)) }) } diff --git a/coderd/httpmw/workspaceagent.go b/coderd/httpmw/workspaceagent.go index 26a7eae418..557dcda1e5 100644 --- a/coderd/httpmw/workspaceagent.go +++ b/coderd/httpmw/workspaceagent.go @@ -29,37 +29,38 @@ func WorkspaceAgent(r *http.Request) database.WorkspaceAgent { func ExtractWorkspaceAgent(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() cookieValue := apiTokenFromRequest(r) if cookieValue == "" { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: fmt.Sprintf("Cookie %q must be provided.", codersdk.SessionTokenKey), }) return } token, err := uuid.Parse(cookieValue) if err != nil { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: "Agent token is invalid.", }) return } - agent, err := db.GetWorkspaceAgentByAuthToken(r.Context(), token) + agent, err := db.GetWorkspaceAgentByAuthToken(ctx, token) if err != nil { if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: "Agent token is invalid.", }) return } - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace agent.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), workspaceAgentContextKey{}, agent) + ctx = context.WithValue(ctx, workspaceAgentContextKey{}, agent) next.ServeHTTP(rw, r.WithContext(ctx)) }) } diff --git a/coderd/httpmw/workspaceagentparam.go b/coderd/httpmw/workspaceagentparam.go index 0f10ea7678..cc0f372dc4 100644 --- a/coderd/httpmw/workspaceagentparam.go +++ b/coderd/httpmw/workspaceagentparam.go @@ -28,59 +28,60 @@ func WorkspaceAgentParam(r *http.Request) database.WorkspaceAgent { func ExtractWorkspaceAgentParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() agentUUID, parsed := parseUUID(rw, r, "workspaceagent") if !parsed { return } - agent, err := db.GetWorkspaceAgentByID(r.Context(), agentUUID) + agent, err := db.GetWorkspaceAgentByID(ctx, agentUUID) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: "Agent doesn't exist with that id.", }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace agent.", Detail: err.Error(), }) return } - resource, err := db.GetWorkspaceResourceByID(r.Context(), agent.ResourceID) + resource, err := db.GetWorkspaceResourceByID(ctx, agent.ResourceID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace resource.", Detail: err.Error(), }) return } - job, err := db.GetProvisionerJobByID(r.Context(), resource.JobID) + job, err := db.GetProvisionerJobByID(ctx, resource.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) return } if job.Type != database.ProvisionerJobTypeWorkspaceBuild { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Workspace agents can only be fetched for builds.", }) return } - build, err := db.GetWorkspaceBuildByJobID(r.Context(), job.ID) + build, err := db.GetWorkspaceBuildByJobID(ctx, job.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace build.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), workspaceAgentParamContextKey{}, agent) + ctx = context.WithValue(ctx, workspaceAgentParamContextKey{}, agent) chi.RouteContext(ctx).URLParams.Add("workspace", build.WorkspaceID.String()) next.ServeHTTP(rw, r.WithContext(ctx)) }) diff --git a/coderd/httpmw/workspacebuildparam.go b/coderd/httpmw/workspacebuildparam.go index 307252bdf6..7ae728dfa6 100644 --- a/coderd/httpmw/workspacebuildparam.go +++ b/coderd/httpmw/workspacebuildparam.go @@ -28,24 +28,25 @@ func WorkspaceBuildParam(r *http.Request) database.WorkspaceBuild { func ExtractWorkspaceBuildParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceBuildID, parsed := parseUUID(rw, r, "workspacebuild") if !parsed { return } - workspaceBuild, err := db.GetWorkspaceBuildByID(r.Context(), workspaceBuildID) + workspaceBuild, err := db.GetWorkspaceBuildByID(ctx, workspaceBuildID) if errors.Is(err, sql.ErrNoRows) { httpapi.ResourceNotFound(rw) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace build.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), workspaceBuildParamContextKey{}, workspaceBuild) + ctx = context.WithValue(ctx, workspaceBuildParamContextKey{}, workspaceBuild) // This injects the "workspace" parameter, because it's expected the consumer // will want to use the Workspace middleware to ensure the caller owns the workspace. chi.RouteContext(ctx).URLParams.Add("workspace", workspaceBuild.WorkspaceID.String()) diff --git a/coderd/httpmw/workspaceparam.go b/coderd/httpmw/workspaceparam.go index 7c38c41f32..f002f25508 100644 --- a/coderd/httpmw/workspaceparam.go +++ b/coderd/httpmw/workspaceparam.go @@ -31,24 +31,25 @@ func WorkspaceParam(r *http.Request) database.Workspace { func ExtractWorkspaceParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceID, parsed := parseUUID(rw, r, "workspace") if !parsed { return } - workspace, err := db.GetWorkspaceByID(r.Context(), workspaceID) + workspace, err := db.GetWorkspaceByID(ctx, workspaceID) if errors.Is(err, sql.ErrNoRows) { httpapi.ResourceNotFound(rw) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), workspaceParamContextKey{}, workspace) + ctx = context.WithValue(ctx, workspaceParamContextKey{}, workspace) next.ServeHTTP(rw, r.WithContext(ctx)) }) } @@ -63,11 +64,12 @@ func ExtractWorkspaceParam(db database.Store) func(http.Handler) http.Handler { func ExtractWorkspaceAndAgentParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() user := UserParam(r) workspaceWithAgent := chi.URLParam(r, "workspace_and_agent") workspaceParts := strings.Split(workspaceWithAgent, ".") - workspace, err := db.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{ + workspace, err := db.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{ OwnerID: user.ID, Name: workspaceParts[0], }) @@ -76,25 +78,25 @@ func ExtractWorkspaceAndAgentParam(db database.Store) func(http.Handler) http.Ha httpapi.ResourceNotFound(rw) return } - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace.", Detail: err.Error(), }) return } - build, err := db.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID) + build, err := db.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace build.", Detail: err.Error(), }) return } - resources, err := db.GetWorkspaceResourcesByJobID(r.Context(), build.JobID) + resources, err := db.GetWorkspaceResourcesByJobID(ctx, build.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace resources.", Detail: err.Error(), }) @@ -105,9 +107,9 @@ func ExtractWorkspaceAndAgentParam(db database.Store) func(http.Handler) http.Ha resourceIDs = append(resourceIDs, resource.ID) } - agents, err := db.GetWorkspaceAgentsByResourceIDs(r.Context(), resourceIDs) + agents, err := db.GetWorkspaceAgentsByResourceIDs(ctx, resourceIDs) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace agents.", Detail: err.Error(), }) @@ -115,7 +117,7 @@ func ExtractWorkspaceAndAgentParam(db database.Store) func(http.Handler) http.Ha } if len(agents) == 0 { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "No agents exist for this workspace", }) return @@ -123,7 +125,7 @@ func ExtractWorkspaceAndAgentParam(db database.Store) func(http.Handler) http.Ha // If we have more than 1 workspace agent, we need to specify which one to use. if len(agents) > 1 && len(workspaceParts) <= 1 { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "More than one agent exists, but no agent specified.", }) return @@ -143,7 +145,7 @@ func ExtractWorkspaceAndAgentParam(db database.Store) func(http.Handler) http.Ha } } if !found { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("No agent exists with the name %q", workspaceParts[1]), }) return @@ -152,7 +154,6 @@ func ExtractWorkspaceAndAgentParam(db database.Store) func(http.Handler) http.Ha agent = agents[0] } - ctx := r.Context() ctx = context.WithValue(ctx, workspaceParamContextKey{}, workspace) ctx = context.WithValue(ctx, workspaceAgentParamContextKey{}, agent) next.ServeHTTP(rw, r.WithContext(ctx)) diff --git a/coderd/httpmw/workspaceresourceparam.go b/coderd/httpmw/workspaceresourceparam.go index fa30d327ef..ecc1acd676 100644 --- a/coderd/httpmw/workspaceresourceparam.go +++ b/coderd/httpmw/workspaceresourceparam.go @@ -28,49 +28,50 @@ func WorkspaceResourceParam(r *http.Request) database.WorkspaceResource { func ExtractWorkspaceResourceParam(db database.Store) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() resourceUUID, parsed := parseUUID(rw, r, "workspaceresource") if !parsed { return } - resource, err := db.GetWorkspaceResourceByID(r.Context(), resourceUUID) + resource, err := db.GetWorkspaceResourceByID(ctx, resourceUUID) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: "Resource doesn't exist with that id.", }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner resource.", Detail: err.Error(), }) return } - job, err := db.GetProvisionerJobByID(r.Context(), resource.JobID) + job, err := db.GetProvisionerJobByID(ctx, resource.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error provisioner job.", Detail: err.Error(), }) return } if job.Type != database.ProvisionerJobTypeWorkspaceBuild { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Workspace resources can only be fetched for builds.", }) return } - build, err := db.GetWorkspaceBuildByJobID(r.Context(), job.ID) + build, err := db.GetWorkspaceBuildByJobID(ctx, job.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error workspace build.", Detail: err.Error(), }) return } - ctx := context.WithValue(r.Context(), workspaceResourceParamContextKey{}, resource) + ctx = context.WithValue(ctx, workspaceResourceParamContextKey{}, resource) ctx = context.WithValue(ctx, workspaceBuildParamContextKey{}, build) chi.RouteContext(ctx).URLParams.Add("workspace", build.WorkspaceID.String()) next.ServeHTTP(rw, r.WithContext(ctx)) diff --git a/coderd/members.go b/coderd/members.go index d270d6682c..2ae7fa9de6 100644 --- a/coderd/members.go +++ b/coderd/members.go @@ -17,21 +17,24 @@ import ( ) func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) { - user := httpmw.UserParam(r) - organization := httpmw.OrganizationParam(r) - member := httpmw.OrganizationMemberParam(r) - apiKey := httpmw.APIKey(r) - actorRoles := httpmw.UserAuthorization(r) + var ( + ctx = r.Context() + user = httpmw.UserParam(r) + organization = httpmw.OrganizationParam(r) + member = httpmw.OrganizationMemberParam(r) + apiKey = httpmw.APIKey(r) + actorRoles = httpmw.UserAuthorization(r) + ) if apiKey.UserID == member.UserID { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "You cannot change your own organization roles.", }) return } var params codersdk.UpdateRoles - if !httpapi.Read(rw, r, ¶ms) { + if !httpapi.Read(ctx, rw, r, ¶ms) { return } @@ -59,19 +62,19 @@ func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) { } } - updatedUser, err := api.updateOrganizationMemberRoles(r.Context(), database.UpdateMemberRolesParams{ + updatedUser, err := api.updateOrganizationMemberRoles(ctx, database.UpdateMemberRolesParams{ GrantedRoles: params.Roles, UserID: user.ID, OrgID: organization.ID, }) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertOrganizationMember(updatedUser)) + httpapi.Write(ctx, rw, http.StatusOK, convertOrganizationMember(updatedUser)) } func (api *API) updateOrganizationMemberRoles(ctx context.Context, args database.UpdateMemberRolesParams) (database.OrganizationMember, error) { diff --git a/coderd/organizations.go b/coderd/organizations.go index dffedaaea6..cdc1040165 100644 --- a/coderd/organizations.go +++ b/coderd/organizations.go @@ -17,6 +17,7 @@ import ( ) func (api *API) organization(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() organization := httpmw.OrganizationParam(r) if !api.Authorize(r, rbac.ActionRead, rbac.ResourceOrganization. @@ -25,10 +26,11 @@ func (api *API) organization(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, convertOrganization(organization)) + httpapi.Write(ctx, rw, http.StatusOK, convertOrganization(organization)) } func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() apiKey := httpmw.APIKey(r) // Create organization uses the organization resource without an OrgID. // This means you need the site wide permission to make a new organization. @@ -38,19 +40,19 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) { } var req codersdk.CreateOrganizationRequest - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } - _, err := api.Database.GetOrganizationByName(r.Context(), req.Name) + _, err := api.Database.GetOrganizationByName(ctx, req.Name) if err == nil { - httpapi.Write(rw, http.StatusConflict, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ Message: "Organization already exists with that name.", }) return } if !errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("Internal error fetching organization %q.", req.Name), Detail: err.Error(), }) @@ -59,7 +61,7 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) { var organization database.Organization err = api.Database.InTx(func(store database.Store) error { - organization, err = store.InsertOrganization(r.Context(), database.InsertOrganizationParams{ + organization, err = store.InsertOrganization(ctx, database.InsertOrganizationParams{ ID: uuid.New(), Name: req.Name, CreatedAt: database.Now(), @@ -68,7 +70,7 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) { if err != nil { return xerrors.Errorf("create organization: %w", err) } - _, err = store.InsertOrganizationMember(r.Context(), database.InsertOrganizationMemberParams{ + _, err = store.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{ OrganizationID: organization.ID, UserID: apiKey.UserID, CreatedAt: database.Now(), @@ -83,14 +85,14 @@ func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) { return nil }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error inserting organization member.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusCreated, convertOrganization(organization)) + httpapi.Write(ctx, rw, http.StatusCreated, convertOrganization(organization)) } // convertOrganization consumes the database representation and outputs an API friendly representation. diff --git a/coderd/pagination.go b/coderd/pagination.go index 918bc0d5fe..8bc4228b3b 100644 --- a/coderd/pagination.go +++ b/coderd/pagination.go @@ -12,6 +12,7 @@ import ( // parsePagination extracts pagination query params from the http request. // If an error is encountered, the error is written to w and ok is set to false. func parsePagination(w http.ResponseWriter, r *http.Request) (p codersdk.Pagination, ok bool) { + ctx := r.Context() queryParams := r.URL.Query() parser := httpapi.NewQueryParamParser() params := codersdk.Pagination{ @@ -21,7 +22,7 @@ func parsePagination(w http.ResponseWriter, r *http.Request) (p codersdk.Paginat Offset: parser.Int(queryParams, 0, "offset"), } if len(parser.Errors) > 0 { - httpapi.Write(w, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, w, http.StatusBadRequest, codersdk.Response{ Message: "Query parameters have invalid values.", Validations: parser.Errors, }) diff --git a/coderd/parameters.go b/coderd/parameters.go index 7675e9ff9b..3dc7325945 100644 --- a/coderd/parameters.go +++ b/coderd/parameters.go @@ -1,6 +1,7 @@ package coderd import ( + "context" "database/sql" "errors" "fmt" @@ -18,7 +19,8 @@ import ( ) func (api *API) postParameter(rw http.ResponseWriter, r *http.Request) { - scope, scopeID, valid := readScopeAndID(rw, r) + ctx := r.Context() + scope, scopeID, valid := readScopeAndID(ctx, rw, r) if !valid { return } @@ -32,29 +34,29 @@ func (api *API) postParameter(rw http.ResponseWriter, r *http.Request) { } var createRequest codersdk.CreateParameterRequest - if !httpapi.Read(rw, r, &createRequest) { + if !httpapi.Read(ctx, rw, r, &createRequest) { return } - _, err := api.Database.GetParameterValueByScopeAndName(r.Context(), database.GetParameterValueByScopeAndNameParams{ + _, err := api.Database.GetParameterValueByScopeAndName(ctx, database.GetParameterValueByScopeAndNameParams{ Scope: scope, ScopeID: scopeID, Name: createRequest.Name, }) if err == nil { - httpapi.Write(rw, http.StatusConflict, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ Message: fmt.Sprintf("Parameter already exists in scope %q and name %q.", scope, createRequest.Name), }) return } if !errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching parameter.", Detail: err.Error(), }) return } - parameterValue, err := api.Database.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ + parameterValue, err := api.Database.InsertParameterValue(ctx, database.InsertParameterValueParams{ ID: uuid.New(), Name: createRequest.Name, CreatedAt: database.Now(), @@ -66,18 +68,19 @@ func (api *API) postParameter(rw http.ResponseWriter, r *http.Request) { DestinationScheme: database.ParameterDestinationScheme(createRequest.DestinationScheme), }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error inserting parameter.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusCreated, convertParameterValue(parameterValue)) + httpapi.Write(ctx, rw, http.StatusCreated, convertParameterValue(parameterValue)) } func (api *API) parameters(rw http.ResponseWriter, r *http.Request) { - scope, scopeID, valid := readScopeAndID(rw, r) + ctx := r.Context() + scope, scopeID, valid := readScopeAndID(ctx, rw, r) if !valid { return } @@ -91,7 +94,7 @@ func (api *API) parameters(rw http.ResponseWriter, r *http.Request) { return } - parameterValues, err := api.Database.ParameterValues(r.Context(), database.ParameterValuesParams{ + parameterValues, err := api.Database.ParameterValues(ctx, database.ParameterValuesParams{ Scopes: []database.ParameterScope{scope}, ScopeIds: []uuid.UUID{scopeID}, }) @@ -99,7 +102,7 @@ func (api *API) parameters(rw http.ResponseWriter, r *http.Request) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching parameter scope values.", Detail: err.Error(), }) @@ -110,11 +113,12 @@ func (api *API) parameters(rw http.ResponseWriter, r *http.Request) { apiParameterValues = append(apiParameterValues, convertParameterValue(parameterValue)) } - httpapi.Write(rw, http.StatusOK, apiParameterValues) + httpapi.Write(ctx, rw, http.StatusOK, apiParameterValues) } func (api *API) deleteParameter(rw http.ResponseWriter, r *http.Request) { - scope, scopeID, valid := readScopeAndID(rw, r) + ctx := r.Context() + scope, scopeID, valid := readScopeAndID(ctx, rw, r) if !valid { return } @@ -129,7 +133,7 @@ func (api *API) deleteParameter(rw http.ResponseWriter, r *http.Request) { } name := chi.URLParam(r, "name") - parameterValue, err := api.Database.GetParameterValueByScopeAndName(r.Context(), database.GetParameterValueByScopeAndNameParams{ + parameterValue, err := api.Database.GetParameterValueByScopeAndName(ctx, database.GetParameterValueByScopeAndNameParams{ Scope: scope, ScopeID: scopeID, Name: name, @@ -139,21 +143,21 @@ func (api *API) deleteParameter(rw http.ResponseWriter, r *http.Request) { return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching parameter.", Detail: err.Error(), }) return } - err = api.Database.DeleteParameterValueByID(r.Context(), parameterValue.ID) + err = api.Database.DeleteParameterValueByID(ctx, parameterValue.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error deleting parameter.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "Parameter deleted.", }) } @@ -225,11 +229,11 @@ func (api *API) parameterRBACResource(rw http.ResponseWriter, r *http.Request, s // Write error payload to rw if we cannot find the resource for the scope if err != nil { if xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: fmt.Sprintf("Scope %q resource %q not found.", scope, scopeID), }) } else { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: err.Error(), }) } @@ -238,12 +242,12 @@ func (api *API) parameterRBACResource(rw http.ResponseWriter, r *http.Request, s return resource, true } -func readScopeAndID(rw http.ResponseWriter, r *http.Request) (database.ParameterScope, uuid.UUID, bool) { +func readScopeAndID(ctx context.Context, rw http.ResponseWriter, r *http.Request) (database.ParameterScope, uuid.UUID, bool) { scope := database.ParameterScope(chi.URLParam(r, "scope")) switch scope { case database.ParameterScopeTemplate, database.ParameterScopeImportJob, database.ParameterScopeWorkspace: default: - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Invalid scope %q.", scope), Validations: []codersdk.ValidationError{ {Field: "scope", Detail: "invalid scope"}, @@ -255,7 +259,7 @@ func readScopeAndID(rw http.ResponseWriter, r *http.Request) (database.Parameter id := chi.URLParam(r, "id") uid, err := uuid.Parse(id) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Invalid UUID %q.", id), Detail: err.Error(), Validations: []codersdk.ValidationError{ diff --git a/coderd/provisionerdaemons.go b/coderd/provisionerdaemons.go index 371ec0d649..58eb2315e1 100644 --- a/coderd/provisionerdaemons.go +++ b/coderd/provisionerdaemons.go @@ -34,12 +34,13 @@ import ( ) func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) { - daemons, err := api.Database.GetProvisionerDaemons(r.Context()) + ctx := r.Context() + daemons, err := api.Database.GetProvisionerDaemons(ctx) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner daemons.", Detail: err.Error(), }) @@ -50,14 +51,14 @@ func (api *API) provisionerDaemons(rw http.ResponseWriter, r *http.Request) { } daemons, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, daemons) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner daemons.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, daemons) + httpapi.Write(ctx, rw, http.StatusOK, daemons) } // ListenProvisionerDaemon is an in-memory connection to a provisionerd. Useful when starting coderd and provisionerd diff --git a/coderd/provisionerjobs.go b/coderd/provisionerjobs.go index fdaf271c7e..63a512f78b 100644 --- a/coderd/provisionerjobs.go +++ b/coderd/provisionerjobs.go @@ -29,12 +29,15 @@ import ( // The combination of these responses should provide all current logs // to the consumer, and future logs are streamed in the follow request. func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job database.ProvisionerJob) { - logger := api.Logger.With(slog.F("job_id", job.ID)) - follow := r.URL.Query().Has("follow") - afterRaw := r.URL.Query().Get("after") - beforeRaw := r.URL.Query().Get("before") + var ( + ctx = r.Context() + logger = api.Logger.With(slog.F("job_id", job.ID)) + follow = r.URL.Query().Has("follow") + afterRaw = r.URL.Query().Get("after") + beforeRaw = r.URL.Query().Get("before") + ) if beforeRaw != "" && follow { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Query param \"before\" cannot be used with \"follow\".", }) return @@ -47,7 +50,7 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job if follow { bl, closeFollow, err := api.followLogs(job.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error watching provisioner logs.", Detail: err.Error(), }) @@ -61,9 +64,9 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job // has, but we need to query it *after* we start following the pubsub to avoid a race condition where the job // completes between the prior query and the start of following the pubsub. A more substantial refactor could // avoid this, but not worth it for one fewer query at this point. - job, err = api.Database.GetProvisionerJobByID(r.Context(), job.ID) + job, err = api.Database.GetProvisionerJobByID(ctx, job.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error querying job.", Detail: err.Error(), }) @@ -76,7 +79,7 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job if afterRaw != "" { afterMS, err := strconv.ParseInt(afterRaw, 10, 64) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Query param \"after\" must be an integer.", Validations: []codersdk.ValidationError{ {Field: "after", Detail: "Must be an integer"}, @@ -95,7 +98,7 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job if beforeRaw != "" { beforeMS, err := strconv.ParseInt(beforeRaw, 10, 64) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Query param \"before\" must be an integer.", Validations: []codersdk.ValidationError{ {Field: "before", Detail: "Must be an integer"}, @@ -111,7 +114,7 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job } } - logs, err := api.Database.GetProvisionerLogsByIDBetween(r.Context(), database.GetProvisionerLogsByIDBetweenParams{ + logs, err := api.Database.GetProvisionerLogsByIDBetween(ctx, database.GetProvisionerLogsByIDBetweenParams{ JobID: job.ID, CreatedAfter: after, CreatedBefore: before, @@ -120,7 +123,7 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner logs.", Detail: err.Error(), }) @@ -131,8 +134,8 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job } if !follow { - logger.Debug(r.Context(), "Finished non-follow job logs") - httpapi.Write(rw, http.StatusOK, convertProvisionerJobLogs(logs)) + logger.Debug(ctx, "Finished non-follow job logs") + httpapi.Write(ctx, rw, http.StatusOK, convertProvisionerJobLogs(logs)) return } @@ -142,14 +145,14 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job defer api.websocketWaitGroup.Done() conn, err := websocket.Accept(rw, r, nil) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to accept websocket.", Detail: err.Error(), }) return } - ctx, wsNetConn := websocketNetConn(r.Context(), conn, websocket.MessageText) + ctx, wsNetConn := websocketNetConn(ctx, conn, websocket.MessageText) defer wsNetConn.Close() // Also closes conn. logIdsDone := make(map[uuid.UUID]bool) @@ -180,10 +183,10 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job return } if logIdsDone[log.ID] { - logger.Debug(r.Context(), "subscribe duplicated log", + logger.Debug(ctx, "subscribe duplicated log", slog.F("stage", log.Stage)) } else { - logger.Debug(r.Context(), "subscribe encoding log", + logger.Debug(ctx, "subscribe encoding log", slog.F("stage", log.Stage)) err = encoder.Encode(convertProvisionerJobLog(log)) if err != nil { @@ -195,18 +198,19 @@ func (api *API) provisionerJobLogs(rw http.ResponseWriter, r *http.Request, job } func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, job database.ProvisionerJob) { + ctx := r.Context() if !job.CompletedAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Job hasn't completed!", }) return } - resources, err := api.Database.GetWorkspaceResourcesByJobID(r.Context(), job.ID) + resources, err := api.Database.GetWorkspaceResourcesByJobID(ctx, job.ID) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching job resources.", Detail: err.Error(), }) @@ -216,12 +220,12 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, for _, resource := range resources { resourceIDs = append(resourceIDs, resource.ID) } - resourceAgents, err := api.Database.GetWorkspaceAgentsByResourceIDs(r.Context(), resourceIDs) + resourceAgents, err := api.Database.GetWorkspaceAgentsByResourceIDs(ctx, resourceIDs) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace agent.", Detail: err.Error(), }) @@ -231,20 +235,20 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, for _, agent := range resourceAgents { resourceAgentIDs = append(resourceAgentIDs, agent.ID) } - apps, err := api.Database.GetWorkspaceAppsByAgentIDs(r.Context(), resourceAgentIDs) + apps, err := api.Database.GetWorkspaceAppsByAgentIDs(ctx, resourceAgentIDs) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace applications.", Detail: err.Error(), }) return } - resourceMetadata, err := api.Database.GetWorkspaceResourceMetadataByResourceIDs(r.Context(), resourceIDs) + resourceMetadata, err := api.Database.GetWorkspaceResourceMetadataByResourceIDs(ctx, resourceIDs) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace metadata.", Detail: err.Error(), }) @@ -267,7 +271,7 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, apiAgent, err := convertWorkspaceAgent(api.DERPMap, api.TailnetCoordinator, agent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading job agent.", Detail: err.Error(), }) @@ -287,7 +291,7 @@ func (api *API) provisionerJobResources(rw http.ResponseWriter, r *http.Request, return apiResources[i].Name < apiResources[j].Name }) - httpapi.Write(rw, http.StatusOK, apiResources) + httpapi.Write(ctx, rw, http.StatusOK, apiResources) } func convertProvisionerJobLogs(provisionerJobLogs []database.ProvisionerJobLog) []codersdk.ProvisionerJobLog { diff --git a/coderd/roles.go b/coderd/roles.go index cfac554cde..48e60a596a 100644 --- a/coderd/roles.go +++ b/coderd/roles.go @@ -13,6 +13,7 @@ import ( // assignableSiteRoles returns all site wide roles that can be assigned. func (api *API) assignableSiteRoles(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() actorRoles := httpmw.UserAuthorization(r) if !api.Authorize(r, rbac.ActionRead, rbac.ResourceRoleAssignment) { httpapi.Forbidden(rw) @@ -20,11 +21,12 @@ func (api *API) assignableSiteRoles(rw http.ResponseWriter, r *http.Request) { } roles := rbac.SiteRoles() - httpapi.Write(rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles)) + httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles)) } // assignableSiteRoles returns all site wide roles that can be assigned. func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() organization := httpmw.OrganizationParam(r) actorRoles := httpmw.UserAuthorization(r) @@ -34,10 +36,11 @@ func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) { } roles := rbac.OrganizationRoles(organization.ID) - httpapi.Write(rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles)) + httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles)) } func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() user := httpmw.UserParam(r) apiKey := httpmw.APIKey(r) @@ -47,21 +50,21 @@ func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) { } // use the roles of the user specified, not the person making the request. - roles, err := api.Database.GetAuthorizationUserRoles(r.Context(), user.ID) + roles, err := api.Database.GetAuthorizationUserRoles(ctx, user.ID) if err != nil { httpapi.Forbidden(rw) return } var params codersdk.UserAuthorizationRequest - if !httpapi.Read(rw, r, ¶ms) { + if !httpapi.Read(ctx, rw, r, ¶ms) { return } response := make(codersdk.UserAuthorizationResponse) for k, v := range params.Checks { if v.Object.ResourceType == "" { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Object's \"resource_type\" field must be defined for key %q.", k), }) return @@ -70,7 +73,7 @@ func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) { if v.Object.OwnerID == "me" { v.Object.OwnerID = roles.ID.String() } - err := api.Authorizer.ByRoleName(r.Context(), roles.ID.String(), roles.Roles, apiKey.Scope.ToRBAC(), rbac.Action(v.Action), + err := api.Authorizer.ByRoleName(ctx, roles.ID.String(), roles.Roles, apiKey.Scope.ToRBAC(), rbac.Action(v.Action), rbac.Object{ Owner: v.Object.OwnerID, OrgID: v.Object.OrganizationID, @@ -79,7 +82,7 @@ func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) { response[k] = err == nil } - httpapi.Write(rw, http.StatusOK, response) + httpapi.Write(ctx, rw, http.StatusOK, response) } func convertRole(role rbac.Role) codersdk.Role { diff --git a/coderd/templates.go b/coderd/templates.go index 96bf39dd26..6721aa0eb4 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -41,6 +41,7 @@ const ( // Returns a single template. func (api *API) template(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() template := httpmw.TemplateParam(r) if !api.Authorize(r, rbac.ActionRead, template) { @@ -48,12 +49,12 @@ func (api *API) template(rw http.ResponseWriter, r *http.Request) { return } - workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), []uuid.UUID{template.ID}) + workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(ctx, []uuid.UUID{template.ID}) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace count.", Detail: err.Error(), }) @@ -70,20 +71,21 @@ func (api *API) template(rw http.ResponseWriter, r *http.Request) { count = uint32(workspaceCounts[0].Count) } - createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{template}) + createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, api.Database, []database.Template{template}) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching creator name.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, api.convertTemplate(template, count, createdByNameMap[template.ID.String()])) + httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(template, count, createdByNameMap[template.ID.String()])) } func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() template = httpmw.TemplateParam(r) auditor = *api.Auditor.Load() aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{ @@ -101,35 +103,35 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) { return } - workspaces, err := api.Database.GetWorkspaces(r.Context(), database.GetWorkspacesParams{ + workspaces, err := api.Database.GetWorkspaces(ctx, database.GetWorkspacesParams{ TemplateIds: []uuid.UUID{template.ID}, }) if err != nil && !errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspaces by template id.", Detail: err.Error(), }) return } if len(workspaces) > 0 { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "All workspaces must be deleted before a template can be removed.", }) return } - err = api.Database.UpdateTemplateDeletedByID(r.Context(), database.UpdateTemplateDeletedByIDParams{ + err = api.Database.UpdateTemplateDeletedByID(ctx, database.UpdateTemplateDeletedByIDParams{ ID: template.ID, Deleted: true, UpdatedAt: database.Now(), }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error deleting template.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "Template has been deleted!", }) } @@ -137,6 +139,7 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) { // Create a new template in an organization. func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() createTemplate codersdk.CreateTemplateRequest organization = httpmw.OrganizationParam(r) apiKey = httpmw.APIKey(r) @@ -162,15 +165,15 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque return } - if !httpapi.Read(rw, r, &createTemplate) { + if !httpapi.Read(ctx, rw, r, &createTemplate) { return } - _, err := api.Database.GetTemplateByOrganizationAndName(r.Context(), database.GetTemplateByOrganizationAndNameParams{ + _, err := api.Database.GetTemplateByOrganizationAndName(ctx, database.GetTemplateByOrganizationAndNameParams{ OrganizationID: organization.ID, Name: createTemplate.Name, }) if err == nil { - httpapi.Write(rw, http.StatusConflict, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ Message: fmt.Sprintf("Template with name %q already exists.", createTemplate.Name), Validations: []codersdk.ValidationError{{ Field: "name", @@ -180,15 +183,15 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque return } if !errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template by name.", Detail: err.Error(), }) return } - templateVersion, err := api.Database.GetTemplateVersionByID(r.Context(), createTemplate.VersionID) + templateVersion, err := api.Database.GetTemplateVersionByID(ctx, createTemplate.VersionID) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: fmt.Sprintf("Template version %q does not exist.", createTemplate.VersionID), Validations: []codersdk.ValidationError{ {Field: "template_version_id", Detail: "Template version does not exist"}, @@ -197,7 +200,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version.", Detail: err.Error(), }) @@ -205,9 +208,9 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque } templateVersionAudit.Old = templateVersion - importJob, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + importJob, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) @@ -219,7 +222,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque maxTTL = time.Duration(*createTemplate.MaxTTLMillis) * time.Millisecond } if maxTTL < 0 { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid create template request.", Validations: []codersdk.ValidationError{ {Field: "max_ttl_ms", Detail: "Must be a positive integer."}, @@ -229,7 +232,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque } if maxTTL > maxTTLDefault { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid create template request.", Validations: []codersdk.ValidationError{ {Field: "max_ttl_ms", Detail: "Cannot be greater than " + maxTTLDefault.String()}, @@ -247,7 +250,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque var template codersdk.Template err = api.Database.InTx(func(db database.Store) error { now := database.Now() - dbTemplate, err = db.InsertTemplate(r.Context(), database.InsertTemplateParams{ + dbTemplate, err = db.InsertTemplate(ctx, database.InsertTemplateParams{ ID: uuid.New(), CreatedAt: now, UpdatedAt: now, @@ -266,7 +269,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque templateAudit.New = dbTemplate - err = db.UpdateTemplateVersionByID(r.Context(), database.UpdateTemplateVersionByIDParams{ + err = db.UpdateTemplateVersionByID(ctx, database.UpdateTemplateVersionByIDParams{ ID: templateVersion.ID, TemplateID: uuid.NullUUID{ UUID: dbTemplate.ID, @@ -285,7 +288,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque templateVersionAudit.New = newTemplateVersion for _, parameterValue := range createTemplate.ParameterValues { - _, err = db.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ + _, err = db.InsertParameterValue(ctx, database.InsertParameterValueParams{ ID: uuid.New(), Name: parameterValue.Name, CreatedAt: database.Now(), @@ -301,7 +304,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque } } - createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), db, []database.Template{dbTemplate}) + createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, db, []database.Template{dbTemplate}) if err != nil { return xerrors.Errorf("get creator name: %w", err) } @@ -310,7 +313,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque return nil }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error inserting template.", Detail: err.Error(), }) @@ -322,19 +325,20 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque TemplateVersions: []telemetry.TemplateVersion{telemetry.ConvertTemplateVersion(templateVersion)}, }) - httpapi.Write(rw, http.StatusCreated, template) + httpapi.Write(ctx, rw, http.StatusCreated, template) } func (api *API) templatesByOrganization(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() organization := httpmw.OrganizationParam(r) - templates, err := api.Database.GetTemplatesWithFilter(r.Context(), database.GetTemplatesWithFilterParams{ + templates, err := api.Database.GetTemplatesWithFilter(ctx, database.GetTemplatesWithFilterParams{ OrganizationID: organization.ID, }) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching templates in organization.", Detail: err.Error(), }) @@ -344,7 +348,7 @@ func (api *API) templatesByOrganization(rw http.ResponseWriter, r *http.Request) // Filter templates based on rbac permissions templates, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, templates) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching templates.", Detail: err.Error(), }) @@ -356,34 +360,35 @@ func (api *API) templatesByOrganization(rw http.ResponseWriter, r *http.Request) for _, template := range templates { templateIDs = append(templateIDs, template.ID) } - workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), templateIDs) + workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(ctx, templateIDs) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace counts.", Detail: err.Error(), }) return } - createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, templates) + createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, api.Database, templates) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching creator names.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, api.convertTemplates(templates, workspaceCounts, createdByNameMap)) + httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplates(templates, workspaceCounts, createdByNameMap)) } func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() organization := httpmw.OrganizationParam(r) templateName := chi.URLParam(r, "templatename") - template, err := api.Database.GetTemplateByOrganizationAndName(r.Context(), database.GetTemplateByOrganizationAndNameParams{ + template, err := api.Database.GetTemplateByOrganizationAndName(ctx, database.GetTemplateByOrganizationAndNameParams{ OrganizationID: organization.ID, Name: templateName, }) @@ -393,7 +398,7 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re return } - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template.", Detail: err.Error(), }) @@ -405,12 +410,12 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re return } - workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), []uuid.UUID{template.ID}) + workspaceCounts, err := api.Database.GetWorkspaceOwnerCountsByTemplateIDs(ctx, []uuid.UUID{template.ID}) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace counts.", Detail: err.Error(), }) @@ -422,20 +427,21 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re count = uint32(workspaceCounts[0].Count) } - createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{template}) + createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, api.Database, []database.Template{template}) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching creator name.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, api.convertTemplate(template, count, createdByNameMap[template.ID.String()])) + httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(template, count, createdByNameMap[template.ID.String()])) } func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() template = httpmw.TemplateParam(r) auditor = *api.Auditor.Load() aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{ @@ -454,7 +460,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { } var req codersdk.UpdateTemplateMeta - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } @@ -466,7 +472,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { validErrs = append(validErrs, codersdk.ValidationError{Field: "min_autostart_interval_ms", Detail: "Must be a positive integer."}) } if req.MaxTTLMillis > maxTTLDefault.Milliseconds() { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid create template request.", Validations: []codersdk.ValidationError{ {Field: "max_ttl_ms", Detail: "Cannot be greater than " + maxTTLDefault.String()}, @@ -476,7 +482,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { } if len(validErrs) > 0 { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid request to update template metadata!", Validations: validErrs, }) @@ -487,7 +493,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { var updated database.Template err := api.Database.InTx(func(s database.Store) error { // Fetch workspace counts - workspaceCounts, err := s.GetWorkspaceOwnerCountsByTemplateIDs(r.Context(), []uuid.UUID{template.ID}) + workspaceCounts, err := s.GetWorkspaceOwnerCountsByTemplateIDs(ctx, []uuid.UUID{template.ID}) if xerrors.Is(err, sql.ErrNoRows) { err = nil } @@ -524,7 +530,7 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { minAutostartInterval = time.Duration(template.MinAutostartInterval) } - updated, err = s.UpdateTemplateMetaByID(r.Context(), database.UpdateTemplateMetaByIDParams{ + updated, err = s.UpdateTemplateMetaByID(ctx, database.UpdateTemplateMetaByIDParams{ ID: template.ID, UpdatedAt: database.Now(), Name: name, @@ -546,24 +552,25 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) { if updated.UpdatedAt.IsZero() { aReq.New = template - httpapi.Write(rw, http.StatusNotModified, nil) + httpapi.Write(ctx, rw, http.StatusNotModified, nil) return } aReq.New = updated - createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{updated}) + createdByNameMap, err := getCreatedByNamesByTemplateIDs(ctx, api.Database, []database.Template{updated}) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching creator name.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, api.convertTemplate(updated, count, createdByNameMap[updated.ID.String()])) + httpapi.Write(ctx, rw, http.StatusOK, api.convertTemplate(updated, count, createdByNameMap[updated.ID.String()])) } func (api *API) templateDAUs(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() template := httpmw.TemplateParam(r) if !api.Authorize(r, rbac.ActionRead, template) { httpapi.ResourceNotFound(rw) @@ -572,12 +579,12 @@ func (api *API) templateDAUs(rw http.ResponseWriter, r *http.Request) { resp, _ := api.metricsCache.TemplateDAUs(template.ID) if resp == nil || resp.Entries == nil { - httpapi.Write(rw, http.StatusOK, &codersdk.TemplateDAUsResponse{ + httpapi.Write(ctx, rw, http.StatusOK, &codersdk.TemplateDAUsResponse{ Entries: []codersdk.DAUEntry{}, }) return } - httpapi.Write(rw, http.StatusOK, resp) + httpapi.Write(ctx, rw, http.StatusOK, resp) } type autoImportTemplateOpts struct { diff --git a/coderd/templateversions.go b/coderd/templateversions.go index 69a98e03b3..03945a6f9b 100644 --- a/coderd/templateversions.go +++ b/coderd/templateversions.go @@ -23,61 +23,63 @@ import ( ) func (api *API) templateVersion(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(r, rbac.ActionRead, templateVersion) { httpapi.ResourceNotFound(rw) return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) return } - createdByName, err := getUsernameByUserID(r.Context(), api.Database, templateVersion.CreatedBy) + createdByName, err := getUsernameByUserID(ctx, api.Database, templateVersion.CreatedBy) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching creator name.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName)) + httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName)) } func (api *API) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(r, rbac.ActionUpdate, templateVersion) { httpapi.ResourceNotFound(rw) return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) return } if job.CompletedAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Job has already completed!", }) return } if job.CanceledAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Job has already been marked as canceled!", }) return } - err = api.Database.UpdateProvisionerJobWithCancelByID(r.Context(), database.UpdateProvisionerJobWithCancelByIDParams{ + err = api.Database.UpdateProvisionerJobWithCancelByID(ctx, database.UpdateProvisionerJobWithCancelByIDParams{ ID: job.ID, CanceledAt: sql.NullTime{ Time: database.Now(), @@ -85,44 +87,45 @@ func (api *API) patchCancelTemplateVersion(rw http.ResponseWriter, r *http.Reque }, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating provisioner job.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "Job has been marked as canceled...", }) } func (api *API) templateVersionSchema(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(r, rbac.ActionRead, templateVersion) { httpapi.ResourceNotFound(rw) return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) return } if !job.CompletedAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Template version job hasn't completed!", }) return } - schemas, err := api.Database.GetParameterSchemasByJobID(r.Context(), job.ID) + schemas, err := api.Database.GetParameterSchemasByJobID(ctx, job.ID) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error listing parameter schemas.", Detail: err.Error(), }) @@ -132,7 +135,7 @@ func (api *API) templateVersionSchema(rw http.ResponseWriter, r *http.Request) { for _, schema := range schemas { apiSchema, err := convertParameterSchema(schema) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("Internal error converting schema %s.", schema.Name), Detail: err.Error(), }) @@ -140,10 +143,11 @@ func (api *API) templateVersionSchema(rw http.ResponseWriter, r *http.Request) { } apiSchemas = append(apiSchemas, apiSchema) } - httpapi.Write(rw, http.StatusOK, apiSchemas) + httpapi.Write(ctx, rw, http.StatusOK, apiSchemas) } func (api *API) templateVersionParameters(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() apiKey := httpmw.APIKey(r) templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(r, rbac.ActionRead, templateVersion) { @@ -151,21 +155,21 @@ func (api *API) templateVersionParameters(rw http.ResponseWriter, r *http.Reques return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) return } if !job.CompletedAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Job hasn't completed!", }) return } - values, err := parameter.Compute(r.Context(), api.Database, parameter.ComputeScope{ + values, err := parameter.Compute(ctx, api.Database, parameter.ComputeScope{ TemplateImportJobID: job.ID, OrganizationID: job.OrganizationID, UserID: apiKey.UserID, @@ -174,7 +178,7 @@ func (api *API) templateVersionParameters(rw http.ResponseWriter, r *http.Reques HideRedisplayValues: true, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error computing values.", Detail: err.Error(), }) @@ -184,10 +188,11 @@ func (api *API) templateVersionParameters(rw http.ResponseWriter, r *http.Reques values = []parameter.ComputedValue{} } - httpapi.Write(rw, http.StatusOK, values) + httpapi.Write(ctx, rw, http.StatusOK, values) } func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() apiKey := httpmw.APIKey(r) templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(r, rbac.ActionRead, templateVersion) { @@ -203,20 +208,20 @@ func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Reques } var req codersdk.CreateTemplateVersionDryRunRequest - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating provisioner job.", Detail: err.Error(), }) return } if !job.CompletedAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Template version import job hasn't completed!", }) return @@ -244,7 +249,7 @@ func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Reques ParameterValues: parameterValues, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error unmarshalling provisioner job.", Detail: err.Error(), }) @@ -253,7 +258,7 @@ func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Reques // Create a dry-run job jobID := uuid.New() - provisionerJob, err := api.Database.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{ + provisionerJob, err := api.Database.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ ID: jobID, CreatedAt: database.Now(), UpdatedAt: database.Now(), @@ -266,23 +271,24 @@ func (api *API) postTemplateVersionDryRun(rw http.ResponseWriter, r *http.Reques Input: input, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error inserting provisioner job.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusCreated, convertProvisionerJob(provisionerJob)) + httpapi.Write(ctx, rw, http.StatusCreated, convertProvisionerJob(provisionerJob)) } func (api *API) templateVersionDryRun(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() job, ok := api.fetchTemplateVersionDryRunJob(rw, r) if !ok { return } - httpapi.Write(rw, http.StatusOK, convertProvisionerJob(job)) + httpapi.Write(ctx, rw, http.StatusOK, convertProvisionerJob(job)) } func (api *API) templateVersionDryRunResources(rw http.ResponseWriter, r *http.Request) { @@ -304,6 +310,7 @@ func (api *API) templateVersionDryRunLogs(rw http.ResponseWriter, r *http.Reques } func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() templateVersion := httpmw.TemplateVersionParam(r) job, ok := api.fetchTemplateVersionDryRunJob(rw, r) @@ -317,19 +324,19 @@ func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http } if job.CompletedAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Job has already completed.", }) return } if job.CanceledAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Job has already been marked as canceled.", }) return } - err := api.Database.UpdateProvisionerJobWithCancelByID(r.Context(), database.UpdateProvisionerJobWithCancelByIDParams{ + err := api.Database.UpdateProvisionerJobWithCancelByID(ctx, database.UpdateProvisionerJobWithCancelByIDParams{ ID: job.ID, CanceledAt: sql.NullTime{ Time: database.Now(), @@ -337,20 +344,21 @@ func (api *API) patchTemplateVersionDryRunCancel(rw http.ResponseWriter, r *http }, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating provisioner job.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "Job has been marked as canceled.", }) } func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Request) (database.ProvisionerJob, bool) { var ( + ctx = r.Context() templateVersion = httpmw.TemplateVersionParam(r) jobID = chi.URLParam(r, "jobID") ) @@ -361,22 +369,22 @@ func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Re jobUUID, err := uuid.Parse(jobID) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Job ID %q must be a valid UUID.", jobID), Detail: err.Error(), }) return database.ProvisionerJob{}, false } - job, err := api.Database.GetProvisionerJobByID(r.Context(), jobUUID) + job, err := api.Database.GetProvisionerJobByID(ctx, jobUUID) if xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: fmt.Sprintf("Provisioner job %q not found.", jobUUID), }) return database.ProvisionerJob{}, false } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) @@ -397,7 +405,7 @@ func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Re var input templateVersionDryRunJob err = json.Unmarshal(job.Input, &input) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error unmarshaling job metadata.", Detail: err.Error(), }) @@ -412,6 +420,7 @@ func (api *API) fetchTemplateVersionDryRunJob(rw http.ResponseWriter, r *http.Re } func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() template := httpmw.TemplateParam(r) if !api.Authorize(r, rbac.ActionRead, template) { httpapi.ResourceNotFound(rw) @@ -429,14 +438,14 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque if paginationParams.AfterID != uuid.Nil { // See if the record exists first. If the record does not exist, the pagination // query will not work. - _, err := store.GetTemplateVersionByID(r.Context(), paginationParams.AfterID) + _, err := store.GetTemplateVersionByID(ctx, paginationParams.AfterID) if err != nil && xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Record at \"after_id\" (%q) does not exists.", paginationParams.AfterID.String()), }) return err } else if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version at after_id.", Detail: err.Error(), }) @@ -444,18 +453,18 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque } } - versions, err := store.GetTemplateVersionsByTemplateID(r.Context(), database.GetTemplateVersionsByTemplateIDParams{ + versions, err := store.GetTemplateVersionsByTemplateID(ctx, database.GetTemplateVersionsByTemplateIDParams{ TemplateID: template.ID, AfterID: paginationParams.AfterID, LimitOpt: int32(paginationParams.Limit), OffsetOpt: int32(paginationParams.Offset), }) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusOK, apiVersions) + httpapi.Write(ctx, rw, http.StatusOK, apiVersions) return err } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template versions.", Detail: err.Error(), }) @@ -466,9 +475,9 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque for _, version := range versions { jobIDs = append(jobIDs, version.JobID) } - jobs, err := store.GetProvisionerJobsByIDs(r.Context(), jobIDs) + jobs, err := store.GetProvisionerJobsByIDs(ctx, jobIDs) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) @@ -482,14 +491,14 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque for _, version := range versions { job, exists := jobByID[version.JobID.String()] if !exists { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("Job %q doesn't exist for version %q.", version.JobID, version.ID), }) return err } - createdByName, err := getUsernameByUserID(r.Context(), store, version.CreatedBy) + createdByName, err := getUsernameByUserID(ctx, store, version.CreatedBy) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching creator name.", Detail: err.Error(), }) @@ -504,10 +513,11 @@ func (api *API) templateVersionsByTemplate(rw http.ResponseWriter, r *http.Reque return } - httpapi.Write(rw, http.StatusOK, apiVersions) + httpapi.Write(ctx, rw, http.StatusOK, apiVersions) } func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() template := httpmw.TemplateParam(r) if !api.Authorize(r, rbac.ActionRead, template) { httpapi.ResourceNotFound(rw) @@ -515,7 +525,7 @@ func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) { } templateVersionName := chi.URLParam(r, "templateversionname") - templateVersion, err := api.Database.GetTemplateVersionByTemplateIDAndName(r.Context(), database.GetTemplateVersionByTemplateIDAndNameParams{ + templateVersion, err := api.Database.GetTemplateVersionByTemplateIDAndName(ctx, database.GetTemplateVersionByTemplateIDAndNameParams{ TemplateID: uuid.NullUUID{ UUID: template.ID, Valid: true, @@ -523,41 +533,42 @@ func (api *API) templateVersionByName(rw http.ResponseWriter, r *http.Request) { Name: templateVersionName, }) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: fmt.Sprintf("No template version found by name %q.", templateVersionName), }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version.", Detail: err.Error(), }) return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) return } - createdByName, err := getUsernameByUserID(r.Context(), api.Database, templateVersion.CreatedBy) + createdByName, err := getUsernameByUserID(ctx, api.Database, templateVersion.CreatedBy) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching creator name.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName)) + httpapi.Write(ctx, rw, http.StatusOK, convertTemplateVersion(templateVersion, convertProvisionerJob(job), createdByName)) } func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() template = httpmw.TemplateParam(r) auditor = *api.Auditor.Load() aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{ @@ -576,32 +587,32 @@ func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque } var req codersdk.UpdateActiveTemplateVersion - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } - version, err := api.Database.GetTemplateVersionByID(r.Context(), req.ID) + version, err := api.Database.GetTemplateVersionByID(ctx, req.ID) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: "Template version not found.", }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version.", Detail: err.Error(), }) return } if version.TemplateID.UUID.String() != template.ID.String() { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "The provided template version doesn't belong to the specified template.", }) return } err = api.Database.InTx(func(store database.Store) error { - err = store.UpdateTemplateActiveVersionByID(r.Context(), database.UpdateTemplateActiveVersionByIDParams{ + err = store.UpdateTemplateActiveVersionByID(ctx, database.UpdateTemplateActiveVersionByIDParams{ ID: template.ID, ActiveVersionID: req.ID, UpdatedAt: database.Now(), @@ -612,7 +623,7 @@ func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque return nil }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating active template version.", Detail: err.Error(), }) @@ -622,7 +633,7 @@ func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque newTemplate.ActiveVersionID = req.ID aReq.New = newTemplate - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "Updated the active template version!", }) } @@ -630,6 +641,7 @@ func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque // Creates a new version of a template. An import job is queued to parse the storage method provided. func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() apiKey = httpmw.APIKey(r) organization = httpmw.OrganizationParam(r) auditor = *api.Auditor.Load() @@ -644,7 +656,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht ) defer commitAudit() - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } @@ -655,15 +667,15 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht } if req.TemplateID != uuid.Nil { - _, err := api.Database.GetTemplateByID(r.Context(), req.TemplateID) + _, err := api.Database.GetTemplateByID(ctx, req.TemplateID) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: "Template does not exist.", }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template.", Detail: err.Error(), }) @@ -671,15 +683,15 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht } } - file, err := api.Database.GetFileByHash(r.Context(), req.StorageSource) + file, err := api.Database.GetFileByHash(ctx, req.StorageSource) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: "File not found.", }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching file.", Detail: err.Error(), }) @@ -708,7 +720,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht return xerrors.Errorf("cannot inherit parameters if template_id is not set") } - inheritedParams, err := db.ParameterValues(r.Context(), database.ParameterValuesParams{ + inheritedParams, err := db.ParameterValues(ctx, database.ParameterValuesParams{ IDs: inherits, }) if err != nil { @@ -717,7 +729,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht for _, copy := range inheritedParams { // This is a bit inefficient, as we make a new db call for each // param. - version, err := db.GetTemplateVersionByJobID(r.Context(), copy.ScopeID) + version, err := db.GetTemplateVersionByJobID(ctx, copy.ScopeID) if err != nil { return xerrors.Errorf("fetch template version for param %q: %w", copy.Name, err) } @@ -742,7 +754,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht continue } - _, err = db.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ + _, err = db.InsertParameterValue(ctx, database.InsertParameterValueParams{ ID: uuid.New(), Name: parameterValue.Name, CreatedAt: database.Now(), @@ -758,7 +770,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht } } - provisionerJob, err = db.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{ + provisionerJob, err = db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ ID: jobID, CreatedAt: database.Now(), UpdatedAt: database.Now(), @@ -782,7 +794,7 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht } } - templateVersion, err = db.InsertTemplateVersion(r.Context(), database.InsertTemplateVersionParams{ + templateVersion, err = db.InsertTemplateVersion(ctx, database.InsertTemplateVersionParams{ ID: uuid.New(), TemplateID: templateID, OrganizationID: organization.ID, @@ -802,23 +814,23 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht return nil }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: err.Error(), }) return } aReq.New = templateVersion - createdByName, err := getUsernameByUserID(r.Context(), api.Database, templateVersion.CreatedBy) + createdByName, err := getUsernameByUserID(ctx, api.Database, templateVersion.CreatedBy) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching creator name.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob), createdByName)) + httpapi.Write(ctx, rw, http.StatusCreated, convertTemplateVersion(templateVersion, convertProvisionerJob(provisionerJob), createdByName)) } // templateVersionResources returns the workspace agent resources associated @@ -827,15 +839,16 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht // The agents returned are informative of the template version, and do not // return agents associated with any particular workspace. func (api *API) templateVersionResources(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(r, rbac.ActionRead, templateVersion) { httpapi.ResourceNotFound(rw) return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) @@ -849,15 +862,16 @@ func (api *API) templateVersionResources(rw http.ResponseWriter, r *http.Request // and not any build logs for a workspace. // Eg: Logs returned from 'terraform plan' when uploading a new terraform file. func (api *API) templateVersionLogs(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() templateVersion := httpmw.TemplateVersionParam(r) if !api.Authorize(r, rbac.ActionRead, templateVersion) { httpapi.ResourceNotFound(rw) return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) diff --git a/coderd/userauth.go b/coderd/userauth.go index c7f1769dbc..f00a2a8f6b 100644 --- a/coderd/userauth.go +++ b/coderd/userauth.go @@ -40,8 +40,8 @@ type GithubOAuth2Config struct { AllowTeams []GithubOAuth2Team } -func (api *API) userAuthMethods(rw http.ResponseWriter, _ *http.Request) { - httpapi.Write(rw, http.StatusOK, codersdk.AuthMethods{ +func (api *API) userAuthMethods(rw http.ResponseWriter, r *http.Request) { + httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.AuthMethods{ Password: true, Github: api.GithubOAuth2Config != nil, OIDC: api.OIDCConfig != nil, @@ -57,7 +57,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { oauthClient := oauth2.NewClient(ctx, oauth2.StaticTokenSource(state.Token)) memberships, err := api.GithubOAuth2Config.ListOrganizationMemberships(ctx, oauthClient) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching authenticated Github user organizations.", Detail: err.Error(), }) @@ -74,7 +74,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { } } if selectedMembership == nil { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: "You aren't a member of the authorized Github organizations!", }) return @@ -82,7 +82,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { ghUser, err := api.GithubOAuth2Config.AuthenticatedUser(ctx, oauthClient) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching authenticated Github user.", Detail: err.Error(), }) @@ -106,7 +106,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { } } if allowedTeam == nil { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: fmt.Sprintf("You aren't a member of an authorized team in the %s Github organization!", *selectedMembership.Organization.Login), }) return @@ -115,7 +115,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { emails, err := api.GithubOAuth2Config.ListEmails(ctx, oauthClient) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching personal Github user.", Detail: err.Error(), }) @@ -131,7 +131,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { } if verifiedEmail == nil { - httpapi.Write(rw, http.StatusPreconditionRequired, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionRequired, codersdk.Response{ Message: "Your primary email must be verified on GitHub!", }) return @@ -148,14 +148,14 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) { }) var httpErr httpError if xerrors.As(err, &httpErr) { - httpapi.Write(rw, httpErr.code, codersdk.Response{ + httpapi.Write(ctx, rw, httpErr.code, codersdk.Response{ Message: httpErr.msg, Detail: httpErr.detail, }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to process OAuth login.", Detail: err.Error(), }) @@ -189,7 +189,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { // See the example here: https://github.com/coreos/go-oidc rawIDToken, ok := state.Token.Extra("id_token").(string) if !ok { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "id_token not found in response payload. Ensure your OIDC callback is configured correctly!", }) return @@ -197,7 +197,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { idToken, err := api.OIDCConfig.Verifier.Verify(ctx, rawIDToken) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to verify OIDC token.", Detail: err.Error(), }) @@ -210,7 +210,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { claims := map[string]interface{}{} err = idToken.Claims(&claims) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to extract OIDC claims.", Detail: err.Error(), }) @@ -218,14 +218,14 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { } emailRaw, ok := claims["email"] if !ok { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "No email found in OIDC payload!", }) return } email, ok := emailRaw.(string) if !ok { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Email in OIDC payload isn't a string. Got: %t", emailRaw), }) return @@ -234,7 +234,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { if ok { verified, ok := verifiedRaw.(bool) if ok && !verified { - httpapi.Write(rw, http.StatusForbidden, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: fmt.Sprintf("Verify the %q email address on your OIDC provider to authenticate!", email), }) return @@ -259,7 +259,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { } if api.OIDCConfig.EmailDomain != "" { if !strings.HasSuffix(email, api.OIDCConfig.EmailDomain) { - httpapi.Write(rw, http.StatusForbidden, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: fmt.Sprintf("Your email %q is not a part of the %q domain!", email, api.OIDCConfig.EmailDomain), }) return @@ -282,14 +282,14 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) { }) var httpErr httpError if xerrors.As(err, &httpErr) { - httpapi.Write(rw, httpErr.code, codersdk.Response{ + httpapi.Write(ctx, rw, httpErr.code, codersdk.Response{ Message: httpErr.msg, Detail: httpErr.detail, }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to process OAuth login.", Detail: err.Error(), }) @@ -479,9 +479,10 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook return nil, xerrors.Errorf("in tx: %w", err) } - cookie, err := api.createAPIKey(r, createAPIKeyParams{ - UserID: user.ID, - LoginType: params.LoginType, + cookie, err := api.createAPIKey(ctx, createAPIKeyParams{ + UserID: user.ID, + LoginType: params.LoginType, + RemoteAddr: r.RemoteAddr, }) if err != nil { return nil, xerrors.Errorf("create API key: %w", err) diff --git a/coderd/users.go b/coderd/users.go index d106645cbd..0e75c45cdf 100644 --- a/coderd/users.go +++ b/coderd/users.go @@ -37,9 +37,10 @@ import ( // Returns whether the initial user has been created or not. func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) { - userCount, err := api.Database.GetUserCount(r.Context()) + ctx := r.Context() + userCount, err := api.Database.GetUserCount(ctx) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user count.", Detail: err.Error(), }) @@ -47,28 +48,29 @@ func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) { } if userCount == 0 { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: "The initial user has not been created!", }) return } - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "The initial user has already been created!", }) } // Creates the initial user for a Coder deployment. func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() var createUser codersdk.CreateFirstUserRequest - if !httpapi.Read(rw, r, &createUser) { + if !httpapi.Read(ctx, rw, r, &createUser) { return } // This should only function for the first user. - userCount, err := api.Database.GetUserCount(r.Context()) + userCount, err := api.Database.GetUserCount(ctx) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user count.", Detail: err.Error(), }) @@ -77,13 +79,13 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { // If a user already exists, the initial admin user no longer can be created. if userCount != 0 { - httpapi.Write(rw, http.StatusConflict, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ Message: "The initial user has already been created.", }) return } - user, organizationID, err := api.CreateUser(r.Context(), api.Database, CreateUserRequest{ + user, organizationID, err := api.CreateUser(ctx, api.Database, CreateUserRequest{ CreateUserRequest: codersdk.CreateUserRequest{ Email: createUser.Email, Username: createUser.Username, @@ -94,7 +96,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { LoginType: database.LoginTypePassword, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error creating user.", Detail: err.Error(), }) @@ -112,12 +114,12 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { // the user. Maybe I add this ability to grant roles in the createUser api // and add some rbac bypass when calling api functions this way?? // Add the admin role to this first user. - _, err = api.Database.UpdateUserRoles(r.Context(), database.UpdateUserRolesParams{ + _, err = api.Database.UpdateUserRoles(ctx, database.UpdateUserRolesParams{ GrantedRoles: []string{rbac.RoleOwner()}, ID: user.ID, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating user's roles.", Detail: err.Error(), }) @@ -128,7 +130,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { for _, template := range api.AutoImportTemplates { archive, err := examples.Archive(string(template)) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error importing template.", Detail: xerrors.Errorf("load template archive for %q: %w", template, err).Error(), }) @@ -152,14 +154,14 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { } default: - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error importing template.", Detail: fmt.Sprintf("cannot auto-import %q template", template), }) return } - tpl, err := api.autoImportTemplate(r.Context(), autoImportTemplateOpts{ + tpl, err := api.autoImportTemplate(ctx, autoImportTemplateOpts{ name: string(template), archive: archive, params: parameters, @@ -167,28 +169,29 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) { orgID: organizationID, }) if err != nil { - api.Logger.Warn(r.Context(), "failed to auto-import template", slog.F("template", template), slog.F("parameters", parameters), slog.Error(err)) - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + api.Logger.Warn(ctx, "failed to auto-import template", slog.F("template", template), slog.F("parameters", parameters), slog.Error(err)) + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error importing template.", Detail: xerrors.Errorf("failed to import template %q: %w", template, err).Error(), }) return } - api.Logger.Info(r.Context(), "auto-imported template", slog.F("id", tpl.ID), slog.F("template", template), slog.F("parameters", parameters)) + api.Logger.Info(ctx, "auto-imported template", slog.F("id", tpl.ID), slog.F("template", template), slog.F("parameters", parameters)) } - httpapi.Write(rw, http.StatusCreated, codersdk.CreateFirstUserResponse{ + httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateFirstUserResponse{ UserID: user.ID, OrganizationID: organizationID, }) } func (api *API) users(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() query := r.URL.Query().Get("q") params, errs := userSearchQuery(query) if len(errs) > 0 { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid user search query.", Validations: errs, }) @@ -200,7 +203,7 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) { return } - users, err := api.Database.GetUsers(r.Context(), database.GetUsersParams{ + users, err := api.Database.GetUsers(ctx, database.GetUsersParams{ AfterID: paginationParams.AfterID, OffsetOpt: int32(paginationParams.Offset), LimitOpt: int32(paginationParams.Limit), @@ -209,11 +212,11 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) { RbacRole: params.RbacRole, }) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusOK, []codersdk.User{}) + httpapi.Write(ctx, rw, http.StatusOK, []codersdk.User{}) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching users.", Detail: err.Error(), }) @@ -222,7 +225,7 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) { users, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, users) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching users.", Detail: err.Error(), }) @@ -233,12 +236,12 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) { for _, user := range users { userIDs = append(userIDs, user.ID) } - organizationIDsByMemberIDsRows, err := api.Database.GetOrganizationIDsByMemberIDs(r.Context(), userIDs) + organizationIDsByMemberIDsRows, err := api.Database.GetOrganizationIDsByMemberIDs(ctx, userIDs) if xerrors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user's organizations.", Detail: err.Error(), }) @@ -255,6 +258,7 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) { // Creates a new user. func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() auditor := *api.Auditor.Load() aReq, commitAudit := audit.InitRequest[database.User](rw, &audit.RequestParams{ Audit: auditor, @@ -271,7 +275,7 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { } var req codersdk.CreateUserRequest - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } @@ -284,45 +288,45 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { // TODO: @emyrk Authorize the organization create if the createUser will do that. - _, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{ + _, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{ Username: req.Username, Email: req.Email, }) if err == nil { - httpapi.Write(rw, http.StatusConflict, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ Message: "User already exists.", }) return } if !errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user.", Detail: err.Error(), }) return } - _, err = api.Database.GetOrganizationByID(r.Context(), req.OrganizationID) + _, err = api.Database.GetOrganizationByID(ctx, req.OrganizationID) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: fmt.Sprintf("Organization does not exist with the provided id %q.", req.OrganizationID), }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching organization.", Detail: err.Error(), }) return } - user, _, err := api.CreateUser(r.Context(), api.Database, CreateUserRequest{ + user, _, err := api.CreateUser(ctx, api.Database, CreateUserRequest{ CreateUserRequest: req, LoginType: database.LoginTypePassword, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error creating user.", Detail: err.Error(), }) @@ -336,10 +340,11 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) { Users: []telemetry.User{telemetry.ConvertUser(user)}, }) - httpapi.Write(rw, http.StatusCreated, convertUser(user, []uuid.UUID{req.OrganizationID})) + httpapi.Write(ctx, rw, http.StatusCreated, convertUser(user, []uuid.UUID{req.OrganizationID})) } func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() auditor := *api.Auditor.Load() user := httpmw.UserParam(r) aReq, commitAudit := audit.InitRequest[database.User](rw, &audit.RequestParams{ @@ -356,29 +361,29 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) { return } - workspaces, err := api.Database.GetWorkspaces(r.Context(), database.GetWorkspacesParams{ + workspaces, err := api.Database.GetWorkspaces(ctx, database.GetWorkspacesParams{ OwnerID: user.ID, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspaces.", Detail: err.Error(), }) return } if len(workspaces) > 0 { - httpapi.Write(rw, http.StatusExpectationFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusExpectationFailed, codersdk.Response{ Message: "You cannot delete a user that has workspaces. Delete their workspaces and try again!", }) return } - err = api.Database.UpdateUserDeletedByID(r.Context(), database.UpdateUserDeletedByIDParams{ + err = api.Database.UpdateUserDeletedByID(ctx, database.UpdateUserDeletedByIDParams{ ID: user.ID, Deleted: true, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error deleting user.", Detail: err.Error(), }) @@ -386,7 +391,7 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) { } user.Deleted = true aReq.New = user - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "User has been deleted!", }) } @@ -394,8 +399,9 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) { // Returns the parameterized user requested. All validation // is completed in the middleware for this route. func (api *API) userByName(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() user := httpmw.UserParam(r) - organizationIDs, err := userOrganizationIDs(r.Context(), api, user) + organizationIDs, err := userOrganizationIDs(ctx, api, user) if !api.Authorize(r, rbac.ActionRead, rbac.ResourceUser) { httpapi.ResourceNotFound(rw) @@ -403,18 +409,19 @@ func (api *API) userByName(rw http.ResponseWriter, r *http.Request) { } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user's organizations.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertUser(user, organizationIDs)) + httpapi.Write(ctx, rw, http.StatusOK, convertUser(user, organizationIDs)) } func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() user = httpmw.UserParam(r) auditor = *api.Auditor.Load() aReq, commitAudit = audit.InitRequest[database.User](rw, &audit.RequestParams{ @@ -433,10 +440,10 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) { } var params codersdk.UpdateUserProfileRequest - if !httpapi.Read(rw, r, ¶ms) { + if !httpapi.Read(ctx, rw, r, ¶ms) { return } - existentUser, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{ + existentUser, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{ Username: params.Username, }) isDifferentUser := existentUser.ID != user.ID @@ -449,21 +456,21 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) { Detail: "this value is already in use and should be unique", }) } - httpapi.Write(rw, http.StatusConflict, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ Message: "User already exists.", Validations: responseErrors, }) return } if !errors.Is(err, sql.ErrNoRows) && isDifferentUser { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user.", Detail: err.Error(), }) return } - updatedUserProfile, err := api.Database.UpdateUserProfile(r.Context(), database.UpdateUserProfileParams{ + updatedUserProfile, err := api.Database.UpdateUserProfile(ctx, database.UpdateUserProfileParams{ ID: user.ID, Email: user.Email, AvatarURL: user.AvatarURL, @@ -473,28 +480,29 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) { aReq.New = updatedUserProfile if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating user.", Detail: err.Error(), }) return } - organizationIDs, err := userOrganizationIDs(r.Context(), api, user) + organizationIDs, err := userOrganizationIDs(ctx, api, user) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user's organizations.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs)) + httpapi.Write(ctx, rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs)) } func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseWriter, r *http.Request) { return func(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() user = httpmw.UserParam(r) apiKey = httpmw.APIKey(r) auditor = *api.Auditor.Load() @@ -520,26 +528,26 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW case user.ID == apiKey.UserID: // Suspending yourself is not allowed, as you can lock yourself // out of the system. - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "You cannot suspend yourself.", }) return case slice.Contains(user.RBACRoles, rbac.RoleOwner()): // You may not suspend an owner - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("You cannot suspend a user with the %q role. You must remove the role first.", rbac.RoleOwner()), }) return } } - suspendedUser, err := api.Database.UpdateUserStatus(r.Context(), database.UpdateUserStatusParams{ + suspendedUser, err := api.Database.UpdateUserStatus(ctx, database.UpdateUserStatusParams{ ID: user.ID, Status: status, UpdatedAt: database.Now(), }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("Internal error updating user's status to %q.", status), Detail: err.Error(), }) @@ -547,21 +555,22 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW } aReq.New = suspendedUser - organizations, err := userOrganizationIDs(r.Context(), api, user) + organizations, err := userOrganizationIDs(ctx, api, user) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user's organizations.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertUser(suspendedUser, organizations)) + httpapi.Write(ctx, rw, http.StatusOK, convertUser(suspendedUser, organizations)) } } func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() user = httpmw.UserParam(r) params codersdk.UpdateUserPasswordRequest auditor = *api.Auditor.Load() @@ -580,13 +589,13 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) { return } - if !httpapi.Read(rw, r, ¶ms) { + if !httpapi.Read(ctx, rw, r, ¶ms) { return } err := userpassword.Validate(params.Password) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid password.", Validations: []codersdk.ValidationError{ { @@ -608,14 +617,14 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) { // if they send something let's validate it ok, err := userpassword.Compare(string(user.HashedPassword), params.OldPassword) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error with passwords.", Detail: err.Error(), }) return } if !ok { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Old password is incorrect.", Validations: []codersdk.ValidationError{ { @@ -630,18 +639,18 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) { hashedPassword, err := userpassword.Hash(params.Password) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error hashing new password.", Detail: err.Error(), }) return } - err = api.Database.UpdateUserHashedPassword(r.Context(), database.UpdateUserHashedPasswordParams{ + err = api.Database.UpdateUserHashedPassword(ctx, database.UpdateUserHashedPasswordParams{ ID: user.ID, HashedPassword: []byte(hashedPassword), }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating user's password.", Detail: err.Error(), }) @@ -652,10 +661,11 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) { newUser.HashedPassword = []byte(hashedPassword) aReq.New = newUser - httpapi.Write(rw, http.StatusNoContent, nil) + httpapi.Write(ctx, rw, http.StatusNoContent, nil) } func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() user := httpmw.UserParam(r) if !api.Authorize(r, rbac.ActionRead, rbac.ResourceUserData.WithOwner(user.ID.String())) { @@ -668,9 +678,9 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) { OrganizationRoles: make(map[uuid.UUID][]string), } - memberships, err := api.Database.GetOrganizationMembershipsByUserID(r.Context(), user.ID) + memberships, err := api.Database.GetOrganizationMembershipsByUserID(ctx, user.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user's organization memberships.", Detail: err.Error(), }) @@ -680,7 +690,7 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) { // Only include ones we can read from RBAC. memberships, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, memberships) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching memberships.", Detail: err.Error(), }) @@ -694,11 +704,12 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) { } } - httpapi.Write(rw, http.StatusOK, resp) + httpapi.Write(ctx, rw, http.StatusOK, resp) } func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() // User is the user to modify. user = httpmw.UserParam(r) actorRoles = httpmw.UserAuthorization(r) @@ -715,14 +726,14 @@ func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) { aReq.Old = user if apiKey.UserID == user.ID { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "You cannot change your own roles.", }) return } var params codersdk.UpdateRoles - if !httpapi.Read(rw, r, ¶ms) { + if !httpapi.Read(ctx, rw, r, ¶ms) { return } @@ -755,28 +766,28 @@ func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) { } } - updatedUser, err := api.updateSiteUserRoles(r.Context(), database.UpdateUserRolesParams{ + updatedUser, err := api.updateSiteUserRoles(ctx, database.UpdateUserRolesParams{ GrantedRoles: params.Roles, ID: user.ID, }) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: err.Error(), }) return } aReq.New = updatedUser - organizationIDs, err := userOrganizationIDs(r.Context(), api, user) + organizationIDs, err := userOrganizationIDs(ctx, api, user) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user's organizations.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertUser(updatedUser, organizationIDs)) + httpapi.Write(ctx, rw, http.StatusOK, convertUser(updatedUser, organizationIDs)) } // updateSiteUserRoles will ensure only site wide roles are passed in as arguments. @@ -802,15 +813,16 @@ func (api *API) updateSiteUserRoles(ctx context.Context, args database.UpdateUse // Returns organizations the parameterized user has access to. func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() user := httpmw.UserParam(r) - organizations, err := api.Database.GetOrganizationsByUserID(r.Context(), user.ID) + organizations, err := api.Database.GetOrganizationsByUserID(ctx, user.ID) if errors.Is(err, sql.ErrNoRows) { err = nil organizations = []database.Organization{} } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user's organizations.", Detail: err.Error(), }) @@ -820,7 +832,7 @@ func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) { // Only return orgs the user can read. organizations, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, organizations) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching organizations.", Detail: err.Error(), }) @@ -832,18 +844,19 @@ func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) { publicOrganizations = append(publicOrganizations, convertOrganization(organization)) } - httpapi.Write(rw, http.StatusOK, publicOrganizations) + httpapi.Write(ctx, rw, http.StatusOK, publicOrganizations) } func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() organizationName := chi.URLParam(r, "organizationname") - organization, err := api.Database.GetOrganizationByName(r.Context(), organizationName) + organization, err := api.Database.GetOrganizationByName(ctx, organizationName) if errors.Is(err, sql.ErrNoRows) { httpapi.ResourceNotFound(rw) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching organization.", Detail: err.Error(), }) @@ -857,21 +870,22 @@ func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques return } - httpapi.Write(rw, http.StatusOK, convertOrganization(organization)) + httpapi.Write(ctx, rw, http.StatusOK, convertOrganization(organization)) } // Authenticates the user with an email and password. func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() var loginWithPassword codersdk.LoginWithPasswordRequest - if !httpapi.Read(rw, r, &loginWithPassword) { + if !httpapi.Read(ctx, rw, r, &loginWithPassword) { return } - user, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{ + user, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{ Email: loginWithPassword.Email, }) if err != nil && !xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error.", }) return @@ -880,7 +894,7 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) { // If the user doesn't exist, it will be a default struct. equal, err := userpassword.Compare(string(user.HashedPassword), loginWithPassword.Password) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error.", }) return @@ -888,14 +902,14 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) { if !equal { // This message is the same as above to remove ease in detecting whether // users are registered or not. Attackers still could with a timing attack. - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: "Incorrect email or password.", }) return } if user.LoginType != database.LoginTypePassword { - httpapi.Write(rw, http.StatusForbidden, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: fmt.Sprintf("Incorrect login type, attempting to use %q but user is of login type %q", database.LoginTypePassword, user.LoginType), }) return @@ -903,18 +917,19 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) { // If the user logged into a suspended account, reject the login request. if user.Status != database.UserStatusActive { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: "Your account is suspended. Contact an admin to reactivate your account.", }) return } - cookie, err := api.createAPIKey(r, createAPIKeyParams{ - UserID: user.ID, - LoginType: database.LoginTypePassword, + cookie, err := api.createAPIKey(ctx, createAPIKeyParams{ + UserID: user.ID, + LoginType: database.LoginTypePassword, + RemoteAddr: r.RemoteAddr, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to create API key.", Detail: err.Error(), }) @@ -923,13 +938,14 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) { api.setAuthCookie(rw, cookie) - httpapi.Write(rw, http.StatusCreated, codersdk.LoginWithPasswordResponse{ + httpapi.Write(ctx, rw, http.StatusCreated, codersdk.LoginWithPasswordResponse{ SessionToken: cookie.Value, }) } // Creates a new session key, used for logging in via the CLI. func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() user := httpmw.UserParam(r) if !api.Authorize(r, rbac.ActionCreate, rbac.ResourceAPIKey.WithOwner(user.ID.String())) { @@ -938,16 +954,17 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) { } lifeTime := time.Hour * 24 * 7 - cookie, err := api.createAPIKey(r, createAPIKeyParams{ - UserID: user.ID, - LoginType: database.LoginTypePassword, + cookie, err := api.createAPIKey(ctx, createAPIKeyParams{ + UserID: user.ID, + LoginType: database.LoginTypePassword, + RemoteAddr: r.RemoteAddr, // All api generated keys will last 1 week. Browser login tokens have // a shorter life. ExpiresAt: database.Now().Add(lifeTime), LifetimeSeconds: int64(lifeTime.Seconds()), }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to create API key.", Detail: err.Error(), }) @@ -958,7 +975,7 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) { // Setting the cookie will couple the browser sesion to the API // key we return here, meaning logging out of the website would // invalid your CLI key. - httpapi.Write(rw, http.StatusCreated, codersdk.GenerateAPIKeyResponse{Key: cookie.Value}) + httpapi.Write(ctx, rw, http.StatusCreated, codersdk.GenerateAPIKeyResponse{Key: cookie.Value}) } func (api *API) apiKey(rw http.ResponseWriter, r *http.Request) { @@ -979,18 +996,19 @@ func (api *API) apiKey(rw http.ResponseWriter, r *http.Request) { return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching API key.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertAPIKey(key)) + httpapi.Write(ctx, rw, http.StatusOK, convertAPIKey(key)) } // Clear the user's session cookie. func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() // Get a blank token cookie. cookie := &http.Cookie{ // MaxAge < 0 means to delete the cookie now. @@ -1002,16 +1020,16 @@ func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) { // Delete the session token from database. apiKey := httpmw.APIKey(r) - err := api.Database.DeleteAPIKeyByID(r.Context(), apiKey.ID) + err := api.Database.DeleteAPIKeyByID(ctx, apiKey.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error deleting API key.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "Logged out!", }) } @@ -1032,15 +1050,16 @@ func generateAPIKeyIDSecret() (id string, secret string, err error) { } type createAPIKeyParams struct { - UserID uuid.UUID - LoginType database.LoginType + UserID uuid.UUID + RemoteAddr string + LoginType database.LoginType // Optional. ExpiresAt time.Time LifetimeSeconds int64 } -func (api *API) createAPIKey(r *http.Request, params createAPIKeyParams) (*http.Cookie, error) { +func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*http.Cookie, error) { keyID, keySecret, err := generateAPIKeyIDSecret() if err != nil { return nil, xerrors.Errorf("generate API key: %w", err) @@ -1056,13 +1075,13 @@ func (api *API) createAPIKey(r *http.Request, params createAPIKeyParams) (*http. } } - host, _, _ := net.SplitHostPort(r.RemoteAddr) + host, _, _ := net.SplitHostPort(params.RemoteAddr) ip := net.ParseIP(host) if ip == nil { ip = net.IPv4(0, 0, 0, 0) } bitlen := len(ip) * 8 - key, err := api.Database.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{ + key, err := api.Database.InsertAPIKey(ctx, database.InsertAPIKeyParams{ ID: keyID, UserID: params.UserID, LifetimeSeconds: params.LifetimeSeconds, diff --git a/coderd/workspaceagents.go b/coderd/workspaceagents.go index 219fbb78b7..0a1c9a2694 100644 --- a/coderd/workspaceagents.go +++ b/coderd/workspaceagents.go @@ -34,15 +34,16 @@ import ( ) func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgentParam(r) workspace := httpmw.WorkspaceParam(r) if !api.Authorize(r, rbac.ActionRead, workspace) { httpapi.ResourceNotFound(rw) return } - dbApps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID) + dbApps, err := api.Database.GetWorkspaceAppsByAgentID(ctx, workspaceAgent.ID) if err != nil && !xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace agent applications.", Detail: err.Error(), }) @@ -50,28 +51,29 @@ func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) { } apiAgent, err := convertWorkspaceAgent(api.DERPMap, api.TailnetCoordinator, workspaceAgent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, apiAgent) + httpapi.Write(ctx, rw, http.StatusOK, apiAgent) } func (api *API) workspaceAgentMetadata(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceAgent := httpmw.WorkspaceAgent(r) apiAgent, err := convertWorkspaceAgent(api.DERPMap, api.TailnetCoordinator, workspaceAgent, nil, api.AgentInactiveDisconnectTimeout) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, agent.Metadata{ + httpapi.Write(ctx, rw, http.StatusOK, agent.Metadata{ DERPMap: api.DERPMap, EnvironmentVariables: apiAgent.EnvironmentVariables, StartupScript: apiAgent.StartupScript, @@ -80,10 +82,11 @@ 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, workspaceAgent, nil, api.AgentInactiveDisconnectTimeout) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", Detail: err.Error(), }) @@ -91,37 +94,39 @@ func (api *API) postWorkspaceAgentVersion(rw http.ResponseWriter, r *http.Reques } var req codersdk.PostWorkspaceAgentVersionRequest - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } - api.Logger.Info(r.Context(), "post workspace agent version", slog.F("agent_id", apiAgent.ID), slog.F("agent_version", req.Version)) + api.Logger.Info(ctx, "post workspace agent version", slog.F("agent_id", apiAgent.ID), slog.F("agent_version", req.Version)) if !semver.IsValid(req.Version) { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid workspace agent version provided.", Detail: fmt.Sprintf("invalid semver version: %q", req.Version), }) return } - if err := api.Database.UpdateWorkspaceAgentVersionByID(r.Context(), database.UpdateWorkspaceAgentVersionByIDParams{ + if err := api.Database.UpdateWorkspaceAgentVersionByID(ctx, database.UpdateWorkspaceAgentVersionByIDParams{ ID: apiAgent.ID, Version: req.Version, }); err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Error setting agent version", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, nil) + httpapi.Write(ctx, rw, http.StatusOK, nil) } // workspaceAgentPTY spawns a PTY and pipes it over a WebSocket. // This is used for the web terminal. func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + api.websocketWaitMutex.Lock() api.websocketWaitGroup.Add(1) api.websocketWaitMutex.Unlock() @@ -135,14 +140,14 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { } apiAgent, err := convertWorkspaceAgent(api.DERPMap, api.TailnetCoordinator, workspaceAgent, nil, api.AgentInactiveDisconnectTimeout) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", Detail: err.Error(), }) return } if apiAgent.Status != codersdk.WorkspaceAgentConnected { - httpapi.Write(rw, http.StatusPreconditionRequired, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionRequired, codersdk.Response{ Message: fmt.Sprintf("Agent state is %q, it must be in the %q state.", apiAgent.Status, codersdk.WorkspaceAgentConnected), }) return @@ -150,7 +155,7 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { reconnect, err := uuid.Parse(r.URL.Query().Get("reconnect")) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Query param 'reconnect' must be a valid UUID.", Validations: []codersdk.ValidationError{ {Field: "reconnect", Detail: "invalid UUID"}, @@ -171,14 +176,14 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) { CompressionMode: websocket.CompressionDisabled, }) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to accept websocket.", Detail: err.Error(), }) return } - _, wsNetConn := websocketNetConn(r.Context(), conn, websocket.MessageBinary) + _, wsNetConn := websocketNetConn(ctx, conn, websocket.MessageBinary) defer wsNetConn.Close() // Also closes conn. agentConn, release, err := api.workspaceAgentCache.Acquire(r, workspaceAgent.ID) @@ -233,34 +238,37 @@ func (api *API) dialWorkspaceAgentTailnet(r *http.Request, agentID uuid.UUID) (* } func (api *API) workspaceAgentConnection(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspace := httpmw.WorkspaceParam(r) if !api.Authorize(r, rbac.ActionRead, workspace) { httpapi.ResourceNotFound(rw) return } - httpapi.Write(rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentConnectionInfo{ DERPMap: api.DERPMap, }) } func (api *API) workspaceAgentCoordinate(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + api.websocketWaitMutex.Lock() api.websocketWaitGroup.Add(1) api.websocketWaitMutex.Unlock() defer api.websocketWaitGroup.Done() workspaceAgent := httpmw.WorkspaceAgent(r) - resource, err := api.Database.GetWorkspaceResourceByID(r.Context(), workspaceAgent.ResourceID) + resource, err := api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to accept websocket.", Detail: err.Error(), }) return } - build, err := api.Database.GetWorkspaceBuildByJobID(r.Context(), resource.JobID) + build, err := api.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Internal error fetching workspace build job.", Detail: err.Error(), }) @@ -269,7 +277,7 @@ func (api *API) workspaceAgentCoordinate(rw http.ResponseWriter, r *http.Request // Ensure the resource is still valid! // We only accept agents for resources on the latest build. ensureLatestBuild := func() error { - latestBuild, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), build.WorkspaceID) + latestBuild, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, build.WorkspaceID) if err != nil { return err } @@ -281,11 +289,11 @@ func (api *API) workspaceAgentCoordinate(rw http.ResponseWriter, r *http.Request err = ensureLatestBuild() if err != nil { - api.Logger.Debug(r.Context(), "agent tried to connect from non-latest built", + api.Logger.Debug(ctx, "agent tried to connect from non-latest built", slog.F("resource", resource), slog.F("agent", workspaceAgent), ) - httpapi.Write(rw, http.StatusForbidden, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: "Agent trying to connect from non-latest build.", Detail: err.Error(), }) @@ -294,13 +302,13 @@ func (api *API) workspaceAgentCoordinate(rw http.ResponseWriter, r *http.Request conn, err := websocket.Accept(rw, r, nil) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to accept websocket.", Detail: err.Error(), }) return } - ctx, wsNetConn := websocketNetConn(r.Context(), conn, websocket.MessageBinary) + ctx, wsNetConn := websocketNetConn(ctx, conn, websocket.MessageBinary) defer wsNetConn.Close() firstConnectedAt := workspaceAgent.FirstConnectedAt @@ -388,6 +396,8 @@ func (api *API) workspaceAgentCoordinate(rw http.ResponseWriter, r *http.Request // After accept a PubSub starts listening for new connection node updates // which are written to the WebSocket. func (api *API) workspaceAgentClientCoordinate(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + workspace := httpmw.WorkspaceParam(r) if !api.Authorize(r, rbac.ActionCreate, workspace.ExecutionRBAC()) { httpapi.ResourceNotFound(rw) @@ -402,14 +412,14 @@ func (api *API) workspaceAgentClientCoordinate(rw http.ResponseWriter, r *http.R conn, err := websocket.Accept(rw, r, nil) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to accept websocket.", Detail: err.Error(), }) return } defer conn.Close(websocket.StatusNormalClosure, "") - err = api.TailnetCoordinator.ServeClient(websocket.NetConn(r.Context(), conn, websocket.MessageBinary), uuid.New(), workspaceAgent.ID) + err = api.TailnetCoordinator.ServeClient(websocket.NetConn(ctx, conn, websocket.MessageBinary), uuid.New(), workspaceAgent.ID) if err != nil { _ = conn.Close(websocket.StatusInternalError, err.Error()) return @@ -508,33 +518,35 @@ func convertWorkspaceAgent(derpMap *tailcfg.DERPMap, coordinator *tailnet.Coordi return workspaceAgent, nil } func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + api.websocketWaitMutex.Lock() api.websocketWaitGroup.Add(1) api.websocketWaitMutex.Unlock() defer api.websocketWaitGroup.Done() workspaceAgent := httpmw.WorkspaceAgent(r) - resource, err := api.Database.GetWorkspaceResourceByID(r.Context(), workspaceAgent.ResourceID) + resource, err := api.Database.GetWorkspaceResourceByID(ctx, workspaceAgent.ResourceID) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to get workspace resource.", Detail: err.Error(), }) return } - build, err := api.Database.GetWorkspaceBuildByJobID(r.Context(), resource.JobID) + build, err := api.Database.GetWorkspaceBuildByJobID(ctx, resource.JobID) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to get build.", Detail: err.Error(), }) return } - workspace, err := api.Database.GetWorkspaceByID(r.Context(), build.WorkspaceID) + workspace, err := api.Database.GetWorkspaceByID(ctx, build.WorkspaceID) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to get workspace.", Detail: err.Error(), }) @@ -545,7 +557,7 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques CompressionMode: websocket.CompressionDisabled, }) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to accept websocket.", Detail: err.Error(), }) @@ -554,11 +566,11 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques defer conn.Close(websocket.StatusAbnormalClosure, "") var lastReport codersdk.AgentStatsReportResponse - latestStat, err := api.Database.GetLatestAgentStat(r.Context(), workspaceAgent.ID) + latestStat, err := api.Database.GetLatestAgentStat(ctx, workspaceAgent.ID) if err == nil { err = json.Unmarshal(latestStat.Payload, &lastReport) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to unmarshal stat payload.", Detail: err.Error(), }) @@ -567,12 +579,11 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques } // Allow overriding the stat interval for debugging and testing purposes. - ctx := r.Context() timer := time.NewTicker(api.AgentStatsRefreshInterval) for { err := wsjson.Write(ctx, conn, codersdk.AgentStatsReportRequest{}) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to write report request.", Detail: err.Error(), }) @@ -582,7 +593,7 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques err = wsjson.Read(ctx, conn, &rep) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to read report response.", Detail: err.Error(), }) @@ -591,7 +602,7 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques repJSON, err := json.Marshal(rep) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to marshal stat json.", Detail: err.Error(), }) @@ -630,7 +641,7 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques Payload: json.RawMessage(repJSON), }) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to insert agent stat.", Detail: err.Error(), }) @@ -642,7 +653,7 @@ func (api *API) workspaceAgentReportStats(rw http.ResponseWriter, r *http.Reques LastUsedAt: database.Now(), }) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to update workspace last used at.", Detail: err.Error(), }) diff --git a/coderd/workspaceapps.go b/coderd/workspaceapps.go index f6bca87869..a73dc184b8 100644 --- a/coderd/workspaceapps.go +++ b/coderd/workspaceapps.go @@ -66,7 +66,7 @@ func (api *API) handleSubdomainApplications(middlewares ...func(http.Handler) ht return } - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Could not determine request Host.", }) return @@ -145,7 +145,7 @@ func (api *API) proxyWorkspaceApplication(proxyApp proxyApplication, rw http.Res Name: proxyApp.AppName, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace application.", Detail: err.Error(), }) @@ -153,7 +153,7 @@ func (api *API) proxyWorkspaceApplication(proxyApp proxyApplication, rw http.Res } if !app.Url.Valid { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Application %s does not have a url.", app.Name), }) return @@ -163,7 +163,7 @@ func (api *API) proxyWorkspaceApplication(proxyApp proxyApplication, rw http.Res appURL, err := url.Parse(internalURL) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("App URL %q is invalid.", internalURL), Detail: err.Error(), }) @@ -205,7 +205,7 @@ func (api *API) proxyWorkspaceApplication(proxyApp proxyApplication, rw http.Res return } - httpapi.Write(w, http.StatusBadGateway, codersdk.Response{ + httpapi.Write(ctx, w, http.StatusBadGateway, codersdk.Response{ Message: "Failed to proxy request to application.", Detail: err.Error(), }) @@ -213,7 +213,7 @@ func (api *API) proxyWorkspaceApplication(proxyApp proxyApplication, rw http.Res conn, release, err := api.workspaceAgentCache.Acquire(r, proxyApp.Agent.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to dial workspace agent.", Detail: err.Error(), }) diff --git a/coderd/workspacebuilds.go b/coderd/workspacebuilds.go index aa73a64e0e..362fb388c0 100644 --- a/coderd/workspacebuilds.go +++ b/coderd/workspacebuilds.go @@ -22,6 +22,7 @@ import ( ) func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceBuild := httpmw.WorkspaceBuildParam(r) workspace := httpmw.WorkspaceParam(r) @@ -30,9 +31,9 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { return } - data, err := api.workspaceBuildsData(r.Context(), []database.Workspace{workspace}, []database.WorkspaceBuild{workspaceBuild}) + data, err := api.workspaceBuildsData(ctx, []database.Workspace{workspace}, []database.WorkspaceBuild{workspaceBuild}) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error getting workspace build data.", Detail: err.Error(), }) @@ -50,17 +51,18 @@ func (api *API) workspaceBuild(rw http.ResponseWriter, r *http.Request) { data.apps, ) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error converting workspace build.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, apiBuild) + httpapi.Write(ctx, rw, http.StatusOK, apiBuild) } func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspace := httpmw.WorkspaceParam(r) if !api.Authorize(r, rbac.ActionRead, workspace) { @@ -80,14 +82,14 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { if paginationParams.AfterID != uuid.Nil { // See if the record exists first. If the record does not exist, the pagination // query will not work. - _, err := store.GetWorkspaceBuildByID(r.Context(), paginationParams.AfterID) + _, err := store.GetWorkspaceBuildByID(ctx, paginationParams.AfterID) if err != nil && xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Record at \"after_id\" (%q) does not exist.", paginationParams.AfterID.String()), }) return err } else if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace build at \"after_id\".", Detail: err.Error(), }) @@ -101,12 +103,12 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { OffsetOpt: int32(paginationParams.Offset), LimitOpt: int32(paginationParams.Limit), } - workspaceBuilds, err = store.GetWorkspaceBuildByWorkspaceID(r.Context(), req) + workspaceBuilds, err = store.GetWorkspaceBuildByWorkspaceID(ctx, req) if xerrors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace build.", Detail: err.Error(), }) @@ -119,9 +121,9 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { return } - data, err := api.workspaceBuildsData(r.Context(), []database.Workspace{workspace}, workspaceBuilds) + data, err := api.workspaceBuildsData(ctx, []database.Workspace{workspace}, workspaceBuilds) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error getting workspace build data.", Detail: err.Error(), }) @@ -139,29 +141,30 @@ func (api *API) workspaceBuilds(rw http.ResponseWriter, r *http.Request) { data.apps, ) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error converting workspace build.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, apiBuilds) + httpapi.Write(ctx, rw, http.StatusOK, apiBuilds) } func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() owner := httpmw.UserParam(r) workspaceName := chi.URLParam(r, "workspacename") buildNumber, err := strconv.ParseInt(chi.URLParam(r, "buildnumber"), 10, 32) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Failed to parse build number as integer.", Detail: err.Error(), }) return } - workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{ + workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{ OwnerID: owner.ID, Name: workspaceName, }) @@ -170,7 +173,7 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace by name.", Detail: err.Error(), }) @@ -182,27 +185,27 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ return } - workspaceBuild, err := api.Database.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(r.Context(), database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{ + workspaceBuild, err := api.Database.GetWorkspaceBuildByWorkspaceIDAndBuildNumber(ctx, database.GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams{ WorkspaceID: workspace.ID, BuildNumber: int32(buildNumber), }) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: fmt.Sprintf("Workspace %q Build %d does not exist.", workspaceName, buildNumber), }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace build.", Detail: err.Error(), }) return } - data, err := api.workspaceBuildsData(r.Context(), []database.Workspace{workspace}, []database.WorkspaceBuild{workspaceBuild}) + data, err := api.workspaceBuildsData(ctx, []database.Workspace{workspace}, []database.WorkspaceBuild{workspaceBuild}) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error getting workspace build data.", Detail: err.Error(), }) @@ -220,21 +223,22 @@ func (api *API) workspaceBuildByBuildNumber(rw http.ResponseWriter, r *http.Requ data.apps, ) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error converting workspace build.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, apiBuild) + httpapi.Write(ctx, rw, http.StatusOK, apiBuild) } func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() apiKey := httpmw.APIKey(r) workspace := httpmw.WorkspaceParam(r) var createBuild codersdk.CreateWorkspaceBuildRequest - if !httpapi.Read(rw, r, &createBuild) { + if !httpapi.Read(ctx, rw, r, &createBuild) { return } @@ -246,7 +250,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { case codersdk.WorkspaceTransitionStart, codersdk.WorkspaceTransitionStop: action = rbac.ActionUpdate default: - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("Transition %q not supported.", createBuild.Transition), }) return @@ -257,9 +261,9 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { } if createBuild.TemplateVersionID == uuid.Nil { - latestBuild, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID) + latestBuild, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching the latest workspace build.", Detail: err.Error(), }) @@ -268,9 +272,9 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { createBuild.TemplateVersionID = latestBuild.TemplateVersionID } - templateVersion, err := api.Database.GetTemplateVersionByID(r.Context(), createBuild.TemplateVersionID) + templateVersion, err := api.Database.GetTemplateVersionByID(ctx, createBuild.TemplateVersionID) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Template version not found.", Validations: []codersdk.ValidationError{{ Field: "template_version_id", @@ -280,16 +284,16 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version.", Detail: err.Error(), }) return } - template, err := api.Database.GetTemplateByID(r.Context(), templateVersion.TemplateID.UUID) + template, err := api.Database.GetTemplateByID(ctx, templateVersion.TemplateID.UUID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to get template", Detail: err.Error(), }) @@ -301,7 +305,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { // cloud state. if createBuild.ProvisionerState != nil || createBuild.Orphan { if !api.Authorize(r, rbac.ActionUpdate, template.RBACObject()) { - httpapi.Write(rw, http.StatusForbidden, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ Message: "Only template managers may provide custom state", }) return @@ -311,7 +315,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { if createBuild.Orphan { if createBuild.Transition != codersdk.WorkspaceTransitionDelete { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Orphan is only permitted when deleting a workspace.", Detail: err.Error(), }) @@ -319,7 +323,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { } if createBuild.ProvisionerState != nil && createBuild.Orphan { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "ProvisionerState cannot be set alongside Orphan since state intent is unclear.", }) return @@ -327,9 +331,9 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { state = []byte{} } - templateVersionJob, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + templateVersionJob, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) @@ -338,17 +342,17 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { templateVersionJobStatus := convertProvisionerJob(templateVersionJob).Status switch templateVersionJobStatus { case codersdk.ProvisionerJobPending, codersdk.ProvisionerJobRunning: - httpapi.Write(rw, http.StatusNotAcceptable, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotAcceptable, codersdk.Response{ Message: fmt.Sprintf("The provided template version is %s. Wait for it to complete importing!", templateVersionJobStatus), }) return case codersdk.ProvisionerJobFailed: - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: fmt.Sprintf("The provided template version %q has failed to import: %q. You cannot build workspaces with it!", templateVersion.Name, templateVersionJob.Error.String), }) return case codersdk.ProvisionerJobCanceled: - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "The provided template version was canceled during import. You cannot builds workspaces with it!", }) return @@ -356,11 +360,11 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { // Store prior build number to compute new build number var priorBuildNum int32 - priorHistory, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID) + priorHistory, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) if err == nil { - priorJob, err := api.Database.GetProvisionerJobByID(r.Context(), priorHistory.JobID) + priorJob, err := api.Database.GetProvisionerJobByID(ctx, priorHistory.JobID) if err == nil && convertProvisionerJob(priorJob).Status.Active() { - httpapi.Write(rw, http.StatusConflict, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ Message: "A workspace build is already active.", }) return @@ -368,7 +372,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { priorBuildNum = priorHistory.BuildNumber } else if !errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching prior workspace build.", Detail: err.Error(), }) @@ -384,7 +388,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { // This must happen in a transaction to ensure history can be inserted, and // the prior history can update it's "after" column to point at the new. err = api.Database.InTx(func(db database.Store) error { - existing, err := db.ParameterValues(r.Context(), database.ParameterValuesParams{ + existing, err := db.ParameterValues(ctx, database.ParameterValuesParams{ Scopes: []database.ParameterScope{database.ParameterScopeWorkspace}, ScopeIds: []uuid.UUID{workspace.ID}, }) @@ -398,14 +402,14 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { for _, exists := range existing { // If the param exists, delete the old param before inserting the new one if exists.Name == param.Name { - err = db.DeleteParameterValueByID(r.Context(), exists.ID) + err = db.DeleteParameterValueByID(ctx, exists.ID) if err != nil && !xerrors.Is(err, sql.ErrNoRows) { return xerrors.Errorf("Failed to delete old param %q: %w", exists.Name, err) } } } - _, err = db.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ + _, err = db.InsertParameterValue(ctx, database.InsertParameterValueParams{ ID: uuid.New(), Name: param.Name, CreatedAt: now, @@ -428,7 +432,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { if err != nil { return xerrors.Errorf("marshal provision job: %w", err) } - provisionerJob, err = db.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{ + provisionerJob, err = db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ ID: uuid.New(), CreatedAt: database.Now(), UpdatedAt: database.Now(), @@ -444,7 +448,7 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { return xerrors.Errorf("insert provisioner job: %w", err) } - workspaceBuild, err = db.InsertWorkspaceBuild(r.Context(), database.InsertWorkspaceBuildParams{ + workspaceBuild, err = db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{ ID: workspaceBuildID, CreatedAt: database.Now(), UpdatedAt: database.Now(), @@ -464,21 +468,21 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { return nil }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error inserting workspace build.", Detail: err.Error(), }) return } - users, err := api.Database.GetUsersByIDs(r.Context(), database.GetUsersByIDsParams{ + users, err := api.Database.GetUsersByIDs(ctx, database.GetUsersByIDsParams{ IDs: []uuid.UUID{ workspace.OwnerID, workspaceBuild.InitiatorID, }, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error getting user.", Detail: err.Error(), }) @@ -496,21 +500,22 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) { []database.WorkspaceApp{}, ) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error converting workspace build.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusCreated, apiBuild) + httpapi.Write(ctx, rw, http.StatusCreated, apiBuild) } func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceBuild := httpmw.WorkspaceBuildParam(r) - workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspaceBuild.WorkspaceID) + workspace, err := api.Database.GetWorkspaceByID(ctx, workspaceBuild.WorkspaceID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "No workspace exists for this job.", }) return @@ -521,27 +526,27 @@ func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Reques return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), workspaceBuild.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, workspaceBuild.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) return } if job.CompletedAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Job has already completed!", }) return } if job.CanceledAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Job has already been marked as canceled!", }) return } - err = api.Database.UpdateProvisionerJobWithCancelByID(r.Context(), database.UpdateProvisionerJobWithCancelByIDParams{ + err = api.Database.UpdateProvisionerJobWithCancelByID(ctx, database.UpdateProvisionerJobWithCancelByIDParams{ ID: job.ID, CanceledAt: sql.NullTime{ Time: database.Now(), @@ -549,22 +554,23 @@ func (api *API) patchCancelWorkspaceBuild(rw http.ResponseWriter, r *http.Reques }, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating provisioner job.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{ Message: "Job has been marked as canceled...", }) } func (api *API) workspaceBuildResources(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceBuild := httpmw.WorkspaceBuildParam(r) - workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspaceBuild.WorkspaceID) + workspace, err := api.Database.GetWorkspaceByID(ctx, workspaceBuild.WorkspaceID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "No workspace exists for this job.", }) return @@ -575,9 +581,9 @@ func (api *API) workspaceBuildResources(rw http.ResponseWriter, r *http.Request) return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), workspaceBuild.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, workspaceBuild.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) @@ -587,10 +593,11 @@ func (api *API) workspaceBuildResources(rw http.ResponseWriter, r *http.Request) } func (api *API) workspaceBuildLogs(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceBuild := httpmw.WorkspaceBuildParam(r) - workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspaceBuild.WorkspaceID) + workspace, err := api.Database.GetWorkspaceByID(ctx, workspaceBuild.WorkspaceID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "No workspace exists for this job.", }) return @@ -601,9 +608,9 @@ func (api *API) workspaceBuildLogs(rw http.ResponseWriter, r *http.Request) { return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), workspaceBuild.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, workspaceBuild.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) @@ -613,10 +620,11 @@ func (api *API) workspaceBuildLogs(rw http.ResponseWriter, r *http.Request) { } func (api *API) workspaceBuildState(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceBuild := httpmw.WorkspaceBuildParam(r) - workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspaceBuild.WorkspaceID) + workspace, err := api.Database.GetWorkspaceByID(ctx, workspaceBuild.WorkspaceID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "No workspace exists for this job.", }) return diff --git a/coderd/workspaceresourceauth.go b/coderd/workspaceresourceauth.go index 048edeaba4..3d8fc0b281 100644 --- a/coderd/workspaceresourceauth.go +++ b/coderd/workspaceresourceauth.go @@ -19,13 +19,14 @@ import ( // Azure supports instance identity verification: // https://docs.microsoft.com/en-us/azure/virtual-machines/windows/instance-metadata-service?tabs=linux#tabgroup_14 func (api *API) postWorkspaceAuthAzureInstanceIdentity(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() var req codersdk.AzureInstanceIdentityToken - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } - instanceID, err := azureidentity.Validate(r.Context(), req.Signature, api.AzureCertificates) + instanceID, err := azureidentity.Validate(ctx, req.Signature, api.AzureCertificates) if err != nil { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: "Invalid Azure identity.", Detail: err.Error(), }) @@ -38,13 +39,14 @@ func (api *API) postWorkspaceAuthAzureInstanceIdentity(rw http.ResponseWriter, r // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html // Using this, we can exchange a signed instance payload for an agent token. func (api *API) postWorkspaceAuthAWSInstanceIdentity(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() var req codersdk.AWSInstanceIdentityToken - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } identity, err := awsidentity.Validate(req.Signature, req.Document, api.AWSCertificates) if err != nil { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: "Invalid AWS identity.", Detail: err.Error(), }) @@ -57,15 +59,16 @@ func (api *API) postWorkspaceAuthAWSInstanceIdentity(rw http.ResponseWriter, r * // https://cloud.google.com/compute/docs/instances/verifying-instance-identity // Using this, we can exchange a signed instance payload for an agent token. func (api *API) postWorkspaceAuthGoogleInstanceIdentity(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() var req codersdk.GoogleInstanceIdentityToken - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } // We leave the audience blank. It's not important we validate who made the token. - payload, err := api.GoogleTokenValidator.Validate(r.Context(), req.JSONWebToken, "") + payload, err := api.GoogleTokenValidator.Validate(ctx, req.JSONWebToken, "") if err != nil { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: "Invalid GCP identity.", Detail: err.Error(), }) @@ -80,7 +83,7 @@ func (api *API) postWorkspaceAuthGoogleInstanceIdentity(rw http.ResponseWriter, }{} err = mapstructure.Decode(payload.Claims, &claims) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Error decoding JWT claims.", Detail: err.Error(), }) @@ -90,38 +93,39 @@ func (api *API) postWorkspaceAuthGoogleInstanceIdentity(rw http.ResponseWriter, } func (api *API) handleAuthInstanceID(rw http.ResponseWriter, r *http.Request, instanceID string) { - agent, err := api.Database.GetWorkspaceAgentByInstanceID(r.Context(), instanceID) + ctx := r.Context() + agent, err := api.Database.GetWorkspaceAgentByInstanceID(ctx, instanceID) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: fmt.Sprintf("Instance with id %q not found.", instanceID), }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job agent.", Detail: err.Error(), }) return } - resource, err := api.Database.GetWorkspaceResourceByID(r.Context(), agent.ResourceID) + resource, err := api.Database.GetWorkspaceResourceByID(ctx, agent.ResourceID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job resource.", Detail: err.Error(), }) return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), resource.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, resource.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) return } if job.Type != database.ProvisionerJobTypeWorkspaceBuild { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("%q jobs cannot be authenticated.", job.Type), }) return @@ -129,15 +133,15 @@ func (api *API) handleAuthInstanceID(rw http.ResponseWriter, r *http.Request, in var jobData workspaceProvisionJob err = json.Unmarshal(job.Input, &jobData) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error extracting job data.", Detail: err.Error(), }) return } - resourceHistory, err := api.Database.GetWorkspaceBuildByID(r.Context(), jobData.WorkspaceBuildID) + resourceHistory, err := api.Database.GetWorkspaceBuildByID(ctx, jobData.WorkspaceBuildID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace build.", Detail: err.Error(), }) @@ -146,22 +150,22 @@ func (api *API) handleAuthInstanceID(rw http.ResponseWriter, r *http.Request, in // This token should only be exchanged if the instance ID is valid // for the latest history. If an instance ID is recycled by a cloud, // we'd hate to leak access to a user's workspace. - latestHistory, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), resourceHistory.WorkspaceID) + latestHistory, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(ctx, resourceHistory.WorkspaceID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching the latest workspace build.", Detail: err.Error(), }) return } if latestHistory.ID != resourceHistory.ID { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Resource found for id %q, but isn't registered on the latest history.", instanceID), }) return } - httpapi.Write(rw, http.StatusOK, codersdk.WorkspaceAgentAuthenticateResponse{ + httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspaceAgentAuthenticateResponse{ SessionToken: agent.AuthToken.String(), }) } diff --git a/coderd/workspaceresources.go b/coderd/workspaceresources.go index 416390132f..0cb9f687e6 100644 --- a/coderd/workspaceresources.go +++ b/coderd/workspaceresources.go @@ -16,6 +16,7 @@ import ( ) func (api *API) workspaceResource(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspaceBuild := httpmw.WorkspaceBuildParam(r) workspaceResource := httpmw.WorkspaceResourceParam(r) workspace := httpmw.WorkspaceParam(r) @@ -24,26 +25,26 @@ func (api *API) workspaceResource(rw http.ResponseWriter, r *http.Request) { return } - job, err := api.Database.GetProvisionerJobByID(r.Context(), workspaceBuild.JobID) + job, err := api.Database.GetProvisionerJobByID(ctx, workspaceBuild.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job.", Detail: err.Error(), }) return } if !job.CompletedAt.Valid { - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "Job hasn't completed!", }) return } - agents, err := api.Database.GetWorkspaceAgentsByResourceIDs(r.Context(), []uuid.UUID{workspaceResource.ID}) + agents, err := api.Database.GetWorkspaceAgentsByResourceIDs(ctx, []uuid.UUID{workspaceResource.ID}) if errors.Is(err, sql.ErrNoRows) { err = nil } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching provisioner job agents.", Detail: err.Error(), }) @@ -53,9 +54,9 @@ func (api *API) workspaceResource(rw http.ResponseWriter, r *http.Request) { for _, agent := range agents { agentIDs = append(agentIDs, agent.ID) } - apps, err := api.Database.GetWorkspaceAppsByAgentIDs(r.Context(), agentIDs) + apps, err := api.Database.GetWorkspaceAppsByAgentIDs(ctx, agentIDs) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace agent applications.", Detail: err.Error(), }) @@ -72,7 +73,7 @@ func (api *API) workspaceResource(rw http.ResponseWriter, r *http.Request) { convertedAgent, err := convertWorkspaceAgent(api.DERPMap, api.TailnetCoordinator, agent, convertApps(dbApps), api.AgentInactiveDisconnectTimeout) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error reading workspace agent.", Detail: err.Error(), }) @@ -84,14 +85,14 @@ func (api *API) workspaceResource(rw http.ResponseWriter, r *http.Request) { return apiAgents[i].Name < apiAgents[j].Name }) - metadata, err := api.Database.GetWorkspaceResourceMetadataByResourceID(r.Context(), workspaceResource.ID) + metadata, err := api.Database.GetWorkspaceResourceMetadataByResourceID(ctx, workspaceResource.ID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace resource metadata.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertWorkspaceResource(workspaceResource, apiAgents, metadata)) + httpapi.Write(ctx, rw, http.StatusOK, convertWorkspaceResource(workspaceResource, apiAgents, metadata)) } diff --git a/coderd/workspaces.go b/coderd/workspaces.go index f3375c6c75..1f32c79759 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -42,6 +42,7 @@ var ( ) func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspace := httpmw.WorkspaceParam(r) if !api.Authorize(r, rbac.ActionRead, workspace) { httpapi.ResourceNotFound(rw) @@ -56,7 +57,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { var err error showDeleted, err = strconv.ParseBool(deletedStr) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Invalid boolean value %q for \"include_deleted\" query param.", deletedStr), Validations: []codersdk.ValidationError{ {Field: "deleted", Detail: "Must be a valid boolean"}, @@ -66,22 +67,22 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { } } if workspace.Deleted && !showDeleted { - httpapi.Write(rw, http.StatusGone, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusGone, codersdk.Response{ Message: fmt.Sprintf("Workspace %q was deleted, you can view this workspace by specifying '?deleted=true' and trying again.", workspace.ID.String()), }) return } - data, err := api.workspaceData(r.Context(), []database.Workspace{workspace}) + data, err := api.workspaceData(ctx, []database.Workspace{workspace}) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace resources.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertWorkspace( + httpapi.Write(ctx, rw, http.StatusOK, convertWorkspace( workspace, data.builds[0], data.templates[0], @@ -92,12 +93,13 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) { // workspaces returns all workspaces a user can read. // Optional filters with query params func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() apiKey := httpmw.APIKey(r) queryStr := r.URL.Query().Get("q") filter, errs := workspaceSearchQuery(queryStr) if len(errs) > 0 { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid workspace search query.", Validations: errs, }) @@ -109,9 +111,9 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { filter.OwnerUsername = "" } - workspaces, err := api.Database.GetWorkspaces(r.Context(), filter) + workspaces, err := api.Database.GetWorkspaces(ctx, filter) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspaces.", Detail: err.Error(), }) @@ -121,16 +123,16 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { // Only return workspaces the user can read workspaces, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, workspaces) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspaces.", Detail: err.Error(), }) return } - data, err := api.workspaceData(r.Context(), workspaces) + data, err := api.workspaceData(ctx, workspaces) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace resources.", Detail: err.Error(), }) @@ -139,17 +141,18 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) { wss, err := convertWorkspaces(workspaces, data) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error converting workspaces.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, wss) + httpapi.Write(ctx, rw, http.StatusOK, wss) } func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() owner := httpmw.UserParam(r) workspaceName := chi.URLParam(r, "workspacename") @@ -158,7 +161,7 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) var err error includeDeleted, err = strconv.ParseBool(s) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Invalid boolean value %q for \"include_deleted\" query param.", s), Validations: []codersdk.ValidationError{ {Field: "include_deleted", Detail: "Must be a valid boolean"}, @@ -168,12 +171,12 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) } } - workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{ + workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{ OwnerID: owner.ID, Name: workspaceName, }) if includeDeleted && errors.Is(err, sql.ErrNoRows) { - workspace, err = api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{ + workspace, err = api.Database.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{ OwnerID: owner.ID, Name: workspaceName, Deleted: includeDeleted, @@ -184,7 +187,7 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace by name.", Detail: err.Error(), }) @@ -195,16 +198,16 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) return } - data, err := api.workspaceData(r.Context(), []database.Workspace{workspace}) + data, err := api.workspaceData(ctx, []database.Workspace{workspace}) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching workspace resources.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, convertWorkspace( + httpapi.Write(ctx, rw, http.StatusOK, convertWorkspace( workspace, data.builds[0], data.templates[0], @@ -215,6 +218,7 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request) // Create a new workspace for the currently authenticated user. func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() organization = httpmw.OrganizationParam(r) apiKey = httpmw.APIKey(r) auditor = api.Auditor.Load() @@ -234,13 +238,13 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req } var createWorkspace codersdk.CreateWorkspaceRequest - if !httpapi.Read(rw, r, &createWorkspace) { + if !httpapi.Read(ctx, rw, r, &createWorkspace) { return } - template, err := api.Database.GetTemplateByID(r.Context(), createWorkspace.TemplateID) + template, err := api.Database.GetTemplateByID(ctx, createWorkspace.TemplateID) if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Template %q doesn't exist.", createWorkspace.TemplateID.String()), Validations: []codersdk.ValidationError{{ Field: "template_id", @@ -250,7 +254,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template.", Detail: err.Error(), }) @@ -263,7 +267,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req } if organization.ID != template.OrganizationID { - httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{ Message: fmt.Sprintf("Template is not in organization %q.", organization.Name), }) return @@ -271,7 +275,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req dbAutostartSchedule, err := validWorkspaceSchedule(createWorkspace.AutostartSchedule, time.Duration(template.MinAutostartInterval)) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid Autostart Schedule.", Validations: []codersdk.ValidationError{{Field: "schedule", Detail: err.Error()}}, }) @@ -280,20 +284,20 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req dbTTL, err := validWorkspaceTTLMillis(createWorkspace.TTLMillis, time.Duration(template.MaxTtl)) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid Workspace Time to Shutdown.", Validations: []codersdk.ValidationError{{Field: "ttl_ms", Detail: err.Error()}}, }) return } - workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(r.Context(), database.GetWorkspaceByOwnerIDAndNameParams{ + workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{ OwnerID: apiKey.UserID, Name: createWorkspace.Name, }) if err == nil { // If the workspace already exists, don't allow creation. - httpapi.Write(rw, http.StatusConflict, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ Message: fmt.Sprintf("Workspace %q already exists.", createWorkspace.Name), Validations: []codersdk.ValidationError{{ Field: "name", @@ -303,24 +307,24 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req return } if err != nil && !errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: fmt.Sprintf("Internal error fetching workspace by name %q.", createWorkspace.Name), Detail: err.Error(), }) return } - templateVersion, err := api.Database.GetTemplateVersionByID(r.Context(), template.ActiveVersionID) + templateVersion, err := api.Database.GetTemplateVersionByID(ctx, template.ActiveVersionID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version.", Detail: err.Error(), }) return } - templateVersionJob, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID) + templateVersionJob, err := api.Database.GetProvisionerJobByID(ctx, templateVersion.JobID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching template version job.", Detail: err.Error(), }) @@ -329,17 +333,17 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req templateVersionJobStatus := convertProvisionerJob(templateVersionJob).Status switch templateVersionJobStatus { case codersdk.ProvisionerJobPending, codersdk.ProvisionerJobRunning: - httpapi.Write(rw, http.StatusNotAcceptable, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotAcceptable, codersdk.Response{ Message: fmt.Sprintf("The provided template version is %s. Wait for it to complete importing!", templateVersionJobStatus), }) return case codersdk.ProvisionerJobFailed: - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: fmt.Sprintf("The provided template version %q has failed to import. You cannot create workspaces using it!", templateVersion.Name), }) return case codersdk.ProvisionerJobCanceled: - httpapi.Write(rw, http.StatusPreconditionFailed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusPreconditionFailed, codersdk.Response{ Message: "The provided template version was canceled during import. You cannot create workspaces using it!", }) return @@ -351,7 +355,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req now := database.Now() workspaceBuildID := uuid.New() // Workspaces are created without any versions. - workspace, err = db.InsertWorkspace(r.Context(), database.InsertWorkspaceParams{ + workspace, err = db.InsertWorkspace(ctx, database.InsertWorkspaceParams{ ID: uuid.New(), CreatedAt: now, UpdatedAt: now, @@ -372,7 +376,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req continue } - _, err = db.InsertParameterValue(r.Context(), database.InsertParameterValueParams{ + _, err = db.InsertParameterValue(ctx, database.InsertParameterValueParams{ ID: uuid.New(), Name: parameterValue.Name, CreatedAt: now, @@ -394,7 +398,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req if err != nil { return xerrors.Errorf("marshal provision job: %w", err) } - provisionerJob, err = db.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{ + provisionerJob, err = db.InsertProvisionerJob(ctx, database.InsertProvisionerJobParams{ ID: uuid.New(), CreatedAt: now, UpdatedAt: now, @@ -409,7 +413,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req if err != nil { return xerrors.Errorf("insert provisioner job: %w", err) } - workspaceBuild, err = db.InsertWorkspaceBuild(r.Context(), database.InsertWorkspaceBuildParams{ + workspaceBuild, err = db.InsertWorkspaceBuild(ctx, database.InsertWorkspaceBuildParams{ ID: workspaceBuildID, CreatedAt: now, UpdatedAt: now, @@ -428,7 +432,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req return nil }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error creating workspace.", Detail: err.Error(), }) @@ -436,11 +440,11 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req } aReq.New = workspace - users, err := api.Database.GetUsersByIDs(r.Context(), database.GetUsersByIDsParams{ + users, err := api.Database.GetUsersByIDs(ctx, database.GetUsersByIDsParams{ IDs: []uuid.UUID{apiKey.UserID, workspaceBuild.InitiatorID}, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching user.", Detail: err.Error(), }) @@ -463,14 +467,14 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req []database.WorkspaceApp{}, ) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error converting workspace build.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusCreated, convertWorkspace( + httpapi.Write(ctx, rw, http.StatusCreated, convertWorkspace( workspace, apiBuild, template, @@ -480,6 +484,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() workspace = httpmw.WorkspaceParam(r) auditor = api.Auditor.Load() aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ @@ -498,7 +503,7 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { } var req codersdk.UpdateWorkspaceRequest - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } @@ -516,7 +521,7 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { name = req.Name } - newWorkspace, err := api.Database.UpdateWorkspace(r.Context(), database.UpdateWorkspaceParams{ + newWorkspace, err := api.Database.UpdateWorkspace(ctx, database.UpdateWorkspaceParams{ ID: workspace.ID, Name: name, }) @@ -528,14 +533,14 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { // We could do this check earlier but we'd need to start a // transaction. if errors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusMethodNotAllowed, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusMethodNotAllowed, codersdk.Response{ Message: fmt.Sprintf("Workspace %q is deleted and cannot be updated.", workspace.Name), }) return } // Check if the name was already in use. if database.IsUniqueViolation(err) { - httpapi.Write(rw, http.StatusConflict, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{ Message: fmt.Sprintf("Workspace %q already exists.", req.Name), Validations: []codersdk.ValidationError{{ Field: "name", @@ -544,7 +549,7 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { }) return } - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating workspace.", Detail: err.Error(), }) @@ -557,6 +562,7 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) { func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() workspace = httpmw.WorkspaceParam(r) auditor = api.Auditor.Load() aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ @@ -575,14 +581,14 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { } var req codersdk.UpdateWorkspaceAutostartRequest - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } - template, err := api.Database.GetTemplateByID(r.Context(), workspace.TemplateID) + template, err := api.Database.GetTemplateByID(ctx, workspace.TemplateID) if err != nil { - api.Logger.Error(r.Context(), "fetch workspace template", slog.F("workspace_id", workspace.ID), slog.F("template_id", workspace.TemplateID), slog.Error(err)) - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + api.Logger.Error(ctx, "fetch workspace template", slog.F("workspace_id", workspace.ID), slog.F("template_id", workspace.TemplateID), slog.Error(err)) + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Error fetching workspace template.", }) return @@ -590,19 +596,19 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { dbSched, err := validWorkspaceSchedule(req.Schedule, time.Duration(template.MinAutostartInterval)) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid autostart schedule.", Validations: []codersdk.ValidationError{{Field: "schedule", Detail: err.Error()}}, }) return } - err = api.Database.UpdateWorkspaceAutostart(r.Context(), database.UpdateWorkspaceAutostartParams{ + err = api.Database.UpdateWorkspaceAutostart(ctx, database.UpdateWorkspaceAutostartParams{ ID: workspace.ID, AutostartSchedule: dbSched, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error updating workspace autostart schedule.", Detail: err.Error(), }) @@ -618,6 +624,7 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) { func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { var ( + ctx = r.Context() workspace = httpmw.WorkspaceParam(r) auditor = api.Auditor.Load() aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{ @@ -635,16 +642,16 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { } var req codersdk.UpdateWorkspaceTTLRequest - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } var dbTTL sql.NullInt64 err := api.Database.InTx(func(s database.Store) error { - template, err := s.GetTemplateByID(r.Context(), workspace.TemplateID) + template, err := s.GetTemplateByID(ctx, workspace.TemplateID) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Error fetching workspace template!", }) return xerrors.Errorf("fetch workspace template: %w", err) @@ -654,7 +661,7 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { if err != nil { return codersdk.ValidationError{Field: "ttl_ms", Detail: err.Error()} } - if err := s.UpdateWorkspaceTTL(r.Context(), database.UpdateWorkspaceTTLParams{ + if err := s.UpdateWorkspaceTTL(ctx, database.UpdateWorkspaceTTLParams{ ID: workspace.ID, Ttl: dbTTL, }); err != nil { @@ -670,12 +677,12 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { var validErr codersdk.ValidationError if errors.As(err, &validErr) { resp.Validations = []codersdk.ValidationError{validErr} - httpapi.Write(rw, http.StatusBadRequest, resp) + httpapi.Write(ctx, rw, http.StatusBadRequest, resp) return } resp.Detail = err.Error() - httpapi.Write(rw, http.StatusInternalServerError, resp) + httpapi.Write(ctx, rw, http.StatusInternalServerError, resp) return } @@ -687,6 +694,7 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) { } func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspace := httpmw.WorkspaceParam(r) if !api.Authorize(r, rbac.ActionUpdate, workspace) { @@ -695,7 +703,7 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) { } var req codersdk.PutExtendWorkspaceRequest - if !httpapi.Read(rw, r, &req) { + if !httpapi.Read(ctx, rw, r, &req) { return } @@ -703,21 +711,21 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) { resp := codersdk.Response{} err := api.Database.InTx(func(s database.Store) error { - template, err := s.GetTemplateByID(r.Context(), workspace.TemplateID) + template, err := s.GetTemplateByID(ctx, workspace.TemplateID) if err != nil { code = http.StatusInternalServerError resp.Message = "Error fetching workspace template!" return xerrors.Errorf("get workspace template: %w", err) } - build, err := s.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID) + build, err := s.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspace.ID) if err != nil { code = http.StatusInternalServerError resp.Message = "Error fetching workspace build." return xerrors.Errorf("get latest workspace build: %w", err) } - job, err := s.GetProvisionerJobByID(r.Context(), build.JobID) + job, err := s.GetProvisionerJobByID(ctx, build.JobID) if err != nil { code = http.StatusInternalServerError resp.Message = "Error fetching workspace provisioner job." @@ -752,7 +760,7 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) { return err } - if err := s.UpdateWorkspaceBuildByID(r.Context(), database.UpdateWorkspaceBuildByIDParams{ + if err := s.UpdateWorkspaceBuildByID(ctx, database.UpdateWorkspaceBuildByIDParams{ ID: build.ID, UpdatedAt: build.UpdatedAt, ProvisionerState: build.ProvisionerState, @@ -767,12 +775,13 @@ func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) { return nil }) if err != nil { - api.Logger.Info(r.Context(), "extending workspace", slog.Error(err)) + api.Logger.Info(ctx, "extending workspace", slog.Error(err)) } - httpapi.Write(rw, code, resp) + httpapi.Write(ctx, rw, code, resp) } func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() workspace := httpmw.WorkspaceParam(r) if !api.Authorize(r, rbac.ActionRead, workspace) { httpapi.ResourceNotFound(rw) @@ -781,7 +790,7 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { sendEvent, err := httpapi.ServerSentEventSender(rw, r) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error setting up server-sent events.", Detail: err.Error(), }) @@ -792,12 +801,12 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { defer t.Stop() for { select { - case <-r.Context().Done(): + case <-ctx.Done(): return case <-t.C: - workspace, err := api.Database.GetWorkspaceByID(r.Context(), workspace.ID) + workspace, err := api.Database.GetWorkspaceByID(ctx, workspace.ID) if err != nil { - _ = sendEvent(r.Context(), codersdk.ServerSentEvent{ + _ = sendEvent(ctx, codersdk.ServerSentEvent{ Type: codersdk.ServerSentEventTypeError, Data: codersdk.Response{ Message: "Internal error fetching workspace.", @@ -807,9 +816,9 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { return } - data, err := api.workspaceData(r.Context(), []database.Workspace{workspace}) + data, err := api.workspaceData(ctx, []database.Workspace{workspace}) if err != nil { - _ = sendEvent(r.Context(), codersdk.ServerSentEvent{ + _ = sendEvent(ctx, codersdk.ServerSentEvent{ Type: codersdk.ServerSentEventTypeError, Data: codersdk.Response{ Message: "Internal error fetching workspace data.", @@ -819,7 +828,7 @@ func (api *API) watchWorkspace(rw http.ResponseWriter, r *http.Request) { return } - _ = sendEvent(r.Context(), codersdk.ServerSentEvent{ + _ = sendEvent(ctx, codersdk.ServerSentEvent{ Type: codersdk.ServerSentEventTypeData, Data: convertWorkspace( workspace, diff --git a/enterprise/cli/licenses_test.go b/enterprise/cli/licenses_test.go index a56e4a7327..b57fca9106 100644 --- a/enterprise/cli/licenses_test.go +++ b/enterprise/cli/licenses_test.go @@ -337,7 +337,7 @@ func (s *fakeLicenseAPI) deleteLicense(rw http.ResponseWriter, r *http.Request) rw.WriteHeader(200) } -func (*fakeLicenseAPI) entitlements(rw http.ResponseWriter, _ *http.Request) { +func (*fakeLicenseAPI) entitlements(rw http.ResponseWriter, r *http.Request) { features := make(map[string]codersdk.Feature) for _, f := range codersdk.FeatureNames { features[f] = codersdk.Feature{ @@ -345,7 +345,7 @@ func (*fakeLicenseAPI) entitlements(rw http.ResponseWriter, _ *http.Request) { Enabled: true, } } - httpapi.Write(rw, http.StatusOK, codersdk.Entitlements{ + httpapi.Write(r.Context(), rw, http.StatusOK, codersdk.Entitlements{ Features: features, Warnings: []string{testWarning}, HasLicense: true, diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index c53b885aaa..bc8b2088a8 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -190,6 +190,7 @@ func (api *API) updateEntitlements(ctx context.Context) error { } func (api *API) serveEntitlements(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() api.entitlementsMu.RLock() entitlements := api.entitlements api.entitlementsMu.RUnlock() @@ -201,9 +202,9 @@ func (api *API) serveEntitlements(rw http.ResponseWriter, r *http.Request) { } if entitlements.activeUsers.Limit != nil { - activeUserCount, err := api.Database.GetActiveUserCount(r.Context()) + activeUserCount, err := api.Database.GetActiveUserCount(ctx) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Unable to query database", Detail: err.Error(), }) @@ -229,7 +230,7 @@ func (api *API) serveEntitlements(rw http.ResponseWriter, r *http.Request) { "Audit logging is enabled but your license for this feature is expired.") } - httpapi.Write(rw, http.StatusOK, resp) + httpapi.Write(ctx, rw, http.StatusOK, resp) } func (api *API) runEntitlementsLoop(ctx context.Context) { diff --git a/enterprise/coderd/licenses.go b/enterprise/coderd/licenses.go index b3516912c8..cf26089872 100644 --- a/enterprise/coderd/licenses.go +++ b/enterprise/coderd/licenses.go @@ -80,19 +80,20 @@ var ( // period on the license, features will continue to work from the old license until its grace // period, then the users will get a warning allowing them to gracefully stop using the feature. func (api *API) postLicense(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() if !api.AGPL.Authorize(r, rbac.ActionCreate, rbac.ResourceLicense) { httpapi.Forbidden(rw) return } var addLicense codersdk.AddLicenseRequest - if !httpapi.Read(rw, r, &addLicense) { + if !httpapi.Read(ctx, rw, r, &addLicense) { return } claims, err := parseLicense(addLicense.License, api.Keys) if err != nil { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid license", Detail: err.Error(), }) @@ -100,7 +101,7 @@ func (api *API) postLicense(rw http.ResponseWriter, r *http.Request) { } exp, ok := claims["exp"].(float64) if !ok { - httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: "Invalid license", Detail: "exp claim missing or not parsable", }) @@ -108,21 +109,21 @@ func (api *API) postLicense(rw http.ResponseWriter, r *http.Request) { } expTime := time.Unix(int64(exp), 0) - dl, err := api.Database.InsertLicense(r.Context(), database.InsertLicenseParams{ + dl, err := api.Database.InsertLicense(ctx, database.InsertLicenseParams{ UploadedAt: database.Now(), JWT: addLicense.License, Exp: expTime, }) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Unable to add license to database", Detail: err.Error(), }) return } - err = api.updateEntitlements(r.Context()) + err = api.updateEntitlements(ctx) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to update entitlements", Detail: err.Error(), }) @@ -134,17 +135,18 @@ func (api *API) postLicense(rw http.ResponseWriter, r *http.Request) { // don't fail the HTTP request, since we did write it successfully to the database } - httpapi.Write(rw, http.StatusCreated, convertLicense(dl, claims)) + httpapi.Write(ctx, rw, http.StatusCreated, convertLicense(dl, claims)) } func (api *API) licenses(rw http.ResponseWriter, r *http.Request) { - licenses, err := api.Database.GetLicenses(r.Context()) + ctx := r.Context() + licenses, err := api.Database.GetLicenses(ctx) if xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusOK, []codersdk.License{}) + httpapi.Write(ctx, rw, http.StatusOK, []codersdk.License{}) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching licenses.", Detail: err.Error(), }) @@ -153,7 +155,7 @@ func (api *API) licenses(rw http.ResponseWriter, r *http.Request) { licenses, err = coderd.AuthorizeFilter(api.AGPL.HTTPAuth, r, rbac.ActionRead, licenses) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error fetching licenses.", Detail: err.Error(), }) @@ -161,16 +163,17 @@ func (api *API) licenses(rw http.ResponseWriter, r *http.Request) { } sdkLicenses, err := convertLicenses(licenses) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error parsing licenses.", Detail: err.Error(), }) return } - httpapi.Write(rw, http.StatusOK, sdkLicenses) + httpapi.Write(ctx, rw, http.StatusOK, sdkLicenses) } func (api *API) deleteLicense(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() if !api.AGPL.Authorize(r, rbac.ActionDelete, rbac.ResourceLicense) { httpapi.Forbidden(rw) return @@ -179,29 +182,29 @@ func (api *API) deleteLicense(rw http.ResponseWriter, r *http.Request) { idStr := chi.URLParam(r, "id") id, err := strconv.ParseInt(idStr, 10, 32) if err != nil { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: "License ID must be an integer", }) return } - _, err = api.Database.DeleteLicense(r.Context(), int32(id)) + _, err = api.Database.DeleteLicense(ctx, int32(id)) if xerrors.Is(err, sql.ErrNoRows) { - httpapi.Write(rw, http.StatusNotFound, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{ Message: "Unknown license ID", }) return } if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Internal error deleting license", Detail: err.Error(), }) return } - err = api.updateEntitlements(r.Context()) + err = api.updateEntitlements(ctx) if err != nil { - httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{ + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ Message: "Failed to update entitlements", Detail: err.Error(), }) diff --git a/enterprise/coderd/scim.go b/enterprise/coderd/scim.go index 0765199256..1d01a5601d 100644 --- a/enterprise/coderd/scim.go +++ b/enterprise/coderd/scim.go @@ -140,7 +140,7 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) { sUser.ID = user.ID.String() sUser.UserName = user.Username - httpapi.Write(rw, http.StatusOK, sUser) + httpapi.Write(ctx, rw, http.StatusOK, sUser) } // scimPatchUser supports suspending and activating users only. @@ -190,5 +190,5 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) { return } - httpapi.Write(rw, http.StatusOK, sUser) + httpapi.Write(ctx, rw, http.StatusOK, sUser) } diff --git a/provisionerd/provisionerd.go b/provisionerd/provisionerd.go index acbc7bf20b..af9444c91c 100644 --- a/provisionerd/provisionerd.go +++ b/provisionerd/provisionerd.go @@ -73,7 +73,7 @@ func New(clientDialer Dialer, opts *Options) *Server { ctx, ctxCancel := context.WithCancel(context.Background()) daemon := &Server{ opts: opts, - tracer: opts.Tracer.Tracer(""), + tracer: opts.Tracer.Tracer(tracing.TracerName), clientDialer: clientDialer, diff --git a/scripts/rules.go b/scripts/rules.go index c34ae6ed3b..35bb9e86dd 100644 --- a/scripts/rules.go +++ b/scripts/rules.go @@ -8,8 +8,10 @@ // - https://pkg.go.dev/github.com/quasilyte/go-ruleguard/dsl // // You run one of the following commands to execute your go rules only: -// golangci-lint run -// golangci-lint run --disable-all --enable=gocritic +// +// golangci-lint run +// golangci-lint run --disable-all --enable=gocritic +// // Note: don't forget to run `golangci-lint cache clean`! package gorules @@ -18,6 +20,7 @@ import ( ) // Use xerrors everywhere! It provides additional stacktrace info! +// //nolint:unused,deadcode,varnamelen func xerrors(m dsl.Matcher) { m.Import("errors") @@ -35,6 +38,7 @@ func xerrors(m dsl.Matcher) { } // databaseImport enforces not importing any database types into /codersdk. +// //nolint:unused,deadcode,varnamelen func databaseImport(m dsl.Matcher) { m.Import("github.com/coder/coder/coderd/database") @@ -46,6 +50,7 @@ func databaseImport(m dsl.Matcher) { // doNotCallTFailNowInsideGoroutine enforces not calling t.FailNow or // functions that may themselves call t.FailNow in goroutines outside // the main test goroutine. See testing.go:834 for why. +// //nolint:unused,deadcode,varnamelen func doNotCallTFailNowInsideGoroutine(m dsl.Matcher) { m.Import("testing") @@ -84,6 +89,7 @@ func doNotCallTFailNowInsideGoroutine(m dsl.Matcher) { // useStandardTimeoutsAndDelaysInTests ensures all tests use common // constants for timeouts and delays in usual scenarios, this allows us // to tweak them based on platform (important to avoid CI flakes). +// //nolint:unused,deadcode,varnamelen func useStandardTimeoutsAndDelaysInTests(m dsl.Matcher) { m.Import("github.com/stretchr/testify/require") @@ -182,13 +188,13 @@ func HttpAPIErrorMessage(m dsl.Matcher) { } m.Match(` - httpapi.Write($_, $s, httpapi.Response{ + httpapi.Write($_, $_, $s, httpapi.Response{ $*_, Message: $m, $*_, }) `, ` - httpapi.Write($_, $s, httpapi.Response{ + httpapi.Write($_, $_, $s, httpapi.Response{ $*_, Message: fmt.$f($m, $*_), $*_,