Skip to content

Commit 075589f

Browse files
committed
Nicify login experience
1 parent 35b1fef commit 075589f

3 files changed

Lines changed: 147 additions & 49 deletions

File tree

cmd/login.go

Lines changed: 50 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net/url"
66
"os"
77
"path/filepath"
8+
"strings"
89

910
"github.com/humio/cli/api"
1011
"github.com/humio/cli/prompt"
@@ -26,16 +27,23 @@ In the config file already exists, the settings will be merged into the existing
2627
var addr, token string
2728
var err error
2829

29-
fmt.Println("This will guide you through setting up the Humio CLI.")
30-
fmt.Println("")
30+
prompt.Output("")
31+
owl := "[purple]" + prompt.Owl() + "[reset]"
32+
fmt.Print((prompt.Colorize(owl)))
33+
prompt.Output("")
34+
prompt.Title("Welcome to Humio")
35+
prompt.Output("")
36+
prompt.Description("This will guide you through setting up the Humio CLI.")
37+
prompt.Output("")
38+
39+
prompt.Info("Which Humio instance should we talk to?") // INFO
40+
prompt.Output("")
41+
prompt.Description("If you are not using Humio Cloud enter the address of your Humio installation,")
42+
prompt.Description("e.g. http://localhost:8080/ or https://humio.example.com/")
3143

3244
for true {
33-
fmt.Println("1. Which Humio instance should we talk to?") // INFO
34-
fmt.Println("")
35-
fmt.Println("If you are not using Humio Cloud enter the address out your Humio installation,")
36-
fmt.Println("e.g. http://localhost:8080/ or https://humio.example.com/")
37-
fmt.Println("")
38-
fmt.Println("Default: https://cloud.humio.com/ [Hit Enter]")
45+
prompt.Output("")
46+
prompt.Output("Default: https://cloud.humio.com/ [Hit Enter]")
3947
addr, err = prompt.Ask("Humio Address")
4048

4149
if addr == "" {
@@ -48,13 +56,16 @@ In the config file already exists, the settings will be merged into the existing
4856

4957
// Make sure it is a valid URL and that
5058
// we always end in a slash.
51-
addrUrl, urlErr := url.Parse(addr)
59+
_, urlErr := url.ParseRequestURI(addr)
5260

5361
if urlErr != nil {
54-
fmt.Println("The valus must be a valid URL.") // ERROR
62+
prompt.Error("The valus must be a valid URL.")
63+
continue
5564
}
5665

57-
addr = fmt.Sprintf("%v", addrUrl)
66+
if !strings.HasSuffix(addr, "/") {
67+
addr = addr + "/"
68+
}
5869

5970
clientConfig := api.DefaultConfig()
6071
clientConfig.Address = addr
@@ -64,38 +75,44 @@ In the config file already exists, the settings will be merged into the existing
6475
return (fmt.Errorf("error initializing the http client: %s", apiErr))
6576
}
6677

78+
prompt.Output("")
79+
fmt.Print("==> Testing Connection...")
80+
6781
status, statusErr := client.Status()
6882

6983
if statusErr != nil {
70-
fmt.Println(fmt.Errorf("Could not connect to the Humio server: %s\nIs the address connect and reachable?", statusErr)) // ERROR
84+
fmt.Println(prompt.Colorize("[[red]Failed[reset]]"))
85+
prompt.Output("")
86+
prompt.Error(fmt.Sprintf("Could not connect to the Humio server: %s\nIs the address connect and reachable?", statusErr))
7187
continue
7288
}
7389

7490
if status.Status != "ok" {
91+
fmt.Println(prompt.Colorize("[[red]Failed[reset]]"))
7592
return (fmt.Errorf("The server reported that is is malfunctioning, status: %s", status.Status))
93+
} else {
94+
fmt.Println(prompt.Colorize("[[green]Ok[reset]]"))
7695
}
7796

78-
fmt.Println("")
79-
fmt.Println("==> Connection Successful") // INFO
8097
fmt.Println("")
8198
break
8299
}
83100

84-
fmt.Println("")
85-
fmt.Println("2. Paste in your API Token") // INFO
86-
fmt.Println("")
87-
fmt.Println("To use Humio's CLI you will need to get a copy of your API Token.")
88-
fmt.Println("The API token can be found in your 'Account Settings' section of the UI.")
89-
fmt.Println("If you are running Humio without authorization just leave the API Token field empty.")
90-
fmt.Println("")
91-
prompt.Ask("We will now open the account page in a browser window. [Hit Any Key]")
101+
prompt.Info("Paste in your Personal API Token")
102+
prompt.Description("")
103+
prompt.Description("To use Humio's CLI you will need to get a copy of your API Token.")
104+
prompt.Description("The API token can be found in your 'Account Settings' section of the UI.")
105+
prompt.Description("If you are running Humio without authorization just leave the API Token field empty.")
106+
prompt.Description("")
92107

