mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
refactor: Generalize log ownership to allow for scratch jobs (#182)
* refactor: Generalize log ownership to allow for scratch jobs Importing may fail when creating a project. We don't want to lose this output, but we don't want to allow users to create a failing project. This generalizes logs to soon enable one-off situations where a user can upload their archive, create a project, and watch the output parse to completion. * Improve file table schema by using hash * Fix racey test by allowing logs before * Add debug logging for PostgreSQL insert
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package httpmw
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/coder/coder/database"
|
||||
"github.com/coder/coder/httpapi"
|
||||
)
|
||||
|
||||
type provisionerJobParamContextKey struct{}
|
||||
|
||||
// ProvisionerJobParam returns the project from the ExtractProjectParam handler.
|
||||
func ProvisionerJobParam(r *http.Request) database.ProvisionerJob {
|
||||
provisionerJob, ok := r.Context().Value(provisionerJobParamContextKey{}).(database.ProvisionerJob)
|
||||
if !ok {
|
||||
panic("developer error: provisioner job param middleware not provided")
|
||||
}
|
||||
return provisionerJob
|
||||
}
|
||||
|
||||
// ExtractProvisionerJobParam grabs a provisioner job from the "provisionerjob" URL parameter.
|
||||
func ExtractProvisionerJobParam(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) {
|
||||
jobID := chi.URLParam(r, "provisionerjob")
|
||||
if jobID == "" {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Message: "provisioner job must be provided",
|
||||
})
|
||||
return
|
||||
}
|
||||
jobUUID, err := uuid.Parse(jobID)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Message: "job id must be a uuid",
|
||||
})
|
||||
return
|
||||
}
|
||||
job, err := db.GetProvisionerJobByID(r.Context(), jobUUID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
|
||||
Message: "job doesn't exist with that id",
|
||||
})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get provisioner job: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), provisionerJobParamContextKey{}, job)
|
||||
next.ServeHTTP(rw, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package httpmw_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/database"
|
||||
"github.com/coder/coder/database/databasefake"
|
||||
"github.com/coder/coder/httpmw"
|
||||
)
|
||||
|
||||
func TestProvisionerJobParam(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
setup := func(db database.Store) (*http.Request, database.ProvisionerJob) {
|
||||
r := httptest.NewRequest("GET", "/", nil)
|
||||
provisionerJob, err := db.InsertProvisionerJob(context.Background(), database.InsertProvisionerJobParams{
|
||||
ID: uuid.New(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := chi.NewRouteContext()
|
||||
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, ctx))
|
||||
return r, provisionerJob
|
||||
}
|
||||
|
||||
t.Run("None", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := databasefake.New()
|
||||
rtr := chi.NewRouter()
|
||||
rtr.Use(
|
||||
httpmw.ExtractProvisionerJobParam(db),
|
||||
)
|
||||
rtr.Get("/", nil)
|
||||
r, _ := setup(db)
|
||||
rw := httptest.NewRecorder()
|
||||
rtr.ServeHTTP(rw, r)
|
||||
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("BadUUID", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := databasefake.New()
|
||||
rtr := chi.NewRouter()
|
||||
rtr.Use(
|
||||
httpmw.ExtractProvisionerJobParam(db),
|
||||
)
|
||||
rtr.Get("/", nil)
|
||||
|
||||
r, _ := setup(db)
|
||||
chi.RouteContext(r.Context()).URLParams.Add("provisionerjob", "nothin")
|
||||
rw := httptest.NewRecorder()
|
||||
rtr.ServeHTTP(rw, r)
|
||||
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusBadRequest, res.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("NotFound", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := databasefake.New()
|
||||
rtr := chi.NewRouter()
|
||||
rtr.Use(
|
||||
httpmw.ExtractProvisionerJobParam(db),
|
||||
)
|
||||
rtr.Get("/", nil)
|
||||
|
||||
r, _ := setup(db)
|
||||
chi.RouteContext(r.Context()).URLParams.Add("provisionerjob", uuid.NewString())
|
||||
rw := httptest.NewRecorder()
|
||||
rtr.ServeHTTP(rw, r)
|
||||
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusNotFound, res.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("ProvisionerJob", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db := databasefake.New()
|
||||
rtr := chi.NewRouter()
|
||||
rtr.Use(
|
||||
httpmw.ExtractProvisionerJobParam(db),
|
||||
)
|
||||
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
|
||||
_ = httpmw.ProvisionerJobParam(r)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
r, job := setup(db)
|
||||
chi.RouteContext(r.Context()).URLParams.Add("provisionerjob", job.ID.String())
|
||||
rw := httptest.NewRecorder()
|
||||
rtr.ServeHTTP(rw, r)
|
||||
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user