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:
Kyle Carberry
2022-02-07 15:32:37 -06:00
committed by GitHub
parent bde732f2ef
commit ed705f6af2
26 changed files with 892 additions and 935 deletions
+64
View File
@@ -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))
})
}
}
+109
View File
@@ -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)
})
}