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

Commitd0e4d4c

Browse files
feat(cli): add exp task logs
There isn't a supporting API for this yet. This is a skeletonimplementation prepared in advance for the API being available.
1 parent0bac5a4 commitd0e4d4c

File tree

3 files changed

+248
-2
lines changed

3 files changed

+248
-2
lines changed

‎cli/exp_task.go‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ func (r *RootCmd) tasksCommand() *serpent.Command {
1313
returni.Command.HelpHandler(i)
1414
},
1515
Children: []*serpent.Command{
16-
r.taskList(),
1716
r.taskCreate(),
18-
r.taskStatus(),
1917
r.taskDelete(),
18+
r.taskList(),
19+
r.taskLogs(),
2020
r.taskSend(),
21+
r.taskStatus(),
2122
},
2223
}
2324
returncmd

‎cli/exp_task_logs.go‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package cli
2+
3+
import (
4+
"encoding/json"
5+
6+
"github.com/google/uuid"
7+
"golang.org/x/xerrors"
8+
9+
"github.com/coder/coder/v2/codersdk"
10+
"github.com/coder/serpent"
11+
)
12+
13+
func (r*RootCmd)taskLogs()*serpent.Command {
14+
cmd:=&serpent.Command{
15+
Use:"logs <task>",
16+
Short:"Show a task's logs",
17+
Middleware:serpent.Chain(
18+
serpent.RequireNArgs(1),
19+
),
20+
Handler:func(inv*serpent.Invocation)error {
21+
client,err:=r.InitClient(inv)
22+
iferr!=nil {
23+
returnxerrors.Errorf("")
24+
}
25+
26+
var (
27+
ctx=inv.Context()
28+
exp=codersdk.NewExperimentalClient(client)
29+
task=inv.Args[0]
30+
taskID uuid.UUID
31+
)
32+
33+
ifid,err:=uuid.Parse(task);err==nil {
34+
taskID=id
35+
}else {
36+
ws,err:=namedWorkspace(ctx,client,task)
37+
iferr!=nil {
38+
returnxerrors.Errorf("resolve task %q: %w",task,err)
39+
}
40+
41+
taskID=ws.ID
42+
}
43+
44+
logs,err:=exp.TaskLogs(ctx,codersdk.Me,taskID)
45+
iferr!=nil {
46+
returnxerrors.Errorf("get task logs: %w",err)
47+
}
48+
49+
enc:=json.NewEncoder(inv.Stdout)
50+
for_,log:=rangelogs.Logs {
51+
iferr:=enc.Encode(log);err!=nil {
52+
returnxerrors.Errorf("encode task log: %w",err)
53+
}
54+
}
55+
56+
returnnil
57+
},
58+
}
59+
60+
returncmd
61+
}

