2015-02-14 08:21:20 +00:00
|
|
|
package hostess
|
|
|
|
|
|
|
|
import (
|
2015-03-29 01:10:35 +00:00
|
|
|
"bytes"
|
2015-02-14 11:01:13 +00:00
|
|
|
"fmt"
|
2015-05-25 17:22:23 +00:00
|
|
|
"io/ioutil"
|
2015-02-14 11:01:13 +00:00
|
|
|
"os"
|
2015-02-14 11:40:31 +00:00
|
|
|
"strings"
|
2015-05-25 17:22:23 +00:00
|
|
|
|
|
|
|
"github.com/codegangsta/cli"
|
2015-02-14 08:21:20 +00:00
|
|
|
)
|
|
|
|
|
2015-03-29 01:10:35 +00:00
|
|
|
// ErrCantWriteHostFile indicates that we are unable to write to the hosts file
|
|
|
|
var ErrCantWriteHostFile = fmt.Errorf(
|
|
|
|
"Unable to write to %s. Maybe you need to sudo?", GetHostsPath())
|
|
|
|
|
2015-02-14 13:01:43 +00:00
|
|
|
// MaybeErrorln will print an error message unless -s is passed
|
2015-02-14 11:01:13 +00:00
|
|
|
func MaybeErrorln(c *cli.Context, message string) {
|
2015-02-14 13:01:43 +00:00
|
|
|
if !c.Bool("s") {
|
2015-03-29 01:10:35 +00:00
|
|
|
os.Stderr.WriteString(fmt.Sprintf("%s\n", message))
|
2015-02-14 08:21:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-14 13:01:43 +00:00
|
|
|
// MaybeError will print an error message unless -s is passed and then exit
|
2015-02-14 11:01:13 +00:00
|
|
|
func MaybeError(c *cli.Context, message string) {
|
2015-02-14 11:07:29 +00:00
|
|
|
MaybeErrorln(c, message)
|
2015-02-14 11:01:13 +00:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2015-02-14 13:01:43 +00:00
|
|
|
// MaybePrintln will print a message unless -q or -s is passed
|
2015-02-14 11:01:13 +00:00
|
|
|
func MaybePrintln(c *cli.Context, message string) {
|
2015-02-14 13:01:43 +00:00
|
|
|
if !c.Bool("q") && !c.Bool("s") {
|
2015-02-14 11:01:13 +00:00
|
|
|
fmt.Println(message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-14 12:33:11 +00:00
|
|
|
// MaybeLoadHostFile will try to load, parse, and return a Hostfile. If we
|
|
|
|
// encounter errors we will terminate, unless -f is passed.
|
2015-02-14 11:01:13 +00:00
|
|
|
func MaybeLoadHostFile(c *cli.Context) *Hostfile {
|
2015-03-21 10:01:41 +00:00
|
|
|
hostsfile, errs := LoadHostfile()
|
2015-02-14 11:01:13 +00:00
|
|
|
if len(errs) > 0 && !c.Bool("f") {
|
|
|
|
for _, err := range errs {
|
|
|
|
MaybeErrorln(c, err.Error())
|
|
|
|
}
|
2015-03-29 01:10:35 +00:00
|
|
|
MaybeError(c, "Errors while parsing hostsfile. Try hostess fix")
|
|
|
|
}
|
|
|
|
return hostsfile
|
|
|
|
}
|
|
|
|
|
|
|
|
// AlwaysLoadHostFile will load, parse, and return a Hostfile. If we encouter
|
|
|
|
// errors they will be printed to the terminal, but we'll try to continue.
|
|
|
|
func AlwaysLoadHostFile(c *cli.Context) *Hostfile {
|
|
|
|
hostsfile, errs := LoadHostfile()
|
|
|
|
if len(errs) > 0 {
|
|
|
|
for _, err := range errs {
|
|
|
|
MaybeErrorln(c, err.Error())
|
|
|
|
}
|
2015-02-14 11:01:13 +00:00
|
|
|
}
|
|
|
|
return hostsfile
|
|
|
|
}
|
|
|
|
|
2015-03-29 08:54:08 +00:00
|
|
|
// MaybeSaveHostFile will output or write the Hostfile, or exit 1 and error.
|
2015-03-29 06:34:08 +00:00
|
|
|
func MaybeSaveHostFile(c *cli.Context, hostfile *Hostfile) {
|
2015-03-29 08:54:08 +00:00
|
|
|
// If -n is passed, no-op and output the resultant hosts file to stdout.
|
|
|
|
// Otherwise it's for real and we're going to write it.
|
|
|
|
if c.Bool("n") {
|
|
|
|
fmt.Printf("%s", hostfile.Format())
|
|
|
|
} else {
|
|
|
|
err := hostfile.Save()
|
|
|
|
if err != nil {
|
|
|
|
MaybeError(c, ErrCantWriteHostFile.Error())
|
|
|
|
}
|
2015-03-29 06:34:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-14 12:33:11 +00:00
|
|
|
// StrPadRight adds spaces to the right of a string until it reaches l length.
|
|
|
|
// If the input string is already that long, do nothing.
|
2015-02-14 11:40:31 +00:00
|
|
|
func StrPadRight(s string, l int) string {
|
|
|
|
return s + strings.Repeat(" ", l-len(s))
|
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Add command parses <hostname> <ip> and adds or updates a hostname in the
|
2015-03-29 06:34:08 +00:00
|
|
|
// hosts file. If the aff command is used the hostname will be disabled or
|
|
|
|
// added in the off state.
|
2015-02-14 11:01:13 +00:00
|
|
|
func Add(c *cli.Context) {
|
|
|
|
if len(c.Args()) != 2 {
|
|
|
|
MaybeError(c, "expected <hostname> <ip>")
|
|
|
|
}
|
|
|
|
|
|
|
|
hostsfile := MaybeLoadHostFile(c)
|
2015-02-24 12:17:31 +00:00
|
|
|
|
2015-03-29 06:34:08 +00:00
|
|
|
hostname := NewHostname(c.Args()[0], c.Args()[1], true)
|
|
|
|
// If the command is aff instead of add then the entry should be disabled
|
|
|
|
if c.Command.Name == "aff" {
|
|
|
|
hostname.Enabled = false
|
2015-02-14 12:33:11 +00:00
|
|
|
}
|
|
|
|
|
2015-03-29 06:34:08 +00:00
|
|
|
replace := hostsfile.Hosts.ContainsDomain(hostname.Domain)
|
|
|
|
// Note that Add() may return an error, but they are informational only. We
|
|
|
|
// don't actually care what the error is -- we just want to add the
|
|
|
|
// hostname and save the file. This way the behavior is idempotent.
|
|
|
|
hostsfile.Hosts.Add(hostname)
|
|
|
|
|
|
|
|
// If the user passes -n then we'll Add and show the new hosts file, but
|
|
|
|
// not save it.
|
|
|
|
if c.Bool("n") {
|
|
|
|
fmt.Println(hostsfile.Format())
|
|
|
|
} else {
|
|
|
|
MaybeSaveHostFile(c, hostsfile)
|
|
|
|
// We'll give a little bit of information about whether we added or
|
|
|
|
// updated, but if the user wants to know they can use has or ls to
|
|
|
|
// show the file before they run the operation. Maybe later we can add
|
|
|
|
// a verbose flag to show more information.
|
|
|
|
if replace {
|
|
|
|
MaybePrintln(c, fmt.Sprintf("Updated %s", hostname.FormatHuman()))
|
2015-02-14 11:01:13 +00:00
|
|
|
} else {
|
2015-03-02 08:55:34 +00:00
|
|
|
MaybePrintln(c, fmt.Sprintf("Added %s", hostname.FormatHuman()))
|
2015-02-14 11:01:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Del command removes any hostname(s) matching <domain> from the hosts file
|
2015-02-14 12:33:11 +00:00
|
|
|
func Del(c *cli.Context) {
|
|
|
|
if len(c.Args()) != 1 {
|
|
|
|
MaybeError(c, "expected <hostname>")
|
|
|
|
}
|
|
|
|
domain := c.Args()[0]
|
|
|
|
hostsfile := MaybeLoadHostFile(c)
|
|
|
|
|
2015-03-21 09:40:19 +00:00
|
|
|
found := hostsfile.Hosts.ContainsDomain(domain)
|
2015-02-14 12:33:11 +00:00
|
|
|
if found {
|
2015-03-21 09:40:19 +00:00
|
|
|
hostsfile.Hosts.RemoveDomain(domain)
|
2015-02-14 12:33:11 +00:00
|
|
|
if c.Bool("n") {
|
|
|
|
fmt.Println(hostsfile.Format())
|
|
|
|
} else {
|
2015-03-29 06:34:08 +00:00
|
|
|
MaybeSaveHostFile(c, hostsfile)
|
2015-02-14 12:33:11 +00:00
|
|
|
MaybePrintln(c, fmt.Sprintf("Deleted %s", domain))
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
MaybePrintln(c, fmt.Sprintf("%s not found in %s", domain, GetHostsPath()))
|
|
|
|
}
|
2015-02-14 08:21:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Has command indicates whether a hostname is present in the hosts file
|
2015-02-14 12:33:11 +00:00
|
|
|
func Has(c *cli.Context) {
|
|
|
|
if len(c.Args()) != 1 {
|
|
|
|
MaybeError(c, "expected <hostname>")
|
|
|
|
}
|
|
|
|
domain := c.Args()[0]
|
|
|
|
hostsfile := MaybeLoadHostFile(c)
|
|
|
|
|
2015-03-21 09:40:19 +00:00
|
|
|
found := hostsfile.Hosts.ContainsDomain(domain)
|
2015-02-14 12:33:11 +00:00
|
|
|
if found {
|
|
|
|
MaybePrintln(c, fmt.Sprintf("Found %s in %s", domain, GetHostsPath()))
|
|
|
|
} else {
|
|
|
|
MaybeError(c, fmt.Sprintf("%s not found in %s", domain, GetHostsPath()))
|
|
|
|
}
|
|
|
|
|
2015-02-14 08:21:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-29 09:21:30 +00:00
|
|
|
// OnOff enables (uncomments) or disables (comments) the specified hostname in
|
|
|
|
// the hosts file. Exits code 1 if the hostname is missing.
|
2015-03-29 08:38:25 +00:00
|
|
|
func OnOff(c *cli.Context) {
|
2015-02-14 12:33:11 +00:00
|
|
|
if len(c.Args()) != 1 {
|
|
|
|
MaybeError(c, "expected <hostname>")
|
|
|
|
}
|
2015-03-29 06:34:08 +00:00
|
|
|
domain := c.Args()[0]
|
|
|
|
hostsfile := MaybeLoadHostFile(c)
|
2015-03-29 08:13:30 +00:00
|
|
|
|
|
|
|
// Switch on / off commands
|
|
|
|
success := false
|
|
|
|
if c.Command.Name == "on" {
|
|
|
|
success = hostsfile.Hosts.Enable(domain)
|
|
|
|
} else {
|
|
|
|
success = hostsfile.Hosts.Disable(domain)
|
|
|
|
}
|
|
|
|
|
2015-03-29 06:34:08 +00:00
|
|
|
if success {
|
|
|
|
MaybeSaveHostFile(c, hostsfile)
|
2015-03-29 08:15:37 +00:00
|
|
|
if c.Command.Name == "on" {
|
|
|
|
MaybePrintln(c, fmt.Sprintf("Enabled %s", domain))
|
|
|
|
} else {
|
|
|
|
MaybePrintln(c, fmt.Sprintf("Disabled %s", domain))
|
|
|
|
}
|
2015-03-29 06:34:08 +00:00
|
|
|
} else {
|
|
|
|
MaybeError(c, fmt.Sprintf("%s not found in %s", domain, GetHostsPath()))
|
|
|
|
}
|
2015-02-14 08:21:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Ls command shows a list of hostnames in the hosts file
|
2015-02-14 11:40:31 +00:00
|
|
|
func Ls(c *cli.Context) {
|
2015-03-29 01:10:35 +00:00
|
|
|
hostsfile := AlwaysLoadHostFile(c)
|
2015-02-14 11:40:31 +00:00
|
|
|
maxdomain := 0
|
|
|
|
maxip := 0
|
2015-02-14 12:33:11 +00:00
|
|
|
for _, hostname := range hostsfile.Hosts {
|
2015-02-14 11:40:31 +00:00
|
|
|
dlen := len(hostname.Domain)
|
|
|
|
if dlen > maxdomain {
|
|
|
|
maxdomain = dlen
|
|
|
|
}
|
2015-03-02 08:55:34 +00:00
|
|
|
ilen := len(hostname.IP)
|
2015-02-14 11:40:31 +00:00
|
|
|
if ilen > maxip {
|
|
|
|
maxip = ilen
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-22 08:42:08 +00:00
|
|
|
for _, hostname := range hostsfile.Hosts {
|
|
|
|
fmt.Printf("%s -> %s %s\n",
|
|
|
|
StrPadRight(hostname.Domain, maxdomain),
|
|
|
|
StrPadRight(hostname.IP.String(), maxip),
|
|
|
|
hostname.FormatEnabled())
|
|
|
|
}
|
2015-02-14 08:21:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 09:46:12 +00:00
|
|
|
const fixHelp = `Programmatically rewrite your hostsfile.
|
2015-02-14 11:01:13 +00:00
|
|
|
|
2015-03-29 06:34:08 +00:00
|
|
|
Domains pointing to the same IP will be consolidated onto single lines and
|
|
|
|
sorted. Duplicates and conflicts will be removed. Extra whitespace and comments
|
|
|
|
will be removed.
|
2015-02-14 11:01:13 +00:00
|
|
|
|
|
|
|
hostess fix Rewrite the hostsfile
|
2015-03-29 09:21:30 +00:00
|
|
|
hostess fix -n Show the new hostsfile. Don't write it to disk.
|
2015-02-14 11:01:13 +00:00
|
|
|
`
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Fix command removes duplicates and conflicts from the hosts file
|
2015-02-14 11:01:13 +00:00
|
|
|
func Fix(c *cli.Context) {
|
2015-03-29 01:10:35 +00:00
|
|
|
hostsfile := AlwaysLoadHostFile(c)
|
|
|
|
if bytes.Equal(hostsfile.GetData(), hostsfile.Format()) {
|
|
|
|
MaybePrintln(c, fmt.Sprintf("%s is already formatted and contains no dupes or conflicts; nothing to do", GetHostsPath()))
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
2015-03-29 08:54:08 +00:00
|
|
|
MaybeSaveHostFile(c, hostsfile)
|
2015-02-14 11:01:13 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Dump command outputs hosts file contents as JSON
|
2015-02-14 12:33:11 +00:00
|
|
|
func Dump(c *cli.Context) {
|
2015-05-25 17:22:23 +00:00
|
|
|
hostsfile := AlwaysLoadHostFile(c)
|
2015-05-25 21:03:48 +00:00
|
|
|
jsonbytes, err := hostsfile.Hosts.Dump()
|
2015-05-25 17:22:23 +00:00
|
|
|
if err != nil {
|
|
|
|
MaybeError(c, err.Error())
|
|
|
|
}
|
2015-05-25 21:03:48 +00:00
|
|
|
fmt.Println(fmt.Sprintf("%s", jsonbytes))
|
2015-02-14 08:21:20 +00:00
|
|
|
}
|
|
|
|
|
2015-03-02 08:22:05 +00:00
|
|
|
// Apply command adds hostnames to the hosts file from JSON
|
2015-02-14 12:33:11 +00:00
|
|
|
func Apply(c *cli.Context) {
|
2015-05-25 17:22:23 +00:00
|
|
|
if len(c.Args()) != 1 {
|
|
|
|
MaybeError(c, "Usage should be apply [filename]")
|
|
|
|
}
|
|
|
|
filename := c.Args()[0]
|
|
|
|
|
2015-05-25 21:03:48 +00:00
|
|
|
jsonbytes, err := ioutil.ReadFile(filename)
|
2015-05-25 17:22:23 +00:00
|
|
|
if err != nil {
|
|
|
|
MaybeError(c, fmt.Sprintf("Unable to read %s: %s", filename, err))
|
|
|
|
}
|
|
|
|
|
|
|
|
hostfile := AlwaysLoadHostFile(c)
|
2015-05-25 21:03:48 +00:00
|
|
|
err = hostfile.Hosts.Apply(jsonbytes)
|
2015-05-25 17:22:23 +00:00
|
|
|
if err != nil {
|
|
|
|
MaybeError(c, fmt.Sprintf("Error applying changes to hosts file: %s", err))
|
|
|
|
}
|
2015-02-14 12:33:11 +00:00
|
|
|
|
2015-05-25 17:22:23 +00:00
|
|
|
MaybeSaveHostFile(c, hostfile)
|
|
|
|
MaybePrintln(c, fmt.Sprintf("%s applied", filename))
|
2015-02-14 08:21:20 +00:00
|
|
|
}
|