ghost 0.2.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Bodaniel Jeanes
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,95 @@
1
+ Ghost
2
+ =====
3
+
4
+ A gem that allows you to create, list, and modify local hostnames
5
+ with ease in linux and OS (more to come)...
6
+
7
+ Requirements
8
+ ============
9
+
10
+ Unix-based OS (for now)
11
+
12
+ Intended Usage
13
+ ==============
14
+
15
+ This gem is designed primarily for web developers who need to add
16
+ and modify hostnames to their system for virtual hosts on their
17
+ local/remote web server. However, it could be of use to other people
18
+ who would otherwise modify their `/etc/hosts` file manually and
19
+ flush the cache.
20
+
21
+ Command
22
+ -------
23
+
24
+ $ ghost add mydevsite.local
25
+ [Adding] mydevsite.local -> 127.0.0.1
26
+
27
+ $ ghost add staging-server.local 67.207.136.164
28
+ [Adding] staging-server.local -> 67.207.136.164
29
+
30
+ $ ghost list
31
+ Listing 2 host(s):
32
+ mydevsite.local -> 127.0.0.1
33
+ staging-server.local -> 67.207.136.164
34
+
35
+ $ ghost delete mydevsite.local
36
+ [Deleting] mydevsite.local
37
+
38
+ $ ghost list
39
+ Listing 1 host(s):
40
+ staging-server.local -> 67.207.136.164
41
+
42
+ $ ghost modify staging-server.local 64.233.167.99
43
+ [Modifying] staging-server.local -> 64.233.167.99
44
+
45
+ $ ghost list
46
+ Listing 1 host(s):
47
+ staging-server.local -> 64.233.167.99
48
+
49
+ $ ghost empty
50
+ [Emptying] Done.
51
+
52
+ $ ghost list
53
+ Listing 0 host(s):
54
+
55
+ Library
56
+ -------
57
+
58
+ There is also a library that can be used in Ruby scripts. The `ghost`
59
+ command is a wrapper for the library. View the source of `bin/ghost`
60
+ to see how to use the library.
61
+
62
+ Installation
63
+ ============
64
+
65
+ sudo gem install ghost
66
+
67
+ Contributors
68
+ ============
69
+
70
+ * [Bodaniel Jeanes](http://github.com/bjeanes)
71
+ * [Mitchell V Riley](http://github.com/mitchellvriley)
72
+
73
+ Legal Stuff
74
+ ===========
75
+
76
+ Copyright (c) 2008 Bodaniel Jeanes
77
+
78
+ Permission is hereby granted, free of charge, to any person obtaining
79
+ a copy of this software and associated documentation files (the
80
+ "Software"), to deal in the Software without restriction, including
81
+ without limitation the rights to use, copy, modify, merge, publish,
82
+ distribute, sublicense, and/or sell copies of the Software, and to
83
+ permit persons to whom the Software is furnished to do so, subject to
84
+ the following conditions:
85
+
86
+ The above copyright notice and this permission notice shall be
87
+ included in all copies or substantial portions of the Software.
88
+
89
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
90
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
91
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
92
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
93
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
94
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
95
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require 'rubygems/specification'
4
+ require 'date'
5
+
6
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
7
+
8
+
9
+ #### MISC TASKS ####
10
+
11
+ desc "list tasks"
12
+ task :default do
13
+ puts `rake -T`.grep(/^[^(].*$/)
14
+ end
15
+
16
+ desc "Outstanding TODO's"
17
+ task :todo do
18
+ files = ["**/*.{rb,rake}" "bin/*", "README.mkdn"]
19
+
20
+ File.open('TODO','w') do |f|
21
+ FileList[*files].egrep(/TODO|FIXME/) do |file, line, text|
22
+ output = "#{file}:#{line} - #{text.chomp.gsub(/^\s+|\s+$/ , "")}"
23
+
24
+ puts output
25
+ f.puts output
26
+ end
27
+ end
28
+ end
data/TODO ADDED
File without changes
data/bin/ghost ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created by Bodaniel Jeanes on 2008-8-19.
4
+ # Copyright (c) 2008. All rights reserved.
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError
9
+ # no rubygems to load, so we fail silently
10
+ end
11
+ require 'ghost'
12
+
13
+ def help_text(exit_code = 0)
14
+ script_name = File.basename $0
15
+ puts """USAGE: #{script_name} add <hostname> [<ip=127.0.1.1>]
16
+ #{script_name} modify <hostname> <ip>
17
+ #{script_name} delete <hostname>
18
+ #{script_name} list
19
+ #{script_name} empty
20
+ """
21
+ exit(exit_code)
22
+ end
23
+
24
+ if ARGV.size.zero? || ['-h', '--help', 'help'].include?(ARGV.first)
25
+ help_text
26
+ else
27
+ case ARGV[0]
28
+ when 'add':
29
+ if [2,3].include?(ARGV.size)
30
+ begin
31
+ ARGV.shift
32
+ host = Host.add(*ARGV)
33
+ puts " [Adding] #{host.name} -> #{host.ip}"
34
+ exit 0
35
+ rescue
36
+ $stderr.puts "Cannot overwrite an existing entry. Use the modify subcommand"
37
+ exit 3
38
+ end
39
+ else
40
+ $stderr.puts "The add subcommand requires at least a hostname.\n\n"
41
+ help_text 2
42
+ end
43
+ when 'modify':
44
+ if ARGV.size == 3
45
+ ARGV.shift
46
+ ARGV << true
47
+ host = Host.add(*ARGV)
48
+ puts " [Modifying] #{host.name} -> #{host.ip}"
49
+ exit 0
50
+ else
51
+ $stderr.puts "The modify subcommand requires a hostname and an IP.\n\n"
52
+ help_text 4
53
+ end
54
+ when 'delete':
55
+ if ARGV.size == 2
56
+ Host.delete(ARGV[1])
57
+ puts " [Deleting] #{ARGV[1]}"
58
+ exit 0
59
+ else
60
+ $stderr.puts "The delete subcommand requires a hostname.\n\n"
61
+ help_text 2
62
+ end
63
+ when 'list':
64
+ hosts = Host.list
65
+ pad = hosts.max{|a,b| a.to_s.length <=> b.to_s.length }.to_s.length
66
+
67
+ puts "Listing #{hosts.size} host(s):"
68
+
69
+ hosts.each do |host|
70
+ puts "#{host.name.rjust(pad+2)} -> #{host.ip}"
71
+ end
72
+ exit 0
73
+ when 'empty':
74
+ print " [Emptying] "
75
+ Host.empty!
76
+ puts "Done."
77
+ exit 0
78
+ else
79
+ $stderr.puts "Invalid option: #{ARGV[0]}"
80
+ help_text 1
81
+ end
82
+ end
data/lib/ghost.rb ADDED
@@ -0,0 +1,8 @@
1
+ $: << File.dirname(__FILE__)
2
+
3
+ case RUBY_PLATFORM
4
+ when /darwin/
5
+ require 'ghost/mac-host'
6
+ when /linux/
7
+ require 'ghost/linux-host'
8
+ end
@@ -0,0 +1,79 @@
1
+ class Host
2
+ attr_reader :host, :ip
3
+
4
+ def initialize(host, ip)
5
+ @host = host
6
+ @ip = ip
7
+ end
8
+
9
+ def ==(other)
10
+ @host == other.host && @ip = other.ip
11
+ end
12
+
13
+ alias :to_s :host
14
+ alias :name :host
15
+ alias :hostname :host
16
+
17
+ @@hosts_file = '/etc/hosts'
18
+ @@permanent_hosts = [Host.new("localhost", "127.0.0.1"),
19
+ Host.new(`hostname`.chomp, "127.0.0.1")]
20
+ class << self
21
+ protected :new
22
+
23
+ def list
24
+ entries = []
25
+ File.open(@@hosts_file).each do |line|
26
+ next if line =~ /^#/
27
+ if line =~ /^(\d+\.\d+\.\d+\.\d+) (.*)/
28
+ ip = $1
29
+ hosts = $2.split(' ')
30
+ hosts.each {|host| entries << Host.new(host, ip) }
31
+ end
32
+ end
33
+ entries.delete_if {|host| @@permanent_hosts.include? host }
34
+ entries
35
+ end
36
+
37
+ def add(host, ip = "127.0.0.1", force = false)
38
+ if find_by_host(host).nil? || force
39
+ delete(host)
40
+ new_host = Host.new(host, ip)
41
+
42
+ hosts = list
43
+ hosts << new_host
44
+ write_out!(hosts)
45
+
46
+ new_host
47
+ else
48
+ raise "Can not overwrite existing record"
49
+ end
50
+ end
51
+
52
+ def find_by_host(hostname)
53
+ list.find{|host| host.hostname == hostname }
54
+ end
55
+
56
+ def find_by_ip(ip)
57
+ list.find_all{|host| host.ip == ip }
58
+ end
59
+
60
+ def empty!
61
+ write_out!([])
62
+ end
63
+
64
+ def delete(name)
65
+ hosts = list
66
+ hosts = hosts.delete_if {|host| host.name == name }
67
+ write_out!(hosts)
68
+ end
69
+
70
+ protected
71
+
72
+ def write_out!(hosts)
73
+ hosts += @@permanent_hosts
74
+ # Har har! inject!
75
+ output = hosts.inject("") {|s, h| s + "#{h.ip} #{h.hostname}\n" }
76
+ File.open(@@hosts_file, 'w') {|f| f.print output }
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,97 @@
1
+ class Host
2
+ ListCmd = "dscl localhost -list /Local/Default/Hosts 2>&1"
3
+ ReadCmd = "dscl localhost -read /Local/Default/Hosts/%s 2>&1"
4
+ CreateCmd = "sudo dscl localhost -create /Local/Default/Hosts/%s IPAddress %s 2>&1"
5
+ DeleteCmd = "sudo dscl localhost -delete /Local/Default/Hosts/%s 2>&1"
6
+
7
+ class << self
8
+ protected :new
9
+
10
+ def list
11
+ list = `#{ListCmd}`
12
+ list.collect { |host| Host.new(host.chomp) }
13
+ end
14
+
15
+ def add(host, ip = "127.0.0.1", force = false)
16
+ if find_by_host(host).nil? || force
17
+ `#{CreateCmd % [host, ip]}`
18
+ flush!
19
+ find_by_host(host)
20
+ else
21
+ raise "Can not overwrite existing record"
22
+ end
23
+ end
24
+
25
+ def find_by_host(host)
26
+ @hosts ||= {}
27
+ @hosts[host] ||= begin
28
+ output = `#{ReadCmd % host}`
29
+
30
+ if output =~ /eDSRecordNotFound/
31
+ nil
32
+ else
33
+ host = parse_host(output)
34
+ ip = parse_ip(output)
35
+
36
+ Host.new(host, ip)
37
+ end
38
+ end
39
+ end
40
+
41
+ def find_by_ip(ip)
42
+ nil
43
+ end
44
+
45
+ def empty!
46
+ list.each { |h| delete(h) }
47
+ nil
48
+ end
49
+
50
+ def delete(host)
51
+ `#{DeleteCmd % host.to_s}`
52
+ flush!
53
+ end
54
+
55
+ # Flushes the DNS Cache
56
+ def flush!
57
+ `dscacheutil -flushcache`
58
+ @hosts = {}
59
+ true
60
+ end
61
+
62
+ protected
63
+ def parse_host(output)
64
+ parse_value(output, 'RecordName')
65
+ end
66
+
67
+ def parse_ip(output)
68
+ parse_value(output, 'IPAddress')
69
+ end
70
+
71
+ def parse_value(output, key)
72
+ match = output.match(Regexp.new("^#{key}: (.*)$"))
73
+ match[1] unless match.nil?
74
+ end
75
+ end
76
+
77
+ def initialize(host, ip=nil)
78
+ @host = host
79
+ @ip = ip
80
+ end
81
+
82
+ def hostname
83
+ @host
84
+ end
85
+ alias :to_s :hostname
86
+ alias :host :hostname
87
+ alias :name :hostname
88
+
89
+ def ip
90
+ @ip ||= self.class.send(:parse_ip, dump)
91
+ end
92
+
93
+ private
94
+ def dump
95
+ @dump ||= `#{ReadCmd % hostname}`
96
+ end
97
+ end
@@ -0,0 +1,120 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ # Warning: these tests will delete all hostnames in the system. Please back them up first
4
+
5
+ Host.empty!
6
+
7
+ describe Host, ".list" do
8
+ after(:each) { Host.empty! }
9
+
10
+ it "should return an array" do
11
+ Host.list.should be_instance_of(Array)
12
+ end
13
+
14
+ it "should contain instances of Host" do
15
+ Host.add('ghost-test-hostname.local')
16
+ Host.list.first.should be_instance_of(Host)
17
+ end
18
+ end
19
+
20
+ describe Host do
21
+ after(:each) { Host.empty! }
22
+
23
+ it "should have an IP" do
24
+ hostname = 'ghost-test-hostname.local'
25
+
26
+ Host.add(hostname)
27
+ host = Host.list.first
28
+ host.ip.should eql('127.0.0.1')
29
+
30
+ Host.empty!
31
+
32
+ ip = '169.254.23.121'
33
+ host = Host.add(hostname, ip)
34
+ host.ip.should eql(ip)
35
+ end
36
+
37
+ it "should have a hostname" do
38
+ hostname = 'ghost-test-hostname.local'
39
+
40
+ Host.add(hostname)
41
+ host = Host.list.first
42
+ host.hostname.should eql(hostname)
43
+
44
+ Host.empty!
45
+
46
+ ip = '169.254.23.121'
47
+ Host.add(hostname, ip)
48
+ host.hostname.should eql(hostname)
49
+ end
50
+
51
+ it ".to_s should return hostname" do
52
+ hostname = 'ghost-test-hostname.local'
53
+
54
+ Host.add(hostname)
55
+ host = Host.list.first
56
+ host.to_s.should eql(hostname)
57
+ end
58
+ end
59
+
60
+ describe Host, "finder methods" do
61
+ after(:all) { Host.empty! }
62
+ before(:all) do
63
+ Host.add('abc.local')
64
+ Host.add('def.local')
65
+ Host.add('efg.local', '10.2.2.4')
66
+ end
67
+
68
+ it "should return valid Host when searching for host name" do
69
+ Host.find_by_host('abc.local').should be_instance_of(Host)
70
+ end
71
+
72
+ end
73
+
74
+ describe Host, ".add" do
75
+ after(:each) { Host.empty! }
76
+
77
+ it "should return Host object when passed hostname" do
78
+ Host.add('ghost-test-hostname.local').should be_instance_of(Host)
79
+ end
80
+
81
+ it "should return Host object when passed hostname" do
82
+ Host.add('ghost-test-hostname.local', '10.0.0.2').should be_instance_of(Host)
83
+ end
84
+
85
+ it "should raise error if hostname already exists and not add a duplicate" do
86
+ Host.empty!
87
+ Host.add('ghost-test-hostname.local')
88
+ lambda { Host.add('ghost-test-hostname.local') }.should raise_error
89
+ Host.list.should have(1).thing
90
+ end
91
+
92
+ it "should overwrite existing hostname if forced" do
93
+ hostname = 'ghost-test-hostname.local'
94
+
95
+ Host.empty!
96
+ Host.add(hostname)
97
+
98
+ Host.list.first.hostname.should eql(hostname)
99
+ Host.list.first.ip.should eql('127.0.0.1')
100
+
101
+ Host.add(hostname, '10.0.0.1', true)
102
+ Host.list.first.hostname.should eql(hostname)
103
+ Host.list.first.ip.should eql('10.0.0.1')
104
+
105
+ Host.list.should have(1).thing
106
+ end
107
+ end
108
+
109
+ describe Host, ".empty!" do
110
+ it "should empty the hostnames" do
111
+ Host.add('ghost-test-hostname.local') # add a hostname to be sure
112
+ Host.empty!
113
+ Host.list.should have(0).things
114
+ end
115
+ end
116
+
117
+ describe Host, ".backup and", Host, ".restore" do
118
+ it "should return a yaml file of all hosts and IPs when backing up"
119
+ it "should empty the hosts and restore only the ones in given yaml"
120
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour
@@ -0,0 +1,3 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
3
+ require 'ghost'
metadata ADDED
@@ -0,0 +1,66 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ghost
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Bodaniel Jeanes
8
+ autorequire: ghost
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-03-29 00:00:00 +10:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Allows you to create, list, and modify .local hostnames in 10.5 with ease
17
+ email: me@bjeanes.com
18
+ executables:
19
+ - ghost
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ - LICENSE
25
+ - TODO
26
+ files:
27
+ - LICENSE
28
+ - README
29
+ - Rakefile
30
+ - TODO
31
+ - bin/ghost
32
+ - lib/ghost
33
+ - lib/ghost/linux-host.rb
34
+ - lib/ghost/mac-host.rb
35
+ - lib/ghost.rb
36
+ - spec/ghost_spec.rb
37
+ - spec/spec.opts
38
+ - spec/spec_helper.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/bjeanes/ghost
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --line-numbers
44
+ require_paths:
45
+ - lib
46
+ required_ruby_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ required_rubygems_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ requirements: []
59
+
60
+ rubyforge_project: ghost
61
+ rubygems_version: 1.3.1
62
+ signing_key:
63
+ specification_version: 2
64
+ summary: Allows you to create, list, and modify .local hostnames in 10.5 with ease
65
+ test_files: []
66
+