mirror of https://github.com/coder/coder.git
feat: trace httpapi.{Read,Write} (#4134)
This commit is contained in:
parent
1bf2dc0cc3
commit
5de6f86959
|
@ -21,12 +21,12 @@ import (
|
|||
)
|
||||
|
||||
func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceAuditLog) {
|
||||
httpapi.Forbidden(rw)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
page, ok := parsePagination(rw, r)
|
||||
if !ok {
|
||||
return
|
||||
|
@ -35,7 +35,7 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) {
|
|||
queryStr := r.URL.Query().Get("q")
|
||||
filter, errs := auditSearchQuery(queryStr)
|
||||
if len(errs) > 0 {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid audit search query.",
|
||||
Validations: errs,
|
||||
})
|
||||
|
@ -56,7 +56,7 @@ func (api *API) auditLogs(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, codersdk.AuditLogResponse{
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.AuditLogResponse{
|
||||
AuditLogs: convertAuditLogs(dblogs),
|
||||
})
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ func (api *API) auditLogCount(rw http.ResponseWriter, r *http.Request) {
|
|||
queryStr := r.URL.Query().Get("q")
|
||||
filter, errs := auditSearchQuery(queryStr)
|
||||
if len(errs) > 0 {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid audit search query.",
|
||||
Validations: errs,
|
||||
})
|
||||
|
@ -90,7 +90,7 @@ func (api *API) auditLogCount(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, codersdk.AuditLogCountResponse{
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.AuditLogCountResponse{
|
||||
Count: count,
|
||||
})
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
var params codersdk.CreateTestAuditLogRequest
|
||||
if !httpapi.Read(rw, r, ¶ms) {
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
return
|
||||
}
|
||||
if params.Action == "" {
|
||||
|
|
|
@ -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!",
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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!",
|
||||
})
|
||||
}),
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -17,21 +17,24 @@ import (
|
|||
)
|
||||
|
||||
func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) {
|
||||
user := httpmw.UserParam(r)
|
||||
organization := httpmw.OrganizationParam(r)
|
||||
member := httpmw.OrganizationMemberParam(r)
|
||||
apiKey := httpmw.APIKey(r)
|
||||
actorRoles := httpmw.UserAuthorization(r)
|
||||
var (
|
||||
ctx = r.Context()
|
||||
user = httpmw.UserParam(r)
|
||||
organization = httpmw.OrganizationParam(r)
|
||||
member = httpmw.OrganizationMemberParam(r)
|
||||
apiKey = httpmw.APIKey(r)
|
||||
actorRoles = httpmw.UserAuthorization(r)
|
||||
)
|
||||
|
||||
if apiKey.UserID == member.UserID {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "You cannot change your own organization roles.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var params codersdk.UpdateRoles
|
||||
if !httpapi.Read(rw, r, ¶ms) {
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -59,19 +62,19 @@ func (api *API) putMemberRoles(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
updatedUser, err := api.updateOrganizationMemberRoles(r.Context(), database.UpdateMemberRolesParams{
|
||||
updatedUser, err := api.updateOrganizationMemberRoles(ctx, database.UpdateMemberRolesParams{
|
||||
GrantedRoles: params.Roles,
|
||||
UserID: user.ID,
|
||||
OrgID: organization.ID,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertOrganizationMember(updatedUser))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertOrganizationMember(updatedUser))
|
||||
}
|
||||
|
||||
func (api *API) updateOrganizationMemberRoles(ctx context.Context, args database.UpdateMemberRolesParams) (database.OrganizationMember, error) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
|
||||
// assignableSiteRoles returns all site wide roles that can be assigned.
|
||||
func (api *API) assignableSiteRoles(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
actorRoles := httpmw.UserAuthorization(r)
|
||||
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceRoleAssignment) {
|
||||
httpapi.Forbidden(rw)
|
||||
|
@ -20,11 +21,12 @@ func (api *API) assignableSiteRoles(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
roles := rbac.SiteRoles()
|
||||
httpapi.Write(rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles))
|
||||
}
|
||||
|
||||
// assignableSiteRoles returns all site wide roles that can be assigned.
|
||||
func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
organization := httpmw.OrganizationParam(r)
|
||||
actorRoles := httpmw.UserAuthorization(r)
|
||||
|
||||
|
@ -34,10 +36,11 @@ func (api *API) assignableOrgRoles(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
roles := rbac.OrganizationRoles(organization.ID)
|
||||
httpapi.Write(rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, assignableRoles(actorRoles.Roles, roles))
|
||||
}
|
||||
|
||||
func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := httpmw.UserParam(r)
|
||||
apiKey := httpmw.APIKey(r)
|
||||
|
||||
|
@ -47,21 +50,21 @@ func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
// use the roles of the user specified, not the person making the request.
|
||||
roles, err := api.Database.GetAuthorizationUserRoles(r.Context(), user.ID)
|
||||
roles, err := api.Database.GetAuthorizationUserRoles(ctx, user.ID)
|
||||
if err != nil {
|
||||
httpapi.Forbidden(rw)
|
||||
return
|
||||
}
|
||||
|
||||
var params codersdk.UserAuthorizationRequest
|
||||
if !httpapi.Read(rw, r, ¶ms) {
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
return
|
||||
}
|
||||
|
||||
response := make(codersdk.UserAuthorizationResponse)
|
||||
for k, v := range params.Checks {
|
||||
if v.Object.ResourceType == "" {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: fmt.Sprintf("Object's \"resource_type\" field must be defined for key %q.", k),
|
||||
})
|
||||
return
|
||||
|
@ -70,7 +73,7 @@ func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) {
|
|||
if v.Object.OwnerID == "me" {
|
||||
v.Object.OwnerID = roles.ID.String()
|
||||
}
|
||||
err := api.Authorizer.ByRoleName(r.Context(), roles.ID.String(), roles.Roles, apiKey.Scope.ToRBAC(), rbac.Action(v.Action),
|
||||
err := api.Authorizer.ByRoleName(ctx, roles.ID.String(), roles.Roles, apiKey.Scope.ToRBAC(), rbac.Action(v.Action),
|
||||
rbac.Object{
|
||||
Owner: v.Object.OwnerID,
|
||||
OrgID: v.Object.OrganizationID,
|
||||
|
@ -79,7 +82,7 @@ func (api *API) checkPermissions(rw http.ResponseWriter, r *http.Request) {
|
|||
response[k] = err == nil
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, response)
|
||||
httpapi.Write(ctx, rw, http.StatusOK, response)
|
||||
}
|
||||
|
||||
func convertRole(role rbac.Role) codersdk.Role {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
|
|
247
coderd/users.go
247
coderd/users.go
|
@ -37,9 +37,10 @@ import (
|
|||
|
||||
// Returns whether the initial user has been created or not.
|
||||
func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
userCount, err := api.Database.GetUserCount(r.Context())
|
||||
ctx := r.Context()
|
||||
userCount, err := api.Database.GetUserCount(ctx)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user count.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -47,28 +48,29 @@ func (api *API) firstUser(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if userCount == 0 {
|
||||
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: "The initial user has not been created!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
|
||||
Message: "The initial user has already been created!",
|
||||
})
|
||||
}
|
||||
|
||||
// Creates the initial user for a Coder deployment.
|
||||
func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var createUser codersdk.CreateFirstUserRequest
|
||||
if !httpapi.Read(rw, r, &createUser) {
|
||||
if !httpapi.Read(ctx, rw, r, &createUser) {
|
||||
return
|
||||
}
|
||||
|
||||
// This should only function for the first user.
|
||||
userCount, err := api.Database.GetUserCount(r.Context())
|
||||
userCount, err := api.Database.GetUserCount(ctx)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user count.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -77,13 +79,13 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// If a user already exists, the initial admin user no longer can be created.
|
||||
if userCount != 0 {
|
||||
httpapi.Write(rw, http.StatusConflict, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
|
||||
Message: "The initial user has already been created.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user, organizationID, err := api.CreateUser(r.Context(), api.Database, CreateUserRequest{
|
||||
user, organizationID, err := api.CreateUser(ctx, api.Database, CreateUserRequest{
|
||||
CreateUserRequest: codersdk.CreateUserRequest{
|
||||
Email: createUser.Email,
|
||||
Username: createUser.Username,
|
||||
|
@ -94,7 +96,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
|||
LoginType: database.LoginTypePassword,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error creating user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -112,12 +114,12 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
|||
// the user. Maybe I add this ability to grant roles in the createUser api
|
||||
// and add some rbac bypass when calling api functions this way??
|
||||
// Add the admin role to this first user.
|
||||
_, err = api.Database.UpdateUserRoles(r.Context(), database.UpdateUserRolesParams{
|
||||
_, err = api.Database.UpdateUserRoles(ctx, database.UpdateUserRolesParams{
|
||||
GrantedRoles: []string{rbac.RoleOwner()},
|
||||
ID: user.ID,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error updating user's roles.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -128,7 +130,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
|||
for _, template := range api.AutoImportTemplates {
|
||||
archive, err := examples.Archive(string(template))
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error importing template.",
|
||||
Detail: xerrors.Errorf("load template archive for %q: %w", template, err).Error(),
|
||||
})
|
||||
|
@ -152,14 +154,14 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
default:
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error importing template.",
|
||||
Detail: fmt.Sprintf("cannot auto-import %q template", template),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
tpl, err := api.autoImportTemplate(r.Context(), autoImportTemplateOpts{
|
||||
tpl, err := api.autoImportTemplate(ctx, autoImportTemplateOpts{
|
||||
name: string(template),
|
||||
archive: archive,
|
||||
params: parameters,
|
||||
|
@ -167,28 +169,29 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
|
|||
orgID: organizationID,
|
||||
})
|
||||
if err != nil {
|
||||
api.Logger.Warn(r.Context(), "failed to auto-import template", slog.F("template", template), slog.F("parameters", parameters), slog.Error(err))
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
api.Logger.Warn(ctx, "failed to auto-import template", slog.F("template", template), slog.F("parameters", parameters), slog.Error(err))
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error importing template.",
|
||||
Detail: xerrors.Errorf("failed to import template %q: %w", template, err).Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
api.Logger.Info(r.Context(), "auto-imported template", slog.F("id", tpl.ID), slog.F("template", template), slog.F("parameters", parameters))
|
||||
api.Logger.Info(ctx, "auto-imported template", slog.F("id", tpl.ID), slog.F("template", template), slog.F("parameters", parameters))
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusCreated, codersdk.CreateFirstUserResponse{
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.CreateFirstUserResponse{
|
||||
UserID: user.ID,
|
||||
OrganizationID: organizationID,
|
||||
})
|
||||
}
|
||||
|
||||
func (api *API) users(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
query := r.URL.Query().Get("q")
|
||||
params, errs := userSearchQuery(query)
|
||||
if len(errs) > 0 {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid user search query.",
|
||||
Validations: errs,
|
||||
})
|
||||
|
@ -200,7 +203,7 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
users, err := api.Database.GetUsers(r.Context(), database.GetUsersParams{
|
||||
users, err := api.Database.GetUsers(ctx, database.GetUsersParams{
|
||||
AfterID: paginationParams.AfterID,
|
||||
OffsetOpt: int32(paginationParams.Offset),
|
||||
LimitOpt: int32(paginationParams.Limit),
|
||||
|
@ -209,11 +212,11 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
|
|||
RbacRole: params.RbacRole,
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusOK, []codersdk.User{})
|
||||
httpapi.Write(ctx, rw, http.StatusOK, []codersdk.User{})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching users.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -222,7 +225,7 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
users, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, users)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching users.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -233,12 +236,12 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
|
|||
for _, user := range users {
|
||||
userIDs = append(userIDs, user.ID)
|
||||
}
|
||||
organizationIDsByMemberIDsRows, err := api.Database.GetOrganizationIDsByMemberIDs(r.Context(), userIDs)
|
||||
organizationIDsByMemberIDsRows, err := api.Database.GetOrganizationIDsByMemberIDs(ctx, userIDs)
|
||||
if xerrors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user's organizations.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -255,6 +258,7 @@ func (api *API) users(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Creates a new user.
|
||||
func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
auditor := *api.Auditor.Load()
|
||||
aReq, commitAudit := audit.InitRequest[database.User](rw, &audit.RequestParams{
|
||||
Audit: auditor,
|
||||
|
@ -271,7 +275,7 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
var req codersdk.CreateUserRequest
|
||||
if !httpapi.Read(rw, r, &req) {
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -284,45 +288,45 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// TODO: @emyrk Authorize the organization create if the createUser will do that.
|
||||
|
||||
_, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
|
||||
_, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
|
||||
Username: req.Username,
|
||||
Email: req.Email,
|
||||
})
|
||||
if err == nil {
|
||||
httpapi.Write(rw, http.StatusConflict, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
|
||||
Message: "User already exists.",
|
||||
})
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
_, err = api.Database.GetOrganizationByID(r.Context(), req.OrganizationID)
|
||||
_, err = api.Database.GetOrganizationByID(ctx, req.OrganizationID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusNotFound, codersdk.Response{
|
||||
Message: fmt.Sprintf("Organization does not exist with the provided id %q.", req.OrganizationID),
|
||||
})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching organization.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
user, _, err := api.CreateUser(r.Context(), api.Database, CreateUserRequest{
|
||||
user, _, err := api.CreateUser(ctx, api.Database, CreateUserRequest{
|
||||
CreateUserRequest: req,
|
||||
LoginType: database.LoginTypePassword,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error creating user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -336,10 +340,11 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
|
|||
Users: []telemetry.User{telemetry.ConvertUser(user)},
|
||||
})
|
||||
|
||||
httpapi.Write(rw, http.StatusCreated, convertUser(user, []uuid.UUID{req.OrganizationID}))
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, convertUser(user, []uuid.UUID{req.OrganizationID}))
|
||||
}
|
||||
|
||||
func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
auditor := *api.Auditor.Load()
|
||||
user := httpmw.UserParam(r)
|
||||
aReq, commitAudit := audit.InitRequest[database.User](rw, &audit.RequestParams{
|
||||
|
@ -356,29 +361,29 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
workspaces, err := api.Database.GetWorkspaces(r.Context(), database.GetWorkspacesParams{
|
||||
workspaces, err := api.Database.GetWorkspaces(ctx, database.GetWorkspacesParams{
|
||||
OwnerID: user.ID,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching workspaces.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if len(workspaces) > 0 {
|
||||
httpapi.Write(rw, http.StatusExpectationFailed, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusExpectationFailed, codersdk.Response{
|
||||
Message: "You cannot delete a user that has workspaces. Delete their workspaces and try again!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err = api.Database.UpdateUserDeletedByID(r.Context(), database.UpdateUserDeletedByIDParams{
|
||||
err = api.Database.UpdateUserDeletedByID(ctx, database.UpdateUserDeletedByIDParams{
|
||||
ID: user.ID,
|
||||
Deleted: true,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error deleting user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -386,7 +391,7 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
user.Deleted = true
|
||||
aReq.New = user
|
||||
httpapi.Write(rw, http.StatusOK, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
|
||||
Message: "User has been deleted!",
|
||||
})
|
||||
}
|
||||
|
@ -394,8 +399,9 @@ func (api *API) deleteUser(rw http.ResponseWriter, r *http.Request) {
|
|||
// Returns the parameterized user requested. All validation
|
||||
// is completed in the middleware for this route.
|
||||
func (api *API) userByName(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := httpmw.UserParam(r)
|
||||
organizationIDs, err := userOrganizationIDs(r.Context(), api, user)
|
||||
organizationIDs, err := userOrganizationIDs(ctx, api, user)
|
||||
|
||||
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceUser) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
|
@ -403,18 +409,19 @@ func (api *API) userByName(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user's organizations.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertUser(user, organizationIDs))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertUser(user, organizationIDs))
|
||||
}
|
||||
|
||||
func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
user = httpmw.UserParam(r)
|
||||
auditor = *api.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.User](rw, &audit.RequestParams{
|
||||
|
@ -433,10 +440,10 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
var params codersdk.UpdateUserProfileRequest
|
||||
if !httpapi.Read(rw, r, ¶ms) {
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
return
|
||||
}
|
||||
existentUser, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
|
||||
existentUser, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
|
||||
Username: params.Username,
|
||||
})
|
||||
isDifferentUser := existentUser.ID != user.ID
|
||||
|
@ -449,21 +456,21 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
|
|||
Detail: "this value is already in use and should be unique",
|
||||
})
|
||||
}
|
||||
httpapi.Write(rw, http.StatusConflict, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
|
||||
Message: "User already exists.",
|
||||
Validations: responseErrors,
|
||||
})
|
||||
return
|
||||
}
|
||||
if !errors.Is(err, sql.ErrNoRows) && isDifferentUser {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
updatedUserProfile, err := api.Database.UpdateUserProfile(r.Context(), database.UpdateUserProfileParams{
|
||||
updatedUserProfile, err := api.Database.UpdateUserProfile(ctx, database.UpdateUserProfileParams{
|
||||
ID: user.ID,
|
||||
Email: user.Email,
|
||||
AvatarURL: user.AvatarURL,
|
||||
|
@ -473,28 +480,29 @@ func (api *API) putUserProfile(rw http.ResponseWriter, r *http.Request) {
|
|||
aReq.New = updatedUserProfile
|
||||
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error updating user.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
organizationIDs, err := userOrganizationIDs(r.Context(), api, user)
|
||||
organizationIDs, err := userOrganizationIDs(ctx, api, user)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user's organizations.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertUser(updatedUserProfile, organizationIDs))
|
||||
}
|
||||
|
||||
func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseWriter, r *http.Request) {
|
||||
return func(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
user = httpmw.UserParam(r)
|
||||
apiKey = httpmw.APIKey(r)
|
||||
auditor = *api.Auditor.Load()
|
||||
|
@ -520,26 +528,26 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
|
|||
case user.ID == apiKey.UserID:
|
||||
// Suspending yourself is not allowed, as you can lock yourself
|
||||
// out of the system.
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "You cannot suspend yourself.",
|
||||
})
|
||||
return
|
||||
case slice.Contains(user.RBACRoles, rbac.RoleOwner()):
|
||||
// You may not suspend an owner
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: fmt.Sprintf("You cannot suspend a user with the %q role. You must remove the role first.", rbac.RoleOwner()),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
suspendedUser, err := api.Database.UpdateUserStatus(r.Context(), database.UpdateUserStatusParams{
|
||||
suspendedUser, err := api.Database.UpdateUserStatus(ctx, database.UpdateUserStatusParams{
|
||||
ID: user.ID,
|
||||
Status: status,
|
||||
UpdatedAt: database.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: fmt.Sprintf("Internal error updating user's status to %q.", status),
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -547,21 +555,22 @@ func (api *API) putUserStatus(status database.UserStatus) func(rw http.ResponseW
|
|||
}
|
||||
aReq.New = suspendedUser
|
||||
|
||||
organizations, err := userOrganizationIDs(r.Context(), api, user)
|
||||
organizations, err := userOrganizationIDs(ctx, api, user)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user's organizations.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertUser(suspendedUser, organizations))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertUser(suspendedUser, organizations))
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
user = httpmw.UserParam(r)
|
||||
params codersdk.UpdateUserPasswordRequest
|
||||
auditor = *api.Auditor.Load()
|
||||
|
@ -580,13 +589,13 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if !httpapi.Read(rw, r, ¶ms) {
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
return
|
||||
}
|
||||
|
||||
err := userpassword.Validate(params.Password)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid password.",
|
||||
Validations: []codersdk.ValidationError{
|
||||
{
|
||||
|
@ -608,14 +617,14 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
|
|||
// if they send something let's validate it
|
||||
ok, err := userpassword.Compare(string(user.HashedPassword), params.OldPassword)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error with passwords.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Old password is incorrect.",
|
||||
Validations: []codersdk.ValidationError{
|
||||
{
|
||||
|
@ -630,18 +639,18 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
hashedPassword, err := userpassword.Hash(params.Password)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error hashing new password.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
err = api.Database.UpdateUserHashedPassword(r.Context(), database.UpdateUserHashedPasswordParams{
|
||||
err = api.Database.UpdateUserHashedPassword(ctx, database.UpdateUserHashedPasswordParams{
|
||||
ID: user.ID,
|
||||
HashedPassword: []byte(hashedPassword),
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error updating user's password.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -652,10 +661,11 @@ func (api *API) putUserPassword(rw http.ResponseWriter, r *http.Request) {
|
|||
newUser.HashedPassword = []byte(hashedPassword)
|
||||
aReq.New = newUser
|
||||
|
||||
httpapi.Write(rw, http.StatusNoContent, nil)
|
||||
httpapi.Write(ctx, rw, http.StatusNoContent, nil)
|
||||
}
|
||||
|
||||
func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := httpmw.UserParam(r)
|
||||
|
||||
if !api.Authorize(r, rbac.ActionRead, rbac.ResourceUserData.WithOwner(user.ID.String())) {
|
||||
|
@ -668,9 +678,9 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
|
|||
OrganizationRoles: make(map[uuid.UUID][]string),
|
||||
}
|
||||
|
||||
memberships, err := api.Database.GetOrganizationMembershipsByUserID(r.Context(), user.ID)
|
||||
memberships, err := api.Database.GetOrganizationMembershipsByUserID(ctx, user.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user's organization memberships.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -680,7 +690,7 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
|
|||
// Only include ones we can read from RBAC.
|
||||
memberships, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, memberships)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching memberships.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -694,11 +704,12 @@ func (api *API) userRoles(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, resp)
|
||||
httpapi.Write(ctx, rw, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
// User is the user to modify.
|
||||
user = httpmw.UserParam(r)
|
||||
actorRoles = httpmw.UserAuthorization(r)
|
||||
|
@ -715,14 +726,14 @@ func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
|
|||
aReq.Old = user
|
||||
|
||||
if apiKey.UserID == user.ID {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "You cannot change your own roles.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
var params codersdk.UpdateRoles
|
||||
if !httpapi.Read(rw, r, ¶ms) {
|
||||
if !httpapi.Read(ctx, rw, r, ¶ms) {
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -755,28 +766,28 @@ func (api *API) putUserRoles(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
updatedUser, err := api.updateSiteUserRoles(r.Context(), database.UpdateUserRolesParams{
|
||||
updatedUser, err := api.updateSiteUserRoles(ctx, database.UpdateUserRolesParams{
|
||||
GrantedRoles: params.Roles,
|
||||
ID: user.ID,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
aReq.New = updatedUser
|
||||
|
||||
organizationIDs, err := userOrganizationIDs(r.Context(), api, user)
|
||||
organizationIDs, err := userOrganizationIDs(ctx, api, user)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user's organizations.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertUser(updatedUser, organizationIDs))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertUser(updatedUser, organizationIDs))
|
||||
}
|
||||
|
||||
// updateSiteUserRoles will ensure only site wide roles are passed in as arguments.
|
||||
|
@ -802,15 +813,16 @@ func (api *API) updateSiteUserRoles(ctx context.Context, args database.UpdateUse
|
|||
|
||||
// Returns organizations the parameterized user has access to.
|
||||
func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := httpmw.UserParam(r)
|
||||
|
||||
organizations, err := api.Database.GetOrganizationsByUserID(r.Context(), user.ID)
|
||||
organizations, err := api.Database.GetOrganizationsByUserID(ctx, user.ID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
organizations = []database.Organization{}
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching user's organizations.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -820,7 +832,7 @@ func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) {
|
|||
// Only return orgs the user can read.
|
||||
organizations, err = AuthorizeFilter(api.HTTPAuth, r, rbac.ActionRead, organizations)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching organizations.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -832,18 +844,19 @@ func (api *API) organizationsByUser(rw http.ResponseWriter, r *http.Request) {
|
|||
publicOrganizations = append(publicOrganizations, convertOrganization(organization))
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, publicOrganizations)
|
||||
httpapi.Write(ctx, rw, http.StatusOK, publicOrganizations)
|
||||
}
|
||||
|
||||
func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
organizationName := chi.URLParam(r, "organizationname")
|
||||
organization, err := api.Database.GetOrganizationByName(r.Context(), organizationName)
|
||||
organization, err := api.Database.GetOrganizationByName(ctx, organizationName)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.ResourceNotFound(rw)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching organization.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -857,21 +870,22 @@ func (api *API) organizationByUserAndName(rw http.ResponseWriter, r *http.Reques
|
|||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertOrganization(organization))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertOrganization(organization))
|
||||
}
|
||||
|
||||
// Authenticates the user with an email and password.
|
||||
func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
var loginWithPassword codersdk.LoginWithPasswordRequest
|
||||
if !httpapi.Read(rw, r, &loginWithPassword) {
|
||||
if !httpapi.Read(ctx, rw, r, &loginWithPassword) {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := api.Database.GetUserByEmailOrUsername(r.Context(), database.GetUserByEmailOrUsernameParams{
|
||||
user, err := api.Database.GetUserByEmailOrUsername(ctx, database.GetUserByEmailOrUsernameParams{
|
||||
Email: loginWithPassword.Email,
|
||||
})
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error.",
|
||||
})
|
||||
return
|
||||
|
@ -880,7 +894,7 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
|
|||
// If the user doesn't exist, it will be a default struct.
|
||||
equal, err := userpassword.Compare(string(user.HashedPassword), loginWithPassword.Password)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error.",
|
||||
})
|
||||
return
|
||||
|
@ -888,14 +902,14 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
|
|||
if !equal {
|
||||
// This message is the same as above to remove ease in detecting whether
|
||||
// users are registered or not. Attackers still could with a timing attack.
|
||||
httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
|
||||
Message: "Incorrect email or password.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if user.LoginType != database.LoginTypePassword {
|
||||
httpapi.Write(rw, http.StatusForbidden, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
|
||||
Message: fmt.Sprintf("Incorrect login type, attempting to use %q but user is of login type %q", database.LoginTypePassword, user.LoginType),
|
||||
})
|
||||
return
|
||||
|
@ -903,18 +917,19 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// If the user logged into a suspended account, reject the login request.
|
||||
if user.Status != database.UserStatusActive {
|
||||
httpapi.Write(rw, http.StatusUnauthorized, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
|
||||
Message: "Your account is suspended. Contact an admin to reactivate your account.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
cookie, err := api.createAPIKey(r, createAPIKeyParams{
|
||||
UserID: user.ID,
|
||||
LoginType: database.LoginTypePassword,
|
||||
cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
|
||||
UserID: user.ID,
|
||||
LoginType: database.LoginTypePassword,
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to create API key.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -923,13 +938,14 @@ func (api *API) postLogin(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
api.setAuthCookie(rw, cookie)
|
||||
|
||||
httpapi.Write(rw, http.StatusCreated, codersdk.LoginWithPasswordResponse{
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.LoginWithPasswordResponse{
|
||||
SessionToken: cookie.Value,
|
||||
})
|
||||
}
|
||||
|
||||
// Creates a new session key, used for logging in via the CLI.
|
||||
func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
user := httpmw.UserParam(r)
|
||||
|
||||
if !api.Authorize(r, rbac.ActionCreate, rbac.ResourceAPIKey.WithOwner(user.ID.String())) {
|
||||
|
@ -938,16 +954,17 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
lifeTime := time.Hour * 24 * 7
|
||||
cookie, err := api.createAPIKey(r, createAPIKeyParams{
|
||||
UserID: user.ID,
|
||||
LoginType: database.LoginTypePassword,
|
||||
cookie, err := api.createAPIKey(ctx, createAPIKeyParams{
|
||||
UserID: user.ID,
|
||||
LoginType: database.LoginTypePassword,
|
||||
RemoteAddr: r.RemoteAddr,
|
||||
// All api generated keys will last 1 week. Browser login tokens have
|
||||
// a shorter life.
|
||||
ExpiresAt: database.Now().Add(lifeTime),
|
||||
LifetimeSeconds: int64(lifeTime.Seconds()),
|
||||
})
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Failed to create API key.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
|
@ -958,7 +975,7 @@ func (api *API) postAPIKey(rw http.ResponseWriter, r *http.Request) {
|
|||
// Setting the cookie will couple the browser sesion to the API
|
||||
// key we return here, meaning logging out of the website would
|
||||
// invalid your CLI key.
|
||||
httpapi.Write(rw, http.StatusCreated, codersdk.GenerateAPIKeyResponse{Key: cookie.Value})
|
||||
httpapi.Write(ctx, rw, http.StatusCreated, codersdk.GenerateAPIKeyResponse{Key: cookie.Value})
|
||||
}
|
||||
|
||||
func (api *API) apiKey(rw http.ResponseWriter, r *http.Request) {
|
||||
|
@ -979,18 +996,19 @@ func (api *API) apiKey(rw http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error fetching API key.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, convertAPIKey(key))
|
||||
httpapi.Write(ctx, rw, http.StatusOK, convertAPIKey(key))
|
||||
}
|
||||
|
||||
// Clear the user's session cookie.
|
||||
func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
// Get a blank token cookie.
|
||||
cookie := &http.Cookie{
|
||||
// MaxAge < 0 means to delete the cookie now.
|
||||
|
@ -1002,16 +1020,16 @@ func (api *API) postLogout(rw http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// Delete the session token from database.
|
||||
apiKey := httpmw.APIKey(r)
|
||||
err := api.Database.DeleteAPIKeyByID(r.Context(), apiKey.ID)
|
||||
err := api.Database.DeleteAPIKeyByID(ctx, apiKey.ID)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error deleting API key.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
httpapi.Write(rw, http.StatusOK, codersdk.Response{
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
|
||||
Message: "Logged out!",
|
||||
})
|
||||
}
|
||||
|
@ -1032,15 +1050,16 @@ func generateAPIKeyIDSecret() (id string, secret string, err error) {
|
|||
}
|
||||
|
||||
type createAPIKeyParams struct {
|
||||
UserID uuid.UUID
|
||||
LoginType database.LoginType
|
||||
UserID uuid.UUID
|
||||
RemoteAddr string
|
||||
LoginType database.LoginType
|
||||
|
||||
// Optional.
|
||||
ExpiresAt time.Time
|
||||
LifetimeSeconds int64
|
||||
}
|
||||
|
||||
func (api *API) createAPIKey(r *http.Request, params createAPIKeyParams) (*http.Cookie, error) {
|
||||
func (api *API) createAPIKey(ctx context.Context, params createAPIKeyParams) (*http.Cookie, error) {
|
||||
keyID, keySecret, err := generateAPIKeyIDSecret()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("generate API key: %w", err)
|
||||
|
@ -1056,13 +1075,13 @@ func (api *API) createAPIKey(r *http.Request, params createAPIKeyParams) (*http.
|
|||
}
|
||||
}
|
||||
|
||||
host, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
host, _, _ := net.SplitHostPort(params.RemoteAddr)
|
||||
ip := net.ParseIP(host)
|
||||
if ip == nil {
|
||||
ip = net.IPv4(0, 0, 0, 0)
|
||||
}
|
||||
bitlen := len(ip) * 8
|
||||
key, err := api.Database.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{
|
||||
key, err := api.Database.InsertAPIKey(ctx, database.InsertAPIKeyParams{
|
||||
ID: keyID,
|
||||
UserID: params.UserID,
|
||||
LifetimeSeconds: params.LifetimeSeconds,
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(),
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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, $*_),
|
||||
$*_,
|
||||
|
|
Loading…
Reference in New Issue