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