Skip to content

Commit

Permalink
Nicify login experience
Browse files Browse the repository at this point in the history
  • Loading branch information
anagrius committed Nov 27, 2018
1 parent 35b1fef commit 075589f
Show file tree
Hide file tree
Showing 3 changed files with 147 additions and 49 deletions.
86 changes: 50 additions & 36 deletions cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"net/url"
"os"
"path/filepath"
"strings"

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

fmt.Println("This will guide you through setting up the Humio CLI.")
fmt.Println("")
prompt.Output("")
owl := "[purple]" + prompt.Owl() + "[reset]"
fmt.Print((prompt.Colorize(owl)))
prompt.Output("")
prompt.Title("Welcome to Humio")
prompt.Output("")
prompt.Description("This will guide you through setting up the Humio CLI.")
prompt.Output("")

prompt.Info("Which Humio instance should we talk to?") // INFO
prompt.Output("")
prompt.Description("If you are not using Humio Cloud enter the address of your Humio installation,")
prompt.Description("e.g. http://localhost:8080/ or https://humio.example.com/")

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

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

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

if urlErr != nil {
fmt.Println("The valus must be a valid URL.") // ERROR
prompt.Error("The valus must be a valid URL.")
continue
}

addr = fmt.Sprintf("%v", addrUrl)
if !strings.HasSuffix(addr, "/") {
addr = addr + "/"
}

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

prompt.Output("")
fmt.Print("==> Testing Connection...")

status, statusErr := client.Status()

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

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

fmt.Println("")
fmt.Println("==> Connection Successful") // INFO
fmt.Println("")
break
}

fmt.Println("")
fmt.Println("2. Paste in your API Token") // INFO
fmt.Println("")
fmt.Println("To use Humio's CLI you will need to get a copy of your API Token.")
fmt.Println("The API token can be found in your 'Account Settings' section of the UI.")
fmt.Println("If you are running Humio without authorization just leave the API Token field empty.")
fmt.Println("")
prompt.Ask("We will now open the account page in a browser window. [Hit Any Key]")
prompt.Info("Paste in your Personal API Token")
prompt.Description("")
prompt.Description("To use Humio's CLI you will need to get a copy of your API Token.")
prompt.Description("The API token can be found in your 'Account Settings' section of the UI.")
prompt.Description("If you are running Humio without authorization just leave the API Token field empty.")
prompt.Description("")

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

fmt.Println("")
fmt.Println(fmt.Sprintf("If the browser did not open, you can manually visit:"))
fmt.Println(fmt.Sprintf("%ssettings", addr))
fmt.Println("")
prompt.Description("")
prompt.Description(fmt.Sprintf("If the browser did not open, you can manually visit:"))
prompt.Description(fmt.Sprintf("%ssettings", addr))
prompt.Description("")
}

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

if apiErr != nil {
fmt.Println(fmt.Errorf("authorization failed, try another token")) // ERROR
prompt.Error("authorization failed, try another token")
continue
}

fmt.Println("")
fmt.Println(fmt.Sprintf("==> Login successful '%s' 🎉", username)) // INFO
fmt.Println("")
fmt.Println(prompt.Colorize(fmt.Sprintf("==> Logged in as: [purple]%s[reset]", username)))
fmt.Println("")
break
}

viper.Set("address", addr)
viper.Set("token", token)

// viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author"))
// viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase"))
// viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper"))

configFile := viper.ConfigFileUsed()

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

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

fmt.Println("")
fmt.Println("Bye bye now!")
prompt.Output("Bye bye now! 🎉")
fmt.Println("")

return nil
Expand Down
34 changes: 23 additions & 11 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"path"

