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