ghost 1.0.0.pre.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +1 -1
- data/README.md +12 -36
- data/lib/ghost/cli.rb +4 -2
- data/lib/ghost/cli/task.rb +6 -4
- data/lib/ghost/cli/task/empty.rb +1 -1
- data/lib/ghost/host.rb +1 -1
- data/lib/ghost/store.rb +4 -5
- data/lib/ghost/store/hosts_file_store.rb +26 -11
- data/lib/ghost/version.rb +1 -1
- data/spec/ghost/cli/task/help_spec.rb +1 -1
- data/spec/ghost/store/hosts_file_store_spec.rb +90 -6
- data/spec/support/cli.rb +1 -1
- metadata +13 -23
- data/lib/ghost/store/dscl_store.rb +0 -71
- data/spec/ghost/store/dscl_store_spec.rb +0 -153
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 720634c91d10a291dcde383fe46275a2dcd9e254
|
4
|
+
data.tar.gz: 0ea12759687e9379208d15ef1710480e5d5b48a0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ba0847a4eeed75a767efdbf05dddd081150afb802a665004d4e9e3cb8e4c5ccf919ac0c533963e0f24675e6c4d9565edfad2a9c2d6f5250d3d3e3877f8613090
|
7
|
+
data.tar.gz: f828292517cd7f73c9d9ec4c130f4bc1954fcd24f3abfbf0a1a3e36013e8453cfba3b66c80f89febd833a6ad3bba9461044352f997d15e16ef9b214fc71da3ae
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -2,7 +2,7 @@ Ghost
|
|
2
2
|
=====
|
3
3
|
|
4
4
|
A gem that allows you to create, list, and modify local hostnames
|
5
|
-
with ease in
|
5
|
+
with ease in Linux and other Unix-based operating systems (more to come)...
|
6
6
|
|
7
7
|
Requirements
|
8
8
|
============
|
@@ -21,10 +21,10 @@ flush the cache.
|
|
21
21
|
Command
|
22
22
|
-------
|
23
23
|
|
24
|
-
$ ghost add mydevsite.local
|
24
|
+
$ sudo ghost add mydevsite.local
|
25
25
|
[Adding] mydevsite.local -> 127.0.0.1
|
26
26
|
|
27
|
-
$ ghost add staging-server.local 67.207.136.164
|
27
|
+
$ sudo ghost add staging-server.local 67.207.136.164
|
28
28
|
[Adding] staging-server.local -> 67.207.136.164
|
29
29
|
|
30
30
|
$ ghost list
|
@@ -32,10 +32,10 @@ Command
|
|
32
32
|
mydevsite.local -> 127.0.0.1
|
33
33
|
staging-server.local -> 67.207.136.164
|
34
34
|
|
35
|
-
$ ghost delete mydevsite.local
|
35
|
+
$ sudo ghost delete mydevsite.local
|
36
36
|
[Deleting] mydevsite.local
|
37
37
|
|
38
|
-
$ ghost delete_matching test
|
38
|
+
$ sudo ghost delete_matching test
|
39
39
|
[Deleting] test2.local
|
40
40
|
[Deleting] test.local
|
41
41
|
|
@@ -43,7 +43,7 @@ Command
|
|
43
43
|
Listing 1 host(s):
|
44
44
|
staging-server.local -> 67.207.136.164
|
45
45
|
|
46
|
-
$ ghost modify staging-server.local 64.233.167.99
|
46
|
+
$ sudo ghost modify staging-server.local 64.233.167.99
|
47
47
|
[Modifying] staging-server.local -> 64.233.167.99
|
48
48
|
|
49
49
|
$ ghost list
|
@@ -65,7 +65,7 @@ Command
|
|
65
65
|
Listing 1 host(s):
|
66
66
|
staging-server.local -> 64.233.167.99
|
67
67
|
|
68
|
-
With RVM you need to add `rvmsudo` before the command:
|
68
|
+
With RVM, you need to add `rvmsudo` before the command:
|
69
69
|
|
70
70
|
$ rvmsudo ghost add mydevsite.local
|
71
71
|
[Adding] mydevsite.local -> 127.0.0.1
|
@@ -80,44 +80,20 @@ to see how to use the library.
|
|
80
80
|
Installation
|
81
81
|
============
|
82
82
|
|
83
|
-
sudo gem install ghost
|
84
|
-
|
85
|
-
If you are using RVM:
|
86
|
-
|
87
83
|
gem install ghost
|
84
|
+
|
85
|
+
Using `sudo` may be necessary in some circumstances, depending on your setup
|
86
|
+
(for example, using the stock Ruby that comes on OS X).
|
88
87
|
|
89
88
|
Contributors
|
90
89
|
============
|
91
90
|
|
92
|
-
|
93
|
-
with `git log --format='%aN (%ae)' | sort -u` or from [here](https://github.com/bjeanes/ghost/graphs/contributors).
|
94
|
-
|
95
|
-
* [Alkesh Vaghmaria](https://github.com/alkesh)
|
96
|
-
* [Andrei Serdeliuc](https://github.com/extraordinaire)
|
97
|
-
* [Ben Hoskings](https://github.com/benhoskings)
|
98
|
-
* [Bo Jeanes](https://github.com/bjeanes)
|
99
|
-
* [David Warkentin](https://github.com/ev0rtex)
|
100
|
-
* [Duncan Beevers](https://github.com/duncanbeevers)
|
101
|
-
* [Felipe Coury](https://github.com/fcoury)
|
102
|
-
* [Finn Smith](https://github.com/finn)
|
103
|
-
* [Geoff Wagstaff](https://github.com/TheDeveloper)
|
104
|
-
* [Johannes Thoenes](https://github.com/jthoenes)
|
105
|
-
* [Justin Mazzi](https://github.com/jmazzi)
|
106
|
-
* [Lars Fronius](https://github.com/LarsFronius)
|
107
|
-
* [Lee Jensen](https://github.com/outerim)
|
108
|
-
* [Luiz Galaviz](https://github.com/MGalv)
|
109
|
-
* [Luiz Rocha](https://github.com/lsdr)
|
110
|
-
* [Mitchell Riley](https://github.com/mitchellvriley)
|
111
|
-
* [Noah Kantrowitz](https://github.com/coderanger)
|
112
|
-
* [Ryan Bigg](https://github.com/radar)
|
113
|
-
* [Sam Beam](https://github.com/sbeam)
|
114
|
-
* [Simon Courtois](https://github.com/simonc)
|
115
|
-
* [Turadg Aleahmad](https://github.com/turadg)
|
91
|
+
A list of contributors can be found [here](https://github.com/bjeanes/ghost/graphs/contributors)..
|
116
92
|
|
117
93
|
Legal Stuff
|
118
94
|
===========
|
119
95
|
|
120
|
-
Copyright (c) 2008-
|
96
|
+
Copyright (c) 2008-2013 Bodaniel Jeanes
|
121
97
|
|
122
98
|
Permission is hereby granted, free of charge, to any person obtaining
|
123
99
|
a copy of this software and associated documentation files (the
|
data/lib/ghost/cli.rb
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
require 'ghost'
|
2
|
+
require 'ghost/store'
|
2
3
|
|
3
4
|
require 'optparse'
|
4
5
|
require 'optparse/version'
|
5
6
|
|
6
7
|
module Ghost
|
7
8
|
class Cli
|
8
|
-
attr_accessor :out, :parser
|
9
|
+
attr_accessor :out, :parser, :store
|
9
10
|
|
10
11
|
def initialize(out = STDOUT)
|
11
|
-
self.
|
12
|
+
self.store = Ghost::Store::HostsFileStore.new(section_name: 'ghost')
|
13
|
+
self.out = out
|
12
14
|
|
13
15
|
setup_parser
|
14
16
|
end
|
data/lib/ghost/cli/task.rb
CHANGED
@@ -3,7 +3,7 @@ require 'unindent'
|
|
3
3
|
module Ghost
|
4
4
|
class Cli
|
5
5
|
class Task
|
6
|
-
attr_accessor :out
|
6
|
+
attr_accessor :out, :store
|
7
7
|
|
8
8
|
class << self
|
9
9
|
attr_accessor :name
|
@@ -25,8 +25,9 @@ module Ghost
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
def initialize(out)
|
29
|
-
self.
|
28
|
+
def initialize(store, out)
|
29
|
+
self.store = store
|
30
|
+
self.out = out
|
30
31
|
end
|
31
32
|
|
32
33
|
def perform(*); end
|
@@ -49,8 +50,9 @@ module Ghost
|
|
49
50
|
end
|
50
51
|
end
|
51
52
|
|
53
|
+
|
52
54
|
def tasks
|
53
|
-
@tasks ||= Hash[self.class.tasks.map { |name, task| [name, task.new(out)] }]
|
55
|
+
@tasks ||= Hash[self.class.tasks.map { |name, task| [name, task.new(store, out)] }]
|
54
56
|
end
|
55
57
|
|
56
58
|
class << self
|
data/lib/ghost/cli/task/empty.rb
CHANGED
data/lib/ghost/host.rb
CHANGED
data/lib/ghost/store.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
module Ghost
|
2
2
|
class << self
|
3
3
|
attr_accessor :store
|
4
|
+
|
5
|
+
def store
|
6
|
+
@store ||= Ghost::Store::HostsFileStore.new(section_name: 'ghost')
|
7
|
+
end
|
4
8
|
end
|
5
9
|
end
|
6
10
|
|
7
|
-
|
8
11
|
require 'ghost/store/hosts_file_store'
|
9
|
-
Ghost.store = Ghost::Store::HostsFileStore.new
|
10
|
-
|
11
|
-
# TODO: only load on OS X and make it default when compatible
|
12
|
-
require 'ghost/store/dscl_store'
|
@@ -9,12 +9,26 @@ module Ghost
|
|
9
9
|
# TODO: A lot of this duplicates Resolv::Hosts in Ruby stdlib.
|
10
10
|
# Can that be modifiied to use tokens in place of this?
|
11
11
|
class HostsFileStore
|
12
|
-
|
12
|
+
MAX_HOSTS_PER_LINE = 5
|
13
|
+
DEFAULT_FILE = Resolv::Hosts::DefaultFileName
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
attr_accessor :path, :file
|
16
|
+
attr_reader :section_name
|
17
|
+
|
18
|
+
def initialize(options = {})
|
19
|
+
self.path = options.fetch(:path, DEFAULT_FILE)
|
20
|
+
self.section_name = options.fetch(:section_name)
|
21
|
+
|
22
|
+
self.file = Ghost::TokenizedFile.new(self.path,
|
23
|
+
"# #{self.section_name} start",
|
24
|
+
"# #{self.section_name} end")
|
25
|
+
end
|
26
|
+
|
27
|
+
def section_name=(name)
|
28
|
+
if self.section_name
|
29
|
+
raise RuntimeError, "Cannot change section name"
|
30
|
+
end
|
31
|
+
@section_name = name
|
18
32
|
end
|
19
33
|
|
20
34
|
def add(host)
|
@@ -109,8 +123,8 @@ module Ghost
|
|
109
123
|
yield(Hash.new { |hash, key| hash[key] = SortedSet.new })
|
110
124
|
end
|
111
125
|
|
112
|
-
def parse_into_buffer(
|
113
|
-
|
126
|
+
def parse_into_buffer(content, buffer)
|
127
|
+
content.split($/).each do |line|
|
114
128
|
ip, hosts = *line.scan(/^\s*([^\s]+)\s+([^#]*)/).first
|
115
129
|
|
116
130
|
return unless ip and hosts
|
@@ -135,10 +149,11 @@ module Ghost
|
|
135
149
|
|
136
150
|
def content(buffer)
|
137
151
|
ips = buffer.keys.sort
|
138
|
-
lines = ips.
|
139
|
-
|
140
|
-
|
141
|
-
|
152
|
+
lines = ips.flat_map do |ip|
|
153
|
+
buffer \
|
154
|
+
.fetch(ip, []) \
|
155
|
+
.each_slice(MAX_HOSTS_PER_LINE) \
|
156
|
+
.map { |hosts| [ip, *hosts].join(' ') }
|
142
157
|
end
|
143
158
|
|
144
159
|
lines.compact.join($/)
|
data/lib/ghost/version.rb
CHANGED
@@ -9,8 +9,8 @@ describe Ghost::Cli, :type => :cli do
|
|
9
9
|
|
10
10
|
The ghost tasks are:
|
11
11
|
add Add a host
|
12
|
+
bust Clear all ghost-managed hosts
|
12
13
|
delete Remove a ghost-managed host
|
13
|
-
empty Clear all ghost-managed hosts
|
14
14
|
export Export all hosts in /etc/hosts format
|
15
15
|
import Import hosts in /etc/hosts format
|
16
16
|
list Show all (or a filtered) list of hosts
|
@@ -24,7 +24,7 @@ describe Ghost::Store::HostsFileStore do
|
|
24
24
|
subject { store }
|
25
25
|
|
26
26
|
let(:file_path) { File.join(Dir.tmpdir, "etc_hosts.#{Process.pid}.#{rand(9999)}") }
|
27
|
-
let(:store) { described_class.new(file_path) }
|
27
|
+
let(:store) { described_class.new(section_name: 'ghost', path: file_path) }
|
28
28
|
let(:contents) do
|
29
29
|
<<-EOF.unindent
|
30
30
|
127.0.0.1 localhost localhost.localdomain
|
@@ -34,14 +34,68 @@ describe Ghost::Store::HostsFileStore do
|
|
34
34
|
before { write(contents) }
|
35
35
|
|
36
36
|
it 'manages the default file of /etc/hosts when no file path is provided' do
|
37
|
-
|
38
|
-
|
39
|
-
described_class.new.path.should == "hosts_location"
|
40
|
-
|
37
|
+
previous_hosts_location = described_class::DEFAULT_FILE
|
38
|
+
described_class::DEFAULT_FILE = "hosts_location"
|
39
|
+
described_class.new(section_name: 'ghost').path.should == "hosts_location"
|
40
|
+
described_class::DEFAULT_FILE = previous_hosts_location
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'manages the file at the provided path when given' do
|
44
|
-
described_class.new('xyz').path.should == 'xyz'
|
44
|
+
described_class.new(section_name: 'ghost', path: 'xyz').path.should == 'xyz'
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'initialize' do
|
48
|
+
it 'accepts section name in option' do
|
49
|
+
store = described_class.new(section_name: 'spook')
|
50
|
+
store.section_name.should eq 'spook'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'can only set section name once' do
|
54
|
+
store = described_class.new(path: file_path, section_name: 'spook')
|
55
|
+
store.section_name.should eq 'spook'
|
56
|
+
-> {
|
57
|
+
store.section_name = 'phantom'
|
58
|
+
}.should raise_error(RuntimeError)
|
59
|
+
store.section_name.should eq 'spook'
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe 'custom section name' do
|
64
|
+
let(:host) { Ghost::Host.new("google.com", "127.0.0.1") }
|
65
|
+
|
66
|
+
it 'uses custom section name in comment' do
|
67
|
+
store = described_class.new(path: file_path, section_name: 'spook')
|
68
|
+
store.add(host)
|
69
|
+
read.should == <<-EOF.gsub(/^\s+/,'')
|
70
|
+
127.0.0.1 localhost localhost.localdomain
|
71
|
+
# spook start
|
72
|
+
127.0.0.1 google.com
|
73
|
+
# spook end
|
74
|
+
EOF
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'co-exists with other section' do
|
78
|
+
store = described_class.new(path: file_path, section_name: 'phantom')
|
79
|
+
store.add(host)
|
80
|
+
read.should == <<-EOF.gsub(/^\s+/,'')
|
81
|
+
127.0.0.1 localhost localhost.localdomain
|
82
|
+
# phantom start
|
83
|
+
127.0.0.1 google.com
|
84
|
+
# phantom end
|
85
|
+
EOF
|
86
|
+
|
87
|
+
store = described_class.new(path: file_path, section_name: 'spook')
|
88
|
+
store.add(host)
|
89
|
+
read.should == <<-EOF.gsub(/^\s+/,'')
|
90
|
+
127.0.0.1 localhost localhost.localdomain
|
91
|
+
# phantom start
|
92
|
+
127.0.0.1 google.com
|
93
|
+
# phantom end
|
94
|
+
# spook start
|
95
|
+
127.0.0.1 google.com
|
96
|
+
# spook end
|
97
|
+
EOF
|
98
|
+
end
|
45
99
|
end
|
46
100
|
|
47
101
|
describe "#all" do
|
@@ -120,6 +174,7 @@ describe Ghost::Store::HostsFileStore do
|
|
120
174
|
<<-EOF.gsub(/^\s+/,'')
|
121
175
|
127.0.0.1 localhost localhost.localdomain
|
122
176
|
# ghost start
|
177
|
+
1.2.3.4 a.com b.com c.com d.com e.com
|
123
178
|
192.168.1.1 github.com
|
124
179
|
# ghost end
|
125
180
|
EOF
|
@@ -133,11 +188,26 @@ describe Ghost::Store::HostsFileStore do
|
|
133
188
|
read.should == <<-EOF.gsub(/^\s+/,'')
|
134
189
|
127.0.0.1 localhost localhost.localdomain
|
135
190
|
# ghost start
|
191
|
+
1.2.3.4 a.com b.com c.com d.com e.com
|
136
192
|
192.168.1.1 github.com google.com
|
137
193
|
# ghost end
|
138
194
|
EOF
|
139
195
|
end
|
140
196
|
|
197
|
+
it 'limits hosts per line to 5' do
|
198
|
+
host = Ghost::Host.new('f.com', '1.2.3.4')
|
199
|
+
|
200
|
+
store.add(host)
|
201
|
+
read.should == <<-EOF.gsub(/^\s+/,'')
|
202
|
+
127.0.0.1 localhost localhost.localdomain
|
203
|
+
# ghost start
|
204
|
+
1.2.3.4 a.com b.com c.com d.com e.com
|
205
|
+
1.2.3.4 f.com
|
206
|
+
192.168.1.1 github.com
|
207
|
+
# ghost end
|
208
|
+
EOF
|
209
|
+
end
|
210
|
+
|
141
211
|
it 'returns true' do
|
142
212
|
store.add(host).should be_true
|
143
213
|
end
|
@@ -149,6 +219,7 @@ describe Ghost::Store::HostsFileStore do
|
|
149
219
|
read.should == <<-EOF.gsub(/^\s+/,'')
|
150
220
|
127.0.0.1 localhost localhost.localdomain
|
151
221
|
# ghost start
|
222
|
+
1.2.3.4 a.com b.com c.com d.com e.com
|
152
223
|
127.0.0.1 google.com
|
153
224
|
192.168.1.1 github.com
|
154
225
|
# ghost end
|
@@ -262,6 +333,19 @@ describe Ghost::Store::HostsFileStore do
|
|
262
333
|
EOF
|
263
334
|
end
|
264
335
|
end
|
336
|
+
|
337
|
+
context 'using a partial string to identify host' do
|
338
|
+
let(:host) { "googl" }
|
339
|
+
|
340
|
+
it 'returns empty array' do
|
341
|
+
store.delete(host).should == []
|
342
|
+
end
|
343
|
+
|
344
|
+
it 'does not modify the host file' do
|
345
|
+
store.delete(host)
|
346
|
+
read.should == contents
|
347
|
+
end
|
348
|
+
end
|
265
349
|
end
|
266
350
|
|
267
351
|
context 'when trying to delete a non-ghost entry' do
|
data/spec/support/cli.rb
CHANGED
@@ -10,7 +10,7 @@ module CliSpecs
|
|
10
10
|
end
|
11
11
|
|
12
12
|
klass.let(:store_path) { File.join(Dir.tmpdir, "etc_hosts.#{Process.pid}.#{rand(9999)}") }
|
13
|
-
klass.let(:store) { Ghost::Store::HostsFileStore.new(store_path) }
|
13
|
+
klass.let(:store) { Ghost::Store::HostsFileStore.new(section_name: 'ghost', path: store_path) }
|
14
14
|
end
|
15
15
|
|
16
16
|
def ghost(args)
|
metadata
CHANGED
@@ -1,20 +1,18 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ghost
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
5
|
-
prerelease: 6
|
4
|
+
version: 1.0.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Bodaniel Jeanes
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2014-07-06 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: unindent
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
17
|
- - '='
|
20
18
|
- !ruby/object:Gem::Version
|
@@ -22,7 +20,6 @@ dependencies:
|
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
22
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
23
|
requirements:
|
27
24
|
- - '='
|
28
25
|
- !ruby/object:Gem::Version
|
@@ -30,7 +27,6 @@ dependencies:
|
|
30
27
|
- !ruby/object:Gem::Dependency
|
31
28
|
name: rspec
|
32
29
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
30
|
requirements:
|
35
31
|
- - '='
|
36
32
|
- !ruby/object:Gem::Version
|
@@ -38,7 +34,6 @@ dependencies:
|
|
38
34
|
type: :development
|
39
35
|
prerelease: false
|
40
36
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
37
|
requirements:
|
43
38
|
- - '='
|
44
39
|
- !ruby/object:Gem::Version
|
@@ -46,7 +41,6 @@ dependencies:
|
|
46
41
|
- !ruby/object:Gem::Dependency
|
47
42
|
name: rake
|
48
43
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
44
|
requirements:
|
51
45
|
- - '='
|
52
46
|
- !ruby/object:Gem::Version
|
@@ -54,7 +48,6 @@ dependencies:
|
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
51
|
requirements:
|
59
52
|
- - '='
|
60
53
|
- !ruby/object:Gem::Version
|
@@ -70,6 +63,9 @@ files:
|
|
70
63
|
- LICENSE
|
71
64
|
- README.md
|
72
65
|
- bin/ghost
|
66
|
+
- lib/ghost.rb
|
67
|
+
- lib/ghost/cli.rb
|
68
|
+
- lib/ghost/cli/task.rb
|
73
69
|
- lib/ghost/cli/task/add.rb
|
74
70
|
- lib/ghost/cli/task/delete.rb
|
75
71
|
- lib/ghost/cli/task/empty.rb
|
@@ -78,15 +74,11 @@ files:
|
|
78
74
|
- lib/ghost/cli/task/import.rb
|
79
75
|
- lib/ghost/cli/task/list.rb
|
80
76
|
- lib/ghost/cli/task/set.rb
|
81
|
-
- lib/ghost/cli/task.rb
|
82
|
-
- lib/ghost/cli.rb
|
83
77
|
- lib/ghost/host.rb
|
84
|
-
- lib/ghost/store/dscl_store.rb
|
85
|
-
- lib/ghost/store/hosts_file_store.rb
|
86
78
|
- lib/ghost/store.rb
|
79
|
+
- lib/ghost/store/hosts_file_store.rb
|
87
80
|
- lib/ghost/tokenized_file.rb
|
88
81
|
- lib/ghost/version.rb
|
89
|
-
- lib/ghost.rb
|
90
82
|
- spec/ghost/cli/task/add_spec.rb
|
91
83
|
- spec/ghost/cli/task/delete_spec.rb
|
92
84
|
- spec/ghost/cli/task/empty_spec.rb
|
@@ -96,7 +88,6 @@ files:
|
|
96
88
|
- spec/ghost/cli/task/list_spec.rb
|
97
89
|
- spec/ghost/cli_spec.rb
|
98
90
|
- spec/ghost/host_spec.rb
|
99
|
-
- spec/ghost/store/dscl_store_spec.rb
|
100
91
|
- spec/ghost/store/hosts_file_store_spec.rb
|
101
92
|
- spec/ghost/store_spec.rb
|
102
93
|
- spec/ghost/tokenized_file_spec.rb
|
@@ -104,26 +95,26 @@ files:
|
|
104
95
|
- spec/support/cli.rb
|
105
96
|
- spec/support/resolv.rb
|
106
97
|
homepage: http://github.com/bjeanes/ghost
|
107
|
-
licenses:
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
metadata: {}
|
108
101
|
post_install_message:
|
109
102
|
rdoc_options: []
|
110
103
|
require_paths:
|
111
104
|
- lib
|
112
105
|
required_ruby_version: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
106
|
requirements:
|
115
|
-
- -
|
107
|
+
- - ">="
|
116
108
|
- !ruby/object:Gem::Version
|
117
109
|
version: '0'
|
118
110
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
119
|
-
none: false
|
120
111
|
requirements:
|
121
|
-
- -
|
112
|
+
- - ">="
|
122
113
|
- !ruby/object:Gem::Version
|
123
|
-
version:
|
114
|
+
version: '0'
|
124
115
|
requirements: []
|
125
116
|
rubyforge_project: ghost
|
126
|
-
rubygems_version:
|
117
|
+
rubygems_version: 2.2.2
|
127
118
|
signing_key:
|
128
119
|
specification_version: 3
|
129
120
|
summary: Allows you to create, list, and modify local hostnames
|
@@ -137,7 +128,6 @@ test_files:
|
|
137
128
|
- spec/ghost/cli/task/list_spec.rb
|
138
129
|
- spec/ghost/cli_spec.rb
|
139
130
|
- spec/ghost/host_spec.rb
|
140
|
-
- spec/ghost/store/dscl_store_spec.rb
|
141
131
|
- spec/ghost/store/hosts_file_store_spec.rb
|
142
132
|
- spec/ghost/store_spec.rb
|
143
133
|
- spec/ghost/tokenized_file_spec.rb
|
@@ -1,71 +0,0 @@
|
|
1
|
-
require 'ghost/host'
|
2
|
-
require 'set'
|
3
|
-
|
4
|
-
module Ghost
|
5
|
-
module Store
|
6
|
-
class DsclStore
|
7
|
-
class Dscl
|
8
|
-
class << self
|
9
|
-
def list(domain)
|
10
|
-
`dscl % -readall /Local/Default/Hosts 2>&1` % domain
|
11
|
-
end
|
12
|
-
|
13
|
-
# TODO is shell injection a concern here?
|
14
|
-
def read(domain, host)
|
15
|
-
`dscl % -read /Local/Default/Hosts/%s 2>&1` % [domain, host]
|
16
|
-
end
|
17
|
-
|
18
|
-
def create(domain, host, ip)
|
19
|
-
`dscl % -create /Local/Default/Hosts/%s IPAddress %s 2>&1` % [domain, host, ip]
|
20
|
-
end
|
21
|
-
|
22
|
-
def delete(domain, host)
|
23
|
-
`dscl % -delete /Local/Default/Hosts/%s 2>&1` % [domain, host]
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
attr_accessor :domain
|
29
|
-
|
30
|
-
def initialize(domain = "localhost")
|
31
|
-
self.domain = domain
|
32
|
-
end
|
33
|
-
|
34
|
-
def add(host)
|
35
|
-
Dscl.create(domain, host.name, host.ip)
|
36
|
-
true
|
37
|
-
end
|
38
|
-
|
39
|
-
def all
|
40
|
-
Dscl.list(domain).map do |host|
|
41
|
-
name = host.scan(/^RecordName: (.+)$/).flatten.first
|
42
|
-
ip = host.scan(/^IPAddress: (.+)$/).flatten.first
|
43
|
-
|
44
|
-
Ghost::Host.new(name, ip)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def find(regex)
|
49
|
-
all.select { |h| h.name =~ regex }
|
50
|
-
end
|
51
|
-
|
52
|
-
def delete(host)
|
53
|
-
result = SortedSet.new
|
54
|
-
|
55
|
-
all.each do |existing_host|
|
56
|
-
next unless host.match(existing_host.name)
|
57
|
-
next if host.respond_to?(:ip) && host.ip != existing_host.ip
|
58
|
-
|
59
|
-
Dscl.delete(domain, existing_host.name)
|
60
|
-
result << existing_host
|
61
|
-
end
|
62
|
-
|
63
|
-
result.to_a
|
64
|
-
end
|
65
|
-
|
66
|
-
def empty
|
67
|
-
all.each { |host| Dscl.delete(domain, host.name) }
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,153 +0,0 @@
|
|
1
|
-
require File.expand_path("#{File.dirname(__FILE__)}/../../spec_helper.rb")
|
2
|
-
require 'ghost/store/dscl_store'
|
3
|
-
|
4
|
-
# TODO: Raise exception error when `dscl` doesn't have sufficient privileges
|
5
|
-
describe Ghost::Store::DsclStore do
|
6
|
-
let(:store) { Ghost::Store::DsclStore.new }
|
7
|
-
let(:cmd) { Ghost::Store::DsclStore::Dscl }
|
8
|
-
|
9
|
-
before do
|
10
|
-
cmd.stub(:list => [])
|
11
|
-
cmd.stub(:read => nil)
|
12
|
-
cmd.stub(:create => true)
|
13
|
-
cmd.stub(:delete => true)
|
14
|
-
end
|
15
|
-
|
16
|
-
let(:dscl_foo_com) do
|
17
|
-
<<-EOF.unindent
|
18
|
-
AppleMetaNodeLocation: /Local/Default
|
19
|
-
GeneratedUID: 7FE20A09-21B5-42C5-9E8C-E64999BD20E2
|
20
|
-
IPAddress: 123.123.123.123
|
21
|
-
RecordName: foo.com
|
22
|
-
RecordType: dsRecTypeStandard:Hosts
|
23
|
-
EOF
|
24
|
-
end
|
25
|
-
|
26
|
-
let(:dscl_bar_com) do
|
27
|
-
<<-EOF.unindent
|
28
|
-
AppleMetaNodeLocation: /Local/Default
|
29
|
-
GeneratedUID: 15286594-C5F1-4508-BAB0-98B96D424657
|
30
|
-
IPAddress: 127.0.0.1
|
31
|
-
RecordName: bar.com
|
32
|
-
RecordType: dsRecTypeStandard:Hosts
|
33
|
-
EOF
|
34
|
-
end
|
35
|
-
|
36
|
-
describe "#all" do
|
37
|
-
it 'returns an empty array when no hosts are in the store' do
|
38
|
-
store.all.should == []
|
39
|
-
end
|
40
|
-
|
41
|
-
it 'returns an array of Ghost::Host entries for each host in the store' do
|
42
|
-
cmd.stub(:list).and_return([dscl_foo_com, dscl_bar_com])
|
43
|
-
store.all.should == [
|
44
|
-
Ghost::Host.new('foo.com', '123.123.123.123'),
|
45
|
-
Ghost::Host.new('bar.com', '127.0.0.1')
|
46
|
-
]
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe "#find" do
|
51
|
-
it 'finds hosts matching a regex' do
|
52
|
-
cmd.stub(:list).and_return([dscl_foo_com, dscl_bar_com])
|
53
|
-
store.find(/.*/).should == store.all
|
54
|
-
store.find(/f/).should == [Ghost::Host.new('foo.com', '123.123.123.123')]
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
describe "#add" do
|
59
|
-
let(:host) { Ghost::Host.new('foo.com', '123.123.123.123') }
|
60
|
-
|
61
|
-
it 'returns true' do
|
62
|
-
store.add(host).should be_true
|
63
|
-
end
|
64
|
-
|
65
|
-
# In order to make this run off OS X and without root, have to use an
|
66
|
-
# expectation... I think?
|
67
|
-
it 'adds the host' do
|
68
|
-
cmd.should_receive(:create).with('localhost', 'foo.com', '123.123.123.123')
|
69
|
-
store.add(host)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
describe "#delete" do
|
74
|
-
before do
|
75
|
-
store.stub(:all).and_return [
|
76
|
-
Ghost::Host.new('foo.com', '127.0.0.1'),
|
77
|
-
Ghost::Host.new('fo.com', '127.0.0.1'),
|
78
|
-
Ghost::Host.new('fooo.com', '127.0.0.2')
|
79
|
-
]
|
80
|
-
end
|
81
|
-
|
82
|
-
context 'using a Ghost::Host to identify host' do
|
83
|
-
context 'and the IP does not match an entry' do
|
84
|
-
let(:host) { Ghost::Host.new("foo.com", "127.0.0.2") }
|
85
|
-
|
86
|
-
it 'returns empty array' do
|
87
|
-
store.delete(host).should == []
|
88
|
-
end
|
89
|
-
|
90
|
-
it 'has no effect' do
|
91
|
-
store.delete(host)
|
92
|
-
cmd.should_not_receive(:delete)
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
context 'and the IP matches an entry' do
|
97
|
-
let(:host) { Ghost::Host.new("foo.com", "127.0.0.1") }
|
98
|
-
|
99
|
-
it 'returns array of deleted hosts' do
|
100
|
-
store.delete(host).should == [host]
|
101
|
-
end
|
102
|
-
|
103
|
-
# In order to make this run off OS X and without root, have to use an
|
104
|
-
# expectation... I think?
|
105
|
-
it 'deletes the host' do
|
106
|
-
cmd.should_receive(:delete).with('localhost', 'foo.com')
|
107
|
-
store.delete(host)
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
context 'using a regex to identify hosts' do
|
113
|
-
let(:host) { /fo*\.com/ }
|
114
|
-
|
115
|
-
it 'returns array of removed hosts' do
|
116
|
-
store.delete(host).should == [
|
117
|
-
Ghost::Host.new('fo.com', '127.0.0.1'),
|
118
|
-
Ghost::Host.new('foo.com', '127.0.0.1'),
|
119
|
-
Ghost::Host.new('fooo.com', '127.0.0.2')
|
120
|
-
]
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'deletes the hosts' do
|
124
|
-
cmd.should_receive(:delete).with('localhost', 'fo.com')
|
125
|
-
cmd.should_receive(:delete).with('localhost', 'foo.com')
|
126
|
-
cmd.should_receive(:delete).with('localhost', 'fooo.com')
|
127
|
-
store.delete(host)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
context 'using a string to identify host' do
|
132
|
-
let(:host) { "foo.com" }
|
133
|
-
|
134
|
-
it 'returns array of removed hosts' do
|
135
|
-
store.delete(host).should == [Ghost::Host.new('foo.com', '127.0.0.1')]
|
136
|
-
end
|
137
|
-
|
138
|
-
it 'removes the host from the file' do
|
139
|
-
cmd.should_receive(:delete).with('localhost', 'foo.com')
|
140
|
-
store.delete(host)
|
141
|
-
end
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
describe "#empty" do
|
146
|
-
it 'deletes all the entries' do
|
147
|
-
store.stub(:all => [Ghost::Host.new('foo'), Ghost::Host.new('bar')])
|
148
|
-
cmd.should_receive(:delete).with('localhost', 'foo')
|
149
|
-
cmd.should_receive(:delete).with('localhost', 'bar')
|
150
|
-
store.empty
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|