github.com/djenriquez/nomad-1@v0.8.1/command/ui.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"net/url"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/nomad/api/contexts"
     9  	"github.com/posener/complete"
    10  	"github.com/skratchdot/open-golang/open"
    11  )
    12  
    13  var (
    14  	// uiContexts is the contexts the ui can open automatically.
    15  	uiContexts = []contexts.Context{contexts.Jobs, contexts.Allocs, contexts.Nodes}
    16  )
    17  
    18  type UiCommand struct {
    19  	Meta
    20  }
    21  
    22  func (c *UiCommand) Help() string {
    23  	helpText := `
    24  Usage: nomad ui [options] <identifier>
    25  
    26  Open the Nomad Web UI in the default browser. An optional identifier may be
    27  provided, in which case the UI will be opened to view the details for that
    28  object. Supported identifiers are jobs, allocations and nodes.
    29  
    30  General Options:
    31  
    32    ` + generalOptionsUsage()
    33  
    34  	return strings.TrimSpace(helpText)
    35  }
    36  
    37  func (c *UiCommand) AutocompleteFlags() complete.Flags {
    38  	return c.Meta.AutocompleteFlags(FlagSetClient)
    39  }
    40  
    41  func (c *UiCommand) AutocompleteArgs() complete.Predictor {
    42  	return complete.PredictFunc(func(a complete.Args) []string {
    43  		client, err := c.Meta.Client()
    44  		if err != nil {
    45  			return nil
    46  		}
    47  
    48  		resp, _, err := client.Search().PrefixSearch(a.Last, contexts.All, nil)
    49  		if err != nil {
    50  			return []string{}
    51  		}
    52  
    53  		final := make([]string, 0)
    54  
    55  		for _, allowed := range uiContexts {
    56  			matches, ok := resp.Matches[allowed]
    57  			if !ok {
    58  				continue
    59  			}
    60  			if len(matches) == 0 {
    61  				continue
    62  			}
    63  
    64  			final = append(final, matches...)
    65  		}
    66  
    67  		return final
    68  	})
    69  }
    70  
    71  func (c *UiCommand) Synopsis() string {
    72  	return "Open the Nomad Web UI"
    73  }
    74  
    75  func (c *UiCommand) Run(args []string) int {
    76  	flags := c.Meta.FlagSet("deployment list", FlagSetClient)
    77  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    78  
    79  	if err := flags.Parse(args); err != nil {
    80  		return 1
    81  	}
    82  
    83  	// Check that we got no more than one argument
    84  	args = flags.Args()
    85  	if l := len(args); l > 1 {
    86  		c.Ui.Error(c.Help())
    87  		return 1
    88  	}
    89  
    90  	// Get the HTTP client
    91  	client, err := c.Meta.Client()
    92  	if err != nil {
    93  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    94  		return 1
    95  	}
    96  
    97  	url, err := url.Parse(client.Address())
    98  	if err != nil {
    99  		c.Ui.Error(fmt.Sprintf("Error parsing Nomad address %q: %s", client.Address(), err))
   100  		return 1
   101  	}
   102  
   103  	// We were given an id so look it up
   104  	if len(args) == 1 {
   105  		id := args[0]
   106  
   107  		// Query for the context associated with the id
   108  		res, _, err := client.Search().PrefixSearch(id, contexts.All, nil)
   109  		if err != nil {
   110  			c.Ui.Error(fmt.Sprintf("Error querying search with id: %q", err))
   111  			return 1
   112  		}
   113  
   114  		if res.Matches == nil {
   115  			c.Ui.Error(fmt.Sprintf("No matches returned for query: %q", err))
   116  			return 1
   117  		}
   118  
   119  		var match contexts.Context
   120  		var fullID string
   121  		matchCount := 0
   122  		for _, ctx := range uiContexts {
   123  			vers, ok := res.Matches[ctx]
   124  			if !ok {
   125  				continue
   126  			}
   127  
   128  			if l := len(vers); l == 1 {
   129  				match = ctx
   130  				fullID = vers[0]
   131  				matchCount++
   132  			} else if l > 0 && vers[0] == id {
   133  				// Exact match
   134  				match = ctx
   135  				fullID = vers[0]
   136  				break
   137  			}
   138  
   139  			// Only a single result should return, as this is a match against a full id
   140  			if matchCount > 1 || len(vers) > 1 {
   141  				c.logMultiMatchError(id, res.Matches)
   142  				return 1
   143  			}
   144  		}
   145  
   146  		switch match {
   147  		case contexts.Nodes:
   148  			url.Path = fmt.Sprintf("ui/nodes/%s", fullID)
   149  		case contexts.Allocs:
   150  			url.Path = fmt.Sprintf("ui/allocations/%s", fullID)
   151  		case contexts.Jobs:
   152  			url.Path = fmt.Sprintf("ui/jobs/%s", fullID)
   153  		default:
   154  			c.Ui.Error(fmt.Sprintf("Unable to resolve ID: %q", id))
   155  			return 1
   156  		}
   157  	}
   158  
   159  	c.Ui.Output(fmt.Sprintf("Opening URL %q", url.String()))
   160  	if err := open.Start(url.String()); err != nil {
   161  		c.Ui.Error(fmt.Sprintf("Error opening URL: %s", err))
   162  		return 1
   163  	}
   164  
   165  	return 0
   166  }
   167  
   168  // logMultiMatchError is used to log an error message when multiple matches are
   169  // found. The error message logged displays the matched IDs per context.
   170  func (c *UiCommand) logMultiMatchError(id string, matches map[contexts.Context][]string) {
   171  	c.Ui.Error(fmt.Sprintf("Multiple matches found for id %q", id))
   172  	for _, ctx := range uiContexts {
   173  		vers, ok := matches[ctx]
   174  		if !ok {
   175  			continue
   176  		}
   177  		if len(vers) == 0 {
   178  			continue
   179  		}
   180  
   181  		c.Ui.Error(fmt.Sprintf("\n%s:", strings.Title(string(ctx))))
   182  		c.Ui.Error(fmt.Sprintf("%s", strings.Join(vers, ", ")))
   183  	}
   184  }