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

Skip to content

Commit 8c55100

Browse files
feat(cli): add task create command
1 parent 73544a1 commit 8c55100

File tree

2 files changed

+292
-0
lines changed

2 files changed

+292
-0
lines changed

cli/exp_taskcreate.go

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"slices"
6+
"strings"
7+
"time"
8+
9+
"github.com/coder/coder/v2/cli/cliui"
10+
"github.com/coder/coder/v2/coderd/util/slice"
11+
"github.com/coder/coder/v2/codersdk"
12+
"github.com/coder/serpent"
13+
"github.com/google/uuid"
14+
"golang.org/x/xerrors"
15+
)
16+
17+
func (r *RootCmd) taskCreate() *serpent.Command {
18+
var (
19+
orgContext = NewOrganizationContext()
20+
client = new(codersdk.Client)
21+
22+
templateName string
23+
templateVersionName string
24+
presetName string
25+
taskInput string
26+
)
27+
28+
return &serpent.Command{
29+
Use: "create [task]",
30+
Short: "Create an experimental task",
31+
Middleware: serpent.Chain(
32+
serpent.RequireRangeArgs(0, 1),
33+
r.InitClient(client),
34+
),
35+
Options: serpent.OptionSet{
36+
{
37+
Flag: "input",
38+
Env: "CODER_TASK_INPUT",
39+
Value: serpent.StringOf(&taskInput),
40+
Required: true,
41+
},
42+
{
43+
Env: "CODER_TEMPLATE_NAME",
44+
Value: serpent.StringOf(&templateName),
45+
},
46+
{
47+
Env: "CODER_TEMPLATE_VERSION",
48+
Value: serpent.StringOf(&templateVersionName),
49+
},
50+
{
51+
Flag: "preset",
52+
Env: "CODER_PRESET_NAME",
53+
Value: serpent.StringOf(&presetName),
54+
Default: PresetNone,
55+
},
56+
},
57+
Handler: func(inv *serpent.Invocation) error {
58+
var (
59+
ctx = inv.Context()
60+
expClient = codersdk.NewExperimentalClient(client)
61+
62+
templateVersionID uuid.UUID
63+
templateVersionPresetID uuid.UUID
64+
)
65+
66+
organization, err := orgContext.Selected(inv, client)
67+
if err != nil {
68+
return xerrors.Errorf("get current organization: %w", err)
69+
}
70+
71+
if len(inv.Args) > 0 {
72+
templateName, templateVersionName, _ = strings.Cut(inv.Args[0], "@")
73+
}
74+
75+
if templateName == "" {
76+
templates, err := client.Templates(ctx, codersdk.TemplateFilter{SearchQuery: "has-ai-task:true"})
77+
if err != nil {
78+
return xerrors.Errorf("get templates: %w", err)
79+
}
80+
81+
slices.SortFunc(templates, func(a, b codersdk.Template) int {
82+
return slice.Descending(a.ActiveUserCount, b.ActiveUserCount)
83+
})
84+
85+
templateNames := make([]string, 0, len(templates))
86+
templateByName := make(map[string]codersdk.Template, len(templates))
87+
88+
// If more than 1 organization exists in the list of templates,
89+
// then include the organization name in the select options.
90+
uniqueOrganizations := make(map[uuid.UUID]bool)
91+
for _, template := range templates {
92+
uniqueOrganizations[template.OrganizationID] = true
93+
}
94+
95+
for _, template := range templates {
96+
templateName := template.Name
97+
if len(uniqueOrganizations) > 1 {
98+
templateName += cliui.Placeholder(
99+
fmt.Sprintf(
100+
" (%s)",
101+
template.OrganizationName,
102+
),
103+
)
104+
}
105+
106+
if template.ActiveUserCount > 0 {
107+
templateName += cliui.Placeholder(
108+
fmt.Sprintf(
109+
" used by %s",
110+
formatActiveDevelopers(template.ActiveUserCount),
111+
),
112+
)
113+
}
114+
115+
templateNames = append(templateNames, templateName)
116+
templateByName[templateName] = template
117+
}
118+
119+
option, err := cliui.Select(inv, cliui.SelectOptions{
120+
Options: templateNames,
121+
HideSearch: true,
122+
})
123+
124+
templateName = templateByName[option].Name
125+
}
126+
127+
if templateVersionName != "" {
128+
templateVersion, err := client.TemplateVersionByOrganizationAndName(ctx, organization.ID, templateName, templateVersionName)
129+
if err != nil {
130+
return xerrors.Errorf("get template version: %w", err)
131+
}
132+
133+
templateVersionID = templateVersion.ID
134+
} else {
135+
template, err := client.TemplateByName(ctx, organization.ID, templateName)
136+
if err != nil {
137+
return xerrors.Errorf("get template: %w", err)
138+
}
139+
140+
templateVersionID = template.ActiveVersionID
141+
}
142+
143+
if presetName != PresetNone {
144+
templatePresets, err := client.TemplateVersionPresets(ctx, templateVersionID)
145+
if err != nil {
146+
return xerrors.Errorf("get template presets: %w", err)
147+
}
148+
149+
preset, err := resolvePreset(templatePresets, presetName)
150+
if err != nil {
151+
return xerrors.Errorf("resolve preset: %w", err)
152+
}
153+
154+
templateVersionID = preset.ID
155+
}
156+
157+
workspace, err := expClient.CreateTask(ctx, codersdk.Me, codersdk.CreateTaskRequest{
158+
TemplateVersionID: templateVersionID,
159+
TemplateVersionPresetID: templateVersionPresetID,
160+
Prompt: taskInput,
161+
})
162+
163+
_, _ = fmt.Fprintf(
164+
inv.Stdout,
165+
"The task %s has been created at %s!\n",
166+
cliui.Keyword(workspace.Name),
167+
cliui.Timestamp(time.Now()),
168+
)
169+
170+
return nil
171+
},
172+
}
173+
}

