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.
@@ -0,0 +1,110 @@
1
+ module Ghost
2
+ class SshConfigDuplicateError < StandardError; end
3
+
4
+ class SshConfig
5
+ @@ssh_config = "#{ENV['HOME']}/.ssh/config"
6
+
7
+ attr_accessor :host, :hostname, :user, :port
8
+ alias :to_s :host
9
+
10
+ def initialize(host, hostname, user, port)
11
+ @host = host
12
+ @hostname = hostname
13
+ @user = user
14
+ @port = port
15
+ end
16
+
17
+ class << self
18
+ protected :new
19
+
20
+ def list
21
+ lines = File.read(@@ssh_config).split("\n")
22
+
23
+ entries = []
24
+ current_entry = []
25
+
26
+ lines << ""
27
+ lines.each do |line|
28
+ if line.strip.empty?
29
+ entries << parse(current_entry) if current_entry.any?
30
+ current_entry = []
31
+ else
32
+ current_entry << line
33
+ end
34
+ end
35
+
36
+ entries
37
+ end
38
+
39
+ def add(args)
40
+ host = args[:host]
41
+ hostname = args[:hostname]
42
+ user = args[:user] || "root"
43
+ port = args[:port] || "22"
44
+
45
+ force = args[:force]
46
+
47
+ if find_by_host(host).nil? || force
48
+ delete(host)
49
+
50
+ new_config = SshConfig.new(host, hostname, user, port)
51
+
52
+ configs = list
53
+ configs << new_config
54
+
55
+ write(configs)
56
+
57
+ new_config
58
+ else
59
+ raise SshConfigDuplicateError, "Can not overwrite existing record"
60
+ end
61
+ end
62
+
63
+ def find_by_host(host)
64
+ list.find { |c| c.host == host }
65
+ end
66
+
67
+ def empty!
68
+ write([])
69
+ end
70
+
71
+ def delete(host)
72
+ configs = list
73
+ configs = configs.delete_if { |c| c.host == host }
74
+ write(configs)
75
+ end
76
+
77
+ def write(configs)
78
+ hosts = []
79
+
80
+ configs.sort! { |a,b| a.host <=> b.host }
81
+ configs.each do |c|
82
+ hosts << "Host #{c.host}"
83
+ hosts << " HostName #{c.hostname}"
84
+ hosts << " User #{c.user}" if c.user
85
+ hosts << " Port #{c.port}" if c.port
86
+ hosts << ""
87
+ end
88
+
89
+ File.open(@@ssh_config, 'w') {|f| f.print hosts.join("\n") }
90
+ end
91
+
92
+ def parse(config)
93
+ host = config.first[/Host (.*)/, 1]
94
+ config_hash = {}
95
+
96
+ config[1..-1].each do |entry|
97
+ entry.strip!
98
+ next if entry.empty?
99
+ key, value = entry.strip.split(" ")
100
+ config_hash[key.downcase] = value
101
+ end
102
+
103
+ SshConfig.new(host,
104
+ config_hash['hostname'], config_hash['user'],
105
+ config_hash['port'])
106
+ end
107
+
108
+ end
109
+ end
110
+ end
@@ -2,154 +2,189 @@ require 'ghost/linux-host'
2
2
 
3
3
  $hosts_file = File.expand_path(File.join(File.dirname(__FILE__), "etc_hosts"))
4
4
 
5
- describe Host do
5
+ describe Ghost::Host do
6
6
  before(:all) do
7
- class Host
7
+ class Ghost::Host
8
8
  @@hosts_file = $hosts_file
9
9
  end
10
10
  end
11
11
  before { `touch #{$hosts_file.inspect}` }
12
12
  after { `rm -f #{$hosts_file.inspect}` }
13
-
13
+
14
14
  it "has an IP" do
15
15
  hostname = 'ghost-test-hostname.local'
16
16
 
17
- Host.add(hostname)
18
- host = Host.list.first
17
+ Ghost::Host.add(hostname)
18
+ host = Ghost::Host.list.first
19
19
  host.ip.should eql('127.0.0.1')
