github.com/hernad/nomad@v1.6.112/command/job_scaling_events.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package command
     5  
     6  import (
     7  	"fmt"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/hernad/nomad/api"
    14  	"github.com/mitchellh/cli"
    15  	"github.com/posener/complete"
    16  )
    17  
    18  // Ensure JobScalingEventsCommand satisfies the cli.Command interface.
    19  var _ cli.Command = &JobScalingEventsCommand{}
    20  
    21  // JobScalingEventsCommand implements cli.Command.
    22  type JobScalingEventsCommand struct {
    23  	Meta
    24  }
    25  
    26  // Help satisfies the cli.Command Help function.
    27  func (j *JobScalingEventsCommand) Help() string {
    28  	helpText := `
    29  Usage: nomad job scaling-events [options] <args>
    30  
    31    List the scaling events for the specified job.
    32  
    33    When ACLs are enabled, this command requires a token with either the
    34    'read-job' or 'read-job-scaling' capability for the job's namespace. The
    35    'list-jobs' capability is required to run the command with a job prefix
    36    instead of the exact job ID.
    37  
    38  General Options:
    39  
    40    ` + generalOptionsUsage(usageOptsDefault) + `
    41  
    42  Scaling-Events Options:
    43  
    44    -verbose
    45      Display full information.
    46  `
    47  	return strings.TrimSpace(helpText)
    48  }
    49  
    50  // Synopsis satisfies the cli.Command Synopsis function.
    51  func (j *JobScalingEventsCommand) Synopsis() string {
    52  	return "Display the most recent scaling events for a job"
    53  }
    54  
    55  func (j *JobScalingEventsCommand) AutocompleteFlags() complete.Flags {
    56  	return mergeAutocompleteFlags(j.Meta.AutocompleteFlags(FlagSetClient),
    57  		complete.Flags{
    58  			"-verbose": complete.PredictNothing,
    59  		})
    60  }
    61  
    62  // Name returns the name of this command.
    63  func (j *JobScalingEventsCommand) Name() string { return "job scaling-events" }
    64  
    65  // Run satisfies the cli.Command Run function.
    66  func (j *JobScalingEventsCommand) Run(args []string) int {
    67  
    68  	var verbose bool
    69  
    70  	flags := j.Meta.FlagSet(j.Name(), FlagSetClient)
    71  	flags.Usage = func() { j.Ui.Output(j.Help()) }
    72  	flags.BoolVar(&verbose, "verbose", false, "")
    73  	if err := flags.Parse(args); err != nil {
    74  		return 1
    75  	}
    76  
    77  	args = flags.Args()
    78  	if len(args) != 1 {
    79  		j.Ui.Error("This command takes one argument: <job_id>")
    80  		j.Ui.Error(commandErrorText(j))
    81  		return 1
    82  	}
    83  
    84  	// Get the HTTP client.
    85  	client, err := j.Meta.Client()
    86  	if err != nil {
    87  		j.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    88  		return 1
    89  	}
    90  
    91  	// Check if the job exists
    92  	jobIDPrefix := strings.TrimSpace(args[0])
    93  	jobID, namespace, err := j.JobIDByPrefix(client, jobIDPrefix, nil)
    94  	if err != nil {
    95  		j.Ui.Error(err.Error())
    96  		return 1
    97  	}
    98  
    99  	q := &api.QueryOptions{Namespace: namespace}
   100  	events, _, err := client.Jobs().ScaleStatus(jobID, q)
   101  	if err != nil {
   102  		j.Ui.Error(fmt.Sprintf("Error listing scaling events: %s", err))
   103  		return 1
   104  	}
   105  
   106  	// Check if any of the task groups have scaling events, otherwise exit
   107  	// indicating there are not any.
   108  	var haveEvents bool
   109  	for _, tg := range events.TaskGroups {
   110  		if tg.Events != nil {
   111  			haveEvents = true
   112  			break
   113  		}
   114  	}
   115  
   116  	if !haveEvents {
   117  		j.Ui.Output("No events found")
   118  		return 0
   119  	}
   120  
   121  	// Create our sorted list of events and output.
   122  	sortedList := sortedScalingEventList(events)
   123  	j.Ui.Output(formatList(formatScalingEventListOutput(sortedList, verbose, 0)))
   124  	return 0
   125  }
   126  
   127  func formatScalingEventListOutput(e scalingEventList, verbose bool, limit int) []string {
   128  
   129  	// If the limit is zero, aka no limit or the limit is greater than the
   130  	// number of events we have then set it this to the length of the event
   131  	// list.
   132  	if limit == 0 || limit > len(e) {
   133  		limit = len(e)
   134  	}
   135  
   136  	// Create the initial output heading.
   137  	output := make([]string, limit+1)
   138  	output[0] = "Task Group|Count|PrevCount"
   139  
   140  	// If we are outputting verbose information, add these fields to the header
   141  	// and then add our end date field.
   142  	if verbose {
   143  		output[0] += "|Error|Message|Eval ID"
   144  	}
   145  	output[0] += "|Date"
   146  
   147  	var i int
   148  
   149  	for i < limit {
   150  		output[i+1] = fmt.Sprintf("%s|%s|%v", e[i].name, valueOrNil(e[i].event.Count), e[i].event.PreviousCount)
   151  		if verbose {
   152  			output[i+1] += fmt.Sprintf("|%v|%s|%s",
   153  				e[i].event.Error, e[i].event.Message, valueOrNil(e[i].event.EvalID))
   154  		}
   155  		output[i+1] += fmt.Sprintf("|%v", formatTime(time.Unix(0, int64(e[i].event.Time))))
   156  		i++
   157  	}
   158  	return output
   159  }
   160  
   161  // sortedScalingEventList generates a time sorted list of scaling events as
   162  // provided by the api.JobScaleStatusResponse.
   163  func sortedScalingEventList(e *api.JobScaleStatusResponse) []groupEvent {
   164  
   165  	// sortedList is our output list.
   166  	var sortedList scalingEventList
   167  
   168  	// Iterate over the response object to create a sorted list.
   169  	for group, status := range e.TaskGroups {
   170  		for _, event := range status.Events {
   171  			sortedList = append(sortedList, groupEvent{name: group, event: event})
   172  		}
   173  	}
   174  	sort.Sort(sortedList)
   175  
   176  	return sortedList
   177  }
   178  
   179  // valueOrNil helps format the event output in cases where the object has a
   180  // potential to be nil.
   181  func valueOrNil(i interface{}) string {
   182  	switch t := i.(type) {
   183  	case *int64:
   184  		if t != nil {
   185  			return strconv.FormatInt(*t, 10)
   186  		}
   187  	case *string:
   188  		if t != nil {
   189  			return *t
   190  		}
   191  	}
   192  	return ""
   193  }
   194  
   195  // scalingEventList is a helper list of all events for the job which allows us
   196  // to sort based on time.
   197  type scalingEventList []groupEvent
   198  
   199  // groupEvent contains all the required information of an individual group
   200  // scaling event.
   201  type groupEvent struct {
   202  	name  string
   203  	event api.ScalingEvent
   204  }
   205  
   206  // Len satisfies the Len function on the sort.Interface.
   207  func (s scalingEventList) Len() int {
   208  	return len(s)
   209  }
   210  
   211  // Less satisfies the Less function on the sort.Interface and sorts by the
   212  // event time.
   213  func (s scalingEventList) Less(i, j int) bool {
   214  	return s[i].event.Time > s[j].event.Time
   215  }
   216  
   217  // Swap satisfies the Swap function on the sort.Interface.
   218  func (s scalingEventList) Swap(i, j int) {
   219  	s[i], s[j] = s[j], s[i]
   220  }