github.com/hernad/nomad@v1.6.112/command/recommendation_apply.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package command 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/hernad/nomad/api" 11 12 "github.com/mitchellh/cli" 13 "github.com/posener/complete" 14 ) 15 16 // Ensure RecommendationApplyCommand satisfies the cli.Command interface. 17 var _ cli.Command = &RecommendationApplyCommand{} 18 19 // RecommendationApplyCommand implements cli.Command. 20 type RecommendationApplyCommand struct { 21 RecommendationAutocompleteCommand 22 } 23 24 // Help satisfies the cli.Command Help function. 25 func (r *RecommendationApplyCommand) Help() string { 26 helpText := ` 27 Usage: nomad recommendation apply [options] <recommendation_ids> 28 29 Apply one or more Nomad recommendations. 30 31 When ACLs are enabled, this command requires a token with the 'submit-job', 32 'read-job', and 'submit-recommendation' capabilities for the 33 recommendation's namespace. 34 35 General Options: 36 37 ` + generalOptionsUsage(usageOptsDefault) + ` 38 39 Recommendation Apply Options: 40 41 -detach 42 Return immediately instead of entering monitor mode. After applying a 43 recommendation, the evaluation ID will be printed to the screen, which can 44 be used to examine the evaluation using the eval-status command. If applying 45 recommendations for multiple jobs, this value will always be true. 46 47 -policy-override 48 If set, any soft mandatory Sentinel policies will be overridden. This allows 49 a recommendation to be applied when it would be denied by a policy. 50 51 -verbose 52 Display full information. 53 ` 54 return strings.TrimSpace(helpText) 55 } 56 57 // Synopsis satisfies the cli.Command Synopsis function. 58 func (r *RecommendationApplyCommand) Synopsis() string { 59 return "Apply one or more Nomad recommendations" 60 } 61 62 func (r *RecommendationApplyCommand) AutocompleteFlags() complete.Flags { 63 return mergeAutocompleteFlags(r.Meta.AutocompleteFlags(FlagSetClient), 64 complete.Flags{ 65 "-detach": complete.PredictNothing, 66 "-policy-override": complete.PredictNothing, 67 "-verbose": complete.PredictNothing, 68 }) 69 } 70 71 // Name returns the name of this command. 72 func (r *RecommendationApplyCommand) Name() string { return "recommendation apply" } 73 74 // Run satisfies the cli.Command Run function. 75 func (r *RecommendationApplyCommand) Run(args []string) int { 76 var detach, override, verbose bool 77 78 flags := r.Meta.FlagSet(r.Name(), FlagSetClient) 79 flags.Usage = func() { r.Ui.Output(r.Help()) } 80 flags.BoolVar(&override, "policy-override", false, "") 81 flags.BoolVar(&detach, "detach", false, "") 82 flags.BoolVar(&verbose, "verbose", false, "") 83 if err := flags.Parse(args); err != nil { 84 return 1 85 } 86 87 if args = flags.Args(); len(args) < 1 { 88 r.Ui.Error("This command takes at least one argument: <recommendation_id>") 89 r.Ui.Error(commandErrorText(r)) 90 return 1 91 } 92 93 // Get the HTTP client. 94 client, err := r.Meta.Client() 95 if err != nil { 96 r.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 97 return 1 98 } 99 100 // Create a list of recommendations to apply. 101 ids := make([]string, len(args)) 102 copy(ids, args) 103 104 resp, _, err := client.Recommendations().Apply(ids, override) 105 if err != nil { 106 r.Ui.Error(fmt.Sprintf("Error applying recommendations: %v", err)) 107 return 1 108 } 109 110 // If we should detach, or must because we applied multiple recommendations 111 // resulting in more than a single eval to monitor. 112 if detach || len(resp.Errors) > 0 || len(resp.UpdatedJobs) > 1 { 113 114 // If we had apply errors, output these at the top so they are easy to 115 // find. Always output the heading, this provides some consistency, 116 // even if just to show there are no errors. 117 r.Ui.Output(r.Colorize().Color("[bold]Errors[reset]")) 118 if len(resp.Errors) > 0 { 119 r.outputApplyErrors(resp.Errors) 120 } else { 121 r.Ui.Output("None\n") 122 } 123 124 // If we had apply results, output these. 125 if len(resp.UpdatedJobs) > 0 { 126 r.outputApplyResult(resp.UpdatedJobs) 127 } 128 return 0 129 } 130 131 // When would we ever reach this case? Probably never, but catch this just 132 // in case. 133 if len(resp.UpdatedJobs) < 1 { 134 return 0 135 } 136 137 // If we reached here, we should have a single entry to interrogate and 138 // monitor. 139 length := shortId 140 if verbose { 141 length = fullId 142 } 143 mon := newMonitor(r.Ui, client, length) 144 return mon.monitor(resp.UpdatedJobs[0].EvalID) 145 } 146 147 func (r *RecommendationApplyCommand) outputApplyErrors(errs []*api.SingleRecommendationApplyError) { 148 output := []string{"IDs|Job ID|Error"} 149 for _, err := range errs { 150 output = append(output, fmt.Sprintf("%s|%s|%s", err.Recommendations, err.JobID, err.Error)) 151 } 152 r.Ui.Output(formatList(output)) 153 r.Ui.Output("\n") 154 } 155 156 func (r *RecommendationApplyCommand) outputApplyResult(res []*api.SingleRecommendationApplyResult) { 157 output := []string{"IDs|Namespace|Job ID|Eval ID|Warnings"} 158 for _, r := range res { 159 output = append(output, fmt.Sprintf( 160 "%s|%s|%s|%s|%s", 161 strings.Join(r.Recommendations, ","), r.Namespace, r.JobID, r.EvalID, r.Warnings)) 162 } 163 r.Ui.Output(r.Colorize().Color("[bold]Results[reset]")) 164 r.Ui.Output(formatList(output)) 165 }