20
20
 
21
- Host.empty!
21
+ Ghost::Host.empty!
22
22
 
23
23
  ip = '169.254.23.121'
24
- host = Host.add(hostname, ip)
24
+ host = Ghost::Host.add(hostname, ip)
25
25
  host.ip.should eql(ip)
26
26
  end
27
27
 
28
28
  it "has a hostname" do
29
29
  hostname = 'ghost-test-hostname.local'
30
30
 
31
- Host.add(hostname)
32
- host = Host.list.first
31
+ Ghost::Host.add(hostname)
32
+ host = Ghost::Host.list.first
33
33
  host.hostname.should eql(hostname)
34
34
 
35
- Host.empty!
35
+ Ghost::Host.empty!
36
36
 
37
37
  ip = '169.254.23.121'
38
- Host.add(hostname, ip)
38
+ Ghost::Host.add(hostname, ip)
39
39
  host.hostname.should eql(hostname)
40
40
  end
41
-
41
+
42
42
  describe ".list" do
43
43
  it "returns an array" do
44
- Host.list.should be_instance_of(Array)
44
+ Ghost::Host.list.should be_instance_of(Array)
45
45
  end
46
46
 
47
- it "contains instances of Host" do
48
- Host.add('ghost-test-hostname.local')
49
- Host.list.first.should be_instance_of(Host)
47
+ it "contains instances of Ghost::Host" do
48
+ Ghost::Host.add('ghost-test-hostname.local')
49
+ Ghost::Host.list.first.should be_instance_of(Ghost::Host)
50
50
  end
51
-
51
+
52
52
  it "gets all hosts on a single /etc/hosts line" do
53
- example = "127.0.0.1\tproject_a.local\t\t\tproject_b.local project_c.local"
54
- File.open($hosts_file, 'w') {|f| f << example}
55
- hosts = Host.list
56
- hosts.should have(3).items
57
- hosts.map{|h|h.ip}.uniq.should == ['127.0.0.1']
58
- hosts.map{|h|h.host}.sort.should == %w[project_a.local project_b.local project_c.local]
59
- Host.add("project_d.local")
60
- Host.list.should have(4).items
53
+ example = <<-EoEx
54
+ 127.0.0.1 localhost.localdomain
55
+ # ghost start
56
+ 123.123.123.1 project_a.local\t\tproject_b.local project_c.local
57
+ 127.0.0.1 local.bb
58
+ # ghost end
59
+ EoEx
60
+ File.open($hosts_file, 'w') {|f| f << example.gsub!(/^\s*/, '')}
61
+ hosts = Ghost::Host.list
62
+ hosts.should have(4).items
63
+ hosts.map{|h|h.ip}.uniq.sort.should == ['123.123.123.1', '127.0.0.1']
64
+ hosts.map{|h|h.host}.sort.should == %w[local.bb project_a.local project_b.local project_c.local]
65
+
66
+ Ghost::Host.add("project_d.local")
67
+ Ghost::Host.list.should have(5).items
61
68
  end
62
69
  end
63
-
70
+
71
+
72
+ it "does not change hosts files outside of control tokens" do
73
+ f = File.open($hosts_file, 'w')
74
+ f.write "10.0.0.23 adserver.example.com\n"
75
+ f.close
76
+ hosts = Ghost::Host.list
77
+ hosts.should have(0).items
78
+
79
+ hostname = "ghost-test-hostname-b.local"
80
+ Ghost::Host.add(hostname)
81
+ Ghost::Host.list.should have(1).items
82
+
83
+ hostsfile = File.read($hosts_file)
84
+ hostsfile.should =~ /^10.0.0.23 adserver.example.com\n/
85
+ end
86
+
87
+ it "adds control tokens to untouched hosts files" do
88
+ f = File.open($hosts_file, 'w')
89
+ f.write "10.0.0.23 adserver.example.com\n"
90
+ f.close
91
+ hostname = "ghost-test-hostname-b.local"
92
+ ip = '123.45.67.89'
93
+ Ghost::Host.add(hostname, ip)
94
+
95
+ hostsfile = File.read($hosts_file)
96
+ hostsfile.should =~ /# ghost start\n#{ip}\s+#{hostname}\n# ghost end/
97
+ end
98
+
64
99
  describe "#to_s" do
