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