-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/save dns records to hosts #172
base: develop
Are you sure you want to change the base?
Changes from all commits
cc38caa
fd8c63f
f628762
73afa6e
d89b894
a55d874
05c5997
d1a9e76
9794851
b1a93f6
85ba91b
09ff3c7
9cd6f8f
ddf80ff
61f9d7b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,11 @@ | ||
package commands | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"os" | ||
"strings" | ||
|
||
"github.com/bitly/go-simplejson" | ||
|
@@ -17,12 +19,61 @@ type DNSRecords struct { | |
BaseCommand | ||
} | ||
|
||
// DNSRecord is the struct for a single DNS entry | ||
type DNSRecord struct { | ||
Id string // nolint | ||
Name string | ||
Image string | ||
IPs []string | ||
TTL int64 | ||
Aliases []string | ||
} | ||
|
||
// DNSRecordsList is an array of DNSRecords | ||
type DNSRecordsList []*DNSRecord | ||
|
||
const ( | ||
unixHostsPreamble = "##+++ added by rig" | ||
unixHostsPostamble = "##--- end rig additions" | ||
) | ||
|
||
func (record *DNSRecord) String() string { | ||
result := "" | ||
for _, ip := range record.IPs { | ||
result += fmt.Sprintf("%s\t%s.%s.%s\n", ip, record.Name, record.Image, "vm") | ||
// attach any aliases too | ||
for _, a := range record.Aliases { | ||
result += fmt.Sprintf("%s\t%s\n", ip, a) | ||
} | ||
} | ||
return result | ||
} | ||
|
||
// String converts a list of DNSRecords to a formatted string | ||
func (hosts DNSRecordsList) String() string { | ||
result := "" | ||
for _, host := range hosts { | ||
result += host.String() | ||
} | ||
return result | ||
} | ||
|
||
// Commands returns the operations supported by this command | ||
func (cmd *DNSRecords) Commands() []cli.Command { | ||
return []cli.Command{ | ||
{ | ||
Name: "dns-records", | ||
Usage: "List all DNS records for running containers", | ||
Name: "dns-records", | ||
Usage: "List all DNS records for running containers", | ||
Flags: []cli.Flag{ | ||
cli.BoolFlag{ | ||
Name: "save", | ||
Usage: "Save the DNS records to /etc/hosts or {FIX insert Windows desscription}", | ||
}, | ||
cli.BoolFlag{ | ||
Name: "remove", | ||
Usage: "Remove the DNS records from /etc/hosts or {FIX insert Windows desscription}", | ||
}, | ||
}, | ||
Before: cmd.Before, | ||
Action: cmd.Run, | ||
}, | ||
|
@@ -31,27 +82,31 @@ func (cmd *DNSRecords) Commands() []cli.Command { | |
|
||
// Run executes the `rig dns-records` command | ||
func (cmd *DNSRecords) Run(c *cli.Context) error { | ||
// Don't require rig to be started to remove records | ||
if c.Bool("remove") { | ||
if c.Bool("save") { | ||
return cmd.Failure("--remove and --save are mutually exclusive", "COMMAND-ERROR", 13) | ||
} | ||
// TODO The VM might have to be up for Windows | ||
return cmd.removeDNSRecords() | ||
} | ||
|
||
records, err := cmd.LoadRecords() | ||
if err != nil { | ||
return cmd.Failure(err.Error(), "COMMAND-ERROR", 13) | ||
} | ||
|
||
for _, record := range records { | ||
for _, ip := range record["IPs"].([]interface{}) { | ||
fmt.Printf("%s\t%s.%s.%s\n", ip, record["Name"], record["Image"], "vm") | ||
// attach any aliases too | ||
for _, a := range record["Aliases"].([]interface{}) { | ||
fmt.Printf("%s\t%s\n", ip, a) | ||
} | ||
} | ||
if c.Bool("save") { | ||
return cmd.saveDNSRecords(records) | ||
} | ||
|
||
printDNSRecords(records) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if you do a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They always return if they do a save or remove. So it just falls through to the default if they don't specify either. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OH yeah. ha. You got it. |
||
|
||
return cmd.Success("") | ||
} | ||
|
||
// LoadRecords retrieves the records from DNSDock and processes/return them | ||
func (cmd *DNSRecords) LoadRecords() ([]map[string]interface{}, error) { | ||
func (cmd *DNSRecords) LoadRecords() ([]*DNSRecord, error) { | ||
ip, err := util.Command("docker", "inspect", "--format", "{{.NetworkSettings.IPAddress}}", "dnsdock").Output() | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to discover dnsdock IP address: %s", err) | ||
|
@@ -74,11 +129,142 @@ func (cmd *DNSRecords) LoadRecords() ([]map[string]interface{}, error) { | |
} | ||
|
||
dnsdockMap, _ := js.Map() | ||
records := []map[string]interface{}{} | ||
for id, value := range dnsdockMap { | ||
record := value.(map[string]interface{}) | ||
record["Id"] = id | ||
records := make([]*DNSRecord, 0, 20) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interested in your thoughts behind the use of the capacity argument here. As near as I can tell this doesn't set any upper bound on the size of the array though I had to write a quick test to make sure. Is the initial capacity you picked arbitrary (both here and below where you used 10) or is there something behind that particular value? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Completely arbitrary. Yes, when accumulating the array contents via There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given that it's a dynamic array perhaps just drop the capacity argument here and anywhere it doesn't have an obvious purpose. This is to prevent confusing anyone in the future about why it is there, how it got picked, does it actually result in enforced bounds, etc? Alternatively, comment something to the effect of what I think may be true about it: // capacity picked arbitrarily with a size likely to prevent need to reallocate underlying arrays while appending items in most cases I also presume that has some potential performance benefit though at the scales here I think it's unobservable. If that comment isn't accurate I'd be interested in what the benefits are of declaring a capacity are in the case where append is going to be used. When using copy it's an obvious useful item to restrict the number of items you want from a potentially larger array. Thanks for the pointers as I drag myself deeper into go. |
||
for id, rawValue := range dnsdockMap { | ||
// Cast rawValue to its actual type | ||
value := rawValue.(map[string]interface{}) | ||
ttl, _ := value["TTL"].(json.Number).Int64() | ||
record := &DNSRecord{ | ||
Id: id, | ||
Name: value["Name"].(string), | ||
Image: value["Image"].(string), | ||
TTL: ttl, | ||
} | ||
record.IPs = make([]string, 0, 10) | ||
for _, ip := range value["IPs"].([]interface{}) { | ||
record.IPs = append(record.IPs, ip.(string)) | ||
} | ||
record.Aliases = make([]string, 0, 10) | ||
for _, alias := range value["Aliases"].([]interface{}) { | ||
record.Aliases = append(record.Aliases, alias.(string)) | ||
} | ||
records = append(records, record) | ||
} | ||
return records, nil | ||
} | ||
|
||
func printDNSRecords(records []*DNSRecord) { | ||
for _, record := range records { | ||
fmt.Print(record) | ||
} | ||
} | ||
|
||
// Write the records to /etc/hosts or FIX Windows? | ||
func (cmd *DNSRecords) saveDNSRecords(records []*DNSRecord) error { | ||
if util.IsMac() || util.IsLinux() { | ||
return cmd.saveDNSRecordsUnix(records) | ||
} else if util.IsWindows() { | ||
return cmd.saveDNSRecordsWindows(records) | ||
} | ||
return cmd.Success("Not implemented") | ||
} | ||
|
||
// ⚠ Administrative privileges needed... | ||
|
||
func (cmd *DNSRecords) saveDNSRecordsUnix(records []*DNSRecord) error { | ||
// Both of these are []string | ||
oldHostEntries := util.LoadFile("/etc/hosts") | ||
newHostEntries := stripDNS(oldHostEntries) | ||
// records.String does the formatting, so convert both to a string | ||
oldHosts := strings.Join(oldHostEntries, "\n") | ||
newHosts := strings.Join(newHostEntries, "\n") + "\n" + | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could also do something like this...
to get the newlines you want. |
||
unixHostsPreamble + "\n" + | ||
DNSRecordsList(records).String() + | ||
unixHostsPostamble + "\n" | ||
if oldHosts == newHosts { | ||
return cmd.Success("No changes made") | ||
} | ||
return cmd.writeEtcHosts(newHosts) | ||
} | ||
|
||
func (cmd *DNSRecords) saveDNSRecordsWindows(records []*DNSRecord) error { | ||
return cmd.Failure("Not Implemented", "COMMAND-ERROR", 13) | ||
} | ||
|
||
func (cmd *DNSRecords) removeDNSRecords() error { | ||
if util.IsMac() || util.IsLinux() { | ||
return cmd.removeDNSRecordsUnix() | ||
} else if util.IsWindows() { | ||
return cmd.removeDNSRecordsWindows() | ||
} | ||
return cmd.Success("Not implemented") | ||
} | ||
|
||
func (cmd *DNSRecords) removeDNSRecordsUnix() error { | ||
oldHostsEntries := util.LoadFile("/etc/hosts") | ||
newHostsEntries := stripDNS(oldHostsEntries) | ||
oldHosts := strings.Join(oldHostsEntries, "\n") | ||
newHosts := strings.Join(newHostsEntries, "\n") | ||
if oldHosts == newHosts { | ||
return cmd.Success("No changes made") | ||
} | ||
return cmd.writeEtcHosts(newHosts) | ||
} | ||
|
||
func (cmd *DNSRecords) removeDNSRecordsWindows() error { | ||
return cmd.Failure("Not Implemented", "COMMAND-ERROR", 13) | ||
} | ||
|
||
// Save a new version of /etc/hosts, arg is the full text to save | ||
func (cmd *DNSRecords) writeEtcHosts(hostsText string) error { | ||
// Make sure it ends in a newline | ||
if hostsText[len(hostsText)-1] != '\n' { | ||
hostsText += "\n" | ||
} | ||
// Write new version to a temp file | ||
tmpfile, err := ioutil.TempFile("", "rig-hosts") | ||
if err != nil { | ||
return cmd.Failure("Unable to create hosts tempfile: "+err.Error(), "COMMAND-ERROR", 13) | ||
} | ||
tmpname := tmpfile.Name() | ||
defer os.Remove(tmpname) | ||
if _, err := tmpfile.Write([]byte(hostsText)); err != nil { | ||
return cmd.Failure("Unable to write hosts tempfile: ("+tmpname+") "+err.Error(), "COMMAND-ERROR", 13) | ||
} | ||
if err := tmpfile.Close(); err != nil { | ||
return cmd.Failure("Unable to close hosts tempfile: ("+tmpname+") "+err.Error(), "COMMAND-ERROR", 13) | ||
} | ||
// mv it into place. This is safer than trying to write /etc/hosts on the fly. | ||
if err := util.EscalatePrivilege(); err != nil { | ||
return cmd.Failure("Unable to obtain privileges to replace: "+err.Error(), "COMMAND-ERROR", 13) | ||
} | ||
if err := util.Command("sudo", "mv", "-f", tmpname, "/etc/hosts").Run(); err != nil { | ||
return cmd.Failure("Unable to replace /etc/hosts: "+err.Error(), "COMMAND-ERROR", 13) | ||
} | ||
return cmd.Success("/etc/hosts updated") | ||
} | ||
|
||
// Remove a section of the hosts file we previously added | ||
func stripDNS(hosts []string) []string { | ||
const ( | ||
looking = iota | ||
found | ||
) | ||
results := make([]string, 0, 1000) | ||
state := looking | ||
for _, host := range hosts { | ||
switch state { | ||
case looking: | ||
if host == unixHostsPreamble { | ||
state = found | ||
} else { | ||
results = append(results, host) | ||
} | ||
case found: | ||
if host == unixHostsPostamble { | ||
state = looking | ||
} | ||
} | ||
} | ||
return results | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you'll use this regardless of the underlying os as markers for starting/removing items so perhaps just call this hostsPreamble
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Thanks.