ghost 0.2.8 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|