Fix argument parsing for CLI

Add tests for main and commands
Fix documentation for installation so the program works with sudo
pull/38/head
Chris Bednarski 4 years ago
parent 127256655c
commit d3e97d1758

@ -5,7 +5,8 @@ test:
go vet ./... go vet ./...
install: install:
go install . go build -o bin/hostess .
sudo mv bin/hostess /usr/local/bin/hostess
release: test release: test
GOOS=windows GOARCH=amd64 go build -ldflags "-X main.Version=${RELEASE_VERSION}" -o bin/hostess_windows_amd64.exe . GOOS=windows GOARCH=amd64 go build -ldflags "-X main.Version=${RELEASE_VERSION}" -o bin/hostess_windows_amd64.exe .

@ -20,7 +20,9 @@ and call it a day.
Download a [precompiled release](https://github.com/cbednarski/hostess/releases) Download a [precompiled release](https://github.com/cbednarski/hostess/releases)
from GitHub, or build from source (with a [recent version of Go](https://golang.org/dl)): from GitHub, or build from source (with a [recent version of Go](https://golang.org/dl)):
go get -u github.com/cbednarski/hostess git clone https://github.com/cbednarski/hostess
cd hostess
make install
## Usage ## Usage

@ -10,6 +10,8 @@ import (
"strings" "strings"
) )
const EnvHostessPath = `HOSTESS_PATH`
const defaultOSX = ` const defaultOSX = `
## ##
# Host Database # Host Database
@ -53,7 +55,7 @@ func NewHostfile() *Hostfile {
// GetHostsPath returns the location of the hostfile; either env HOSTESS_PATH // GetHostsPath returns the location of the hostfile; either env HOSTESS_PATH
// or /etc/hosts if HOSTESS_PATH is not set. // or /etc/hosts if HOSTESS_PATH is not set.
func GetHostsPath() string { func GetHostsPath() string {
path := os.Getenv("HOSTESS_PATH") path := os.Getenv(EnvHostessPath)
if path == "" { if path == "" {
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
path = "C:\\Windows\\System32\\drivers\\etc\\hosts" path = "C:\\Windows\\System32\\drivers\\etc\\hosts"

@ -9,6 +9,8 @@ import (
"strings" "strings"
) )
const EnvHostessFmt = `HOSTESS_FMT`
// ErrInvalidVersionArg is raised when a function expects IPv 4 or 6 but is // ErrInvalidVersionArg is raised when a function expects IPv 4 or 6 but is
// passed a value not 4 or 6. // passed a value not 4 or 6.
var ErrInvalidVersionArg = errors.New("version argument must be 4 or 6") var ErrInvalidVersionArg = errors.New("version argument must be 4 or 6")

@ -65,8 +65,8 @@ func CommandUsage(command string) error {
return fmt.Errorf("Usage: %s %s <hostname>", os.Args[0], command) return fmt.Errorf("Usage: %s %s <hostname>", os.Args[0], command)
} }
func wrappedMain() error { func wrappedMain(args []string) error {
cli := flag.NewFlagSet(os.Args[0], flag.ExitOnError) cli := flag.NewFlagSet(args[0], flag.ExitOnError)
ipv4 := cli.Bool("4", false, "IPv4") ipv4 := cli.Bool("4", false, "IPv4")
ipv6 := cli.Bool("6", false, "IPv6") ipv6 := cli.Bool("6", false, "IPv6")
preview := cli.Bool("n", false, "preview") preview := cli.Bool("n", false, "preview")
@ -74,7 +74,7 @@ func wrappedMain() error {
fmt.Printf(help, hostess.GetHostsPath()) fmt.Printf(help, hostess.GetHostsPath())
} }
if err := cli.Parse(os.Args[1:]); err != nil { if err := cli.Parse(args[1:]); err != nil {
return err return err
} }
@ -96,7 +96,7 @@ func wrappedMain() error {
fmt.Println(Version) fmt.Println(Version)
return nil return nil
case "-h", "--help", "help": case "", "-h", "--help", "help":
cli.Usage() cli.Usage()
return nil return nil
@ -104,46 +104,46 @@ func wrappedMain() error {
return Format(options) return Format(options)
case "add": case "add":
if len(cli.Args()) != 2 { if len(cli.Args()) != 3 {
return fmt.Errorf("Usage: %s add <hostname> <ip>", cli.Name()) return fmt.Errorf("Usage: %s add <hostname> <ip>", cli.Name())
} }
return Add(options, cli.Arg(0), cli.Arg(1)) return Add(options, cli.Arg(1), cli.Arg(2))
case "rm": case "rm":
if cli.Arg(0) == "" { if cli.Arg(1) == "" {
return CommandUsage(command) return CommandUsage(command)
} }
return Remove(options, cli.Arg(0)) return Remove(options, cli.Arg(1))
case "on": case "on":
if cli.Arg(0) == "" { if cli.Arg(1) == "" {
return CommandUsage(command) return CommandUsage(command)
} }
return Enable(options, cli.Arg(0)) return Enable(options, cli.Arg(1))
case "off": case "off":
if cli.Arg(0) == "" { if cli.Arg(1) == "" {
return CommandUsage(command) return CommandUsage(command)
} }
return Disable(options, cli.Arg(0)) return Disable(options, cli.Arg(1))
case "ls": case "ls":
return List(options) return List(options)
case "has": case "has":
if cli.Arg(0) == "" { if cli.Arg(1) == "" {
return CommandUsage(command) return CommandUsage(command)
} }
return Has(options, cli.Arg(0)) return Has(options, cli.Arg(1))
case "dump": case "dump":
return Dump(options) return Dump(options)
case "apply": case "apply":
if cli.Arg(0) == "" { if cli.Arg(1) == "" {
return fmt.Errorf("Usage: %s apply <filename>", os.Args[0]) return fmt.Errorf("Usage: %s apply <filename>", args[0])
} }
return Apply(options, cli.Arg(0)) return Apply(options, cli.Arg(1))
default: default:
return ErrInvalidCommand return ErrInvalidCommand
@ -151,5 +151,5 @@ func wrappedMain() error {
} }
func main() { func main() {
ExitWithError(wrappedMain()) ExitWithError(wrappedMain(os.Args))
} }

@ -0,0 +1,172 @@
package main
import (
"io"
"io/ioutil"
"os"
"strings"
"testing"
"github.com/cbednarski/hostess/hostess"
)
// CopyHostsFile creates a temporary hosts file in the system temp directory,
// sets the HOSTESS_PATH environment variable, and returns the file path and a
// cleanup function
func CopyHostsFile(t *testing.T) (string, func()) {
t.Helper()
fixture, err := os.Open("testdata/ubuntu.hosts")
if err != nil {
t.Fatal(err)
}
temp, err := ioutil.TempFile("", "hostess-test-*")
if err != nil {
t.Fatal(err)
}
if _, err := io.Copy(temp, fixture); err != nil {
t.Fatal(err)
}
if err := os.Setenv(hostess.EnvHostessPath, temp.Name()); err != nil {
t.Fatal(err)
}
cleanup := func() {
os.Remove(temp.Name())
}
return temp.Name(), cleanup
}
func TestFormat(t *testing.T) {
temp, cleanup := CopyHostsFile(t)
defer cleanup()
if err := wrappedMain(strings.Split("hostess fmt", " ")); err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadFile(temp)
if err != nil {
t.Fatal(err)
}
output := string(data)
expected := `127.0.0.1 localhost myapp.local
127.0.1.1 ubuntu
192.168.0.30 raspberrypi
::1 ip6-localhost ip6-loopback
fe00:: ip6-localnet
ff00:: ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
`
if output != expected {
t.Errorf("--- Expected ---\n%s\n--- Found ---\n%s\n", expected, output)
}
}
func TestAddHostname(t *testing.T) {
temp, cleanup := CopyHostsFile(t)
defer cleanup()
if err := wrappedMain(strings.Split("hostess add my.new.website 127.0.0.1", " ")); err != nil {
t.Fatal(err)
}
if err := wrappedMain(strings.Split("hostess add mediaserver 192.168.0.82", " ")); err != nil {
t.Fatal(err)
}
if err := wrappedMain(strings.Split("hostess add myapp.local 10.20.0.23", " ")); err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadFile(temp)
if err != nil {
t.Fatal(err)
}
output := string(data)
expected := `127.0.0.1 localhost my.new.website
127.0.1.1 ubuntu
10.20.0.23 myapp.local
192.168.0.30 raspberrypi
192.168.0.82 mediaserver
::1 ip6-localhost ip6-loopback
fe00:: ip6-localnet
ff00:: ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
`
if output != expected {
t.Errorf("--- Expected ---\n%s\n--- Found ---\n%s\n", expected, output)
}
}
func TestRemoveHostname(t *testing.T) {
temp, cleanup := CopyHostsFile(t)
defer cleanup()
if err := wrappedMain(strings.Split("hostess rm myapp.local", " ")); err != nil {
t.Fatal(err)
}
if err := wrappedMain(strings.Split("hostess rm raspberrypi", " ")); err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadFile(temp)
if err != nil {
t.Fatal(err)
}
output := string(data)
expected := `127.0.0.1 localhost
127.0.1.1 ubuntu
::1 ip6-localhost ip6-loopback
fe00:: ip6-localnet
ff00:: ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
`
if output != expected {
t.Errorf("--- Expected ---\n%s\n--- Found ---\n%s\n", expected, output)
}
}
func TestHostnameOff(t *testing.T) {
temp, cleanup := CopyHostsFile(t)
defer cleanup()
if err := wrappedMain(strings.Split("hostess off myapp.local", " ")); err != nil {
t.Fatal(err)
}
if err := wrappedMain(strings.Split("hostess off raspberrypi", " ")); err != nil {
t.Fatal(err)
}
data, err := ioutil.ReadFile(temp)
if err != nil {
t.Fatal(err)
}
output := string(data)
expected := `127.0.0.1 localhost
# 127.0.0.1 myapp.local
127.0.1.1 ubuntu
# 192.168.0.30 raspberrypi
::1 ip6-localhost ip6-loopback
fe00:: ip6-localnet
ff00:: ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
`
if output != expected {
t.Errorf("--- Expected ---\n%s\n--- Found ---\n%s\n", expected, output)
}
}

@ -0,0 +1,10 @@
127.0.0.1 localhost myapp.local
127.0.1.1 ubuntu
192.168.0.30 raspberrypi
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
Loading…
Cancel
Save