ghost 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +1 -1
- data/{README → README.md} +20 -11
- data/Rakefile +6 -6
- data/bin/ghost +101 -92
- data/bin/ghost-ssh +132 -0
- data/lib/ghost.rb +16 -2
- data/lib/ghost/linux-host.rb +146 -80
- data/lib/ghost/mac-host.rb +101 -99
- data/lib/ghost/ssh_config.rb +110 -0
- data/spec/etc_hosts_spec.rb +100 -65
- data/spec/ghost_spec.rb +89 -89
- data/spec/spec.opts +1 -1
- data/spec/spec_helper.rb +1 -1
- data/spec/ssh_config_spec.rb +80 -0
- data/spec/ssh_config_template +11 -0
- metadata +29 -43
data/lib/ghost.rb
CHANGED
@@ -1,8 +1,22 @@
|
|
1
1
|
$: << File.dirname(__FILE__)
|
2
2
|
|
3
|
-
|
3
|
+
module Ghost
|
4
|
+
class RecordExists < StandardError
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
require 'rbconfig'
|
9
|
+
|
10
|
+
case RbConfig::CONFIG['host_os']
|
4
11
|
when /darwin/
|
5
|
-
|
12
|
+
productVersion = `/usr/bin/sw_vers -productVersion`.strip
|
13
|
+
if productVersion =~ /^10\.7\.[2-9]{1}$/
|
14
|
+
require 'ghost/linux-host'
|
15
|
+
else
|
16
|
+
require 'ghost/mac-host'
|
17
|
+
end
|
6
18
|
when /linux/
|
7
19
|
require 'ghost/linux-host'
|
8
20
|
end
|
21
|
+
|
22
|
+
require 'ghost/ssh_config'
|
data/lib/ghost/linux-host.rb
CHANGED
@@ -1,92 +1,158 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
module Ghost
|
4
|
+
class Host
|
5
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
|
-
|
6
|
+
attr_reader :host, :ip
|
7
|
+
|
8
|
+
def initialize(host, ip)
|
9
|
+
@host = host
|
10
|
+
@ip = ip
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
@host == other.host && @ip = other.ip
|
15
|
+
end
|
16
|
+
|
17
|
+
alias :to_s :host
|
18
|
+
alias :name :host
|
19
|
+
alias :hostname :host
|
20
|
+
|
21
|
+
@@hosts_file = '/etc/hosts'
|
22
|
+
@@start_token, @@end_token = '# ghost start', '# ghost end'
|
23
|
+
|
24
|
+
class << self
|
25
|
+
protected :new
|
26
|
+
|
27
|
+
def list
|
28
|
+
with_exclusive_file_access(true) do |file|
|
29
|
+
entries = []
|
30
|
+
in_ghost_area = false
|
31
|
+
file.pos = 0
|
32
|
+
|
33
|
+
file.each do |line|
|
34
|
+
if !in_ghost_area and line =~ /^#{@@start_token}/
|
35
|
+
in_ghost_area = true
|
36
|
+
elsif in_ghost_area
|
37
|
+
if line =~ /^#{@@end_token}/o
|
38
|
+
in_ghost_area = false
|
39
|
+
elsif line =~ /^(\d+\.\d+\.\d+\.\d+)\s+(.*)$/
|
40
|
+
ip = $1
|
41
|
+
hosts = $2.split(/\s+/)
|
42
|
+
hosts.each { |host| entries << Host.new(host, ip) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
entries
|
33
48
|
end
|
34
49
|
end
|
35
|
-
entries.delete_if { |host| @@permanent_hosts.include? host }
|
36
|
-
entries
|
37
|
-
end
|
38
50
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
51
|
+
def add(host, ip = "127.0.0.1", force = false)
|
52
|
+
with_exclusive_file_access do
|
53
|
+
if find_by_host(host).nil? || force
|
54
|
+
hosts = list
|
55
|
+
hosts = hosts.delete_if { |h| h.name == host }
|
56
|
+
|
57
|
+
unless ip[/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/]
|
58
|
+
ip = Socket.gethostbyname(ip)[3].bytes.to_a.join('.')
|
59
|
+
end
|
60
|
+
|
61
|
+
new_host = Host.new(host, ip)
|
62
|
+
|
63
|
+
hosts << new_host
|
64
|
+
write_out!(hosts)
|
65
|
+
|
66
|
+
new_host
|
67
|
+
else
|
68
|
+
raise Ghost::RecordExists, "Can not overwrite existing record"
|
69
|
+
end
|
45
70
|
end
|
46
|
-
|
47
|
-
new_host = Host.new(host, ip)
|
48
|
-
|
49
|
-
hosts = list
|
50
|
-
hosts << new_host
|
51
|
-
write_out!(hosts)
|
52
|
-
|
53
|
-
new_host
|
54
|
-
else
|
55
|
-
raise "Can not overwrite existing record"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def find_by_host(hostname)
|
60
|
-
list.find{ |host| host.hostname == hostname }
|
61
|
-
end
|
62
|
-
|
63
|
-
def find_by_ip(ip)
|
64
|
-
list.find_all{ |host| host.ip == ip }
|
65
|
-
end
|
66
|
-
|
67
|
-
def empty!
|
68
|
-
write_out!([])
|
69
|
-
end
|
70
|
-
|
71
|
-
def delete(name)
|
72
|
-
hosts = list
|
73
|
-
hosts = hosts.delete_if { |host| host.name == name }
|
74
|
-
write_out!(hosts)
|
75
|
-
end
|
76
|
-
|
77
|
-
def delete_matching(pattern)
|
78
|
-
pattern = Regexp.escape(pattern)
|
79
|
-
hosts = list.select { |host| host.name.match(/#{pattern}/) }
|
80
|
-
hosts.each { |host| delete(host.name) }
|
81
|
-
hosts
|
82
|
-
end
|
71
|
+
end
|
83
72
|
|
84
|
-
|
73
|
+
def find_by_host(hostname)
|
74
|
+
list.find { |host| host.hostname == hostname }
|
75
|
+
end
|
85
76
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
77
|
+
def find_by_ip(ip)
|
78
|
+
list.find_all{ |host| host.ip == ip }
|
79
|
+
end
|
80
|
+
|
81
|
+
def empty!
|
82
|
+
write_out!([])
|
83
|
+
end
|
84
|
+
|
85
|
+
def delete(name)
|
86
|
+
with_exclusive_file_access do
|
87
|
+
hosts = list
|
88
|
+
hosts = hosts.delete_if { |host| host.name == name }
|
89
|
+
write_out!(hosts)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def delete_matching(pattern)
|
94
|
+
with_exclusive_file_access do
|
95
|
+
pattern = Regexp.escape(pattern)
|
96
|
+
hosts = list.select { |host| host.name.match(/#{pattern}/) }
|
97
|
+
hosts.each { |host| delete(host.name) }
|
98
|
+
hosts
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
protected
|
103
|
+
|
104
|
+
def with_exclusive_file_access(read_only = false)
|
105
|
+
return_val = nil
|
106
|
+
flag = read_only ? 'r' : 'r+'
|
107
|
+
|
108
|
+
if @_file
|
109
|
+
return_val = yield @_file
|
110
|
+
else
|
111
|
+
File.open(@@hosts_file, flag) do |f|
|
112
|
+
f.flock File::LOCK_EX
|
113
|
+
begin
|
114
|
+
@_file = f
|
115
|
+
return_val = yield f
|
116
|
+
ensure
|
117
|
+
@_file = nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
return_val
|
123
|
+
end
|
124
|
+
|
125
|
+
def write_out!(hosts)
|
126
|
+
with_exclusive_file_access do |f|
|
127
|
+
new_ghosts = hosts.inject("") {|s, h| s + "#{h.ip} #{h.hostname}\n" }
|
128
|
+
|
129
|
+
output = ""
|
130
|
+
in_ghost_area, seen_tokens = false,false
|
131
|
+
f.pos = 0
|
132
|
+
|
133
|
+
f.each do |line|
|
134
|
+
if line =~ /^#{@@start_token}/o
|
135
|
+
in_ghost_area, seen_tokens = true,true
|
136
|
+
output << line << new_ghosts
|
137
|
+
elsif line =~ /^#{@@end_token}/o
|
138
|
+
in_ghost_area = false
|
139
|
+
end
|
140
|
+
output << line unless in_ghost_area
|
141
|
+
end
|
142
|
+
if !seen_tokens
|
143
|
+
output << surround_with_tokens( new_ghosts )
|
144
|
+
end
|
145
|
+
|
146
|
+
f.pos = 0
|
147
|
+
f.print output
|
148
|
+
f.truncate(f.pos)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
def surround_with_tokens(str)
|
154
|
+
"\n#{@@start_token}\n#{str}#{@@end_token}\n"
|
155
|
+
end
|
90
156
|
end
|
91
157
|
end
|
92
158
|
end
|
data/lib/ghost/mac-host.rb
CHANGED
@@ -1,114 +1,116 @@
|
|
1
1
|
require 'socket'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
list
|
14
|
-
|
15
|
-
|
16
|
-
|
3
|
+
module Ghost
|
4
|
+
class Host
|
5
|
+
ListCmd = "dscl localhost -list /Local/Default/Hosts 2>&1"
|
6
|
+
ReadCmd = "dscl localhost -read /Local/Default/Hosts/%s 2>&1"
|
7
|
+
CreateCmd = "dscl localhost -create /Local/Default/Hosts/%s IPAddress %s 2>&1"
|
8
|
+
DeleteCmd = "dscl localhost -delete /Local/Default/Hosts/%s 2>&1"
|
9
|
+
|
10
|
+
class << self
|
11
|
+
protected :new
|
12
|
+
|
13
|
+
def list
|
14
|
+
list = `#{ListCmd}`
|
15
|
+
list = list.split("\n")
|
16
|
+
list.collect { |host| Host.new(host.chomp) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def add(host, ip = "127.0.0.1", force = false)
|
20
|
+
if find_by_host(host).nil? || force
|
21
|
+
unless ip[/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})?$/]
|
22
|
+
ip = Socket.gethostbyname(ip)[3].bytes.to_a.join('.')
|
23
|
+
end
|
24
|
+
|
25
|
+
`#{CreateCmd % [host, ip]}`
|
26
|
+
flush!
|
27
|
+
find_by_host(host)
|
28
|
+
else
|
29
|
+
raise Ghost::RecordExists, "Can not overwrite existing record"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def find_by_host(host)
|
34
|
+
@hosts ||= {}
|
35
|
+
@hosts[host] ||= begin
|
36
|
+
output = `#{ReadCmd % host}`
|
17
37
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
38
|
+
if output =~ /eDSRecordNotFound/
|
39
|
+
nil
|
40
|
+
else
|
41
|
+
host = parse_host(output)
|
42
|
+
ip = parse_ip(output)
|
43
|
+
|
44
|
+
Host.new(host, ip)
|
45
|
+
end
|
22
46
|
end
|
23
|
-
|
24
|
-
|
47
|
+
end
|
48
|
+
|
49
|
+
def find_by_ip(ip)
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def empty!
|
54
|
+
list.each { |h| delete(h) }
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def delete(host)
|
59
|
+
`#{DeleteCmd % host.to_s}`
|
25
60
|
flush!
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
@hosts ||= {}
|
34
|
-
@hosts[host] ||= begin
|
35
|
-
output = `#{ReadCmd % host}`
|
36
|
-
|
37
|
-
if output =~ /eDSRecordNotFound/
|
38
|
-
nil
|
39
|
-
else
|
40
|
-
host = parse_host(output)
|
41
|
-
ip = parse_ip(output)
|
42
|
-
|
43
|
-
Host.new(host, ip)
|
61
|
+
end
|
62
|
+
|
63
|
+
def delete_matching(pattern)
|
64
|
+
pattern = Regexp.escape(pattern)
|
65
|
+
hosts = list.select { |h| h.to_s.match(/#{pattern}/) }
|
66
|
+
hosts.each do |h|
|
67
|
+
delete(h)
|
44
68
|
end
|
69
|
+
flush! unless hosts.empty?
|
70
|
+
hosts
|
45
71
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
delete(h)
|
72
|
+
|
73
|
+
# Flushes the DNS Cache
|
74
|
+
def flush!
|
75
|
+
`dscacheutil -flushcache`
|
76
|
+
@hosts = {}
|
77
|
+
true
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
def parse_host(output)
|
82
|
+
parse_value(output, 'RecordName')
|
83
|
+
end
|
84
|
+
|
85
|
+
def parse_ip(output)
|
86
|
+
parse_value(output, 'IPAddress')
|
87
|
+
end
|
88
|
+
|
89
|
+
def parse_value(output, key)
|
90
|
+
match = output.match(Regexp.new("^#{key}: (.*)$"))
|
91
|
+
match[1] unless match.nil?
|
67
92
|
end
|
68
|
-
flush! unless hosts.empty?
|
69
|
-
hosts
|
70
93
|
end
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
@hosts = {}
|
76
|
-
true
|
94
|
+
|
95
|
+
def initialize(host, ip=nil)
|
96
|
+
@host = host
|
97
|
+
@ip = ip
|
77
98
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
parse_value(output, 'RecordName')
|
99
|
+
|
100
|
+
def hostname
|
101
|
+
@host
|
82
102
|
end
|
83
|
-
|
84
|
-
|
85
|
-
|
103
|
+
alias :to_s :hostname
|
104
|
+
alias :host :hostname
|
105
|
+
alias :name :hostname
|
106
|
+
|
107
|
+
def ip
|
108
|
+
@ip ||= self.class.send(:parse_ip, dump)
|
86
109
|
end
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
110
|
+
|
111
|
+
private
|
112
|
+
def dump
|
113
|
+
@dump ||= `#{ReadCmd % hostname}`
|
91
114
|
end
|
92
115
|
end
|
93
|
-
|
94
|
-
def initialize(host, ip=nil)
|
95
|
-
@host = host
|
96
|
-
@ip = ip
|
97
|
-
end
|
98
|
-
|
99
|
-
def hostname
|
100
|
-
@host
|
101
|
-
end
|
102
|
-
alias :to_s :hostname
|
103
|
-
alias :host :hostname
|
104
|
-
alias :name :hostname
|
105
|
-
|
106
|
-
def ip
|
107
|
-
@ip ||= self.class.send(:parse_ip, dump)
|
108
|
-
end
|
109
|
-
|
110
|
-
private
|
111
|
-
def dump
|
112
|
-
@dump ||= `#{ReadCmd % hostname}`
|
113
|
-
end
|
114
116
|
end
|