emissary 1.3.20 → 1.3.21
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +1 -0
- data/LICENSE +203 -0
- data/Manifest.txt +38 -0
- data/README.txt +54 -0
- data/Rakefile +98 -0
- data/VERSION.yml +4 -0
- data/lib/emissary.rb +226 -0
- data/lib/emissary/agent.rb +61 -0
- data/lib/emissary/agent/emissary.rb +163 -0
- data/lib/emissary/agent/error.rb +26 -0
- data/lib/emissary/agent/file.rb +26 -0
- data/lib/emissary/agent/gem.rb +42 -0
- data/lib/emissary/agent/mysql.rb +230 -0
- data/lib/emissary/agent/ping.rb +37 -0
- data/lib/emissary/agent/proxy.rb +26 -0
- data/lib/emissary/agent/rabbitmq.rb +233 -0
- data/lib/emissary/agent/sshkeys.rb +152 -0
- data/lib/emissary/agent/stats.rb +96 -0
- data/lib/emissary/agent/test.rb +40 -0
- data/lib/emissary/config.rb +231 -0
- data/lib/emissary/core_ext/blank.rb +60 -0
- data/lib/emissary/core_ext/misc_object.rb +21 -0
- data/lib/emissary/core_ext/symbolize.rb +33 -0
- data/lib/emissary/daemon.rb +404 -0
- data/lib/emissary/errors.rb +106 -0
- data/lib/emissary/gem_helper.rb +183 -0
- data/lib/emissary/identity.rb +183 -0
- data/lib/emissary/identity/ec2.rb +64 -0
- data/lib/emissary/identity/unix.rb +67 -0
- data/lib/emissary/inifile.rb +148 -0
- data/lib/emissary/logger.rb +130 -0
- data/lib/emissary/message.rb +217 -0
- data/lib/emissary/operator.rb +275 -0
- data/lib/emissary/operator/amqp.rb +205 -0
- data/lib/emissary/server.rb +98 -0
- data/lib/emissary/servolux.rb +75 -0
- metadata +42 -6
@@ -0,0 +1,183 @@
|
|
1
|
+
# Copyright 2010 The New York Times
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
require 'rubygems/spec_fetcher'
|
17
|
+
require 'rubygems/dependency_installer'
|
18
|
+
require 'rubygems/uninstaller'
|
19
|
+
|
20
|
+
module Emissary
|
21
|
+
class GemHelper
|
22
|
+
attr_reader :name, :version
|
23
|
+
|
24
|
+
def initialize(name)
|
25
|
+
@name = name
|
26
|
+
@version = current_version
|
27
|
+
end
|
28
|
+
|
29
|
+
def version
|
30
|
+
(v = @version.to_s.delete('<>!=~ ').strip).empty? ? 0 : v
|
31
|
+
end
|
32
|
+
|
33
|
+
def normalize_version value
|
34
|
+
case value
|
35
|
+
when Gem::Requirement, Gem::Version
|
36
|
+
value
|
37
|
+
|
38
|
+
when /^(<|<=|=|!=|>|>=|~>)\s*[0-9.]+$/
|
39
|
+
Gem::Requirement.new value
|
40
|
+
|
41
|
+
when /^[0-9.]+$/, String, Fixnum
|
42
|
+
Gem::Version.new value
|
43
|
+
|
44
|
+
when :current
|
45
|
+
Gem::Version.new(self.version)
|
46
|
+
|
47
|
+
when :all, :any, :installed, :available
|
48
|
+
Gem::Requirement.default
|
49
|
+
|
50
|
+
when :newer_than_me, :latest, :newest
|
51
|
+
Gem::Requirement.new "> #{self.version}"
|
52
|
+
|
53
|
+
when :older_than_me
|
54
|
+
Gem::Requirement.new "< #{self.version}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def updateable?
|
59
|
+
installed? && !versions(:newer_than_me).empty?
|
60
|
+
end
|
61
|
+
|
62
|
+
def is_a_provider? version = :current
|
63
|
+
return false unless (spec = Gem.source_index.find_name(@name, normalize_version(version)).first)
|
64
|
+
!Gem::DependencyList.from_source_index(Gem.source_index).ok_to_remove?(spec.full_name)
|
65
|
+
end
|
66
|
+
alias :have_dependents? :is_a_provider?
|
67
|
+
alias :has_dependents? :is_a_provider?
|
68
|
+
|
69
|
+
def removable? version = :current, ignore_dependents = false
|
70
|
+
installed?(version) && (!!ignore_dependents || !have_dependents?(version))
|
71
|
+
end
|
72
|
+
|
73
|
+
def installable? version = :any
|
74
|
+
!versions(:any).empty? && !installed?(version) && normalize_version(version) > @version
|
75
|
+
end
|
76
|
+
|
77
|
+
def installed? version = :any
|
78
|
+
!Gem.source_index.search(Gem::Dependency.new(name, normalize_version(version))).empty?
|
79
|
+
end
|
80
|
+
|
81
|
+
def current_version
|
82
|
+
return Gem::Version.new(0) unless installed?
|
83
|
+
specs = Gem.source_index.search(Gem::Dependency.new(name, normalize_version(:newest)))
|
84
|
+
specs.map { |spec| spec.version }.sort{ |a,b| a <=> b }.first
|
85
|
+
end
|
86
|
+
|
87
|
+
def versions which = :all
|
88
|
+
# don't include others for latest/newest - do include otherwise
|
89
|
+
others = [:latest, :newest ].include?(which) ? false : true
|
90
|
+
dependency = Gem::Dependency.new(name, normalize_version(which))
|
91
|
+
list = Gem::SpecFetcher.fetcher.find_matching(dependency, others).map do |spec, source_uri|
|
92
|
+
_, version = spec
|
93
|
+
[version, source_uri]
|
94
|
+
end.sort { |a,b| b[0] <=> a[0] }
|
95
|
+
|
96
|
+
which != :installed ? list : list.select { |v| installed? v[0] }
|
97
|
+
end
|
98
|
+
|
99
|
+
def dependents version = :current
|
100
|
+
specs = Gem.source_index.find_name(@name, normalize_version(version))
|
101
|
+
specs.inject([]) do |list,spec|
|
102
|
+
list |= spec.dependent_gems
|
103
|
+
list
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def install version = :latest, source = :default
|
108
|
+
return @version unless installable? version
|
109
|
+
|
110
|
+
source = URI.parse(source).to_s rescue :default
|
111
|
+
|
112
|
+
options = {}
|
113
|
+
options[:version], source_uri = case version
|
114
|
+
when :latest
|
115
|
+
ver, uri = versions(:newer_than_me).first
|
116
|
+
[ ver, source == :default ? uri : source ]
|
117
|
+
else
|
118
|
+
ver, uri = versions(:newer_than_me).select { |v| v[0] == normalize_version(version) }.flatten
|
119
|
+
[ ver, source == :default ? uri : source ]
|
120
|
+
end
|
121
|
+
|
122
|
+
raise ArgumentError, "Bad version '#{version.inspect}' - can't install specified version." unless options[:version]
|
123
|
+
|
124
|
+
with_gem_source(source_uri) do
|
125
|
+
installer = Gem::DependencyInstaller.new options
|
126
|
+
installer.install name, options[:version]
|
127
|
+
@version = Gem::Version.new(normalize_version(options[:version]).to_s.delete('<>!=~ '))
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def update version = :latest, source = :default, keep_old = true
|
132
|
+
return false unless updateable?
|
133
|
+
uninstall(@version, false) unless keep_old
|
134
|
+
install version, source
|
135
|
+
end
|
136
|
+
|
137
|
+
def uninstall version = :current, ignore_deps = false, remove_execs = false
|
138
|
+
options = {}
|
139
|
+
options[:ignore] = !!ignore_deps
|
140
|
+
options[:executables] = !!remove_execs
|
141
|
+
|
142
|
+
options[:version] = case version
|
143
|
+
when :current
|
144
|
+
@version
|
145
|
+
when :all
|
146
|
+
options[:all] = true
|
147
|
+
Gem::Requirement.default
|
148
|
+
else
|
149
|
+
versions(:all).select { |v| v[0] == normalize_version(version) }.flatten[0]
|
150
|
+
end
|
151
|
+
|
152
|
+
return true if not installed? version
|
153
|
+
raise ArgumentError, "Cannot uninstall version #{version.inspect} - is it installed? [#{options.inspect}]" unless options[:version]
|
154
|
+
|
155
|
+
unless removable?(version, !!ignore_deps)
|
156
|
+
msg = ['Refusing to uninstall gem required by other gems:']
|
157
|
+
dependents(options[:version]).each do |gem, dep, satlist|
|
158
|
+
msg << " Gem '#{gem.name}-#{gem.version}' depends on '#{dep.name}' (#{dep.requirement})";
|
159
|
+
end
|
160
|
+
raise Exception, msg.join("\n")
|
161
|
+
end
|
162
|
+
|
163
|
+
Gem::Uninstaller.new(name, options).uninstall
|
164
|
+
Gem.refresh
|
165
|
+
@version = Gem::Version.new '0'
|
166
|
+
end
|
167
|
+
alias :remove :uninstall
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def with_gem_source(source)
|
172
|
+
begin
|
173
|
+
original_sources = Gem.sources.dup
|
174
|
+
Gem.sources.replace [source]
|
175
|
+
return yield
|
176
|
+
ensure
|
177
|
+
Gem.sources.replace original_sources
|
178
|
+
Gem.refresh
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
@@ -0,0 +1,183 @@
|
|
1
|
+
# Copyright 2010 The New York Times
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
require 'net/http'
|
17
|
+
require 'socket'
|
18
|
+
|
19
|
+
module Emissary
|
20
|
+
class Identity
|
21
|
+
|
22
|
+
INTERNAL_IDENTITY_GLOB = ::File.join(Emissary::LIBPATH, 'emissary', 'identity', '*.rb')
|
23
|
+
EXTERNAL_IDENTITY_GLOB = ::File.join(Emissary::EXTERNAL_IDENTITIES, '*.rb')
|
24
|
+
|
25
|
+
attr_reader :loaded, :methods
|
26
|
+
alias :loaded? :loaded
|
27
|
+
|
28
|
+
private :initialize
|
29
|
+
|
30
|
+
def initialize
|
31
|
+
@loaded = false
|
32
|
+
@methods = nil
|
33
|
+
@identities = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.instance
|
37
|
+
@@instance ||= self.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.new(*largs)
|
41
|
+
# prevent subclasses from being instantiated directly, except through
|
42
|
+
# the identities() method of this parent class. This class is just a
|
43
|
+
# simple method factory and the children shouldn't need to be accessed
|
44
|
+
# except through the interface provided here.
|
45
|
+
if self == Emissary::Identity
|
46
|
+
raise Exception, "Cannot instantiate singleton class directly - use #{self.name}#instance" unless __caller__ == :instance
|
47
|
+
else
|
48
|
+
klass = self.name.split(/::/)[0..-2].join '::'; subklass = self.name.split(/::/).last
|
49
|
+
raise Exception, "Cannot instantiate '#{klass}' subclass '#{subklass}' directly" unless __caller__ == :identities
|
50
|
+
end
|
51
|
+
|
52
|
+
allocate.instance_eval(<<-EOS, __FILE__, __LINE__)
|
53
|
+
load_identities unless self.class != Emissary::Identity
|
54
|
+
self
|
55
|
+
EOS
|
56
|
+
end
|
57
|
+
|
58
|
+
# Delegation Factory - Uses registered high priority subclasses
|
59
|
+
# for delegation of method calls, falling back to lower priority
|
60
|
+
# subclasess when the method isn't available or throws a :pass
|
61
|
+
# in the higher priority subclass
|
62
|
+
#
|
63
|
+
def method_missing name, *args
|
64
|
+
# don't perform lookups on subclasses
|
65
|
+
super name, *args unless self.class == Emissary::Identity
|
66
|
+
|
67
|
+
name = name.to_sym
|
68
|
+
unless (@methods||={}).has_key? name
|
69
|
+
method_delegate = value = nil
|
70
|
+
|
71
|
+
# loop through each possible identity, higher priority identities
|
72
|
+
# first (0 == low, > 0 == higher).
|
73
|
+
identities.each do |id,object|
|
74
|
+
value = nil
|
75
|
+
if object.respond_to?(name)
|
76
|
+
method_delegate = catch(:pass) {
|
77
|
+
value = object.__send__(name, *args) # test for :pass request
|
78
|
+
object
|
79
|
+
}
|
80
|
+
break unless method_delegate.nil?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# if we've gone through all the possible delegates, then pass it
|
85
|
+
# up to the superclass (in this case, Object)
|
86
|
+
if method_delegate.nil?
|
87
|
+
return super(name, *args)
|
88
|
+
end
|
89
|
+
|
90
|
+
@methods[name] = method_delegate
|
91
|
+
return value
|
92
|
+
end
|
93
|
+
|
94
|
+
return @methods[name].__send__(name, *args)
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.register name, opts = {}
|
98
|
+
priority = if name != :unix
|
99
|
+
opts[:priority].to_i > 0 ? opts.delete(:priority).to_i : 1
|
100
|
+
else
|
101
|
+
# unix identity always gets zero - though this locks us
|
102
|
+
# into *nix as our base. XXX maybe rethink this?
|
103
|
+
0
|
104
|
+
end
|
105
|
+
|
106
|
+
(self.registry[priority] ||= []) << name
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.exclude names
|
110
|
+
@@exclusions ||= []
|
111
|
+
@@exclusions |= (names.is_a?(String) ? names.split(/\s*,\s*/) : names.to_a.collect { |n| n.to_s.to_sym })
|
112
|
+
end
|
113
|
+
|
114
|
+
# Exclude an identity type when delegating identity method calls
|
115
|
+
#
|
116
|
+
def self.exclusions
|
117
|
+
@@exclusions ||= []
|
118
|
+
@@exclusions.to_a.map! { |e| e.to_sym }
|
119
|
+
end
|
120
|
+
|
121
|
+
def to_s
|
122
|
+
s = ''
|
123
|
+
s << '#<%s:0x%x ' % [ self.class, self.__id__ * 2]
|
124
|
+
s << to_h.inject([]) { |a,(k,v)| a << %Q|@#{k}="#{v}"| }.join(', ')
|
125
|
+
s << '>'
|
126
|
+
end
|
127
|
+
|
128
|
+
# Retrieve a list of all the available identifers through reflection of the subclasses
|
129
|
+
#
|
130
|
+
def identifiers
|
131
|
+
identities.inject([]) do |list,(id,object)|
|
132
|
+
list |= object.public_methods - object.class.superclass.public_methods - self.public_methods; list
|
133
|
+
end.sort.collect { |id| id.to_sym }
|
134
|
+
end
|
135
|
+
|
136
|
+
# Retreive a hash of all identifiers and their values
|
137
|
+
#
|
138
|
+
def to_h
|
139
|
+
Hash[(identifiers).zip(identifiers.collect { |identifier| self.send(identifier) })]
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def self.registry
|
145
|
+
@@registry ||= []
|
146
|
+
end
|
147
|
+
|
148
|
+
# Loads all available identities included with this gem
|
149
|
+
#
|
150
|
+
def load_identities
|
151
|
+
return unless not !!loaded?
|
152
|
+
|
153
|
+
Dir[INTERNAL_IDENTITY_GLOB, EXTERNAL_IDENTITY_GLOB].reject do |id|
|
154
|
+
self.class.exclusions.include?("/#{id.to_s.downcase}.rb")
|
155
|
+
end.each do |file|
|
156
|
+
::Emissary.logger.info "Loading Identity: #{file}"
|
157
|
+
require File.expand_path(file)
|
158
|
+
end
|
159
|
+
|
160
|
+
@loaded = true
|
161
|
+
end
|
162
|
+
|
163
|
+
# Generates list of possible identities after removing registered exclusions
|
164
|
+
# and then ordering them highest priority (> 0) to lowest priority (== 0)
|
165
|
+
#
|
166
|
+
def identities
|
167
|
+
@@identities ||= begin
|
168
|
+
self.class.registry.reverse.flatten.reject do |id|
|
169
|
+
id.nil? || self.class.exclusions.include?(id)
|
170
|
+
end.inject([]) do |a,id|
|
171
|
+
a << [ id.to_s, ::Emissary.klass_const("Emissary::Identity::#{id.to_s.capitalize}").new ]; a
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
if __FILE__ == $0
|
179
|
+
puts "-----> Name: " + (Emissary.identity.name rescue 'Could not acquire name')
|
180
|
+
puts "-----> P IP: " + (Emissary.identity.public_ip rescue 'Could not acquire public ip')
|
181
|
+
puts "-----> L IP: " + (Emissary.identity.local_ip rescue 'Could not acquire local_ip')
|
182
|
+
puts "-- Identity: " + (Emissary.identity.to_s rescue 'identity not set...')
|
183
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright 2010 The New York Times
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
require 'net/http'
|
17
|
+
require 'socket'
|
18
|
+
|
19
|
+
module Emissary
|
20
|
+
class Identity::Ec2 < Identity
|
21
|
+
register :ec2, :priority => 25
|
22
|
+
|
23
|
+
QUERY_IP = '169.254.169.254'
|
24
|
+
INSTANCE_ID_PATH = '/latest/meta-data/instance-id'
|
25
|
+
LOCAL_IPV4_PATH = '/latest/meta-data/local-ipv4'
|
26
|
+
PUBLIC_IPV4_PATH = '/latest/meta-data/public-ipv4'
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@instance_id = nil
|
30
|
+
@local_ipv4 = nil
|
31
|
+
@public_ipv4 = nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def instance_id
|
35
|
+
@instance_id ||= get(INSTANCE_ID_PATH)
|
36
|
+
end
|
37
|
+
|
38
|
+
alias :queue_name :instance_id
|
39
|
+
|
40
|
+
def local_ip
|
41
|
+
@local_ipv4 ||= get(LOCAL_IPV4_PATH)
|
42
|
+
end
|
43
|
+
|
44
|
+
def public_ip
|
45
|
+
@public_ipv4 ||= get(PUBLIC_IPV4_PATH)
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def get uri
|
51
|
+
begin
|
52
|
+
http = Net::HTTP.new(QUERY_IP)
|
53
|
+
http.open_timeout = 0.5
|
54
|
+
http.read_timeout = 0.5
|
55
|
+
http.start do |http|
|
56
|
+
http.get(uri).body
|
57
|
+
end
|
58
|
+
rescue Exception => e
|
59
|
+
::Emissary.logger.debug "Passing identifier request to next identity handler due to failed HTTP#get request: #{e.class.name}: #{e.message}"
|
60
|
+
throw :pass
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# Copyright 2010 The New York Times
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
#
|
15
|
+
#
|
16
|
+
module Emissary
|
17
|
+
class Identity::Unix < Identity
|
18
|
+
# defaults to priority 0 (which means, it's the last option if nothing else takes it)
|
19
|
+
register :unix
|
20
|
+
|
21
|
+
def name
|
22
|
+
@hostname ||= `uname -n`.strip
|
23
|
+
end
|
24
|
+
|
25
|
+
alias :queue_name :name
|
26
|
+
|
27
|
+
def roles
|
28
|
+
ENV['ROLES'] || ''
|
29
|
+
end
|
30
|
+
|
31
|
+
def instance_id
|
32
|
+
ENV['INSTANCE_ID'] || -1
|
33
|
+
end
|
34
|
+
|
35
|
+
def server_id
|
36
|
+
ENV['SERVER_ID'] || -1
|
37
|
+
end
|
38
|
+
|
39
|
+
def cluster_id
|
40
|
+
ENV['CLUSTER_ID'] || -1
|
41
|
+
end
|
42
|
+
|
43
|
+
def account_id
|
44
|
+
ENV['ACCOUNT_ID'] || -1
|
45
|
+
end
|
46
|
+
|
47
|
+
def local_ip
|
48
|
+
@ip_local ||= begin
|
49
|
+
# turn off reverse DNS resolution temporarily
|
50
|
+
orig, Socket.do_not_reverse_lookup = Socket.do_not_reverse_lookup, true
|
51
|
+
@ip_local = UDPSocket.open { |s| s.connect IP_CHECK_DOMAIN, 1; s.addr.last }
|
52
|
+
rescue
|
53
|
+
nil
|
54
|
+
ensure
|
55
|
+
Socket.do_not_reverse_lookup = orig rescue nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def public_ip
|
60
|
+
@ip_public ||= begin
|
61
|
+
Net::HTTP.get(URI.parse(IP_CHECK_URL)).gsub(/.*?((\d{1,3}\.){3}\d{1,3}).*/m, '\1')
|
62
|
+
rescue
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|