93-
open.Start(fmt.Sprintf("%ssettings", addr))
108+
if prompt.Confirm("Would you like us to open a browser on the account page?") {
109+
open.Start(fmt.Sprintf("%ssettings", addr))
94110

95-
fmt.Println("")
96-
fmt.Println(fmt.Sprintf("If the browser did not open, you can manually visit:"))
97-
fmt.Println(fmt.Sprintf("%ssettings", addr))
98-
fmt.Println("")
111+
prompt.Description("")
112+
prompt.Description(fmt.Sprintf("If the browser did not open, you can manually visit:"))
113+
prompt.Description(fmt.Sprintf("%ssettings", addr))
114+
prompt.Description("")
115+
}
99116

100117
for true {
101118
token, err = prompt.AskSecret("API Token")
@@ -117,28 +134,25 @@ In the config file already exists, the settings will be merged into the existing
117134
username, apiErr := client.Viewer().Username()
118135

119136
if apiErr != nil {
120-
fmt.Println(fmt.Errorf("authorization failed, try another token")) // ERROR
137+
prompt.Error("authorization failed, try another token")
121138
continue
122139
}
123140

124141
fmt.Println("")
125-
fmt.Println(fmt.Sprintf("==> Login successful '%s' 🎉", username)) // INFO
142+
fmt.Println("")
143+
fmt.Println(prompt.Colorize(fmt.Sprintf("==> Logged in as: [purple]%s[reset]", username)))
126144
fmt.Println("")
127145
break
128146
}
129147

130148
viper.Set("address", addr)
131149
viper.Set("token", token)
132150

133-
// viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
134-
// viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase"))
135-
// viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))
136-
137151
configFile := viper.ConfigFileUsed()
138152

139-
fmt.Println("==> Writing settings to: " + configFile) // INFO
153+
fmt.Println(prompt.Colorize("==> Writing settings to: [purple]" + configFile + "[reset]"))
140154

