feat: Add APIs for querying workspaces (#61)

* Add SQL migration

* Add query functions for workspaces

* Add create routes

* Add tests for codersdk

* Add workspace parameter route

* Add workspace query

* Move workspace function

* Add querying for workspace history

* Fix query

* Fix syntax error

* Move workspace routes

* Fix version

* Add CLI tests

* Fix syntax error

* Remove error

* Fix history error

* Add new user test

* Fix test

* Lower target to 70%

* Improve comments

* Add comment
This commit is contained in:
Kyle Carberry
2022-01-25 13:52:58 -06:00
committed by GitHub
parent 139828d594
commit 5b01f615eb
27 changed files with 2511 additions and 81 deletions

View File

@ -57,9 +57,9 @@ func (c *Client) CreateProject(ctx context.Context, organization string, request
return project, json.NewDecoder(res.Body).Decode(&project)
}
// ProjectVersions lists history for a project.
func (c *Client) ProjectVersions(ctx context.Context, organization, project string) ([]coderd.ProjectVersion, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/%s/versions", organization, project), nil)
// ProjectHistory lists history for a project.
func (c *Client) ProjectHistory(ctx context.Context, organization, project string) ([]coderd.ProjectHistory, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/%s/history", organization, project), nil)
if err != nil {
return nil, err
}
@ -67,20 +67,20 @@ func (c *Client) ProjectVersions(ctx context.Context, organization, project stri
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var projectVersions []coderd.ProjectVersion
var projectVersions []coderd.ProjectHistory
return projectVersions, json.NewDecoder(res.Body).Decode(&projectVersions)
}
// CreateProjectVersion inserts a new version for the project.
func (c *Client) CreateProjectVersion(ctx context.Context, organization, project string, request coderd.CreateProjectVersionRequest) (coderd.ProjectVersion, error) {
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/projects/%s/%s/versions", organization, project), request)
// CreateProjectHistory inserts a new version for the project.
func (c *Client) CreateProjectHistory(ctx context.Context, organization, project string, request coderd.CreateProjectVersionRequest) (coderd.ProjectHistory, error) {
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/projects/%s/%s/history", organization, project), request)
if err != nil {
return coderd.ProjectVersion{}, err
return coderd.ProjectHistory{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.ProjectVersion{}, readBodyAsError(res)
return coderd.ProjectHistory{}, readBodyAsError(res)
}
var projectVersion coderd.ProjectVersion
var projectVersion coderd.ProjectHistory
return projectVersion, json.NewDecoder(res.Body).Decode(&projectVersion)
}

View File

@ -74,7 +74,7 @@ func TestProjects(t *testing.T) {
t.Run("UnauthenticatedVersions", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
_, err := server.Client.ProjectVersions(context.Background(), "org", "project")
_, err := server.Client.ProjectHistory(context.Background(), "org", "project")
require.Error(t, err)
})
@ -87,15 +87,14 @@ func TestProjects(t *testing.T) {
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
_, err = server.Client.ProjectVersions(context.Background(), user.Organization, project.Name)
_, err = server.Client.ProjectHistory(context.Background(), user.Organization, project.Name)
require.NoError(t, err)
})
t.Run("CreateVersionUnauthenticated", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
_, err := server.Client.CreateProjectVersion(context.Background(), "org", "project", coderd.CreateProjectVersionRequest{
Name: "hello",
_, err := server.Client.CreateProjectHistory(context.Background(), "org", "project", coderd.CreateProjectVersionRequest{
StorageMethod: database.ProjectStorageMethodInlineArchive,
StorageSource: []byte{},
})
@ -120,8 +119,7 @@ func TestProjects(t *testing.T) {
require.NoError(t, err)
_, err = writer.Write(make([]byte, 1<<10))
require.NoError(t, err)
_, err = server.Client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
Name: "hello",
_, err = server.Client.CreateProjectHistory(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
StorageMethod: database.ProjectStorageMethodInlineArchive,
StorageSource: buffer.Bytes(),
})

View File

@ -13,6 +13,20 @@ import (
// This initial user has superadmin privileges. If >0 users exist, this request
// will fail.
func (c *Client) CreateInitialUser(ctx context.Context, req coderd.CreateInitialUserRequest) (coderd.User, error) {
res, err := c.request(ctx, http.MethodPost, "/api/v2/user", req)
if err != nil {
return coderd.User{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.User{}, readBodyAsError(res)
}
var user coderd.User
return user, json.NewDecoder(res.Body).Decode(&user)
}
// CreateUser creates a new user.
func (c *Client) CreateUser(ctx context.Context, req coderd.CreateUserRequest) (coderd.User, error) {
res, err := c.request(ctx, http.MethodPost, "/api/v2/users", req)
if err != nil {
return coderd.User{}, err

View File

@ -55,4 +55,16 @@ func TestUsers(t *testing.T) {
err := server.Client.Logout(context.Background())
require.NoError(t, err)
})
t.Run("CreateMultiple", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
_ = server.RandomInitialUser(t)
_, err := server.Client.CreateUser(context.Background(), coderd.CreateUserRequest{
Email: "wow@ok.io",
Username: "example",
Password: "tomato",
})
require.NoError(t, err)
})
}

129
codersdk/workspaces.go Normal file
View File

@ -0,0 +1,129 @@
package codersdk
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/coder/coder/coderd"
)
// Workspaces returns all workspaces the authenticated session has access to.
// If owner is specified, all workspaces for an organization will be returned.
// If owner is empty, all workspaces the caller has access to will be returned.
func (c *Client) WorkspacesByUser(ctx context.Context, user string) ([]coderd.Workspace, error) {
route := "/api/v2/workspaces"
if user != "" {
route += fmt.Sprintf("/%s", user)
}
res, err := c.request(ctx, http.MethodGet, route, nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var workspaces []coderd.Workspace
return workspaces, json.NewDecoder(res.Body).Decode(&workspaces)
}
// WorkspacesByProject lists all workspaces for a specific project.
func (c *Client) WorkspacesByProject(ctx context.Context, organization, project string) ([]coderd.Workspace, error) {
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/projects/%s/%s/workspaces", organization, project), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var workspaces []coderd.Workspace
return workspaces, json.NewDecoder(res.Body).Decode(&workspaces)
}
// Workspace returns a single workspace by owner and name.
func (c *Client) Workspace(ctx context.Context, owner, name string) (coderd.Workspace, error) {
if owner == "" {
owner = "me"
}
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/%s", owner, name), nil)
if err != nil {
return coderd.Workspace{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.Workspace{}, readBodyAsError(res)
}
var workspace coderd.Workspace
return workspace, json.NewDecoder(res.Body).Decode(&workspace)
}
// WorkspaceHistory returns historical data for workspace builds.
func (c *Client) WorkspaceHistory(ctx context.Context, owner, workspace string) ([]coderd.WorkspaceHistory, error) {
if owner == "" {
owner = "me"
}
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/%s/history", owner, workspace), nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return nil, readBodyAsError(res)
}
var workspaceHistory []coderd.WorkspaceHistory
return workspaceHistory, json.NewDecoder(res.Body).Decode(&workspaceHistory)
}
// LatestWorkspaceHistory returns the newest build for a workspace.
func (c *Client) LatestWorkspaceHistory(ctx context.Context, owner, workspace string) (coderd.WorkspaceHistory, error) {
if owner == "" {
owner = "me"
}
res, err := c.request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/%s/history/latest", owner, workspace), nil)
if err != nil {
return coderd.WorkspaceHistory{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
return coderd.WorkspaceHistory{}, readBodyAsError(res)
}
var workspaceHistory coderd.WorkspaceHistory
return workspaceHistory, json.NewDecoder(res.Body).Decode(&workspaceHistory)
}
// CreateWorkspace creates a new workspace for the project specified.
func (c *Client) CreateWorkspace(ctx context.Context, user string, request coderd.CreateWorkspaceRequest) (coderd.Workspace, error) {
if user == "" {
user = "me"
}
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/workspaces/%s", user), request)
if err != nil {
return coderd.Workspace{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.Workspace{}, readBodyAsError(res)
}
var workspace coderd.Workspace
return workspace, json.NewDecoder(res.Body).Decode(&workspace)
}
// CreateWorkspaceHistory queues a new build to occur for a workspace.
func (c *Client) CreateWorkspaceHistory(ctx context.Context, owner, workspace string, request coderd.CreateWorkspaceHistoryRequest) (coderd.WorkspaceHistory, error) {
if owner == "" {
owner = "me"
}
res, err := c.request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/workspaces/%s/%s/history", owner, workspace), request)
if err != nil {
return coderd.WorkspaceHistory{}, err
}
defer res.Body.Close()
if res.StatusCode != http.StatusCreated {
return coderd.WorkspaceHistory{}, readBodyAsError(res)
}
var workspaceHistory coderd.WorkspaceHistory
return workspaceHistory, json.NewDecoder(res.Body).Decode(&workspaceHistory)
}

169
codersdk/workspaces_test.go Normal file
View File

@ -0,0 +1,169 @@
package codersdk_test
import (
"context"
"testing"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/database"
)
func TestWorkspaces(t *testing.T) {
t.Parallel()
t.Run("ListError", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
_, err := server.Client.WorkspacesByUser(context.Background(), "")
require.Error(t, err)
})
t.Run("ListNoOwner", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
_, err := server.Client.WorkspacesByUser(context.Background(), "")
require.Error(t, err)
})
t.Run("ListByUser", 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: "tomato",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
_, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
Name: "wooow",
ProjectID: project.ID,
})
require.NoError(t, err)
_, err = server.Client.WorkspacesByUser(context.Background(), "me")
require.NoError(t, err)
})
t.Run("ListByProject", 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: "tomato",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
_, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
Name: "wooow",
ProjectID: project.ID,
})
require.NoError(t, err)
_, err = server.Client.WorkspacesByProject(context.Background(), user.Organization, project.Name)
require.NoError(t, err)
})
t.Run("ListByProjectError", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
_, err := server.Client.WorkspacesByProject(context.Background(), "", "")
require.Error(t, err)
})
t.Run("CreateError", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
_, err := server.Client.CreateWorkspace(context.Background(), "no", coderd.CreateWorkspaceRequest{})
require.Error(t, err)
})
t.Run("Single", 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: "tomato",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
Name: "wooow",
ProjectID: project.ID,
})
require.NoError(t, err)
_, err = server.Client.Workspace(context.Background(), "", workspace.Name)
require.NoError(t, err)
})
t.Run("SingleError", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
_, err := server.Client.Workspace(context.Background(), "", "blob")
require.Error(t, err)
})
t.Run("History", 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: "tomato",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
Name: "wooow",
ProjectID: project.ID,
})
require.NoError(t, err)
_, err = server.Client.WorkspaceHistory(context.Background(), "", workspace.Name)
require.NoError(t, err)
})
t.Run("HistoryError", func(t *testing.T) {
t.Parallel()
server := coderdtest.New(t)
_, err := server.Client.WorkspaceHistory(context.Background(), "", "blob")
require.Error(t, err)
})
t.Run("LatestHistory", 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: "tomato",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
Name: "wooow",
ProjectID: project.ID,
})
require.NoError(t, err)
_, err = server.Client.LatestWorkspaceHistory(context.Background(), "", workspace.Name)
require.Error(t, err)
})
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: "tomato",
Provisioner: database.ProvisionerTypeTerraform,
})
require.NoError(t, err)
workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
Name: "wooow",
ProjectID: project.ID,
})
require.NoError(t, err)
_, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectHistoryID: uuid.New(),
Transition: database.WorkspaceTransitionCreate,
})
require.Error(t, err)
})
}