forked from cbednarski/hostess
-
Notifications
You must be signed in to change notification settings - Fork 0
/
commands.go
280 lines (229 loc) · 6.79 KB
/
commands.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
package main
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/cbednarski/hostess/hostess"
)
var ErrParsingHostsFile = errors.New("Errors while parsing hostsfile. Please resolve any conflicts and try again.")
type Options struct {
Preview bool
}
// PrintErrLn will print to stderr followed by a newline
func PrintErrLn(err error) {
os.Stderr.WriteString(fmt.Sprintf("%s\n", err))
}
// LoadHostfile will try to load, parse, and return a Hostfile. If we
// encounter errors we will terminate.
func LoadHostfile(options *Options) (*hostess.Hostfile, error) {
hosts, errs := hostess.LoadHostfile()
var err error
if len(errs) > 0 {
// If -n is passed, we'll always exit with an error code on duplicate.
// See issue 39
if options.Preview {
err = ErrParsingHostsFile
}
for _, currentErr := range errs {
PrintErrLn(currentErr)
// If we find a duplicate we'll notify the user and continue. For
// other errors we'll bail out.
if !strings.Contains(currentErr.Error(), "duplicate hostname entry") {
err = ErrParsingHostsFile
}
}
}
return hosts, err
}
// SaveOrPreview will display or write the Hostfile
func SaveOrPreview(options *Options, hostfile *hostess.Hostfile) error {
// 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 options.Preview {
fmt.Printf("%s", hostfile.Format())
return nil
}
if err := hostfile.Save(); err != nil {
return fmt.Errorf("Unable to write to %s. (error: %s)", hostess.GetHostsPath(), err)
}
return nil
}
// StrPadRight adds spaces to the right of a string until it reaches length.
// If the input string is already that long, do nothing.
func StrPadRight(input string, length int) string {
minimum := len(input)
if length <= minimum {
return input
}
return input + strings.Repeat(" ", length-minimum)
}
// Add command parses <hostname> <ip> and adds or updates a hostname in the
// hosts file
func Add(options *Options, hostname, ip string) error {
hostsfile, err := LoadHostfile(options)
newHostname, err := hostess.NewHostname(hostname, ip, true)
if err != nil {
return err
}
replaced := hostsfile.Hosts.ContainsDomain(newHostname.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(newHostname)
// If the user passes -n then we'll Add and show the new hosts file, but
// not save it.
if err := SaveOrPreview(options, hostsfile); err != nil {
return err
}
// 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 replaced {
fmt.Printf("Updated %s\n", newHostname.FormatHuman())
} else {
fmt.Printf("Added %s\n", newHostname.FormatHuman())
}
return nil
}
// Remove command removes any hostname(s) matching <domain> from the hosts file
func Remove(options *Options, hostname string) error {
hostsfile, err := LoadHostfile(options)
if err != nil {
return err
}
found := hostsfile.Hosts.ContainsDomain(hostname)
if !found {
fmt.Printf("%s not found in %s", hostname, hostess.GetHostsPath())
}
hostsfile.Hosts.RemoveDomain(hostname)
if err := SaveOrPreview(options, hostsfile); err != nil {
return err
}
fmt.Printf("Deleted %s\n", hostname)
return nil
}
// Has command indicates whether a hostname is present in the hosts file
func Has(options *Options, hostname string) error {
hostsfile, err := LoadHostfile(options)
if err != nil {
return err
}
found := hostsfile.Hosts.ContainsDomain(hostname)
if found {
fmt.Printf("Found %s in %s\n", hostname, hostess.GetHostsPath())
} else {
fmt.Printf("%s not found in %s\n", hostname, hostess.GetHostsPath())
// Exit 1 for bash scripts to use this as a check
os.Exit(1)
}
return nil
}
func Enable(options *Options, hostname string) error {
hostsfile, err := LoadHostfile(options)
if err != nil {
return err
}
if err := hostsfile.Hosts.Enable(hostname); err != nil {
return err
}
if err := SaveOrPreview(options, hostsfile); err != nil {
return err
}
fmt.Printf("Enabled %s\n", hostname)
return nil
}
func Disable(options *Options, hostname string) error {
hostsfile, err := LoadHostfile(options)
if err != nil {
return err
}
if err := hostsfile.Hosts.Disable(hostname); err != nil {
if err == hostess.ErrHostnameNotFound {
// If the hostname does not exist then we have still achieved the
// desired result, so we will not exit with an error here. We'll
// handle the error by displaying it to the user.
PrintErrLn(err)
return nil
}
return err
}
if err := SaveOrPreview(options, hostsfile); err != nil {
return err
}
fmt.Printf("Disabled %s\n", hostname)
return nil
}
// List command shows a list of hostnames in the hosts file
func List(options *Options) error {
hostsfile, err := LoadHostfile(options)
if err != nil {
return err
}
widestHostname := 0
widestIP := 0
for _, hostname := range hostsfile.Hosts {
dlen := len(hostname.Domain)
if dlen > widestHostname {
widestHostname = dlen
}
ilen := len(hostname.IP)
if ilen > widestIP {
widestIP = ilen
}
}
for _, hostname := range hostsfile.Hosts {
fmt.Printf("%s -> %s %s\n",
StrPadRight(hostname.Domain, widestHostname),
StrPadRight(hostname.IP.String(), widestIP),
hostname.FormatEnabled())
}
return nil
}
// Format command removes duplicates from the hosts file
func Format(options *Options) error {
hostsfile, err := LoadHostfile(options)
if err != nil {
return err
}
if bytes.Equal(hostsfile.GetData(), hostsfile.Format()) {
fmt.Printf("%s is already formatted and contains no dupes or conflicts; nothing to do\n", hostess.GetHostsPath())
return nil
}
return SaveOrPreview(options, hostsfile)
}
// Dump command outputs hosts file contents as JSON
func Dump(options *Options) error {
hostsfile, err := LoadHostfile(options)
if err != nil {
return err
}
jsonbytes, err := hostsfile.Hosts.Dump()
if err != nil {
return err
}
fmt.Println(fmt.Sprintf("%s", jsonbytes))
return nil
}
// Apply command adds hostnames to the hosts file from JSON
func Apply(options *Options, filename string) error {
jsonbytes, err := ioutil.ReadFile(filename)
if err != nil {
return fmt.Errorf("Unable to read JSON from %s: %s", filename, err)
}
hostfile, err := LoadHostfile(options)
if err != nil {
return err
}
if err := hostfile.Hosts.Apply(jsonbytes); err != nil {
return fmt.Errorf("Error applying changes to hosts file: %s", err)
}
if err := SaveOrPreview(options, hostfile); err != nil {
return err
}
fmt.Printf("%s applied\n", filename)
return nil
}