Add job list to API and CLI

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
This commit is contained in:
Gabriel Adrian Samfira 2023-06-23 21:15:46 +00:00
parent b6a02db446
commit 1287a93cf2
8 changed files with 148 additions and 2 deletions

View file

@ -306,3 +306,17 @@ func (a *APIController) ListProviders(w http.ResponseWriter, r *http.Request) {
log.Printf("failed to encode response: %q", err)
}
}
func (a *APIController) ListAllJobs(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
jobs, err := a.r.ListAllJobs(ctx)
if err != nil {
handleError(w, err)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(jobs); err != nil {
log.Printf("failed to encode response: %q", err)
}
}

View file

@ -94,6 +94,13 @@ func NewAPIRouter(han *controllers.APIController, logWriter io.Writer, authMiddl
apiRouter.Handle("/metrics-token/", http.HandlerFunc(han.MetricsTokenHandler)).Methods("GET", "OPTIONS")
apiRouter.Handle("/metrics-token", http.HandlerFunc(han.MetricsTokenHandler)).Methods("GET", "OPTIONS")
//////////
// Jobs //
//////////
// List all jobs
apiRouter.Handle("/jobs/", http.HandlerFunc(han.ListAllJobs)).Methods("GET", "OPTIONS")
apiRouter.Handle("/jobs", http.HandlerFunc(han.ListAllJobs)).Methods("GET", "OPTIONS")
///////////
// Pools //
///////////

View file

@ -183,6 +183,19 @@ func (c *Client) DeleteRunner(instanceName string) error {
return nil
}
func (c *Client) ListAllJobs() ([]params.Job, error) {
url := fmt.Sprintf("%s/api/v1/jobs", c.Config.BaseURL)
var response []params.Job
resp, err := c.client.R().
SetResult(&response).
Get(url)
if err != nil || resp.IsError() {
return response, c.handleError(err, resp)
}
return response, nil
}
func (c *Client) ListPoolInstances(poolID string) ([]params.Instance, error) {
url := fmt.Sprintf("%s/api/v1/pools/%s/instances", c.Config.BaseURL, poolID)

77
cmd/garm-cli/cmd/jobs.go Normal file
View file

@ -0,0 +1,77 @@
// Copyright 2023 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package cmd
import (
"fmt"
"github.com/cloudbase/garm/params"
"github.com/google/uuid"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"
)
// runnerCmd represents the runner command
var jobsCmd = &cobra.Command{
Use: "job",
SilenceUsage: true,
Short: "Information about jobs",
Long: `Query information about jobs.`,
Run: nil,
}
var jobsListCmd = &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List jobs",
Long: `List all jobs currently recorded in the system.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
if needsInit {
return errNeedsInitError
}
jobs, err := cli.ListAllJobs()
if err != nil {
return err
}
formatJobs(jobs)
return nil
},
}
func formatJobs(jobs []params.Job) {
t := table.NewWriter()
header := table.Row{"ID", "Name", "Status", "Conclusion", "Runner Name", "Locked by"}
t.AppendHeader(header)
for _, job := range jobs {
lockedBy := ""
if job.LockedBy != uuid.Nil {
lockedBy = job.LockedBy.String()
}
t.AppendRow(table.Row{job.ID, job.Name, job.Status, job.Conclusion, job.RunnerName, lockedBy})
t.AppendSeparator()
}
fmt.Println(t.Render())
}
func init() {
jobsCmd.AddCommand(
jobsListCmd,
)
rootCmd.AddCommand(jobsCmd)
}

View file

@ -116,6 +116,7 @@ type JobsStore interface {
CreateOrUpdateJob(ctx context.Context, job params.Job) (params.Job, error)
ListEntityJobsByStatus(ctx context.Context, entityType params.PoolType, entityID string, status params.JobStatus) ([]params.Job, error)
ListJobsByStatus(ctx context.Context, status params.JobStatus) ([]params.Job, error)
ListAllJobs(ctx context.Context) ([]params.Job, error)
GetJobByID(ctx context.Context, jobID int64) (params.Job, error)
DeleteJob(ctx context.Context, jobID int64) error

View file

@ -261,6 +261,28 @@ func (s *sqlDatabase) ListEntityJobsByStatus(ctx context.Context, entityType par
return ret, nil
}
func (s *sqlDatabase) ListAllJobs(ctx context.Context) ([]params.Job, error) {
var jobs []WorkflowJob
query := s.conn.Model(&WorkflowJob{})
if err := query.Find(&jobs); err.Error != nil {
if errors.Is(err.Error, gorm.ErrRecordNotFound) {
return []params.Job{}, nil
}
return nil, err.Error
}
ret := make([]params.Job, len(jobs))
for idx, job := range jobs {
jobParam, err := sqlWorkflowJobToParamsJob(job)
if err != nil {
return nil, errors.Wrap(err, "converting job")
}
ret[idx] = jobParam
}
return ret, nil
}
// GetJobByID gets a job by id.
func (s *sqlDatabase) GetJobByID(ctx context.Context, jobID int64) (params.Job, error) {
var job WorkflowJob

View file

@ -429,9 +429,9 @@ type Job struct {
// ID is the ID of the job.
ID int64 `json:"id"`
// RunID is the ID of the workflow run. A run may have multiple jobs.
RunID int64
RunID int64 `json:"run_id"`
// Action is the specific activity that triggered the event.
Action string `json:"run_id"`
Action string `json:"action"`
// Conclusion is the outcome of the job.
// Possible values: "success", "failure", "neutral", "cancelled", "skipped",
// "timed_out", "action_required"

View file

@ -125,3 +125,15 @@ func (r *Runner) UpdatePoolByID(ctx context.Context, poolID string, param params
}
return newPool, nil
}
func (r *Runner) ListAllJobs(ctx context.Context) ([]params.Job, error) {
if !auth.IsAdmin(ctx) {
return []params.Job{}, runnerErrors.ErrUnauthorized
}
jobs, err := r.store.ListAllJobs(ctx)
if err != nil {
return nil, errors.Wrap(err, "fetching jobs")
}
return jobs, nil
}