-
Notifications
You must be signed in to change notification settings - Fork 582
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: Move all HTTP routes to top-level struct (#130)
* feat: Add history middleware parameters These will be used for streaming logs, checking status, and other operations related to workspace and project history. * refactor: Move all HTTP routes to top-level struct Nesting all structs behind their respective structures is leaky, and promotes naming conflicts between handlers. Our HTTP routes cannot have conflicts, so neither should function naming.
- Loading branch information
Showing
14 changed files
with
623 additions
and
552 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
package coderd | ||
|
||
import ( | ||
"archive/tar" | ||
"bytes" | ||
"database/sql" | ||
"errors" | ||
"fmt" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/go-chi/render" | ||
"github.com/google/uuid" | ||
"github.com/moby/moby/pkg/namesgenerator" | ||
|
||
"github.com/coder/coder/database" | ||
"github.com/coder/coder/httpapi" | ||
"github.com/coder/coder/httpmw" | ||
) | ||
|
||
// ProjectHistory is the JSON representation of Coder project version history. | ||
type ProjectHistory struct { | ||
ID uuid.UUID `json:"id"` | ||
ProjectID uuid.UUID `json:"project_id"` | ||
CreatedAt time.Time `json:"created_at"` | ||
UpdatedAt time.Time `json:"updated_at"` | ||
Name string `json:"name"` | ||
StorageMethod database.ProjectStorageMethod `json:"storage_method"` | ||
} | ||
|
||
// CreateProjectHistoryRequest enables callers to create a new Project Version. | ||
type CreateProjectHistoryRequest struct { | ||
StorageMethod database.ProjectStorageMethod `json:"storage_method" validate:"oneof=inline-archive,required"` | ||
StorageSource []byte `json:"storage_source" validate:"max=1048576,required"` | ||
} | ||
|
||
// Lists history for a single project. | ||
func (api *api) projectHistoryByOrganization(rw http.ResponseWriter, r *http.Request) { | ||
project := httpmw.ProjectParam(r) | ||
|
||
history, err := api.Database.GetProjectHistoryByProjectID(r.Context(), project.ID) | ||
if errors.Is(err, sql.ErrNoRows) { | ||
err = nil | ||
} | ||
if err != nil { | ||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ | ||
Message: fmt.Sprintf("get project history: %s", err), | ||
}) | ||
return | ||
} | ||
apiHistory := make([]ProjectHistory, 0) | ||
for _, version := range history { | ||
apiHistory = append(apiHistory, convertProjectHistory(version)) | ||
} | ||
render.Status(r, http.StatusOK) | ||
render.JSON(rw, r, apiHistory) | ||
} | ||
|
||
// Creates a new version of the project. An import job is queued to parse | ||
// the storage method provided. Once completed, the import job will specify | ||
// the version as latest. | ||
func (api *api) postProjectHistoryByOrganization(rw http.ResponseWriter, r *http.Request) { | ||
var createProjectVersion CreateProjectHistoryRequest | ||
if !httpapi.Read(rw, r, &createProjectVersion) { | ||
return | ||
} | ||
|
||
switch createProjectVersion.StorageMethod { | ||
case database.ProjectStorageMethodInlineArchive: | ||
tarReader := tar.NewReader(bytes.NewReader(createProjectVersion.StorageSource)) | ||
_, err := tarReader.Next() | ||
if err != nil { | ||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ | ||
Message: "the archive must be a tar", | ||
}) | ||
return | ||
} | ||
default: | ||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{ | ||
Message: fmt.Sprintf("unsupported storage method %s", createProjectVersion.StorageMethod), | ||
}) | ||
return | ||
} | ||
|
||
project := httpmw.ProjectParam(r) | ||
history, err := api.Database.InsertProjectHistory(r.Context(), database.InsertProjectHistoryParams{ | ||
ID: uuid.New(), | ||
ProjectID: project.ID, | ||
CreatedAt: database.Now(), | ||
UpdatedAt: database.Now(), | ||
Name: namesgenerator.GetRandomName(1), | ||
StorageMethod: createProjectVersion.StorageMethod, | ||
StorageSource: createProjectVersion.StorageSource, | ||
// TODO: Make this do something! | ||
ImportJobID: uuid.New(), | ||
}) | ||
if err != nil { | ||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{ | ||
Message: fmt.Sprintf("insert project history: %s", err), | ||
}) | ||
return | ||
} | ||
|
||
// TODO: A job to process the new version should occur here. | ||
|
||
render.Status(r, http.StatusCreated) | ||
render.JSON(rw, r, convertProjectHistory(history)) | ||
} | ||
|
||
func convertProjectHistory(history database.ProjectHistory) ProjectHistory { | ||
return ProjectHistory{ | ||
ID: history.ID, | ||
ProjectID: history.ProjectID, | ||
CreatedAt: history.CreatedAt, | ||
UpdatedAt: history.UpdatedAt, | ||
Name: history.Name, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package coderd_test | ||
|
||
import ( | ||
"archive/tar" | ||
"bytes" | ||
"context" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/coder/coder/coderd" | ||
"github.com/coder/coder/coderd/coderdtest" | ||
"github.com/coder/coder/database" | ||
) | ||
|
||
func TestProjectHistory(t *testing.T) { | ||
t.Parallel() | ||
|
||
t.Run("NoHistory", func(t *testing.T) { | ||
t.Parallel() | ||
server := coderdtest.New(t) | ||
user := server.RandomInitialUser(t) | ||
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ | ||
Name: "someproject", | ||
Provisioner: database.ProvisionerTypeTerraform, | ||
}) | ||
require.NoError(t, err) | ||
versions, err := server.Client.ProjectHistory(context.Background(), user.Organization, project.Name) | ||
require.NoError(t, err) | ||
require.Len(t, versions, 0) | ||
}) | ||
|
||
t.Run("CreateHistory", func(t *testing.T) { | ||
t.Parallel() | ||
server := coderdtest.New(t) | ||
user := server.RandomInitialUser(t) | ||
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ | ||
Name: "someproject", | ||
Provisioner: database.ProvisionerTypeTerraform, | ||
}) | ||
require.NoError(t, err) | ||
var buffer bytes.Buffer | ||
writer := tar.NewWriter(&buffer) | ||
err = writer.WriteHeader(&tar.Header{ | ||
Name: "file", | ||
Size: 1 << 10, | ||
}) | ||
require.NoError(t, err) | ||
_, err = writer.Write(make([]byte, 1<<10)) | ||
require.NoError(t, err) | ||
_, err = server.Client.CreateProjectHistory(context.Background(), user.Organization, project.Name, coderd.CreateProjectHistoryRequest{ | ||
StorageMethod: database.ProjectStorageMethodInlineArchive, | ||
StorageSource: buffer.Bytes(), | ||
}) | ||
require.NoError(t, err) | ||
versions, err := server.Client.ProjectHistory(context.Background(), user.Organization, project.Name) | ||
require.NoError(t, err) | ||
require.Len(t, versions, 1) | ||
}) | ||
|
||
t.Run("CreateHistoryArchiveTooBig", func(t *testing.T) { | ||
t.Parallel() | ||
server := coderdtest.New(t) | ||
user := server.RandomInitialUser(t) | ||
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ | ||
Name: "someproject", | ||
Provisioner: database.ProvisionerTypeTerraform, | ||
}) | ||
require.NoError(t, err) | ||
var buffer bytes.Buffer | ||
writer := tar.NewWriter(&buffer) | ||
err = writer.WriteHeader(&tar.Header{ | ||
Name: "file", | ||
Size: 1 << 21, | ||
}) | ||
require.NoError(t, err) | ||
_, err = writer.Write(make([]byte, 1<<21)) | ||
require.NoError(t, err) | ||
_, err = server.Client.CreateProjectHistory(context.Background(), user.Organization, project.Name, coderd.CreateProjectHistoryRequest{ | ||
StorageMethod: database.ProjectStorageMethodInlineArchive, | ||
StorageSource: buffer.Bytes(), | ||
}) | ||
require.Error(t, err) | ||
}) | ||
|
||
t.Run("CreateHistoryInvalidArchive", func(t *testing.T) { | ||
t.Parallel() | ||
server := coderdtest.New(t) | ||
user := server.RandomInitialUser(t) | ||
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{ | ||
Name: "someproject", | ||
Provisioner: database.ProvisionerTypeTerraform, | ||
}) | ||
require.NoError(t, err) | ||
_, err = server.Client.CreateProjectHistory(context.Background(), user.Organization, project.Name, coderd.CreateProjectHistoryRequest{ | ||
StorageMethod: database.ProjectStorageMethodInlineArchive, | ||
StorageSource: []byte{}, | ||
}) | ||
require.Error(t, err) | ||
}) | ||
} |
Oops, something went wrong.