2022-03-07 17:40:54 +00:00
package coderd
import (
2022-03-22 19:17:50 +00:00
"database/sql"
2022-05-16 19:36:27 +00:00
"encoding/json"
"errors"
2022-03-07 17:40:54 +00:00
"fmt"
"net/http"
2022-06-10 16:08:50 +00:00
"strconv"
2022-03-07 17:40:54 +00:00
2022-05-16 19:36:27 +00:00
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/moby/moby/pkg/namesgenerator"
"golang.org/x/xerrors"
2022-03-25 21:07:45 +00:00
"github.com/coder/coder/coderd/database"
"github.com/coder/coder/coderd/httpapi"
"github.com/coder/coder/coderd/httpmw"
2022-05-18 23:15:19 +00:00
"github.com/coder/coder/coderd/rbac"
2022-03-22 19:17:50 +00:00
"github.com/coder/coder/codersdk"
2022-03-07 17:40:54 +00:00
)
2022-05-26 03:14:08 +00:00
func ( api * API ) workspaceBuild ( rw http . ResponseWriter , r * http . Request ) {
2022-03-07 17:40:54 +00:00
workspaceBuild := httpmw . WorkspaceBuildParam ( r )
2022-06-01 23:49:43 +00:00
workspace := httpmw . WorkspaceParam ( r )
2022-05-18 23:15:19 +00:00
2022-06-14 15:14:05 +00:00
if ! api . Authorize ( r , rbac . ActionRead , rbac . ResourceWorkspace .
2022-05-18 23:15:19 +00:00
InOrg ( workspace . OrganizationID ) . WithOwner ( workspace . OwnerID . String ( ) ) . WithID ( workspace . ID . String ( ) ) ) {
2022-06-14 15:14:05 +00:00
httpapi . ResourceNotFound ( rw )
2022-05-18 23:15:19 +00:00
return
}
2022-03-07 17:40:54 +00:00
job , err := api . Database . GetProvisionerJobByID ( r . Context ( ) , workspaceBuild . JobID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching provisioner job." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-03-07 17:40:54 +00:00
} )
return
}
2022-04-12 15:17:33 +00:00
2022-06-09 01:23:35 +00:00
users , err := api . Database . GetUsersByIDs ( r . Context ( ) , [ ] uuid . UUID { workspace . OwnerID , workspaceBuild . InitiatorID } )
2022-06-07 14:28:47 +00:00
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:52:44 +00:00
Message : "Internal error fetching user." ,
2022-06-07 14:28:47 +00:00
Detail : err . Error ( ) ,
} )
return
}
2022-06-09 01:23:35 +00:00
httpapi . Write ( rw , http . StatusOK ,
convertWorkspaceBuild ( findUser ( workspace . OwnerID , users ) , findUser ( workspaceBuild . InitiatorID , users ) ,
workspace , workspaceBuild , job ) )
2022-03-07 17:40:54 +00:00
}
2022-05-26 03:14:08 +00:00
func ( api * API ) workspaceBuilds ( rw http . ResponseWriter , r * http . Request ) {
2022-05-16 19:36:27 +00:00
workspace := httpmw . WorkspaceParam ( r )
2022-06-14 15:14:05 +00:00
if ! api . Authorize ( r , rbac . ActionRead , workspace ) {
httpapi . ResourceNotFound ( rw )
2022-05-18 23:15:19 +00:00
return
}
2022-05-18 16:33:33 +00:00
paginationParams , ok := parsePagination ( rw , r )
if ! ok {
return
}
2022-06-02 14:01:45 +00:00
var builds [ ] database . WorkspaceBuild
// Ensure all db calls happen in the same tx
err := api . Database . InTx ( func ( store database . Store ) error {
var err error
if paginationParams . AfterID != uuid . Nil {
// See if the record exists first. If the record does not exist, the pagination
// query will not work.
_ , err := store . GetWorkspaceBuildByID ( r . Context ( ) , paginationParams . AfterID )
if err != nil && xerrors . Is ( err , sql . ErrNoRows ) {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusBadRequest , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : fmt . Sprintf ( "Record at \"after_id\" (%q) does not exist." , paginationParams . AfterID . String ( ) ) ,
2022-06-02 14:01:45 +00:00
} )
return err
} else if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching workspace build at \"after_id\"." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-06-02 14:01:45 +00:00
} )
return err
}
}
req := database . GetWorkspaceBuildByWorkspaceIDParams {
WorkspaceID : workspace . ID ,
AfterID : paginationParams . AfterID ,
OffsetOpt : int32 ( paginationParams . Offset ) ,
LimitOpt : int32 ( paginationParams . Limit ) ,
}
builds , err = store . GetWorkspaceBuildByWorkspaceID ( r . Context ( ) , req )
if xerrors . Is ( err , sql . ErrNoRows ) {
err = nil
}
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching workspace build." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-06-02 14:01:45 +00:00
} )
return err
}
return nil
} )
2022-05-16 19:36:27 +00:00
if err != nil {
return
}
2022-06-02 14:01:45 +00:00
2022-05-16 19:36:27 +00:00
jobIDs := make ( [ ] uuid . UUID , 0 , len ( builds ) )
2022-06-07 14:28:47 +00:00
for _ , build := range builds {
jobIDs = append ( jobIDs , build . JobID )
2022-05-16 19:36:27 +00:00
}
jobs , err := api . Database . GetProvisionerJobsByIDs ( r . Context ( ) , jobIDs )
if errors . Is ( err , sql . ErrNoRows ) {
err = nil
}
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching provisioner jobs." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
jobByID := map [ string ] database . ProvisionerJob { }
for _ , job := range jobs {
jobByID [ job . ID . String ( ) ] = job
}
2022-06-09 01:23:35 +00:00
userIDs := [ ] uuid . UUID { workspace . OwnerID }
for _ , build := range builds {
userIDs = append ( userIDs , build . InitiatorID )
}
users , err := api . Database . GetUsersByIDs ( r . Context ( ) , userIDs )
2022-06-07 14:28:47 +00:00
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:52:44 +00:00
Message : "Internal error fetching user." ,
2022-06-07 14:28:47 +00:00
Detail : err . Error ( ) ,
} )
return
}
2022-05-16 19:36:27 +00:00
apiBuilds := make ( [ ] codersdk . WorkspaceBuild , 0 )
for _ , build := range builds {
job , exists := jobByID [ build . JobID . String ( ) ]
if ! exists {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : fmt . Sprintf ( "Job %q doesn't exist for build %q." , build . JobID , build . ID ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
2022-06-09 01:23:35 +00:00
apiBuilds = append ( apiBuilds ,
convertWorkspaceBuild ( findUser ( workspace . OwnerID , users ) , findUser ( build . InitiatorID , users ) ,
workspace , build , job ) )
2022-05-16 19:36:27 +00:00
}
httpapi . Write ( rw , http . StatusOK , apiBuilds )
}
2022-06-10 16:08:50 +00:00
func ( api * API ) workspaceBuildByBuildNumber ( rw http . ResponseWriter , r * http . Request ) {
owner := httpmw . UserParam ( r )
workspaceName := chi . URLParam ( r , "workspacename" )
buildNumber , err := strconv . ParseInt ( chi . URLParam ( r , "buildnumber" ) , 10 , 32 )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusBadRequest , codersdk . Response {
2022-06-10 16:08:50 +00:00
Message : "Failed to parse build number as integer." ,
Detail : err . Error ( ) ,
} )
return
}
workspace , err := api . Database . GetWorkspaceByOwnerIDAndName ( r . Context ( ) , database . GetWorkspaceByOwnerIDAndNameParams {
OwnerID : owner . ID ,
Name : workspaceName ,
} )
if errors . Is ( err , sql . ErrNoRows ) {
2022-06-14 15:14:05 +00:00
httpapi . ResourceNotFound ( rw )
2022-06-10 16:08:50 +00:00
return
}
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-10 16:08:50 +00:00
Message : "Internal error fetching workspace by name." ,
Detail : err . Error ( ) ,
} )
return
}
2022-06-14 15:14:05 +00:00
if ! api . Authorize ( r , rbac . ActionRead , workspace ) {
httpapi . ResourceNotFound ( rw )
2022-06-10 16:08:50 +00:00
return
}
workspaceBuild , err := api . Database . GetWorkspaceBuildByWorkspaceIDAndBuildNumber ( r . Context ( ) , database . GetWorkspaceBuildByWorkspaceIDAndBuildNumberParams {
WorkspaceID : workspace . ID ,
BuildNumber : int32 ( buildNumber ) ,
} )
if errors . Is ( err , sql . ErrNoRows ) {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusNotFound , codersdk . Response {
2022-06-10 16:08:50 +00:00
Message : fmt . Sprintf ( "Workspace %q Build %d does not exist." , workspaceName , buildNumber ) ,
} )
return
}
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-10 16:08:50 +00:00
Message : "Internal error fetching workspace build." ,
Detail : err . Error ( ) ,
} )
return
}
job , err := api . Database . GetProvisionerJobByID ( r . Context ( ) , workspaceBuild . JobID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-10 16:08:50 +00:00
Message : "Internal error fetching provisioner job." ,
Detail : err . Error ( ) ,
} )
return
}
users , err := api . Database . GetUsersByIDs ( r . Context ( ) , [ ] uuid . UUID { workspace . OwnerID , workspaceBuild . InitiatorID } )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-10 16:08:50 +00:00
Message : "Internal error fetching user." ,
Detail : err . Error ( ) ,
} )
return
}
httpapi . Write ( rw , http . StatusOK ,
convertWorkspaceBuild ( findUser ( workspace . OwnerID , users ) , findUser ( workspaceBuild . InitiatorID , users ) ,
workspace , workspaceBuild , job ) )
}
2022-05-26 03:14:08 +00:00
func ( api * API ) workspaceBuildByName ( rw http . ResponseWriter , r * http . Request ) {
2022-05-16 19:36:27 +00:00
workspace := httpmw . WorkspaceParam ( r )
2022-06-14 15:14:05 +00:00
workspaceBuildName := chi . URLParam ( r , "workspacebuildname" )
if ! api . Authorize ( r , rbac . ActionRead , rbac . ResourceWorkspace .
2022-05-18 23:15:19 +00:00
InOrg ( workspace . OrganizationID ) . WithOwner ( workspace . OwnerID . String ( ) ) . WithID ( workspace . ID . String ( ) ) ) {
2022-06-14 15:14:05 +00:00
httpapi . ResourceNotFound ( rw )
2022-05-18 23:15:19 +00:00
return
}
2022-05-16 19:36:27 +00:00
workspaceBuild , err := api . Database . GetWorkspaceBuildByWorkspaceIDAndName ( r . Context ( ) , database . GetWorkspaceBuildByWorkspaceIDAndNameParams {
WorkspaceID : workspace . ID ,
Name : workspaceBuildName ,
} )
if errors . Is ( err , sql . ErrNoRows ) {
2022-06-14 15:14:05 +00:00
httpapi . ResourceNotFound ( rw )
2022-05-16 19:36:27 +00:00
return
}
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching workspace build by name." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
job , err := api . Database . GetProvisionerJobByID ( r . Context ( ) , workspaceBuild . JobID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching provisioner job." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
2022-06-09 01:23:35 +00:00
users , err := api . Database . GetUsersByIDs ( r . Context ( ) , [ ] uuid . UUID { workspace . OwnerID , workspaceBuild . InitiatorID } )
2022-06-07 14:28:47 +00:00
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:52:44 +00:00
Message : "Internal error getting user." ,
2022-06-07 14:28:47 +00:00
Detail : err . Error ( ) ,
} )
return
}
2022-05-16 19:36:27 +00:00
2022-06-09 01:23:35 +00:00
httpapi . Write ( rw , http . StatusOK ,
convertWorkspaceBuild ( findUser ( workspace . OwnerID , users ) , findUser ( workspaceBuild . InitiatorID , users ) ,
workspace , workspaceBuild , job ) )
2022-05-16 19:36:27 +00:00
}
2022-05-26 03:14:08 +00:00
func ( api * API ) postWorkspaceBuilds ( rw http . ResponseWriter , r * http . Request ) {
2022-05-16 19:36:27 +00:00
apiKey := httpmw . APIKey ( r )
workspace := httpmw . WorkspaceParam ( r )
var createBuild codersdk . CreateWorkspaceBuildRequest
if ! httpapi . Read ( rw , r , & createBuild ) {
return
}
2022-05-18 23:15:19 +00:00
// Rbac action depends on the transition
var action rbac . Action
switch createBuild . Transition {
2022-05-19 18:04:44 +00:00
case codersdk . WorkspaceTransitionDelete :
2022-05-18 23:15:19 +00:00
action = rbac . ActionDelete
2022-05-19 18:04:44 +00:00
case codersdk . WorkspaceTransitionStart , codersdk . WorkspaceTransitionStop :
2022-05-18 23:15:19 +00:00
action = rbac . ActionUpdate
default :
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : fmt . Sprintf ( "Transition %q not supported." , createBuild . Transition ) ,
2022-05-18 23:15:19 +00:00
} )
return
}
2022-06-14 15:14:05 +00:00
if ! api . Authorize ( r , action , rbac . ResourceWorkspace .
2022-05-18 23:15:19 +00:00
InOrg ( workspace . OrganizationID ) . WithOwner ( workspace . OwnerID . String ( ) ) . WithID ( workspace . ID . String ( ) ) ) {
2022-06-14 15:14:05 +00:00
httpapi . ResourceNotFound ( rw )
2022-05-18 23:15:19 +00:00
return
}
2022-05-16 19:36:27 +00:00
if createBuild . TemplateVersionID == uuid . Nil {
2022-05-18 16:33:33 +00:00
latestBuild , err := api . Database . GetLatestWorkspaceBuildByWorkspaceID ( r . Context ( ) , workspace . ID )
2022-05-16 19:36:27 +00:00
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching the latest workspace build." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
createBuild . TemplateVersionID = latestBuild . TemplateVersionID
}
templateVersion , err := api . Database . GetTemplateVersionByID ( r . Context ( ) , createBuild . TemplateVersionID )
if errors . Is ( err , sql . ErrNoRows ) {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusBadRequest , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Template version not found." ,
2022-07-13 00:15:02 +00:00
Validations : [ ] codersdk . ValidationError { {
2022-05-16 19:36:27 +00:00
Field : "template_version_id" ,
Detail : "template version not found" ,
} } ,
} )
return
}
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching template version." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
templateVersionJob , err := api . Database . GetProvisionerJobByID ( r . Context ( ) , templateVersion . JobID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching provisioner job." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
templateVersionJobStatus := convertProvisionerJob ( templateVersionJob ) . Status
switch templateVersionJobStatus {
case codersdk . ProvisionerJobPending , codersdk . ProvisionerJobRunning :
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusNotAcceptable , codersdk . Response {
2022-05-16 19:36:27 +00:00
Message : fmt . Sprintf ( "The provided template version is %s. Wait for it to complete importing!" , templateVersionJobStatus ) ,
} )
return
case codersdk . ProvisionerJobFailed :
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusPreconditionFailed , codersdk . Response {
2022-05-16 19:36:27 +00:00
Message : fmt . Sprintf ( "The provided template version %q has failed to import: %q. You cannot build workspaces with it!" , templateVersion . Name , templateVersionJob . Error . String ) ,
} )
return
case codersdk . ProvisionerJobCanceled :
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusPreconditionFailed , codersdk . Response {
2022-05-16 19:36:27 +00:00
Message : "The provided template version was canceled during import. You cannot builds workspaces with it!" ,
} )
return
}
template , err := api . Database . GetTemplateByID ( r . Context ( ) , templateVersion . TemplateID . UUID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching template job." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
2022-05-18 16:33:33 +00:00
// Store prior build number to compute new build number
var priorBuildNum int32
priorHistory , err := api . Database . GetLatestWorkspaceBuildByWorkspaceID ( r . Context ( ) , workspace . ID )
2022-05-16 19:36:27 +00:00
if err == nil {
priorJob , err := api . Database . GetProvisionerJobByID ( r . Context ( ) , priorHistory . JobID )
if err == nil && convertProvisionerJob ( priorJob ) . Status . Active ( ) {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusConflict , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "A workspace build is already active." ,
2022-05-16 19:36:27 +00:00
} )
return
}
2022-05-18 16:33:33 +00:00
priorBuildNum = priorHistory . BuildNumber
2022-05-16 19:36:27 +00:00
} else if ! errors . Is ( err , sql . ErrNoRows ) {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching prior workspace build." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
var workspaceBuild database . WorkspaceBuild
var provisionerJob database . ProvisionerJob
// 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.
err = api . Database . InTx ( func ( db database . Store ) error {
2022-06-27 16:19:10 +00:00
existing , err := db . ParameterValues ( r . Context ( ) , database . ParameterValuesParams {
Scopes : [ ] database . ParameterScope { database . ParameterScopeWorkspace } ,
ScopeIds : [ ] uuid . UUID { workspace . ID } ,
} )
if err != nil && ! xerrors . Is ( err , sql . ErrNoRows ) {
return xerrors . Errorf ( "Fetch previous parameters: %w" , err )
}
// Write/Update any new params
now := database . Now ( )
for _ , param := range createBuild . ParameterValues {
for _ , exists := range existing {
// If the param exists, delete the old param before inserting the new one
if exists . Name == param . Name {
err = db . DeleteParameterValueByID ( r . Context ( ) , exists . ID )
if err != nil && ! xerrors . Is ( err , sql . ErrNoRows ) {
return xerrors . Errorf ( "Failed to delete old param %q: %w" , exists . Name , err )
}
}
}
_ , err = db . InsertParameterValue ( r . Context ( ) , database . InsertParameterValueParams {
ID : uuid . New ( ) ,
Name : param . Name ,
CreatedAt : now ,
UpdatedAt : now ,
Scope : database . ParameterScopeWorkspace ,
ScopeID : workspace . ID ,
SourceScheme : database . ParameterSourceScheme ( param . SourceScheme ) ,
SourceValue : param . SourceValue ,
DestinationScheme : database . ParameterDestinationScheme ( param . DestinationScheme ) ,
} )
if err != nil {
return xerrors . Errorf ( "insert parameter value: %w" , err )
}
}
2022-05-16 19:36:27 +00:00
workspaceBuildID := uuid . New ( )
input , err := json . Marshal ( workspaceProvisionJob {
WorkspaceBuildID : workspaceBuildID ,
} )
if err != nil {
return xerrors . Errorf ( "marshal provision job: %w" , err )
}
provisionerJob , err = db . InsertProvisionerJob ( r . Context ( ) , database . InsertProvisionerJobParams {
ID : uuid . New ( ) ,
CreatedAt : database . Now ( ) ,
UpdatedAt : database . Now ( ) ,
InitiatorID : apiKey . UserID ,
OrganizationID : template . OrganizationID ,
Provisioner : template . Provisioner ,
Type : database . ProvisionerJobTypeWorkspaceBuild ,
StorageMethod : templateVersionJob . StorageMethod ,
StorageSource : templateVersionJob . StorageSource ,
Input : input ,
} )
if err != nil {
return xerrors . Errorf ( "insert provisioner job: %w" , err )
}
state := createBuild . ProvisionerState
if len ( state ) == 0 {
state = priorHistory . ProvisionerState
}
workspaceBuild , err = db . InsertWorkspaceBuild ( r . Context ( ) , database . InsertWorkspaceBuildParams {
ID : workspaceBuildID ,
CreatedAt : database . Now ( ) ,
UpdatedAt : database . Now ( ) ,
WorkspaceID : workspace . ID ,
TemplateVersionID : templateVersion . ID ,
2022-05-18 16:33:33 +00:00
BuildNumber : priorBuildNum + 1 ,
2022-05-16 19:36:27 +00:00
Name : namesgenerator . GetRandomName ( 1 ) ,
ProvisionerState : state ,
InitiatorID : apiKey . UserID ,
2022-05-19 18:04:44 +00:00
Transition : database . WorkspaceTransition ( createBuild . Transition ) ,
2022-05-16 19:36:27 +00:00
JobID : provisionerJob . ID ,
2022-06-17 17:41:11 +00:00
Reason : database . BuildReasonInitiator ,
2022-05-16 19:36:27 +00:00
} )
if err != nil {
return xerrors . Errorf ( "insert workspace build: %w" , err )
}
return nil
} )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error inserting workspace build." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
}
2022-06-09 01:23:35 +00:00
users , err := api . Database . GetUsersByIDs ( r . Context ( ) , [ ] uuid . UUID {
workspace . OwnerID ,
workspaceBuild . InitiatorID ,
} )
2022-06-07 14:28:47 +00:00
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:52:44 +00:00
Message : "Internal error getting user." ,
2022-06-07 14:28:47 +00:00
Detail : err . Error ( ) ,
} )
return
}
2022-06-01 23:49:43 +00:00
httpapi . Write ( rw , http . StatusCreated ,
2022-06-09 01:23:35 +00:00
convertWorkspaceBuild ( findUser ( workspace . OwnerID , users ) , findUser ( workspaceBuild . InitiatorID , users ) ,
workspace , workspaceBuild , provisionerJob ) )
2022-05-16 19:36:27 +00:00
}
2022-05-26 03:14:08 +00:00
func ( api * API ) patchCancelWorkspaceBuild ( rw http . ResponseWriter , r * http . Request ) {
2022-03-22 19:17:50 +00:00
workspaceBuild := httpmw . WorkspaceBuildParam ( r )
2022-05-18 23:15:19 +00:00
workspace , err := api . Database . GetWorkspaceByID ( r . Context ( ) , workspaceBuild . WorkspaceID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "No workspace exists for this job." ,
2022-05-18 23:15:19 +00:00
} )
return
}
2022-06-14 15:14:05 +00:00
if ! api . Authorize ( r , rbac . ActionUpdate , rbac . ResourceWorkspace .
2022-05-18 23:15:19 +00:00
InOrg ( workspace . OrganizationID ) . WithOwner ( workspace . OwnerID . String ( ) ) . WithID ( workspace . ID . String ( ) ) ) {
2022-06-14 15:14:05 +00:00
httpapi . ResourceNotFound ( rw )
2022-05-18 23:15:19 +00:00
return
}
2022-03-22 19:17:50 +00:00
job , err := api . Database . GetProvisionerJobByID ( r . Context ( ) , workspaceBuild . JobID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching provisioner job." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-03-22 19:17:50 +00:00
} )
return
}
if job . CompletedAt . Valid {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusPreconditionFailed , codersdk . Response {
2022-03-22 19:17:50 +00:00
Message : "Job has already completed!" ,
} )
return
}
if job . CanceledAt . Valid {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusPreconditionFailed , codersdk . Response {
2022-03-22 19:17:50 +00:00
Message : "Job has already been marked as canceled!" ,
} )
return
}
err = api . Database . UpdateProvisionerJobWithCancelByID ( r . Context ( ) , database . UpdateProvisionerJobWithCancelByIDParams {
ID : job . ID ,
CanceledAt : sql . NullTime {
Time : database . Now ( ) ,
Valid : true ,
} ,
} )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error updating provisioner job." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-03-22 19:17:50 +00:00
} )
return
}
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusOK , codersdk . Response {
2022-03-22 19:17:50 +00:00
Message : "Job has been marked as canceled..." ,
} )
}
2022-05-26 03:14:08 +00:00
func ( api * API ) workspaceBuildResources ( rw http . ResponseWriter , r * http . Request ) {
2022-03-07 17:40:54 +00:00
workspaceBuild := httpmw . WorkspaceBuildParam ( r )
2022-05-18 23:15:19 +00:00
workspace , err := api . Database . GetWorkspaceByID ( r . Context ( ) , workspaceBuild . WorkspaceID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "No workspace exists for this job." ,
2022-05-18 23:15:19 +00:00
} )
return
}
2022-06-14 15:14:05 +00:00
if ! api . Authorize ( r , rbac . ActionRead , rbac . ResourceWorkspace .
2022-05-18 23:15:19 +00:00
InOrg ( workspace . OrganizationID ) . WithOwner ( workspace . OwnerID . String ( ) ) . WithID ( workspace . ID . String ( ) ) ) {
2022-06-14 15:14:05 +00:00
httpapi . ResourceNotFound ( rw )
2022-05-18 23:15:19 +00:00
return
}
2022-03-07 17:40:54 +00:00
job , err := api . Database . GetProvisionerJobByID ( r . Context ( ) , workspaceBuild . JobID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching provisioner job." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-03-07 17:40:54 +00:00
} )
return
}
api . provisionerJobResources ( rw , r , job )
}
2022-05-26 03:14:08 +00:00
func ( api * API ) workspaceBuildLogs ( rw http . ResponseWriter , r * http . Request ) {
2022-03-07 17:40:54 +00:00
workspaceBuild := httpmw . WorkspaceBuildParam ( r )
2022-05-18 23:15:19 +00:00
workspace , err := api . Database . GetWorkspaceByID ( r . Context ( ) , workspaceBuild . WorkspaceID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "No workspace exists for this job." ,
2022-05-18 23:15:19 +00:00
} )
return
}
2022-06-14 15:14:05 +00:00
if ! api . Authorize ( r , rbac . ActionRead , rbac . ResourceWorkspace .
2022-05-18 23:15:19 +00:00
InOrg ( workspace . OrganizationID ) . WithOwner ( workspace . OwnerID . String ( ) ) . WithID ( workspace . ID . String ( ) ) ) {
2022-06-14 15:14:05 +00:00
httpapi . ResourceNotFound ( rw )
2022-05-18 23:15:19 +00:00
return
}
2022-03-07 17:40:54 +00:00
job , err := api . Database . GetProvisionerJobByID ( r . Context ( ) , workspaceBuild . JobID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching provisioner job." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-03-07 17:40:54 +00:00
} )
return
}
api . provisionerJobLogs ( rw , r , job )
}
2022-05-26 03:14:08 +00:00
func ( api * API ) workspaceBuildState ( rw http . ResponseWriter , r * http . Request ) {
2022-05-02 22:51:58 +00:00
workspaceBuild := httpmw . WorkspaceBuildParam ( r )
2022-05-18 23:15:19 +00:00
workspace , err := api . Database . GetWorkspaceByID ( r . Context ( ) , workspaceBuild . WorkspaceID )
if err != nil {
2022-07-13 00:15:02 +00:00
httpapi . Write ( rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "No workspace exists for this job." ,
2022-05-18 23:15:19 +00:00
} )
return
}
2022-06-14 15:14:05 +00:00
if ! api . Authorize ( r , rbac . ActionRead , workspace ) {
httpapi . ResourceNotFound ( rw )
2022-05-18 23:15:19 +00:00
return
}
2022-05-02 22:51:58 +00:00
rw . Header ( ) . Set ( "Content-Type" , "application/json" )
rw . WriteHeader ( http . StatusOK )
_ , _ = rw . Write ( workspaceBuild . ProvisionerState )
}
2022-06-01 23:49:43 +00:00
func convertWorkspaceBuild (
2022-06-09 01:23:35 +00:00
workspaceOwner * database . User ,
buildInitiator * database . User ,
2022-06-01 23:49:43 +00:00
workspace database . Workspace ,
workspaceBuild database . WorkspaceBuild ,
job database . ProvisionerJob ) codersdk . WorkspaceBuild {
2022-03-07 17:40:54 +00:00
//nolint:unconvert
2022-06-01 23:49:43 +00:00
if workspace . ID != workspaceBuild . WorkspaceID {
panic ( "workspace and build do not match" )
}
2022-06-09 01:23:35 +00:00
// Both owner and initiator should always be present. But from a static
// code analysis POV, these could be nil.
ownerName := "unknown"
if workspaceOwner != nil {
ownerName = workspaceOwner . Username
}
initiatorName := "unknown"
if workspaceOwner != nil {
initiatorName = buildInitiator . Username
}
2022-03-22 19:17:50 +00:00
return codersdk . WorkspaceBuild {
2022-06-07 14:28:47 +00:00
ID : workspaceBuild . ID ,
CreatedAt : workspaceBuild . CreatedAt ,
UpdatedAt : workspaceBuild . UpdatedAt ,
WorkspaceOwnerID : workspace . OwnerID ,
2022-06-09 01:23:35 +00:00
WorkspaceOwnerName : ownerName ,
2022-06-07 14:28:47 +00:00
WorkspaceID : workspaceBuild . WorkspaceID ,
WorkspaceName : workspace . Name ,
TemplateVersionID : workspaceBuild . TemplateVersionID ,
BuildNumber : workspaceBuild . BuildNumber ,
Name : workspaceBuild . Name ,
Transition : codersdk . WorkspaceTransition ( workspaceBuild . Transition ) ,
InitiatorID : workspaceBuild . InitiatorID ,
2022-06-09 01:23:35 +00:00
InitiatorUsername : initiatorName ,
2022-06-07 14:28:47 +00:00
Job : convertProvisionerJob ( job ) ,
Deadline : workspaceBuild . Deadline ,
2022-06-17 17:41:11 +00:00
Reason : codersdk . BuildReason ( workspaceBuild . Reason ) ,
2022-03-22 19:17:50 +00:00
}
2022-03-07 17:40:54 +00:00
}
2022-04-11 21:06:15 +00:00
func convertWorkspaceResource ( resource database . WorkspaceResource , agents [ ] codersdk . WorkspaceAgent ) codersdk . WorkspaceResource {
2022-03-22 19:17:50 +00:00
return codersdk . WorkspaceResource {
2022-03-07 17:40:54 +00:00
ID : resource . ID ,
CreatedAt : resource . CreatedAt ,
JobID : resource . JobID ,
2022-05-19 18:04:44 +00:00
Transition : codersdk . WorkspaceTransition ( resource . Transition ) ,
2022-03-07 17:40:54 +00:00
Type : resource . Type ,
Name : resource . Name ,
2022-04-11 21:06:15 +00:00
Agents : agents ,
2022-03-07 17:40:54 +00:00
}
}