Skip to content

Commit 143e33e

Browse files
committed
Merge branch 'main' into aqandrew/replace-popover-in-helptooltip
2 parents c142b5a + 39bf3ba commit 143e33e

File tree

75 files changed

+2476
-1308
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+2476
-1308
lines changed

cli/exp_scaletest.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
864864
tickInterval time.Duration
865865
bytesPerTick int64
866866
ssh bool
867+
disableDirect bool
867868
useHostLogin bool
868869
app string
869870
template string
@@ -1023,15 +1024,16 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
10231024

10241025
// Setup our workspace agent connection.
10251026
config := workspacetraffic.Config{
1026-
AgentID: agent.ID,
1027-
BytesPerTick: bytesPerTick,
1028-
Duration: strategy.timeout,
1029-
TickInterval: tickInterval,
1030-
ReadMetrics: metrics.ReadMetrics(ws.OwnerName, ws.Name, agent.Name),
1031-
WriteMetrics: metrics.WriteMetrics(ws.OwnerName, ws.Name, agent.Name),
1032-
SSH: ssh,
1033-
Echo: ssh,
1034-
App: appConfig,
1027+
AgentID: agent.ID,
1028+
BytesPerTick: bytesPerTick,
1029+
Duration: strategy.timeout,
1030+
TickInterval: tickInterval,
1031+
ReadMetrics: metrics.ReadMetrics(ws.OwnerName, ws.Name, agent.Name),
1032+
WriteMetrics: metrics.WriteMetrics(ws.OwnerName, ws.Name, agent.Name),
1033+
SSH: ssh,
1034+
DisableDirect: disableDirect,
1035+
Echo: ssh,
1036+
App: appConfig,
10351037
}
10361038