65
100
  it "returns hostname" do
66
101
  hostname = 'ghost-test-hostname.local'
67
102
 
68
- Host.add(hostname)
69
- host = Host.list.first
103
+ Ghost::Host.add(hostname)
104
+ host = Ghost::Host.list.first
70
105
  host.to_s.should eql(hostname)
71
106
  end
72
107
  end
73
108
 
74
109
  describe "finder methods" do
75
110
  before do
76
- Host.add('abc.local')
77
- Host.add('def.local')
78
- Host.add('efg.local', '10.2.2.4')
111
+ Ghost::Host.add('abc.local')
112
+ Ghost::Host.add('def.local')
113
+ Ghost::Host.add('efg.local', '10.2.2.4')
79
114
  end
80
115
 
81
- it "returns valid Host when searching for host name" do
82
- Host.find_by_host('abc.local').should be_instance_of(Host)
116
+ it "returns valid Ghost::Host when searching for host name" do
117
+ Ghost::Host.find_by_host('abc.local').should be_instance_of(Ghost::Host)
83
118
  end
84
119
  end
85
120
 
86
121
  describe ".add" do
87
- it "returns Host object when passed hostname" do
88
- Host.add('ghost-test-hostname.local').should be_instance_of(Host)
122
+ it "returns Ghost::Host object when passed hostname" do
123
+ Ghost::Host.add('ghost-test-hostname.local').should be_instance_of(Ghost::Host)
89
124
  end
90
125
 
91
- it "returns Host object when passed hostname" do
92
- Host.add('ghost-test-hostname.local', '10.0.0.2').should be_instance_of(Host)
126
+ it "returns Ghost::Host object when passed hostname" do
127
+ Ghost::Host.add('ghost-test-hostname.local', '10.0.0.2').should be_instance_of(Ghost::Host)
93
128
  end
94
129
 
95
130
  it "raises error if hostname already exists and not add a duplicate" do
96
- Host.empty!
97
- Host.add('ghost-test-hostname.local')
98
- lambda { Host.add('ghost-test-hostname.local') }.should raise_error
99
- Host.list.should have(1).thing
131
+ Ghost::Host.empty!
132
+ Ghost::Host.add('ghost-test-hostname.local')
133
+ lambda { Ghost::Host.add('ghost-test-hostname.local') }.should raise_error
134
+ Ghost::Host.list.should have(1).thing
100
135
  end
101
136
 
102
137
  it "overwrites existing hostname if forced" do
103
138
  hostname = 'ghost-test-hostname.local'
104
139
 
105
- Host.empty!
106
- Host.add(hostname)
140
+ Ghost::Host.empty!
141
+ Ghost::Host.add(hostname)
107
142
 
108
- Host.list.first.hostname.should eql(hostname)
109
- Host.list.first.ip.should eql('127.0.0.1')
143
+ Ghost::Host.list.first.hostname.should eql(hostname)
144
+ Ghost::Host.list.first.ip.should eql('127.0.0.1')
110
145
 
111
- Host.add(hostname, '10.0.0.1', true)
112
- Host.list.first.hostname.should eql(hostname)
113
- Host.list.first.ip.should eql('10.0.0.1')
146
+ Ghost::Host.add(hostname, '10.0.0.1', true)
147
+ Ghost::Host.list.first.hostname.should eql(hostname)
148
+ Ghost::Host.list.first.ip.should eql('10.0.0.1')
114
149
 
115
- Host.list.should have(1).thing
150
+ Ghost::Host.list.should have(1).thing
116
151
  end
117
-
152
+
118
153
  it "should add a hostname using second hostname's ip" do
