mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(cli): add exp task send (#19922)
Closes https://github.com/coder/internal/issues/895 --------- Co-authored-by: Mathias Fredriksson <mafredri@gmail.com>
This commit is contained in:
co-authored by
Mathias Fredriksson
parent
0a79817050
commit
252f430eb9
@@ -17,6 +17,7 @@ func (r *RootCmd) tasksCommand() *serpent.Command {
|
||||
r.taskCreate(),
|
||||
r.taskStatus(),
|
||||
r.taskDelete(),
|
||||
r.taskSend(),
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/serpent"
|
||||
)
|
||||
|
||||
func (r *RootCmd) taskSend() *serpent.Command {
|
||||
var stdin bool
|
||||
|
||||
cmd := &serpent.Command{
|
||||
Use: "send <task> [<input> | --stdin]",
|
||||
Short: "Send input to a task",
|
||||
Middleware: serpent.RequireRangeArgs(1, 2),
|
||||
Options: serpent.OptionSet{
|
||||
{
|
||||
Name: "stdin",
|
||||
Flag: "stdin",
|
||||
Description: "Reads the input from stdin.",
|
||||
Value: serpent.BoolOf(&stdin),
|
||||
},
|
||||
},
|
||||
Handler: func(inv *serpent.Invocation) error {
|
||||
client, err := r.InitClient(inv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
ctx = inv.Context()
|
||||
exp = codersdk.NewExperimentalClient(client)
|
||||
task = inv.Args[0]
|
||||
|
||||
taskInput string
|
||||
taskID uuid.UUID
|
||||
)
|
||||
|
||||
if stdin {
|
||||
bytes, err := io.ReadAll(inv.Stdin)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("reading stdio: %w", err)
|
||||
}
|
||||
|
||||
taskInput = string(bytes)
|
||||
} else {
|
||||
if len(inv.Args) != 2 {
|
||||
return xerrors.Errorf("expected an input for the task")
|
||||
}
|
||||
|
||||
taskInput = inv.Args[1]
|
||||
}
|
||||
|
||||
if id, err := uuid.Parse(task); err == nil {
|
||||
taskID = id
|
||||
} else {
|
||||
ws, err := namedWorkspace(ctx, client, task)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("resolve task: %w", err)
|
||||
}
|
||||
|
||||
taskID = ws.ID
|
||||
}
|
||||
|
||||
if err = exp.TaskSend(ctx, codersdk.Me, taskID, codersdk.TaskSendRequest{Input: taskInput}); err != nil {
|
||||
return xerrors.Errorf("send input to task: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package cli_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/coder/coder/v2/cli/clitest"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
func Test_TaskSend(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
taskName = "task-workspace"
|
||||
taskID = uuid.MustParse("11111111-1111-1111-1111-111111111111")
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
args []string
|
||||
stdin string
|
||||
expectError string
|
||||
handler func(t *testing.T, ctx context.Context) http.HandlerFunc
|
||||
}{
|
||||
{
|
||||
args: []string{taskName, "carry on with the task"},
|
||||
handler: func(t *testing.T, ctx context.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case fmt.Sprintf("/api/v2/users/me/workspace/%s", taskName):
|
||||
httpapi.Write(ctx, w, http.StatusOK, codersdk.Workspace{
|
||||
ID: taskID,
|
||||
})
|
||||
case fmt.Sprintf("/api/experimental/tasks/me/%s/send", taskID.String()):
|
||||
var req codersdk.TaskSendRequest
|
||||
if !httpapi.Read(ctx, w, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, "carry on with the task", req.Input)
|
||||
|
||||
httpapi.Write(ctx, w, http.StatusNoContent, nil)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
args: []string{taskID.String(), "carry on with the task"},
|
||||
handler: func(t *testing.T, ctx context.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case fmt.Sprintf("/api/experimental/tasks/me/%s/send", taskID.String()):
|
||||
var req codersdk.TaskSendRequest
|
||||
if !httpapi.Read(ctx, w, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, "carry on with the task", req.Input)
|
||||
|
||||
httpapi.Write(ctx, w, http.StatusNoContent, nil)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
args: []string{taskName, "--stdin"},
|
||||
stdin: "carry on with the task",
|
||||
handler: func(t *testing.T, ctx context.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case fmt.Sprintf("/api/v2/users/me/workspace/%s", taskName):
|
||||
httpapi.Write(ctx, w, http.StatusOK, codersdk.Workspace{
|
||||
ID: taskID,
|
||||
})
|
||||
case fmt.Sprintf("/api/experimental/tasks/me/%s/send", taskID.String()):
|
||||
var req codersdk.TaskSendRequest
|
||||
if !httpapi.Read(ctx, w, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, "carry on with the task", req.Input)
|
||||
|
||||
httpapi.Write(ctx, w, http.StatusNoContent, nil)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
args: []string{"doesnotexist", "some task input"},
|
||||
expectError: httpapi.ResourceNotFoundResponse.Message,
|
||||
handler: func(t *testing.T, ctx context.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/api/v2/users/me/workspace/doesnotexist":
|
||||
httpapi.ResourceNotFound(w)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
args: []string{uuid.Nil.String(), "some task input"},
|
||||
expectError: httpapi.ResourceNotFoundResponse.Message,
|
||||
handler: func(t *testing.T, ctx context.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case fmt.Sprintf("/api/experimental/tasks/me/%s/send", uuid.Nil.String()):
|
||||
httpapi.ResourceNotFound(w)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
args: []string{uuid.Nil.String(), "some task input"},
|
||||
expectError: assert.AnError.Error(),
|
||||
handler: func(t *testing.T, ctx context.Context) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case fmt.Sprintf("/api/experimental/tasks/me/%s/send", uuid.Nil.String()):
|
||||
httpapi.InternalServerError(w, assert.AnError)
|
||||
default:
|
||||
t.Errorf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(strings.Join(tt.args, ","), func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var (
|
||||
ctx = testutil.Context(t, testutil.WaitShort)
|
||||
srv = httptest.NewServer(tt.handler(t, ctx))
|
||||
client = codersdk.New(testutil.MustURL(t, srv.URL))
|
||||
args = []string{"exp", "task", "send"}
|
||||
err error
|
||||
)
|
||||
|
||||
t.Cleanup(srv.Close)
|
||||
|
||||
inv, root := clitest.New(t, append(args, tt.args...)...)
|
||||
inv.Stdin = strings.NewReader(tt.stdin)
|
||||
clitest.SetupConfig(t, client, root)
|
||||
|
||||
err = inv.WithContext(ctx).Run()
|
||||
if tt.expectError == "" {
|
||||
assert.NoError(t, err)
|
||||
} else {
|
||||
assert.ErrorContains(t, err, tt.expectError)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user