Ctrl K
ComunidadGuías…

cli/provisionerjobs.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package cli

import (
	"fmt"
	"slices"

	"github.com/google/uuid"
	"golang.org/x/xerrors"

	"github.com/coder/coder/v2/cli/cliui"
	"github.com/coder/coder/v2/coderd/util/ptr"
	"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.provisionerJobsCancel(),
			r.provisionerJobsList(),
		},
	}
	return cmd
}

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

	var (
		orgContext = NewOrganizationContext()
		formatter  = cliui.NewOutputFormatter(
			cliui.TableFormat([]provisionerJobRow{}, []string{"created at", "id", "type", "template display name", "status", "queue", "tags"}),
			cliui.JSONFormat(),
		)
		status    []string
		limit     int64
		initiator string
	)

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

			if initiator != "" {
				user, err := client.User(ctx, initiator)
				if err != nil {
					return xerrors.Errorf("initiator not found: %s", initiator)
				}
				initiator = user.ID.String()
			}

			jobs, err := client.OrganizationProvisionerJobs(ctx, org.ID, &codersdk.OrganizationProvisionerJobsOptions{
				Status:    slice.StringEnums[codersdk.ProvisionerJobStatus](status),
				Limit:     int(limit),
				Initiator: initiator,
			})
			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)
			}
			// Sort manually because the cliui table truncates timestamps and
			// produces an unstable sort with timestamps that are all the same.
			slices.SortStableFunc(rows, func(a provisionerJobRow, b provisionerJobRow) int {
				return a.CreatedAt.Compare(b.CreatedAt)
			})

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

			if out == "" {
				cliui.Infof(inv.Stderr, "No provisioner jobs found.")
				return nil
			}

			_, _ = 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),
		},
		{
			Flag:          "initiator",
			FlagShorthand: "i",
			Env:           "CODER_PROVISIONER_JOB_LIST_INITIATOR",
			Description:   "Filter by initiator (user ID or username).",
			Value:         serpent.StringOf(&initiator),
		},
	}...)

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

	return cmd
}

func (r *RootCmd) provisionerJobsCancel() *serpent.Command {
	orgContext := NewOrganizationContext()
	cmd := &serpent.Command{
		Use:   "cancel <job_id>",
		Short: "Cancel a provisioner job",
		Middleware: serpent.Chain(
			serpent.RequireNArgs(1),
		),
		Handler: func(inv *serpent.Invocation) error {
			ctx := inv.Context()
			client, err := r.InitClient(inv)
			if err != nil {
				return err
			}
			org, err := orgContext.Selected(inv, client)
			if err != nil {
				return xerrors.Errorf("current organization: %w", err)
			}

			jobID, err := uuid.Parse(inv.Args[0])
			if err != nil {
				return xerrors.Errorf("invalid job ID: %w", err)
			}

			job, err := client.OrganizationProvisionerJob(ctx, org.ID, jobID)
			if err != nil {
				return xerrors.Errorf("get provisioner job: %w", err)
			}

			switch job.Type {
			case codersdk.ProvisionerJobTypeTemplateVersionDryRun:
				_, _ = fmt.Fprintf(inv.Stdout, "Canceling template version dry run job %s...\n", job.ID)
				err = client.CancelTemplateVersionDryRun(ctx, ptr.NilToEmpty(job.Input.TemplateVersionID), job.ID)
			case codersdk.ProvisionerJobTypeTemplateVersionImport:
				_, _ = fmt.Fprintf(inv.Stdout, "Canceling template version import job %s...\n", job.ID)
				err = client.CancelTemplateVersion(ctx, ptr.NilToEmpty(job.Input.TemplateVersionID))
			case codersdk.ProvisionerJobTypeWorkspaceBuild:
				_, _ = fmt.Fprintf(inv.Stdout, "Canceling workspace build job %s...\n", job.ID)
				err = client.CancelWorkspaceBuild(ctx, ptr.NilToEmpty(job.Input.WorkspaceBuildID), codersdk.CancelWorkspaceBuildParams{})
			}
			if err != nil {
				return xerrors.Errorf("cancel provisioner job: %w", err)
			}

			_, _ = fmt.Fprintln(inv.Stdout, "Job canceled")

			return nil
		},
	}

	orgContext.AttachOptions(cmd)

	return cmd
}