119
154
  hostname = 'ghost-test-hostname.local'
120
155
  alias_hostname = 'ghost-test-alias-hostname.local'
121
156
 
122
- Host.empty!
157
+ Ghost::Host.empty!
123
158
 
124
- Host.add(hostname)
125
- Host.add(alias_hostname, hostname)
159
+ Ghost::Host.add(hostname)
160
+ Ghost::Host.add(alias_hostname, hostname)
126
161
 
127
- Host.list.last.ip.should eql(Host.list.first.ip)
162
+ Ghost::Host.list.last.ip.should eql(Ghost::Host.list.first.ip)
128
163
  end
129
164
 
130
165
  it "should raise SocketError if it can't find hostname's ip" do
131
- Host.empty!
132
- lambda { Host.add('ghost-test-alias-hostname.google', 'google') }.should raise_error(SocketError)
166
+ Ghost::Host.empty!
167
+ lambda { Ghost::Host.add('ghost-test-alias-hostname.google', 'google') }.should raise_error(SocketError)
133
168
  end
134
169
  end
135
170
 
136
171
  describe ".empty!" do
137
172
  it "empties the hostnames" do
138
- Host.add('ghost-test-hostname.local') # add a hostname to be sure
139
- Host.empty!
140
- Host.list.should have(0).things
173
+ Ghost::Host.add('ghost-test-hostname.local') # add a hostname to be sure
174
+ Ghost::Host.empty!
175
+ Ghost::Host.list.should have(0).things
141
176
  end
142
177
  end
143
178
 
144
179
  describe ".delete_matching" do
145
180
  it "deletes matching hostnames" do
146
181
  keep = 'ghost-test-hostname-keep.local'
147
- Host.add(keep)
148
- Host.add('ghost-test-hostname-match1.local')
149
- Host.add('ghost-test-hostname-match2.local')
150
- Host.delete_matching('match')
151
- Host.list.should have(1).thing
152
- Host.list.first.hostname.should eql(keep)
182
+ Ghost::Host.add(keep)
183
+ Ghost::Host.add('ghost-test-hostname-match1.local')
184
+ Ghost::Host.add('ghost-test-hostname-match2.local')
185
+ Ghost::Host.delete_matching('match')
186
+ Ghost::Host.list.should have(1).thing
187
+ Ghost::Host.list.first.hostname.should eql(keep)
153
188
  end
154
189
  end
155
190
  end
data/spec/ghost_spec.rb CHANGED
@@ -3,149 +3,149 @@ require 'ghost'
3
3
 
4
4
  # Warning: these tests will delete all hostnames in the system. Please back them up first
5
5
 
6
- Host.empty!
6
+ Ghost::Host.empty!
7
+
8
+ describe Ghost::Host, ".list" do
9
+ after(:each) { Ghost::Host.empty! }
7
10
 
8
- describe Host, ".list" do
9
- after(:each) { Host.empty! }
10
-
11
11
  it "should return an array" do
12
- Host.list.should be_instance_of(Array)
12
+ Ghost::Host.list.should be_instance_of(Array)
13
13
  end
14
-
15
- it "should contain instances of Host" do
16
- Host.add('ghost-test-hostname.local')
17
- Host.list.first.should be_instance_of(Host)
14
+
15
+ it "should contain instances of Ghost::Host" do
16
+ Ghost::Host.add('ghost-test-hostname.local')
17
+ Ghost::Host.list.first.should be_instance_of(Ghost::Host)
18
18
  end
19
19
  end
20
20
 
21
- describe Host do
22
- after(:each) { Host.empty! }
23
-
21
+ describe Ghost::Host do
22
+ after(:each) { Ghost::Host.empty! }
23
+
24
24
  it "should have an IP" do
25
25
  hostname = 'ghost-test-hostname.local'
26
-
27
- Host.add(hostname)
28
- host = Host.list.first
26
+
27
+ Ghost::Host.add(hostname)
28
+ host = Ghost::Host.list.first
29
29
  host.ip.should eql('127.0.0.1')
