ip_tracker 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "http://rubygems.org"
2
+
3
+
4
+ platforms :mswin, :mingw do
5
+ gem 'win32console'
6
+ gem 'win32-changenotify'
7
+ gem 'win32-event'
8
+ gem 'rb-readline'
9
+ gem 'rb-notifu'
10
+ end
11
+
12
+ platform :ruby do
13
+ gem 'rb-inotify'
14
+ end
15
+
16
+ gem 'guard', :git => "git://github.com/guard/guard.git"
17
+ gem 'looper', :git => "git@github.com:diedthreetimes/looper.git"
18
+
19
+ # Specify your gem's dependencies in ip_tracker.gemspec
20
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :all_after_pass => false, :cli => '--color', :version => 2 do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ end
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/ipme ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'ip_tracker'
4
+
5
+ IpTracker::CLI.start
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "ip_tracker/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "ip_tracker"
7
+ s.version = IpTracker::VERSION
8
+ s.authors = ["Sky Faber"]
9
+ s.email = ["skyfaber@gmail.com"]
10
+ s.homepage = "https://github.com/diedthreetimes/IpTracker"
11
+ s.summary = %q{A command line utility to interface with IpMe}
12
+ s.description = %q{Keep a dynamic IP up to date without the use of DNS. Either sync manually or automatically to a known location.}
13
+
14
+ s.rubyforge_project = "ip_tracker"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec", '~> 2.6'
23
+ s.add_development_dependency "guard"
24
+ s.add_development_dependency "guard-rspec"
25
+ s.add_development_dependency "rb-readline"
26
+ s.add_development_dependency "webmock"
27
+
28
+ s.add_runtime_dependency "thor"
29
+ s.add_runtime_dependency "faraday_middleware"
30
+ s.add_runtime_dependency "yajl-ruby"
31
+ s.add_runtime_dependency "multi_json"
32
+ s.add_runtime_dependency "rash"
33
+ s.add_runtime_dependency "looper"
34
+ end
@@ -0,0 +1,24 @@
1
+ module IpTracker
2
+ class CLI
3
+ desc "register", "register this computer with IpMe"
4
+ method_option :hostname, :type => :string, :desc => "What name to store this hoste under"
5
+ def register
6
+ unless config.host_token
7
+ hostname = options[:hostname] || get_option(:hostname)
8
+
9
+ say "Attempting to register this computer.", :yellow
10
+
11
+ token = client.register(hostname)
12
+ say "Registration completed.", :green
13
+ config.update(:host_token, token)
14
+ else
15
+ say "This computer has already been registered."
16
+ end
17
+ rescue IpTracker::Client::TargetError
18
+ say "Registration failed."
19
+ rescue IpTracker::Client::HostTakenError
20
+ say "You must enter a unique name."
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,44 @@
1
+ module IpTracker
2
+ class CLI
3
+ desc "sync", "continuously sync the ip with IpMe"
4
+ method_option :start, :type => :boolean, :desc => "Command start", :default => true
5
+ method_option :stop, :type => :boolean, :desc => "Command stop", :default => false
6
+ method_option :daemon, :type => :boolean, :aliases => ["-d"], :default => false, :desc => "Run this as a daemon, only works with start"
7
+
8
+ def sync
9
+ command = options[:stop] ? :stop : :start
10
+ daemonize = options[:daemon] && Process.respond_to?(:fork)
11
+
12
+ case command
13
+ when :start
14
+ if config.pid
15
+ say "IpMe is already running."
16
+ else
17
+ if daemonize
18
+ pid = Process.fork do
19
+ SyncDaemon.new.run
20
+ Process.daemon
21
+ end
22
+
23
+ # TODO verify if detach and daemon work together
24
+ config.update(:pid, pid)
25
+ Process.detach(pid)
26
+ else
27
+ SyncDaemon.new.run
28
+ end
29
+ end
30
+ when :stop
31
+ if config.pid
32
+ say "Killing #{config.pid}"
33
+
34
+ Process.kill("TERM", config.pid)
35
+
36
+ say "Killed"
37
+ else
38
+ say "No PID saved"
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,21 @@
1
+ module IpTracker
2
+ class CLI
3
+ desc "update", "update IpMe with the specified IP"
4
+ method_option :ip, :type => :string, :desc => "Which IP to update use", :required => true
5
+ def update
6
+ ip = options[:ip]
7
+ host = config.host_token
8
+ if host.nil?
9
+ say "Please first register this computer."
10
+ else
11
+ say "Attempting to update ip.", :yellow
12
+
13
+ client.update(host, :ip, ip)
14
+
15
+ say "Update completed", :green
16
+ end
17
+ rescue IpTracker::Client::TargetError
18
+ say "Update failed."
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ require 'thor'
2
+
3
+ module IpTracker
4
+ class CLI < Thor
5
+ protected
6
+
7
+ def get_option(option)
8
+ value = ask("Please enter your #{option}:")
9
+ raise Thor::Error, "You must enter a value for that field." if value.empty?
10
+ value
11
+ end
12
+
13
+ def client
14
+ @client ||= IpTracker::Client.new
15
+ end
16
+
17
+ def config
18
+ @config ||= IpTracker::Config.new
19
+ end
20
+ end
21
+ end
22
+
23
+ require 'ip_tracker/cli/methods/register'
24
+ require 'ip_tracker/cli/methods/update'
25
+ require 'ip_tracker/cli/methods/sync'
@@ -0,0 +1,18 @@
1
+ module IpTracker
2
+ class Client
3
+ module Authentication
4
+ attr_accessor :auth_token
5
+ attr_reader :user
6
+
7
+ def login(username, password)
8
+ response = post("#{IpTracker::USERS_PATH}/#{username}/tokens",
9
+ :body => { :password => password })
10
+
11
+ raise TargetError if response.code == 200
12
+
13
+ @user = username
14
+ @auth_token = response.token
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
1
+ require'faraday_middleware'
2
+
3
+ module IpTracker
4
+ class Client
5
+ module Connection
6
+ private
7
+
8
+ def connection
9
+ connection = Faraday.new(:url => target_url) do |builder|
10
+ builder.use Faraday::Request::JSON
11
+ builder.use Faraday::Response::Rashify
12
+ builder.use Faraday::Response::ParseJson
13
+
14
+ builder.adapter http_adapter
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ module IpTracker
2
+ class Client
3
+ class HostTakenError < RuntimeError; end
4
+ class TargetError < RuntimeError; end
5
+ end
6
+ end
@@ -0,0 +1,23 @@
1
+ module IpTracker
2
+ class Client
3
+ module Request
4
+ def post(path, options={})
5
+ request(:post, path, options)
6
+ end
7
+
8
+ def put(path, options={})
9
+ request(:put, path, options)
10
+ end
11
+
12
+ private
13
+
14
+ def request(action, path, options)
15
+ response = connection.send(action, path) do |request|
16
+ request.body = options[:body] if options[:body]
17
+ end
18
+
19
+ response.body
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+
2
+ require 'ip_tracker/client/authentication'
3
+ require 'ip_tracker/client/connection'
4
+ require 'ip_tracker/client/errors'
5
+ require 'ip_tracker/client/request'
6
+
7
+ module IpTracker
8
+ class Client
9
+ attr_reader :http_adapter, :target_url
10
+
11
+ def initialize
12
+ @target_url = IpTracker::DEFAULT_LOCAL_TARGET
13
+ @http_adapter = :net_http
14
+ end
15
+
16
+ # include Authentication
17
+ include Connection
18
+ include Request
19
+
20
+ attr_accessor :host_token
21
+
22
+ def register hostname
23
+ response = post(IpTracker::HOSTS_PATH, :body => { name: hostname } )
24
+ raise HostTakenError if response.code == 201
25
+ raise TargetError if response.code == 200 || response.id == nil
26
+
27
+ @host_token = response.id
28
+ end
29
+
30
+ def update host_id, attr, value
31
+ response = put(IpTracker::HOSTS_PATH + "/#{host_id}", :body => {attr.to_sym => value})
32
+
33
+ raise TargetError if response.code == 200 || response.send(attr) != value
34
+
35
+ true
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ module IpTracker
2
+ class Config
3
+ attr_accessor :config_hash, :settings_path
4
+
5
+ def initialize(options={})
6
+ @settings_path = File.expand_path(IpTracker::DEFAULT_CONFIG_PATH)
7
+ @config_hash = load_settings || {}
8
+ end
9
+
10
+ def host_token
11
+ config_hash["host_token"] || nil
12
+ end
13
+
14
+ def pid
15
+ config_hash["pid"] || nil
16
+ end
17
+
18
+ def update(attr, value)
19
+ config_hash[attr.to_s] = value
20
+
21
+ File.open(settings_path, 'w') do |out|
22
+ YAML.dump(config_hash, out)
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def load_settings
29
+ File.exists?(settings_path) ? YAML.load_file(settings_path) : nil
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,64 @@
1
+ require 'socket'
2
+ require 'looper'
3
+
4
+ module IpTracker
5
+ class SyncDaemon
6
+ include ::Looper
7
+
8
+ attr_accessor :last_ip
9
+
10
+ def initialize(config = {})
11
+ @run = true
12
+ @runs = config[:runs].nil? ? nil : config[:runs]
13
+ @sleep = config[:sleep].nil? ? 60 : config[:sleep]
14
+ end
15
+
16
+ def run
17
+ loopme(@sleep) do
18
+ if @runs == 0
19
+ @run = false
20
+ elsif !@runs.nil?
21
+ @runs -= 1
22
+ end
23
+
24
+ begin
25
+ new_ip = local_ip
26
+
27
+ if @last_ip != new_ip
28
+ puts "#{Time.now}: Updating to #{new_ip}"
29
+ update_ip new_ip
30
+
31
+ @last_ip = new_ip
32
+ end
33
+ rescue Client::TargetError
34
+ puts "An error occured trying to communicate with the server, sleeping"
35
+ sleep(6000) unless !@runs.nil?
36
+ end
37
+ end
38
+ end
39
+
40
+ def local_ip
41
+ orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true # turn off reverse DNS resolution temporarily
42
+
43
+ UDPSocket.open do |s|
44
+ s.connect '64.233.187.99', 1
45
+ s.addr.last
46
+ end
47
+ ensure
48
+ Socket.do_not_reverse_lookup = orig
49
+ end
50
+
51
+ def update_ip ip
52
+ client.update(config.host_token, :ip, ip)
53
+ # CLI.start( ['update', '--ip', ip] )
54
+ end
55
+
56
+ def config
57
+ @config ||= Config.new
58
+ end
59
+
60
+ def client
61
+ @client ||= Client.new
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module IpTracker
2
+ VERSION = "0.0.1"
3
+ end
data/lib/ip_tracker.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "ip_tracker/version"
2
+
3
+ module IpTracker
4
+ DEFAULT_CONFIG_PATH = '~/.ip_tracker'
5
+ DEFAULT_LOCAL_TARGET = 'http://ipme.herokuapp.com'
6
+ # DEFAULT_LOCAL_TARGET = 'http://localhost:4567'
7
+ HOSTS_PATH = '/hosts'
8
+ end
9
+
10
+ require 'ip_tracker/config'
11
+ require 'ip_tracker/cli'
12
+ require 'ip_tracker/client'
13
+ require 'ip_tracker/sync_daemon'
@@ -0,0 +1,9 @@
1
+ HTTP/1.1 403 Forbidden
2
+ Server: nginx/0.7.65
3
+ Date: Thu, 03 Mar 2011 18:29:40 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ Keep-Alive: timeout=20
7
+ Content-Length: 52
8
+
9
+ {"code":200,"description":"Operation not permitted"}
@@ -0,0 +1,9 @@
1
+ HTTP/1.1 200 OK
2
+ Server: nginx/0.7.65
3
+ Date: Thu, 03 Mar 2011 18:26:45 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ Keep-Alive: timeout=20
7
+ Content-Length: 28
8
+
9
+ {"id":null}
@@ -0,0 +1,9 @@
1
+ HTTP/1.1 200 OK
2
+ Server: nginx/0.7.65
3
+ Date: Thu, 03 Mar 2011 18:26:45 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ Keep-Alive: timeout=20
7
+ Content-Length: 28
8
+
9
+ {"id":6}
@@ -0,0 +1,9 @@
1
+ HTTP/1.1 400 Bad Request
2
+ Server: nginx/0.7.65
3
+ Date: Thu, 03 Mar 2011 18:29:40 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ Keep-Alive: timeout=20
7
+ Content-Length: 52
8
+
9
+ {"code":201,"description":"Name already taken."}
@@ -0,0 +1,9 @@
1
+ HTTP/1.1 403 Forbidden
2
+ Server: nginx/0.7.65
3
+ Date: Thu, 03 Mar 2011 18:29:40 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ Keep-Alive: timeout=20
7
+ Content-Length: 52
8
+
9
+ {"code":200,"description":"Operation not permitted"}
@@ -0,0 +1,9 @@
1
+ HTTP/1.1 200 OK
2
+ Server: nginx/0.7.65
3
+ Date: Thu, 03 Mar 2011 18:26:45 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ Keep-Alive: timeout=20
7
+ Content-Length: 30
8
+
9
+ {"id":7, "ip":"127.0.1.10034"}
@@ -0,0 +1,9 @@
1
+ HTTP/1.1 200 OK
2
+ Server: nginx/0.7.65
3
+ Date: Thu, 03 Mar 2011 18:26:45 GMT
4
+ Content-Type: application/json
5
+ Connection: keep-alive
6
+ Keep-Alive: timeout=20
7
+ Content-Length: 28
8
+
9
+ {"id":7, "ip":"127.0.0.1"}
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'ip_tracker'
3
+
4
+ describe IpTracker::CLI do
5
+
6
+ describe 'executable' do
7
+ it 'should print usage' do
8
+ `ipme`.should match /Tasks:/
9
+ end
10
+ end
11
+
12
+ describe '#register' do
13
+ context "when there is no hostid token" do
14
+ def stub_client_and_config
15
+ mock_client.should_receive(:register).with('foo') { 'token' }
16
+
17
+ config = mock_config
18
+ config.should_receive(:update).with(:host_token, 'token')
19
+ config.should_receive(:host_token).and_return(nil)
20
+ end
21
+
22
+ context 'and no arguments are provided' do
23
+ let(:register) { IpTracker::CLI.start(['register']) }
24
+
25
+ it "asks for a new hostname" do
26
+ stub_client_and_config
27
+ $stdin.should_receive(:gets).and_return('foo')
28
+
29
+ results = capture(:stdout) { register }
30
+ results.should match /Please enter your hostname:/
31
+ end
32
+
33
+ context "displays an error if" do
34
+ it "hostname is blank" do
35
+ mock_config.should_receive(:host_token).and_return(nil)
36
+
37
+ $stdin.should_receive(:gets).and_return('')
38
+ results = capture(:stderr, :stdout) { register }
39
+ results.should match /must enter a value/
40
+ results.should_not match /success/
41
+ end
42
+
43
+ it "hostname is taken" do
44
+ mock_config.should_receive(:host_token).and_return(nil)
45
+
46
+ $stdin.should_receive(:gets).and_return('')
47
+ results = capture(:stderr, :stdout) { register }
48
+ results.should match /must enter a/
49
+ results.should_not match /success/
50
+ end
51
+ end
52
+
53
+ end
54
+
55
+ context "and a hostname is provided" do
56
+ let(:register) do
57
+ IpTracker::CLI.start(["register", "--hostname", "foo"])
58
+ end
59
+
60
+ it "does not ask for hostnme" do
61
+ stub_client_and_config
62
+ $stdin.should_not_receive(:gets)
63
+ results = capture(:stdout) { register }
64
+ results.should match /Attempting to register/
65
+ end
66
+
67
+ context "and hostname will fail" do
68
+ it "displays a failed message if host taken" do
69
+ mock_client.should_receive(:register).
70
+ with('foo') { raise IpTracker::Client::HostTakenError }
71
+ mock_config.should_receive(:host_token).and_return(nil)
72
+
73
+ results = capture(:stdout) { register }
74
+ results.should match /You must enter a unique name./
75
+ end
76
+
77
+ it "displays a failed message if client errors" do
78
+ mock_client.should_receive(:register).
79
+ with('foo') { raise IpTracker::Client::TargetError }
80
+ mock_config.should_receive(:host_token).and_return(nil)
81
+
82
+ results = capture(:stdout) { register }
83
+ results.should match /Registration failed./
84
+
85
+ end
86
+ end
87
+
88
+ context "and hostname will succeed" do
89
+ it "saves a hostid token and displays success" do
90
+ stub_client_and_config
91
+
92
+ results = capture(:stdout) { register }
93
+ results.should match /completed/
94
+ end
95
+ end
96
+ end
97
+ end
98
+
99
+ context "when there is a hostid token" do
100
+ let(:register) { IpTracker::CLI.start(['register']) }
101
+ it "should not ask for hostname and print a message" do
102
+ mock_config.should_receive(:host_token).and_return('token')
103
+ $stdin.should_not_receive(:gets)
104
+ results = capture(:stdout) { register }
105
+ results.should match /This computer has already been registered./
106
+ end
107
+ end
108
+ end # register
109
+ end # CLI
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+ require 'ip_tracker'
3
+
4
+ describe IpTracker::CLI do
5
+ describe '#sync' do
6
+
7
+ def stub_config(pid=nil)
8
+ config = mock_config
9
+ config.should_receive(:update).with(:pid, pid) if !pid.nil?
10
+ config.should_receive(:pid).and_return(nil)
11
+ end
12
+
13
+ def stub_daemon
14
+ daemon = double(IpTracker::SyncDaemon, {})
15
+ IpTracker::SyncDaemon.stub(:new) { daemon }
16
+ daemon.should_receive(:run)
17
+ end
18
+ describe "start" do
19
+ context "when there are no options" do
20
+ let(:sync) { IpTracker::CLI.start(['sync']) }
21
+
22
+ it "should call sync_daemon" do
23
+ stub_config
24
+ stub_daemon
25
+
26
+ sync
27
+ end
28
+
29
+ it "prints an error when a pid is saved" do
30
+ mock_config.should_receive(:pid).and_return(343)
31
+ IpTracker::SyncDaemon.should_not_receive(:new)
32
+
33
+ results = capture(:stdout) { sync }
34
+ results.should match /IpMe is already running./
35
+ end
36
+ end
37
+
38
+ context "when daemonize is provided" do
39
+ let(:sync) { IpTracker::CLI.start(['sync', '--start', '-d']) }
40
+
41
+ it "should detach and store a pid" do
42
+ stub_config(36)
43
+
44
+ Process.should_receive(:fork).and_return(36)
45
+ Process.should_receive(:respond_to?).with(:fork).and_return(true)
46
+ Process.should_receive(:detach)
47
+
48
+ results = capture(:stdout) { sync }
49
+ results.should == ""
50
+
51
+ #TODO: assert output is redirected to a file
52
+ end
53
+ end
54
+ end
55
+
56
+ describe "#stop" do
57
+ let(:sync) { IpTracker::CLI.start(['sync', '--stop']) }
58
+ context "when a pid is saved" do
59
+ it "should send sigterm to the daemon and delete the pid" do
60
+ mock_config.should_receive(:pid).at_least(1).times.and_return(36)
61
+ Process.should_receive(:kill).with("TERM", 36)
62
+
63
+ results = capture(:stdout) { sync }
64
+ results.should match /killed/i
65
+ end
66
+ end
67
+
68
+ it "should print a warning when a pid doesn't exist" do
69
+ stub_config
70
+
71
+ results = capture(:stdout) { sync }
72
+ results.should match /no pid/i
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'ip_tracker'
3
+
4
+ describe IpTracker::CLI do
5
+ describe '#update' do
6
+ let(:update) { IpTracker::CLI.start(['update','--ip','127.0.0.1']) }
7
+ context "should display an error if" do
8
+ it "no arguments are provided" do
9
+ mock_config
10
+ mock_client
11
+
12
+ results = capture(:stderr) { IpTracker::CLI.start(['update']) }
13
+ results.should match /No value provided/
14
+ end
15
+
16
+ # TODO: Automatically call register
17
+ it "no hostid token present" do
18
+ mock_config.should_receive(:host_token).and_return nil
19
+ mock_client
20
+
21
+ results = capture(:stdout) { update }
22
+ results.should match /Please first register this computer./
23
+ end
24
+ end
25
+
26
+ context "when a hostid token is present" do
27
+ it "displays a failed message if client errors" do
28
+ mock_client.should_receive(:update).
29
+ with('token', :ip, '127.0.0.1') { raise IpTracker::Client::TargetError }
30
+ mock_config.should_receive(:host_token).and_return('token')
31
+
32
+ results = capture(:stdout) { update }
33
+ results.should match /Update failed./
34
+ end
35
+
36
+ context "and client will succeed" do
37
+ it "displays success" do
38
+ mock_client.should_receive(:update).
39
+ with('token', :ip, '127.0.0.1')
40
+ mock_config.should_receive(:host_token).and_return('token')
41
+
42
+ results = capture(:stdout) { update }
43
+ results.should match /completed/
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,92 @@
1
+ require 'spec_helper'
2
+ require 'ip_tracker'
3
+
4
+ describe IpTracker::Client do
5
+ def stub_register(status)
6
+ fixture_file =
7
+ case status
8
+ when :success
9
+ "register_success.txt"
10
+ when :no_name
11
+ "register_taken.txt"
12
+ when :invalid
13
+ "register_invalid.txt"
14
+ else
15
+ "register_failure.txt"
16
+ end
17
+ stub_request(:post, IpTracker::DEFAULT_LOCAL_TARGET + IpTracker::HOSTS_PATH).
18
+ with(:body => {:name => "foo"}).
19
+ to_return(fixture(fixture_file))
20
+ end
21
+
22
+ def stub_update(status)
23
+ fixture_file =
24
+ case status
25
+ when :success
26
+ "update_success.txt"
27
+ when :invalid
28
+ "update_invalid.txt"
29
+ else
30
+ "update_failed.txt"
31
+ end
32
+
33
+ stub_request(:put, IpTracker::DEFAULT_LOCAL_TARGET + IpTracker::HOSTS_PATH+ '/9').
34
+ with(:body => {:ip => "127.0.0.1"}).
35
+ to_return(fixture(fixture_file))
36
+ end
37
+
38
+ #TODO: Refactor target+hosts to be configurable
39
+ describe "#register" do
40
+
41
+ context "when successful" do
42
+
43
+ it "sets and returns the token" do
44
+ stub_register(:success)
45
+
46
+ host_token = subject.register 'foo'
47
+ subject.host_token.should == host_token
48
+ host_token.should == 6
49
+ end
50
+ end
51
+
52
+ context "raises an exception when" do
53
+
54
+ it "failed" do
55
+ stub_register(:failed)
56
+ expect { subject.register('foo') }.to raise_error IpTracker::Client::TargetError
57
+ end
58
+
59
+
60
+ it "name taken" do
61
+ stub_register(:no_name)
62
+ expect { subject.register('foo') }.to raise_error IpTracker::Client::HostTakenError
63
+ end
64
+
65
+ it "response invalid" do
66
+ stub_register(:invalid)
67
+ expect { subject.register('foo') }.to raise_error IpTracker::Client::TargetError
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "#update" do
73
+ context "when successful" do
74
+ it "returns true" do
75
+ stub_update(:success)
76
+ subject.update(9, :ip, '127.0.0.1').should == true
77
+ end
78
+ end
79
+
80
+ context "raises an exception when" do
81
+ it "response failed " do
82
+ stub_update(:failed)
83
+ expect { subject.update(9, :ip, '127.0.0.1') }.to raise_error IpTracker::Client::TargetError
84
+ end
85
+
86
+ it "response invalid" do
87
+ stub_update(:invalid)
88
+ expect { subject.update(9, :ip, '127.0.0.1') }.to raise_error IpTracker::Client::TargetError
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+ require 'ip_tracker'
3
+
4
+ describe IpTracker::Config do
5
+ let(:path) { File.expand_path(IpTracker::DEFAULT_CONFIG_PATH) }
6
+ let(:io) { StringIO.new }
7
+
8
+ # TODO refactor tests to use settings once configurable
9
+
10
+ def stub_config_file(contents=nil)
11
+ File.stub(:exists?).with(path) { contents ? true : false }
12
+ YAML.stub(:load_file).with(path) { contents } if contents
13
+ File.stub(:open).with(path, 'w').and_yield(io)
14
+ end
15
+
16
+ describe "#update" do
17
+ context "when a .ip_tracker file doesn't exist" do
18
+ it "creates the file and can add a new token" do
19
+ expected_token = 'new_token'
20
+
21
+ stub_config_file(nil)
22
+ YAML.should_receive(:dump).with({"host_token" => expected_token}, io)
23
+
24
+ config = IpTracker::Config.new
25
+ config.update(:host_token, 'new_token')
26
+ config.host_token.should == expected_token
27
+ end
28
+ end
29
+
30
+ context "when a .ip_tracker file exists" do
31
+ it "can update tokens in the .ip_tracker file" do
32
+ expected_token = 'new_token'
33
+ old_token = 'old_token'
34
+
35
+ stub_config_file("host_token" => old_token)
36
+ YAML.should_receive(:dump).with({"host_token" => expected_token}, io)
37
+
38
+ config = IpTracker::Config.new
39
+ config.host_token.should == old_token
40
+ config.update(:host_token, 'new_token')
41
+ config.host_token.should == expected_token
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,30 @@
1
+ require 'spec_helper'
2
+ require 'ip_tracker'
3
+
4
+ describe IpTracker::SyncDaemon do
5
+ describe "#run" do
6
+ let(:run) { IpTracker::SyncDaemon.new( runs: 5, sleep: 1 ).run }
7
+ let(:mock_ip) { "127.0.0.1" }
8
+
9
+ it "should update the ip exactly once" do
10
+ UDPSocket.should_receive(:open).exactly(6).times.and_return(mock_ip)
11
+
12
+ mock_config.should_receive(:host_token).and_return(6)
13
+ mock_client.should_receive(:update).with(6, :ip, mock_ip)
14
+
15
+ results = capture(:stdout) { run }
16
+ results.should match /process started/
17
+ results.should_not match /error/
18
+ end
19
+
20
+ it "should degrade gracefully upon error" do
21
+ UDPSocket.should_receive(:open).exactly(6).times.and_return(mock_ip)
22
+
23
+ mock_config.should_receive(:host_token).at_least(1).times.and_return(6)
24
+ mock_client.should_receive(:update).at_least(1).times.with(6, :ip, mock_ip) { raise IpTracker::Client::TargetError }
25
+
26
+ results = capture(:stdout){ run }
27
+ results.should match /error/
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,54 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'ip_tracker'
5
+
6
+ require 'webmock/rspec'
7
+
8
+ RSpec.configure do |config|
9
+
10
+ end
11
+
12
+ def fixture_path
13
+ File.expand_path("../fixtures", __FILE__)
14
+ end
15
+
16
+ def fixture(file)
17
+ File.new(fixture_path + '/' + file)
18
+ end
19
+
20
+ def capture(*streams)
21
+ streams = [*streams]
22
+ begin
23
+ streams.each { |stream|
24
+ stream = stream.to_s
25
+ eval "$#{stream} = StringIO.new"
26
+ }
27
+
28
+ yield
29
+
30
+ result = streams.map { |stream|
31
+ eval("$#{stream}").string
32
+ }.inject(:+)
33
+ ensure
34
+ streams.each { |stream|
35
+ eval("$#{stream} = #{stream.upcase}")
36
+ }
37
+ end
38
+
39
+ result
40
+ end
41
+
42
+
43
+ def mock_client(stubs={})
44
+ client = double(IpTracker::Client, stubs)
45
+ IpTracker::Client.stub(:new) { client }
46
+ client
47
+ end
48
+
49
+ def mock_config(stubs={})
50
+ config = double(IpTracker::Config, stubs)
51
+ IpTracker::Config.stub(:new) { config }
52
+ config
53
+ end
54
+
metadata ADDED
@@ -0,0 +1,203 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ip_tracker
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sky Faber
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-04 00:00:00.000000000 -07:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &16815264 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '2.6'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *16815264
26
+ - !ruby/object:Gem::Dependency
27
+ name: guard
28
+ requirement: &16815012 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *16815012
37
+ - !ruby/object:Gem::Dependency
38
+ name: guard-rspec
39
+ requirement: &16814736 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ type: :development
46
+ prerelease: false
47
+ version_requirements: *16814736
48
+ - !ruby/object:Gem::Dependency
49
+ name: rb-readline
50
+ requirement: &16814484 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: *16814484
59
+ - !ruby/object:Gem::Dependency
60
+ name: webmock
61
+ requirement: &16814232 !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ! '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ type: :development
68
+ prerelease: false
69
+ version_requirements: *16814232
70
+ - !ruby/object:Gem::Dependency
71
+ name: thor
72
+ requirement: &16813980 !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ type: :runtime
79
+ prerelease: false
80
+ version_requirements: *16813980
81
+ - !ruby/object:Gem::Dependency
82
+ name: faraday_middleware
83
+ requirement: &16813728 !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ! '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: *16813728
92
+ - !ruby/object:Gem::Dependency
93
+ name: yajl-ruby
94
+ requirement: &22777668 !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ type: :runtime
101
+ prerelease: false
102
+ version_requirements: *22777668
103
+ - !ruby/object:Gem::Dependency
104
+ name: multi_json
105
+ requirement: &22777416 !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ! '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ type: :runtime
112
+ prerelease: false
113
+ version_requirements: *22777416
114
+ - !ruby/object:Gem::Dependency
115
+ name: rash
116
+ requirement: &22777164 !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ type: :runtime
123
+ prerelease: false
124
+ version_requirements: *22777164
125
+ - !ruby/object:Gem::Dependency
126
+ name: looper
127
+ requirement: &22776912 !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :runtime
134
+ prerelease: false
135
+ version_requirements: *22776912
136
+ description: Keep a dynamic IP up to date without the use of DNS. Either sync manually
137
+ or automatically to a known location.
138
+ email:
139
+ - skyfaber@gmail.com
140
+ executables:
141
+ - ipme
142
+ extensions: []
143
+ extra_rdoc_files: []
144
+ files:
145
+ - .gitignore
146
+ - Gemfile
147
+ - Guardfile
148
+ - Rakefile
149
+ - bin/ipme
150
+ - ip_tracker.gemspec
151
+ - lib/ip_tracker.rb
152
+ - lib/ip_tracker/cli.rb
153
+ - lib/ip_tracker/cli/methods/register.rb
154
+ - lib/ip_tracker/cli/methods/sync.rb
155
+ - lib/ip_tracker/cli/methods/update.rb
156
+ - lib/ip_tracker/client.rb
157
+ - lib/ip_tracker/client/authentication.rb
158
+ - lib/ip_tracker/client/connection.rb
159
+ - lib/ip_tracker/client/errors.rb
160
+ - lib/ip_tracker/client/request.rb
161
+ - lib/ip_tracker/config.rb
162
+ - lib/ip_tracker/sync_daemon.rb
163
+ - lib/ip_tracker/version.rb
164
+ - spec/fixtures/register_failure.txt
165
+ - spec/fixtures/register_invalid.txt
166
+ - spec/fixtures/register_success.txt
167
+ - spec/fixtures/register_taken.txt
168
+ - spec/fixtures/update_failed.txt
169
+ - spec/fixtures/update_invalid.txt
170
+ - spec/fixtures/update_success.txt
171
+ - spec/ip_tracker/cli/methods/register_spec.rb
172
+ - spec/ip_tracker/cli/methods/sync_spec.rb
173
+ - spec/ip_tracker/cli/methods/update_spec.rb
174
+ - spec/ip_tracker/client_spec.rb
175
+ - spec/ip_tracker/config_spec.rb
176
+ - spec/ip_tracker/sync_daemon_spec.rb
177
+ - spec/spec_helper.rb
178
+ has_rdoc: true
179
+ homepage: https://github.com/diedthreetimes/IpTracker
180
+ licenses: []
181
+ post_install_message:
182
+ rdoc_options: []
183
+ require_paths:
184
+ - lib
185
+ required_ruby_version: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ! '>='
189
+ - !ruby/object:Gem::Version
190
+ version: '0'
191
+ required_rubygems_version: !ruby/object:Gem::Requirement
192
+ none: false
193
+ requirements:
194
+ - - ! '>='
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements: []
198
+ rubyforge_project: ip_tracker
199
+ rubygems_version: 1.6.2
200
+ signing_key:
201
+ specification_version: 3
202
+ summary: A command line utility to interface with IpMe
203
+ test_files: []