‎cli/exp_task_logs_test.go‎

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package cli_test
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"net/http"
8+
"net/http/httptest"
9+
"strings"
10+
"testing"
11+
"time"
12+
13+
"github.com/google/uuid"
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
17+
"github.com/coder/coder/v2/cli/clitest"
18+
"github.com/coder/coder/v2/coderd/httpapi"
19+
"github.com/coder/coder/v2/coderd/util/slice"
20+
"github.com/coder/coder/v2/codersdk"
21+
"github.com/coder/coder/v2/testutil"
22+
)
23+
24+
funcTest_TaskLogs(t*testing.T) {
25+
t.Parallel()
26+
27+
var (
28+
clock=time.Date(2025,8,26,12,34,56,0,time.UTC)
29+
30+
taskID=uuid.MustParse("11111111-1111-1111-1111-111111111111")
31+
taskName="task-workspace"
32+
33+
taskLogs= []codersdk.TaskLogEntry{
34+
{
35+
ID:0,
36+
Content:"What is 1 + 1?",
37+
Type:codersdk.TaskLogTypeInput,
38+
Time:clock,
39+
},
40+
{
41+
ID:1,
42+
Content:"2",
43+
Type:codersdk.TaskLogTypeOutput,
44+
Time:clock.Add(1*time.Second),
45+
},
46+
}
47+
)
48+
49+
tests:= []struct {
50+
args []string
51+
expectLogs []codersdk.TaskLogEntry
52+
expectErrorstring
53+
handlerfunc(t*testing.T,ctx context.Context) http.HandlerFunc
54+
}{
55+
{
56+
args: []string{taskName},
57+
expectLogs:taskLogs,
58+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
59+
returnfunc(w http.ResponseWriter,r*http.Request) {
60+
switchr.URL.Path {
61+
casefmt.Sprintf("/api/v2/users/me/workspace/%s",taskName):
62+
httpapi.Write(ctx,w,http.StatusOK, codersdk.Workspace{
63+
ID:taskID,
64+
})
65+
casefmt.Sprintf("/api/experimental/tasks/me/%s/logs",taskID.String()):
66+
httpapi.Write(ctx,w,http.StatusOK, codersdk.TaskLogsResponse{
67+
Logs:taskLogs,
68+
})
69+
default:
70+
t.Errorf("unexpected path: %s",r.URL.Path)
71+
}
72+
}
73+
},
74+
},
75+
{
76+
args: []string{taskID.String()},
77+
expectLogs:taskLogs,
78+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
79+
returnfunc(w http.ResponseWriter,r*http.Request) {
80+
switchr.URL.Path {
81+
casefmt.Sprintf("/api/experimental/tasks/me/%s/logs",taskID.String()):
82+
httpapi.Write(ctx,w,http.StatusOK, codersdk.TaskLogsResponse{
83+
Logs:taskLogs,
84+
})
85+
default:
86+
t.Errorf("unexpected path: %s",r.URL.Path)
87+
}
88+
}
89+
},
90+
},
91+
{
92+
args: []string{"doesnotexist"},
93+
expectError:httpapi.ResourceNotFoundResponse.Message,
94+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
95+
returnfunc(w http.ResponseWriter,r*http.Request) {
96+
switchr.URL.Path {
97+
case"/api/v2/users/me/workspace/doesnotexist":
98+
httpapi.ResourceNotFound(w)
99+
default:
100+
t.Errorf("unexpected path: %s",r.URL.Path)
101+
}
102+
}
103+
},
104+
},
105+
{
106+
args: []string{uuid.Nil.String()},// uuid does not exist
107+
expectError:httpapi.ResourceNotFoundResponse.Message,
108+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
109+
returnfunc(w http.ResponseWriter,r*http.Request) {
110+
switchr.URL.Path {
111+
casefmt.Sprintf("/api/experimental/tasks/me/%s/logs",uuid.Nil.String()):
112+
httpapi.ResourceNotFound(w)
113+
default:
114+
t.Errorf("unexpected path: %s",r.URL.Path)
115+
}
116+
}
117+
},
118+
},
119+
{
120+
args: []string{"err-fetching-logs"},
121+
expectError:assert.AnError.Error(),
122+
handler:func(t*testing.T,ctx context.Context) http.HandlerFunc {
123+
returnfunc(w http.ResponseWriter,r*http.Request) {
124+
switchr.URL.Path {
125+
case"/api/v2/users/me/workspace/err-fetching-logs":
126+
httpapi.Write(ctx,w,http.StatusOK, codersdk.Workspace{
127+
ID:taskID,
128+
})
129+
casefmt.Sprintf("/api/experimental/tasks/me/%s/logs",taskID.String()):
130+
httpapi.InternalServerError(w,assert.AnError)
131+
default:
132+
t.Errorf("unexpected path: %s",r.URL.Path)
133+
}
134+
}
135+
},
136+
},
137+
}
138+
139+
for_,tt:=rangetests {
140+
t.Run(strings.Join(tt.args,","),func(t*testing.T) {
141+
t.Parallel()
142+
143+
var (
144+
ctx=testutil.Context(t,testutil.WaitShort)
145+
srv=httptest.NewServer(tt.handler(t,ctx))
146+
client=codersdk.New(testutil.MustURL(t,srv.URL))
147+
args= []string{"exp","task","logs"}
148+
stdout strings.Builder
149+
errerror
150+
)
151+
152+
t.Cleanup(srv.Close)
153+
154+
inv,root:=clitest.New(t,append(args,tt.args...)...)
155+
inv.Stdout=&stdout
156+
inv.Stderr=&stdout
157+
clitest.SetupConfig(t,client,root)
158+
159+
err=inv.WithContext(ctx).Run()
160+
iftt.expectError=="" {
161+
assert.NoError(t,err)
162+
}else {
163+
assert.ErrorContains(t,err,tt.expectError)
164+
}
165+
166+
iftt.expectLogs!=nil {
167+
lines:=strings.Split(stdout.String(),"\n")
168+
lines=slice.Filter(lines,func(sstring)bool {
169+
returns!=""
170+
})
171+
172+
require.Len(t,lines,len(tt.expectLogs))
173+
174+
fori,line:=rangelines {
175+
varlog codersdk.TaskLogEntry
176+
err=json.NewDecoder(strings.NewReader(line)).Decode(&log)
177+
require.NoError(t,err)
178+
179+
assert.Equal(t,tt.expectLogs[i],log)
180+
}
181+
}
182+
})
183+
}
184+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp