ip_tracker 0.0.1
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/.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: []
|