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.
@@ -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