In this example we are going to create a terminal (CLI) application with Golang. The command will have two required flags. Read flag package here. It will also progressively prompt user with three questions. One question is a secret value so we will make sure that the terminal doesn't echo it. For the secret inputs we use the terminal package.


Code


package main

import (
"bufio"
"flag"
"fmt"
"os"
"strings"

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

const usage = `Description: Login to server

Usage: %s [options]

Options:
`

var input struct {
env string
uid string
psw string
key string
pin string
}

func main() {
// Collect user input
flag.StringVar(&input.env, "env", input.env, "The environment to login - {dev|stag|prod}")
flag.StringVar(&input.uid, "uid", input.uid, "The user identifier.")
flag.Usage = func() {
_, _ = fmt.Fprintf(flag.CommandLine.Output(), usage, os.Args[0])
flag.PrintDefaults()
}
flag.Parse()

// Validate user input
validate()

// Prompt user for more input
key, err := promptPlain("What is your key?")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
input.key = key

psw, err := promptSecret("What is your password?")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
input.psw = psw

pin, err := promptPlain("What is your pin?")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
input.pin = pin

// Print user input
fmt.Printf("%+v\n", input)
}

// validate validates user input.
func validate() {
switch input.env {
case "dev", "stag", "prod":
break
default:
fmt.Println("The -env option is required.")
os.Exit(2)
}

if len(input.uid) == 0 {
fmt.Println("The -uid option is required.")
os.Exit(2)
}
}

// promptPlain prompts user for an input that is echo-ed on terminal.
func promptPlain(question string) (string, error) {
fmt.Printf(question + "\n> ")

reader := bufio.NewReader(os.Stdin)
for {
answer, err := reader.ReadString('\n')
if err != nil {
return "", err
}

answer = strings.TrimSuffix(answer, "\n")
return answer, nil
}
}

// promptSecret prompts user for an input that is not echo-ed on terminal.
func promptSecret(question string) (string, error) {
fmt.Printf(question + "\n> ")

raw, err := terminal.MakeRaw(0)
if err != nil {
return "", err
}
defer terminal.Restore(0, raw)

var (
prompt string
answer string
)

term := terminal.NewTerminal(os.Stdin, prompt)
for {
char, err := term.ReadPassword(prompt)
if err != nil {
return "", err
}
answer += char

if char == "" || char == answer {
return answer, nil
}
}
}

Build


$ go build -race -o login main.go

Help


$ ./login --help
Description: Login to server

Usage: ./login [options]

Options:
-env string
The environment to login - {dev|stag|prod}
-uid string
The user identifier.

Tests


Error


$ ./login
The -env option is required.

$ ./login -env dev
The -uid option is required.

Success


As you can see below, I answered to "What is your password?" but terminal didn't echo it!


$ ./login -env dev -uid inanzzz
What is your key?
> my-key
What is your password?
>
What is your pin?
> my-pin

{env:dev uid:inanzzz psw:my-password key:my-key pin:my-pin}