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 }