"github.com/humio/cli/api"
"github.com/humio/cli/prompt"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -45,14 +46,16 @@ func init() {
rootCmd = &cobra.Command{
Use: "humio [subcommand] [flags] [arguments]",
Short: "A management CLI for Humio.",
Long: `To set up your environment run:
Long: `
Environment Setup:
$ humio login
Sending Data:
Humio's CLI is not a replacement for fully-featured data-shippers like
LogStash, FileBeat or MetricBeat. It can be handy to easily send logs
to Humio to, e.g examine a local log file or test a parser on test input.
LogStash, FileBeat or MetricBeat. It can be handy to easily send logs
to Humio, e.g examine a local log file or test a parser on test input.
To stream the content of "/var/log/system.log" data to Humio:
Expand All @@ -62,11 +65,23 @@ or
$ humio ingest -o --tail=/var/log/system.log
Common commands:
Common Management Commands:
users <subcommand>
parsers <subcommand>
views <subcommand>
views <subcommand>
`,
Run: func(cmd *cobra.Command, args []string) {
// If no token or address flags are passed
// and no configuration file exists, run login.
if viper.GetString("token") == "" && viper.GetString("address") == "" {
newLoginCmd().Execute()
} else {
err := cmd.Help()
if err != nil {
fmt.Println(fmt.Errorf("error printing help: %s", err))
}
}
},
}

cobra.OnInitialize(initConfig)
Expand All @@ -76,7 +91,7 @@ Common commands:
// will be global for your application.
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.humio/config.yaml)")
rootCmd.PersistentFlags().StringVarP(&token, "token", "t", "", "The API token to user when talking to Humio. Overrides the value in your config file.")
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/)")
rootCmd.PersistentFlags().StringVarP(&address, "address", "a", "", "The HTTP address of the Humio cluster. Overrides the value in your config file.")

viper.BindPFlag("address", rootCmd.PersistentFlags().Lookup("address"))
viper.BindPFlag("token", rootCmd.PersistentFlags().Lookup("token"))
Expand Down Expand Up @@ -113,12 +128,9 @@ func initConfig() {
// If a config file is found, read it in.
err := viper.ReadInConfig()
if err == nil {
fmt.Println("Cluster Address:", viper.Get("address"))
} else {
fmt.Println(err)
prompt.Output("Using config file: " + cfgFile)
prompt.Output("Cluster address: " + viper.GetString("address"))
}

fmt.Println("Using config file:", cfgFile)
}

func NewApiClient(cmd *cobra.Command) *api.Client {
Expand Down
76 changes: 74 additions & 2 deletions prompt/prompt.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package prompt

import (
"bufio"
"fmt"
"log"
"os"
"strings"

"golang.org/x/crypto/ssh/terminal"
)

func Ask(question string) (string, error) {
var answer string
fmt.Print(question + ": ")
fmt.Print(" " + question + ": ")
n, err := fmt.Scanln(&answer)

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

func Confirm(text string) bool {
fmt.Print(" " + text + " [Y/n]: ")

reader := bufio.NewReader(os.Stdin)

for {
response, err := reader.ReadString('\n')
if err != nil {
log.Fatal(err)
}

response = strings.ToLower(strings.TrimSpace(response))

if response == "" || response == "y" || response == "yes" {
return true
} else if response == "n" || response == "no" {
return false
}
}
}

func AskSecret(question string) (string, error) {
fmt.Print(question + ": ")
fmt.Print(" " + question + ": ")
bytes, err := terminal.ReadPassword(0)

if err != nil {
return "", err
}

fmt.Print("***********************\n")
return string(bytes), nil
}

func Output(text string) {
fmt.Println(" ", text)
}

func Title(text string) {
c := "[underline][bold]" + text + "[reset]"
Output(Colorize(c))
}

func Description(text string) {
c := "[gray]" + text + "[reset]"
Output(Colorize(c))
}

func Error(text string) {
c := "[red]" + text + "[reset]"
Output(Colorize(c))
}

func Info(text string) {
c := "[purple]" + text + "[reset]"
Output(Colorize(c))
}

func Colorize(text string) string {
replacer := strings.NewReplacer(
"[reset]", "\x1b[0m",
"[gray]", "\x1b[38;5;249m",
"[purple]", "\x1b[38;5;129m",
"[bold]", "\x1b[1m",
"[red]", "\x1b[38;5;1m",
"[green]", "\x1b[38;5;2m",
"[underline]", "\x1b[4m",
)

return replacer.Replace(text)
}

func Owl() string {
return ` , ,
(O,o)
|)__)
-”-”-
`
}

0 comments on commit 075589f

Please sign in to comment.