github.com/aminovpavel/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  }