30
-
31
- Host.empty!
32
-
30
+
31
+ Ghost::Host.empty!
32
+
33
33
  ip = '169.254.23.121'
34
- host = Host.add(hostname, ip)
34
+ host = Ghost::Host.add(hostname, ip)
35
35
  host.ip.should eql(ip)
36
36
  end
37
-
37
+
38
38
  it "should have a hostname" do
39
39
  hostname = 'ghost-test-hostname.local'
40
-
41
- Host.add(hostname)
42
- host = Host.list.first
40
+
41
+ Ghost::Host.add(hostname)
42
+ host = Ghost::Host.list.first
43
43
  host.hostname.should eql(hostname)
44
-
45
- Host.empty!
46
-
44
+
45
+ Ghost::Host.empty!
46
+
47
47
  ip = '169.254.23.121'
48
- Host.add(hostname, ip)
48
+ Ghost::Host.add(hostname, ip)
49
49
  host.hostname.should eql(hostname)
50
50
  end
51
-
51
+
52
52
  it ".to_s should return hostname" do
53
53
  hostname = 'ghost-test-hostname.local'
54
-
55
- Host.add(hostname)
56
- host = Host.list.first
54
+
55
+ Ghost::Host.add(hostname)
56
+ host = Ghost::Host.list.first
57
57
  host.to_s.should eql(hostname)
58
58
  end
59
59
  end
60
60
 
61
- describe Host, "finder methods" do
62
- after(:all) { Host.empty! }
61
+ describe Ghost::Host, "finder methods" do
62
+ after(:all) { Ghost::Host.empty! }
63
63
  before(:all) do
64
- Host.add('abc.local')
65
- Host.add('def.local')
66
- Host.add('efg.local', '10.2.2.4')
64
+ Ghost::Host.add('abc.local')
65
+ Ghost::Host.add('def.local')
66
+ Ghost::Host.add('efg.local', '10.2.2.4')
67
67
  end
68
-
69
- it "should return valid Host when searching for host name" do
70
- Host.find_by_host('abc.local').should be_instance_of(Host)
68
+
69
+ it "should return valid Ghost::Host when searching for host name" do
70
+ Ghost::Host.find_by_host('abc.local').should be_instance_of(Ghost::Host)
71
71
  end
72
-
72
+
73
73
  end
74
74
 
75
- describe Host, ".add" do
76
- after(:each) { Host.empty! }
77
-
78
- it "should return Host object when passed hostname" do
79
- Host.add('ghost-test-hostname.local').should be_instance_of(Host)
75
+ describe Ghost::Host, ".add" do
76
+ after(:each) { Ghost::Host.empty! }
77
+
78
+ it "should return Ghost::Host object when passed hostname" do
79
+ Ghost::Host.add('ghost-test-hostname.local').should be_instance_of(Ghost::Host)
80
80
  end
81
-
82
- it "should return Host object when passed hostname" do
83
- Host.add('ghost-test-hostname.local', '10.0.0.2').should be_instance_of(Host)
81
+
82
+ it "should return Ghost::Host object when passed hostname" do
83
+ Ghost::Host.add('ghost-test-hostname.local', '10.0.0.2').should be_instance_of(Ghost::Host)
84
84
  end
85
-
85
+
86
86
  it "should raise error if hostname already exists and not add a duplicate" do
87
- Host.empty!
88
- Host.add('ghost-test-hostname.local')
89
- lambda { Host.add('ghost-test-hostname.local') }.should raise_error
90
- Host.list.should have(1).thing
87
+ Ghost::Host.empty!
88
+ Ghost::Host.add('ghost-test-hostname.local')
89
+ lambda { Ghost::Host.add('ghost-test-hostname.local') }.should raise_error
90
+ Ghost::Host.list.should have(1).thing
91
91
  end
92
-
92
+
93
93
  it "should overwrite existing hostname if forced" do
94
94
  hostname = 'ghost-test-hostname.local'
