github.com/nir0s/nomad@v0.8.7-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  }