ghost 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
+