95
-
96
- Host.empty!
97
- Host.add(hostname)
98
-
99
- Host.list.first.hostname.should eql(hostname)
100
- Host.list.first.ip.should eql('127.0.0.1')
101
-
102
- Host.add(hostname, '10.0.0.1', true)
103
- Host.list.first.hostname.should eql(hostname)
104
- Host.list.first.ip.should eql('10.0.0.1')
105
-
106
- Host.list.should have(1).thing
95
+
96
+ Ghost::Host.empty!
97
+ Ghost::Host.add(hostname)
98
+
99
+ Ghost::Host.list.first.hostname.should eql(hostname)
100
+ Ghost::Host.list.first.ip.should eql('127.0.0.1')
101
+
102
+ Ghost::Host.add(hostname, '10.0.0.1', true)
103
+ Ghost::Host.list.first.hostname.should eql(hostname)
104
+ Ghost::Host.list.first.ip.should eql('10.0.0.1')
105
+
106
+ Ghost::Host.list.should have(1).thing
107
107
  end
108
-
108
+
109
109
  it "should add a hostname using second hostname's ip" do
110
110
  hostname = 'ghost-test-hostname.local'
111
111
  alias_hostname = 'ghost-test-alias-hostname.local'
112
-
113
- Host.empty!
114
-
115
- Host.add(hostname)
116
- Host.add(alias_hostname, hostname)
117
-
118
- Host.list.last.ip.should eql(Host.list.first.ip)
112
+
113
+ Ghost::Host.empty!
114
+
115
+ Ghost::Host.add(hostname)
116
+ Ghost::Host.add(alias_hostname, hostname)
117
+
118
+ Ghost::Host.list.last.ip.should eql(Ghost::Host.list.first.ip)
119
119
  end
120
-
120
+
121
121
  it "should raise SocketError if it can't find hostname's ip" do
122
- Host.empty!
123
- lambda { Host.add('ghost-test-alias-hostname.google', 'google') }.should raise_error(SocketError)
122
+ Ghost::Host.empty!
123
+ lambda { Ghost::Host.add('ghost-test-alias-hostname.google', 'google') }.should raise_error(SocketError)
124
124
  end
125
125
  end
126
126
 
127
- describe Host, ".empty!" do
127
+ describe Ghost::Host, ".empty!" do
128
128
  it "should empty the hostnames" do
129
- Host.add('ghost-test-hostname.local') # add a hostname to be sure
130
- Host.empty!
131
- Host.list.should have(0).things
129
+ Ghost::Host.add('ghost-test-hostname.local') # add a hostname to be sure
130
+ Ghost::Host.empty!
131
+ Ghost::Host.list.should have(0).things
132
132
  end
133
133
  end
134
134
 
135
- describe Host, ".delete_matching" do
135
+ describe Ghost::Host, ".delete_matching" do
136
136
  it "should delete matching hostnames" do
137
137
  keep = 'ghost-test-hostname-keep.local'
138
- Host.add(keep)
139
- Host.add('ghost-test-hostname-match1.local')
140
- Host.add('ghost-test-hostname-match2.local')
141
- Host.delete_matching('match')
142
- Host.list.should have(1).thing
143
- Host.list.first.hostname.should eql(keep)
138
+ Ghost::Host.add(keep)
139
+ Ghost::Host.add('ghost-test-hostname-match1.local')
140
+ Ghost::Host.add('ghost-test-hostname-match2.local')
141
+ Ghost::Host.delete_matching('match')
142
+ Ghost::Host.list.should have(1).thing
143
+ Ghost::Host.list.first.hostname.should eql(keep)
144
144
  end
145
145
  end
146
146
 
147
147
 
148
- describe Host, ".backup and", Host, ".restore" do
148
+ describe Ghost::Host, ".backup and", Ghost::Host, ".restore" do
149
149
  it "should return a yaml file of all hosts and IPs when backing up"
150
150
  it "should empty the hosts and restore only the ones in given yaml"
151
151
  end