ip_tracker 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +20 -0
- data/Guardfile +9 -0
- data/Rakefile +1 -0
- data/bin/ipme +5 -0
- data/ip_tracker.gemspec +34 -0
- data/lib/ip_tracker/cli/methods/register.rb +24 -0
- data/lib/ip_tracker/cli/methods/sync.rb +44 -0
- data/lib/ip_tracker/cli/methods/update.rb +21 -0
- data/lib/ip_tracker/cli.rb +25 -0
- data/lib/ip_tracker/client/authentication.rb +18 -0
- data/lib/ip_tracker/client/connection.rb +19 -0
- data/lib/ip_tracker/client/errors.rb +6 -0
- data/lib/ip_tracker/client/request.rb +23 -0
- data/lib/ip_tracker/client.rb +38 -0
- data/lib/ip_tracker/config.rb +32 -0
- data/lib/ip_tracker/sync_daemon.rb +64 -0
- data/lib/ip_tracker/version.rb +3 -0
- data/lib/ip_tracker.rb +13 -0
- data/spec/fixtures/register_failure.txt +9 -0
- data/spec/fixtures/register_invalid.txt +9 -0
- data/spec/fixtures/register_success.txt +9 -0
- data/spec/fixtures/register_taken.txt +9 -0
- data/spec/fixtures/update_failed.txt +9 -0
- data/spec/fixtures/update_invalid.txt +9 -0
- data/spec/fixtures/update_success.txt +9 -0
- data/spec/ip_tracker/cli/methods/register_spec.rb +109 -0
- data/spec/ip_tracker/cli/methods/sync_spec.rb +76 -0
- data/spec/ip_tracker/cli/methods/update_spec.rb +48 -0
- data/spec/ip_tracker/client_spec.rb +92 -0
- data/spec/ip_tracker/config_spec.rb +45 -0
- data/spec/ip_tracker/sync_daemon_spec.rb +30 -0
- data/spec/spec_helper.rb +54 -0
- metadata +203 -0
data/.gitignore
ADDED
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
data/ip_tracker.gemspec
ADDED
@@ -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,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
|
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,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
|
data/spec/spec_helper.rb
ADDED
@@ -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: []
|