mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
feat: add an endpoint to manually pause a coder task (#21889)
Closes https://github.com/coder/internal/issues/1261. This pull request adds an endpoint to pause coder tasks by stopping the underlying workspace. * Instead of `POST /api/v2/tasks/{user}/{task}/pause`, the endpoint is currently experimental. * We do not currently set the build reason to `task_manual_pause`, because build reasons are currently only used on stop transitions.
This commit is contained in:
@@ -1244,3 +1244,63 @@ func (api *API) postWorkspaceAgentTaskLogSnapshot(rw http.ResponseWriter, r *htt
|
|||||||
|
|
||||||
rw.WriteHeader(http.StatusNoContent)
|
rw.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary Pause task
|
||||||
|
// @ID pause-task
|
||||||
|
// @Security CoderSessionToken
|
||||||
|
// @Accept json
|
||||||
|
// @Tags Tasks
|
||||||
|
// @Param user path string true "Username, user ID, or 'me' for the authenticated user"
|
||||||
|
// @Param task path string true "Task ID" format(uuid)
|
||||||
|
// @Success 202 {object} codersdk.PauseTaskResponse
|
||||||
|
// @Router /tasks/{user}/{task}/pause [post]
|
||||||
|
func (api *API) pauseTask(rw http.ResponseWriter, r *http.Request) {
|
||||||
|
var (
|
||||||
|
ctx = r.Context()
|
||||||
|
apiKey = httpmw.APIKey(r)
|
||||||
|
task = httpmw.TaskParam(r)
|
||||||
|
)
|
||||||
|
|
||||||
|
if !task.WorkspaceID.Valid {
|
||||||
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
|
Message: "Task does not have a workspace.",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace, err := api.Database.GetWorkspaceByID(ctx, task.WorkspaceID.UUID)
|
||||||
|
if err != nil {
|
||||||
|
if httpapi.Is404Error(err) {
|
||||||
|
httpapi.ResourceNotFound(rw)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||||
|
Message: "Internal error fetching task workspace.",
|
||||||
|
Detail: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buildReq := codersdk.CreateWorkspaceBuildRequest{
|
||||||
|
Transition: codersdk.WorkspaceTransitionStop,
|
||||||
|
Reason: codersdk.CreateWorkspaceBuildReasonTaskManualPause,
|
||||||
|
}
|
||||||
|
build, err := api.postWorkspaceBuildsInternal(
|
||||||
|
ctx,
|
||||||
|
apiKey,
|
||||||
|
workspace,
|
||||||
|
buildReq,
|
||||||
|
func(action policy.Action, object rbac.Objecter) bool {
|
||||||
|
return api.Authorize(r, action, object)
|
||||||
|
},
|
||||||
|
audit.WorkspaceBuildBaggageFromRequest(r),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
httperror.WriteWorkspaceBuildError(ctx, rw, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
httpapi.Write(ctx, rw, http.StatusAccepted, codersdk.PauseTaskResponse{
|
||||||
|
WorkspaceBuild: &build,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"golang.org/x/xerrors"
|
||||||
|
|
||||||
agentapisdk "github.com/coder/agentapi-sdk-go"
|
agentapisdk "github.com/coder/agentapi-sdk-go"
|
||||||
"github.com/coder/coder/v2/agent"
|
"github.com/coder/coder/v2/agent"
|
||||||
@@ -26,11 +27,14 @@ import (
|
|||||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbfake"
|
"github.com/coder/coder/v2/coderd/database/dbfake"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbgen"
|
"github.com/coder/coder/v2/coderd/database/dbgen"
|
||||||
|
"github.com/coder/coder/v2/coderd/database/dbtestutil"
|
||||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||||
|
"github.com/coder/coder/v2/coderd/database/pubsub"
|
||||||
"github.com/coder/coder/v2/coderd/httpapi"
|
"github.com/coder/coder/v2/coderd/httpapi"
|
||||||
"github.com/coder/coder/v2/coderd/notifications"
|
"github.com/coder/coder/v2/coderd/notifications"
|
||||||
"github.com/coder/coder/v2/coderd/notifications/notificationstest"
|
"github.com/coder/coder/v2/coderd/notifications/notificationstest"
|
||||||
"github.com/coder/coder/v2/coderd/rbac"
|
"github.com/coder/coder/v2/coderd/rbac"
|
||||||
|
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||||
"github.com/coder/coder/v2/coderd/util/slice"
|
"github.com/coder/coder/v2/coderd/util/slice"
|
||||||
"github.com/coder/coder/v2/codersdk"
|
"github.com/coder/coder/v2/codersdk"
|
||||||
"github.com/coder/coder/v2/codersdk/agentsdk"
|
"github.com/coder/coder/v2/codersdk/agentsdk"
|
||||||
@@ -100,6 +104,36 @@ func createTaskInState(db database.Store, ownerSubject rbac.Subject, ownerOrgID,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type aiTaskStoreWrapper struct {
|
||||||
|
database.Store
|
||||||
|
getWorkspaceByID func(ctx context.Context, id uuid.UUID) (database.Workspace, error)
|
||||||
|
insertWorkspaceBuild func(ctx context.Context, arg database.InsertWorkspaceBuildParams) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s aiTaskStoreWrapper) GetWorkspaceByID(ctx context.Context, id uuid.UUID) (database.Workspace, error) {
|
||||||
|
if s.getWorkspaceByID != nil {
|
||||||
|
return s.getWorkspaceByID(ctx, id)
|
||||||
|
}
|
||||||
|
return s.Store.GetWorkspaceByID(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s aiTaskStoreWrapper) InsertWorkspaceBuild(ctx context.Context, arg database.InsertWorkspaceBuildParams) error {
|
||||||
|
if s.insertWorkspaceBuild != nil {
|
||||||
|
return s.insertWorkspaceBuild(ctx, arg)
|
||||||
|
}
|
||||||
|
return s.Store.InsertWorkspaceBuild(ctx, arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s aiTaskStoreWrapper) InTx(fn func(database.Store) error, opts *database.TxOptions) error {
|
||||||
|
return s.Store.InTx(func(tx database.Store) error {
|
||||||
|
return fn(aiTaskStoreWrapper{
|
||||||
|
Store: tx,
|
||||||
|
getWorkspaceByID: s.getWorkspaceByID,
|
||||||
|
insertWorkspaceBuild: s.insertWorkspaceBuild,
|
||||||
|
})
|
||||||
|
}, opts)
|
||||||
|
}
|
||||||
|
|
||||||
func TestTasks(t *testing.T) {
|
func TestTasks(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -2422,3 +2456,328 @@ func TestPostWorkspaceAgentTaskSnapshot(t *testing.T) {
|
|||||||
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
|
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPauseTask(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
setupClient := func(t *testing.T, db database.Store, ps pubsub.Pubsub, authorizer rbac.Authorizer) *codersdk.Client {
|
||||||
|
t.Helper()
|
||||||
|
client, _, _ := coderdtest.NewWithAPI(t, &coderdtest.Options{
|
||||||
|
Database: db,
|
||||||
|
Pubsub: ps,
|
||||||
|
Authorizer: authorizer,
|
||||||
|
})
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
setupWorkspaceTask := func(t *testing.T, db database.Store, user codersdk.CreateFirstUserResponse) (database.Task, uuid.UUID) {
|
||||||
|
t.Helper()
|
||||||
|
workspaceBuild := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
||||||
|
OrganizationID: user.OrganizationID,
|
||||||
|
OwnerID: user.UserID,
|
||||||
|
}).WithTask(database.TaskTable{
|
||||||
|
Prompt: "pause me",
|
||||||
|
}, nil).Do()
|
||||||
|
return workspaceBuild.Task, workspaceBuild.Workspace.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("OK", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitLong)
|
||||||
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
|
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
|
||||||
|
Parse: echo.ParseComplete,
|
||||||
|
ProvisionApply: echo.ApplyComplete,
|
||||||
|
ProvisionGraph: []*proto.Response{
|
||||||
|
{Type: &proto.Response_Graph{Graph: &proto.GraphComplete{
|
||||||
|
HasAiTasks: true,
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
|
||||||
|
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
|
||||||
|
|
||||||
|
task, err := client.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
|
||||||
|
TemplateVersionID: template.ActiveVersionID,
|
||||||
|
Input: "pause me",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, task.WorkspaceID.Valid)
|
||||||
|
|
||||||
|
workspace, err := client.Workspace(ctx, task.WorkspaceID.UUID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID)
|
||||||
|
|
||||||
|
resp, err := client.PauseTask(ctx, codersdk.Me, task.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
build := *resp.WorkspaceBuild
|
||||||
|
require.NotNil(t, build)
|
||||||
|
require.Equal(t, codersdk.WorkspaceTransitionStop, build.Transition)
|
||||||
|
require.Equal(t, task.WorkspaceID.UUID, build.WorkspaceID)
|
||||||
|
require.Equal(t, workspace.LatestBuild.BuildNumber+1, build.BuildNumber)
|
||||||
|
require.Equal(t, string(codersdk.CreateWorkspaceBuildReasonTaskManualPause), string(build.Reason))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Non-owner role access", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
db, ps := dbtestutil.NewDB(t)
|
||||||
|
client := setupClient(t, db, ps, nil)
|
||||||
|
owner := coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
roles []rbac.RoleIdentifier
|
||||||
|
expectedStatus int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "org_member",
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "org_admin",
|
||||||
|
roles: []rbac.RoleIdentifier{rbac.ScopedRoleOrgAdmin(owner.OrganizationID)},
|
||||||
|
expectedStatus: http.StatusAccepted,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sitewide_member",
|
||||||
|
roles: []rbac.RoleIdentifier{rbac.RoleMember()},
|
||||||
|
expectedStatus: http.StatusNotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sitewide_admin",
|
||||||
|
roles: []rbac.RoleIdentifier{rbac.RoleOwner()},
|
||||||
|
expectedStatus: http.StatusAccepted,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
task, _ := setupWorkspaceTask(t, db, owner)
|
||||||
|
userClient, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID, tc.roles...)
|
||||||
|
|
||||||
|
resp, err := userClient.PauseTask(ctx, codersdk.Me, task.ID)
|
||||||
|
if tc.expectedStatus == http.StatusAccepted {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, resp.WorkspaceBuild)
|
||||||
|
require.NotEqual(t, uuid.Nil, resp.WorkspaceBuild.ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, tc.expectedStatus, apiErr.StatusCode())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Task not found", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
|
||||||
|
_ = coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
|
_, err := client.PauseTask(ctx, codersdk.Me, uuid.New())
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Task lookup forbidden", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
db, ps := dbtestutil.NewDB(t)
|
||||||
|
auth := &coderdtest.FakeAuthorizer{
|
||||||
|
ConditionalReturn: func(_ context.Context, _ rbac.Subject, action policy.Action, object rbac.Object) error {
|
||||||
|
if action == policy.ActionRead && object.Type == rbac.ResourceTask.Type {
|
||||||
|
return rbac.UnauthorizedError{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := setupClient(t, db, ps, auth)
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
task, _ := setupWorkspaceTask(t, db, user)
|
||||||
|
|
||||||
|
_, err := client.PauseTask(ctx, codersdk.Me, task.ID)
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Workspace lookup forbidden", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
db, ps := dbtestutil.NewDB(t)
|
||||||
|
auth := &coderdtest.FakeAuthorizer{
|
||||||
|
ConditionalReturn: func(_ context.Context, _ rbac.Subject, action policy.Action, object rbac.Object) error {
|
||||||
|
if action == policy.ActionRead && object.Type == rbac.ResourceWorkspace.Type {
|
||||||
|
return rbac.UnauthorizedError{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := setupClient(t, db, ps, auth)
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
task, _ := setupWorkspaceTask(t, db, user)
|
||||||
|
|
||||||
|
_, err := client.PauseTask(ctx, codersdk.Me, task.ID)
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("No Workspace for Task", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
db, ps := dbtestutil.NewDB(t)
|
||||||
|
client := setupClient(t, db, ps, nil)
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
|
||||||
|
workspaceBuild := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
||||||
|
OrganizationID: user.OrganizationID,
|
||||||
|
OwnerID: user.UserID,
|
||||||
|
}).Do()
|
||||||
|
task := dbgen.Task(t, db, database.TaskTable{
|
||||||
|
OrganizationID: user.OrganizationID,
|
||||||
|
OwnerID: user.UserID,
|
||||||
|
TemplateVersionID: workspaceBuild.Build.TemplateVersionID,
|
||||||
|
Prompt: "no workspace",
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := client.PauseTask(ctx, codersdk.Me, task.ID)
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusInternalServerError, apiErr.StatusCode())
|
||||||
|
require.Equal(t, "Task does not have a workspace.", apiErr.Message)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Workspace not found", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
db, ps := dbtestutil.NewDB(t)
|
||||||
|
var workspaceID uuid.UUID
|
||||||
|
wrapped := aiTaskStoreWrapper{
|
||||||
|
Store: db,
|
||||||
|
getWorkspaceByID: func(ctx context.Context, id uuid.UUID) (database.Workspace, error) {
|
||||||
|
if id == workspaceID && id != uuid.Nil {
|
||||||
|
return database.Workspace{}, sql.ErrNoRows
|
||||||
|
}
|
||||||
|
return db.GetWorkspaceByID(ctx, id)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := setupClient(t, wrapped, ps, nil)
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
task, workspaceIDValue := setupWorkspaceTask(t, db, user)
|
||||||
|
workspaceID = workspaceIDValue
|
||||||
|
|
||||||
|
_, err := client.PauseTask(ctx, codersdk.Me, task.ID)
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusNotFound, apiErr.StatusCode())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Workspace lookup internal error", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
db, ps := dbtestutil.NewDB(t)
|
||||||
|
var workspaceID uuid.UUID
|
||||||
|
wrapped := aiTaskStoreWrapper{
|
||||||
|
Store: db,
|
||||||
|
getWorkspaceByID: func(ctx context.Context, id uuid.UUID) (database.Workspace, error) {
|
||||||
|
if id == workspaceID && id != uuid.Nil {
|
||||||
|
return database.Workspace{}, xerrors.New("boom")
|
||||||
|
}
|
||||||
|
return db.GetWorkspaceByID(ctx, id)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := setupClient(t, wrapped, ps, nil)
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
task, workspaceIDValue := setupWorkspaceTask(t, db, user)
|
||||||
|
workspaceID = workspaceIDValue
|
||||||
|
|
||||||
|
_, err := client.PauseTask(ctx, codersdk.Me, task.ID)
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusInternalServerError, apiErr.StatusCode())
|
||||||
|
require.Equal(t, "Internal error fetching task workspace.", apiErr.Message)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Build Forbidden", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
db, ps := dbtestutil.NewDB(t)
|
||||||
|
auth := &coderdtest.FakeAuthorizer{
|
||||||
|
ConditionalReturn: func(_ context.Context, _ rbac.Subject, action policy.Action, object rbac.Object) error {
|
||||||
|
if action == policy.ActionWorkspaceStop && object.Type == rbac.ResourceWorkspace.Type {
|
||||||
|
return rbac.UnauthorizedError{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := setupClient(t, db, ps, auth)
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
task, _ := setupWorkspaceTask(t, db, user)
|
||||||
|
|
||||||
|
_, err := client.PauseTask(ctx, codersdk.Me, task.ID)
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusForbidden, apiErr.StatusCode())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Job already in progress", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
db, ps := dbtestutil.NewDB(t)
|
||||||
|
client := setupClient(t, db, ps, nil)
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
workspaceBuild := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
||||||
|
OrganizationID: user.OrganizationID,
|
||||||
|
OwnerID: user.UserID,
|
||||||
|
}).
|
||||||
|
WithTask(database.TaskTable{
|
||||||
|
Prompt: "pause me",
|
||||||
|
}, nil).
|
||||||
|
Starting().
|
||||||
|
Do()
|
||||||
|
|
||||||
|
_, err := client.PauseTask(ctx, codersdk.Me, workspaceBuild.Task.ID)
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusConflict, apiErr.StatusCode())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Build Internal Error", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := testutil.Context(t, testutil.WaitShort)
|
||||||
|
db, ps := dbtestutil.NewDB(t)
|
||||||
|
wrapped := aiTaskStoreWrapper{
|
||||||
|
Store: db,
|
||||||
|
insertWorkspaceBuild: func(ctx context.Context, arg database.InsertWorkspaceBuildParams) error {
|
||||||
|
return xerrors.New("insert failed")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client := setupClient(t, wrapped, ps, nil)
|
||||||
|
user := coderdtest.CreateFirstUser(t, client)
|
||||||
|
task, _ := setupWorkspaceTask(t, db, user)
|
||||||
|
|
||||||
|
_, err := client.PauseTask(ctx, codersdk.Me, task.ID)
|
||||||
|
var apiErr *codersdk.Error
|
||||||
|
require.ErrorAs(t, err, &apiErr)
|
||||||
|
require.Equal(t, http.StatusInternalServerError, apiErr.StatusCode())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Generated
+56
-3
@@ -5824,6 +5824,48 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/tasks/{user}/{task}/pause": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Tasks"
|
||||||
|
],
|
||||||
|
"summary": "Pause task",
|
||||||
|
"operationId": "pause-task",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username, user ID, or 'me' for the authenticated user",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Task ID",
|
||||||
|
"name": "task",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"202": {
|
||||||
|
"description": "Accepted",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.PauseTaskResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/tasks/{user}/{task}/send": {
|
"/tasks/{user}/{task}/send": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -14102,14 +14144,16 @@ const docTemplate = `{
|
|||||||
"cli",
|
"cli",
|
||||||
"ssh_connection",
|
"ssh_connection",
|
||||||
"vscode_connection",
|
"vscode_connection",
|
||||||
"jetbrains_connection"
|
"jetbrains_connection",
|
||||||
|
"task_manual_pause"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"CreateWorkspaceBuildReasonDashboard",
|
"CreateWorkspaceBuildReasonDashboard",
|
||||||
"CreateWorkspaceBuildReasonCLI",
|
"CreateWorkspaceBuildReasonCLI",
|
||||||
"CreateWorkspaceBuildReasonSSHConnection",
|
"CreateWorkspaceBuildReasonSSHConnection",
|
||||||
"CreateWorkspaceBuildReasonVSCodeConnection",
|
"CreateWorkspaceBuildReasonVSCodeConnection",
|
||||||
"CreateWorkspaceBuildReasonJetbrainsConnection"
|
"CreateWorkspaceBuildReasonJetbrainsConnection",
|
||||||
|
"CreateWorkspaceBuildReasonTaskManualPause"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"codersdk.CreateWorkspaceBuildRequest": {
|
"codersdk.CreateWorkspaceBuildRequest": {
|
||||||
@@ -14143,7 +14187,8 @@ const docTemplate = `{
|
|||||||
"cli",
|
"cli",
|
||||||
"ssh_connection",
|
"ssh_connection",
|
||||||
"vscode_connection",
|
"vscode_connection",
|
||||||
"jetbrains_connection"
|
"jetbrains_connection",
|
||||||
|
"task_manual_pause"
|
||||||
],
|
],
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
@@ -17014,6 +17059,14 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"codersdk.PauseTaskResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"workspace_build": {
|
||||||
|
"$ref": "#/definitions/codersdk.WorkspaceBuild"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"codersdk.Permission": {
|
"codersdk.Permission": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
Generated
+52
-3
@@ -5147,6 +5147,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/tasks/{user}/{task}/pause": {
|
||||||
|
"post": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"CoderSessionToken": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"consumes": ["application/json"],
|
||||||
|
"tags": ["Tasks"],
|
||||||
|
"summary": "Pause task",
|
||||||
|
"operationId": "pause-task",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username, user ID, or 'me' for the authenticated user",
|
||||||
|
"name": "user",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"format": "uuid",
|
||||||
|
"description": "Task ID",
|
||||||
|
"name": "task",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"202": {
|
||||||
|
"description": "Accepted",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/codersdk.PauseTaskResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/tasks/{user}/{task}/send": {
|
"/tasks/{user}/{task}/send": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
@@ -12662,14 +12700,16 @@
|
|||||||
"cli",
|
"cli",
|
||||||
"ssh_connection",
|
"ssh_connection",
|
||||||
"vscode_connection",
|
"vscode_connection",
|
||||||
"jetbrains_connection"
|
"jetbrains_connection",
|
||||||
|
"task_manual_pause"
|
||||||
],
|
],
|
||||||
"x-enum-varnames": [
|
"x-enum-varnames": [
|
||||||
"CreateWorkspaceBuildReasonDashboard",
|
"CreateWorkspaceBuildReasonDashboard",
|
||||||
"CreateWorkspaceBuildReasonCLI",
|
"CreateWorkspaceBuildReasonCLI",
|
||||||
"CreateWorkspaceBuildReasonSSHConnection",
|
"CreateWorkspaceBuildReasonSSHConnection",
|
||||||
"CreateWorkspaceBuildReasonVSCodeConnection",
|
"CreateWorkspaceBuildReasonVSCodeConnection",
|
||||||
"CreateWorkspaceBuildReasonJetbrainsConnection"
|
"CreateWorkspaceBuildReasonJetbrainsConnection",
|
||||||
|
"CreateWorkspaceBuildReasonTaskManualPause"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"codersdk.CreateWorkspaceBuildRequest": {
|
"codersdk.CreateWorkspaceBuildRequest": {
|
||||||
@@ -12699,7 +12739,8 @@
|
|||||||
"cli",
|
"cli",
|
||||||
"ssh_connection",
|
"ssh_connection",
|
||||||
"vscode_connection",
|
"vscode_connection",
|
||||||
"jetbrains_connection"
|
"jetbrains_connection",
|
||||||
|
"task_manual_pause"
|
||||||
],
|
],
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
@@ -15477,6 +15518,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"codersdk.PauseTaskResponse": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"workspace_build": {
|
||||||
|
"$ref": "#/definitions/codersdk.WorkspaceBuild"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"codersdk.Permission": {
|
"codersdk.Permission": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -1078,6 +1078,7 @@ func New(options *Options) *API {
|
|||||||
r.Patch("/input", api.taskUpdateInput)
|
r.Patch("/input", api.taskUpdateInput)
|
||||||
r.Post("/send", api.taskSend)
|
r.Post("/send", api.taskSend)
|
||||||
r.Get("/logs", api.taskLogs)
|
r.Get("/logs", api.taskLogs)
|
||||||
|
r.Post("/pause", api.pauseTask)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -384,7 +384,7 @@ func (api *API) postWorkspaceBuildsInternal(
|
|||||||
Experiments(api.Experiments).
|
Experiments(api.Experiments).
|
||||||
TemplateVersionPresetID(createBuild.TemplateVersionPresetID)
|
TemplateVersionPresetID(createBuild.TemplateVersionPresetID)
|
||||||
|
|
||||||
if transition == database.WorkspaceTransitionStart && createBuild.Reason != "" {
|
if (transition == database.WorkspaceTransitionStart || transition == database.WorkspaceTransitionStop) && createBuild.Reason != "" {
|
||||||
builder = builder.Reason(database.BuildReason(createBuild.Reason))
|
builder = builder.Reason(database.BuildReason(createBuild.Reason))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -329,6 +329,31 @@ func (c *Client) UpdateTaskInput(ctx context.Context, user string, id uuid.UUID,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PauseTaskResponse represents the response from pausing a task.
|
||||||
|
type PauseTaskResponse struct {
|
||||||
|
WorkspaceBuild *WorkspaceBuild `json:"workspace_build"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PauseTask pauses a task by stopping its workspace.
|
||||||
|
// Experimental: uses the /api/experimental endpoint.
|
||||||
|
func (c *Client) PauseTask(ctx context.Context, user string, id uuid.UUID) (PauseTaskResponse, error) {
|
||||||
|
res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/experimental/tasks/%s/%s/pause", user, id.String()), nil)
|
||||||
|
if err != nil {
|
||||||
|
return PauseTaskResponse{}, err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
if res.StatusCode != http.StatusAccepted {
|
||||||
|
return PauseTaskResponse{}, ReadBodyAsError(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp PauseTaskResponse
|
||||||
|
if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
|
||||||
|
return PauseTaskResponse{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// TaskLogType indicates the source of a task log entry.
|
// TaskLogType indicates the source of a task log entry.
|
||||||
type TaskLogType string
|
type TaskLogType string
|
||||||
|
|
||||||
|
|||||||
@@ -109,6 +109,7 @@ const (
|
|||||||
CreateWorkspaceBuildReasonSSHConnection CreateWorkspaceBuildReason = "ssh_connection"
|
CreateWorkspaceBuildReasonSSHConnection CreateWorkspaceBuildReason = "ssh_connection"
|
||||||
CreateWorkspaceBuildReasonVSCodeConnection CreateWorkspaceBuildReason = "vscode_connection"
|
CreateWorkspaceBuildReasonVSCodeConnection CreateWorkspaceBuildReason = "vscode_connection"
|
||||||
CreateWorkspaceBuildReasonJetbrainsConnection CreateWorkspaceBuildReason = "jetbrains_connection"
|
CreateWorkspaceBuildReasonJetbrainsConnection CreateWorkspaceBuildReason = "jetbrains_connection"
|
||||||
|
CreateWorkspaceBuildReasonTaskManualPause CreateWorkspaceBuildReason = "task_manual_pause"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateWorkspaceBuildRequest provides options to update the latest workspace build.
|
// CreateWorkspaceBuildRequest provides options to update the latest workspace build.
|
||||||
@@ -129,7 +130,7 @@ type CreateWorkspaceBuildRequest struct {
|
|||||||
// TemplateVersionPresetID is the ID of the template version preset to use for the build.
|
// TemplateVersionPresetID is the ID of the template version preset to use for the build.
|
||||||
TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"`
|
TemplateVersionPresetID uuid.UUID `json:"template_version_preset_id,omitempty" format:"uuid"`
|
||||||
// Reason sets the reason for the workspace build.
|
// Reason sets the reason for the workspace build.
|
||||||
Reason CreateWorkspaceBuildReason `json:"reason,omitempty" validate:"omitempty,oneof=dashboard cli ssh_connection vscode_connection jetbrains_connection"`
|
Reason CreateWorkspaceBuildReason `json:"reason,omitempty" validate:"omitempty,oneof=dashboard cli ssh_connection vscode_connection jetbrains_connection task_manual_pause"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WorkspaceOptions struct {
|
type WorkspaceOptions struct {
|
||||||
|
|||||||
Generated
+227
-8
@@ -2184,9 +2184,9 @@ This is required on creation to enable a user-flow of validating a template work
|
|||||||
|
|
||||||
#### Enumerated Values
|
#### Enumerated Values
|
||||||
|
|
||||||
| Value(s) |
|
| Value(s) |
|
||||||
|-----------------------------------------------------------------------------------|
|
|--------------------------------------------------------------------------------------------------------|
|
||||||
| `cli`, `dashboard`, `jetbrains_connection`, `ssh_connection`, `vscode_connection` |
|
| `cli`, `dashboard`, `jetbrains_connection`, `ssh_connection`, `task_manual_pause`, `vscode_connection` |
|
||||||
|
|
||||||
## codersdk.CreateWorkspaceBuildRequest
|
## codersdk.CreateWorkspaceBuildRequest
|
||||||
|
|
||||||
@@ -2227,11 +2227,11 @@ This is required on creation to enable a user-flow of validating a template work
|
|||||||
|
|
||||||
#### Enumerated Values
|
#### Enumerated Values
|
||||||
|
|
||||||
| Property | Value(s) |
|
| Property | Value(s) |
|
||||||
|--------------|-----------------------------------------------------------------------------------|
|
|--------------|--------------------------------------------------------------------------------------------------------|
|
||||||
| `log_level` | `debug` |
|
| `log_level` | `debug` |
|
||||||
| `reason` | `cli`, `dashboard`, `jetbrains_connection`, `ssh_connection`, `vscode_connection` |
|
| `reason` | `cli`, `dashboard`, `jetbrains_connection`, `ssh_connection`, `task_manual_pause`, `vscode_connection` |
|
||||||
| `transition` | `delete`, `start`, `stop` |
|
| `transition` | `delete`, `start`, `stop` |
|
||||||
|
|
||||||
## codersdk.CreateWorkspaceProxyRequest
|
## codersdk.CreateWorkspaceProxyRequest
|
||||||
|
|
||||||
@@ -6178,6 +6178,225 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
|
|||||||
| `name` | string | true | | |
|
| `name` | string | true | | |
|
||||||
| `regenerate_token` | boolean | false | | |
|
| `regenerate_token` | boolean | false | | |
|
||||||
|
|
||||||
|
## codersdk.PauseTaskResponse
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"workspace_build": {
|
||||||
|
"build_number": 0,
|
||||||
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
|
"daily_cost": 0,
|
||||||
|
"deadline": "2019-08-24T14:15:22Z",
|
||||||
|
"has_ai_task": true,
|
||||||
|
"has_external_agent": true,
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
|
||||||
|
"initiator_name": "string",
|
||||||
|
"job": {
|
||||||
|
"available_workers": [
|
||||||
|
"497f6eca-6276-4993-bfeb-53cbbbba6f08"
|
||||||
|
],
|
||||||
|
"canceled_at": "2019-08-24T14:15:22Z",
|
||||||
|
"completed_at": "2019-08-24T14:15:22Z",
|
||||||
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
|
"error": "string",
|
||||||
|
"error_code": "REQUIRED_TEMPLATE_VARIABLES",
|
||||||
|
"file_id": "8a0cfb4f-ddc9-436d-91bb-75133c583767",
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"initiator_id": "06588898-9a84-4b35-ba8f-f9cbd64946f3",
|
||||||
|
"input": {
|
||||||
|
"error": "string",
|
||||||
|
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||||
|
"workspace_build_id": "badaf2eb-96c5-4050-9f1d-db2d39ca5478"
|
||||||
|
},
|
||||||
|
"logs_overflowed": true,
|
||||||
|
"metadata": {
|
||||||
|
"template_display_name": "string",
|
||||||
|
"template_icon": "string",
|
||||||
|
"template_id": "c6d67e98-83ea-49f0-8812-e4abae2b68bc",
|
||||||
|
"template_name": "string",
|
||||||
|
"template_version_name": "string",
|
||||||
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
|
"workspace_name": "string"
|
||||||
|
},
|
||||||
|
"organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6",
|
||||||
|
"queue_position": 0,
|
||||||
|
"queue_size": 0,
|
||||||
|
"started_at": "2019-08-24T14:15:22Z",
|
||||||
|
"status": "pending",
|
||||||
|
"tags": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"type": "template_version_import",
|
||||||
|
"worker_id": "ae5fa6f7-c55b-40c1-b40a-b36ac467652b",
|
||||||
|
"worker_name": "string"
|
||||||
|
},
|
||||||
|
"matched_provisioners": {
|
||||||
|
"available": 0,
|
||||||
|
"count": 0,
|
||||||
|
"most_recently_seen": "2019-08-24T14:15:22Z"
|
||||||
|
},
|
||||||
|
"max_deadline": "2019-08-24T14:15:22Z",
|
||||||
|
"reason": "initiator",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"agents": [
|
||||||
|
{
|
||||||
|
"api_version": "string",
|
||||||
|
"apps": [
|
||||||
|
{
|
||||||
|
"command": "string",
|
||||||
|
"display_name": "string",
|
||||||
|
"external": true,
|
||||||
|
"group": "string",
|
||||||
|
"health": "disabled",
|
||||||
|
"healthcheck": {
|
||||||
|
"interval": 0,
|
||||||
|
"threshold": 0,
|
||||||
|
"url": "string"
|
||||||
|
},
|
||||||
|
"hidden": true,
|
||||||
|
"icon": "string",
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"open_in": "slim-window",
|
||||||
|
"sharing_level": "owner",
|
||||||
|
"slug": "string",
|
||||||
|
"statuses": [
|
||||||
|
{
|
||||||
|
"agent_id": "2b1e3b65-2c04-4fa2-a2d7-467901e98978",
|
||||||
|
"app_id": "affd1d10-9538-4fc8-9e0b-4594a28c1335",
|
||||||
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
|
"icon": "string",
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"message": "string",
|
||||||
|
"needs_user_attention": true,
|
||||||
|
"state": "working",
|
||||||
|
"uri": "string",
|
||||||
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"subdomain": true,
|
||||||
|
"subdomain_name": "string",
|
||||||
|
"tooltip": "string",
|
||||||
|
"url": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"architecture": "string",
|
||||||
|
"connection_timeout_seconds": 0,
|
||||||
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
|
"directory": "string",
|
||||||
|
"disconnected_at": "2019-08-24T14:15:22Z",
|
||||||
|
"display_apps": [
|
||||||
|
"vscode"
|
||||||
|
],
|
||||||
|
"environment_variables": {
|
||||||
|
"property1": "string",
|
||||||
|
"property2": "string"
|
||||||
|
},
|
||||||
|
"expanded_directory": "string",
|
||||||
|
"first_connected_at": "2019-08-24T14:15:22Z",
|
||||||
|
"health": {
|
||||||
|
"healthy": false,
|
||||||
|
"reason": "agent has lost connection"
|
||||||
|
},
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"instance_id": "string",
|
||||||
|
"last_connected_at": "2019-08-24T14:15:22Z",
|
||||||
|
"latency": {
|
||||||
|
"property1": {
|
||||||
|
"latency_ms": 0,
|
||||||
|
"preferred": true
|
||||||
|
},
|
||||||
|
"property2": {
|
||||||
|
"latency_ms": 0,
|
||||||
|
"preferred": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lifecycle_state": "created",
|
||||||
|
"log_sources": [
|
||||||
|
{
|
||||||
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
|
"display_name": "string",
|
||||||
|
"icon": "string",
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"workspace_agent_id": "7ad2e618-fea7-4c1a-b70a-f501566a72f1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"logs_length": 0,
|
||||||
|
"logs_overflowed": true,
|
||||||
|
"name": "string",
|
||||||
|
"operating_system": "string",
|
||||||
|
"parent_id": {
|
||||||
|
"uuid": "string",
|
||||||
|
"valid": true
|
||||||
|
},
|
||||||
|
"ready_at": "2019-08-24T14:15:22Z",
|
||||||
|
"resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f",
|
||||||
|
"scripts": [
|
||||||
|
{
|
||||||
|
"cron": "string",
|
||||||
|
"display_name": "string",
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"log_path": "string",
|
||||||
|
"log_source_id": "4197ab25-95cf-4b91-9c78-f7f2af5d353a",
|
||||||
|
"run_on_start": true,
|
||||||
|
"run_on_stop": true,
|
||||||
|
"script": "string",
|
||||||
|
"start_blocks_login": true,
|
||||||
|
"timeout": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"started_at": "2019-08-24T14:15:22Z",
|
||||||
|
"startup_script_behavior": "blocking",
|
||||||
|
"status": "connecting",
|
||||||
|
"subsystems": [
|
||||||
|
"envbox"
|
||||||
|
],
|
||||||
|
"troubleshooting_url": "string",
|
||||||
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
|
"version": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"created_at": "2019-08-24T14:15:22Z",
|
||||||
|
"daily_cost": 0,
|
||||||
|
"hide": true,
|
||||||
|
"icon": "string",
|
||||||
|
"id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
|
||||||
|
"job_id": "453bd7d7-5355-4d6d-a38e-d9e7eb218c3f",
|
||||||
|
"metadata": [
|
||||||
|
{
|
||||||
|
"key": "string",
|
||||||
|
"sensitive": true,
|
||||||
|
"value": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name": "string",
|
||||||
|
"type": "string",
|
||||||
|
"workspace_transition": "start"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"status": "pending",
|
||||||
|
"template_version_id": "0ba39c92-1f1b-4c32-aa3e-9925d7713eb1",
|
||||||
|
"template_version_name": "string",
|
||||||
|
"template_version_preset_id": "512a53a7-30da-446e-a1fc-713c630baff1",
|
||||||
|
"transition": "start",
|
||||||
|
"updated_at": "2019-08-24T14:15:22Z",
|
||||||
|
"workspace_id": "0967198e-ec7b-4c6b-b4d3-f71244cadbe9",
|
||||||
|
"workspace_name": "string",
|
||||||
|
"workspace_owner_avatar_url": "string",
|
||||||
|
"workspace_owner_id": "e7078695-5279-4c86-8774-3ac2367a2fc7",
|
||||||
|
"workspace_owner_name": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
| Name | Type | Required | Restrictions | Description |
|
||||||
|
|-------------------|----------------------------------------------------|----------|--------------|-------------|
|
||||||
|
| `workspace_build` | [codersdk.WorkspaceBuild](#codersdkworkspacebuild) | false | | |
|
||||||
|
|
||||||
## codersdk.Permission
|
## codersdk.Permission
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|||||||
Generated
+32
@@ -365,6 +365,38 @@ curl -X GET http://coder-server:8080/api/v2/tasks/{user}/{task}/logs \
|
|||||||
|
|
||||||
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
|
## Pause task
|
||||||
|
|
||||||
|
### Code samples
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# Example request using curl
|
||||||
|
curl -X POST http://coder-server:8080/api/v2/tasks/{user}/{task}/pause \
|
||||||
|
-H 'Accept: */*' \
|
||||||
|
-H 'Coder-Session-Token: API_KEY'
|
||||||
|
```
|
||||||
|
|
||||||
|
`POST /tasks/{user}/{task}/pause`
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
| Name | In | Type | Required | Description |
|
||||||
|
|--------|------|--------------|----------|-------------------------------------------------------|
|
||||||
|
| `user` | path | string | true | Username, user ID, or 'me' for the authenticated user |
|
||||||
|
| `task` | path | string(uuid) | true | Task ID |
|
||||||
|
|
||||||
|
### Example responses
|
||||||
|
|
||||||
|
> 202 Response
|
||||||
|
|
||||||
|
### Responses
|
||||||
|
|
||||||
|
| Status | Meaning | Description | Schema |
|
||||||
|
|--------|---------------------------------------------------------------|-------------|--------------------------------------------------------------------|
|
||||||
|
| 202 | [Accepted](https://tools.ietf.org/html/rfc7231#section-6.3.3) | Accepted | [codersdk.PauseTaskResponse](schemas.md#codersdkpausetaskresponse) |
|
||||||
|
|
||||||
|
To perform this operation, you must be authenticated. [Learn more](authentication.md).
|
||||||
|
|
||||||
## Send input to AI task
|
## Send input to AI task
|
||||||
|
|
||||||
### Code samples
|
### Code samples
|
||||||
|
|||||||
Generated
+10
@@ -1425,6 +1425,7 @@ export type CreateWorkspaceBuildReason =
|
|||||||
| "dashboard"
|
| "dashboard"
|
||||||
| "jetbrains_connection"
|
| "jetbrains_connection"
|
||||||
| "ssh_connection"
|
| "ssh_connection"
|
||||||
|
| "task_manual_pause"
|
||||||
| "vscode_connection";
|
| "vscode_connection";
|
||||||
|
|
||||||
export const CreateWorkspaceBuildReasons: CreateWorkspaceBuildReason[] = [
|
export const CreateWorkspaceBuildReasons: CreateWorkspaceBuildReason[] = [
|
||||||
@@ -1432,6 +1433,7 @@ export const CreateWorkspaceBuildReasons: CreateWorkspaceBuildReason[] = [
|
|||||||
"dashboard",
|
"dashboard",
|
||||||
"jetbrains_connection",
|
"jetbrains_connection",
|
||||||
"ssh_connection",
|
"ssh_connection",
|
||||||
|
"task_manual_pause",
|
||||||
"vscode_connection",
|
"vscode_connection",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -3583,6 +3585,14 @@ export interface PatchWorkspaceProxy {
|
|||||||
*/
|
*/
|
||||||
export const PathAppSessionTokenCookie = "coder_path_app_session_token";
|
export const PathAppSessionTokenCookie = "coder_path_app_session_token";
|
||||||
|
|
||||||
|
// From codersdk/aitasks.go
|
||||||
|
/**
|
||||||
|
* PauseTaskResponse represents the response from pausing a task.
|
||||||
|
*/
|
||||||
|
export interface PauseTaskResponse {
|
||||||
|
readonly workspace_build: WorkspaceBuild | null;
|
||||||
|
}
|
||||||
|
|
||||||
// From codersdk/roles.go
|
// From codersdk/roles.go
|
||||||
/**
|
/**
|
||||||
* Permission is the format passed into the rego.
|
* Permission is the format passed into the rego.
|
||||||
|
|||||||
Reference in New Issue
Block a user