github.com/anuvu/nomad@v0.8.7-atom1/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) Name() string { return "ui" }
    76  
    77  func (c *UiCommand) Run(args []string) int {
    78  	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
    79  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    80  
    81  	if err := flags.Parse(args); err != nil {
    82  		return 1
    83  	}
    84  
    85  	// Check that we got no more than one argument
    86  	args = flags.Args()
    87  	if l := len(args); l > 1 {
    88  		c.Ui.Error("This command takes no or one optional argument, [<identifier>]")
    89  		c.Ui.Error(commandErrorText(c))
    90  		return 1
    91  	}
    92  
    93  	// Get the HTTP client
    94  	client, err := c.Meta.Client()
    95  	if err != nil {
    96  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    97  		return 1
    98  	}
    99  
   100  	url, err := url.Parse(client.Address())
   101  	if err != nil {
   102  		c.Ui.Error(fmt.Sprintf("Error parsing Nomad address %q: %s", client.Address(), err))
   103  		return 1
   104  	}
   105  
   106  	// We were given an id so look it up
   107  	if len(args) == 1 {
   108  		id := args[0]
   109  
   110  		// Query for the context associated with the id
   111  		res, _, err := client.Search().PrefixSearch(id, contexts.All, nil)
   112  		if err != nil {
   113  			c.Ui.Error(fmt.Sprintf("Error querying search with id: %q", err))
   114  			return 1
   115  		}
   116  
   117  		if res.Matches == nil {
   118  			c.Ui.Error(fmt.Sprintf("No matches returned for query: %q", err))
   119  			return 1
   120  		}
   121  
   122  		var match contexts.Context
   123  		var fullID string
   124  		matchCount := 0
   125  		for _, ctx := range uiContexts {
   126  			vers, ok := res.Matches[ctx]
   127  			if !ok {
   128  				continue
   129  			}
   130  
   131  			if l := len(vers); l == 1 {
   132  				match = ctx
   133  				fullID = vers[0]
   134  				matchCount++
   135  			} else if l > 0 && vers[0] == id {
   136  				// Exact match
   137  				match = ctx
   138  				fullID = vers[0]
   139  				break
   140  			}
   141  
   142  			// Only a single result should return, as this is a match against a full id
   143  			if matchCount > 1 || len(vers) > 1 {
   144  				c.logMultiMatchError(id, res.Matches)
   145  				return 1
   146  			}
   147  		}
   148  
   149  		switch match {
   150  		case contexts.Nodes:
   151  			url.Path = fmt.Sprintf("ui/nodes/%s", fullID)
   152  		case contexts.Allocs:
   153  			url.Path = fmt.Sprintf("ui/allocations/%s", fullID)
   154  		case contexts.Jobs:
   155  			url.Path = fmt.Sprintf("ui/jobs/%s", fullID)
   156  		default:
   157  			c.Ui.Error(fmt.Sprintf("Unable to resolve ID: %q", id))
   158  			return 1
   159  		}
   160  	}
   161  
   162  	c.Ui.Output(fmt.Sprintf("Opening URL %q", url.String()))
   163  	if err := open.Start(url.String()); err != nil {
   164  		c.Ui.Error(fmt.Sprintf("Error opening URL: %s", err))
   165  		return 1
   166  	}
   167  
   168  	return 0
   169  }
   170  
   171  // logMultiMatchError is used to log an error message when multiple matches are
   172  // found. The error message logged displays the matched IDs per context.
   173  func (c *UiCommand) logMultiMatchError(id string, matches map[contexts.Context][]string) {
   174  	c.Ui.Error(fmt.Sprintf("Multiple matches found for id %q", id))
   175  	for _, ctx := range uiContexts {
   176  		vers, ok := matches[ctx]
   177  		if !ok {
   178  			continue
   179  		}
   180  		if len(vers) == 0 {
   181  			continue
   182  		}
   183  
   184  		c.Ui.Error(fmt.Sprintf("\n%s:", strings.Title(string(ctx))))
   185  		c.Ui.Error(fmt.Sprintf("%s", strings.Join(vers, ", ")))
   186  	}
   187  }