mirror of https://github.com/coder/coder.git
chore: Add test helpers to improve coverage (#166)
* chore: Rename ProjectHistory to ProjectVersion Version more accurately represents version storage. This forks from the WorkspaceHistory name, but I think it's easier to understand Workspace history. * Rename files * Standardize tests a bit more * Remove Server struct from coderdtest * Improve test coverage for workspace history * Fix linting errors * Fix coderd test leak * Fix coderd test leak * Improve workspace history logs * Standardize test structure for codersdk * Fix linting errors * Fix WebSocket compression * Update coderd/workspaces.go Co-authored-by: Bryan <bryan@coder.com> * Add test for listing project parameters * Cache npm dependencies with setup node * Remove windows npm cache key Co-authored-by: Bryan <bryan@coder.com>
This commit is contained in:
parent
f19770b2c6
commit
1796dc6c2f
|
@ -165,12 +165,13 @@ jobs:
|
|||
-covermode=atomic -coverprofile="gotests.coverage" -timeout=3m
|
||||
-count=1 -race -parallel=2
|
||||
|
||||
- uses: actions/setup-node@v2
|
||||
- name: Setup Node for DataDog CLI
|
||||
uses: actions/setup-node@v2
|
||||
if: always() && github.actor != 'dependabot[bot]'
|
||||
with:
|
||||
node-version: "14"
|
||||
|
||||
- name: Cache DataDog CI
|
||||
- name: Cache DataDog CLI
|
||||
if: always() && github.actor != 'dependabot[bot]'
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
|
|
|
@ -100,6 +100,10 @@ linters-settings:
|
|||
# - whyNoLint
|
||||
# - wrapperFunc
|
||||
# - yodaStyleExpr
|
||||
settings:
|
||||
ruleguard:
|
||||
failOn: all
|
||||
rules: rules.go
|
||||
|
||||
goimports:
|
||||
local-prefixes: coder.com,cdr.dev,go.coder.com,github.com/cdr,github.com/coder
|
||||
|
|
|
@ -86,7 +86,6 @@ func New(options *Options) http.Handler {
|
|||
r.Get("/", api.workspaces)
|
||||
r.Route("/{user}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractUserParam(options.Database))
|
||||
r.Get("/", api.workspaces)
|
||||
r.Post("/", api.postWorkspaceByUser)
|
||||
r.Route("/{workspace}", func(r chi.Router) {
|
||||
r.Use(httpmw.ExtractWorkspaceParam(options.Database))
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package coderd_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"go.uber.org/goleak"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
goleak.VerifyTestMain(m)
|
||||
}
|
|
@ -7,16 +7,18 @@ import (
|
|||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/moby/moby/pkg/namesgenerator"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
"github.com/coder/coder/coderd"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/cryptorand"
|
||||
"github.com/coder/coder/database"
|
||||
"github.com/coder/coder/database/databasefake"
|
||||
"github.com/coder/coder/database/postgres"
|
||||
|
@ -26,79 +28,9 @@ import (
|
|||
"github.com/coder/coder/provisionersdk/proto"
|
||||
)
|
||||
|
||||
// Server represents a test instance of coderd.
|
||||
// The database is intentionally omitted from
|
||||
// this struct to promote data being exposed via
|
||||
// the API.
|
||||
type Server struct {
|
||||
Client *codersdk.Client
|
||||
URL *url.URL
|
||||
}
|
||||
|
||||
// RandomInitialUser generates a random initial user and authenticates
|
||||
// it with the client on the Server struct.
|
||||
func (s *Server) RandomInitialUser(t *testing.T) coderd.CreateInitialUserRequest {
|
||||
username, err := cryptorand.String(12)
|
||||
require.NoError(t, err)
|
||||
password, err := cryptorand.String(12)
|
||||
require.NoError(t, err)
|
||||
organization, err := cryptorand.String(12)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := coderd.CreateInitialUserRequest{
|
||||
Email: "testuser@coder.com",
|
||||
Username: username,
|
||||
Password: password,
|
||||
Organization: organization,
|
||||
}
|
||||
_, err = s.Client.CreateInitialUser(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
|
||||
login, err := s.Client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: "testuser@coder.com",
|
||||
Password: password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = s.Client.SetSessionToken(login.SessionToken)
|
||||
require.NoError(t, err)
|
||||
return req
|
||||
}
|
||||
|
||||
// AddProvisionerd launches a new provisionerd instance with the
|
||||
// test provisioner registered.
|
||||
func (s *Server) AddProvisionerd(t *testing.T) io.Closer {
|
||||
echoClient, echoServer := provisionersdk.TransportPipe()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
t.Cleanup(func() {
|
||||
_ = echoClient.Close()
|
||||
_ = echoServer.Close()
|
||||
cancelFunc()
|
||||
})
|
||||
go func() {
|
||||
err := echo.Serve(ctx, &provisionersdk.ServeOptions{
|
||||
Listener: echoServer,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
closer := provisionerd.New(s.Client.ProvisionerDaemonClient, &provisionerd.Options{
|
||||
Logger: slogtest.Make(t, nil).Named("provisionerd").Leveled(slog.LevelDebug),
|
||||
PollInterval: 50 * time.Millisecond,
|
||||
UpdateInterval: 50 * time.Millisecond,
|
||||
Provisioners: provisionerd.Provisioners{
|
||||
string(database.ProvisionerTypeEcho): proto.NewDRPCProvisionerClient(provisionersdk.Conn(echoClient)),
|
||||
},
|
||||
WorkDirectory: t.TempDir(),
|
||||
})
|
||||
t.Cleanup(func() {
|
||||
_ = closer.Close()
|
||||
})
|
||||
return closer
|
||||
}
|
||||
|
||||
// New constructs a new coderd test instance. This returned Server
|
||||
// should contain no side-effects.
|
||||
func New(t *testing.T) Server {
|
||||
func New(t *testing.T) *codersdk.Client {
|
||||
// This can be hotswapped for a live database instance.
|
||||
db := databasefake.New()
|
||||
pubsub := database.NewPubsubInMemory()
|
||||
|
@ -132,8 +64,123 @@ func New(t *testing.T) Server {
|
|||
require.NoError(t, err)
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
return Server{
|
||||
Client: codersdk.New(serverURL),
|
||||
URL: serverURL,
|
||||
}
|
||||
return codersdk.New(serverURL)
|
||||
}
|
||||
|
||||
// NewProvisionerDaemon launches a provisionerd instance configured to work
|
||||
// well with coderd testing. It registers the "echo" provisioner for
|
||||
// quick testing.
|
||||
func NewProvisionerDaemon(t *testing.T, client *codersdk.Client) io.Closer {
|
||||
echoClient, echoServer := provisionersdk.TransportPipe()
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
t.Cleanup(func() {
|
||||
_ = echoClient.Close()
|
||||
_ = echoServer.Close()
|
||||
cancelFunc()
|
||||
})
|
||||
go func() {
|
||||
err := echo.Serve(ctx, &provisionersdk.ServeOptions{
|
||||
Listener: echoServer,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
closer := provisionerd.New(client.ProvisionerDaemonClient, &provisionerd.Options{
|
||||
Logger: slogtest.Make(t, nil).Named("provisionerd").Leveled(slog.LevelDebug),
|
||||
PollInterval: 50 * time.Millisecond,
|
||||
UpdateInterval: 50 * time.Millisecond,
|
||||
Provisioners: provisionerd.Provisioners{
|
||||
string(database.ProvisionerTypeEcho): proto.NewDRPCProvisionerClient(provisionersdk.Conn(echoClient)),
|
||||
},
|
||||
WorkDirectory: t.TempDir(),
|
||||
})
|
||||
t.Cleanup(func() {
|
||||
_ = closer.Close()
|
||||
})
|
||||
return closer
|
||||
}
|
||||
|
||||
// CreateInitialUser creates a user with preset credentials and authenticates
|
||||
// with the passed in codersdk client.
|
||||
func CreateInitialUser(t *testing.T, client *codersdk.Client) coderd.CreateInitialUserRequest {
|
||||
req := coderd.CreateInitialUserRequest{
|
||||
Email: "testuser@coder.com",
|
||||
Username: "testuser",
|
||||
Password: "testpass",
|
||||
Organization: "testorg",
|
||||
}
|
||||
_, err := client.CreateInitialUser(context.Background(), req)
|
||||
require.NoError(t, err)
|
||||
|
||||
login, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: req.Email,
|
||||
Password: req.Password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = client.SetSessionToken(login.SessionToken)
|
||||
require.NoError(t, err)
|
||||
return req
|
||||
}
|
||||
|
||||
// CreateProject creates a project with the "echo" provisioner for
|
||||
// compatibility with testing. The name assigned is randomly generated.
|
||||
func CreateProject(t *testing.T, client *codersdk.Client, organization string) coderd.Project {
|
||||
project, err := client.CreateProject(context.Background(), organization, coderd.CreateProjectRequest{
|
||||
Name: randomUsername(),
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return project
|
||||
}
|
||||
|
||||
// CreateProjectVersion creates a project version for the "echo" provisioner
|
||||
// for compatibility with testing.
|
||||
func CreateProjectVersion(t *testing.T, client *codersdk.Client, organization, project string, responses *echo.Responses) coderd.ProjectVersion {
|
||||
data, err := echo.Tar(responses)
|
||||
require.NoError(t, err)
|
||||
version, err := client.CreateProjectVersion(context.Background(), organization, project, coderd.CreateProjectVersionRequest{
|
||||
StorageMethod: database.ProjectStorageMethodInlineArchive,
|
||||
StorageSource: data,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return version
|
||||
}
|
||||
|
||||
// AwaitProjectVersionImported awaits for the project import job to reach completed status.
|
||||
func AwaitProjectVersionImported(t *testing.T, client *codersdk.Client, organization, project, version string) coderd.ProjectVersion {
|
||||
var projectVersion coderd.ProjectVersion
|
||||
require.Eventually(t, func() bool {
|
||||
var err error
|
||||
projectVersion, err = client.ProjectVersion(context.Background(), organization, project, version)
|
||||
require.NoError(t, err)
|
||||
return projectVersion.Import.Status.Completed()
|
||||
}, 3*time.Second, 25*time.Millisecond)
|
||||
return projectVersion
|
||||
}
|
||||
|
||||
// CreateWorkspace creates a workspace for the user and project provided.
|
||||
// A random name is generated for it.
|
||||
func CreateWorkspace(t *testing.T, client *codersdk.Client, user string, projectID uuid.UUID) coderd.Workspace {
|
||||
workspace, err := client.CreateWorkspace(context.Background(), user, coderd.CreateWorkspaceRequest{
|
||||
ProjectID: projectID,
|
||||
Name: randomUsername(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return workspace
|
||||
}
|
||||
|
||||
// AwaitWorkspaceHistoryProvisioned awaits for the workspace provision job to reach completed status.
|
||||
func AwaitWorkspaceHistoryProvisioned(t *testing.T, client *codersdk.Client, user, workspace, history string) coderd.WorkspaceHistory {
|
||||
var workspaceHistory coderd.WorkspaceHistory
|
||||
require.Eventually(t, func() bool {
|
||||
var err error
|
||||
workspaceHistory, err = client.WorkspaceHistory(context.Background(), user, workspace, history)
|
||||
require.NoError(t, err)
|
||||
return workspaceHistory.Provision.Status.Completed()
|
||||
}, 3*time.Second, 25*time.Millisecond)
|
||||
return workspaceHistory
|
||||
}
|
||||
|
||||
func randomUsername() string {
|
||||
return strings.ReplaceAll(namesgenerator.GetRandomName(0), "_", "-")
|
||||
}
|
||||
|
|
|
@ -1,11 +1,16 @@
|
|||
package coderdtest_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/goleak"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/database"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
|
@ -14,7 +19,18 @@ func TestMain(m *testing.M) {
|
|||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(t)
|
||||
_ = server.AddProvisionerd(t)
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
closer := coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
history, err := client.CreateWorkspaceHistory(context.Background(), "me", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "me", workspace.Name, history.Name)
|
||||
closer.Close()
|
||||
}
|
||||
|
|
|
@ -49,6 +49,9 @@ func (api *api) projects(rw http.ResponseWriter, r *http.Request) {
|
|||
})
|
||||
return
|
||||
}
|
||||
if projects == nil {
|
||||
projects = []database.Project{}
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(rw, r, projects)
|
||||
}
|
||||
|
@ -66,6 +69,9 @@ func (api *api) projectsByOrganization(rw http.ResponseWriter, r *http.Request)
|
|||
})
|
||||
return
|
||||
}
|
||||
if projects == nil {
|
||||
projects = []database.Project{}
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(rw, r, projects)
|
||||
}
|
||||
|
@ -124,32 +130,6 @@ func (*api) projectByOrganization(rw http.ResponseWriter, r *http.Request) {
|
|||
render.JSON(rw, r, project)
|
||||
}
|
||||
|
||||
// Returns all workspaces for a specific project.
|
||||
func (api *api) workspacesByProject(rw http.ResponseWriter, r *http.Request) {
|
||||
apiKey := httpmw.APIKey(r)
|
||||
project := httpmw.ProjectParam(r)
|
||||
workspaces, err := api.Database.GetWorkspacesByProjectAndUserID(r.Context(), database.GetWorkspacesByProjectAndUserIDParams{
|
||||
OwnerID: apiKey.UserID,
|
||||
ProjectID: project.ID,
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get workspaces: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
apiWorkspaces := make([]Workspace, 0, len(workspaces))
|
||||
for _, workspace := range workspaces {
|
||||
apiWorkspaces = append(apiWorkspaces, convertWorkspace(workspace))
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(rw, r, apiWorkspaces)
|
||||
}
|
||||
|
||||
// Creates parameters for a project.
|
||||
// This should validate the calling user has permissions!
|
||||
func (api *api) postParametersByProject(rw http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -2,124 +2,109 @@ package coderd_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/database"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
)
|
||||
|
||||
func TestProjects(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "someproject",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("AlreadyExists", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "someproject",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "someproject",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("ListEmpty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(t)
|
||||
projects, err := server.Client.Projects(context.Background(), "")
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
projects, err := client.Projects(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, projects)
|
||||
require.Len(t, projects, 0)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "someproject",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Ensure global query works.
|
||||
projects, err := server.Client.Projects(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, projects, 1)
|
||||
|
||||
// Ensure specified query works.
|
||||
projects, err = server.Client.Projects(context.Background(), user.Organization)
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.CreateProject(t, client, user.Organization)
|
||||
projects, err := client.Projects(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, projects, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProjectsByOrganization(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("ListEmpty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
|
||||
projects, err := server.Client.Projects(context.Background(), user.Organization)
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
projects, err := client.Projects(context.Background(), user.Organization)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, projects)
|
||||
require.Len(t, projects, 0)
|
||||
})
|
||||
|
||||
t.Run("Single", func(t *testing.T) {
|
||||
t.Run("List", 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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.Project(context.Background(), user.Organization, project.Name)
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.CreateProject(t, client, user.Organization)
|
||||
projects, err := client.Projects(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, projects, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostProjectsByOrganization(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.CreateProject(t, client, user.Organization)
|
||||
})
|
||||
|
||||
t.Run("Parameters", func(t *testing.T) {
|
||||
t.Run("AlreadyExists", 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",
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: project.Name,
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.ProjectParameters(context.Background(), user.Organization, project.Name)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
||||
})
|
||||
}
|
||||
|
||||
func TestProjectByOrganization(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_, err := client.Project(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("CreateParameter", func(t *testing.T) {
|
||||
func TestPostParametersByProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Create", 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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{
|
||||
Name: "hi",
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_, err := client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{
|
||||
Name: "somename",
|
||||
SourceValue: "tomato",
|
||||
SourceScheme: database.ParameterSourceSchemeData,
|
||||
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
|
||||
|
@ -127,40 +112,36 @@ func TestProjects(t *testing.T) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Import", func(t *testing.T) {
|
||||
func TestParametersByProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("ListEmpty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_ = server.AddProvisionerd(t)
|
||||
project, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "someproject",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
params, err := client.ProjectParameters(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, params)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_, err := client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{
|
||||
Name: "example",
|
||||
SourceValue: "source-value",
|
||||
SourceScheme: database.ParameterSourceSchemeData,
|
||||
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
|
||||
DestinationValue: "destination-value",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
data, err := echo.Tar([]*proto.Parse_Response{{
|
||||
Type: &proto.Parse_Response_Complete{
|
||||
Complete: &proto.Parse_Complete{
|
||||
ParameterSchemas: []*proto.ParameterSchema{{
|
||||
Name: "example",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}}, nil)
|
||||
require.NoError(t, err)
|
||||
version, err := server.Client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
|
||||
StorageMethod: database.ProjectStorageMethodInlineArchive,
|
||||
StorageSource: data,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Eventually(t, func() bool {
|
||||
projectVersion, err := server.Client.ProjectVersion(context.Background(), user.Organization, project.Name, version.Name)
|
||||
require.NoError(t, err)
|
||||
return projectVersion.Import.Status.Completed()
|
||||
}, 15*time.Second, 10*time.Millisecond)
|
||||
params, err := server.Client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, version.Name)
|
||||
params, err := client.ProjectParameters(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, params)
|
||||
require.Len(t, params, 1)
|
||||
require.Equal(t, "example", params[0].Name)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -110,19 +110,11 @@ func (api *api) postProjectVersionByOrganization(rw http.ResponseWriter, r *http
|
|||
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:
|
||||
tarReader := tar.NewReader(bytes.NewReader(createProjectVersion.StorageSource))
|
||||
_, err := tarReader.Next()
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
Message: fmt.Sprintf("unsupported storage method %s", createProjectVersion.StorageMethod),
|
||||
Message: "the archive must be a tar",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
@ -132,7 +124,7 @@ func (api *api) postProjectVersionByOrganization(rw http.ResponseWriter, r *http
|
|||
|
||||
var provisionerJob database.ProvisionerJob
|
||||
var projectVersion database.ProjectVersion
|
||||
err := api.Database.InTx(func(db database.Store) error {
|
||||
err = api.Database.InTx(func(db database.Store) error {
|
||||
projectVersionID := uuid.New()
|
||||
input, err := json.Marshal(projectImportJob{
|
||||
ProjectVersionID: projectVersionID,
|
||||
|
|
|
@ -1,103 +1,129 @@
|
|||
package coderd_test
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/database"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
)
|
||||
|
||||
func TestProjectVersion(t *testing.T) {
|
||||
func TestProjectVersionsByOrganization(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("NoHistory", func(t *testing.T) {
|
||||
t.Run("ListEmpty", 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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
versions, err := server.Client.ProjectVersions(context.Background(), user.Organization, project.Name)
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
versions, err := client.ProjectVersions(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, versions)
|
||||
require.Len(t, versions, 0)
|
||||
})
|
||||
|
||||
t.Run("CreateVersion", func(t *testing.T) {
|
||||
t.Run("List", 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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
data, err := echo.Tar([]*proto.Parse_Response{{
|
||||
Type: &proto.Parse_Response_Complete{
|
||||
Complete: &proto.Parse_Complete{},
|
||||
},
|
||||
}}, nil)
|
||||
require.NoError(t, err)
|
||||
version, err := server.Client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
|
||||
StorageMethod: database.ProjectStorageMethodInlineArchive,
|
||||
StorageSource: data,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
versions, err := server.Client.ProjectVersions(context.Background(), user.Organization, project.Name)
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_ = coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
versions, err := client.ProjectVersions(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, versions, 1)
|
||||
})
|
||||
}
|
||||
|
||||
_, err = server.Client.ProjectVersion(context.Background(), user.Organization, project.Name, version.Name)
|
||||
require.NoError(t, err)
|
||||
func TestProjectVersionByOrganizationAndName(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
require.Equal(t, version.Import.Status, coderd.ProvisionerJobStatusPending)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostProjectVersionByOrganization(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_ = coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
})
|
||||
|
||||
t.Run("CreateHistoryArchiveTooBig", func(t *testing.T) {
|
||||
t.Run("InvalidStorage", 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.ProvisionerTypeEcho,
|
||||
})
|
||||
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.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
|
||||
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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
|
||||
StorageMethod: database.ProjectStorageMethodInlineArchive,
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_, err := client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
|
||||
StorageMethod: database.ProjectStorageMethod("invalid"),
|
||||
StorageSource: []byte{},
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProjectVersionParametersByOrganizationAndName(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("NotImported", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
_, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, version.Name)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusPreconditionRequired, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("FailedImport", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
|
||||
Provision: []*proto.Provision_Response{{}},
|
||||
})
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
_, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, version.Name)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
|
||||
})
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
|
||||
Parse: []*proto.Parse_Response{{
|
||||
Type: &proto.Parse_Response_Complete{
|
||||
Complete: &proto.Parse_Complete{
|
||||
ParameterSchemas: []*proto.ParameterSchema{{
|
||||
Name: "example",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
params, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, version.Name)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, params, 1)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"time"
|
||||
|
@ -35,7 +36,6 @@ func (api *api) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
|
|||
daemons, err := api.Database.GetProvisionerDaemons(r.Context())
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
daemons = []database.ProvisionerDaemon{}
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
|
@ -43,7 +43,9 @@ func (api *api) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
|
|||
})
|
||||
return
|
||||
}
|
||||
|
||||
if daemons == nil {
|
||||
daemons = []database.ProvisionerDaemon{}
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(rw, r, daemons)
|
||||
}
|
||||
|
@ -51,7 +53,7 @@ func (api *api) provisionerDaemons(rw http.ResponseWriter, r *http.Request) {
|
|||
// Serves the provisioner daemon protobuf API over a WebSocket.
|
||||
func (api *api) provisionerDaemonsServe(rw http.ResponseWriter, r *http.Request) {
|
||||
conn, err := websocket.Accept(rw, r, &websocket.AcceptOptions{
|
||||
// Need to disable compression to avoid a data-race
|
||||
// Need to disable compression to avoid a data-race.
|
||||
CompressionMode: websocket.CompressionDisabled,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -75,7 +77,9 @@ func (api *api) provisionerDaemonsServe(rw http.ResponseWriter, r *http.Request)
|
|||
// Multiplexes the incoming connection using yamux.
|
||||
// This allows multiple function calls to occur over
|
||||
// the same connection.
|
||||
session, err := yamux.Server(websocket.NetConn(r.Context(), conn, websocket.MessageBinary), nil)
|
||||
config := yamux.DefaultConfig()
|
||||
config.LogOutput = io.Discard
|
||||
session, err := yamux.Server(websocket.NetConn(r.Context(), conn, websocket.MessageBinary), config)
|
||||
if err != nil {
|
||||
_ = conn.Close(websocket.StatusInternalError, fmt.Sprintf("multiplex server: %s", err))
|
||||
return
|
||||
|
@ -221,25 +225,11 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
|
|||
protoParameters = append(protoParameters, parameter.Proto)
|
||||
}
|
||||
|
||||
provisionerState := []byte{}
|
||||
// If workspace history exists before this entry, use that state.
|
||||
// We can't use the before state everytime, because if a job fails
|
||||
// for some random reason, the workspace shouldn't be reset.
|
||||
//
|
||||
// Maybe we should make state global on a workspace?
|
||||
if workspaceHistory.BeforeID.Valid {
|
||||
beforeHistory, err := server.Database.GetWorkspaceHistoryByID(ctx, workspaceHistory.BeforeID.UUID)
|
||||
if err != nil {
|
||||
return nil, failJob(fmt.Sprintf("get workspace history: %s", err))
|
||||
}
|
||||
provisionerState = beforeHistory.ProvisionerState
|
||||
}
|
||||
|
||||
protoJob.Type = &proto.AcquiredJob_WorkspaceProvision_{
|
||||
WorkspaceProvision: &proto.AcquiredJob_WorkspaceProvision{
|
||||
WorkspaceHistoryId: workspaceHistory.ID.String(),
|
||||
WorkspaceName: workspace.Name,
|
||||
State: provisionerState,
|
||||
State: workspaceHistory.ProvisionerState,
|
||||
ParameterValues: protoParameters,
|
||||
},
|
||||
}
|
||||
|
@ -286,10 +276,10 @@ func (server *provisionerdServer) UpdateJob(stream proto.DRPCProvisionerDaemon_U
|
|||
return xerrors.Errorf("get job: %w", err)
|
||||
}
|
||||
if !job.WorkerID.Valid {
|
||||
return errors.New("job isn't running yet")
|
||||
return xerrors.New("job isn't running yet")
|
||||
}
|
||||
if job.WorkerID.UUID.String() != server.ID.String() {
|
||||
return errors.New("you don't own this job")
|
||||
return xerrors.New("you don't own this job")
|
||||
}
|
||||
|
||||
err = server.Database.UpdateProvisionerJobByID(stream.Context(), database.UpdateProvisionerJobByIDParams{
|
||||
|
|
|
@ -11,16 +11,18 @@ import (
|
|||
)
|
||||
|
||||
func TestProvisionerDaemons(t *testing.T) {
|
||||
// Tests for properly processing specific job
|
||||
// types should be placed in their respective
|
||||
// resource location.
|
||||
//
|
||||
// eg. project import is a project-related job
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Register", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.AddProvisionerd(t)
|
||||
require.Eventually(t, func() bool {
|
||||
daemons, err := server.Client.ProvisionerDaemons(context.Background())
|
||||
require.NoError(t, err)
|
||||
return len(daemons) > 0
|
||||
}, time.Second, 10*time.Millisecond)
|
||||
})
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.NewProvisionerDaemon(t, client)
|
||||
require.Eventually(t, func() bool {
|
||||
daemons, err := client.ProvisionerDaemons(context.Background())
|
||||
require.NoError(t, err)
|
||||
return len(daemons) > 0
|
||||
}, time.Second, 25*time.Millisecond)
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ func convertProvisionerJob(provisionerJob database.ProvisionerJob) ProvisionerJo
|
|||
job.Status = ProvisionerJobStatusRunning
|
||||
}
|
||||
|
||||
if job.Error != "" {
|
||||
if !provisionerJob.CancelledAt.Valid && job.Error != "" {
|
||||
job.Status = ProvisionerJobStatusFailed
|
||||
}
|
||||
|
||||
|
|
|
@ -195,6 +195,10 @@ func (api *api) organizationsByUser(rw http.ResponseWriter, r *http.Request) {
|
|||
user := httpmw.UserParam(r)
|
||||
|
||||
organizations, err := api.Database.GetOrganizationsByUserID(r.Context(), user.ID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
organizations = []database.Organization{}
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get organizations: %s", err.Error()),
|
||||
|
|
|
@ -9,107 +9,143 @@ import (
|
|||
|
||||
"github.com/coder/coder/coderd"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/httpmw"
|
||||
)
|
||||
|
||||
func TestUsers(t *testing.T) {
|
||||
func TestPostUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("Authenticated", func(t *testing.T) {
|
||||
t.Run("BadRequest", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(t)
|
||||
_, err := server.Client.User(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("CreateMultipleInitial", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(t)
|
||||
_, err := server.Client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
|
||||
Email: "dummy@coder.com",
|
||||
Organization: "bananas",
|
||||
Username: "fake",
|
||||
Password: "password",
|
||||
})
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Login", func(t *testing.T) {
|
||||
t.Run("AlreadyExists", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
|
||||
Email: "some@email.com",
|
||||
Username: "exampleuser",
|
||||
Password: "password",
|
||||
Organization: "someorg",
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostUsers(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("BadRequest", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Conflicting", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
|
||||
Email: user.Email,
|
||||
Username: user.Username,
|
||||
Password: "password",
|
||||
Organization: "someorg",
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.CreateUser(context.Background(), coderd.CreateUserRequest{
|
||||
Email: "another@user.org",
|
||||
Username: "someone-else",
|
||||
Password: "testing",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserByName(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.User(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestOrganizationsByUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
orgs, err := client.UserOrganizations(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, orgs)
|
||||
require.Len(t, orgs, 1)
|
||||
}
|
||||
|
||||
func TestPostLogin(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("InvalidUser", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: "my@email.org",
|
||||
Password: "password",
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("BadPassword", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: user.Email,
|
||||
Password: "badpass",
|
||||
})
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: user.Email,
|
||||
Password: user.Password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("LoginInvalidUser", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: "hello@io.io",
|
||||
Password: "wowie",
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("LoginBadPassword", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: user.Email,
|
||||
Password: "bananas",
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("ListOrganizations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(t)
|
||||
orgs, err := server.Client.UserOrganizations(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, orgs, 1)
|
||||
})
|
||||
|
||||
t.Run("CreateUser", 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: "tomato",
|
||||
Password: "bananas",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("CreateUserConflict", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.CreateUser(context.Background(), coderd.CreateUserRequest{
|
||||
Email: "wow@ok.io",
|
||||
Username: user.Username,
|
||||
Password: "bananas",
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogout(t *testing.T) {
|
||||
func TestPostLogout(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("LogoutShouldClearCookie", func(t *testing.T) {
|
||||
t.Run("ClearCookie", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
server := coderdtest.New(t)
|
||||
fullURL, err := server.URL.Parse("/api/v2/logout")
|
||||
client := coderdtest.New(t)
|
||||
fullURL, err := client.URL.Parse("/api/v2/logout")
|
||||
require.NoError(t, err, "Server URL should parse successfully")
|
||||
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, fullURL.String(), nil)
|
||||
|
|
|
@ -74,12 +74,12 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
|
|||
projectVersionJobStatus := convertProvisionerJob(projectVersionJob).Status
|
||||
switch projectVersionJobStatus {
|
||||
case ProvisionerJobStatusPending, ProvisionerJobStatusRunning:
|
||||
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
|
||||
httpapi.Write(rw, http.StatusNotAcceptable, httpapi.Response{
|
||||
Message: fmt.Sprintf("The provided project version is %s. Wait for it to complete importing!", projectVersionJobStatus),
|
||||
})
|
||||
return
|
||||
case ProvisionerJobStatusFailed:
|
||||
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
|
||||
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
|
||||
Message: fmt.Sprintf("The provided project version %q has failed to import. You cannot create workspaces using it!", projectVersion.Name),
|
||||
})
|
||||
return
|
||||
|
@ -87,6 +87,7 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
|
|||
httpapi.Write(rw, http.StatusPreconditionFailed, httpapi.Response{
|
||||
Message: "The provided project version was canceled during import. You cannot create workspaces using it!",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
project, err := api.Database.GetProjectByID(r.Context(), projectVersion.ProjectID)
|
||||
|
@ -102,7 +103,7 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
|
|||
priorHistory, err := api.Database.GetWorkspaceHistoryByWorkspaceIDWithoutAfter(r.Context(), workspace.ID)
|
||||
if err == nil {
|
||||
priorJob, err := api.Database.GetProvisionerJobByID(r.Context(), priorHistory.ProvisionJobID)
|
||||
if err == nil && convertProvisionerJob(priorJob).Status.Completed() {
|
||||
if err == nil && !convertProvisionerJob(priorJob).Status.Completed() {
|
||||
httpapi.Write(rw, http.StatusConflict, httpapi.Response{
|
||||
Message: "a workspace build is already active",
|
||||
})
|
||||
|
@ -113,8 +114,7 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
|
|||
UUID: priorHistory.ID,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
if !errors.Is(err, sql.ErrNoRows) {
|
||||
} else if !errors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get prior workspace history: %s", err),
|
||||
})
|
||||
|
@ -168,8 +168,9 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
|
|||
if priorHistoryID.Valid {
|
||||
// Update the prior history entries "after" column.
|
||||
err = db.UpdateWorkspaceHistoryByID(r.Context(), database.UpdateWorkspaceHistoryByIDParams{
|
||||
ID: priorHistory.ID,
|
||||
UpdatedAt: database.Now(),
|
||||
ID: priorHistory.ID,
|
||||
ProvisionerState: priorHistory.ProvisionerState,
|
||||
UpdatedAt: database.Now(),
|
||||
AfterID: uuid.NullUUID{
|
||||
UUID: workspaceHistory.ID,
|
||||
Valid: true,
|
||||
|
@ -197,9 +198,10 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
|
|||
func (api *api) workspaceHistoryByUser(rw http.ResponseWriter, r *http.Request) {
|
||||
workspace := httpmw.WorkspaceParam(r)
|
||||
|
||||
histories, err := api.Database.GetWorkspaceHistoryByWorkspaceID(r.Context(), workspace.ID)
|
||||
history, err := api.Database.GetWorkspaceHistoryByWorkspaceID(r.Context(), workspace.ID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
history = []database.WorkspaceHistory{}
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
|
@ -208,8 +210,8 @@ func (api *api) workspaceHistoryByUser(rw http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
apiHistory := make([]WorkspaceHistory, 0, len(histories))
|
||||
for _, history := range histories {
|
||||
apiHistory := make([]WorkspaceHistory, 0, len(history))
|
||||
for _, history := range history {
|
||||
job, err := api.Database.GetProvisionerJobByID(r.Context(), history.ProvisionJobID)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
|
|
|
@ -2,8 +2,8 @@ package coderd_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -13,141 +13,150 @@ import (
|
|||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/database"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
)
|
||||
|
||||
func TestWorkspaceHistory(t *testing.T) {
|
||||
func TestPostWorkspaceHistoryByUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
setupProjectAndWorkspace := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest) (coderd.Project, coderd.Workspace) {
|
||||
project, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "banana",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspace, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: "example",
|
||||
ProjectID: project.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return project, workspace
|
||||
}
|
||||
|
||||
setupProjectVersion := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest, project coderd.Project, data []byte) coderd.ProjectVersion {
|
||||
projectVersion, err := client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
|
||||
StorageMethod: database.ProjectStorageMethodInlineArchive,
|
||||
StorageSource: data,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Eventually(t, func() bool {
|
||||
version, err := client.ProjectVersion(context.Background(), user.Organization, project.Name, projectVersion.Name)
|
||||
require.NoError(t, err)
|
||||
t.Logf("Import status: %s\n", version.Import.Status)
|
||||
return version.Import.Status.Completed()
|
||||
}, 15*time.Second, 50*time.Millisecond)
|
||||
return projectVersion
|
||||
}
|
||||
|
||||
t.Run("AllHistory", func(t *testing.T) {
|
||||
t.Run("NoProjectVersion", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_ = server.AddProvisionerd(t)
|
||||
project, workspace := setupProjectAndWorkspace(t, server.Client, user)
|
||||
history, err := server.Client.ListWorkspaceHistory(context.Background(), "", workspace.Name)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, history, 0)
|
||||
data, err := echo.Tar(echo.ParseComplete, echo.ProvisionComplete)
|
||||
require.NoError(t, err)
|
||||
projectVersion := setupProjectVersion(t, server.Client, user, project, data)
|
||||
_, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: projectVersion.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history, err = server.Client.ListWorkspaceHistory(context.Background(), "", workspace.Name)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, history, 1)
|
||||
})
|
||||
|
||||
t.Run("LatestHistory", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_ = server.AddProvisionerd(t)
|
||||
project, workspace := setupProjectAndWorkspace(t, server.Client, user)
|
||||
_, err := server.Client.WorkspaceHistory(context.Background(), "", workspace.Name, "")
|
||||
require.Error(t, err)
|
||||
data, err := echo.Tar(echo.ParseComplete, echo.ProvisionComplete)
|
||||
require.NoError(t, err)
|
||||
projectVersion := setupProjectVersion(t, server.Client, user, project, data)
|
||||
_, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: projectVersion.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.WorkspaceHistory(context.Background(), "", workspace.Name, "")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("CreateHistory", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_ = server.AddProvisionerd(t)
|
||||
project, workspace := setupProjectAndWorkspace(t, server.Client, user)
|
||||
data, err := echo.Tar(echo.ParseComplete, echo.ProvisionComplete)
|
||||
require.NoError(t, err)
|
||||
projectVersion := setupProjectVersion(t, server.Client, user, project, data)
|
||||
_, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: projectVersion.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var workspaceHistory coderd.WorkspaceHistory
|
||||
require.Eventually(t, func() bool {
|
||||
workspaceHistory, err = server.Client.WorkspaceHistory(context.Background(), "", workspace.Name, "")
|
||||
require.NoError(t, err)
|
||||
return workspaceHistory.Provision.Status.Completed()
|
||||
}, 15*time.Second, 50*time.Millisecond)
|
||||
require.Equal(t, "", workspaceHistory.Provision.Error)
|
||||
require.Equal(t, coderd.ProvisionerJobStatusSucceeded, workspaceHistory.Provision.Status)
|
||||
})
|
||||
|
||||
t.Run("CreateHistoryAlreadyInProgress", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_ = server.AddProvisionerd(t)
|
||||
project, workspace := setupProjectAndWorkspace(t, server.Client, user)
|
||||
data, err := echo.Tar(echo.ParseComplete, echo.ProvisionComplete)
|
||||
require.NoError(t, err)
|
||||
projectVersion := setupProjectVersion(t, server.Client, user, project, data)
|
||||
|
||||
_, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: projectVersion.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: projectVersion.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("CreateHistoryInvalidProjectVersion", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_ = server.AddProvisionerd(t)
|
||||
_, workspace := setupProjectAndWorkspace(t, server.Client, user)
|
||||
|
||||
_, err := server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: uuid.New(),
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.Error(t, err)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("ProjectVersionFailedImport", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
|
||||
Provision: []*proto.Provision_Response{{}},
|
||||
})
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.Error(t, err)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("AlreadyActive", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.Error(t, err)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("UpdatePriorAfterField", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
firstHistory, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "me", workspace.Name, firstHistory.Name)
|
||||
secondHistory, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, firstHistory.ID.String(), secondHistory.BeforeID.String())
|
||||
|
||||
firstHistory, err = client.WorkspaceHistory(context.Background(), "", workspace.Name, firstHistory.Name)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, secondHistory.ID.String(), firstHistory.AfterID.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceHistoryByUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("ListEmpty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
history, err := client.ListWorkspaceHistory(context.Background(), "me", workspace.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, history)
|
||||
require.Len(t, history, 0)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
history, err := client.ListWorkspaceHistory(context.Background(), "me", workspace.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, history)
|
||||
require.Len(t, history, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceHistoryByName(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = client.WorkspaceHistory(context.Background(), "me", workspace.Name, history.Name)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
|
|
@ -87,7 +87,6 @@ func (api *api) workspaceHistoryLogsByName(rw http.ResponseWriter, r *http.Reque
|
|||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
logs = []database.WorkspaceHistoryLog{}
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
|
@ -95,6 +94,9 @@ func (api *api) workspaceHistoryLogsByName(rw http.ResponseWriter, r *http.Reque
|
|||
})
|
||||
return
|
||||
}
|
||||
if logs == nil {
|
||||
logs = []database.WorkspaceHistoryLog{}
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(rw, r, logs)
|
||||
return
|
||||
|
@ -113,12 +115,8 @@ func (api *api) workspaceHistoryLogsByName(rw http.ResponseWriter, r *http.Reque
|
|||
select {
|
||||
case bufferedLogs <- log:
|
||||
default:
|
||||
// This is a case that shouldn't happen, but totally could.
|
||||
// There's no way to stream data from the database, so we'll
|
||||
// need to maintain some level of internal buffer.
|
||||
//
|
||||
// If this overflows users could miss logs when streaming.
|
||||
// We warn to make sure we know when it happens!
|
||||
// If this overflows users could miss logs streaming. This can happen
|
||||
// if a database request takes a long amount of time, and we get a lot of logs.
|
||||
api.Logger.Warn(r.Context(), "workspace history log overflowing channel")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,90 +9,132 @@ import (
|
|||
|
||||
"github.com/coder/coder/coderd"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/database"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
)
|
||||
|
||||
func TestWorkspaceHistoryLogs(t *testing.T) {
|
||||
func TestWorkspaceHistoryLogsByName(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
setupProjectAndWorkspace := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest) (coderd.Project, coderd.Workspace) {
|
||||
project, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "banana",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspace, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: "example",
|
||||
ProjectID: project.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return project, workspace
|
||||
}
|
||||
|
||||
setupProjectVersion := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest, project coderd.Project, data []byte) coderd.ProjectVersion {
|
||||
projectVersion, err := client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
|
||||
StorageMethod: database.ProjectStorageMethodInlineArchive,
|
||||
StorageSource: data,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Eventually(t, func() bool {
|
||||
hist, err := client.ProjectVersion(context.Background(), user.Organization, project.Name, projectVersion.Name)
|
||||
require.NoError(t, err)
|
||||
return hist.Import.Status.Completed()
|
||||
}, 15*time.Second, 50*time.Millisecond)
|
||||
return projectVersion
|
||||
}
|
||||
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_ = server.AddProvisionerd(t)
|
||||
project, workspace := setupProjectAndWorkspace(t, server.Client, user)
|
||||
data, err := echo.Tar(echo.ParseComplete, []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Output: "test",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
projectVersion := setupProjectVersion(t, server.Client, user, project, data)
|
||||
|
||||
workspaceHistory, err := server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: projectVersion.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
now := database.Now()
|
||||
logChan, err := server.Client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, workspaceHistory.Name, now)
|
||||
require.NoError(t, err)
|
||||
|
||||
for {
|
||||
log, more := <-logChan
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
t.Logf("Output: %s", log.Output)
|
||||
}
|
||||
|
||||
t.Run("ReturnAll", func(t *testing.T) {
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := server.Client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, workspaceHistory.Name)
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
Output: "log-output",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Successfully return empty logs before the job starts!
|
||||
logs, err := client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, history.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, logs)
|
||||
require.Len(t, logs, 0)
|
||||
|
||||
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "", workspace.Name, history.Name)
|
||||
|
||||
// Return the log after completion!
|
||||
logs, err = client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, history.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, logs)
|
||||
require.Len(t, logs, 1)
|
||||
})
|
||||
|
||||
t.Run("Between", func(t *testing.T) {
|
||||
t.Run("StreamAfterComplete", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := server.Client.WorkspaceHistoryLogsBetween(context.Background(), "", workspace.Name, workspaceHistory.Name, time.Time{}, database.Now())
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
Output: "log-output",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
before := time.Now().UTC()
|
||||
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "", workspace.Name, history.Name)
|
||||
|
||||
logs, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, history.Name, before)
|
||||
require.NoError(t, err)
|
||||
log := <-logs
|
||||
require.Equal(t, "log-output", log.Output)
|
||||
// Make sure the channel automatically closes!
|
||||
_, ok := <-logs
|
||||
require.False(t, ok)
|
||||
})
|
||||
|
||||
t.Run("StreamWhileRunning", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Level: proto.LogLevel_INFO,
|
||||
Output: "log-output",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
|
||||
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
logs, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, history.Name, time.Time{})
|
||||
require.NoError(t, err)
|
||||
log := <-logs
|
||||
require.Equal(t, "log-output", log.Output)
|
||||
// Make sure the channel automatically closes!
|
||||
_, ok := <-logs
|
||||
require.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ func (api *api) postWorkspaceByUser(rw http.ResponseWriter, r *http.Request) {
|
|||
render.JSON(rw, r, convertWorkspace(workspace))
|
||||
}
|
||||
|
||||
// Returns a single singleWorkspace.
|
||||
// Returns a single workspace.
|
||||
func (*api) workspaceByUser(rw http.ResponseWriter, r *http.Request) {
|
||||
workspace := httpmw.WorkspaceParam(r)
|
||||
|
||||
|
@ -145,6 +145,32 @@ func (*api) workspaceByUser(rw http.ResponseWriter, r *http.Request) {
|
|||
render.JSON(rw, r, convertWorkspace(workspace))
|
||||
}
|
||||
|
||||
// Returns all workspaces for a specific project.
|
||||
func (api *api) workspacesByProject(rw http.ResponseWriter, r *http.Request) {
|
||||
apiKey := httpmw.APIKey(r)
|
||||
project := httpmw.ProjectParam(r)
|
||||
workspaces, err := api.Database.GetWorkspacesByProjectAndUserID(r.Context(), database.GetWorkspacesByProjectAndUserIDParams{
|
||||
OwnerID: apiKey.UserID,
|
||||
ProjectID: project.ID,
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: fmt.Sprintf("get workspaces: %s", err),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
apiWorkspaces := make([]Workspace, 0, len(workspaces))
|
||||
for _, workspace := range workspaces {
|
||||
apiWorkspaces = append(apiWorkspaces, convertWorkspace(workspace))
|
||||
}
|
||||
render.Status(r, http.StatusOK)
|
||||
render.JSON(rw, r, apiWorkspaces)
|
||||
}
|
||||
|
||||
// Converts the internal workspace representation to a public external-facing model.
|
||||
func convertWorkspace(workspace database.Workspace) Workspace {
|
||||
return Workspace(workspace)
|
||||
|
|
|
@ -2,6 +2,7 @@ package coderd_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
@ -10,143 +11,135 @@ import (
|
|||
"github.com/coder/coder/coderd"
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/database"
|
||||
)
|
||||
|
||||
func TestWorkspaces(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("ListNone", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(t)
|
||||
workspaces, err := server.Client.WorkspacesByUser(context.Background(), "")
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
workspaces, err := client.Workspaces(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, workspaces)
|
||||
require.Len(t, workspaces, 0)
|
||||
})
|
||||
|
||||
setupProjectAndWorkspace := func(t *testing.T, client *codersdk.Client, user coderd.CreateInitialUserRequest) (coderd.Project, coderd.Workspace) {
|
||||
project, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "banana",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspace, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: "example",
|
||||
ProjectID: project.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
return project, workspace
|
||||
}
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, _ = setupProjectAndWorkspace(t, server.Client, user)
|
||||
workspaces, err := server.Client.WorkspacesByUser(context.Background(), "")
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_ = coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
workspaces, err := client.Workspaces(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspaces, 1)
|
||||
})
|
||||
|
||||
t.Run("ListNoneForProject", 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: "banana",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspaces, err := server.Client.WorkspacesByProject(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspaces, 0)
|
||||
})
|
||||
|
||||
t.Run("ListForProject", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
project, _ := setupProjectAndWorkspace(t, server.Client, user)
|
||||
workspaces, err := server.Client.WorkspacesByProject(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, workspaces, 1)
|
||||
})
|
||||
|
||||
t.Run("CreateInvalidInput", 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: "banana",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
ProjectID: project.ID,
|
||||
Name: "$$$",
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("CreateInvalidProject", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(t)
|
||||
_, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
ProjectID: uuid.New(),
|
||||
Name: "moo",
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("CreateNotInProjectOrganization", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
initial := server.RandomInitialUser(t)
|
||||
project, err := server.Client.CreateProject(context.Background(), initial.Organization, coderd.CreateProjectRequest{
|
||||
Name: "banana",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.CreateUser(context.Background(), coderd.CreateUserRequest{
|
||||
Email: "hello@ok.io",
|
||||
Username: "example",
|
||||
Password: "password",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
token, err := server.Client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: "hello@ok.io",
|
||||
Password: "password",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = server.Client.SetSessionToken(token.SessionToken)
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
ProjectID: project.ID,
|
||||
Name: "moo",
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("CreateAlreadyExists", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
project, workspace := setupProjectAndWorkspace(t, server.Client, user)
|
||||
_, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: workspace.Name,
|
||||
ProjectID: project.ID,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Single", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, workspace := setupProjectAndWorkspace(t, server.Client, user)
|
||||
_, err := server.Client.Workspace(context.Background(), "", workspace.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPostWorkspaceByUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("InvalidProject", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
ProjectID: uuid.New(),
|
||||
Name: "workspace",
|
||||
})
|
||||
require.Error(t, err)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusBadRequest, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("NoProjectAccess", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
|
||||
anotherUser := coderd.CreateUserRequest{
|
||||
Email: "another@user.org",
|
||||
Username: "someuser",
|
||||
Password: "somepass",
|
||||
}
|
||||
_, err := client.CreateUser(context.Background(), anotherUser)
|
||||
require.NoError(t, err)
|
||||
token, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: anotherUser.Email,
|
||||
Password: anotherUser.Password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = client.SetSessionToken(token.SessionToken)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
ProjectID: project.ID,
|
||||
Name: "workspace",
|
||||
})
|
||||
require.Error(t, err)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusUnauthorized, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("AlreadyExists", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
_, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
ProjectID: project.ID,
|
||||
Name: workspace.Name,
|
||||
})
|
||||
require.Error(t, err)
|
||||
var apiErr *codersdk.Error
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
||||
})
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_ = coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceByUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
_, err := client.Workspace(context.Background(), "", workspace.Name)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestWorkspacesByProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("ListEmpty", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
workspaces, err := client.WorkspacesByProject(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, workspaces)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_ = coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
workspaces, err := client.WorkspacesByProject(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, workspaces)
|
||||
require.Len(t, workspaces, 1)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
|
@ -20,14 +21,15 @@ import (
|
|||
// New creates a Coder client for the provided URL.
|
||||
func New(serverURL *url.URL) *Client {
|
||||
return &Client{
|
||||
url: serverURL,
|
||||
URL: serverURL,
|
||||
httpClient: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
// Client is an HTTP caller for methods to the Coder API.
|
||||
type Client struct {
|
||||
url *url.URL
|
||||
URL *url.URL
|
||||
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
|
@ -40,7 +42,7 @@ func (c *Client) SetSessionToken(token string) error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
c.httpClient.Jar.SetCookies(c.url, []*http.Cookie{{
|
||||
c.httpClient.Jar.SetCookies(c.URL, []*http.Cookie{{
|
||||
Name: httpmw.AuthCookie,
|
||||
Value: token,
|
||||
}})
|
||||
|
@ -50,7 +52,7 @@ func (c *Client) SetSessionToken(token string) error {
|
|||
// request performs an HTTP request with the body provided.
|
||||
// The caller is responsible for closing the response body.
|
||||
func (c *Client) request(ctx context.Context, method, path string, body interface{}) (*http.Response, error) {
|
||||
serverURL, err := c.url.Parse(path)
|
||||
serverURL, err := c.URL.Parse(path)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse url: %w", err)
|
||||
}
|
||||
|
@ -112,5 +114,10 @@ func (e *Error) StatusCode() int {
|
|||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return fmt.Sprintf("status code %d: %s", e.statusCode, e.Message)
|
||||
var builder strings.Builder
|
||||
_, _ = fmt.Fprintf(&builder, "status code %d: %s", e.statusCode, e.Message)
|
||||
for _, err := range e.Errors {
|
||||
_, _ = fmt.Fprintf(&builder, "\n\t%s: %s", err.Field, err.Code)
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package codersdk_test
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
|
@ -15,160 +13,183 @@ import (
|
|||
|
||||
func TestProjects(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
t.Run("UnauthenticatedList", func(t *testing.T) {
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.Projects(context.Background(), "")
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.Projects(context.Background(), "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.Projects(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.Projects(context.Background(), user.Organization)
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.Projects(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("UnauthenticatedCreate", func(t *testing.T) {
|
||||
func TestProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.CreateProject(context.Background(), "", coderd.CreateProjectRequest{})
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.Project(context.Background(), "", "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_, err := client.Project(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.CreateProject(context.Background(), "org", coderd.CreateProjectRequest{
|
||||
Name: "something",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "bananas",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("UnauthenticatedSingle", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.Project(context.Background(), "wow", "example")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Single", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
|
||||
Name: "bananas",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.Project(context.Background(), user.Organization, "bananas")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("UnauthenticatedHistory", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.ProjectVersions(context.Background(), "org", "project")
|
||||
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: "bananas",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.ProjectVersions(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("CreateHistoryUnauthenticated", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.CreateProjectVersion(context.Background(), "org", "project", coderd.CreateProjectVersionRequest{
|
||||
StorageMethod: database.ProjectStorageMethodInlineArchive,
|
||||
StorageSource: []byte{},
|
||||
})
|
||||
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: "bananas",
|
||||
Provisioner: database.ProvisionerTypeEcho,
|
||||
})
|
||||
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)
|
||||
version, err := server.Client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
|
||||
StorageMethod: database.ProjectStorageMethodInlineArchive,
|
||||
StorageSource: buffer.Bytes(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = server.Client.ProjectVersion(context.Background(), user.Organization, project.Name, version.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Parameters", 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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
params, err := server.Client.ProjectParameters(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, params)
|
||||
require.Len(t, params, 0)
|
||||
})
|
||||
|
||||
t.Run("CreateParameter", 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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
param, err := server.Client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{
|
||||
Name: "hi",
|
||||
SourceValue: "tomato",
|
||||
SourceScheme: database.ParameterSourceSchemeData,
|
||||
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
|
||||
DestinationValue: "moo",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "hi", param.Name)
|
||||
})
|
||||
|
||||
t.Run("HistoryParametersError", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
user := server.RandomInitialUser(t)
|
||||
_, err := server.Client.ProjectVersionParameters(context.Background(), user.Organization, "nothing", "nope")
|
||||
require.Error(t, err)
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.CreateProject(t, client, user.Organization)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProjectVersions(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.ProjectVersions(context.Background(), "some", "project")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
|
||||
_, err := client.ProjectVersions(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProjectVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.ProjectVersion(context.Background(), "some", "project", "version")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
_, err := client.ProjectVersion(context.Background(), user.Organization, project.Name, version.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateProjectVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.CreateProjectVersion(context.Background(), "some", "project", coderd.CreateProjectVersionRequest{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_ = coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProjectVersionParameters(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.ProjectVersionParameters(context.Background(), "some", "project", "version")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
_, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, version.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProjectParameters(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.ProjectParameters(context.Background(), "some", "project")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_, err := client.ProjectParameters(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateProjectParameter(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.CreateProjectParameter(context.Background(), "some", "project", coderd.CreateParameterValueRequest{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_, err := client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{
|
||||
Name: "example",
|
||||
SourceValue: "source-value",
|
||||
SourceScheme: database.ParameterSourceSchemeData,
|
||||
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
|
||||
DestinationValue: "destination-value",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package codersdk
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
|
@ -29,12 +30,14 @@ func (c *Client) ProvisionerDaemons(ctx context.Context) ([]coderd.ProvisionerDa
|
|||
|
||||
// ProvisionerDaemonClient returns the gRPC service for a provisioner daemon implementation.
|
||||
func (c *Client) ProvisionerDaemonClient(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) {
|
||||
serverURL, err := c.url.Parse("/api/v2/provisioners/daemons/serve")
|
||||
serverURL, err := c.URL.Parse("/api/v2/provisioners/daemons/serve")
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("parse url: %w", err)
|
||||
}
|
||||
conn, res, err := websocket.Dial(ctx, serverURL.String(), &websocket.DialOptions{
|
||||
HTTPClient: c.httpClient,
|
||||
// Need to disable compression to avoid a data-race.
|
||||
CompressionMode: websocket.CompressionDisabled,
|
||||
})
|
||||
if err != nil {
|
||||
if res == nil {
|
||||
|
@ -42,7 +45,9 @@ func (c *Client) ProvisionerDaemonClient(ctx context.Context) (proto.DRPCProvisi
|
|||
}
|
||||
return nil, readBodyAsError(res)
|
||||
}
|
||||
session, err := yamux.Client(websocket.NetConn(context.Background(), conn, websocket.MessageBinary), nil)
|
||||
config := yamux.DefaultConfig()
|
||||
config.LogOutput = io.Discard
|
||||
session, err := yamux.Client(websocket.NetConn(ctx, conn, websocket.MessageBinary), config)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("multiplex client: %w", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
package codersdk_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/coderd/coderdtest"
|
||||
"github.com/coder/coder/provisionerd/proto"
|
||||
)
|
||||
|
||||
func TestProvisionerDaemons(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.ProvisionerDaemons(context.Background())
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestProvisionerDaemonClient(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
daemon, err := client.ProvisionerDaemonClient(ctx)
|
||||
require.NoError(t, err)
|
||||
cancelFunc()
|
||||
_, err = daemon.AcquireJob(context.Background(), &proto.Empty{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Connect", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
defer cancelFunc()
|
||||
daemon, err := client.ProvisionerDaemonClient(ctx)
|
||||
require.NoError(t, err)
|
||||
_, err = daemon.AcquireJob(ctx, &proto.Empty{})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
|
@ -10,61 +10,104 @@ import (
|
|||
"github.com/coder/coder/coderd/coderdtest"
|
||||
)
|
||||
|
||||
func TestUsers(t *testing.T) {
|
||||
func TestCreateInitialUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("CreateInitial", func(t *testing.T) {
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{
|
||||
Email: "wowie@coder.com",
|
||||
Organization: "somethin",
|
||||
Username: "tester",
|
||||
Password: "moo",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("NoUser", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.User(context.Background(), "")
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.CreateInitialUser(context.Background(), coderd.CreateInitialUserRequest{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("User", func(t *testing.T) {
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(t)
|
||||
_, err := server.Client.User(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.CreateUser(context.Background(), coderd.CreateUserRequest{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("UserOrganizations", func(t *testing.T) {
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(t)
|
||||
orgs, err := server.Client.UserOrganizations(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, orgs, 1)
|
||||
})
|
||||
|
||||
t.Run("LogoutIsSuccessful", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_ = server.RandomInitialUser(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",
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.CreateUser(context.Background(), coderd.CreateUserRequest{
|
||||
Email: "example@coder.com",
|
||||
Username: "something",
|
||||
Password: "password",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoginWithPassword(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Success", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.LoginWithPassword(context.Background(), coderd.LoginWithPasswordRequest{
|
||||
Email: user.Email,
|
||||
Password: user.Password,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogout(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
err := client.Logout(context.Background())
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.User(context.Background(), "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.User(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserOrganizations(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.UserOrganizations(context.Background(), "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.UserOrganizations(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
// 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) {
|
||||
func (c *Client) Workspaces(ctx context.Context, user string) ([]coderd.Workspace, error) {
|
||||
route := "/api/v2/workspaces"
|
||||
if user != "" {
|
||||
route += fmt.Sprintf("/%s", user)
|
||||
|
|
|
@ -3,167 +3,233 @@ package codersdk_test
|
|||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/coder/coder/provisioner/echo"
|
||||
"github.com/coder/coder/provisionersdk/proto"
|
||||
)
|
||||
|
||||
func TestWorkspaces(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("ListError", func(t *testing.T) {
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.WorkspacesByUser(context.Background(), "")
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.Workspaces(context.Background(), "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("ListNoOwner", func(t *testing.T) {
|
||||
t.Run("List", 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.ProvisionerTypeEcho,
|
||||
})
|
||||
client := coderdtest.New(t)
|
||||
_ = coderdtest.CreateInitialUser(t, client)
|
||||
_, err := client.Workspaces(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: "wow",
|
||||
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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: "wow",
|
||||
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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: "wow",
|
||||
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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: "wow",
|
||||
ProjectID: project.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.ListWorkspaceHistory(context.Background(), "", workspace.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("HistoryError", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
server := coderdtest.New(t)
|
||||
_, err := server.Client.ListWorkspaceHistory(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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: "wow",
|
||||
ProjectID: project.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.WorkspaceHistory(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.ProvisionerTypeEcho,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
workspace, err := server.Client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
|
||||
Name: "wow",
|
||||
ProjectID: project.ID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = server.Client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: uuid.New(),
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspacesByProject(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.WorkspacesByProject(context.Background(), "", "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_, err := client.WorkspacesByProject(context.Background(), user.Organization, project.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspace(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.Workspace(context.Background(), "", "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
_, err := client.Workspace(context.Background(), "", workspace.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListWorkspaceHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.ListWorkspaceHistory(context.Background(), "", "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
_, err := client.ListWorkspaceHistory(context.Background(), "", workspace.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.WorkspaceHistory(context.Background(), "", "", "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateWorkspace(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Get", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
_ = coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateWorkspaceHistory(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.CreateWorkspaceHistory(context.Background(), "", "", coderd.CreateWorkspaceHistoryRequest{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestWorkspaceHistoryLogs(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.WorkspaceHistoryLogs(context.Background(), "", "", "")
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("List", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, nil)
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, err = client.WorkspaceHistoryLogs(context.Background(), "", workspace.Name, history.Name)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFollowWorkspaceHistoryLogsAfter(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("Error", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
_, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", "", "", time.Time{})
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("Stream", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client := coderdtest.New(t)
|
||||
user := coderdtest.CreateInitialUser(t, client)
|
||||
_ = coderdtest.NewProvisionerDaemon(t, client)
|
||||
project := coderdtest.CreateProject(t, client, user.Organization)
|
||||
version := coderdtest.CreateProjectVersion(t, client, user.Organization, project.Name, &echo.Responses{
|
||||
Parse: echo.ParseComplete,
|
||||
Provision: []*proto.Provision_Response{{
|
||||
Type: &proto.Provision_Response_Log{
|
||||
Log: &proto.Log{
|
||||
Output: "hello",
|
||||
},
|
||||
},
|
||||
}, {
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{},
|
||||
},
|
||||
}},
|
||||
})
|
||||
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
|
||||
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
|
||||
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
|
||||
ProjectVersionID: version.ID,
|
||||
Transition: database.WorkspaceTransitionCreate,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
logs, err := client.FollowWorkspaceHistoryLogsAfter(context.Background(), "", workspace.Name, history.Name, time.Time{})
|
||||
require.NoError(t, err)
|
||||
_, ok := <-logs
|
||||
require.True(t, ok)
|
||||
_, ok = <-logs
|
||||
require.False(t, ok)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -137,6 +137,26 @@ CREATE TABLE project (
|
|||
active_version_id uuid
|
||||
);
|
||||
|
||||
CREATE TABLE project_parameter (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
project_version_id uuid NOT NULL,
|
||||
name character varying(64) NOT NULL,
|
||||
description character varying(8192) DEFAULT ''::character varying NOT NULL,
|
||||
default_source_scheme parameter_source_scheme,
|
||||
default_source_value text,
|
||||
allow_override_source boolean NOT NULL,
|
||||
default_destination_scheme parameter_destination_scheme,
|
||||
default_destination_value text,
|
||||
allow_override_destination boolean NOT NULL,
|
||||
default_refresh text NOT NULL,
|
||||
redisplay_value boolean NOT NULL,
|
||||
validation_error character varying(256) NOT NULL,
|
||||
validation_condition character varying(512) NOT NULL,
|
||||
validation_type_system parameter_type_system NOT NULL,
|
||||
validation_value_type character varying(64) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE project_version (
|
||||
id uuid NOT NULL,
|
||||
project_id uuid NOT NULL,
|
||||
|
@ -158,26 +178,6 @@ CREATE TABLE project_version_log (
|
|||
output character varying(1024) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE project_parameter (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
project_version_id uuid NOT NULL,
|
||||
name character varying(64) NOT NULL,
|
||||
description character varying(8192) DEFAULT ''::character varying NOT NULL,
|
||||
default_source_scheme parameter_source_scheme,
|
||||
default_source_value text,
|
||||
allow_override_source boolean NOT NULL,
|
||||
default_destination_scheme parameter_destination_scheme,
|
||||
default_destination_value text,
|
||||
allow_override_destination boolean NOT NULL,
|
||||
default_refresh text NOT NULL,
|
||||
redisplay_value boolean NOT NULL,
|
||||
validation_error character varying(256) NOT NULL,
|
||||
validation_condition character varying(512) NOT NULL,
|
||||
validation_type_system parameter_type_system NOT NULL,
|
||||
validation_value_type character varying(64) NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE provisioner_daemon (
|
||||
id uuid NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
|
@ -282,15 +282,6 @@ ALTER TABLE ONLY parameter_value
|
|||
ALTER TABLE ONLY parameter_value
|
||||
ADD CONSTRAINT parameter_value_name_scope_scope_id_key UNIQUE (name, scope, scope_id);
|
||||
|
||||
ALTER TABLE ONLY project_version
|
||||
ADD CONSTRAINT project_version_id_key UNIQUE (id);
|
||||
|
||||
ALTER TABLE ONLY project_version_log
|
||||
ADD CONSTRAINT project_version_log_id_key UNIQUE (id);
|
||||
|
||||
ALTER TABLE ONLY project_version
|
||||
ADD CONSTRAINT project_version_project_id_name_key UNIQUE (project_id, name);
|
||||
|
||||
ALTER TABLE ONLY project
|
||||
ADD CONSTRAINT project_id_key UNIQUE (id);
|
||||
|
||||
|
@ -303,6 +294,15 @@ ALTER TABLE ONLY project_parameter
|
|||
ALTER TABLE ONLY project_parameter
|
||||
ADD CONSTRAINT project_parameter_project_version_id_name_key UNIQUE (project_version_id, name);
|
||||
|
||||
ALTER TABLE ONLY project_version
|
||||
ADD CONSTRAINT project_version_id_key UNIQUE (id);
|
||||
|
||||
ALTER TABLE ONLY project_version_log
|
||||
ADD CONSTRAINT project_version_log_id_key UNIQUE (id);
|
||||
|
||||
ALTER TABLE ONLY project_version
|
||||
ADD CONSTRAINT project_version_project_id_name_key UNIQUE (project_id, name);
|
||||
|
||||
ALTER TABLE ONLY provisioner_daemon
|
||||
ADD CONSTRAINT provisioner_daemon_id_key UNIQUE (id);
|
||||
|
||||
|
@ -339,15 +339,15 @@ ALTER TABLE ONLY workspace_resource
|
|||
ALTER TABLE ONLY workspace_resource
|
||||
ADD CONSTRAINT workspace_resource_workspace_history_id_name_key UNIQUE (workspace_history_id, name);
|
||||
|
||||
ALTER TABLE ONLY project_parameter
|
||||
ADD CONSTRAINT project_parameter_project_version_id_fkey FOREIGN KEY (project_version_id) REFERENCES project_version(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY project_version_log
|
||||
ADD CONSTRAINT project_version_log_project_version_id_fkey FOREIGN KEY (project_version_id) REFERENCES project_version(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY project_version
|
||||
ADD CONSTRAINT project_version_project_id_fkey FOREIGN KEY (project_id) REFERENCES project(id);
|
||||
|
||||
ALTER TABLE ONLY project_parameter
|
||||
ADD CONSTRAINT project_parameter_project_version_id_fkey FOREIGN KEY (project_version_id) REFERENCES project_version(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY provisioner_job
|
||||
ADD CONSTRAINT provisioner_job_project_id_fkey FOREIGN KEY (project_id) REFERENCES project(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
1
go.mod
1
go.mod
|
@ -28,6 +28,7 @@ require (
|
|||
github.com/pion/logging v0.2.2
|
||||
github.com/pion/transport v0.13.0
|
||||
github.com/pion/webrtc/v3 v3.1.21
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.16
|
||||
github.com/spf13/cobra v1.3.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/unrolled/secure v1.0.9
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1087,6 +1087,8 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
|||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.16 h1:yJtIpd4oyNS+/c/gKqxNwoGO9+lPOsy1A4BzKjJRcrI=
|
||||
github.com/quasilyte/go-ruleguard/dsl v0.3.16/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
|
|
|
@ -2,7 +2,6 @@ package peer_test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
|
@ -17,6 +16,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/goleak"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"cdr.dev/slog/sloggers/slogtest"
|
||||
|
@ -231,7 +231,7 @@ func TestConn(t *testing.T) {
|
|||
t.Parallel()
|
||||
conn, err := peer.Client([]webrtc.ICEServer{}, nil)
|
||||
require.NoError(t, err)
|
||||
expectedErr := errors.New("wow")
|
||||
expectedErr := xerrors.New("wow")
|
||||
_ = conn.CloseWithError(expectedErr)
|
||||
_, err = conn.Dial(context.Background(), "", nil)
|
||||
require.ErrorIs(t, err, expectedErr)
|
||||
|
|
|
@ -48,6 +48,10 @@ func (*echo) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_Pa
|
|||
path := filepath.Join(request.Directory, fmt.Sprintf("%d.parse.protobuf", index))
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if index == 0 {
|
||||
// Error if nothing is around to enable failed states.
|
||||
return xerrors.New("no state")
|
||||
}
|
||||
break
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
|
@ -64,7 +68,8 @@ func (*echo) Parse(request *proto.Parse_Request, stream proto.DRPCProvisioner_Pa
|
|||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
<-stream.Context().Done()
|
||||
return stream.Context().Err()
|
||||
}
|
||||
|
||||
// Provision reads requests from the provided directory to stream responses.
|
||||
|
@ -73,6 +78,10 @@ func (*echo) Provision(request *proto.Provision_Request, stream proto.DRPCProvis
|
|||
path := filepath.Join(request.Directory, fmt.Sprintf("%d.provision.protobuf", index))
|
||||
_, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if index == 0 {
|
||||
// Error if nothing is around to enable failed states.
|
||||
return xerrors.New("no state")
|
||||
}
|
||||
break
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
|
@ -89,14 +98,24 @@ func (*echo) Provision(request *proto.Provision_Request, stream proto.DRPCProvis
|
|||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
<-stream.Context().Done()
|
||||
return stream.Context().Err()
|
||||
}
|
||||
|
||||
type Responses struct {
|
||||
Parse []*proto.Parse_Response
|
||||
Provision []*proto.Provision_Response
|
||||
}
|
||||
|
||||
// Tar returns a tar archive of responses to provisioner operations.
|
||||
func Tar(parseResponses []*proto.Parse_Response, provisionResponses []*proto.Provision_Response) ([]byte, error) {
|
||||
func Tar(responses *Responses) ([]byte, error) {
|
||||
if responses == nil {
|
||||
responses = &Responses{ParseComplete, ProvisionComplete}
|
||||
}
|
||||
|
||||
var buffer bytes.Buffer
|
||||
writer := tar.NewWriter(&buffer)
|
||||
for index, response := range parseResponses {
|
||||
for index, response := range responses.Parse {
|
||||
data, err := protobuf.Marshal(response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -113,7 +132,7 @@ func Tar(parseResponses []*proto.Parse_Response, provisionResponses []*proto.Pro
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
for index, response := range provisionResponses {
|
||||
for index, response := range responses.Provision {
|
||||
data, err := protobuf.Marshal(response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -53,7 +53,9 @@ func TestEcho(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}}
|
||||
data, err := echo.Tar(responses, nil)
|
||||
data, err := echo.Tar(&echo.Responses{
|
||||
Parse: responses,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
client, err := api.Parse(ctx, &proto.Parse_Request{
|
||||
Directory: unpackTar(t, data),
|
||||
|
@ -86,7 +88,9 @@ func TestEcho(t *testing.T) {
|
|||
},
|
||||
},
|
||||
}}
|
||||
data, err := echo.Tar(nil, responses)
|
||||
data, err := echo.Tar(&echo.Responses{
|
||||
Provision: responses,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
client, err := api.Provision(ctx, &proto.Provision_Request{
|
||||
Directory: unpackTar(t, data),
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
"go.uber.org/atomic"
|
||||
|
||||
"cdr.dev/slog"
|
||||
"github.com/coder/coder/provisionerd/proto"
|
||||
|
@ -54,7 +55,8 @@ func New(clientDialer Dialer, opts *Options) io.Closer {
|
|||
closeCancel: ctxCancel,
|
||||
closed: make(chan struct{}),
|
||||
|
||||
jobRunning: make(chan struct{}),
|
||||
jobRunning: make(chan struct{}),
|
||||
jobCancelled: *atomic.NewBool(true),
|
||||
}
|
||||
// Start off with a closed channel so
|
||||
// isRunningJob() returns properly.
|
||||
|
@ -77,10 +79,11 @@ type provisionerDaemon struct {
|
|||
closeError error
|
||||
|
||||
// Locked when acquiring or canceling a job.
|
||||
jobMutex sync.Mutex
|
||||
jobID string
|
||||
jobRunning chan struct{}
|
||||
jobCancel context.CancelFunc
|
||||
jobMutex sync.Mutex
|
||||
jobID string
|
||||
jobRunning chan struct{}
|
||||
jobCancelled atomic.Bool
|
||||
jobCancel context.CancelFunc
|
||||
}
|
||||
|
||||
// Connect establishes a connection to coderd.
|
||||
|
@ -193,6 +196,7 @@ func (p *provisionerDaemon) acquireJob(ctx context.Context) {
|
|||
}
|
||||
ctx, p.jobCancel = context.WithCancel(ctx)
|
||||
p.jobRunning = make(chan struct{})
|
||||
p.jobCancelled.Store(false)
|
||||
p.jobID = job.JobId
|
||||
|
||||
p.opts.Logger.Info(context.Background(), "acquired job",
|
||||
|
@ -220,7 +224,7 @@ func (p *provisionerDaemon) runJob(ctx context.Context, job *proto.AcquiredJob)
|
|||
JobId: job.JobId,
|
||||
})
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("send periodic update: %s", err)
|
||||
p.cancelActiveJobf("send periodic update: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -247,13 +251,13 @@ func (p *provisionerDaemon) runJob(ctx context.Context, job *proto.AcquiredJob)
|
|||
// It's safe to cast this ProvisionerType. This data is coming directly from coderd.
|
||||
provisioner, hasProvisioner := p.opts.Provisioners[job.Provisioner]
|
||||
if !hasProvisioner {
|
||||
go p.cancelActiveJobf("provisioner %q not registered", job.Provisioner)
|
||||
p.cancelActiveJobf("provisioner %q not registered", job.Provisioner)
|
||||
return
|
||||
}
|
||||
|
||||
err := os.MkdirAll(p.opts.WorkDirectory, 0700)
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("create work directory %q: %s", p.opts.WorkDirectory, err)
|
||||
p.cancelActiveJobf("create work directory %q: %s", p.opts.WorkDirectory, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -265,13 +269,13 @@ func (p *provisionerDaemon) runJob(ctx context.Context, job *proto.AcquiredJob)
|
|||
break
|
||||
}
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("read project source archive: %s", err)
|
||||
p.cancelActiveJobf("read project source archive: %s", err)
|
||||
return
|
||||
}
|
||||
// #nosec
|
||||
path := filepath.Join(p.opts.WorkDirectory, header.Name)
|
||||
if !strings.HasPrefix(path, filepath.Clean(p.opts.WorkDirectory)) {
|
||||
go p.cancelActiveJobf("tar attempts to target relative upper directory")
|
||||
p.cancelActiveJobf("tar attempts to target relative upper directory")
|
||||
return
|
||||
}
|
||||
mode := header.FileInfo().Mode()
|
||||
|
@ -282,14 +286,14 @@ func (p *provisionerDaemon) runJob(ctx context.Context, job *proto.AcquiredJob)
|
|||
case tar.TypeDir:
|
||||
err = os.MkdirAll(path, mode)
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("mkdir %q: %s", path, err)
|
||||
p.cancelActiveJobf("mkdir %q: %s", path, err)
|
||||
return
|
||||
}
|
||||
p.opts.Logger.Debug(context.Background(), "extracted directory", slog.F("path", path))
|
||||
case tar.TypeReg:
|
||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, mode)
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("create file %q (mode %s): %s", path, mode, err)
|
||||
p.cancelActiveJobf("create file %q (mode %s): %s", path, mode, err)
|
||||
return
|
||||
}
|
||||
// Max file size of 10MB.
|
||||
|
@ -299,12 +303,12 @@ func (p *provisionerDaemon) runJob(ctx context.Context, job *proto.AcquiredJob)
|
|||
}
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
go p.cancelActiveJobf("copy file %q: %s", path, err)
|
||||
p.cancelActiveJobf("copy file %q: %s", path, err)
|
||||
return
|
||||
}
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("close file %q: %s", path, err)
|
||||
p.cancelActiveJobf("close file %q: %s", path, err)
|
||||
return
|
||||
}
|
||||
p.opts.Logger.Debug(context.Background(), "extracted file",
|
||||
|
@ -331,7 +335,7 @@ func (p *provisionerDaemon) runJob(ctx context.Context, job *proto.AcquiredJob)
|
|||
|
||||
p.runWorkspaceProvision(ctx, provisioner, job)
|
||||
default:
|
||||
go p.cancelActiveJobf("unknown job type %q; ensure your provisioner daemon is up-to-date", reflect.TypeOf(job.Type).String())
|
||||
p.cancelActiveJobf("unknown job type %q; ensure your provisioner daemon is up-to-date", reflect.TypeOf(job.Type).String())
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -347,14 +351,14 @@ func (p *provisionerDaemon) runProjectImport(ctx context.Context, provisioner sd
|
|||
Directory: p.opts.WorkDirectory,
|
||||
})
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("parse source: %s", err)
|
||||
p.cancelActiveJobf("parse source: %s", err)
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
for {
|
||||
msg, err := stream.Recv()
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("recv parse source: %s", err)
|
||||
p.cancelActiveJobf("recv parse source: %s", err)
|
||||
return
|
||||
}
|
||||
switch msgType := msg.Type.(type) {
|
||||
|
@ -375,7 +379,7 @@ func (p *provisionerDaemon) runProjectImport(ctx context.Context, provisioner sd
|
|||
}},
|
||||
})
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("update job: %s", err)
|
||||
p.cancelActiveJobf("update job: %s", err)
|
||||
return
|
||||
}
|
||||
case *sdkproto.Parse_Response_Complete:
|
||||
|
@ -391,13 +395,13 @@ func (p *provisionerDaemon) runProjectImport(ctx context.Context, provisioner sd
|
|||
},
|
||||
})
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("complete job: %s", err)
|
||||
p.cancelActiveJobf("complete job: %s", err)
|
||||
return
|
||||
}
|
||||
// Return so we stop looping!
|
||||
return
|
||||
default:
|
||||
go p.cancelActiveJobf("invalid message type %q received from provisioner",
|
||||
p.cancelActiveJobf("invalid message type %q received from provisioner",
|
||||
reflect.TypeOf(msg.Type).String())
|
||||
return
|
||||
}
|
||||
|
@ -411,7 +415,7 @@ func (p *provisionerDaemon) runWorkspaceProvision(ctx context.Context, provision
|
|||
State: job.GetWorkspaceProvision().State,
|
||||
})
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("provision: %s", err)
|
||||
p.cancelActiveJobf("provision: %s", err)
|
||||
return
|
||||
}
|
||||
defer stream.Close()
|
||||
|
@ -419,7 +423,7 @@ func (p *provisionerDaemon) runWorkspaceProvision(ctx context.Context, provision
|
|||
for {
|
||||
msg, err := stream.Recv()
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("recv workspace provision: %s", err)
|
||||
p.cancelActiveJobf("recv workspace provision: %s", err)
|
||||
return
|
||||
}
|
||||
switch msgType := msg.Type.(type) {
|
||||
|
@ -440,7 +444,7 @@ func (p *provisionerDaemon) runWorkspaceProvision(ctx context.Context, provision
|
|||
}},
|
||||
})
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("send job update: %s", err)
|
||||
p.cancelActiveJobf("send job update: %s", err)
|
||||
return
|
||||
}
|
||||
case *sdkproto.Provision_Response_Complete:
|
||||
|
@ -462,13 +466,13 @@ func (p *provisionerDaemon) runWorkspaceProvision(ctx context.Context, provision
|
|||
},
|
||||
})
|
||||
if err != nil {
|
||||
go p.cancelActiveJobf("complete job: %s", err)
|
||||
p.cancelActiveJobf("complete job: %s", err)
|
||||
return
|
||||
}
|
||||
// Return so we stop looping!
|
||||
return
|
||||
default:
|
||||
go p.cancelActiveJobf("invalid message type %q received from provisioner",
|
||||
p.cancelActiveJobf("invalid message type %q received from provisioner",
|
||||
reflect.TypeOf(msg.Type).String())
|
||||
return
|
||||
}
|
||||
|
@ -481,12 +485,16 @@ func (p *provisionerDaemon) cancelActiveJobf(format string, args ...interface{})
|
|||
errMsg := fmt.Sprintf(format, args...)
|
||||
if !p.isRunningJob() {
|
||||
if p.isClosed() {
|
||||
// We don't want to log if we're already closed!
|
||||
return
|
||||
}
|
||||
p.opts.Logger.Warn(context.Background(), "skipping job cancel; none running", slog.F("error_message", errMsg))
|
||||
p.opts.Logger.Info(context.Background(), "skipping job cancel; none running", slog.F("error_message", errMsg))
|
||||
return
|
||||
}
|
||||
if p.jobCancelled.Load() {
|
||||
p.opts.Logger.Warn(context.Background(), "job has already been canceled", slog.F("error_messsage", errMsg))
|
||||
return
|
||||
}
|
||||
p.jobCancelled.Store(true)
|
||||
p.jobCancel()
|
||||
p.opts.Logger.Info(context.Background(), "canceling running job",
|
||||
slog.F("error_message", errMsg),
|
||||
|
@ -500,7 +508,6 @@ func (p *provisionerDaemon) cancelActiveJobf(format string, args ...interface{})
|
|||
p.opts.Logger.Warn(context.Background(), "failed to notify of cancel; job is no longer running", slog.Error(err))
|
||||
return
|
||||
}
|
||||
<-p.jobRunning
|
||||
p.opts.Logger.Debug(context.Background(), "canceled running job")
|
||||
}
|
||||
|
||||
|
@ -534,6 +541,7 @@ func (p *provisionerDaemon) closeWithError(err error) error {
|
|||
errMsg = err.Error()
|
||||
}
|
||||
p.cancelActiveJobf(errMsg)
|
||||
<-p.jobRunning
|
||||
p.closeCancel()
|
||||
|
||||
p.opts.Logger.Debug(context.Background(), "closing server with error", slog.Error(err))
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
@ -15,6 +14,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/atomic"
|
||||
"go.uber.org/goleak"
|
||||
"golang.org/x/xerrors"
|
||||
"storj.io/drpc/drpcmux"
|
||||
"storj.io/drpc/drpcserver"
|
||||
|
||||
|
@ -52,7 +52,7 @@ func TestProvisionerd(t *testing.T) {
|
|||
completeChan := make(chan struct{})
|
||||
closer := createProvisionerd(t, func(ctx context.Context) (proto.DRPCProvisionerDaemonClient, error) {
|
||||
defer close(completeChan)
|
||||
return nil, errors.New("an error")
|
||||
return nil, xerrors.New("an error")
|
||||
}, provisionerd.Provisioners{})
|
||||
<-completeChan
|
||||
require.NoError(t, closer.Close())
|
||||
|
|
|
@ -39,6 +39,7 @@ func TestProvisionerSDK(t *testing.T) {
|
|||
_, err = stream.Recv()
|
||||
require.Equal(t, drpcerr.Unimplemented, int(drpcerr.Code(err)))
|
||||
})
|
||||
|
||||
t.Run("ServeClosedPipe", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
client, server := provisionersdk.TransportPipe()
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package gorules
|
||||
|
||||
import (
|
||||
"github.com/quasilyte/go-ruleguard/dsl"
|
||||
)
|
||||
|
||||
// Use xerrors everywhere! It provides additional stacktrace info!
|
||||
//nolint:unused,deadcode,varnamelen
|
||||
func xerrors(m dsl.Matcher) {
|
||||
m.Import("errors")
|
||||
m.Import("fmt")
|
||||
m.Import("golang.org/x/xerrors")
|
||||
msg := "Use xerrors to provide additional stacktrace information!"
|
||||
|
||||
m.Match("fmt.Errorf($*args)").
|
||||
Suggest("xerrors.New($args)").
|
||||
Report(msg)
|
||||
|
||||
m.Match("fmt.Errorf($*args)").
|
||||
Suggest("xerrors.Errorf($args)").
|
||||
Report(msg)
|
||||
|
||||
m.Match("errors.New($msg)").
|
||||
Where(m["msg"].Type.Is("string")).
|
||||
Suggest("xerrors.New($msg)").
|
||||
Report(msg)
|
||||
}
|
Loading…
Reference in New Issue