Skip to content

Commit

Permalink
txeh command line utility
Browse files Browse the repository at this point in the history
  • Loading branch information
cjimti committed Feb 16, 2019
1 parent 7d68f9b commit 06a2e74
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 2 deletions.
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
![txeh - /etc/hosts mangement](txeh.png)


# Etc Hosts Management Library
# Etc Hosts Management Utility & Go Library

[![Go Report Card](https://goreportcard.com/badge/github.com/txn2/txeh)](https://goreportcard.com/report/github.com/txn2/txeh)
[![GoDoc](https://godoc.org/github.com/txn2/irsync/txeh?status.svg)](https://godoc.org/github.com/txn2/txeh)

## txeh Utility

The txeh CLI application allows command line or scripted access to /etc/hosts file modification.

Examples:
```bash
# point the hostnames "test" and "test.two" to local loopback
sudo txeh add 127.0.0.1 test test.two

# remove the hostname "test"
sudo txeh remove test
```




### Motivation

TXEH was build to support [kubefwd](https://github.com/txn2/kubefwd), a Kubernetes port-forwarding utility utilizing [/etc/hosts] to associate custom hostnames with local IP addresses. A computer's [/etc/hosts] file is a powerful utility for developers and system administrators to create localized, custom DNS entries.

This small go library was developed to encapsulate the complexity of
working with /etc/hosts by providing a simple interface to load, create, remove and save entries to an /etc/host file. No validation is done on the input data. Validation is considered out of scope for this project, use with caution.
working with /etc/hosts by providing a simple interface to load, create, remove and save entries to an /etc/host file. No validation is done on the input data. **Validation is considered out of scope for this project, use with caution**.



Basic implemention:
```go
Expand Down Expand Up @@ -55,3 +75,5 @@ func main() {
}

```

[/etc/hosts]:https://en.wikipedia.org/wiki/Hosts_(file)
Binary file modified txeh.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
58 changes: 58 additions & 0 deletions util/cmd/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package cmd

import (
"errors"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(addCmd)
}

var addCmd = &cobra.Command{
Use: "add [IP] [HOSTNAME] [HOSTNAME] [HOSTNAME]...",
Short: "Add hostnames to /etc/hosts",
Long: `Add/Associate one or more hostnames to an IP address `,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("the \"add\" command requires an IP address and at least one hostname")
}

if validateIPAddress(args[0]) == false {
return errors.New("the IP address provided is not a valid ipv4 or ipv6 address")
}

if ok, hn := validateHostnames(args[1:]); !ok {
return errors.New(fmt.Sprintf("\"%s\" is not a valid hostname", hn))
}

return nil
},
Run: func(cmd *cobra.Command, args []string) {
if !Quiet {
fmt.Printf("Adding host(s) \"%s\" to IP address %s\n", strings.Join(args[1:], " "), args[0])
}

AddHosts(args[0], args[1:])
},
}

func AddHosts(ip string, hosts []string) {

etcHosts.AddHosts(ip, hosts)

if DryRun {
fmt.Print(etcHosts.RenderHostsFile())
return
}

err := etcHosts.Save()
if err != nil {
fmt.Printf("Error: could not save %s. Reason: %s\n", etcHosts.WriteFilePath, err.Error())
os.Exit(1)
}
}
28 changes: 28 additions & 0 deletions util/cmd/remove.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cmd

import (
"fmt"
"os"

"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(removeCmd)
}

var removeCmd = &cobra.Command{
Use: "remove [TYPE] [HOSTNAME|IP] [HOSTNAME|IP] [HOSTNAME|IP]...",
Short: "Remove a hostname or ip address",
Long: `Remove one or more hostnames or ip addresses from /etc/hosts`,
Run: func(cmd *cobra.Command, args []string) {
err := cmd.Help()
if err != nil {
fmt.Printf("Error: can not display help, reason: %s\n", err.Error())
os.Exit(1)
}

fmt.Println("Please specify a sub-command such as \"host\" or \"ip\"")
os.Exit(1)
},
}
54 changes: 54 additions & 0 deletions util/cmd/remove_host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cmd

import (
"errors"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
)

func init() {
removeCmd.AddCommand(removeHostCmd)
}

var removeHostCmd = &cobra.Command{
Use: "host [HOSTNAME] [HOSTNAME] [HOSTNAME]...",
Short: "Remove hostnames from /etc/hosts",
Long: `Remove one or more hostnames from /etc/hosts`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("the \"remove hosts\" command requires at least one hostname to remove")
}

if ok, hn := validateHostnames(args); !ok {
return errors.New(fmt.Sprintf("\"%s\" is not a valid hostname", hn))
}

return nil
},
Run: func(cmd *cobra.Command, args []string) {
if !Quiet {
fmt.Printf("Removing host(s) \"%s\"\n", strings.Join(args, " "))
}

RemoveHosts(args)
},
}

func RemoveHosts(hosts []string) {

etcHosts.RemoveHosts(hosts)

if DryRun {
fmt.Print(etcHosts.RenderHostsFile())
return
}

err := etcHosts.Save()
if err != nil {
fmt.Printf("Error: could not save %s. Reason: %s\n", etcHosts.WriteFilePath, err.Error())
os.Exit(1)
}
}
54 changes: 54 additions & 0 deletions util/cmd/remove_ip.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cmd

import (
"errors"
"fmt"
"os"
"strings"

"github.com/spf13/cobra"
)

func init() {
removeCmd.AddCommand(removeIpCmd)
}

var removeIpCmd = &cobra.Command{
Use: "ip [IP] [IP] [IP]...",
Short: "Remove IP addresses from /etc/hosts",
Long: `Remove one or more IP addresses from /etc/hosts`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("the \"remove ip\" command requires at least one IP address to remove")
}

if ok, ip := validateIPAddresses(args); !ok {
return errors.New(fmt.Sprintf("\"%s\" is not a valid ip address", ip))
}

return nil
},
Run: func(cmd *cobra.Command, args []string) {
if !Quiet {
fmt.Printf("Removing ip(s) \"%s\"\n", strings.Join(args, " "))
}

RemoveIPs(args)
},
}

