github.com/smithx10/nomad@v0.9.1-rc1/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 dID := args[0] 95 96 // Truncate the id unless full length is requested 97 length := shortId 98 if verbose { 99 length = fullId 100 } 101 102 // Get the HTTP client 103 client, err := c.Meta.Client() 104 if err != nil { 105 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 106 return 1 107 } 108 109 // Do a prefix lookup 110 deploy, possible, err := getDeployment(client.Deployments(), dID) 111 if err != nil { 112 c.Ui.Error(fmt.Sprintf("Error retrieving deployment: %s", err)) 113 return 1 114 } 115 116 if len(possible) != 0 { 117 c.Ui.Error(fmt.Sprintf("Prefix matched multiple deployments\n\n%s", formatDeployments(possible, length))) 118 return 1 119 } 120 121 if json || len(tmpl) > 0 { 122 out, err := Format(json, tmpl, deploy) 123 if err != nil { 124 c.Ui.Error(err.Error()) 125 return 1 126 } 127 128 c.Ui.Output(out) 129 return 0 130 } 131 132 c.Ui.Output(c.Colorize().Color(formatDeployment(deploy, length))) 133 return 0 134 } 135 136 func getDeployment(client *api.Deployments, dID string) (match *api.Deployment, possible []*api.Deployment, err error) { 137 // First attempt an immediate lookup if we have a proper length 138 if len(dID) == 36 { 139 d, _, err := client.Info(dID, nil) 140 if err != nil { 141 return nil, nil, err 142 } 143 144 return d, nil, nil 145 } 146 147 dID = strings.Replace(dID, "-", "", -1) 148 if len(dID) == 1 { 149 return nil, nil, fmt.Errorf("Identifier must contain at least two characters.") 150 } 151 if len(dID)%2 == 1 { 152 // Identifiers must be of even length, so we strip off the last byte 153 // to provide a consistent user experience. 154 dID = dID[:len(dID)-1] 155 } 156 157 // Have to do a prefix lookup 158 deploys, _, err := client.PrefixList(dID) 159 if err != nil { 160 return nil, nil, err 161 } 162 163 l := len(deploys) 164 switch { 165 case l == 0: 166 return nil, nil, fmt.Errorf("Deployment ID %q matched no deployments", dID) 167 case l == 1: 168 return deploys[0], nil, nil 169 default: 170 return nil, deploys, nil 171 } 172 } 173 174 func formatDeployment(d *api.Deployment, uuidLength int) string { 175 if d == nil { 176 return "No deployment found" 177 } 178 // Format the high-level elements 179 high := []string{ 180 fmt.Sprintf("ID|%s", limit(d.ID, uuidLength)), 181 fmt.Sprintf("Job ID|%s", d.JobID), 182 fmt.Sprintf("Job Version|%d", d.JobVersion), 183 fmt.Sprintf("Status|%s", d.Status), 184 fmt.Sprintf("Description|%s", d.StatusDescription), 185 } 186 187 base := formatKV(high) 188 if len(d.TaskGroups) == 0 { 189 return base 190 } 191 base += "\n\n[bold]Deployed[reset]\n" 192 base += formatDeploymentGroups(d, uuidLength) 193 return base 194 } 195 196 func formatDeploymentGroups(d *api.Deployment, uuidLength int) string { 197 // Detect if we need to add these columns 198 var canaries, autorevert, progressDeadline bool 199 tgNames := make([]string, 0, len(d.TaskGroups)) 200 for name, state := range d.TaskGroups { 201 tgNames = append(tgNames, name) 202 if state.AutoRevert { 203 autorevert = true 204 } 205 if state.DesiredCanaries > 0 { 206 canaries = true 207 } 208 if state.ProgressDeadline != 0 { 209 progressDeadline = true 210 } 211 } 212 213 // Sort the task group names to get a reliable ordering 214 sort.Strings(tgNames) 215 216 // Build the row string 217 rowString := "Task Group|" 218 if autorevert { 219 rowString += "Auto Revert|" 220 } 221 if canaries { 222 rowString += "Promoted|" 223 } 224 rowString += "Desired|" 225 if canaries { 226 rowString += "Canaries|" 227 } 228 rowString += "Placed|Healthy|Unhealthy" 229 if progressDeadline { 230 rowString += "|Progress Deadline" 231 } 232 233 rows := make([]string, len(d.TaskGroups)+1) 234 rows[0] = rowString 235 i := 1 236 for _, tg := range tgNames { 237 state := d.TaskGroups[tg] 238 row := fmt.Sprintf("%s|", tg) 239 if autorevert { 240 row += fmt.Sprintf("%v|", state.AutoRevert) 241 } 242 if canaries { 243 if state.DesiredCanaries > 0 { 244 row += fmt.Sprintf("%v|", state.Promoted) 245 } else { 246 row += fmt.Sprintf("%v|", "N/A") 247 } 248 } 249 row += fmt.Sprintf("%d|", state.DesiredTotal) 250 if canaries { 251 row += fmt.Sprintf("%d|", state.DesiredCanaries) 252 } 253 row += fmt.Sprintf("%d|%d|%d", state.PlacedAllocs, state.HealthyAllocs, state.UnhealthyAllocs) 254 if progressDeadline { 255 if state.RequireProgressBy.IsZero() { 256 row += fmt.Sprintf("|%v", "N/A") 257 } else { 258 row += fmt.Sprintf("|%v", formatTime(state.RequireProgressBy)) 259 } 260 } 261 rows[i] = row 262 i++ 263 } 264 265 return formatList(rows) 266 }