iparty 0.1.1 → 0.1.2
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 +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +1 -1
- data/docs/benchmark.md +40 -0
- data/docs/cli/README.md +132 -0
- data/docs/cli/action_cookbook/show_my_remote_ips.rb +30 -0
- data/docs/configuration.md +170 -0
- data/docs/exceptions.md +22 -0
- data/docs/maxmind_result.md +82 -0
- data/docs/mmdb_download.md +84 -0
- data/lib/iparty/address.rb +9 -1
- data/lib/iparty/config.rb +13 -0
- data/lib/iparty/max_mind/database.rb +8 -3
- data/lib/iparty/max_mind/result.rb +5 -5
- data/lib/iparty/rake_task.rb +8 -1
- data/lib/iparty/version.rb +1 -1
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5f040fd93232947aa2be967cac0ad59617cc24e4751c14908466e221d4830fc6
|
|
4
|
+
data.tar.gz: 4b34b52ee226d40b306da7d8dcf7f576ceb37c98d2695c1e2a78db43703ba40a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1f319c5dbfcad4f51192b750a973f1de60dbee54db84f176aa67ed921f883770eaf61fb82f1b32a837e93072eb0a10f562bed4e2743cf5df3a4936e957bcf1b1
|
|
7
|
+
data.tar.gz: 67f50c5df51c1c1e06266c780d5b27163b9ee5a7bb45c22f8c846c4de8baa6c8f083db174d09690211780b8edcbc13bb0196acc7355e6e8f24ee18f75d0b7e4a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.1.2] - 2026-05-27
|
|
4
|
+
|
|
5
|
+
* Include documentation in gem package
|
|
6
|
+
* Automatically set a network for localhost
|
|
7
|
+
* Add config option (proc) transform_result
|
|
8
|
+
* iso_code fallbacks to code and vice versa so you don't have to think about it (continent has code and rest has iso_code).
|
|
9
|
+
This only applies to method access, `[:code]` does not fallback.
|
|
10
|
+
* Add alias `timezone` for `time_zone`
|
|
11
|
+
* Memoize annotations
|
|
12
|
+
* Add `tag?` helper for annotations
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
3
16
|
## [0.1.1] - 2026-05-26
|
|
4
17
|
|
|
5
18
|
* Add missing pathname dependency (stdlib)
|
data/README.md
CHANGED
data/docs/benchmark.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# IParty
|
|
2
|
+
|
|
3
|
+
## Benchmark
|
|
4
|
+
|
|
5
|
+
This is the result of `script/benchmark.rb` on my M1.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
RSS-before[uncached]: 30.31 MB
|
|
9
|
+
ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [arm64-darwin21]
|
|
10
|
+
Warming up --------------------------------------
|
|
11
|
+
uncached 145.000 i/100ms
|
|
12
|
+
Calculating -------------------------------------
|
|
13
|
+
uncached 1.448k (± 1.9%) i/s (690.59 μs/i) - 14.500k in 10.017464s
|
|
14
|
+
RSS-after[uncached]: 48.75 MB
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
RSS-before[singletons]: 29.77 MB
|
|
18
|
+
ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [arm64-darwin21]
|
|
19
|
+
Warming up --------------------------------------
|
|
20
|
+
singletons 162.000 i/100ms
|
|
21
|
+
Calculating -------------------------------------
|
|
22
|
+
singletons 1.565k (± 8.0%) i/s (638.87 μs/i) - 15.552k in 10.006033s
|
|
23
|
+
RSS-after[singletons]: 31.27 MB
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
RSS-before[eager_load]: 30.20 MB
|
|
27
|
+
ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [arm64-darwin21]
|
|
28
|
+
Warming up --------------------------------------
|
|
29
|
+
eager_load 500.000 i/100ms
|
|
30
|
+
Calculating -------------------------------------
|
|
31
|
+
eager_load 4.940k (± 4.1%) i/s (202.43 μs/i) - 49.500k in 10.038651s
|
|
32
|
+
RSS-after[eager_load]: 112.73 MB
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Personal conclusion
|
|
36
|
+
|
|
37
|
+
I personally would rather not have mmdb-freshness tied to app-boot.
|
|
38
|
+
Hence I personally use no singletons unless I heavily use IParty in which case I would use the
|
|
39
|
+
CurrentAttributes way of caching non-eagerly-loaded instances per request with an additional per-request ipcache.
|
|
40
|
+
See documentation for more details.
|
data/docs/cli/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# IParty CLI utility
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
Usage: iparty <IP|host...> [options]
|
|
5
|
+
|
|
6
|
+
# Application options
|
|
7
|
+
-a, --[no-]all full non-summarized output
|
|
8
|
+
-f, --format <FORMATTER> formatter (pretty|json|off) or template string [default: pretty]
|
|
9
|
+
-l, --language <LANG> limit output to language (or all) [default: en]
|
|
10
|
+
-r, --[no-]resolve resolve hosts and include hostnames in data (requires resolv)
|
|
11
|
+
-o, --only key,deep.key,*country* list of key expressions (grep on full key)
|
|
12
|
+
-e, --except key,deep.key,sub* list of key expressions (grep_v on full key)
|
|
13
|
+
** matches .*
|
|
14
|
+
* matches [^.]*
|
|
15
|
+
--[no-]stdin read from stdin (one IP per line)
|
|
16
|
+
|
|
17
|
+
# (Custom) actions
|
|
18
|
+
-d, --dispatch ACTION Dispatch given action, you may add your own
|
|
19
|
+
--irb IRB repl with iparty context and helpers
|
|
20
|
+
|
|
21
|
+
# MMDB actions
|
|
22
|
+
--mmdb-status Show mmdb file status
|
|
23
|
+
--mmdb-fetch Fetch missing mmdb-editions
|
|
24
|
+
--mmdb-update Update all mmdb-editions
|
|
25
|
+
|
|
26
|
+
# General options
|
|
27
|
+
-h, --help Shows this help
|
|
28
|
+
-v, --version Shows version and mmdb info (and config with --debug)
|
|
29
|
+
-m, --[no-]monochrome Don't or do colorize output
|
|
30
|
+
--[no-]debug Enable debug, raise exceptions and print config with -v
|
|
31
|
+
--no-rc Do not eval config.rb
|
|
32
|
+
|
|
33
|
+
The current config directory is /Users/chaos/.iparty
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## Configuration
|
|
39
|
+
|
|
40
|
+
IParty CLI attempts to load a config file (`config.rb`) in `ENV.fetch("IPARTY_CFGDIR", "~/.iparty")` by default, `--no-rc` will skip this.
|
|
41
|
+
|
|
42
|
+
You can configure application defaults or define custom formatters, actions, or do whatever really. The file will be eval'd in the application context.
|
|
43
|
+
|
|
44
|
+
Also look at the action cookbook in `docs/cli/action_cookbook`.
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# Create this file as ~/.iparty/config.rb
|
|
48
|
+
# This file is eval'd in the application object's context after it's initialized!
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# For IParty options refer to the IParty documentation.
|
|
52
|
+
# https://github.com/2called-chaos/iparty/blob/master/README.md
|
|
53
|
+
|
|
54
|
+
IParty.config.annotate "1.1.1.1", "1.0.0.1", tags: %i[cloudflare_dns]
|
|
55
|
+
IParty.config.annotate_tag %i[loopback], "127.0.0.1/8", "::1"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# Change defaults (arguments will still override)
|
|
59
|
+
# For CLI options refer to application/options.rb#default_options
|
|
60
|
+
# https://github.com/2called-chaos/iparty/blob/master/lib/iparty/cli/application/options.rb
|
|
61
|
+
|
|
62
|
+
# @opts[:summarize] = false
|
|
63
|
+
# @opts[:except] += [
|
|
64
|
+
# "registered_country",
|
|
65
|
+
# "subdivisions",
|
|
66
|
+
# ]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# =================
|
|
71
|
+
# = Custom Action =
|
|
72
|
+
# =================
|
|
73
|
+
# Also look at the action cookbook in `docs/cli/action_cookbook`!
|
|
74
|
+
|
|
75
|
+
@optparse.separator("\n# Options for --dispatch ban")
|
|
76
|
+
@optparse.on("--reason REASON", String, "ban reason") {|v| @opts[:ban_reason] = v }
|
|
77
|
+
|
|
78
|
+
# iparty -d ban --reason "they suck" 1.2.3.4 c498:81dd:12bc:b812:a2c4:b003:303d:6707
|
|
79
|
+
def dispatch_ban
|
|
80
|
+
each_address do |ip|
|
|
81
|
+
ipp = IParty(ip, significant: false)
|
|
82
|
+
puts "ban #{ipp} (#{ipp.geo.detailed}) because #{@opts[:reason]}" # @todo ban ip
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ====================
|
|
89
|
+
# = Custom Formatter =
|
|
90
|
+
# ====================
|
|
91
|
+
|
|
92
|
+
# Descendants of CLI::Formatter are accessible by their ::id (compared `fmt.id === input`)
|
|
93
|
+
# i.e. self.id = "foo" # --format foo
|
|
94
|
+
# i.e. self.id = /^foo$/
|
|
95
|
+
# i.e. self.id = ->{ _1 == "foo" }
|
|
96
|
+
# If no id is specified it will default to the class name (unhandy but accessible)
|
|
97
|
+
# You can also list all formatters with `-v --debug`
|
|
98
|
+
|
|
99
|
+
# The app will call #format for single IPs and #format_all for multiple (or stdin)
|
|
100
|
+
# but you may handle them the same way.
|
|
101
|
+
|
|
102
|
+
class MyFormatter < IParty::CLI::Formatter
|
|
103
|
+
# self.id = "my"
|
|
104
|
+
|
|
105
|
+
# def setup
|
|
106
|
+
# # optional, called at the end of initialize if responds_to?(:setup)
|
|
107
|
+
# end
|
|
108
|
+
|
|
109
|
+
# return an array of put-able things (false will be skipped)
|
|
110
|
+
def format_all ips, **kw, &to_data
|
|
111
|
+
ips.map.with_index {|ip, index| format(ip, index: index, **kw, &to_data) }
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# return a put-able thing (or false to skip output)
|
|
115
|
+
# to_data will turn an IP into filtered data according to app-args and opts
|
|
116
|
+
def format ip, index: 0, **kw, &to_data
|
|
117
|
+
"[#{index}] #{to_data.call(ip)}"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
class MyJsonFormatter < IParty::CLI::Formatter::JsonFormatter
|
|
122
|
+
self.id = "my_json"
|
|
123
|
+
|
|
124
|
+
def format ip, index: 0, **kw, &to_data
|
|
125
|
+
if @opts[:foo] == "bar"
|
|
126
|
+
format_all(Array(ip), **kw, &to_data)
|
|
127
|
+
else
|
|
128
|
+
super
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
```
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# within ~/.iparty/config.rb context
|
|
4
|
+
|
|
5
|
+
# iparty -d me
|
|
6
|
+
# iparty --dispatch me
|
|
7
|
+
def dispatch_me
|
|
8
|
+
require "net/http"
|
|
9
|
+
|
|
10
|
+
get_ip = proc do |url|
|
|
11
|
+
Net::HTTP.get(URI(url))
|
|
12
|
+
rescue Socket::ResolutionError
|
|
13
|
+
formatter.colorize? ? c("SOCKET_ERROR", :red) : :SOCKET_ERROR
|
|
14
|
+
rescue StandardError
|
|
15
|
+
formatter.colorize? ? c("ERROR", :red) : :ERROR
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if the_more_clever_way = true
|
|
19
|
+
@argv << get_ip["https://api.ipify.org"]
|
|
20
|
+
@argv << get_ip["https://api6.ipify.org"]
|
|
21
|
+
dispatch_info
|
|
22
|
+
else
|
|
23
|
+
out << formatter.format("my external ips") do
|
|
24
|
+
onlyexcept_data!(
|
|
25
|
+
ipv4: get_ip["https://api.ipify.org"],
|
|
26
|
+
ipv6: get_ip["https://api6.ipify.org"],
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# IParty
|
|
2
|
+
|
|
3
|
+
## Configuration
|
|
4
|
+
|
|
5
|
+
You may configure and extend IParty in an initializer or in the init part of your non-rails app.
|
|
6
|
+
For basic usage you only really need MaxMind credentials to download the mmdb files.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
defined?(IParty) && IParty.configure do |config|
|
|
10
|
+
config.account_id = config.env_value("MAXMIND_ACCOUNT_ID", nil)
|
|
11
|
+
config.license_key = config.env_value("MAXMIND_LICENSE_KEY", nil)
|
|
12
|
+
end
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
### Easily extend your custom accessors
|
|
18
|
+
|
|
19
|
+
For more info on extending address or result data look at docs/maxmind_result.md
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
IParty::MaxMind::Result::Geo.class_eval do
|
|
23
|
+
define_attr(:best_tenant, export: true) { country.iso_code == "DE" ? :de : :us }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
IParty(request.remote_ip).best_tenant # => :us
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Full config
|
|
32
|
+
|
|
33
|
+
This config, minus the CurrentAttributes cache is default config. For basic usage you only need account_id/license_key.
|
|
34
|
+
Please also take a look at docs/maxmind_result.md for extending the address and geo results.
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
defined?(IParty) && IParty.configure do |config|
|
|
38
|
+
# If set to false drop last half of v6-addresses as they are insignificant for most applications.
|
|
39
|
+
# The then 64-bit addresses also fit into unsigned bigints allowing for easy range representations.
|
|
40
|
+
# Each IParty::Address can overwrite this with
|
|
41
|
+
# * #ipv6_significant accessors
|
|
42
|
+
# * significant: keyword (affected methods only but including new/initialize)
|
|
43
|
+
config.ipv6_significant = config.env_value("IPARTY_IPV6_SIGNIFICANT", true)
|
|
44
|
+
|
|
45
|
+
# Whether to use the low memory file reader or load mmdb into memory as a whole (see docs/benchmark.md)
|
|
46
|
+
config.eager_load = config.env_value("IPARTY_EAGER_LOAD", false)
|
|
47
|
+
|
|
48
|
+
# Use singleton instances of MaxMind::Database readers.
|
|
49
|
+
# These are lazily initialized so in a threaded environment or with eager load enabled you should pre-init them.
|
|
50
|
+
# Side Effect: You will have to reboot your app for mmdb changes to take effect.
|
|
51
|
+
config.singletons = config.env_value("IPARTY_SINGLETONS", false)
|
|
52
|
+
#config.singletons = {} # lazy load singleton instances, not thread-safe
|
|
53
|
+
#config.singletons = true # eagerly init all editions (eager_load into memory if enabled, thread-safe after init)
|
|
54
|
+
|
|
55
|
+
# alternatively you can do something like this:
|
|
56
|
+
# * singleton DB instances (lazily memoized) per-request (eager load should be disabled)
|
|
57
|
+
# * IParty::Address cache (including their geo lookups)
|
|
58
|
+
if defined?(ActiveSupport::CurrentAttributes)
|
|
59
|
+
class IPartyCache < ActiveSupport::CurrentAttributes
|
|
60
|
+
attribute(:databases, default: {})
|
|
61
|
+
|
|
62
|
+
# cached IParty addresses, i.e. IPartyCache.ips["1.2.3.4"]
|
|
63
|
+
# Be wary of mutating (i.e. mask!), use clones
|
|
64
|
+
attribute(:ips, default: -> { Hash.new{|h, ip| h[ip.to_s] = IParty(ip.to_s) } })
|
|
65
|
+
end
|
|
66
|
+
config.singletons = -> { IPartyCache.databases }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# An IP that is used instead of local IPs
|
|
70
|
+
config.local_ip_alias = config.env_value("IPARTY_LOCAL_IP_ALIAS", nil)
|
|
71
|
+
|
|
72
|
+
# MaxMind account_id and license_key aka mirror basic-auth
|
|
73
|
+
config.account_id = config.env_value("MAXMIND_ACCOUNT_ID", nil)
|
|
74
|
+
config.license_key = config.env_value("MAXMIND_LICENSE_KEY", nil)
|
|
75
|
+
|
|
76
|
+
# Mirror to download tar.gz compressed mmdb-files from
|
|
77
|
+
config.mirror = config.env_value("MAXMIND_MIRROR", "https://download.maxmind.com/geoip/databases/:edition/download?suffix=tar.gz")
|
|
78
|
+
|
|
79
|
+
# Editions
|
|
80
|
+
config.editions = config.env_value("MAXMIND_EDITIONS", "GeoLite2-ASN GeoLite2-Country GeoLite2-City") {|v| v.split(/\s+|,\s*/) }
|
|
81
|
+
|
|
82
|
+
# Target directory (for mmdb files, will also create subdirectory for updating)
|
|
83
|
+
# Note: Must be a pathname
|
|
84
|
+
config.directory = config.env_value("IPARTY_DIRECTORY", nil) do |dir|
|
|
85
|
+
if dir && !dir.empty?
|
|
86
|
+
Pathname.new(dir)
|
|
87
|
+
elsif defined?(Rails)
|
|
88
|
+
Rails.root.join("vendor", "maxmind")
|
|
89
|
+
else
|
|
90
|
+
Pathname.new(Dir.tmpdir).join("iparty")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Proc to download mmdb files
|
|
95
|
+
config.url_to_mmdb = proc do |url, dir, config|
|
|
96
|
+
auth = %{-u "#{config.account_id}:#{config.license_key}"} if config.account_id && config.license_key
|
|
97
|
+
curl = %{curl -L -s #{"#{auth} " if auth}"#{url}"}
|
|
98
|
+
tar = %{tar xz --strip-components 1 --exclude "*.txt" --no-same-owner -C #{dir.to_s.shellescape}}
|
|
99
|
+
system("#{curl} | #{tar}")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# --- following is example code and not default behaviour ---
|
|
103
|
+
|
|
104
|
+
# Proc to transform geo result data, by default does nothing.
|
|
105
|
+
# yields
|
|
106
|
+
# data the result hash
|
|
107
|
+
# addr the looked up address (as IParty or IPAddr)
|
|
108
|
+
# result_class the class that later will get instantiated with data
|
|
109
|
+
config.transform_result = proc do |data, addr, result_class|
|
|
110
|
+
if result_class != IParty::MaxMind::Result::Asn && addr.loopback?
|
|
111
|
+
data[:country] ||= { iso_code: "ZZ", names: { en: "Local" } }
|
|
112
|
+
data[:continent] ||= { code: "ZZ", names: { en: "Local" } }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# For more info on extending look at docs/maxmind_result.md
|
|
117
|
+
IParty::Address.define_method(:detailed) do |*args|
|
|
118
|
+
"#{to_s} -- #{geo.detailed} -- #{asn.detailed}"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# You may want to rely on rake task and/or ensure on app boot?
|
|
122
|
+
# fetch_when can be
|
|
123
|
+
# * :always
|
|
124
|
+
# * :missing
|
|
125
|
+
# * (Numeric) maxAge (i.e. (int)seconds or (AS::Duration)14.days)
|
|
126
|
+
IParty.fetch_db_files!(:missing, verbose: true)
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
### Annotations
|
|
133
|
+
|
|
134
|
+
You can annotate IPs or networks with arbitrary data whereas `tags` has special behaviour (they merge).
|
|
135
|
+
IParty CLI will display `name` along `tags` in the summarized view.
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
defined?(IParty) && IParty.configure do |config|
|
|
139
|
+
# annotate(*addresses, **data) -- merges data and tags
|
|
140
|
+
IParty.config.annotate "1.0.0.0/8", tags: %i[foo]
|
|
141
|
+
IParty.config.annotate "1.2.3.4", tags: %i[bar]
|
|
142
|
+
IParty.config.annotate "127.0.0.1/8", "::1", name: "loopback", tags: %i[localhost]
|
|
143
|
+
|
|
144
|
+
# annotate_tag(tags, *addresses) -- merges tags
|
|
145
|
+
IParty.config.annotate_tag :one, "1.0.0.0/8"
|
|
146
|
+
IParty.config.annotate_tag %i[local ipv4], "127.0.0.1/8"
|
|
147
|
+
IParty.config.annotate_tag %i[local], "127.0.0.1/8", "::1"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
IParty("127.1.2.3").annotations # => {:name=>"loopback", :tags=>[:localhost, :local, :ipv4]}
|
|
151
|
+
IParty("127.1.2.3").tag?(:local) # => true
|
|
152
|
+
IParty("69.4.20.0").annotations # => nil
|
|
153
|
+
IParty("69.4.20.0").tag?(:local) # => false
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
### Check configuration
|
|
159
|
+
|
|
160
|
+
You can use the shipped rake tasks to check your effective configuration.
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
# shows effective IParty config (including license_key)
|
|
164
|
+
rake iparty:config
|
|
165
|
+
rake iparty:config[inspect]
|
|
166
|
+
rake iparty:config[json]
|
|
167
|
+
|
|
168
|
+
# check mmdb file status
|
|
169
|
+
rake iparty:status
|
|
170
|
+
```
|
data/docs/exceptions.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# IParty
|
|
2
|
+
|
|
3
|
+
## Exceptions
|
|
4
|
+
|
|
5
|
+
These are the exceptions you might encounter using this gem. Only IParty exceptions are added by this gem, the other ones are for your reference.
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
StandardError
|
|
9
|
+
SystemCallError
|
|
10
|
+
Errno::*
|
|
11
|
+
ArgumentError
|
|
12
|
+
IPAddr::Error
|
|
13
|
+
IPAddr::AddressFamilyError
|
|
14
|
+
IPAddr::InvalidAddressError
|
|
15
|
+
IPAddr::InvalidPrefixError
|
|
16
|
+
IParty::Error
|
|
17
|
+
IParty::MaxMind::Database::Error
|
|
18
|
+
IParty::MaxMind::Database::InvalidFileFormatError
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
IParty::Address will generally raise IPAddr exceptions.
|
|
22
|
+
To guard against IP errors, if you do not already rescue broader, you typically want to rescue IPAddr::Error and IParty::Error.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# IParty
|
|
2
|
+
|
|
3
|
+
## MaxMind Result
|
|
4
|
+
|
|
5
|
+
Result is_a?(Hash) with some magic sprinkled in, party!
|
|
6
|
+
|
|
7
|
+
* Defined attributes can be retrieved via method(chaining)
|
|
8
|
+
* Even without ActiveSupport core_ext, Result(Hash) and Subdivisions(Array) implements blank?/present?/presence (values do not however)
|
|
9
|
+
* Dynamically compares
|
|
10
|
+
* result == Numeric => geoname_id
|
|
11
|
+
* result == String => name.en
|
|
12
|
+
* Dynamically inquires
|
|
13
|
+
* result.us? (2-char checks against code on continent or iso_code otherwise)
|
|
14
|
+
* result.foo? (!2-char checks against english name (lowercased, underscored))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
### Class Structure
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
IParty::MaxMind::Result < Hash
|
|
22
|
+
Asn < Result
|
|
23
|
+
Geo < Result
|
|
24
|
+
GeoCountry < Geo
|
|
25
|
+
GeoCity < Geo
|
|
26
|
+
|
|
27
|
+
Subdivisions < Array
|
|
28
|
+
Location < Result
|
|
29
|
+
NamedLocation < Result
|
|
30
|
+
Continent < NamedLocation
|
|
31
|
+
Country < NamedLocation
|
|
32
|
+
City < NamedLocation
|
|
33
|
+
Subdivision < NamedLocation
|
|
34
|
+
Postal < Result
|
|
35
|
+
Traits < Result
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Note: There's little reason to define attributes on GeoCountry or GeoCity since by default country is the fallback of city lookups.
|
|
39
|
+
You should therefore define attributes on Geo and make sure they work with nil data to preserve safe navigation access.
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Add methods
|
|
44
|
+
|
|
45
|
+
You can (in an initializer) add your own representations and helpers to the result classes or address class.
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
# define_attr is a helper to define accessors on the result data with the following synopsis:
|
|
49
|
+
# define_attr(
|
|
50
|
+
# name, # The method name
|
|
51
|
+
# attribute = name, # The attribute to dig
|
|
52
|
+
# type: nil, # sugar for transform ->(v) { type.new(v) }
|
|
53
|
+
# aliases: nil, # Symbol or Array[Symbol] of alias method names
|
|
54
|
+
# memoize: nil, # Memoize the value (true/false, nil: true if transform block is given)
|
|
55
|
+
# export: nil, # True, Symbol or Array[Symbol] of method names to export to Address (def_delegate)
|
|
56
|
+
# # Note: Only works on Result/Geo(Country/City)/Asn
|
|
57
|
+
# &transform # The value to return, if omitted it's the digged value
|
|
58
|
+
# )
|
|
59
|
+
|
|
60
|
+
IParty::MaxMind::Result::Geo.define_attr(:best_tenant, export: true) { continent.eu? ? :eu : :us }
|
|
61
|
+
IParty::MaxMind::Result::Geo.define_attr(:short, export: true) { [continent.code, country.name].compact.join(" / ") }
|
|
62
|
+
IParty::MaxMind::Result::Asn.define_attr(:short, export: :asn_short) { "AS#{number} #{organization}" }
|
|
63
|
+
IParty::Address.define_method(:detailed) { "#{to_s} -- #{geo.detailed} -- #{asn_short}" }
|
|
64
|
+
# IParty(ip).short/asn_short/detailed
|
|
65
|
+
|
|
66
|
+
# Delegate geo/asn methods so you can do `ip.METHOD` instead of `ip.geo.METHOD`
|
|
67
|
+
IParty::Address.def_delegators(:geo, *%i[registered_country])
|
|
68
|
+
IParty::Address.def_delegators(:asn, *%i[network])
|
|
69
|
+
# Note that some are already exported, namely:
|
|
70
|
+
# * city
|
|
71
|
+
# * continent
|
|
72
|
+
# * country
|
|
73
|
+
# * in_european_union?
|
|
74
|
+
# * latitude
|
|
75
|
+
# * longitude
|
|
76
|
+
# * time_zone
|
|
77
|
+
# * postal_code
|
|
78
|
+
# * (asn) autonomous_system_network
|
|
79
|
+
# * (asn) autonomous_system_number
|
|
80
|
+
# * (asn) autonomous_system_organization
|
|
81
|
+
# * (asn) autonomous_system_detailed
|
|
82
|
+
```
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# IParty
|
|
2
|
+
|
|
3
|
+
## Download of mmdb-files
|
|
4
|
+
|
|
5
|
+
Note: You need to configure valid credentials and/or mirror, see Configuration.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Download method
|
|
12
|
+
|
|
13
|
+
By default IParty will use this proc (and you may change it) to turn a URL (to a gz compressed mmdb-file) to a mmdb-file inside the temporary directory.
|
|
14
|
+
|
|
15
|
+
Note: This requires a unix-oid environment with curl, tar and gzip available.
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
IParty::MaxMind.download_proc = proc do |url, temp_dir|
|
|
19
|
+
auth = %{-u "#{account_id}:#{license_key}"} if account_id && license_key
|
|
20
|
+
curl = %{curl -L -s #{"#{auth} " if auth}"#{url}"}
|
|
21
|
+
tar = %{tar xz --strip-components 1 --exclude "*.txt" --no-same-owner -C #{temp_dir.to_s.shellescape}}
|
|
22
|
+
system("#{curl} | #{tar}")
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
The file fetching process will
|
|
28
|
+
|
|
29
|
+
* download all editions (`IParty::MaxMind.editions`) in sequence, extract them flat into the temporary directory
|
|
30
|
+
* the MaxMind account_id and license_key are HTTP Basic Auth username and password (this also works for your mirror)
|
|
31
|
+
* move all mmdb files inside the temp directory into the data directory
|
|
32
|
+
* this is essentially an atomic-ish update and will not nuke your existing files on error
|
|
33
|
+
* files that failed to download or are no longer requested will remain (no cleanup)
|
|
34
|
+
* remove the temp directory
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
### Download
|
|
39
|
+
|
|
40
|
+
#### Ruby
|
|
41
|
+
|
|
42
|
+
In your application startup, or in a rake task:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
# only download missing or expired files
|
|
46
|
+
IParty.fetch_db_files!(14 * 24 * 60 * 60) # or 14.days
|
|
47
|
+
|
|
48
|
+
# only download missing files, verbose will print files downloaded to stderr
|
|
49
|
+
IParty.fetch_db_files!(:missing, verbose: true)
|
|
50
|
+
|
|
51
|
+
# always download a fresh copy
|
|
52
|
+
IParty.fetch_db_files! # (:always)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
#### Rake tasks
|
|
57
|
+
|
|
58
|
+
You may also use the shipped rake tasks for this purpose:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
rake iparty:fetch
|
|
62
|
+
rake iparty:fetch[14.days] # or int-seconds without AS
|
|
63
|
+
rake iparty:update
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
There are also these rake tasks
|
|
67
|
+
```ruby
|
|
68
|
+
# prints mmdb file status, expiry check if max_age is provided
|
|
69
|
+
# will exit(1) if any file is missing, invalid or expired
|
|
70
|
+
rake iparty:status
|
|
71
|
+
rake iparty:status[14.days]
|
|
72
|
+
|
|
73
|
+
# shows effective IParty config (including license_key)
|
|
74
|
+
rake iparty:config
|
|
75
|
+
rake iparty:config[inspect]
|
|
76
|
+
rake iparty:config[json]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Outside of Rails you need to register them manually in your Rakefile:
|
|
80
|
+
|
|
81
|
+
```ruby
|
|
82
|
+
require "iparty/rake_task"
|
|
83
|
+
IParty::RakeTask.new
|
|
84
|
+
```
|
data/lib/iparty/address.rb
CHANGED
|
@@ -122,6 +122,8 @@ module IParty
|
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
def annotations
|
|
125
|
+
return @_annotations if defined?(@_annotations)
|
|
126
|
+
|
|
125
127
|
result = {}
|
|
126
128
|
IParty.config.annotations&.each do |ipp, adata|
|
|
127
129
|
next unless ipp.include?(self)
|
|
@@ -129,7 +131,13 @@ module IParty
|
|
|
129
131
|
result.merge!(adata.merge(tags: result.fetch(:tags, []) | adata.fetch(:tags, [])))
|
|
130
132
|
end
|
|
131
133
|
|
|
132
|
-
|
|
134
|
+
@_annotations = result.empty? ? nil : result
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def tag? tag
|
|
138
|
+
return false unless tags = annotations&.fetch(:tags, nil)
|
|
139
|
+
|
|
140
|
+
tags.include?(tag)
|
|
133
141
|
end
|
|
134
142
|
|
|
135
143
|
def as_json
|
data/lib/iparty/config.rb
CHANGED
|
@@ -12,6 +12,7 @@ module IParty
|
|
|
12
12
|
:local_ip_alias,
|
|
13
13
|
:ipv6_significant,
|
|
14
14
|
:url_to_mmdb,
|
|
15
|
+
:transform_result,
|
|
15
16
|
:annotations,
|
|
16
17
|
keyword_init: true,
|
|
17
18
|
) do
|
|
@@ -135,6 +136,18 @@ module IParty
|
|
|
135
136
|
tar = %{tar xz --strip-components 1 --exclude "*.txt" --no-same-owner -C #{dir.to_s.shellescape}}
|
|
136
137
|
system("#{curl} | #{tar}")
|
|
137
138
|
end,
|
|
139
|
+
|
|
140
|
+
# Proc to transform geo result data, by default does nothing.
|
|
141
|
+
# yields
|
|
142
|
+
# data the result hash
|
|
143
|
+
# addr the looked up address (as IParty or IPAddr)
|
|
144
|
+
# result_class the class that later will get instantiated with data
|
|
145
|
+
# transform_result: proc do |data, addr, result_class|
|
|
146
|
+
# if addr.loopback?
|
|
147
|
+
# data[:country] ||= { iso_code: "ZZ", names: { en: "Local" } }
|
|
148
|
+
# data[:continent] ||= { code: "ZZ", names: { en: "Local" } }
|
|
149
|
+
# end
|
|
150
|
+
# end,
|
|
138
151
|
)
|
|
139
152
|
end
|
|
140
153
|
end
|
|
@@ -49,8 +49,8 @@ module IParty
|
|
|
49
49
|
addr = IPAddr.new(addr) unless addr.is_a?(IPAddr)
|
|
50
50
|
addr = IParty.config.local_ip_alias if IParty.config.local_ip_alias && addr.loopback?
|
|
51
51
|
addr = IPAddr.new(addr) unless addr.is_a?(IPAddr)
|
|
52
|
-
|
|
53
|
-
long =
|
|
52
|
+
compat_addr = addr.ipv4? ? addr.ipv4_compat : addr
|
|
53
|
+
long = compat_addr.is_a?(IParty::Address) ? compat_addr.to_i(significant: true) : compat_addr.to_i
|
|
54
54
|
node_no = 0
|
|
55
55
|
|
|
56
56
|
(@start_idx...128).each do |i|
|
|
@@ -62,7 +62,12 @@ module IParty
|
|
|
62
62
|
elsif next_node_no >= @node_count
|
|
63
63
|
pos = (next_node_no - @node_count) - DATA_SECTION_SEPARATOR_SIZE
|
|
64
64
|
result = decode(@data_section_start, pos)[1]
|
|
65
|
-
result[:network] =
|
|
65
|
+
result[:network] = if !result.empty?
|
|
66
|
+
cidr_from_long(long, i)
|
|
67
|
+
elsif addr.loopback?
|
|
68
|
+
@family == Socket::AF_INET6 ? "::1/128" : "127.0.0.0/8"
|
|
69
|
+
end
|
|
70
|
+
IParty.config.transform_result&.call(result, addr, result_class)
|
|
66
71
|
return result_class.new(result)
|
|
67
72
|
else
|
|
68
73
|
node_no = next_node_no
|
|
@@ -41,14 +41,14 @@ module IParty
|
|
|
41
41
|
define_attr(:latitude)
|
|
42
42
|
define_attr(:longitude)
|
|
43
43
|
define_attr(:metro_code)
|
|
44
|
-
define_attr(:time_zone)
|
|
44
|
+
define_attr(:time_zone, aliases: :timezone)
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
class NamedLocation < Result
|
|
48
|
-
define_attr(:code)
|
|
48
|
+
define_attr(:code, memoize: false) {|v| v || self[:iso_code] }
|
|
49
49
|
define_attr(:geoname_id)
|
|
50
50
|
define_attr(:is_in_european_union, aliases: :in_european_union?)
|
|
51
|
-
define_attr(:iso_code)
|
|
51
|
+
define_attr(:iso_code, memoize: false) {|v| v || self[:code] }
|
|
52
52
|
define_attr(:names, type: Result)
|
|
53
53
|
|
|
54
54
|
def name(locale = :en, fallback_locale: :en)
|
|
@@ -75,7 +75,7 @@ module IParty
|
|
|
75
75
|
|
|
76
76
|
# dynamic inquiry
|
|
77
77
|
define_attr(:inquire_on_name) { name&.downcase&.tr(" ", "_") }
|
|
78
|
-
define_attr(:inquire_on_code) {
|
|
78
|
+
define_attr(:inquire_on_code) { iso_code&.downcase }
|
|
79
79
|
|
|
80
80
|
def respond_to_missing? method_name, include_private = false
|
|
81
81
|
method_name.end_with?("?") || super
|
|
@@ -170,7 +170,7 @@ module IParty
|
|
|
170
170
|
define_attr(:latitude, memoize: false, export: true) { location.latitude }
|
|
171
171
|
define_attr(:longitude, memoize: false, export: true) { location.longitude }
|
|
172
172
|
define_attr(:metro_code, memoize: false) { location.metro_code }
|
|
173
|
-
define_attr(:time_zone, memoize: false, export: true) { location.time_zone }
|
|
173
|
+
define_attr(:time_zone, memoize: false, aliases: :timezone, export: true) { location.time_zone }
|
|
174
174
|
define_attr(:postal_code, aliases: :zip, export: true, memoize: false) { postal.code }
|
|
175
175
|
|
|
176
176
|
define_attr(:detailed_parts, memoize: false) { [continent.code, country.name, city.name].compact }
|
data/lib/iparty/rake_task.rb
CHANGED
|
@@ -111,7 +111,14 @@ module IParty
|
|
|
111
111
|
puts IParty.config.inspect
|
|
112
112
|
else
|
|
113
113
|
IParty.config.each_pair do |key, value|
|
|
114
|
-
|
|
114
|
+
if key == :annotations && value
|
|
115
|
+
puts "#{key.to_s.rjust(16)}:"
|
|
116
|
+
value.each do |ipp, adata|
|
|
117
|
+
puts "#{"".rjust(16)} #{ipp.to_cidr}: #{adata.inspect}"
|
|
118
|
+
end
|
|
119
|
+
else
|
|
120
|
+
puts "#{key.to_s.rjust(16)}: #{value.inspect}"
|
|
121
|
+
end
|
|
115
122
|
end
|
|
116
123
|
end
|
|
117
124
|
end
|
data/lib/iparty/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: iparty
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sven Pachnit
|
|
@@ -91,6 +91,13 @@ files:
|
|
|
91
91
|
- LICENSE.txt
|
|
92
92
|
- README.md
|
|
93
93
|
- Rakefile
|
|
94
|
+
- docs/benchmark.md
|
|
95
|
+
- docs/cli/README.md
|
|
96
|
+
- docs/cli/action_cookbook/show_my_remote_ips.rb
|
|
97
|
+
- docs/configuration.md
|
|
98
|
+
- docs/exceptions.md
|
|
99
|
+
- docs/maxmind_result.md
|
|
100
|
+
- docs/mmdb_download.md
|
|
94
101
|
- exe/iparty
|
|
95
102
|
- lib/iparty.rb
|
|
96
103
|
- lib/iparty/address.rb
|