func RemoveIPs(ips []string) {

etcHosts.RemoveAddresses(ips)

if DryRun {
fmt.Print(etcHosts.RenderHostsFile())
return
}

err := etcHosts.Save()
if err != nil {
fmt.Printf("Error: could not save %s. Reason: %s\n", etcHosts.WriteFilePath, err.Error())
os.Exit(1)
}
}
120 changes: 120 additions & 0 deletions util/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package cmd

import (
"fmt"
"net"
"os"
"regexp"

"github.com/spf13/cobra"
"github.com/txn2/txeh"
)

var rootCmd = &cobra.Command{
Use: "txeh",
Short: "txeh is a /etc/hosts manager",
Long: ` _ _
| |___ _____| |__
| __\ \/ / _ \ '_ \
| |_ > < __/ | | |
\__/_/\_\___|_| |_| v` + VERSION + `
Add, remove and re-associate hostname entries in your /etc/hosts file.
Read more including useage as a Go library at https://github.com/txn2/txeh`,
Run: func(cmd *cobra.Command, args []string) {
err := cmd.Help()
if err != nil {
fmt.Printf("Error: can not display help, reason: %s\n", err.Error())
os.Exit(1)
}

fmt.Println("Please specify a sub-command such as \"add\" or \"remove\"")
os.Exit(1)
},
PersistentPreRun: func(cmd *cobra.Command, args []string) {
initEtcHosts()
},
}

var Quiet bool
var HostsFileReadPath string
var HostsFileWritePath string
var DryRun bool

var etcHosts *txeh.Hosts
var hostnameRegex *regexp.Regexp

func init() {
rootCmd.PersistentFlags().BoolVarP(&DryRun, "dryrun", "d", false, "dry run, output to stdout (ignores quiet)")
rootCmd.PersistentFlags().BoolVarP(&Quiet, "quiet", "q", false, "no output")
rootCmd.PersistentFlags().StringVarP(&HostsFileReadPath, "read", "r", "", "(override) Path to read /etc/hosts file.")
rootCmd.PersistentFlags().StringVarP(&HostsFileWritePath, "write", "w", "", "(override) Path to write /etc/hosts file.")

// validate hostnames (allow underscore for service records)
hostnameRegex = regexp.MustCompile(`^([A-Za-z]|[0-9]|-|_|\.)+$`)
}

func validateIPAddresses(ips []string) (bool, string) {
for _, ip := range ips {
if validateIPAddress(ip) == false {
return false, ip
}
}

return true, ""
}

func validateIPAddress(ip string) bool {

if net.ParseIP(ip) == nil {
return false
}

return true
}

func validateHostnames(hostnames []string) (bool, string) {
for _, hn := range hostnames {
if validateHostname(hn) != true {
return false, hn
}
}

return true, ""
}

func validateHostname(hostname string) bool {
return hostnameRegex.MatchString(hostname)
}

func initEtcHosts() {
if HostsFileReadPath == "" && HostsFileWritePath == "" {
hosts, err := txeh.NewHostsDefault()
if err != nil {
fmt.Println(err)
os.Exit(1)
}

etcHosts = hosts
return
}

hosts, err := txeh.NewHosts(&txeh.HostsConfig{
ReadFilePath: HostsFileReadPath,
WriteFilePath: HostsFileWritePath,
})
if err != nil {
fmt.Println(err)
os.Exit(1)
}

etcHosts = hosts
}

func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
return
}
22 changes: 22 additions & 0 deletions util/cmd/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package cmd

import (
"fmt"

"github.com/spf13/cobra"
)

const VERSION = "0.0.0"

func init() {
rootCmd.AddCommand(versionCmd)
}

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of txeh",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("txeh Version %s", VERSION)
},
}
7 changes: 7 additions & 0 deletions util/txeh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package main

import "github.com/txn2/txeh/util/cmd"

func main() {
cmd.Execute()
}

0 comments on commit 06a2e74

Please sign in to comment.