Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Feb 8, 2022
Prev Previous commit
Next Next commit
Update API to use jobs first
  • Loading branch information
kylecarbs committed Feb 8, 2022
commit aa2c8520e17146698dce8cf7bf7765f06a0287f8
4 changes: 2 additions & 2 deletions coderd/coderd.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ func New(options *Options) http.Handler {
httpmw.ExtractAPIKey(options.Database, nil),
httpmw.ExtractOrganizationParam(options.Database),
)
r.Post("/", api.postProvisionerJobsByOrganization)
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)
r.Get("/resources", api.provisionerJobResourcesByOrganization)
})
})
})
Expand Down
59 changes: 26 additions & 33 deletions coderd/coderdtest/coderdtest.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,39 +122,44 @@ func CreateInitialUser(t *testing.T, client *codersdk.Client) coderd.CreateIniti
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,
// 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 project
return job
}

// 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{
StorageSource: data,
// 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, job uuid.UUID) coderd.Project {
project, err := client.CreateProject(context.Background(), organization, coderd.CreateProjectRequest{
Name: randomUsername(),
VersionImportJobID: job,
})
require.NoError(t, err)
return version
return project
}

// 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.
Expand All @@ -168,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), "_", "-")
}
10 changes: 5 additions & 5 deletions coderd/coderdtest/coderdtest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
2 changes: 1 addition & 1 deletion coderd/files_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func TestPostFiles(t *testing.T) {
client := coderdtest.New(t)
_ = coderdtest.CreateInitialUser(t, client)
_, err := client.UploadFile(context.Background(), "bad", []byte{'a'})
require.NoError(t, err)
require.Error(t, err)
})

t.Run("MassiveBody", func(t *testing.T) {
Expand Down
67 changes: 55 additions & 12 deletions coderd/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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.
Expand Down Expand Up @@ -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) {
Expand All @@ -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)
}
Expand Down
28 changes: 18 additions & 10 deletions coderd/projects_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
})
Expand All @@ -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",
Expand All @@ -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)
Expand All @@ -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",
Expand Down
Loading