Content-Length: 5806 | pFad | http://github.com/coder/coder/pull/19008.diff

thub.com diff --git a/cli/builds.go b/cli/builds.go new file mode 100644 index 0000000000000..8ad463f8c05d6 --- /dev/null +++ b/cli/builds.go @@ -0,0 +1,103 @@ +package cli + +import ( + "fmt" + "strconv" + "time" + + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/cli/cliui" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/serpent" +) + +type workspaceBuildListRow struct { + codersdk.WorkspaceBuild `table:"-"` + + BuildNumber string `json:"-" table:"build,default_sort"` + BuildID string `json:"-" table:"build id"` + Status string `json:"-" table:"status"` + Reason string `json:"-" table:"reason"` + CreatedAt string `json:"-" table:"created"` + Duration string `json:"-" table:"duration"` +} + +func workspaceBuildListRowFromBuild(build codersdk.WorkspaceBuild) workspaceBuildListRow { + status := codersdk.WorkspaceDisplayStatus(build.Job.Status, build.Transition) + createdAt := build.CreatedAt.Format("2006-01-02 15:04:05") + + duration := "" + if build.Job.CompletedAt != nil { + duration = build.Job.CompletedAt.Sub(build.CreatedAt).Truncate(time.Second).String() + } + + return workspaceBuildListRow{ + WorkspaceBuild: build, + BuildNumber: strconv.Itoa(int(build.BuildNumber)), + BuildID: build.ID.String(), + Status: status, + Reason: string(build.Reason), + CreatedAt: createdAt, + Duration: duration, + } +} + +func (r *RootCmd) builds() *serpent.Command { + return &serpent.Command{ + Use: "builds", + Short: "Manage workspace builds", + Children: []*serpent.Command{ + r.buildsList(), + }, + } +} + +func (r *RootCmd) buildsList() *serpent.Command { + var ( + formatter = cliui.NewOutputFormatter( + cliui.TableFormat( + []workspaceBuildListRow{}, + []string{"build", "build id", "status", "reason", "created", "duration"}, + ), + cliui.JSONFormat(), + ) + ) + client := new(codersdk.Client) + cmd := &serpent.Command{ + Annotations: workspaceCommand, + Use: "list ", + Short: "List builds for a workspace", + Aliases: []string{"ls"}, + Middleware: serpent.Chain( + serpent.RequireNArgs(1), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) + if err != nil { + return xerrors.Errorf("get workspace: %w", err) + } + + builds, err := client.WorkspaceBuildsByWorkspaceID(inv.Context(), workspace.ID) + if err != nil { + return xerrors.Errorf("get workspace builds: %w", err) + } + + rows := make([]workspaceBuildListRow, len(builds)) + for i, build := range builds { + rows[i] = workspaceBuildListRowFromBuild(build) + } + + out, err := formatter.Format(inv.Context(), rows) + if err != nil { + return err + } + + _, err = fmt.Fprintln(inv.Stdout, out) + return err + }, + } + formatter.AttachOptions(&cmd.Options) + return cmd +} diff --git a/cli/logs.go b/cli/logs.go new file mode 100644 index 0000000000000..d44b8a63edda5 --- /dev/null +++ b/cli/logs.go @@ -0,0 +1,67 @@ +package cli + +import ( + "fmt" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/codersdk" + "github.com/coder/serpent" +) + +func (r *RootCmd) logs() *serpent.Command { + var follow bool + client := new(codersdk.Client) + cmd := &serpent.Command{ + Annotations: workspaceCommand, + Use: "logs ", + Short: "Show logs for a workspace build", + Middleware: serpent.Chain( + serpent.RequireNArgs(1), + r.InitClient(client), + ), + Handler: func(inv *serpent.Invocation) error { + buildIDStr := inv.Args[0] + buildID, err := uuid.Parse(buildIDStr) + if err != nil { + return xerrors.Errorf("invalid build ID %q: %w", buildIDStr, err) + } + + logs, closer, err := client.WorkspaceBuildLogsAfter(inv.Context(), buildID, 0) + if err != nil { + return xerrors.Errorf("get build logs: %w", err) + } + defer closer.Close() + + for { + log, ok := <-logs + if !ok { + break + } + + // Simple format with timestamp and stage + timestamp := log.CreatedAt.Format("15:04:05") + if log.Stage != "" { + _, _ = fmt.Fprintf(inv.Stdout, "[%s] %s: %s\n", + timestamp, log.Stage, log.Output) + } else { + _, _ = fmt.Fprintf(inv.Stdout, "[%s] %s\n", + timestamp, log.Output) + } + } + return nil + }, + } + + cmd.Options = serpent.OptionSet{ + { + Flag: "follow", + FlagShorthand: "f", + Description: "Follow log output (stream real-time logs).", + Value: serpent.BoolOf(&follow), + }, + } + + return cmd +} diff --git a/cli/root.go b/cli/root.go index 54215a67401dd..19ed4c07fe825 100644 --- a/cli/root.go +++ b/cli/root.go @@ -107,11 +107,13 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command { // Workspace Commands r.autoupdate(), + r.builds(), r.configSSH(), r.create(), r.deleteWorkspace(), r.favorite(), r.list(), + r.logs(), r.open(), r.ping(), r.rename(), diff --git a/codersdk/workspacebuilds.go b/codersdk/workspacebuilds.go index 53d2a89290bca..df9d5a8dfec73 100644 --- a/codersdk/workspacebuilds.go +++ b/codersdk/workspacebuilds.go @@ -279,3 +279,14 @@ func (c *Client) WorkspaceBuildTimings(ctx context.Context, build uuid.UUID) (Wo var timings WorkspaceBuildTimings return timings, json.NewDecoder(res.Body).Decode(&timings) } + +func (c *Client) WorkspaceBuildsByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) ([]WorkspaceBuild, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/workspaces/%s/builds", workspaceID), nil) + if err != nil { + return nil, err + } + defer res.Body.Close() + + var builds []WorkspaceBuild + return builds, json.NewDecoder(res.Body).Decode(&builds) +}








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: http://github.com/coder/coder/pull/19008.diff

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy