github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/command/job_scale.go (about) 1 package command 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "strings" 8 9 "github.com/hashicorp/nomad/api" 10 "github.com/mitchellh/cli" 11 "github.com/posener/complete" 12 ) 13 14 // Ensure JobScaleCommand satisfies the cli.Command interface. 15 var _ cli.Command = &JobScaleCommand{} 16 17 // JobScaleCommand implements cli.Command. 18 type JobScaleCommand struct { 19 Meta 20 } 21 22 // Help satisfies the cli.Command Help function. 23 func (j *JobScaleCommand) Help() string { 24 helpText := ` 25 Usage: nomad job scale [options] <job> [<group>] <count> 26 27 Perform a scaling action by altering the count within a job group. 28 29 Upon successful job submission, this command will immediately 30 enter an interactive monitor. This is useful to watch Nomad's 31 internals make scheduling decisions and place the submitted work 32 onto nodes. The monitor will end once job placement is done. It 33 is safe to exit the monitor early using ctrl+c. 34 35 When ACLs are enabled, this command requires a token with the 'scale-job' 36 capability for the job's namespace. 37 38 General Options: 39 40 ` + generalOptionsUsage(usageOptsDefault) + ` 41 42 Scale Options: 43 44 -detach 45 Return immediately instead of entering monitor mode. After job scaling, 46 the evaluation ID will be printed to the screen, which can be used to 47 examine the evaluation using the eval-status command. 48 49 -verbose 50 Display full information. 51 ` 52 return strings.TrimSpace(helpText) 53 } 54 55 // Synopsis satisfies the cli.Command Synopsis function. 56 func (j *JobScaleCommand) Synopsis() string { 57 return "Change the count of a Nomad job group" 58 } 59 60 func (j *JobScaleCommand) AutocompleteFlags() complete.Flags { 61 return mergeAutocompleteFlags(j.Meta.AutocompleteFlags(FlagSetClient), 62 complete.Flags{ 63 "-detach": complete.PredictNothing, 64 "-verbose": complete.PredictNothing, 65 }) 66 } 67 68 // Name returns the name of this command. 69 func (j *JobScaleCommand) Name() string { return "job scale" } 70 71 // Run satisfies the cli.Command Run function. 72 func (j *JobScaleCommand) Run(args []string) int { 73 var detach, verbose bool 74 75 flags := j.Meta.FlagSet(j.Name(), FlagSetClient) 76 flags.Usage = func() { j.Ui.Output(j.Help()) } 77 flags.BoolVar(&detach, "detach", false, "") 78 flags.BoolVar(&verbose, "verbose", false, "") 79 if err := flags.Parse(args); err != nil { 80 return 1 81 } 82 83 var jobString, countString, groupString string 84 args = flags.Args() 85 86 // It is possible to specify either 2 or 3 arguments. Check and assign the 87 // args so they can be validate later on. 88 if numArgs := len(args); numArgs < 2 || numArgs > 3 { 89 j.Ui.Error("Command requires at least two arguments and no more than three") 90 return 1 91 } else if numArgs == 3 { 92 groupString = args[1] 93 countString = args[2] 94 } else { 95 countString = args[1] 96 } 97 jobString = args[0] 98 99 // Convert the count string arg to an int as required by the API. 100 count, err := strconv.Atoi(countString) 101 if err != nil { 102 j.Ui.Error(fmt.Sprintf("Failed to convert count string to int: %s", err)) 103 return 1 104 } 105 106 // Get the HTTP client. 107 client, err := j.Meta.Client() 108 if err != nil { 109 j.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 110 return 1 111 } 112 113 // Detail the job so we can perform addition checks before submitting the 114 // scaling request. 115 job, _, err := client.Jobs().ScaleStatus(jobString, nil) 116 if err != nil { 117 j.Ui.Error(fmt.Sprintf("Error querying job: %v", err)) 118 return 1 119 } 120 121 if err := j.performGroupCheck(job.TaskGroups, &groupString); err != nil { 122 j.Ui.Error(err.Error()) 123 return 1 124 } 125 126 // This is our default message added to scaling submissions. 127 msg := "submitted using the Nomad CLI" 128 129 // Perform the scaling action. 130 resp, _, err := client.Jobs().Scale(jobString, groupString, &count, msg, false, nil, nil) 131 if err != nil { 132 j.Ui.Error(fmt.Sprintf("Error submitting scaling request: %s", err)) 133 return 1 134 } 135 136 // Print any warnings if we have some. 137 if resp.Warnings != "" { 138 j.Ui.Output( 139 j.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings))) 140 } 141 142 // If we are to detach, log the evaluation ID and exit. 143 if detach { 144 j.Ui.Output("Evaluation ID: " + resp.EvalID) 145 return 0 146 } 147 148 // Truncate the ID unless full length is requested. 149 length := shortId 150 if verbose { 151 length = fullId 152 } 153 154 // Create and monitor the evaluation. 155 mon := newMonitor(j.Ui, client, length) 156 return mon.monitor(resp.EvalID) 157 } 158 159 // performGroupCheck performs logic to ensure the user specified the correct 160 // group argument. 161 func (j *JobScaleCommand) performGroupCheck(groups map[string]api.TaskGroupScaleStatus, group *string) error { 162 163 // If the job contains multiple groups and the user did not supply a task 164 // group, return an error. 165 if len(groups) > 1 && *group == "" { 166 return errors.New("Group name required") 167 } 168 169 // We have to iterate the map to have any idea what task groups we are 170 // dealing with. 171 for groupName := range groups { 172 173 // If the job has a single task group, and the user did not supply a 174 // task group, it is assumed we scale the only group in the job. 175 if len(groups) == 1 && *group == "" { 176 *group = groupName 177 return nil 178 } 179 180 // If we found a match, return. 181 if groupName == *group { 182 return nil 183 } 184 } 185 186 // If we got here, we didn't find a match and therefore return an error. 187 return fmt.Errorf("Group %v not found within job", *group) 188 }