141-
if writeErr := viper.MergeInConfig(); writeErr != nil {
155+
if writeErr := viper.WriteConfig(); writeErr != nil {
142156
if os.IsNotExist(writeErr) {
143157
dirName := filepath.Dir(configFile)
144158
if dirErr := os.MkdirAll(dirName, 0700); dirErr != nil {
@@ -151,7 +165,7 @@ In the config file already exists, the settings will be merged into the existing
151165
}
152166

153167
fmt.Println("")
154-
fmt.Println("Bye bye now!")
168+
prompt.Output("Bye bye now! 🎉")
155169
fmt.Println("")
156170

157171
return nil

cmd/root.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"path"
2121

2222
"github.com/humio/cli/api"
23+
"github.com/humio/cli/prompt"
2324
homedir "github.com/mitchellh/go-homedir"
2425
"github.com/spf13/cobra"
2526
"github.com/spf13/viper"
@@ -45,14 +46,16 @@ func init() {
4546
rootCmd = &cobra.Command{
4647
Use: "humio [subcommand] [flags] [arguments]",
4748
Short: "A management CLI for Humio.",
48-
Long: `To set up your environment run:
49+
Long: `
50+
Environment Setup:
4951
5052
$ humio login
5153
5254
Sending Data:
55+
5356
Humio's CLI is not a replacement for fully-featured data-shippers like
54-
LogStash, FileBeat or MetricBeat. It can be handy to easily send logs
55-
to Humio to, e.g examine a local log file or test a parser on test input.
57+
LogStash, FileBeat or MetricBeat. It can be handy to easily send logs
58+
to Humio, e.g examine a local log file or test a parser on test input.
5659
5760
To stream the content of "/var/log/system.log" data to Humio:
5861
@@ -62,11 +65,23 @@ or
6265
6366
$ humio ingest -o --tail=/var/log/system.log
6467
65-
Common commands:
68+
Common Management Commands:
6669
users <subcommand>
6770
parsers <subcommand>
68-
views <subcommand>
71+
views <subcommand>
6972
`,
73+
Run: func(cmd *cobra.Command, args []string) {
74+
// If no token or address flags are passed
75+
// and no configuration file exists, run login.
76+
if viper.GetString("token") == "" && viper.GetString("address") == "" {
77+
newLoginCmd().Execute()
78+
} else {
79+
err := cmd.Help()
80+
if err != nil {
81+
fmt.Println(fmt.Errorf("error printing help: %s", err))
82+
}
83+
}
84+
},
7085
}
7186

7287
cobra.OnInitialize(initConfig)
@@ -76,7 +91,7 @@ Common commands:
7691
// will be global for your application.
7792
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.humio/config.yaml)")
7893
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "The API token to user when talking to Humio. Overrides the value in your config file.")
79-
rootCmd.PersistentFlags().StringVarP(&address, "address", "a", "http://localhost:8080/", "The HTTP address of the Humio cluster. Overrides the value in your config file. (default http://localhost:8080/)")
94+
rootCmd.PersistentFlags().StringVarP(&address, "address", "a", "", "The HTTP address of the Humio cluster. Overrides the value in your config file.")
8095

8196
viper.BindPFlag("address", rootCmd.PersistentFlags().Lookup("address"))
8297
viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))
@@ -113,12 +128,9 @@ func initConfig() {
113128
// If a config file is found, read it in.
114129
err := viper.ReadInConfig()
115130
if err == nil {
116-
fmt.Println("Cluster Address:", viper.Get("address"))
117-
} else {
118-
fmt.Println(err)
131+
prompt.Output("Using config file: " + cfgFile)
132+
prompt.Output("Cluster address: " + viper.GetString("address"))
119133
}
120-
121-
fmt.Println("Using config file:", cfgFile)
122134
}
123135

124136
func NewApiClient(cmd *cobra.Command) *api.Client {

prompt/prompt.go

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package prompt
22

33
import (
4+
"bufio"
45
"fmt"
6+
"log"
7+
"os"
8+
"strings"
59

610
"golang.org/x/crypto/ssh/terminal"
711
)
812

913
func Ask(question string) (string, error) {
1014
var answer string
11-
fmt.Print(question + ": ")
15+
fmt.Print(" " + question + ": ")
1216
n, err := fmt.Scanln(&answer)
1317

1418
if n == 0 {
@@ -22,13 +26,81 @@ func Ask(question string) (string, error) {
2226
return answer, nil
2327
}
2428

29+
func Confirm(text string) bool {
30+
fmt.Print(" " + text + " [Y/n]: ")
31+
32+
reader := bufio.NewReader(os.Stdin)
33+
34+
for {
35+
response, err := reader.ReadString('\n')
36+
if err != nil {
37+
log.Fatal(err)
38+
}
39+
40+
response = strings.ToLower(strings.TrimSpace(response))
41+
42+
if response == "" || response == "y" || response == "yes" {
43+
return true
44+
} else if response == "n" || response == "no" {
45+
return false
46+
}
47+
}
48+
}
49+
2550
func AskSecret(question string) (string, error) {
26-
fmt.Print(question + ": ")
51+
fmt.Print(" " + question + ": ")
2752
bytes, err := terminal.ReadPassword(0)
2853

2954
if err != nil {
3055
return "", err
3156
}
3257

58+
fmt.Print("***********************\n")
3359
return string(bytes), nil
3460
}
61+
62+
func Output(text string) {
63+
fmt.Println(" ", text)
64+
}
65+
66+
func Title(text string) {
67+
c := "[underline][bold]" + text + "[reset]"
68+
Output(Colorize(c))
69+
}
70+
71+
func Description(text string) {
72+
c := "[gray]" + text + "[reset]"
73+
Output(Colorize(c))
74+
}
75+
76+
func Error(text string) {
77+
c := "[red]" + text + "[reset]"
78+
Output(Colorize(c))
79+
}
80+
81+
func Info(text string) {
82+
c := "[purple]" + text + "[reset]"
83+
Output(Colorize(c))
84+
}
85+
86+
func Colorize(text string) string {
87+
replacer := strings.NewReplacer(
88+
"[reset]", "\x1b[0m",
89+
"[gray]", "\x1b[38;5;249m",
90+
"[purple]", "\x1b[38;5;129m",
91+
"[bold]", "\x1b[1m",
92+
"[red]", "\x1b[38;5;1m",
93+
"[green]", "\x1b[38;5;2m",
94+
"[underline]", "\x1b[4m",
95+
)
96+
97+
return replacer.Replace(text)
98+
}
99+
100+
func Owl() string {
101+
return ` , ,
102+
(O,o)
103+
|)__)
104+
-”-”-
105+
`
106+
}

0 commit comments

Comments
 (0)