Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit63c1325

Browse files
feat(cli): add exp task create command (#19492)
Partially implementscoder/internal#893This isn't the full implementation of `coder exp tasks create` asdefined in the issue, but it is the minimum required to create a task.
1 parent73544a1 commit63c1325

File tree

3 files changed

+355
-0
lines changed

3 files changed

+355
-0
lines changed

‎cli/exp_task.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func (r *RootCmd) tasksCommand() *serpent.Command {
1414
},
1515
Children: []*serpent.Command{
1616
r.taskList(),
17+
r.taskCreate(),
1718
},
1819
}
1920
returncmd

‎cli/exp_taskcreate.go‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package cli
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"time"
7+
8+
"github.com/google/uuid"
9+
"golang.org/x/xerrors"
10+
11+
"github.com/coder/coder/v2/cli/cliui"
12+
"github.com/coder/coder/v2/codersdk"
13+
"github.com/coder/serpent"
14+
)
15+
16+
func (r*RootCmd)taskCreate()*serpent.Command {
17+
var (
18+
orgContext=NewOrganizationContext()
19+
client=new(codersdk.Client)
20+
21+
templateNamestring
22+
templateVersionNamestring
23+
presetNamestring
24+
taskInputstring
25+
)
26+
27+
return&serpent.Command{
28+
Use:"create [template]",
29+
Short:"Create an experimental task",
30+
Middleware:serpent.Chain(
31+
serpent.RequireRangeArgs(0,1),
32+
r.InitClient(client),
33+
),
34+
Options: serpent.OptionSet{
35+
{
36+
Flag:"input",
37+
Env:"CODER_TASK_INPUT",
38+
Value:serpent.StringOf(&taskInput),
39+
Required:true,
40+
},
41+
{
42+
Env:"CODER_TASK_TEMPLATE_NAME",
43+
Value:serpent.StringOf(&templateName),
44+
},
45+
{
46+
Env:"CODER_TASK_TEMPLATE_VERSION",
47+
Value:serpent.StringOf(&templateVersionName),
48+
},
49+
{
50+
Flag:"preset",
51+
Env:"CODER_TASK_PRESET_NAME",
52+
Value:serpent.StringOf(&presetName),
53+
Default:PresetNone,
54+
},
55+
},
56+
Handler:func(inv*serpent.Invocation)error {
57+
var (
58+
ctx=inv.Context()
59+
expClient=codersdk.NewExperimentalClient(client)
60+
61+
templateVersionID uuid.UUID
62+
templateVersionPresetID uuid.UUID
63+
)
64+
65+
organization,err:=orgContext.Selected(inv,client)
66+
iferr!=nil {
67+
returnxerrors.Errorf("get current organization: %w",err)
68+
}
69+
70+
iflen(inv.Args)>0 {
71+
templateName,templateVersionName,_=strings.Cut(inv.Args[0],"@")
72+
}
73+
74+
iftemplateName=="" {
75+
returnxerrors.Errorf("template name not provided")
76+
}
77+
78+
iftemplateVersionName!="" {
79+
templateVersion,err:=client.TemplateVersionByOrganizationAndName(ctx,organization.ID,templateName,templateVersionName)
80+
iferr!=nil {
81+
returnxerrors.Errorf("get template version: %w",err)
82+
}
83+
84+
templateVersionID=templateVersion.ID
85+
}else {
86+
template,err:=client.TemplateByName(ctx,organization.ID,templateName)
87+
iferr!=nil {
88+
returnxerrors.Errorf("get template: %w",err)
89+
}
90+
91+
templateVersionID=template.ActiveVersionID
92+
}
93+
94+
ifpresetName!=PresetNone {
95+
templatePresets,err:=client.TemplateVersionPresets(ctx,templateVersionID)
96+
iferr!=nil {
97+
returnxerrors.Errorf("get template presets: %w",err)
98+
}
99+
100+
preset,err:=resolvePreset(templatePresets,presetName)
101+
iferr!=nil {
102+
returnxerrors.Errorf("resolve preset: %w",err)
103+
}
104+
105+
templateVersionPresetID=preset.ID
106+
}
107+
108+
workspace,err:=expClient.CreateTask(ctx,codersdk.Me, codersdk.CreateTaskRequest{
109+
TemplateVersionID:templateVersionID,
110+
TemplateVersionPresetID:templateVersionPresetID,
111+
Prompt:taskInput,
112+
})
113+
iferr!=nil {
114+
returnxerrors.Errorf("create task: %w",err)
115+
}
116+
117+
_,_=fmt.Fprintf(
118+
inv.Stdout,
119+
"The task %s has been created at %s!\n",
120+
cliui.Keyword(workspace.Name),
121+
cliui.Timestamp(time.Now()),
122+
)
123+
124+
returnnil
125+
},
126+
}
127+
}

