iparty 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +187 -0
- data/Rakefile +62 -0
- data/exe/iparty +10 -0
- data/lib/iparty/address.rb +146 -0
- data/lib/iparty/cli/application/actions.rb +56 -0
- data/lib/iparty/cli/application/appinfo.rb +107 -0
- data/lib/iparty/cli/application/irb_context.rb +41 -0
- data/lib/iparty/cli/application/options.rb +133 -0
- data/lib/iparty/cli/application.rb +258 -0
- data/lib/iparty/cli/colorize.rb +39 -0
- data/lib/iparty/cli/formatter.rb +169 -0
- data/lib/iparty/config.rb +141 -0
- data/lib/iparty/max_mind/database.rb +216 -0
- data/lib/iparty/max_mind/eager_reader.rb +33 -0
- data/lib/iparty/max_mind/lazy_reader.rb +47 -0
- data/lib/iparty/max_mind/result.rb +205 -0
- data/lib/iparty/max_mind.rb +93 -0
- data/lib/iparty/railtie.rb +22 -0
- data/lib/iparty/rake_task.rb +121 -0
- data/lib/iparty/version.rb +5 -0
- data/lib/iparty.rb +71 -0
- metadata +125 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
require_relative "max_mind/database"
|
|
6
|
+
|
|
7
|
+
module IParty
|
|
8
|
+
class MaxMind
|
|
9
|
+
class << self
|
|
10
|
+
def db edition
|
|
11
|
+
edition = "GeoLite2-#{edition}" if edition.is_a?(Symbol)
|
|
12
|
+
file = IParty.config.directory.join("#{edition}.mmdb")
|
|
13
|
+
return unless file.exist?
|
|
14
|
+
|
|
15
|
+
if ctn = IParty.config.singletons
|
|
16
|
+
ctn = ctn.call if ctn.is_a?(Proc)
|
|
17
|
+
return ctn.fetch(edition) if ctn.key?(edition)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
Database.new(file, reader: IParty.config.eager_load ? EagerReader : LazyReader).tap do |dbi|
|
|
21
|
+
ctn[edition] ||= dbi if ctn
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def lookup edition, *args, close: true, **kw
|
|
26
|
+
return unless mmdb = db(edition)
|
|
27
|
+
|
|
28
|
+
mmdb.lookup(*args, **kw).tap do
|
|
29
|
+
mmdb.close if close && !IParty.config.singletons
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def with_transactional_update_directory
|
|
34
|
+
temp_dir = IParty.config.directory.join(".updating")
|
|
35
|
+
return yield(temp_dir) if @in_transaction
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
@in_transaction = true
|
|
39
|
+
temp_dir.mkpath
|
|
40
|
+
yield(temp_dir)
|
|
41
|
+
ensure
|
|
42
|
+
@in_transaction = false
|
|
43
|
+
temp_dir.glob("*.mmdb").each do |file|
|
|
44
|
+
Database.new(file, reader: LazyReader)
|
|
45
|
+
FileUtils.mv(file, IParty.config.directory)
|
|
46
|
+
rescue Database::InvalidFileFormatError
|
|
47
|
+
warn "iparty: ignoring invalid mmdb file: #{file}"
|
|
48
|
+
end
|
|
49
|
+
FileUtils.rm_rf(temp_dir)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def fetch_db_files! fetch_when = :always, verbose: false
|
|
54
|
+
with_transactional_update_directory do
|
|
55
|
+
IParty.config.editions.each do |edition|
|
|
56
|
+
fetch_db_file!(edition, fetch_when, verbose: verbose)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def fetch_db_file_reason file, fetch_when = :always
|
|
62
|
+
return fetch_when if fetch_when == :always
|
|
63
|
+
return :missing unless file.exist?
|
|
64
|
+
|
|
65
|
+
begin
|
|
66
|
+
Database.new(file, reader: LazyReader)
|
|
67
|
+
rescue Database::InvalidFileFormatError
|
|
68
|
+
return :invalid
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
return unless fetch_when.is_a?(Numeric)
|
|
72
|
+
|
|
73
|
+
ctime = file.ctime
|
|
74
|
+
age = Time.now - ctime
|
|
75
|
+
:expired unless fetch_when > age
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def fetch_db_file! edition, fetch_when = :always, verbose: false
|
|
79
|
+
target_file = IParty.config.directory.join("#{edition}.mmdb")
|
|
80
|
+
return target_file unless reason = fetch_db_file_reason(target_file, fetch_when)
|
|
81
|
+
|
|
82
|
+
with_transactional_update_directory do |temp_dir|
|
|
83
|
+
warn "iparty: fetching #{reason}: #{target_file.basename}" if verbose
|
|
84
|
+
IParty.config.url_to_mmdb.call(
|
|
85
|
+
IParty.config.mirror.gsub(":edition", edition),
|
|
86
|
+
temp_dir,
|
|
87
|
+
IParty.config,
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IParty
|
|
4
|
+
class Railtie < Rails::Railtie
|
|
5
|
+
railtie_name :iparty
|
|
6
|
+
|
|
7
|
+
rake_tasks do
|
|
8
|
+
require "iparty/rake_task"
|
|
9
|
+
IParty::RakeTask.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
config.before_configuration do
|
|
13
|
+
IParty.config.directory = IParty.env_value("IPARTY_DIRECTORY", nil) do |dir|
|
|
14
|
+
if dir && !dir.empty?
|
|
15
|
+
Pathname.new(dir)
|
|
16
|
+
else
|
|
17
|
+
Rails.root.join("vendor", "maxmind")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rake"
|
|
4
|
+
require "rake/tasklib"
|
|
5
|
+
|
|
6
|
+
module IParty
|
|
7
|
+
class RakeTask < ::Rake::TaskLib
|
|
8
|
+
attr_accessor :name, :verbose
|
|
9
|
+
|
|
10
|
+
def initialize(name = :iparty)
|
|
11
|
+
super()
|
|
12
|
+
|
|
13
|
+
@name = name
|
|
14
|
+
@verbose = true
|
|
15
|
+
|
|
16
|
+
yield self if block_given?
|
|
17
|
+
define_update
|
|
18
|
+
define_fetch
|
|
19
|
+
define_status
|
|
20
|
+
define_config
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parse_duration input, default = :missing
|
|
24
|
+
return default unless input.is_a?(String)
|
|
25
|
+
|
|
26
|
+
if input.match?(/\A\d+\z/)
|
|
27
|
+
input.to_i
|
|
28
|
+
elsif asm = input.match(/\A(\d+)\.(second|minute|hour|day|week|month|year)s?\z/)
|
|
29
|
+
ActiveSupport::Duration.send(:"#{asm[2]}s", asm[1].to_i)
|
|
30
|
+
else
|
|
31
|
+
default
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# iparty:update
|
|
36
|
+
def define_update
|
|
37
|
+
namespace(name) do
|
|
38
|
+
desc "Updates geoip mmdb-files"
|
|
39
|
+
task :update do
|
|
40
|
+
Rake.application.lookup("environment")&.invoke
|
|
41
|
+
|
|
42
|
+
IParty::MaxMind.fetch_db_files!(verbose: @verbose)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# iparty:fetch
|
|
48
|
+
# iparty:fetch[86400]
|
|
49
|
+
# iparty:fetch[14.days]
|
|
50
|
+
def define_fetch
|
|
51
|
+
namespace(name) do
|
|
52
|
+
desc "Fetches missing or expired geoip mmdb-files (optional numeric max_age)"
|
|
53
|
+
task :fetch, [:max_age] do |task, args|
|
|
54
|
+
Rake.application.lookup("environment")&.invoke
|
|
55
|
+
|
|
56
|
+
IParty::MaxMind.fetch_db_files!(parse_duration(args[:max_age]), verbose: @verbose)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# iparty:status
|
|
62
|
+
def define_status
|
|
63
|
+
namespace(name) do
|
|
64
|
+
desc "Show status of geoip mmdb-files (optional numeric max_age)"
|
|
65
|
+
task :status, [:max_age] do |task, args|
|
|
66
|
+
Rake.application.lookup("environment")&.invoke
|
|
67
|
+
|
|
68
|
+
max_age = parse_duration(args[:max_age])
|
|
69
|
+
|
|
70
|
+
success = IParty.config.editions.map do |edition|
|
|
71
|
+
file = IParty.config.directory.join("#{edition}.mmdb")
|
|
72
|
+
reason = IParty::MaxMind.fetch_db_file_reason(file, max_age)
|
|
73
|
+
|
|
74
|
+
stat_string = if file.exist?
|
|
75
|
+
ctime = file.ctime
|
|
76
|
+
age = Time.now - ctime
|
|
77
|
+
days = (age / 86_400).floor
|
|
78
|
+
hours = ((age / 3_600) % 24).floor
|
|
79
|
+
minutes = ((age / 60) % 60).floor
|
|
80
|
+
age_string = [
|
|
81
|
+
("#{days}d" if days > 0),
|
|
82
|
+
("%02d:%02d" % [hours, minutes] if hours > 0 || minutes > 0),
|
|
83
|
+
].compact.join(" ")
|
|
84
|
+
age_string = "#{age.floor}s" if age_string.empty?
|
|
85
|
+
"[age: #{age_string}, ctime: #{ctime}]"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
puts [reason&.upcase || "OK", stat_string, file].compact.join(" ")
|
|
89
|
+
!reason
|
|
90
|
+
end.all?
|
|
91
|
+
|
|
92
|
+
exit(1) unless success
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# iparty:config
|
|
98
|
+
# iparty:config[inspect]
|
|
99
|
+
# iparty:config[json]
|
|
100
|
+
def define_config
|
|
101
|
+
namespace(name) do
|
|
102
|
+
desc "Shows effective IParty config (including license_key, optional format json/inspect)"
|
|
103
|
+
task :config, [:format] do |task, args|
|
|
104
|
+
Rake.application.lookup("environment")&.invoke
|
|
105
|
+
|
|
106
|
+
case args[:format]
|
|
107
|
+
when "json"
|
|
108
|
+
require "json"
|
|
109
|
+
puts JSON.pretty_generate(IParty.config.to_h)
|
|
110
|
+
when "inspect"
|
|
111
|
+
puts IParty.config.inspect
|
|
112
|
+
else
|
|
113
|
+
IParty.config.each_pair do |key, value|
|
|
114
|
+
puts "#{key.to_s.rjust(16)}: #{value.inspect}"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
data/lib/iparty.rb
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "tmpdir"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "forwardable"
|
|
6
|
+
|
|
7
|
+
# rubocop:disable Naming/MethodName -- we party!
|
|
8
|
+
def IParty *args, **kw
|
|
9
|
+
IParty.normalize(*args, **kw)
|
|
10
|
+
end
|
|
11
|
+
# rubocop:enable Naming/MethodName
|
|
12
|
+
|
|
13
|
+
module IParty
|
|
14
|
+
class Error < StandardError; end
|
|
15
|
+
|
|
16
|
+
require_relative "iparty/version"
|
|
17
|
+
require_relative "iparty/config"
|
|
18
|
+
require_relative "iparty/address"
|
|
19
|
+
require_relative "iparty/max_mind"
|
|
20
|
+
require_relative "iparty/railtie" if defined?(Rails)
|
|
21
|
+
|
|
22
|
+
@config = default_config
|
|
23
|
+
|
|
24
|
+
def self.fetch_db_files! *args, **kw
|
|
25
|
+
IParty::MaxMind.fetch_db_files!(*args, **kw)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.normalize input, family = nil, native: false, **kw
|
|
29
|
+
return unless input
|
|
30
|
+
return if input.is_a?(String) && input.match?(/\A[[:space:]]*\z/)
|
|
31
|
+
return if input.respond_to?(:empty?) && input.empty?
|
|
32
|
+
|
|
33
|
+
addr = case input
|
|
34
|
+
when String
|
|
35
|
+
Address.new(input.strip, **kw)
|
|
36
|
+
when IPAddr
|
|
37
|
+
Address.new(input.to_i, input.family)
|
|
38
|
+
when Integer
|
|
39
|
+
family ||= input > (2**32) - 1 ? Socket::AF_INET6 : Socket::AF_INET
|
|
40
|
+
Address.new(input, family, **kw)
|
|
41
|
+
else
|
|
42
|
+
raise IPAddr::InvalidAddressError, "invalid address: #{input}"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
native ? addr.native : addr
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.classify input
|
|
49
|
+
normalize(input).type
|
|
50
|
+
rescue IPAddr::Error
|
|
51
|
+
:invalid
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def self.expand_hostnames *ips_or_hosts
|
|
55
|
+
ips_or_hosts.flatten.flat_map do |name|
|
|
56
|
+
next name unless name.is_a?(String) && !name.include?(":") && name.match?(/[a-z]/i)
|
|
57
|
+
|
|
58
|
+
if defined?(Resolv)
|
|
59
|
+
Resolv.getaddresses(name)
|
|
60
|
+
elsif defined?(Addrinfo)
|
|
61
|
+
begin
|
|
62
|
+
Addrinfo.getaddrinfo(name, nil).map(&:ip_address).uniq
|
|
63
|
+
rescue Socket::ResolutionError
|
|
64
|
+
[]
|
|
65
|
+
end
|
|
66
|
+
else
|
|
67
|
+
raise "neither resolv nor addrinfo is available (gem install resolv)"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: iparty
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Sven Pachnit
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: fileutils
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: forwardable
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: optparse
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: tmpdir
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
68
|
+
description: Makes (geo) IP fun again! Geo, v6 significance and more.
|
|
69
|
+
email:
|
|
70
|
+
- sven@bmonkeys.net
|
|
71
|
+
executables:
|
|
72
|
+
- iparty
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- CHANGELOG.md
|
|
77
|
+
- LICENSE.txt
|
|
78
|
+
- README.md
|
|
79
|
+
- Rakefile
|
|
80
|
+
- exe/iparty
|
|
81
|
+
- lib/iparty.rb
|
|
82
|
+
- lib/iparty/address.rb
|
|
83
|
+
- lib/iparty/cli/application.rb
|
|
84
|
+
- lib/iparty/cli/application/actions.rb
|
|
85
|
+
- lib/iparty/cli/application/appinfo.rb
|
|
86
|
+
- lib/iparty/cli/application/irb_context.rb
|
|
87
|
+
- lib/iparty/cli/application/options.rb
|
|
88
|
+
- lib/iparty/cli/colorize.rb
|
|
89
|
+
- lib/iparty/cli/formatter.rb
|
|
90
|
+
- lib/iparty/config.rb
|
|
91
|
+
- lib/iparty/max_mind.rb
|
|
92
|
+
- lib/iparty/max_mind/database.rb
|
|
93
|
+
- lib/iparty/max_mind/eager_reader.rb
|
|
94
|
+
- lib/iparty/max_mind/lazy_reader.rb
|
|
95
|
+
- lib/iparty/max_mind/result.rb
|
|
96
|
+
- lib/iparty/railtie.rb
|
|
97
|
+
- lib/iparty/rake_task.rb
|
|
98
|
+
- lib/iparty/version.rb
|
|
99
|
+
homepage: https://github.com/2called-chaos/iparty
|
|
100
|
+
licenses:
|
|
101
|
+
- MIT
|
|
102
|
+
metadata:
|
|
103
|
+
allowed_push_host: https://rubygems.org
|
|
104
|
+
homepage_uri: https://github.com/2called-chaos/iparty/blob/master/README.md
|
|
105
|
+
source_code_uri: https://github.com/2called-chaos/iparty
|
|
106
|
+
changelog_uri: https://github.com/2called-chaos/iparty/blob/master/CHANGELOG.md
|
|
107
|
+
rubygems_mfa_required: 'true'
|
|
108
|
+
rdoc_options: []
|
|
109
|
+
require_paths:
|
|
110
|
+
- lib
|
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
|
+
requirements:
|
|
113
|
+
- - ">="
|
|
114
|
+
- !ruby/object:Gem::Version
|
|
115
|
+
version: 3.2.0
|
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
117
|
+
requirements:
|
|
118
|
+
- - ">="
|
|
119
|
+
- !ruby/object:Gem::Version
|
|
120
|
+
version: '0'
|
|
121
|
+
requirements: []
|
|
122
|
+
rubygems_version: 4.0.6
|
|
123
|
+
specification_version: 4
|
|
124
|
+
summary: Makes (geo) IP fun again!
|
|
125
|
+
test_files: []
|