github.com/hernad/nomad@v1.6.112/command/recommendation_list.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 "strings" 10 11 "github.com/hernad/nomad/api" 12 "github.com/mitchellh/cli" 13 "github.com/posener/complete" 14 ) 15 16 // Ensure RecommendationListCommand satisfies the cli.Command interface. 17 var _ cli.Command = &RecommendationListCommand{} 18 19 // RecommendationListCommand implements cli.Command. 20 type RecommendationListCommand struct { 21 Meta 22 } 23 24 // Help satisfies the cli.Command Help function. 25 func (r *RecommendationListCommand) Help() string { 26 helpText := ` 27 Usage: nomad recommendation list [options] 28 29 List is used to list the available 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 namespace being 33 queried. 34 35 General Options: 36 37 ` + generalOptionsUsage(usageOptsDefault) + ` 38 39 Recommendation List Options: 40 41 -job 42 Specifies the job ID to filter the recommendations list by. 43 44 -group 45 Specifies the task group name to filter within a job. If specified, the -job 46 flag must also be specified. 47 48 -task 49 Specifies the task name to filter within a job and task group. If specified, 50 the -job and -group flags must also be specified. 51 52 -json 53 Output the recommendations in JSON format. 54 55 -t 56 Format and display the recommendations using a Go template. 57 ` 58 return strings.TrimSpace(helpText) 59 } 60 61 // Synopsis satisfies the cli.Command Synopsis function. 62 func (r *RecommendationListCommand) Synopsis() string { 63 return "Display all Nomad recommendations" 64 } 65 66 func (r *RecommendationListCommand) AutocompleteFlags() complete.Flags { 67 return mergeAutocompleteFlags(r.Meta.AutocompleteFlags(FlagSetClient), 68 complete.Flags{ 69 "-job": complete.PredictNothing, 70 "-group": complete.PredictNothing, 71 "-task": complete.PredictNothing, 72 "-json": complete.PredictNothing, 73 "-t": complete.PredictAnything, 74 }) 75 } 76 77 // Name returns the name of this command. 78 func (r *RecommendationListCommand) Name() string { return "recommendation list" } 79 80 // Run satisfies the cli.Command Run function. 81 func (r *RecommendationListCommand) Run(args []string) int { 82 var json bool 83 var tmpl, job, group, task string 84 85 flags := r.Meta.FlagSet(r.Name(), FlagSetClient) 86 flags.Usage = func() { r.Ui.Output(r.Help()) } 87 flags.BoolVar(&json, "json", false, "") 88 flags.StringVar(&tmpl, "t", "", "") 89 flags.StringVar(&job, "job", "", "") 90 flags.StringVar(&group, "group", "", "") 91 flags.StringVar(&task, "task", "", "") 92 if err := flags.Parse(args); err != nil { 93 return 1 94 } 95 96 if args = flags.Args(); len(args) > 0 { 97 r.Ui.Error("This command takes no arguments") 98 r.Ui.Error(commandErrorText(r)) 99 } 100 101 // Get the HTTP client. 102 client, err := r.Meta.Client() 103 if err != nil { 104 r.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 105 return 1 106 } 107 108 // Validate the input flags. This is done by the HTTP API anyway, but there 109 // is no harm doing it here to avoid calls that we know wont succeed. 110 if group != "" && job == "" { 111 r.Ui.Error("Job flag must be supplied when using group flag") 112 return 1 113 114 } 115 if task != "" && group == "" { 116 r.Ui.Error("Group flag must be supplied when using task flag") 117 return 1 118 } 119 120 // Setup the query params. 121 q := &api.QueryOptions{ 122 Params: map[string]string{}, 123 } 124 if job != "" { 125 q.Params["job"] = job 126 } 127 if group != "" { 128 q.Params["group"] = group 129 } 130 if task != "" { 131 q.Params["task"] = task 132 } 133 134 recommendations, _, err := client.Recommendations().List(q) 135 if err != nil { 136 r.Ui.Error(fmt.Sprintf("Error listing recommendations: %s", err)) 137 return 1 138 } 139 140 if len(recommendations) == 0 { 141 r.Ui.Output("No recommendations found") 142 return 0 143 } 144 145 if json || len(tmpl) > 0 { 146 out, err := Format(json, tmpl, recommendations) 147 if err != nil { 148 r.Ui.Error(err.Error()) 149 return 1 150 } 151 r.Ui.Output(out) 152 return 0 153 } 154 155 // Create the output table header. 156 output := []string{"ID|"} 157 158 // If the operator is using the namespace wildcard option, add this header. 159 if r.Meta.namespace == "*" { 160 output[0] += "Namespace|" 161 } 162 output[0] += "Job|Group|Task|Resource|Value" 163 164 // Sort the list of recommendations based on their job, group and task. 165 sortedRecs := recommendationList{r: recommendations} 166 sort.Sort(sortedRecs) 167 168 // Iterate the recommendations and add to the output. 169 for i, rec := range sortedRecs.r { 170 171 output = append(output, rec.ID) 172 173 if r.Meta.namespace == "*" { 174 output[i+1] += fmt.Sprintf("|%s", rec.Namespace) 175 } 176 output[i+1] += fmt.Sprintf("|%s|%s|%s|%s|%v", rec.JobID, rec.Group, rec.Task, rec.Resource, rec.Value) 177 } 178 179 // Output. 180 r.Ui.Output(formatList(output)) 181 return 0 182 } 183 184 // recommendationList is a wrapper around []*api.Recommendation that lets us 185 // sort the recommendations alphabetically based on their job, group and task. 186 type recommendationList struct { 187 r []*api.Recommendation 188 } 189 190 // Len satisfies the Len function of the sort.Interface interface. 191 func (r recommendationList) Len() int { return len(r.r) } 192 193 // Swap satisfies the Swap function of the sort.Interface interface. 194 func (r recommendationList) Swap(i, j int) { 195 r.r[i], r.r[j] = r.r[j], r.r[i] 196 } 197 198 // Less satisfies the Less function of the sort.Interface interface. 199 func (r recommendationList) Less(i, j int) bool { 200 recI := r.stringFromResource(i) 201 recJ := r.stringFromResource(j) 202 stringList := []string{recI, recJ} 203 sort.Strings(stringList) 204 return stringList[0] == recI 205 } 206 207 func (r recommendationList) stringFromResource(i int) string { 208 return strings.Join([]string{r.r[i].Namespace, r.r[i].JobID, r.r[i].Group, r.r[i].Task, r.r[i].Resource}, ":") 209 }