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 }