github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/job_scaling_events.go (about)

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