‎cli/exp_taskcreate_test.go‎

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package cli_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"net/http/httptest"
8+
"net/url"
9+
"strings"
10+
"testing"
11+
12+
"github.com/google/uuid"
13+
"github.com/stretchr/testify/assert"
14+
"github.com/stretchr/testify/require"
15+
16+
"github.com/coder/coder/v2/cli/clitest"
17+
"github.com/coder/coder/v2/cli/cliui"
18+
"github.com/coder/coder/v2/coderd/httpapi"
19+
"github.com/coder/coder/v2/codersdk"
20+
"github.com/coder/coder/v2/testutil"
21+
"github.com/coder/serpent"
22+
)
23+
24+
funcTestTaskCreate(t*testing.T) {
25+
t.Parallel()
26+
27+
var (
28+
organizationID=uuid.New()
29+
templateID=uuid.New()
30+
templateVersionID=uuid.New()
31+
templateVersionPresetID=uuid.New()
32+
)
33+
34+
templateAndVersionFoundHandler:=func(t*testing.T,ctx context.Context,templateName,templateVersionName,presetName,promptstring) http.HandlerFunc {
35+
t.Helper()
36+
37+
returnfunc(w http.ResponseWriter,r*http.Request) {
38+
switchr.URL.Path {
39+
case"/api/v2/users/me/organizations":
40+
httpapi.Write(ctx,w,http.StatusOK, []codersdk.Organization{
41+
{MinimalOrganization: codersdk.MinimalOrganization{
42+
ID:organizationID,
43+
}},
44+
})
45+
casefmt.Sprintf("/api/v2/organizations/%s/templates/my-template/versions/my-template-version",organizationID):
46+
httpapi.Write(ctx,w,http.StatusOK, codersdk.TemplateVersion{
47+
ID:templateVersionID,
48+
})
49+
casefmt.Sprintf("/api/v2/organizations/%s/templates/my-template",organizationID):
50+
httpapi.Write(ctx,w,http.StatusOK, codersdk.Template{
51+
ID:templateID,
52+
ActiveVersionID:templateVersionID,
53+
})
54+
casefmt.Sprintf("/api/v2/templateversions/%s/presets",templateVersionID):
55+
httpapi.Write(ctx,w,http.StatusOK, []codersdk.Preset{
56+
{
57+
ID:templateVersionPresetID,
58+
Name:presetName,
59+
},
60+
})
61+
case"/api/experimental/tasks/me":
62+
varreq codersdk.CreateTaskRequest
63+
if!httpapi.Read(ctx,w,r,&req) {
64+
return
65+
}
66+
67+
assert.Equal(t,prompt,req.Prompt,"prompt mismatch")
68+
assert.Equal(t,templateVersionID,req.TemplateVersionID,"template version mismatch")
69+
70+
ifpresetName=="" {
71+
assert.Equal(t,uuid.Nil,req.TemplateVersionPresetID,"expected no template preset id")
72+
}else {
73+
assert.Equal(t,templateVersionPresetID,req.TemplateVersionPresetID,"template version preset id mismatch")
74+
}
75+
76+
httpapi.Write(ctx,w,http.StatusCreated, codersdk.Workspace{
77+
Name:"task-wild-goldfish-27",
78+
})
79+
default:
80+
t.Errorf("unexpected path: %s",r.URL.Path)
81+
}
82+
}
83+
}
84+
85+
tests:= []struct {
86+
args []string
87+
env []string
88+
expectErrorstring
89+
expectOutputstring
90+
handlerfunc(t*testing.T,ctx context.Context) http.HandlerFunc
91+
}{
92+
{
93+
args: []string{"my-template@my-template-version","--input","my custom prompt"},
94+
expectOutput:fmt.Sprintf("The task %s has been created",cliui.Keyword("task-wild-goldfish-27")),
95+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
96+
returntemplateAndVersionFoundHandler(t,ctx,"my-template","my-template-version","","my custom prompt")
97+
},
98+
},
99+
{
100+
args: []string{"my-template","--input","my custom prompt"},
101+
env: []string{"CODER_TASK_TEMPLATE_VERSION=my-template-version"},
102+
expectOutput:fmt.Sprintf("The task %s has been created",cliui.Keyword("task-wild-goldfish-27")),
103+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
104+
returntemplateAndVersionFoundHandler(t,ctx,"my-template","my-template-version","","my custom prompt")
105+
},
106+
},
107+
{
108+
args: []string{"--input","my custom prompt"},
109+
env: []string{"CODER_TASK_TEMPLATE_NAME=my-template","CODER_TASK_TEMPLATE_VERSION=my-template-version"},
110+
expectOutput:fmt.Sprintf("The task %s has been created",cliui.Keyword("task-wild-goldfish-27")),
111+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
112+
returntemplateAndVersionFoundHandler(t,ctx,"my-template","my-template-version","","my custom prompt")
113+
},
114+
},
115+
{
116+
env: []string{"CODER_TASK_TEMPLATE_NAME=my-template","CODER_TASK_TEMPLATE_VERSION=my-template-version","CODER_TASK_INPUT=my custom prompt"},
117+
expectOutput:fmt.Sprintf("The task %s has been created",cliui.Keyword("task-wild-goldfish-27")),
118+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
119+
returntemplateAndVersionFoundHandler(t,ctx,"my-template","my-template-version","","my custom prompt")
120+
},
121+
},
122+
{
123+
args: []string{"my-template","--input","my custom prompt"},
124+
expectOutput:fmt.Sprintf("The task %s has been created",cliui.Keyword("task-wild-goldfish-27")),
125+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
126+
returntemplateAndVersionFoundHandler(t,ctx,"my-template","","","my custom prompt")
127+
},
128+
},
129+
{
130+
args: []string{"my-template","--input","my custom prompt","--preset","my-preset"},
131+
expectOutput:fmt.Sprintf("The task %s has been created",cliui.Keyword("task-wild-goldfish-27")),
132+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
133+
returntemplateAndVersionFoundHandler(t,ctx,"my-template","","my-preset","my custom prompt")
134+
},
135+
},
136+
{
137+
args: []string{"my-template","--input","my custom prompt"},
138+
env: []string{"CODER_TASK_PRESET_NAME=my-preset"},
139+
expectOutput:fmt.Sprintf("The task %s has been created",cliui.Keyword("task-wild-goldfish-27")),
140+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
141+
returntemplateAndVersionFoundHandler(t,ctx,"my-template","","my-preset","my custom prompt")
142+
},
143+
},
144+
{
145+
args: []string{"my-template","--input","my custom prompt","--preset","not-real-preset"},
146+
expectError:`preset "not-real-preset" not found`,
147+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
148+
returntemplateAndVersionFoundHandler(t,ctx,"my-template","","my-preset","my custom prompt")
149+
},
150+
},
151+
{
152+
args: []string{"my-template@not-real-template-version","--input","my custom prompt"},
153+
expectError:httpapi.ResourceNotFoundResponse.Message,
154+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
155+
returnfunc(w http.ResponseWriter,r*http.Request) {
156+
switchr.URL.Path {
157+
case"/api/v2/users/me/organizations":
158+
httpapi.Write(ctx,w,http.StatusOK, []codersdk.Organization{
159+
{MinimalOrganization: codersdk.MinimalOrganization{
160+
ID:organizationID,
161+
}},
162+
})
163+
casefmt.Sprintf("/api/v2/organizations/%s/templates/my-template/versions/not-real-template-version",organizationID):
164+
httpapi.ResourceNotFound(w)
165+
default:
166+
t.Errorf("unexpected path: %s",r.URL.Path)
167+
}
168+
}
169+
},
170+
},
171+
{
172+
args: []string{"not-real-template","--input","my custom prompt"},
173+
expectError:httpapi.ResourceNotFoundResponse.Message,
174+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
175+
returnfunc(w http.ResponseWriter,r*http.Request) {
176+
switchr.URL.Path {
177+
case"/api/v2/users/me/organizations":
178+
httpapi.Write(ctx,w,http.StatusOK, []codersdk.Organization{
179+
{MinimalOrganization: codersdk.MinimalOrganization{
180+
ID:organizationID,
181+
}},
182+
})
183+
casefmt.Sprintf("/api/v2/organizations/%s/templates/not-real-template",organizationID):
184+
httpapi.ResourceNotFound(w)
185+
default:
186+
t.Errorf("unexpected path: %s",r.URL.Path)
187+
}
188+
}
189+
},
190+
},
191+
}
192+
193+
for_,tt:=rangetests {
194+
t.Run(strings.Join(tt.args,","),func(t*testing.T) {
195+
t.Parallel()
196+
197+
var (
198+
ctx=testutil.Context(t,testutil.WaitShort)
199+
srv=httptest.NewServer(tt.handler(t,ctx))
200+
client=new(codersdk.Client)
201+
args= []string{"exp","task","create"}
202+
sb strings.Builder
203+
errerror
204+
)
205+
206+
t.Cleanup(srv.Close)
207+
208+
client.URL,err=url.Parse(srv.URL)
209+
require.NoError(t,err)
210+
211+
inv,root:=clitest.New(t,append(args,tt.args...)...)
212+
inv.Environ=serpent.ParseEnviron(tt.env,"")
213+
inv.Stdout=&sb
214+
inv.Stderr=&sb
215+
clitest.SetupConfig(t,client,root)
216+
217+
err=inv.WithContext(ctx).Run()
218+
iftt.expectError=="" {
219+
assert.NoError(t,err)
220+
}else {
221+
assert.ErrorContains(t,err,tt.expectError)
222+
}
223+
224+
assert.Contains(t,sb.String(),tt.expectOutput)
225+
})
226+
}
227+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp