feat: trace httpapi.{Read,Write} (#4134)

This commit is contained in:
Colin Adler 2022-09-21 17:07:00 -05:00 committed by GitHub
parent 1bf2dc0cc3
commit 5de6f86959
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 919 additions and 774 deletions

View File

@ -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, &params) {
if !httpapi.Read(ctx, rw, r, &params) {
return
}
if params.Action == "" {

View File

@ -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!",
})
})

View File

@ -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")
}

View File

@ -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(),
})

View File

@ -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,
})

View File

@ -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(),
})

View File

@ -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)

View File

@ -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,

View File

@ -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)

View File

@ -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(),
})

View File

@ -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,
})

View File

@ -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))
})
}

View File

@ -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!",
})
}),

View File

@ -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))
})
}

View File

@ -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))
})

View File

@ -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))
})

View File

@ -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))
})
}

View File

@ -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))
})
}

View File

@ -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))
})

View File

@ -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())

View File

@ -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))

View File

@ -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))

View File

@ -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, &params) {
if !httpapi.Read(ctx, rw, r, &params) {
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) {

View File

@ -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.

View File

@ -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,
})

View File

@ -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{

View File

@ -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

View File

@ -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 {

View File

@ -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, &params) {
if !httpapi.Read(ctx, rw, r, &params) {
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 {

View File

@ -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 {

View File

@ -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(),
})

View File

@ -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)

View File

@ -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, &params) {
if !httpapi.Read(ctx, rw, r, &params) {
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, &params) {
if !httpapi.Read(ctx, rw, r, &params) {
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, &params) {
if !httpapi.Read(ctx, rw, r, &params) {
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,

View File

@ -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(),
})

View File

@ -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(),
})

View File

@ -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

View File

@ -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(),
})
}

View File

@ -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))
}

View File

@ -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,

View File

@ -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,

View File

@ -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) {

View File

@ -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(),
})

View File

@ -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)
}

View File

@ -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,

View File

@ -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, $*_),
$*_,