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.
@@ -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
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008-2012 Bodaniel Jeanes
1
+ Copyright (c) 2008-2014 Bodaniel Jeanes
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
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 linux and OS (more to come)...
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
- If this list is ever out of date, you can get full contributor list
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-2012 Bodaniel Jeanes
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
@@ -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.out = out
12
+ self.store = Ghost::Store::HostsFileStore.new(section_name: 'ghost')
13
+ self.out = out
12
14
 
13
15
  setup_parser
14
16
  end
@@ -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.out = out
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
@@ -1,4 +1,4 @@
1
- Ghost::Cli.task :empty do
1
+ Ghost::Cli.task :bust, :empty do
2
2
  desc "Clear all ghost-managed hosts"
3
3
 
4
4
  def perform
@@ -2,7 +2,7 @@ require 'socket'
2
2
 
3
3
  module Ghost
4
4
  class Host < Struct.new(:name, :ip)
5
- class NotResolvable < Exception; end
5
+ class NotResolvable < StandardError; end
6
6
 
7
7
  alias :to_s :name
8
8
  alias :host :name
@@ -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
- attr_accessor :path, :file, :strict
12
+ MAX_HOSTS_PER_LINE = 5
13
+ DEFAULT_FILE = Resolv::Hosts::DefaultFileName
13
14
 
14
- def initialize(path = Resolv::Hosts::DefaultFileName)
15
- self.path = path
16
- self.file = Ghost::TokenizedFile.new(path, "# ghost start", "# ghost end")
17
- self.strict = true
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(lines, buffer)
113
- lines.split($/).each do |line|
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.map do |ip|
139
- unless (hosts = buffer[ip]).empty?
140
- "#{ip} #{buffer[ip].to_a.join(" ")}"
141
- end
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($/)
@@ -1,3 +1,3 @@
1
1
  module Ghost
2
- VERSION = "1.0.0.pre.2"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -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
- previous_hosts_location = Resolv::Hosts::DefaultFileName
38
- Resolv::Hosts::DefaultFileName = "hosts_location"
39
- described_class.new.path.should == "hosts_location"
40
- Resolv::Hosts::DefaultFileName = previous_hosts_location
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
@@ -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.pre.2
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: 2012-11-14 00:00:00.000000000 Z
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: 1.3.1
114
+ version: '0'
124
115
  requirements: []
125
116
  rubyforge_project: ghost
126
- rubygems_version: 1.8.23
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