Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: add provisioners and jobs endspoints and CLI commands
  • Loading branch information
mafredri committed Jan 2, 2025
commit 650cbaf3ab2775c568dcd973209d4d94ff60fa22
125 changes: 125 additions & 0 deletions cli/provisionerjobs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package cli

import (
"fmt"

"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)

func (r *RootCmd) provisionerJobs() *serpent.Command {
cmd := &serpent.Command{
Use: "jobs",
Short: "View and manage provisioner jobs",
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Aliases: []string{"job"},
Children: []*serpent.Command{
r.provisionerJobsList(),
},
}
return cmd
}

func (r *RootCmd) provisionerJobsList() *serpent.Command {
type provisionerJobRow struct {
codersdk.ProvisionerJob `table:"provisioner_job,recursive_inline"`
OrganizaitonName string `json:"organization_name" table:"organization"`
Queue string `json:"-" table:"queue"`
}

var (
client = new(codersdk.Client)
orgContext = NewOrganizationContext()
formatter = cliui.NewOutputFormatter(
cliui.TableFormat([]provisionerJobRow{}, []string{"created at", "id", "organization", "status", "type", "queue", "tags"}),
cliui.JSONFormat(),
)
status []string
limit int64 = 50
)

cmd := &serpent.Command{
Use: "list",
Short: "List provisioner jobs",
Aliases: []string{"ls"},
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()
org, err := orgContext.Selected(inv, client)
if err != nil {
return xerrors.Errorf("current organization: %w", err)
}

jobs, err := client.OrganizationProvisionerJobs(ctx, org.ID, &codersdk.OrganizationProvisionerJobsOptions{
Status: convertSlice([]codersdk.ProvisionerJobStatus{}, status),
Limit: int(limit),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: paging?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to keep it simple for now, I realized we don't have a precedent on the CLI for paging so I didn't want to introduce it here. Granted --limit is also new, so I could remove that too but I was worried about how many entries there will be.

The original issue #15084 mentioned we should have "created at" (or rather "created after"), and I added that initially, but then I realized we don't have a precedent for filtering on time on the CLI either. (There's even a lack of serpent support there.) For this reason both pagination and created at filter have been omitted and should IMO be part of a grander plan for how to support these in a unified way on the CLI.

})
if err != nil {
return xerrors.Errorf("list provisioner jobs: %w", err)
}

if len(jobs) == 0 {
_, _ = fmt.Fprintln(inv.Stdout, "No provisioner jobs found")
return nil
}

var rows []provisionerJobRow
for _, job := range jobs {
row := provisionerJobRow{
ProvisionerJob: job,
OrganizaitonName: org.HumanName(),
}
if job.Status == codersdk.ProvisionerJobPending {
row.Queue = fmt.Sprintf("%d/%d", job.QueuePosition, job.QueueSize)
}
rows = append(rows, row)
}

out, err := formatter.Format(ctx, rows)
if err != nil {
return xerrors.Errorf("display provisioner daemons: %w", err)
}

_, _ = fmt.Fprintln(inv.Stdout, out)

return nil
},
}

cmd.Options = append(cmd.Options, []serpent.Option{
{
Flag: "status",
FlagShorthand: "s",
Env: "CODER_PROVISIONER_JOB_LIST_STATUS",
Description: "Filter by job status.",
Value: serpent.EnumArrayOf(&status, convertSlice([]string{}, codersdk.ProvisionerJobStatusEnums())...),
},
{
Flag: "limit",
FlagShorthand: "l",
Env: "CODER_PROVISIONER_JOB_LIST_LIMIT",
Description: "Limit the number of jobs returned.",
Value: serpent.Int64Of(&limit),
},
}...)

orgContext.AttachOptions(cmd)
formatter.AttachOptions(&cmd.Options)

return cmd
}

func convertSlice[D, S ~string](dstType []D, src []S) []D {
for _, item := range src {
dstType = append(dstType, D(item))
}
return dstType
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe throw this into slice as ToEnum 🤷‍♂️

Similar to ToStrings

// ToStrings works for any type where the base type is a string.
func ToStrings[T ~string](a []T) []string {
tmp := make([]string, 0, len(a))
for _, v := range a {
tmp = append(tmp, string(v))
}
return tmp
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can do 👍🏻

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, any good reason these utils under coderd? I wouldn't move it in this PR but could make it more accessible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no good reason at all. I think it just grew over time

93 changes: 93 additions & 0 deletions cli/provisioners.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package cli

import (
"fmt"

"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/serpent"
)

func (r *RootCmd) Provisioners() *serpent.Command {
cmd := &serpent.Command{
Use: "provisioners",
Short: "View and manage provisioner daemons and jobs",
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Aliases: []string{"provisioner"},
Children: []*serpent.Command{
r.provisionerList(),
r.provisionerJobs(),
},
}

return cmd
}

func (r *RootCmd) provisionerList() *serpent.Command {
type provisionerDaemonRow struct {
codersdk.ProvisionerDaemonWithStatus `table:"provisioner_daemon,recursive_inline"`
OrganizationName string `json:"organization_name" table:"organization"`
}
var (
client = new(codersdk.Client)
orgContext = NewOrganizationContext()
formatter = cliui.NewOutputFormatter(
cliui.TableFormat([]provisionerDaemonRow{}, []string{"name", "organization", "status" /*"type",*/, "created at", "last seen at", "version", "tags"}),
cliui.JSONFormat(),
)
)

cmd := &serpent.Command{
Use: "list",
Short: "List provisioner daemons in an organization",
Aliases: []string{"ls"},
Middleware: serpent.Chain(
serpent.RequireNArgs(0),
r.InitClient(client),
),
Handler: func(inv *serpent.Invocation) error {
ctx := inv.Context()

org, err := orgContext.Selected(inv, client)
if err != nil {
return xerrors.Errorf("current organization: %w", err)
}

daemons, err := client.OrganizationProvisionerDaemons(ctx, org.ID, nil)
if err != nil {
return xerrors.Errorf("list provisioner daemons: %w", err)
}

if len(daemons) == 0 {
_, _ = fmt.Fprintln(inv.Stdout, "No provisioner daemons found")
return nil
}

var rows []provisionerDaemonRow
for _, daemon := range daemons {
rows = append(rows, provisionerDaemonRow{
ProvisionerDaemonWithStatus: daemon,
OrganizationName: org.HumanName(),
})
}

out, err := formatter.Format(ctx, rows)
if err != nil {
return xerrors.Errorf("display provisioner daemons: %w", err)
}

_, _ = fmt.Fprintln(inv.Stdout, out)

return nil
},
}

orgContext.AttachOptions(cmd)
formatter.AttachOptions(&cmd.Options)

return cmd
}
6 changes: 5 additions & 1 deletion cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
}

func (r *RootCmd) AGPL() []*serpent.Command {
all := append(r.CoreSubcommands(), r.Server( /* Do not import coderd here. */ nil))
all := append(
r.CoreSubcommands(),
r.Server( /* Do not import coderd here. */ nil),
r.Provisioners(),
)
return all
}

Expand Down
Loading