refactor: Allow provisioner jobs to be disconnected from projects (#194)

* Nest jobs under an organization

* Rename project parameter to parameter schema

* Update references when computing project parameters

* Add files endpoint

* Allow one-off project import jobs

* Allow variables to be injected that are not defined by the schema

* Update API to use jobs first

* Fix CLI tests

* Fix linting

* Fix hex length for files table

* Reduce memory allocation for windows
This commit is contained in:
Kyle Carberry
2022-02-08 12:00:44 -06:00
committed by GitHub
parent 4c5e443b63
commit 7364933e65
37 changed files with 1380 additions and 995 deletions
+16 -3
View File
@@ -102,14 +102,27 @@ func New(options *Options) http.Handler {
})
})
r.Route("/files", func(r chi.Router) {
r.Use(httpmw.ExtractAPIKey(options.Database, nil))
r.Post("/", api.postFiles)
})
r.Route("/provisioners", func(r chi.Router) {
r.Route("/daemons", func(r chi.Router) {
r.Get("/", api.provisionerDaemons)
r.Get("/serve", api.provisionerDaemonsServe)
})
r.Route("/jobs/{provisionerjob}", func(r chi.Router) {
r.Use(httpmw.ExtractProvisionerJobParam(options.Database))
r.Get("/logs", api.provisionerJobLogsByID)
r.Route("/jobs/{organization}", func(r chi.Router) {
r.Use(
httpmw.ExtractAPIKey(options.Database, nil),
httpmw.ExtractOrganizationParam(options.Database),
)
r.Post("/import", api.postProvisionerImportJobByOrganization)
r.Route("/{provisionerjob}", func(r chi.Router) {
r.Use(httpmw.ExtractProvisionerJobParam(options.Database))
r.Get("/", api.provisionerJobByOrganization)
r.Get("/logs", api.provisionerJobLogsByID)
})
})
})
})
+26 -34
View File
@@ -122,40 +122,44 @@ func CreateInitialUser(t *testing.T, client *codersdk.Client) coderd.CreateIniti
return req
}
// CreateProjectImportProvisionerJob creates a project import provisioner job
// with the responses provided. It uses the "echo" provisioner for compatibility
// with testing.
func CreateProjectImportProvisionerJob(t *testing.T, client *codersdk.Client, organization string, res *echo.Responses) coderd.ProvisionerJob {
data, err := echo.Tar(res)
require.NoError(t, err)
file, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, data)
require.NoError(t, err)
job, err := client.CreateProjectVersionImportProvisionerJob(context.Background(), organization, coderd.CreateProjectImportJobRequest{
StorageSource: file.Hash,
StorageMethod: database.ProvisionerStorageMethodFile,
Provisioner: database.ProvisionerTypeEcho,
})
require.NoError(t, err)
return job
}
// 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 {
func CreateProject(t *testing.T, client *codersdk.Client, organization string, job uuid.UUID) coderd.Project {
project, err := client.CreateProject(context.Background(), organization, coderd.CreateProjectRequest{
Name: randomUsername(),
Provisioner: database.ProvisionerTypeEcho,
Name: randomUsername(),
VersionImportJobID: job,
})
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
// AwaitProvisionerJob awaits for a job to reach completed status.
func AwaitProvisionerJob(t *testing.T, client *codersdk.Client, organization string, job uuid.UUID) coderd.ProvisionerJob {
var provisionerJob coderd.ProvisionerJob
require.Eventually(t, func() bool {
var err error
projectVersion, err = client.ProjectVersion(context.Background(), organization, project, version)
provisionerJob, err = client.ProvisionerJob(context.Background(), organization, job)
require.NoError(t, err)
return projectVersion.Import.Status.Completed()
return provisionerJob.Status.Completed()
}, 3*time.Second, 25*time.Millisecond)
return projectVersion
return provisionerJob
}
// CreateWorkspace creates a workspace for the user and project provided.
@@ -169,18 +173,6 @@ func CreateWorkspace(t *testing.T, client *codersdk.Client, user string, project
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), "_", "-")
}
+5 -5
View File
@@ -22,15 +22,15 @@ func TestNew(t *testing.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)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
history, err := client.CreateWorkspaceHistory(context.Background(), "me", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "me", workspace.Name, history.Name)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, history.ProvisionJobID)
closer.Close()
}
+60
View File
@@ -0,0 +1,60 @@
package coderd
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"net/http"
"github.com/go-chi/render"
"github.com/coder/coder/database"
"github.com/coder/coder/httpapi"
"github.com/coder/coder/httpmw"
)
type UploadFileResponse struct {
Hash string `json:"hash"`
}
func (api *api) postFiles(rw http.ResponseWriter, r *http.Request) {
apiKey := httpmw.APIKey(r)
contentType := r.Header.Get("Content-Type")
switch contentType {
case "application/x-tar":
default:
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: fmt.Sprintf("unsupported content type: %s", contentType),
})
return
}
r.Body = http.MaxBytesReader(rw, r.Body, 10*(10<<20))
data, err := io.ReadAll(r.Body)
if err != nil {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: fmt.Sprintf("read file: %s", err),
})
return
}
hashBytes := sha256.Sum256(data)
file, err := api.Database.InsertFile(r.Context(), database.InsertFileParams{
Hash: hex.EncodeToString(hashBytes[:]),
CreatedBy: apiKey.UserID,
CreatedAt: database.Now(),
Mimetype: contentType,
Data: data,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("insert file: %s", err),
})
return
}
render.Status(r, http.StatusCreated)
render.JSON(rw, r, UploadFileResponse{
Hash: file.Hash,
})
}
+30
View File
@@ -0,0 +1,30 @@
package coderd_test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/codersdk"
)
func TestPostFiles(t *testing.T) {
t.Parallel()
t.Run("BadContentType", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.UploadFile(context.Background(), "bad", []byte{'a'})
require.Error(t, err)
})
t.Run("Insert", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.UploadFile(context.Background(), codersdk.ContentTypeTar, make([]byte, 1024))
require.NoError(t, err)
})
}
+67 -54
View File
@@ -15,11 +15,11 @@ import (
// Scope targets identifiers to pull parameters from.
type Scope struct {
OrganizationID string
ProjectID uuid.UUID
ProjectVersionID uuid.UUID
UserID sql.NullString
WorkspaceID uuid.NullUUID
ImportJobID uuid.UUID
OrganizationID string
ProjectID uuid.NullUUID
UserID sql.NullString
WorkspaceID uuid.NullUUID
}
// Value represents a computed parameter.
@@ -35,29 +35,27 @@ type Value struct {
// Compute accepts a scope in which parameter values are sourced.
// These sources are iterated in a hierarchical fashion to determine
// the runtime parameter values for a project.
func Compute(ctx context.Context, db database.Store, scope Scope) ([]Value, error) {
func Compute(ctx context.Context, db database.Store, scope Scope, additional ...database.ParameterValue) ([]Value, error) {
compute := &compute{
db: db,
computedParameterByName: map[string]Value{},
projectVersionParametersByName: map[string]database.ProjectVersionParameter{},
db: db,
computedParameterByName: map[string]Value{},
parameterSchemasByName: map[string]database.ParameterSchema{},
}
// All parameters for the project version!
projectVersionParameters, err := db.GetProjectVersionParametersByVersionID(ctx, scope.ProjectVersionID)
// All parameters for the import job ID!
parameterSchemas, err := db.GetParameterSchemasByJobID(ctx, scope.ImportJobID)
if errors.Is(err, sql.ErrNoRows) {
// This occurs when the project version has defined
// no parameters, so we have nothing to compute!
return []Value{}, nil
err = nil
}
if err != nil {
return nil, xerrors.Errorf("get project parameters: %w", err)
}
for _, projectVersionParameter := range projectVersionParameters {
compute.projectVersionParametersByName[projectVersionParameter.Name] = projectVersionParameter
for _, projectVersionParameter := range parameterSchemas {
compute.parameterSchemasByName[projectVersionParameter.Name] = projectVersionParameter
}
// Organization parameters come first!
err = compute.inject(ctx, database.GetParameterValuesByScopeParams{
err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{
Scope: database.ParameterScopeOrganization,
ScopeID: scope.OrganizationID,
})
@@ -66,7 +64,7 @@ func Compute(ctx context.Context, db database.Store, scope Scope) ([]Value, erro
}
// Default project parameter values come second!
for _, projectVersionParameter := range projectVersionParameters {
for _, projectVersionParameter := range parameterSchemas {
if !projectVersionParameter.DefaultSourceValue.Valid {
continue
}
@@ -89,25 +87,27 @@ func Compute(ctx context.Context, db database.Store, scope Scope) ([]Value, erro
},
DefaultValue: true,
Scope: database.ParameterScopeProject,
ScopeID: scope.ProjectID.String(),
ScopeID: scope.ProjectID.UUID.String(),
}
default:
return nil, xerrors.Errorf("unsupported source scheme for project version parameter %q: %q", projectVersionParameter.Name, string(projectVersionParameter.DefaultSourceScheme))
}
}
// Project parameters come third!
err = compute.inject(ctx, database.GetParameterValuesByScopeParams{
Scope: database.ParameterScopeProject,
ScopeID: scope.ProjectID.String(),
})
if err != nil {
return nil, err
if scope.ProjectID.Valid {
// Project parameters come third!
err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{
Scope: database.ParameterScopeProject,
ScopeID: scope.ProjectID.UUID.String(),
})
if err != nil {
return nil, err
}
}
if scope.UserID.Valid {
// User parameters come fourth!
err = compute.inject(ctx, database.GetParameterValuesByScopeParams{
err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{
Scope: database.ParameterScopeUser,
ScopeID: scope.UserID.String,
})
@@ -118,7 +118,7 @@ func Compute(ctx context.Context, db database.Store, scope Scope) ([]Value, erro
if scope.WorkspaceID.Valid {
// Workspace parameters come last!
err = compute.inject(ctx, database.GetParameterValuesByScopeParams{
err = compute.injectScope(ctx, database.GetParameterValuesByScopeParams{
Scope: database.ParameterScopeWorkspace,
ScopeID: scope.WorkspaceID.UUID.String(),
})
@@ -127,7 +127,14 @@ func Compute(ctx context.Context, db database.Store, scope Scope) ([]Value, erro
}
}
for _, projectVersionParameter := range compute.projectVersionParametersByName {
for _, parameterValue := range additional {
err = compute.injectSingle(parameterValue)
if err != nil {
return nil, xerrors.Errorf("inject %q: %w", parameterValue.Name, err)
}
}
for _, projectVersionParameter := range compute.parameterSchemasByName {
if _, ok := compute.computedParameterByName[projectVersionParameter.Name]; ok {
continue
}
@@ -145,13 +152,13 @@ func Compute(ctx context.Context, db database.Store, scope Scope) ([]Value, erro
}
type compute struct {
db database.Store
computedParameterByName map[string]Value
projectVersionParametersByName map[string]database.ProjectVersionParameter
db database.Store
computedParameterByName map[string]Value
parameterSchemasByName map[string]database.ParameterSchema
}
// Validates and computes the value for parameters; setting the value on "parameterByName".
func (c *compute) inject(ctx context.Context, scopeParams database.GetParameterValuesByScopeParams) error {
func (c *compute) injectScope(ctx context.Context, scopeParams database.GetParameterValuesByScopeParams) error {
scopedParameters, err := c.db.GetParameterValuesByScope(ctx, scopeParams)
if errors.Is(err, sql.ErrNoRows) {
err = nil
@@ -161,39 +168,45 @@ func (c *compute) inject(ctx context.Context, scopeParams database.GetParameterV
}
for _, scopedParameter := range scopedParameters {
projectVersionParameter, hasProjectVersionParameter := c.projectVersionParametersByName[scopedParameter.Name]
if !hasProjectVersionParameter {
// Don't inject parameters that aren't defined by the project.
continue
err = c.injectSingle(scopedParameter)
if err != nil {
return xerrors.Errorf("inject single %q: %w", scopedParameter.Name, err)
}
}
return nil
}
func (c *compute) injectSingle(scopedParameter database.ParameterValue) error {
parameterSchema, hasParameterSchema := c.parameterSchemasByName[scopedParameter.Name]
if hasParameterSchema {
// Don't inject parameters that aren't defined by the project.
_, hasExistingParameter := c.computedParameterByName[scopedParameter.Name]
if hasExistingParameter {
// If a parameter already exists, check if this variable can override it.
// Injection hierarchy is the responsibility of the caller. This check ensures
// project parameters cannot be overridden if already set.
if !projectVersionParameter.AllowOverrideSource && scopedParameter.Scope != database.ParameterScopeProject {
continue
if !parameterSchema.AllowOverrideSource && scopedParameter.Scope != database.ParameterScopeProject {
return nil
}
}
}
destinationScheme, err := convertDestinationScheme(scopedParameter.DestinationScheme)
if err != nil {
return xerrors.Errorf("convert destination scheme: %w", err)
}
destinationScheme, err := convertDestinationScheme(scopedParameter.DestinationScheme)
if err != nil {
return xerrors.Errorf("convert destination scheme: %w", err)
}
switch scopedParameter.SourceScheme {
case database.ParameterSourceSchemeData:
c.computedParameterByName[projectVersionParameter.Name] = Value{
Proto: &proto.ParameterValue{
DestinationScheme: destinationScheme,
Name: scopedParameter.SourceValue,
Value: scopedParameter.DestinationValue,
},
}
default:
return xerrors.Errorf("unsupported source scheme: %q", string(projectVersionParameter.DefaultSourceScheme))
switch scopedParameter.SourceScheme {
case database.ParameterSourceSchemeData:
c.computedParameterByName[scopedParameter.Name] = Value{
Proto: &proto.ParameterValue{
DestinationScheme: destinationScheme,
Name: scopedParameter.SourceValue,
Value: scopedParameter.DestinationValue,
},
}
default:
return xerrors.Errorf("unsupported source scheme: %q", string(parameterSchema.DefaultSourceScheme))
}
return nil
}
@@ -19,9 +19,12 @@ func TestCompute(t *testing.T) {
t.Parallel()
generateScope := func() projectparameter.Scope {
return projectparameter.Scope{
OrganizationID: uuid.New().String(),
ProjectID: uuid.New(),
ProjectVersionID: uuid.New(),
ImportJobID: uuid.New(),
OrganizationID: uuid.NewString(),
ProjectID: uuid.NullUUID{
UUID: uuid.New(),
Valid: true,
},
WorkspaceID: uuid.NullUUID{
UUID: uuid.New(),
Valid: true,
@@ -36,9 +39,9 @@ func TestCompute(t *testing.T) {
AllowOverrideSource bool
AllowOverrideDestination bool
DefaultDestinationScheme database.ParameterDestinationScheme
ProjectVersionID uuid.UUID
ImportJobID uuid.UUID
}
generateProjectParameter := func(t *testing.T, db database.Store, opts projectParameterOptions) database.ProjectVersionParameter {
generateProjectParameter := func(t *testing.T, db database.Store, opts projectParameterOptions) database.ParameterSchema {
if opts.DefaultDestinationScheme == "" {
opts.DefaultDestinationScheme = database.ParameterDestinationSchemeEnvironmentVariable
}
@@ -48,10 +51,10 @@ func TestCompute(t *testing.T) {
require.NoError(t, err)
destinationValue, err := cryptorand.String(8)
require.NoError(t, err)
param, err := db.InsertProjectVersionParameter(context.Background(), database.InsertProjectVersionParameterParams{
param, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{
ID: uuid.New(),
Name: name,
ProjectVersionID: opts.ProjectVersionID,
JobID: opts.ImportJobID,
DefaultSourceScheme: database.ParameterSourceSchemeData,
DefaultSourceValue: sql.NullString{
String: sourceValue,
@@ -73,10 +76,10 @@ func TestCompute(t *testing.T) {
t.Parallel()
db := databasefake.New()
scope := generateScope()
parameter, err := db.InsertProjectVersionParameter(context.Background(), database.InsertProjectVersionParameterParams{
ID: uuid.New(),
ProjectVersionID: scope.ProjectVersionID,
Name: "hey",
parameter, err := db.InsertParameterSchema(context.Background(), database.InsertParameterSchemaParams{
ID: uuid.New(),
JobID: scope.ImportJobID,
Name: "hey",
})
require.NoError(t, err)
@@ -92,7 +95,7 @@ func TestCompute(t *testing.T) {
db := databasefake.New()
scope := generateScope()
parameter := generateProjectParameter(t, db, projectParameterOptions{
ProjectVersionID: scope.ProjectVersionID,
ImportJobID: scope.ImportJobID,
DefaultDestinationScheme: database.ParameterDestinationSchemeProvisionerVariable,
})
values, err := projectparameter.Compute(context.Background(), db, scope)
@@ -101,7 +104,7 @@ func TestCompute(t *testing.T) {
value := values[0]
require.True(t, value.DefaultValue)
require.Equal(t, database.ParameterScopeProject, value.Scope)
require.Equal(t, scope.ProjectID.String(), value.ScopeID)
require.Equal(t, scope.ProjectID.UUID.String(), value.ScopeID)
require.Equal(t, value.Proto.Name, parameter.DefaultDestinationValue.String)
require.Equal(t, value.Proto.DestinationScheme, proto.ParameterDestination_PROVISIONER_VARIABLE)
require.Equal(t, value.Proto.Value, parameter.DefaultSourceValue.String)
@@ -112,7 +115,7 @@ func TestCompute(t *testing.T) {
db := databasefake.New()
scope := generateScope()
parameter := generateProjectParameter(t, db, projectParameterOptions{
ProjectVersionID: scope.ProjectVersionID,
ImportJobID: scope.ImportJobID,
})
_, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{
ID: uuid.New(),
@@ -138,13 +141,13 @@ func TestCompute(t *testing.T) {
db := databasefake.New()
scope := generateScope()
parameter := generateProjectParameter(t, db, projectParameterOptions{
ProjectVersionID: scope.ProjectVersionID,
ImportJobID: scope.ImportJobID,
})
value, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{
ID: uuid.New(),
Name: parameter.Name,
Scope: database.ParameterScopeProject,
ScopeID: scope.ProjectID.String(),
ScopeID: scope.ProjectID.UUID.String(),
SourceScheme: database.ParameterSourceSchemeData,
SourceValue: "nop",
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
@@ -164,7 +167,7 @@ func TestCompute(t *testing.T) {
db := databasefake.New()
scope := generateScope()
parameter := generateProjectParameter(t, db, projectParameterOptions{
ProjectVersionID: scope.ProjectVersionID,
ImportJobID: scope.ImportJobID,
})
_, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{
ID: uuid.New(),
@@ -190,7 +193,7 @@ func TestCompute(t *testing.T) {
scope := generateScope()
parameter := generateProjectParameter(t, db, projectParameterOptions{
AllowOverrideSource: true,
ProjectVersionID: scope.ProjectVersionID,
ImportJobID: scope.ImportJobID,
})
_, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{
ID: uuid.New(),
@@ -209,4 +212,37 @@ func TestCompute(t *testing.T) {
require.Len(t, values, 1)
require.Equal(t, false, values[0].DefaultValue)
})
t.Run("AdditionalOverwriteWorkspace", func(t *testing.T) {
t.Parallel()
db := databasefake.New()
scope := generateScope()
parameter := generateProjectParameter(t, db, projectParameterOptions{
AllowOverrideSource: true,
ImportJobID: scope.ImportJobID,
})
_, err := db.InsertParameterValue(context.Background(), database.InsertParameterValueParams{
ID: uuid.New(),
Name: parameter.Name,
Scope: database.ParameterScopeWorkspace,
ScopeID: scope.WorkspaceID.UUID.String(),
SourceScheme: database.ParameterSourceSchemeData,
SourceValue: "nop",
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
DestinationValue: "projectvalue",
})
require.NoError(t, err)
values, err := projectparameter.Compute(context.Background(), db, scope, database.ParameterValue{
Name: parameter.Name,
Scope: database.ParameterScopeUser,
SourceScheme: database.ParameterSourceSchemeData,
SourceValue: "nop",
DestinationScheme: database.ParameterDestinationSchemeEnvironmentVariable,
DestinationValue: "testing",
})
require.NoError(t, err)
require.Len(t, values, 1)
require.Equal(t, "testing", values[0].Proto.Value)
})
}
+55 -12
View File
@@ -8,6 +8,8 @@ import (
"github.com/go-chi/render"
"github.com/google/uuid"
"github.com/moby/moby/pkg/namesgenerator"
"golang.org/x/xerrors"
"github.com/coder/coder/database"
"github.com/coder/coder/httpapi"
@@ -21,8 +23,15 @@ type Project database.Project
// CreateProjectRequest enables callers to create a new Project.
type CreateProjectRequest struct {
Name string `json:"name" validate:"username,required"`
Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform echo,required"`
Name string `json:"name" validate:"username,required"`
// VersionImportJobID is an in-progress or completed job to use as
// an initial version of the project.
//
// This is required on creation to enable a user-flow of validating
// the project works. There is no reason the data-model cannot support
// empty projects, but it doesn't make sense for users.
VersionImportJobID uuid.UUID `json:"import_job_id" validate:"required"`
}
// Lists all projects the authenticated user has access to.
@@ -76,7 +85,7 @@ func (api *api) projectsByOrganization(rw http.ResponseWriter, r *http.Request)
render.JSON(rw, r, projects)
}
// Creates a new project in an organization.
// Create a new project in an organization.
func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Request) {
var createProject CreateProjectRequest
if !httpapi.Read(rw, r, &createProject) {
@@ -99,25 +108,59 @@ func (api *api) postProjectsByOrganization(rw http.ResponseWriter, r *http.Reque
}
if !errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get project by name: %s", err.Error()),
Message: fmt.Sprintf("get project by name: %s", err),
})
return
}
importJob, err := api.Database.GetProvisionerJobByID(r.Context(), createProject.VersionImportJobID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: "import job does not exist",
})
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get import job by id: %s", err),
})
return
}
project, err := api.Database.InsertProject(r.Context(), database.InsertProjectParams{
ID: uuid.New(),
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
OrganizationID: organization.ID,
Name: createProject.Name,
Provisioner: createProject.Provisioner,
var project Project
err = api.Database.InTx(func(db database.Store) error {
projectVersionID := uuid.New()
dbProject, err := db.InsertProject(r.Context(), database.InsertProjectParams{
ID: uuid.New(),
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
OrganizationID: organization.ID,
Name: createProject.Name,
Provisioner: importJob.Provisioner,
ActiveVersionID: projectVersionID,
})
if err != nil {
return xerrors.Errorf("insert project: %s", err)
}
_, err = db.InsertProjectVersion(r.Context(), database.InsertProjectVersionParams{
ID: projectVersionID,
ProjectID: dbProject.ID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Name: namesgenerator.GetRandomName(1),
ImportJobID: importJob.ID,
})
if err != nil {
return xerrors.Errorf("insert project version: %s", err)
}
project = Project(dbProject)
return nil
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("insert project: %s", err),
Message: err.Error(),
})
return
}
render.Status(r, http.StatusCreated)
render.JSON(rw, r, project)
}
+18 -10
View File
@@ -30,7 +30,8 @@ func TestProjects(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
_ = coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
_ = coderdtest.CreateProject(t, client, user.Organization, job.ID)
projects, err := client.Projects(context.Background(), "")
require.NoError(t, err)
require.Len(t, projects, 1)
@@ -53,7 +54,8 @@ func TestProjectsByOrganization(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
_ = coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
_ = coderdtest.CreateProject(t, client, user.Organization, job.ID)
projects, err := client.Projects(context.Background(), "")
require.NoError(t, err)
require.Len(t, projects, 1)
@@ -66,17 +68,19 @@ func TestPostProjectsByOrganization(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
_ = coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
_ = coderdtest.CreateProject(t, client, user.Organization, job.ID)
})
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)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_, err := client.CreateProject(context.Background(), user.Organization, coderd.CreateProjectRequest{
Name: project.Name,
Provisioner: database.ProvisionerTypeEcho,
Name: project.Name,
VersionImportJobID: job.ID,
})
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
@@ -90,7 +94,8 @@ func TestProjectByOrganization(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_, err := client.Project(context.Background(), user.Organization, project.Name)
require.NoError(t, err)
})
@@ -102,7 +107,8 @@ func TestPostParametersByProject(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_, err := client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{
Name: "somename",
SourceValue: "tomato",
@@ -120,7 +126,8 @@ func TestParametersByProject(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
params, err := client.ProjectParameters(context.Background(), user.Organization, project.Name)
require.NoError(t, err)
require.NotNil(t, params)
@@ -130,7 +137,8 @@ func TestParametersByProject(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_, err := client.CreateProjectParameter(context.Background(), user.Organization, project.Name, coderd.CreateParameterValueRequest{
Name: "example",
SourceValue: "source-value",
+38 -90
View File
@@ -1,10 +1,7 @@
package coderd
import (
"archive/tar"
"bytes"
"database/sql"
"encoding/json"
"errors"
"fmt"
"net/http"
@@ -13,7 +10,6 @@ import (
"github.com/go-chi/render"
"github.com/google/uuid"
"github.com/moby/moby/pkg/namesgenerator"
"golang.org/x/xerrors"
"github.com/coder/coder/database"
"github.com/coder/coder/httpapi"
@@ -22,20 +18,18 @@ import (
// ProjectVersion represents a single version of a project.
type ProjectVersion struct {
ID uuid.UUID `json:"id"`
ProjectID uuid.UUID `json:"project_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Name string `json:"name"`
StorageMethod database.ProjectStorageMethod `json:"storage_method"`
Import ProvisionerJob `json:"import"`
ID uuid.UUID `json:"id"`
ProjectID uuid.UUID `json:"project_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Name string `json:"name"`
ImportJobID uuid.UUID `json:"import_job_id"`
}
// ProjectVersionParameter represents a parameter parsed from project version source on creation.
type ProjectVersionParameter struct {
ID uuid.UUID `json:"id"`
CreatedAt time.Time `json:"created_at"`
ProjectVersionID uuid.UUID `json:"project_version_id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
DefaultSourceScheme database.ParameterSourceScheme `json:"default_source_scheme,omitempty"`
@@ -54,8 +48,7 @@ type ProjectVersionParameter struct {
// CreateProjectVersionRequest enables callers to create a new Project Version.
type CreateProjectVersionRequest struct {
StorageMethod database.ProjectStorageMethod `json:"storage_method" validate:"oneof=inline-archive,required"`
StorageSource []byte `json:"storage_source" validate:"max=1048576,required"`
ImportJobID uuid.UUID `json:"import_job_id" validate:"required"`
}
// Lists versions for a single project.
@@ -74,31 +67,17 @@ func (api *api) projectVersionsByOrganization(rw http.ResponseWriter, r *http.Re
}
apiVersion := make([]ProjectVersion, 0)
for _, version := range version {
job, err := api.Database.GetProvisionerJobByID(r.Context(), version.ImportJobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
})
return
}
apiVersion = append(apiVersion, convertProjectVersion(version, job))
apiVersion = append(apiVersion, convertProjectVersion(version))
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, apiVersion)
}
// Return a single project version by organization and name.
func (api *api) projectVersionByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
func (*api) projectVersionByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
projectVersion := httpmw.ProjectVersionParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), projectVersion.ImportJobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
})
return
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertProjectVersion(projectVersion, job))
render.JSON(rw, r, convertProjectVersion(projectVersion))
}
// Creates a new version of the project. An import job is queued to parse
@@ -109,66 +88,37 @@ func (api *api) postProjectVersionByOrganization(rw http.ResponseWriter, r *http
if !httpapi.Read(rw, r, &createProjectVersion) {
return
}
tarReader := tar.NewReader(bytes.NewReader(createProjectVersion.StorageSource))
_, err := tarReader.Next()
job, err := api.Database.GetProvisionerJobByID(r.Context(), createProjectVersion.ImportJobID)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
Message: "job not found",
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: "the archive must be a tar",
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
})
return
}
apiKey := httpmw.APIKey(r)
project := httpmw.ProjectParam(r)
var provisionerJob database.ProvisionerJob
var projectVersion database.ProjectVersion
err = api.Database.InTx(func(db database.Store) error {
provisionerJobID := uuid.New()
projectVersion, err = api.Database.InsertProjectVersion(r.Context(), database.InsertProjectVersionParams{
ID: uuid.New(),
ProjectID: project.ID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Name: namesgenerator.GetRandomName(1),
StorageMethod: createProjectVersion.StorageMethod,
StorageSource: createProjectVersion.StorageSource,
ImportJobID: provisionerJobID,
})
if err != nil {
return xerrors.Errorf("insert project version: %s", err)
}
input, err := json.Marshal(projectImportJob{
ProjectVersionID: projectVersion.ID,
})
if err != nil {
return xerrors.Errorf("marshal import job: %w", err)
}
provisionerJob, err = db.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{
ID: provisionerJobID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
InitiatorID: apiKey.UserID,
Provisioner: project.Provisioner,
Type: database.ProvisionerJobTypeProjectImport,
Input: input,
})
if err != nil {
return xerrors.Errorf("insert provisioner job: %w", err)
}
return nil
projectVersion, err := api.Database.InsertProjectVersion(r.Context(), database.InsertProjectVersionParams{
ID: uuid.New(),
ProjectID: project.ID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
Name: namesgenerator.GetRandomName(1),
ImportJobID: job.ID,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: err.Error(),
Message: fmt.Sprintf("insert project version: %s", err),
})
return
}
render.Status(r, http.StatusCreated)
render.JSON(rw, r, convertProjectVersion(projectVersion, provisionerJob))
render.JSON(rw, r, convertProjectVersion(projectVersion))
}
func (api *api) projectVersionParametersByOrganizationAndName(rw http.ResponseWriter, r *http.Request) {
@@ -194,10 +144,10 @@ func (api *api) projectVersionParametersByOrganizationAndName(rw http.ResponseWr
return
}
parameters, err := api.Database.GetProjectVersionParametersByVersionID(r.Context(), projectVersion.ID)
parameters, err := api.Database.GetParameterSchemasByJobID(r.Context(), projectVersion.ImportJobID)
if errors.Is(err, sql.ErrNoRows) {
err = nil
parameters = []database.ProjectVersionParameter{}
parameters = []database.ParameterSchema{}
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
@@ -215,23 +165,21 @@ func (api *api) projectVersionParametersByOrganizationAndName(rw http.ResponseWr
render.JSON(rw, r, apiParameters)
}
func convertProjectVersion(version database.ProjectVersion, job database.ProvisionerJob) ProjectVersion {
func convertProjectVersion(version database.ProjectVersion) ProjectVersion {
return ProjectVersion{
ID: version.ID,
ProjectID: version.ProjectID,
CreatedAt: version.CreatedAt,
UpdatedAt: version.UpdatedAt,
Name: version.Name,
StorageMethod: version.StorageMethod,
Import: convertProvisionerJob(job),
ID: version.ID,
ProjectID: version.ProjectID,
CreatedAt: version.CreatedAt,
UpdatedAt: version.UpdatedAt,
Name: version.Name,
ImportJobID: version.ImportJobID,
}
}
func convertProjectParameter(parameter database.ProjectVersionParameter) ProjectVersionParameter {
func convertProjectParameter(parameter database.ParameterSchema) ProjectVersionParameter {
return ProjectVersionParameter{
ID: parameter.ID,
CreatedAt: parameter.CreatedAt,
ProjectVersionID: parameter.ProjectVersionID,
Name: parameter.Name,
Description: parameter.Description,
DefaultSourceScheme: parameter.DefaultSourceScheme,
+22 -40
View File
@@ -10,32 +10,21 @@ 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 TestProjectVersionsByOrganization(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)
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("List", 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)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
versions, err := client.ProjectVersions(context.Background(), user.Organization, project.Name)
require.NoError(t, err)
require.NotNil(t, versions)
require.Len(t, versions, 1)
})
}
@@ -46,9 +35,10 @@ func TestProjectVersionByOrganizationAndName(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)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_, err := client.ProjectVersion(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String())
require.NoError(t, err)
})
}
@@ -58,20 +48,12 @@ func TestPostProjectVersionByOrganization(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("InvalidStorage", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_, err := client.CreateProjectVersion(context.Background(), user.Organization, project.Name, coderd.CreateProjectVersionRequest{
StorageMethod: database.ProjectStorageMethod("invalid"),
StorageSource: []byte{},
ImportJobID: job.ID,
})
require.Error(t, err)
require.NoError(t, err)
})
}
@@ -81,9 +63,9 @@ func TestProjectVersionParametersByOrganizationAndName(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)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String())
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusPreconditionRequired, apiErr.StatusCode())
@@ -94,12 +76,12 @@ func TestProjectVersionParametersByOrganizationAndName(t *testing.T) {
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{
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &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)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
_, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String())
var apiErr *codersdk.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, http.StatusPreconditionFailed, apiErr.StatusCode())
@@ -109,8 +91,7 @@ func TestProjectVersionParametersByOrganizationAndName(t *testing.T) {
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{
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{
Parse: []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
@@ -122,8 +103,9 @@ func TestProjectVersionParametersByOrganizationAndName(t *testing.T) {
}},
Provision: echo.ProvisionComplete,
})
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
params, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, version.Name)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
params, err := client.ProjectVersionParameters(context.Background(), user.Organization, project.Name, project.ActiveVersionID.String())
require.NoError(t, err)
require.Len(t, params, 1)
})
+60 -29
View File
@@ -106,11 +106,17 @@ func (api *api) provisionerDaemonsServe(rw http.ResponseWriter, r *http.Request)
// The input for a "workspace_provision" job.
type workspaceProvisionJob struct {
WorkspaceHistoryID uuid.UUID `json:"workspace_history_id"`
DryRun bool `json:"dry_run"`
}
// The input for a "project_import" job.
type projectImportJob struct {
ProjectVersionID uuid.UUID `json:"project_version_id"`
type projectVersionImportJob struct {
OrganizationID string `json:"organization_id"`
ProjectID uuid.UUID `json:"project_id"`
AdditionalParameters []database.ParameterValue `json:"parameters"`
SkipParameterSchemas bool `json:"skip_parameter_schemas"`
SkipResources bool `json:"skip_resources"`
}
// Implementation of the provisioner daemon protobuf server.
@@ -176,7 +182,6 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
Provisioner: string(job.Provisioner),
UserName: user.Username,
}
var projectVersion database.ProjectVersion
switch job.Type {
case database.ProvisionerJobTypeWorkspaceProvision:
var input workspaceProvisionJob
@@ -192,7 +197,7 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
if err != nil {
return nil, failJob(fmt.Sprintf("get workspace: %s", err))
}
projectVersion, err = server.Database.GetProjectVersionByID(ctx, workspaceHistory.ProjectVersionID)
projectVersion, err := server.Database.GetProjectVersionByID(ctx, workspaceHistory.ProjectVersionID)
if err != nil {
return nil, failJob(fmt.Sprintf("get project version: %s", err))
}
@@ -207,9 +212,12 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
// Compute parameters for the workspace to consume.
parameters, err := projectparameter.Compute(ctx, server.Database, projectparameter.Scope{
OrganizationID: organization.ID,
ProjectID: project.ID,
ProjectVersionID: projectVersion.ID,
ImportJobID: projectVersion.ImportJobID,
OrganizationID: organization.ID,
ProjectID: uuid.NullUUID{
UUID: project.ID,
Valid: true,
},
UserID: sql.NullString{
String: user.ID,
Valid: true,
@@ -236,29 +244,52 @@ func (server *provisionerdServer) AcquireJob(ctx context.Context, _ *proto.Empty
ParameterValues: protoParameters,
},
}
case database.ProvisionerJobTypeProjectImport:
var input projectImportJob
case database.ProvisionerJobTypeProjectVersionImport:
var input projectVersionImportJob
err = json.Unmarshal(job.Input, &input)
if err != nil {
return nil, failJob(fmt.Sprintf("unmarshal job input %q: %s", job.Input, err))
}
projectVersion, err = server.Database.GetProjectVersionByID(ctx, input.ProjectVersionID)
// Compute parameters for the workspace to consume.
parameters, err := projectparameter.Compute(ctx, server.Database, projectparameter.Scope{
ImportJobID: job.ID,
OrganizationID: input.OrganizationID,
ProjectID: uuid.NullUUID{
UUID: input.ProjectID,
Valid: input.ProjectID.String() != uuid.Nil.String(),
},
UserID: sql.NullString{
String: user.ID,
Valid: true,
},
}, input.AdditionalParameters...)
if err != nil {
return nil, failJob(fmt.Sprintf("get project version: %s", err))
return nil, failJob(fmt.Sprintf("compute parameters: %s", err))
}
// Convert parameters to the protobuf type.
protoParameters := make([]*sdkproto.ParameterValue, 0, len(parameters))
for _, parameter := range parameters {
protoParameters = append(protoParameters, parameter.Proto)
}
protoJob.Type = &proto.AcquiredJob_ProjectImport_{
ProjectImport: &proto.AcquiredJob_ProjectImport{
// This will be replaced once the project import has been refactored.
ProjectName: "placeholder",
ParameterValues: protoParameters,
SkipParameterSchemas: input.SkipParameterSchemas,
SkipResources: input.SkipResources,
},
}
}
switch projectVersion.StorageMethod {
case database.ProjectStorageMethodInlineArchive:
protoJob.ProjectSourceArchive = projectVersion.StorageSource
switch job.StorageMethod {
case database.ProvisionerStorageMethodFile:
file, err := server.Database.GetFileByHash(ctx, job.StorageSource)
if err != nil {
return nil, failJob(fmt.Sprintf("get file by hash: %s", err))
}
protoJob.ProjectSourceArchive = file.Data
default:
return nil, failJob(fmt.Sprintf("unsupported storage source: %q", projectVersion.StorageMethod))
return nil, failJob(fmt.Sprintf("unsupported storage method: %s", job.StorageMethod))
}
return protoJob, err
@@ -374,7 +405,7 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr
switch jobType := completed.Type.(type) {
case *proto.CompletedJob_ProjectImport_:
var input projectImportJob
var input projectVersionImportJob
err = json.Unmarshal(job.Input, &input)
if err != nil {
return nil, xerrors.Errorf("unmarshal job data: %w", err)
@@ -382,17 +413,17 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr
// Validate that all parameters send from the provisioner daemon
// follow the protocol.
projectVersionParameters := make([]database.InsertProjectVersionParameterParams, 0, len(jobType.ProjectImport.ParameterSchemas))
parameterSchemas := make([]database.InsertParameterSchemaParams, 0, len(jobType.ProjectImport.ParameterSchemas))
for _, protoParameter := range jobType.ProjectImport.ParameterSchemas {
validationTypeSystem, err := convertValidationTypeSystem(protoParameter.ValidationTypeSystem)
if err != nil {
return nil, xerrors.Errorf("convert validation type system for %q: %w", protoParameter.Name, err)
}
projectParameter := database.InsertProjectVersionParameterParams{
parameterSchema := database.InsertParameterSchemaParams{
ID: uuid.New(),
CreatedAt: database.Now(),
ProjectVersionID: input.ProjectVersionID,
JobID: job.ID,
Name: protoParameter.Name,
Description: protoParameter.Description,
RedisplayValue: protoParameter.RedisplayValue,
@@ -414,8 +445,8 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr
if err != nil {
return nil, xerrors.Errorf("convert parameter source scheme: %w", err)
}
projectParameter.DefaultSourceScheme = parameterSourceScheme
projectParameter.DefaultSourceValue = sql.NullString{
parameterSchema.DefaultSourceScheme = parameterSourceScheme
parameterSchema.DefaultSourceValue = sql.NullString{
String: protoParameter.DefaultSource.Value,
Valid: protoParameter.DefaultSource.Value != "",
}
@@ -427,14 +458,14 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr
if err != nil {
return nil, xerrors.Errorf("convert parameter destination scheme: %w", err)
}
projectParameter.DefaultDestinationScheme = parameterDestinationScheme
projectParameter.DefaultDestinationValue = sql.NullString{
parameterSchema.DefaultDestinationScheme = parameterDestinationScheme
parameterSchema.DefaultDestinationValue = sql.NullString{
String: protoParameter.DefaultDestination.Value,
Valid: protoParameter.DefaultDestination.Value != "",
}
}
projectVersionParameters = append(projectVersionParameters, projectParameter)
parameterSchemas = append(parameterSchemas, parameterSchema)
}
// This must occur in a transaction in case of failure.
@@ -452,10 +483,10 @@ func (server *provisionerdServer) CompleteJob(ctx context.Context, completed *pr
}
// This could be a bulk-insert operation to improve performance.
// See the "InsertWorkspaceHistoryLogs" query.
for _, projectParameter := range projectVersionParameters {
_, err = db.InsertProjectVersionParameter(ctx, projectParameter)
for _, parameterSchema := range parameterSchemas {
_, err = db.InsertParameterSchema(ctx, parameterSchema)
if err != nil {
return xerrors.Errorf("insert project parameter %q: %w", projectParameter.Name, err)
return xerrors.Errorf("insert parameter schema %q: %w", parameterSchema.Name, err)
}
}
server.Logger.Debug(ctx, "marked import job as completed", slog.F("job_id", jobID))
+17 -18
View File
@@ -21,8 +21,7 @@ func TestProvisionerJobLogsByName(t *testing.T) {
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{
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@@ -37,17 +36,17 @@ func TestProvisionerJobLogsByName(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "", workspace.Name, history.Name)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, history.ProvisionJobID)
// Return the log after completion!
logs, err := client.ProvisionerJobLogs(context.Background(), history.Provision.ID)
logs, err := client.ProvisionerJobLogs(context.Background(), user.Organization, history.ProvisionJobID)
require.NoError(t, err)
require.NotNil(t, logs)
require.Len(t, logs, 1)
@@ -58,8 +57,7 @@ func TestProvisionerJobLogsByName(t *testing.T) {
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{
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@@ -74,17 +72,18 @@ func TestProvisionerJobLogsByName(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
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,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "", workspace.Name, history.Name)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, history.ProvisionJobID)
logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), history.Provision.ID, before)
logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), user.Organization, history.ProvisionJobID, before)
require.NoError(t, err)
log, ok := <-logs
require.True(t, ok)
@@ -99,8 +98,7 @@ func TestProvisionerJobLogsByName(t *testing.T) {
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{
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{
Parse: echo.ParseComplete,
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Log{
@@ -115,15 +113,16 @@ func TestProvisionerJobLogsByName(t *testing.T) {
},
}},
})
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
before := database.Now()
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), history.Provision.ID, before)
logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), user.Organization, history.ProvisionJobID, before)
require.NoError(t, err)
log := <-logs
require.Equal(t, "log-output", log.Output)
@@ -1,12 +1,19 @@
package coderd
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"github.com/go-chi/render"
"github.com/google/uuid"
"github.com/coder/coder/database"
"github.com/coder/coder/httpapi"
"github.com/coder/coder/httpmw"
)
type ProvisionerJobStatus string
@@ -37,6 +44,80 @@ type ProvisionerJob struct {
WorkerID *uuid.UUID `json:"worker_id,omitempty"`
}
type CreateProjectImportJobRequest struct {
StorageMethod database.ProvisionerStorageMethod `json:"storage_method" validate:"oneof=file,required"`
StorageSource string `json:"storage_source" validate:"required"`
Provisioner database.ProvisionerType `json:"provisioner" validate:"oneof=terraform echo,required"`
AdditionalParameters []ParameterValue `json:"parameter_values"`
SkipParameterSchemas bool `json:"skip_parameter_schemas"`
SkipResources bool `json:"skip_resources"`
}
func (*api) provisionerJobByOrganization(rw http.ResponseWriter, r *http.Request) {
job := httpmw.ProvisionerJobParam(r)
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertProvisionerJob(job))
}
func (api *api) postProvisionerImportJobByOrganization(rw http.ResponseWriter, r *http.Request) {
apiKey := httpmw.APIKey(r)
organization := httpmw.OrganizationParam(r)
var req CreateProjectImportJobRequest
if !httpapi.Read(rw, r, &req) {
return
}
file, err := api.Database.GetFileByHash(r.Context(), req.StorageSource)
if errors.Is(err, sql.ErrNoRows) {
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
Message: "file not found",
})
return
}
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get file: %s", err),
})
return
}
input, err := json.Marshal(projectVersionImportJob{
// AdditionalParameters: req.AdditionalParameters,
OrganizationID: organization.ID,
SkipParameterSchemas: req.SkipParameterSchemas,
SkipResources: req.SkipResources,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("marshal job: %s", err),
})
return
}
job, err := api.Database.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{
ID: uuid.New(),
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
OrganizationID: organization.ID,
InitiatorID: apiKey.UserID,
Provisioner: req.Provisioner,
StorageMethod: database.ProvisionerStorageMethodFile,
StorageSource: file.Hash,
Type: database.ProvisionerJobTypeProjectVersionImport,
Input: input,
})
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("insert provisioner job: %s", err),
})
return
}
render.Status(r, http.StatusCreated)
render.JSON(rw, r, convertProvisionerJob(job))
}
func convertProvisionerJob(provisionerJob database.ProvisionerJob) ProvisionerJob {
job := ProvisionerJob{
ID: provisionerJob.ID,
+52
View File
@@ -0,0 +1,52 @@
package coderd_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/coder/coder/coderd/coderdtest"
"github.com/coder/coder/provisioner/echo"
"github.com/coder/coder/provisionersdk/proto"
)
func TestPostProvisionerImportJobByOrganization(t *testing.T) {
t.Parallel()
t.Run("Create", func(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
_ = coderdtest.NewProvisionerDaemon(t, client)
before := time.Now()
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{
Parse: []*proto.Parse_Response{{
Type: &proto.Parse_Response_Complete{
Complete: &proto.Parse_Complete{
ParameterSchemas: []*proto.ParameterSchema{},
},
},
}},
Provision: []*proto.Provision_Response{{
Type: &proto.Provision_Response_Complete{
Complete: &proto.Provision_Complete{
Resources: []*proto.Resource{{
Name: "dev",
Type: "ec2_instance",
}},
},
},
}},
})
logs, err := client.FollowProvisionerJobLogsAfter(context.Background(), user.Organization, job.ID, before)
require.NoError(t, err)
for {
log, ok := <-logs
if !ok {
break
}
t.Log(log.Output)
}
})
}
+18 -31
View File
@@ -31,7 +31,7 @@ type WorkspaceHistory struct {
Name string `json:"name"`
Transition database.WorkspaceTransition `json:"transition"`
Initiator string `json:"initiator"`
Provision ProvisionerJob `json:"provision"`
ProvisionJobID uuid.UUID `json:"provision_job_id"`
}
// CreateWorkspaceHistoryRequest provides options to update the latest workspace history.
@@ -121,7 +121,6 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
return
}
var provisionerJob database.ProvisionerJob
var workspaceHistory database.WorkspaceHistory
// This must happen in a transaction to ensure history can be inserted, and
// the prior history can update it's "after" column to point at the new.
@@ -150,14 +149,17 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
return xerrors.Errorf("marshal provision job: %w", err)
}
provisionerJob, err = db.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{
ID: provisionerJobID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
InitiatorID: user.ID,
Provisioner: project.Provisioner,
Type: database.ProvisionerJobTypeWorkspaceProvision,
Input: input,
_, err = db.InsertProvisionerJob(r.Context(), database.InsertProvisionerJobParams{
ID: provisionerJobID,
CreatedAt: database.Now(),
UpdatedAt: database.Now(),
InitiatorID: user.ID,
OrganizationID: project.OrganizationID,
Provisioner: project.Provisioner,
Type: database.ProvisionerJobTypeWorkspaceProvision,
StorageMethod: projectVersionJob.StorageMethod,
StorageSource: projectVersionJob.StorageSource,
Input: input,
})
if err != nil {
return xerrors.Errorf("insert provisioner job: %w", err)
@@ -189,7 +191,7 @@ func (api *api) postWorkspaceHistoryByUser(rw http.ResponseWriter, r *http.Reque
}
render.Status(r, http.StatusCreated)
render.JSON(rw, r, convertWorkspaceHistory(workspaceHistory, provisionerJob))
render.JSON(rw, r, convertWorkspaceHistory(workspaceHistory))
}
// Returns all workspace history. This is not sorted. Use before/after to chronologically sort.
@@ -210,36 +212,21 @@ func (api *api) workspaceHistoryByUser(rw http.ResponseWriter, r *http.Request)
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{
Message: fmt.Sprintf("get provisioner job: %s", err),
})
return
}
apiHistory = append(apiHistory, convertWorkspaceHistory(history, job))
apiHistory = append(apiHistory, convertWorkspaceHistory(history))
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, apiHistory)
}
func (api *api) workspaceHistoryByName(rw http.ResponseWriter, r *http.Request) {
func (*api) workspaceHistoryByName(rw http.ResponseWriter, r *http.Request) {
workspaceHistory := httpmw.WorkspaceHistoryParam(r)
job, err := api.Database.GetProvisionerJobByID(r.Context(), workspaceHistory.ProvisionJobID)
if err != nil {
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
Message: fmt.Sprintf("get provisioner job: %s", err),
})
return
}
render.Status(r, http.StatusOK)
render.JSON(rw, r, convertWorkspaceHistory(workspaceHistory, job))
render.JSON(rw, r, convertWorkspaceHistory(workspaceHistory))
}
// Converts the internal history representation to a public external-facing model.
func convertWorkspaceHistory(workspaceHistory database.WorkspaceHistory, provisionerJob database.ProvisionerJob) WorkspaceHistory {
func convertWorkspaceHistory(workspaceHistory database.WorkspaceHistory) WorkspaceHistory {
//nolint:unconvert
return WorkspaceHistory(WorkspaceHistory{
ID: workspaceHistory.ID,
@@ -252,6 +239,6 @@ func convertWorkspaceHistory(workspaceHistory database.WorkspaceHistory, provisi
Name: workspaceHistory.Name,
Transition: workspaceHistory.Transition,
Initiator: workspaceHistory.Initiator,
Provision: convertProvisionerJob(provisionerJob),
ProvisionJobID: workspaceHistory.ProvisionJobID,
})
}
+27 -25
View File
@@ -22,7 +22,8 @@ func TestPostWorkspaceHistoryByUser(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: uuid.New(),
@@ -39,14 +40,14 @@ func TestPostWorkspaceHistoryByUser(t *testing.T) {
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{
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, &echo.Responses{
Provision: []*proto.Provision_Response{{}},
})
coderdtest.AwaitProjectVersionImported(t, client, user.Organization, project.Name, version.Name)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.Error(t, err)
@@ -60,19 +61,19 @@ func TestPostWorkspaceHistoryByUser(t *testing.T) {
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
closeDaemon := 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)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
// Close here so workspace history doesn't process!
closeDaemon.Close()
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
_, err = client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.Error(t, err)
@@ -86,18 +87,18 @@ func TestPostWorkspaceHistoryByUser(t *testing.T) {
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)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
firstHistory, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
coderdtest.AwaitWorkspaceHistoryProvisioned(t, client, "me", workspace.Name, firstHistory.Name)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, firstHistory.ProvisionJobID)
secondHistory, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
@@ -116,7 +117,8 @@ func TestWorkspaceHistoryByUser(t *testing.T) {
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
coderdtest.NewProvisionerDaemon(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
history, err := client.ListWorkspaceHistory(context.Background(), "me", workspace.Name)
require.NoError(t, err)
@@ -129,12 +131,12 @@ func TestWorkspaceHistoryByUser(t *testing.T) {
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)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
_, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
@@ -150,12 +152,12 @@ func TestWorkspaceHistoryByName(t *testing.T) {
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)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
coderdtest.AwaitProvisionerJob(t, client, user.Organization, job.ID)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "me", project.ID)
history, err := client.CreateWorkspaceHistory(context.Background(), "", workspace.Name, coderd.CreateWorkspaceHistoryRequest{
ProjectVersionID: version.ID,
ProjectVersionID: project.ActiveVersionID,
Transition: database.WorkspaceTransitionStart,
})
require.NoError(t, err)
+14 -7
View File
@@ -29,7 +29,8 @@ func TestWorkspaces(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_ = coderdtest.CreateWorkspace(t, client, "", project.ID)
workspaces, err := client.Workspaces(context.Background(), "")
require.NoError(t, err)
@@ -57,7 +58,8 @@ func TestPostWorkspaceByUser(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
anotherUser := coderd.CreateUserRequest{
Email: "another@user.org",
@@ -88,7 +90,8 @@ func TestPostWorkspaceByUser(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
_, err := client.CreateWorkspace(context.Background(), "", coderd.CreateWorkspaceRequest{
ProjectID: project.ID,
@@ -104,7 +107,8 @@ func TestPostWorkspaceByUser(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_ = coderdtest.CreateWorkspace(t, client, "", project.ID)
})
}
@@ -113,7 +117,8 @@ func TestWorkspaceByUser(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
workspace := coderdtest.CreateWorkspace(t, client, "", project.ID)
_, err := client.Workspace(context.Background(), "", workspace.Name)
require.NoError(t, err)
@@ -125,7 +130,8 @@ func TestWorkspacesByProject(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
workspaces, err := client.WorkspacesByProject(context.Background(), user.Organization, project.Name)
require.NoError(t, err)
require.NotNil(t, workspaces)
@@ -135,7 +141,8 @@ func TestWorkspacesByProject(t *testing.T) {
t.Parallel()
client := coderdtest.New(t)
user := coderdtest.CreateInitialUser(t, client)
project := coderdtest.CreateProject(t, client, user.Organization)
job := coderdtest.CreateProjectImportProvisionerJob(t, client, user.Organization, nil)
project := coderdtest.CreateProject(t, client, user.Organization, job.ID)
_ = coderdtest.CreateWorkspace(t, client, "", project.ID)
workspaces, err := client.WorkspacesByProject(context.Background(), user.Organization, project.Name)
require.NoError(t, err)