10371039
if webClient != nil {
@@ -1117,6 +1119,13 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command {
11171119
Description: "Send traffic over SSH, cannot be used with --app.",
11181120
Value: serpent.BoolOf(&ssh),
11191121
},
1122+
{
1123+
Flag: "disable-direct",
1124+
Env: "CODER_SCALETEST_WORKSPACE_TRAFFIC_DISABLE_DIRECT_CONNECTIONS",
1125+
Default: "false",
1126+
Description: "Disable direct connections for SSH traffic to workspaces. Does nothing if `--ssh` is not also set.",
1127+
Value: serpent.BoolOf(&disableDirect),
1128+
},
11201129
{
11211130
Flag: "app",
11221131
Env: "CODER_SCALETEST_WORKSPACE_TRAFFIC_APP",

cli/exp_task_status_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,13 +243,12 @@ STATE CHANGED STATUS STATE MESSAGE
243243
ctx = testutil.Context(t, testutil.WaitShort)
244244
now = time.Now().UTC() // TODO: replace with quartz
245245
srv = httptest.NewServer(http.HandlerFunc(tc.hf(ctx, now)))
246-
client = new(codersdk.Client)
246+
client = codersdk.New(testutil.MustURL(t, srv.URL))
247247
sb = strings.Builder{}
248248
args = []string{"exp", "task", "status", "--watch-interval", testutil.IntervalFast.String()}
249249
)
250250

251251
t.Cleanup(srv.Close)
252-
client.URL = testutil.MustURL(t, srv.URL)
253252
args = append(args, tc.args...)
254253
inv, root := clitest.New(t, args...)
255254
inv.Stdout = &sb

cli/exp_taskcreate.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func (r *RootCmd) taskCreate() *serpent.Command {
104104
templateVersionPresetID = preset.ID
105105
}
106106

107-
workspace, err := expClient.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
107+
task, err := expClient.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
108108
TemplateVersionID: templateVersionID,
109109
TemplateVersionPresetID: templateVersionPresetID,
110110
Prompt: taskInput,
@@ -116,8 +116,8 @@ func (r *RootCmd) taskCreate() *serpent.Command {
116116
_, _ = fmt.Fprintf(
117117
inv.Stdout,
118118
"The task %s has been created at %s!\n",
119-
cliui.Keyword(workspace.Name),
120-
cliui.Timestamp(workspace.CreatedAt),
119+
cliui.Keyword(task.Name),
120+
cliui.Timestamp(task.CreatedAt),
121121
)
122122

123123
return nil

cli/exp_taskcreate_test.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@ import (
55
"fmt"
66
"net/http"
77
"net/http/httptest"
8-
"net/url"
98
"strings"
109
"testing"
1110
"time"
1211

1312
"github.com/google/uuid"
1413
"github.com/stretchr/testify/assert"
15-
"github.com/stretchr/testify/require"
1614

1715
"github.com/coder/coder/v2/cli/clitest"
1816
"github.com/coder/coder/v2/cli/cliui"
@@ -236,17 +234,14 @@ func TestTaskCreate(t *testing.T) {
236234
var (
237235
ctx = testutil.Context(t, testutil.WaitShort)
238236
srv = httptest.NewServer(tt.handler(t, ctx))
239-
client = new(codersdk.Client)
237+
client = codersdk.New(testutil.MustURL(t, srv.URL))
240238
args = []string{"exp", "task", "create"}
241239
sb strings.Builder
242240
err error
243241
)
244242

245243
t.Cleanup(srv.Close)
246244

247-
client.URL, err = url.Parse(srv.URL)
248-
require.NoError(t, err)
249-
250245
inv, root := clitest.New(t, append(args, tt.args...)...)
251246
inv.Environ = serpent.ParseEnviron(tt.env, "")
252247
inv.Stdout = &sb

cli/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -635,6 +635,9 @@ func (r *RootCmd) HeaderTransport(ctx context.Context, serverURL *url.URL) (*cod
635635
}
636636

637637
func (r *RootCmd) configureClient(ctx context.Context, client *codersdk.Client, serverURL *url.URL, inv *serpent.Invocation) error {
638+
if client.SessionTokenProvider == nil {
639+
client.SessionTokenProvider = codersdk.FixedSessionTokenProvider{}
640+
}
638641
transport := http.DefaultTransport
639642
transport = wrapTransportWithTelemetryHeader(transport, inv)
640643
if !r.noVersionCheck {

coderd/aitasks.go

Lines changed: 145 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/coder/coder/v2/coderd/audit"
1818
"github.com/coder/coder/v2/coderd/database"
1919
"github.com/coder/coder/v2/coderd/httpapi"
20+
"github.com/coder/coder/v2/coderd/httpapi/httperror"
2021
"github.com/coder/coder/v2/coderd/httpmw"
2122
"github.com/coder/coder/v2/coderd/rbac"
2223
"github.com/coder/coder/v2/coderd/rbac/policy"
@@ -154,8 +155,9 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
154155
// This can be optimized. It exists as it is now for code simplicity.
155156
// The most common case is to create a workspace for 'Me'. Which does
156157
// not enter this code branch.
157-
template, ok := requestTemplate(ctx, rw, createReq, api.Database)
158-
if !ok {
158+
template, err := requestTemplate(ctx, createReq, api.Database)
159+
if err != nil {
160+
httperror.WriteResponseError(ctx, rw, err)
159161
return
160162
}
161163

@@ -186,9 +188,72 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
186188
WorkspaceOwner: owner.Username,
187189
},
188190
})
189-
190191
defer commitAudit()
191-
createWorkspace(ctx, aReq, apiKey.UserID, api, owner, createReq, rw, r)
192+
w, err := createWorkspace(ctx, aReq, apiKey.UserID, api, owner, createReq, r)
193+
if err != nil {
194+
httperror.WriteResponseError(ctx, rw, err)
195+
return
196+
}
197+
198+
task := taskFromWorkspace(w, req.Prompt)
199+
httpapi.Write(ctx, rw, http.StatusCreated, task)
200+
}
201+
202+
func taskFromWorkspace(ws codersdk.Workspace, initialPrompt string) codersdk.Task {
203+
// TODO(DanielleMaywood):
204+
// This just picks up the first agent it discovers.
205+
// This approach _might_ break when a task has multiple agents,
206+
// depending on which agent was found first.
207+
//
208+
// We explicitly do not have support for running tasks
209+
// inside of a sub agent at the moment, so we can be sure
210+
// that any sub agents are not the agent we're looking for.
211+
var taskAgentID uuid.NullUUID
212+
var taskAgentLifecycle *codersdk.WorkspaceAgentLifecycle
213+
var taskAgentHealth *codersdk.WorkspaceAgentHealth
214+
for _, resource := range ws.LatestBuild.Resources {
215+
for _, agent := range resource.Agents {
216+
if agent.ParentID.Valid {
217+
continue
218+
}
219+
220+
taskAgentID = uuid.NullUUID{Valid: true, UUID: agent.ID}
221+
taskAgentLifecycle = &agent.LifecycleState
222+
taskAgentHealth = &agent.Health
223+
break
224+
}
225+
}
226+
227+
var currentState *codersdk.TaskStateEntry
228+
if ws.LatestAppStatus != nil {
229+
currentState = &codersdk.TaskStateEntry{
230+
Timestamp: ws.LatestAppStatus.CreatedAt,
231+
State: codersdk.TaskState(ws.LatestAppStatus.State),
232+
Message: ws.LatestAppStatus.Message,
233+
URI: ws.LatestAppStatus.URI,
234+
}
235+
}
236+
237+
return codersdk.Task{
238+
ID: ws.ID,
239+
OrganizationID: ws.OrganizationID,
240+
OwnerID: ws.OwnerID,
241+
OwnerName: ws.OwnerName,
242+
Name: ws.Name,
243+
TemplateID: ws.TemplateID,
244+
TemplateName: ws.TemplateName,
245+
TemplateDisplayName: ws.TemplateDisplayName,
246+
TemplateIcon: ws.TemplateIcon,
247+
WorkspaceID: uuid.NullUUID{Valid: true, UUID: ws.ID},
248+
WorkspaceAgentID: taskAgentID,
249+
WorkspaceAgentLifecycle: taskAgentLifecycle,
250+
WorkspaceAgentHealth: taskAgentHealth,
251+
CreatedAt: ws.CreatedAt,
252+
UpdatedAt: ws.UpdatedAt,
253+
InitialPrompt: initialPrompt,
254+
Status: ws.LatestBuild.Status,
255+
CurrentState: currentState,
256+
}
192257
}
193258

194259
// tasksFromWorkspaces converts a slice of API workspaces into tasks, fetching
@@ -213,60 +278,7 @@ func (api *API) tasksFromWorkspaces(ctx context.Context, apiWorkspaces []codersd
213278

214279
tasks := make([]codersdk.Task, 0, len(apiWorkspaces))
215280
for _, ws := range apiWorkspaces {
216-
// TODO(DanielleMaywood):
217-
// This just picks up the first agent it discovers.
218-
// This approach _might_ break when a task has multiple agents,
219-
// depending on which agent was found first.
220-
//
221-
// We explicitly do not have support for running tasks
222-
// inside of a sub agent at the moment, so we can be sure
223-
// that any sub agents are not the agent we're looking for.
224-
var taskAgentID uuid.NullUUID
225-
var taskAgentLifecycle *codersdk.WorkspaceAgentLifecycle
226-
var taskAgentHealth *codersdk.WorkspaceAgentHealth
227-
for _, resource := range ws.LatestBuild.Resources {
228-
for _, agent := range resource.Agents {
229-
if agent.ParentID.Valid {
230-
continue
231-
}
232-
233-
taskAgentID = uuid.NullUUID{Valid: true, UUID: agent.ID}
234-
taskAgentLifecycle = &agent.LifecycleState
235-
taskAgentHealth = &agent.Health
236-
break
237-
}
238-
}
239-
240-
var currentState *codersdk.TaskStateEntry
241-
if ws.LatestAppStatus != nil {
242-
currentState = &codersdk.TaskStateEntry{
243-
Timestamp: ws.LatestAppStatus.CreatedAt,
244-
State: codersdk.TaskState(ws.LatestAppStatus.State),
245-
Message: ws.LatestAppStatus.Message,
246-
URI: ws.LatestAppStatus.URI,
247-
}
248-
}
249-
250-
tasks = append(tasks, codersdk.Task{
251-
ID: ws.ID,
252-
OrganizationID: ws.OrganizationID,
253-
OwnerID: ws.OwnerID,
254-
OwnerName: ws.OwnerName,
255-
Name: ws.Name,
256-
TemplateID: ws.TemplateID,
257-
TemplateName: ws.TemplateName,
258-
TemplateDisplayName: ws.TemplateDisplayName,
259-
TemplateIcon: ws.TemplateIcon,
260-
WorkspaceID: uuid.NullUUID{Valid: true, UUID: ws.ID},
261-
WorkspaceAgentID: taskAgentID,
262-
WorkspaceAgentLifecycle: taskAgentLifecycle,
263-
WorkspaceAgentHealth: taskAgentHealth,
264-
CreatedAt: ws.CreatedAt,
265-
UpdatedAt: ws.UpdatedAt,
266-
InitialPrompt: promptsByBuildID[ws.LatestBuild.ID],
267-
Status: ws.LatestBuild.Status,
268-
CurrentState: currentState,
269-
})
281+
tasks = append(tasks, taskFromWorkspace(ws, promptsByBuildID[ws.LatestBuild.ID]))
270282
}
271283

272284
return tasks, nil
@@ -464,3 +476,78 @@ func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
464476

465477
httpapi.Write(ctx, rw, http.StatusOK, tasks[0])
466478
}
479+
480+
// taskDelete is an experimental endpoint to delete a task by ID (workspace ID).
481+
// It creates a delete workspace build and returns 202 Accepted if the build was
482+
// created.
483+
func (api *API) taskDelete(rw http.ResponseWriter, r *http.Request) {
484+
ctx := r.Context()
485+
apiKey := httpmw.APIKey(r)
486+
487+
idStr := chi.URLParam(r, "id")
488+
taskID, err := uuid.Parse(idStr)
489+
if err != nil {
490+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
491+
Message: fmt.Sprintf("Invalid UUID %q for task ID.", idStr),
492+
})
493+
return
494+
}
495+
496+
// For now, taskID = workspaceID, once we have a task data model in
497+
// the DB, we can change this lookup.
498+
workspaceID := taskID
499+
workspace, err := api.Database.GetWorkspaceByID(ctx, workspaceID)
500+
if httpapi.Is404Error(err) {
501+
httpapi.ResourceNotFound(rw)
502+
return
503+
}
504+
if err != nil {
505+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
506+
Message: "Internal error fetching workspace.",
507+
Detail: err.Error(),
508+
})
509+
return
510+
}
511+
512+
data, err := api.workspaceData(ctx, []database.Workspace{workspace})
513+
if err != nil {
514+
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
515+
Message: "Internal error fetching workspace resources.",
516+
Detail: err.Error(),
517+
})
518+
return
519+
}
520+
if len(data.builds) == 0 || len(data.templates) == 0 {
521+
httpapi.ResourceNotFound(rw)
522+
return
523+
}
524+
if data.builds[0].HasAITask == nil || !*data.builds[0].HasAITask {
525+
httpapi.ResourceNotFound(rw)
526+
return
527+
}
528+
529+
// Construct a request to the workspace build creation handler to
530+
// initiate deletion.
531+
buildReq := codersdk.CreateWorkspaceBuildRequest{
532+
Transition: codersdk.WorkspaceTransitionDelete,
533+
Reason: "Deleted via tasks API",
534+
}
535+
536+
_, err = api.postWorkspaceBuildsInternal(
537+
ctx,
538+
apiKey,
539+
workspace,
540+
buildReq,
541+
func(action policy.Action, object rbac.Objecter) bool {
542+
return api.Authorize(r, action, object)
543+
},
544+
audit.WorkspaceBuildBaggageFromRequest(r),
545+
)
546+
if err != nil {
547+
httperror.WriteWorkspaceBuildError(ctx, rw, err)
548+
return
549+
}
550+
551+
// Delete build created successfully.
552+
rw.WriteHeader(http.StatusAccepted)
553+
}

0 commit comments

Comments
 (0)