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)
+}
--- 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