ghost 1.0.0.pre.2 → 1.0.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.
- 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
|