github.com/rohankumardubey/nomad@v0.11.8/command/deployment_status.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/hashicorp/nomad/api" 9 "github.com/hashicorp/nomad/api/contexts" 10 "github.com/posener/complete" 11 ) 12 13 type DeploymentStatusCommand struct { 14 Meta 15 } 16 17 func (c *DeploymentStatusCommand) Help() string { 18 helpText := ` 19 Usage: nomad deployment status [options] <deployment id> 20 21 Status is used to display the status of a deployment. The status will display 22 the number of desired changes as well as the currently applied changes. 23 24 General Options: 25 26 ` + generalOptionsUsage() + ` 27 28 Status Options: 29 30 -verbose 31 Display full information. 32 33 -json 34 Output the deployment in its JSON format. 35 36 -t 37 Format and display deployment using a Go template. 38 ` 39 return strings.TrimSpace(helpText) 40 } 41 42 func (c *DeploymentStatusCommand) Synopsis() string { 43 return "Display the status of a deployment" 44 } 45 46 func (c *DeploymentStatusCommand) AutocompleteFlags() complete.Flags { 47 return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient), 48 complete.Flags{ 49 "-verbose": complete.PredictNothing, 50 "-json": complete.PredictNothing, 51 "-t": complete.PredictAnything, 52 }) 53 } 54 55 func (c *DeploymentStatusCommand) AutocompleteArgs() complete.Predictor { 56 return complete.PredictFunc(func(a complete.Args) []string { 57 client, err := c.Meta.Client() 58 if err != nil { 59 return nil 60 } 61 62 resp, _, err := client.Search().PrefixSearch(a.Last, contexts.Deployments, nil) 63 if err != nil { 64 return []string{} 65 } 66 return resp.Matches[contexts.Deployments] 67 }) 68 } 69 70 func (c *DeploymentStatusCommand) Name() string { return "deployment status" } 71 72 func (c *DeploymentStatusCommand) Run(args []string) int { 73 var json, verbose bool 74 var tmpl string 75 76 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 77 flags.Usage = func() { c.Ui.Output(c.Help()) } 78 flags.BoolVar(&verbose, "verbose", false, "") 79 flags.BoolVar(&json, "json", false, "") 80 flags.StringVar(&tmpl, "t", "", "") 81 82 if err := flags.Parse(args); err != nil { 83 return 1 84 } 85 86 // Check that we got exactly one argument 87 args = flags.Args() 88 if l := len(args); l > 1 { 89 c.Ui.Error("This command takes one argument: <deployment id>") 90 c.Ui.Error(commandErrorText(c)) 91 return 1 92 } 93 94 // Truncate the id unless full length is requested 95 length := shortId 96 if verbose { 97 length = fullId 98 } 99 100 // Get the HTTP client 101 client, err := c.Meta.Client() 102 if err != nil { 103 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 104 return 1 105 } 106 107 // List if no arguments are provided 108 if len(args) == 0 { 109 deploys, _, err := client.Deployments().List(nil) 110 if err != nil { 111 c.Ui.Error(fmt.Sprintf("Error retrieving deployments: %s", err)) 112 return 1 113 } 114 115 c.Ui.Output(formatDeployments(deploys, length)) 116 return 0 117 } 118 119 // Do a prefix lookup 120 dID := args[0] 121 deploy, possible, err := getDeployment(client.Deployments(), dID) 122 if err != nil { 123 c.Ui.Error(fmt.Sprintf("Error retrieving deployment: %s", err)) 124 return 1 125 } 126 127 if len(possible) != 0 { 128 c.Ui.Error(fmt.Sprintf("Prefix matched multiple deployments\n\n%s", formatDeployments(possible, length))) 129 return 1 130 } 131 132 if json || len(tmpl) > 0 { 133 out, err := Format(json, tmpl, deploy) 134 if err != nil { 135 c.Ui.Error(err.Error()) 136 return 1 137 } 138 139 c.Ui.Output(out) 140 return 0 141 } 142 143 c.Ui.Output(c.Colorize().Color(formatDeployment(deploy, length))) 144 return 0 145 } 146 147 func getDeployment(client *api.Deployments, dID string) (match *api.Deployment, possible []*api.Deployment, err error) { 148 // First attempt an immediate lookup if we have a proper length 149 if len(dID) == 36 { 150 d, _, err := client.Info(dID, nil) 151 if err != nil { 152 return nil, nil, err 153 } 154 155 return d, nil, nil 156 } 157 158 dID = strings.Replace(dID, "-", "", -1) 159 if len(dID) == 1 { 160 return nil, nil, fmt.Errorf("Identifier must contain at least two characters.") 161 } 162 if len(dID)%2 == 1 { 163 // Identifiers must be of even length, so we strip off the last byte 164 // to provide a consistent user experience. 165 dID = dID[:len(dID)-1] 166 } 167 168 // Have to do a prefix lookup 169 deploys, _, err := client.PrefixList(dID) 170 if err != nil { 171 return nil, nil, err 172 } 173 174 l := len(deploys) 175 switch { 176 case l == 0: 177 return nil, nil, fmt.Errorf("Deployment ID %q matched no deployments", dID) 178 case l == 1: 179 return deploys[0], nil, nil 180 default: 181 return nil, deploys, nil 182 } 183 } 184 185 func formatDeployment(d *api.Deployment, uuidLength int) string { 186 if d == nil { 187 return "No deployment found" 188 } 189 // Format the high-level elements 190 high := []string{ 191 fmt.Sprintf("ID|%s", limit(d.ID, uuidLength)), 192 fmt.Sprintf("Job ID|%s", d.JobID), 193 fmt.Sprintf("Job Version|%d", d.JobVersion), 194 fmt.Sprintf("Status|%s", d.Status), 195 fmt.Sprintf("Description|%s", d.StatusDescription), 196 } 197 198 base := formatKV(high) 199 if len(d.TaskGroups) == 0 { 200 return base 201 } 202 base += "\n\n[bold]Deployed[reset]\n" 203 base += formatDeploymentGroups(d, uuidLength) 204 return base 205 } 206 207 func formatDeploymentGroups(d *api.Deployment, uuidLength int) string { 208 // Detect if we need to add these columns 209 var canaries, autorevert, progressDeadline bool 210 tgNames := make([]string, 0, len(d.TaskGroups)) 211 for name, state := range d.TaskGroups { 212 tgNames = append(tgNames, name) 213 if state.AutoRevert { 214 autorevert = true 215 } 216 if state.DesiredCanaries > 0 { 217 canaries = true 218 } 219 if state.ProgressDeadline != 0 { 220 progressDeadline = true 221 } 222 } 223 224 // Sort the task group names to get a reliable ordering 225 sort.Strings(tgNames) 226 227 // Build the row string 228 rowString := "Task Group|" 229 if autorevert { 230 rowString += "Auto Revert|" 231 } 232 if canaries { 233 rowString += "Promoted|" 234 } 235 rowString += "Desired|" 236 if canaries { 237 rowString += "Canaries|" 238 } 239 rowString += "Placed|Healthy|Unhealthy" 240 if progressDeadline { 241 rowString += "|Progress Deadline" 242 } 243 244 rows := make([]string, len(d.TaskGroups)+1) 245 rows[0] = rowString 246 i := 1 247 for _, tg := range tgNames { 248 state := d.TaskGroups[tg] 249 row := fmt.Sprintf("%s|", tg) 250 if autorevert { 251 row += fmt.Sprintf("%v|", state.AutoRevert) 252 } 253 if canaries { 254 if state.DesiredCanaries > 0 { 255 row += fmt.Sprintf("%v|", state.Promoted) 256 } else { 257 row += fmt.Sprintf("%v|", "N/A") 258 } 259 } 260 row += fmt.Sprintf("%d|", state.DesiredTotal) 261 if canaries { 262 row += fmt.Sprintf("%d|", state.DesiredCanaries) 263 } 264 row += fmt.Sprintf("%d|%d|%d", state.PlacedAllocs, state.HealthyAllocs, state.UnhealthyAllocs) 265 if progressDeadline { 266 if state.RequireProgressBy.IsZero() { 267 row += fmt.Sprintf("|%v", "N/A") 268 } else { 269 row += fmt.Sprintf("|%v", formatTime(state.RequireProgressBy)) 270 } 271 } 272 rows[i] = row 273 i++ 274 } 275 276 return formatList(rows) 277 }