cli/taskcreate_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package cli_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/coder/coder/v2/cli/clitest"
8+
"github.com/coder/coder/v2/coderd/coderdtest"
9+
"github.com/coder/coder/v2/codersdk"
10+
"github.com/coder/coder/v2/provisioner/echo"
11+
"github.com/coder/coder/v2/provisionersdk/proto"
12+
"github.com/coder/coder/v2/testutil"
13+
"github.com/google/uuid"
14+
"github.com/stretchr/testify/require"
15+
)
16+
17+
func TestTaskCreate(t *testing.T) {
18+
t.Parallel()
19+
20+
createAITemplate := func(t *testing.T, client *codersdk.Client, user codersdk.CreateFirstUserResponse) (codersdk.TemplateVersion, codersdk.Template) {
21+
t.Helper()
22+
23+
taskAppID := uuid.New()
24+
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, &echo.Responses{
25+
Parse: echo.ParseComplete,
26+
ProvisionPlan: []*proto.Response{
27+
{
28+
Type: &proto.Response_Plan{
29+
Plan: &proto.PlanComplete{
30+
Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}},
31+
HasAiTasks: true,
32+
AiTasks: []*proto.AITask{},
33+
},
34+
},
35+
},
36+
},
37+
ProvisionApply: []*proto.Response{
38+
{
39+
Type: &proto.Response_Apply{
40+
Apply: &proto.ApplyComplete{
41+
Resources: []*proto.Resource{{
42+
Name: "example",
43+
Type: "aws_instance",
44+
Agents: []*proto.Agent{{
45+
Id: uuid.NewString(),
46+
Name: "example",
47+
Apps: []*proto.App{
48+
{
49+
Id: taskAppID.String(),
50+
Slug: "task-sidebar",
51+
DisplayName: "Task Sidebar",
52+
},
53+
},
54+
}},
55+
}},
56+
Parameters: []*proto.RichParameter{{Name: codersdk.AITaskPromptParameterName, Type: "string"}},
57+
AiTasks: []*proto.AITask{{
58+
SidebarApp: &proto.AITaskSidebarApp{
59+
Id: taskAppID.String(),
60+
},
61+
}},
62+
},
63+
},
64+
},
65+
},
66+
})
67+
coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID)
68+
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
69+
70+
return version, template
71+
}
72+
73+
t.Run("CreateWithTemplateNameAndVersion", func(t *testing.T) {
74+
t.Parallel()
75+
76+
var (
77+
ctx = testutil.Context(t, testutil.WaitShort)
78+
79+
prompt = "Task prompt"
80+
)
81+
82+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
83+
owner := coderdtest.CreateFirstUser(t, client)
84+
templateVersion, template := createAITemplate(t, client, owner)
85+
86+
member, _ := coderdtest.CreateAnotherUser(t, client, owner.OrganizationID)
87+
expMember := codersdk.NewExperimentalClient(member)
88+
89+
tasks, err := expMember.Tasks(ctx, nil)
90+
require.NoError(t, err)
91+
require.Empty(t, tasks)
92+
93+
args := []string{
94+
"exp",
95+
"task",
96+
"create",
97+
fmt.Sprintf("%s@%s", template.Name, templateVersion.Name),
98+
"--input", prompt,
99+
}
100+
101+
inv, root := clitest.New(t, args...)
102+
clitest.SetupConfig(t, member, root)
103+
104+
err = inv.Run()
105+
require.NoError(t, err)
106+
107+
workspaces, err := member.Workspaces(ctx, codersdk.WorkspaceFilter{FilterQuery: "has-ai-task:true"})
108+
require.NoError(t, err)
109+
require.Len(t, workspaces.Workspaces, 1)
110+
111+
coderdtest.AwaitWorkspaceBuildJobCompleted(t, member, workspaces.Workspaces[0].LatestBuild.ID)
112+
113+
tasks, err = expMember.Tasks(ctx, nil)
114+
require.NoError(t, err)
115+
require.Len(t, tasks, 1)
116+
117+
require.Equal(t, prompt, tasks[0].InitialPrompt)
118+
})
119+
}

0 commit comments

Comments
 (0)