Skip to content

feat: add provisioner daemon and jobs endpoints and commands #15940

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 21 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
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
120 changes: 120 additions & 0 deletions cli/provisionerjobs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package cli

import (
"fmt"

"golang.org/x/xerrors"

"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/coderd/util/slice"
"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"`
OrganizationName 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
)

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: slice.ToStringEnums[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,
OrganizationName: 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, slice.ToStrings(codersdk.ProvisionerJobStatusEnums())...),
},
{
Flag: "limit",
FlagShorthand: "l",
Env: "CODER_PROVISIONER_JOB_LIST_LIMIT",
Description: "Limit the number of jobs returned.",
Default: "50",
Value: serpent.Int64Of(&limit),
},
}...)

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

return cmd
}
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: "provisioner",
Short: "View and manage provisioner daemons and jobs",
Handler: func(inv *serpent.Invocation) error {
return inv.Command.HelpHandler(inv)
},
Aliases: []string{"provisioners"},
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", "key name", "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
}
Loading
Loading