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
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2d819e6f38465278e0ef5afc147405a67bcf1e7868779dc3a719392301802840
|
|
4
|
+
data.tar.gz: a09d60d32a690d4ad066503ac81a6462a4750a613b772a2c56b14689a6d44663
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: daae7a38a07d47641cfd4ff24e1fce4e07e2100e99a4cb66a9181ece7132a82fe9543f899e2b282bf699639fdd36e4f75e1724a12cab789c0e19be9c53f6a0dc
|
|
7
|
+
data.tar.gz: dc37d49c82e4eb2f7c4bc0c108654a1b4b8b5f4109d56fdf4531077485f2003efeee0165df152815c05314a4092de5ee1a9bd9670c53331df0653b281446e5c9
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sven Pachnit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# IParty
|
|
2
|
+
|
|
3
|
+
## 0.1.0 alpha: risk of party crashers
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Makes (geo) IP fun again! Ain't no party like an IParty, because an IParty don't stop.
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
IParty.fetch_db_files! # api key required
|
|
10
|
+
ip = IParty(request.remote_ip)
|
|
11
|
+
|
|
12
|
+
# all these are true
|
|
13
|
+
ip.country.de?
|
|
14
|
+
ip.country.germany?
|
|
15
|
+
ip.country.in_european_union?
|
|
16
|
+
ip.country.is_a?(Hash)
|
|
17
|
+
ip.country == "Germany" # 🤨
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
* IParty handles download\* and decoding of, and lookup in, mmdb-files (\* = shelling to curl and tar)
|
|
21
|
+
* IParty lets you annotate IPs/networks with arbitrary helpers, data and tags
|
|
22
|
+
* IParty lets you ignore the MAC address part of an ipv6 address more easily
|
|
23
|
+
* IParty has *no*\* dependencies (\* = stdlib dependencies: fileutils, forwardable, tmpdir, optparse)
|
|
24
|
+
* IParty is essentially a fork/refactor of the [maxminddb](https://github.com/yhirose/maxminddb) gem.
|
|
25
|
+
The reimaginated implementation details were however too party for a pull request in my opinion.
|
|
26
|
+
* IParty parties hard!
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## IParty CLI utility
|
|
30
|
+
|
|
31
|
+
IParty ships with a cli utility `iparty`, refer to [docs/cli/README.md](docs/cli/README.md) for more information.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
## Start partying
|
|
36
|
+
|
|
37
|
+
### Requirements
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
spec.required_ruby_version = ">= 3.2.0"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
### Installation
|
|
45
|
+
|
|
46
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bundle add iparty
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
gem install iparty
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### Configuration
|
|
60
|
+
|
|
61
|
+
These default settings should or could be changed at "boot" (i.e. in an initializer):
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
defined?(IParty) && IParty.configure do |config|
|
|
65
|
+
config.account_id = config.env_value("MAXMIND_ACCOUNT_ID", nil)
|
|
66
|
+
config.license_key = config.env_value("MAXMIND_LICENSE_KEY", nil)
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
There are more ways to configure and/or customize IParty.
|
|
71
|
+
You can also change most of these settings via ENV variables.
|
|
72
|
+
|
|
73
|
+
See [docs/configuration.md](docs/configuration.md) for more information.
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
### Usage
|
|
77
|
+
|
|
78
|
+
#### Fetching mmdb-files
|
|
79
|
+
|
|
80
|
+
Note: This requires a unix-oid environment with curl, tar and gzip available.
|
|
81
|
+
You may change the download process.
|
|
82
|
+
For more information see [docs/configuration.md](docs/configuration.md) and [docs/mmdb_download.md](docs/mmdb_download.md)
|
|
83
|
+
|
|
84
|
+
Either in your application startup, and/or in a rake task:
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
IParty.fetch_db_files! # always download a fresh copy
|
|
88
|
+
IParty.fetch_db_files!(:missing) # only download missing files
|
|
89
|
+
IParty.fetch_db_files!(14 * 24 * 60 * 60) # only download missing or expired files (14.days also works with ActiveSupport)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
You may also use the shipped rake tasks for this purpose:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
rake iparty:fetch
|
|
96
|
+
rake iparty:fetch[14.days] # or int-seconds without AS
|
|
97
|
+
rake iparty:update
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Outside of Rails you need to register them manually in your Rakefile:
|
|
101
|
+
|
|
102
|
+
```ruby
|
|
103
|
+
require "iparty/rake_task"
|
|
104
|
+
IParty::RakeTask.new
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
#### Basic usage
|
|
109
|
+
|
|
110
|
+
```ruby
|
|
111
|
+
ip = IParty("1.2.3.4") # shorthand for IParty.normalize
|
|
112
|
+
ip.as_json
|
|
113
|
+
|
|
114
|
+
# all these are true
|
|
115
|
+
ip.country.de?
|
|
116
|
+
ip.country.germany?
|
|
117
|
+
ip.country.in_european_union?
|
|
118
|
+
ip.country.is_a?(Hash)
|
|
119
|
+
ip.country == "Germany" # 🤨
|
|
120
|
+
ip.country == 123_345 # you may want to read the docs at this point lol
|
|
121
|
+
ip.country.names.de == "Deutschland"
|
|
122
|
+
ip.country.name(:es, fallback_locale: :fr) == "Germany"
|
|
123
|
+
ip.country.dig(:names, :en) == "Germany"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
You should definitely take a quick look at the documentation, specifically about the [MaxMine::Result](docs/maxmind_result.md) object.
|
|
127
|
+
It should be intuitive magic but you may scratch your head if you "don't get it".
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
## Further reading
|
|
132
|
+
|
|
133
|
+
* [docs/benchmark.md](docs/benchmark.md)
|
|
134
|
+
* [docs/configuration.md](docs/configuration.md)
|
|
135
|
+
* [docs/exceptions.md](docs/exceptions.md)
|
|
136
|
+
* [docs/maxmind_result.md](docs/maxmind_result.md)
|
|
137
|
+
* [docs/mmdb_download.md](docs/mmdb_download.md)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
## Compatibility to maxminddb gem
|
|
142
|
+
|
|
143
|
+
IParty is somewhat compatible with (read: replacing) maxminddb depending on your usage. Most notably the result data hash is symbolized.
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
## Development
|
|
148
|
+
|
|
149
|
+
* Check out the repository
|
|
150
|
+
* Run `bin/setup` to install dependencies
|
|
151
|
+
* Run `bin/console` to experiment with an interactive irb prompt
|
|
152
|
+
* Run `rake` to run all the tests
|
|
153
|
+
* Run `rake ci` to fetch mmdb-files, then run all the tests (more coverage)
|
|
154
|
+
|
|
155
|
+
In order to run all the tests you must have API credentials for MaxMind (or a mirror / local copy of the mmdb files for Country, City and ASN).
|
|
156
|
+
The mmdb-files can no longer be distributed or downloaded without API credentials due to licensing. See Configuration.
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
## Contributing
|
|
161
|
+
|
|
162
|
+
Bug reports, ideas, feedback and pull requests are welcome on GitHub at https://github.com/2called-chaos/iparty.
|
|
163
|
+
|
|
164
|
+
* [Open an issue](https://github.com/2called-chaos/iparty/issues/new)
|
|
165
|
+
|
|
166
|
+
or
|
|
167
|
+
|
|
168
|
+
1. [Fork it](http://github.com/2called-chaos/iparty/fork)
|
|
169
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
170
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
171
|
+
4. Make sure the tests pass and test your changes too (`rake` or `rake ci`)
|
|
172
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
|
173
|
+
6. Create new Pull Request
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT), see [LICENSE.txt](https://github.com/2called-chaos/iparty/blob/master/LICENSE.txt).
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
## Legal
|
|
184
|
+
|
|
185
|
+
* © 2014, yhirose [maxminddb](https://github.com/yhirose/maxminddb) and contributors
|
|
186
|
+
* © 2026, Sven Pachnit (www.bmonkeys.net) and contributors
|
|
187
|
+
* iparty is licensed under the MIT license
|
data/Rakefile
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
|
|
5
|
+
require "rspec/core/rake_task"
|
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
7
|
+
|
|
8
|
+
require "rubocop/rake_task"
|
|
9
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
|
10
|
+
task.options = ["--fail-level", "W"]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
task default: %i[spec rubocop]
|
|
14
|
+
task ci: %i[early_simplecov fetch_mmdb_files default]
|
|
15
|
+
|
|
16
|
+
desc "load simplecov early"
|
|
17
|
+
task :early_simplecov do
|
|
18
|
+
require "simplecov"
|
|
19
|
+
SimpleCov.command_name "early"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc "download mmdb files for testing"
|
|
23
|
+
task :fetch_mmdb_files do
|
|
24
|
+
require "iparty"
|
|
25
|
+
IParty.config.directory = Pathname.new(__dir__).join("spec", "cache")
|
|
26
|
+
IParty.config.mirror = "https://statics.bmonkeys.net/maxmind/:edition.tar.gz"
|
|
27
|
+
|
|
28
|
+
require "iparty/rake_task"
|
|
29
|
+
IParty::RakeTask.new
|
|
30
|
+
|
|
31
|
+
# always fetch one for coverage
|
|
32
|
+
smallest = IParty.config.directory.join("GeoLite2-ASN.mmdb")
|
|
33
|
+
smallest.unlink if smallest.exist?
|
|
34
|
+
|
|
35
|
+
Rake::Task["iparty:fetch"].invoke
|
|
36
|
+
|
|
37
|
+
# invalid file
|
|
38
|
+
IParty.config.directory.join("GeoLite2-INVALID.mmdb").binwrite("INVALID")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# ---
|
|
42
|
+
|
|
43
|
+
desc "same as cop:html_open"
|
|
44
|
+
task cop: "cop:html_open"
|
|
45
|
+
|
|
46
|
+
namespace :cop do
|
|
47
|
+
desc "Show worst offenders / worst files"
|
|
48
|
+
task :worst do
|
|
49
|
+
sh("rubocop -f autogenconf -f worst --fail-level W") {} # ignore non-zero exit code
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "Create rubocop HTML report"
|
|
53
|
+
task :html do
|
|
54
|
+
sh("rubocop -f autogenconf -f html -o tmp/rubocop.html") {} # ignore non-zero exit code
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
desc "Create rubocop HTML report and open it"
|
|
58
|
+
task :html_open do
|
|
59
|
+
Rake::Task["cop:html"].invoke
|
|
60
|
+
sh "open tmp/rubocop.html"
|
|
61
|
+
end
|
|
62
|
+
end
|
data/exe/iparty
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ipaddr"
|
|
4
|
+
|
|
5
|
+
module IParty
|
|
6
|
+
class Address < IPAddr
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
attr_accessor :ipv6_significant
|
|
10
|
+
|
|
11
|
+
def initialize *args, **kw
|
|
12
|
+
self.ipv6_significant = kw.fetch(:significant, true)
|
|
13
|
+
super(*args)
|
|
14
|
+
self.ipv6_significant = true if force_significant?
|
|
15
|
+
|
|
16
|
+
raise IPAddr::AddressFamilyError, "unsupported address family" unless ipv4? || ipv6?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def force_significant?
|
|
20
|
+
@family == Socket::AF_INET6 && @addr == 1
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def type
|
|
24
|
+
if @family == Socket::AF_INET
|
|
25
|
+
:ipv4
|
|
26
|
+
elsif @family == Socket::AF_INET6
|
|
27
|
+
:ipv6
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def size significant: ipv6_significant
|
|
32
|
+
if ipv4?
|
|
33
|
+
2**(32 - prefix)
|
|
34
|
+
elsif ipv6?
|
|
35
|
+
if force_significant? || significant || ipv4_mapped? || ipv4_compat?
|
|
36
|
+
2**(128 - prefix)
|
|
37
|
+
else
|
|
38
|
+
2**[0, 128 - prefix - 64].max
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def range? **kw
|
|
44
|
+
size(**kw) > 1
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# super but keeping significant option
|
|
48
|
+
def to_range
|
|
49
|
+
self.class.new(begin_addr, @family, significant: ipv6_significant)..self.class.new(end_addr, @family, significant: ipv6_significant)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def to_long_range **kw
|
|
53
|
+
range = to_range
|
|
54
|
+
[range.first.to_i(**kw), range.last.to_i(**kw)]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def to_significant
|
|
58
|
+
self.class.new(@addr, @family, significant: true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def to_insignificant
|
|
62
|
+
self.class.new(@addr, @family, significant: false)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def to_cidr expand_v6: false, default_masks: false, netmask: false, significant: ipv6_significant
|
|
66
|
+
significant = true if force_significant?
|
|
67
|
+
cidr = expand_v6 ? to_string(significant:) : to_s(significant:)
|
|
68
|
+
return cidr if !default_masks && !range?(significant: true) && !(ipv6? && !significant)
|
|
69
|
+
|
|
70
|
+
mp = prefix
|
|
71
|
+
if @family == Socket::AF_INET
|
|
72
|
+
masklen = 32 - mp
|
|
73
|
+
mask_addr = ((IN4MASK >> masklen) << masklen)
|
|
74
|
+
else
|
|
75
|
+
mp = 64 if !significant && mp > 64
|
|
76
|
+
masklen = 128 - mp
|
|
77
|
+
mask_addr = ((IN6MASK >> masklen) << masklen)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
"#{cidr}/#{netmask ? _to_string(mask_addr) : mp}"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def prefix significant: ipv6_significant
|
|
84
|
+
return super() if force_significant? || significant || !ipv6? || ipv4_mapped? || ipv4_compat? || super() <= 64
|
|
85
|
+
|
|
86
|
+
64
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def to_i significant: ipv6_significant
|
|
90
|
+
return super() if force_significant? || significant || !ipv6? || ipv4_mapped? || ipv4_compat? || prefix(significant: true) <= 64
|
|
91
|
+
|
|
92
|
+
# drop upper 64 bits / host-identifier of ipv6
|
|
93
|
+
(super() >> 64) & ((1 << 64) - 1)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def to_s significant: ipv6_significant
|
|
97
|
+
return super() if force_significant? || significant || !ipv6? || ipv4_mapped? || ipv4_compat? || prefix(significant: true) <= 64
|
|
98
|
+
|
|
99
|
+
mask(64).to_s(significant: true)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def to_string significant: ipv6_significant
|
|
103
|
+
return super() if force_significant? || significant || !ipv6? || ipv4_mapped? || ipv4_compat? || prefix(significant: true) <= 64
|
|
104
|
+
|
|
105
|
+
mask(64).to_string(significant: true)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def asn
|
|
109
|
+
defined?(@_asn) ? @_asn : (@_asn = MaxMind.lookup(:ASN, self, result_class: MaxMind::Result::Asn) || MaxMind::Result::Asn.new)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def geo_country
|
|
113
|
+
defined?(@_country) ? @_country : (@_country = MaxMind.lookup(:Country, self, result_class: MaxMind::Result::GeoCountry) || MaxMind::Result::GeoCountry.new)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def geo_city
|
|
117
|
+
defined?(@_city) ? @_city : (@_city = MaxMind.lookup(:City, self, result_class: MaxMind::Result::GeoCity) || MaxMind::Result::GeoCity.new)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def geo
|
|
121
|
+
defined?(@_geo) ? @_geo : (@_geo = geo_city.presence || geo_country.presence || MaxMind::Result::Geo.new)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def annotations
|
|
125
|
+
result = {}
|
|
126
|
+
IParty.config.annotations&.each do |ipp, adata|
|
|
127
|
+
next unless ipp.include?(self)
|
|
128
|
+
|
|
129
|
+
result.merge!(adata.merge(tags: result.fetch(:tags, []) | adata.fetch(:tags, [])))
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
result unless result.empty?
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def as_json
|
|
136
|
+
{
|
|
137
|
+
type: type,
|
|
138
|
+
prefix: prefix,
|
|
139
|
+
address: to_s,
|
|
140
|
+
cidr: to_cidr,
|
|
141
|
+
network: nil,
|
|
142
|
+
annotations: annotations,
|
|
143
|
+
}.merge(asn.merge(network: nil), geo).compact
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IParty
|
|
4
|
+
module CLI
|
|
5
|
+
class Application
|
|
6
|
+
module Actions
|
|
7
|
+
# via --irb/-d irb
|
|
8
|
+
def dispatch_irb
|
|
9
|
+
IrbContext.new(self).start
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# via -h/--help
|
|
13
|
+
def dispatch_help
|
|
14
|
+
help_text = colorized_help_text
|
|
15
|
+
help_text = help_text.map{ decolorize(_1) } unless @opts[:colorize]
|
|
16
|
+
|
|
17
|
+
puts help_text, nil, c("The current config directory is #{c @config_path, :magenta}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# via -v/--version
|
|
21
|
+
def dispatch_appinfo pad: 20
|
|
22
|
+
parts = if @opts[:debug]
|
|
23
|
+
%i[runtime cli_opts cli_config formatters iparty_config mmdb_status]
|
|
24
|
+
else
|
|
25
|
+
%i[runtime cli_config mmdb_status]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
parts.each_with_index do |imeth, i|
|
|
29
|
+
puts unless i.zero?
|
|
30
|
+
send(:"appinfo_#{imeth}", pad: pad)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def dispatch_info use_argf: read_from_stdin?
|
|
35
|
+
return dispatch_help if @argv.empty? && !use_argf
|
|
36
|
+
|
|
37
|
+
ensure_mmdb_files!
|
|
38
|
+
|
|
39
|
+
if use_argf
|
|
40
|
+
each_line_in_argf_as_addresses do |addresses, index|
|
|
41
|
+
out << formatter.format_all(addresses, base_index: index){|ip| ip_to_data(ip, colorize: formatter.colorize?) }
|
|
42
|
+
end
|
|
43
|
+
else
|
|
44
|
+
addresses = IParty.expand_hostnames(@argv)
|
|
45
|
+
|
|
46
|
+
out << if addresses.length > 1
|
|
47
|
+
formatter.format_all(addresses){|ip| ip_to_data(ip, colorize: formatter.colorize?) }
|
|
48
|
+
else
|
|
49
|
+
formatter.format(addresses[0]){|ip| ip_to_data(ip, colorize: formatter.colorize?) }
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IParty
|
|
4
|
+
module CLI
|
|
5
|
+
class Application
|
|
6
|
+
module Appinfo
|
|
7
|
+
def appinfo_runtime pad: 20
|
|
8
|
+
puts c("#{"".rjust(pad + 2)}# Runtime", :magenta)
|
|
9
|
+
puts c("#{"IParty".rjust(pad)}: #{c IParty::VERSION, :blue}")
|
|
10
|
+
puts c("#{"Ruby".rjust(pad)}: #{c RUBY_DESCRIPTION, :blue}")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def appinfo_cli_opts pad: 20
|
|
14
|
+
puts c("#{"".rjust(pad + 2)}# CLI options", :magenta)
|
|
15
|
+
@opts.each_pair do |key, value|
|
|
16
|
+
puts c("#{key.to_s.rjust(pad)}: #{c value.inspect, default_options[key] == value ? :cyan : :blue}")
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def appinfo_cli_config pad: 20
|
|
21
|
+
path_inaccessible = c(" (inaccessible)", :red) if !@config_path.directory? || !@config_path.readable?
|
|
22
|
+
file_inaccessible = c(" (inaccessible)", :red) if !@config_file.exist? || !@config_file.readable?
|
|
23
|
+
file_disabled = c(" (disabled)", :red) if @rc_disabled
|
|
24
|
+
|
|
25
|
+
puts c("#{"".rjust(pad + 2)}# CLI config", :magenta)
|
|
26
|
+
puts c("#{"config_path".rjust(pad)}: #{c @config_path.inspect, :blue}#{path_inaccessible}")
|
|
27
|
+
puts c("#{"config_file".rjust(pad)}: #{c @config_file.inspect, :blue}#{file_disabled || file_inaccessible}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def appinfo_iparty_config pad: 20
|
|
31
|
+
puts c("#{"".rjust(pad + 2)}# IParty.config", :magenta)
|
|
32
|
+
IParty.config.each_pair do |key, value|
|
|
33
|
+
if key == :annotations && value
|
|
34
|
+
puts c("#{key.to_s.rjust(pad)}:")
|
|
35
|
+
value.each do |ipp, adata|
|
|
36
|
+
puts c(" #{"".rjust(pad)}#{ipp.to_cidr}: #{c adata.inspect, :blue}")
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
puts c("#{key.to_s.rjust(pad)}: #{c value.inspect, :blue}")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def appinfo_mmdb_status pad: 20
|
|
45
|
+
puts c("#{"".rjust(pad + 2)}# MMDB file status", :magenta)
|
|
46
|
+
IParty.config.editions.map do |edition|
|
|
47
|
+
file = IParty.config.directory.join("#{edition}.mmdb")
|
|
48
|
+
reason = IParty::MaxMind.fetch_db_file_reason(file, @opts[:mmdb_fetch_when])
|
|
49
|
+
|
|
50
|
+
status = if file.exist?
|
|
51
|
+
ctime = file.ctime
|
|
52
|
+
age = (Time.now - ctime)
|
|
53
|
+
days = (age / (60 * 60 * 24)).floor
|
|
54
|
+
hours = ((age / (60 * 60)) % 24).floor
|
|
55
|
+
minutes = ((age / 60) % 60).floor
|
|
56
|
+
age_string = [
|
|
57
|
+
("#{days}d" if days > 0),
|
|
58
|
+
("%02d:%02d" % [hours, minutes] if hours > 0 || minutes > 0),
|
|
59
|
+
].compact.join(" ")
|
|
60
|
+
age_string = "#{age.floor}s" if age_string.empty?
|
|
61
|
+
|
|
62
|
+
if reason
|
|
63
|
+
"#{c(reason.upcase, :red)} #{c("[age: #{age_string}, ctime: #{ctime}]", :black)} #{file}"
|
|
64
|
+
else
|
|
65
|
+
"#{c("OK", :green)} #{c("[age: #{age_string}, ctime: #{ctime}]", :black)} #{file}"
|
|
66
|
+
end
|
|
67
|
+
else
|
|
68
|
+
"#{c("MISSING", :red)} #{file}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
puts c("#{edition.to_s.rjust(pad)}: #{status}")
|
|
72
|
+
!reason
|
|
73
|
+
end.all?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def appinfo_formatters pad: 20
|
|
77
|
+
pad = -2 if pad.zero?
|
|
78
|
+
|
|
79
|
+
puts c("#{"".rjust(pad + 2)}# Available formatters", :magenta)
|
|
80
|
+
CLI::Formatter.descendants.each do |fmt|
|
|
81
|
+
name = fmt.to_s
|
|
82
|
+
source_location = begin
|
|
83
|
+
if name.start_with?("#<Class:")
|
|
84
|
+
rest = name.split("::", 2)[1]
|
|
85
|
+
name = c("<IPARTYRC> ", :red) + c(rest, :blue)
|
|
86
|
+
singleton_class.const_source_location(rest) if singleton_class.const_defined?(rest)
|
|
87
|
+
else
|
|
88
|
+
Object.const_source_location(name)
|
|
89
|
+
end
|
|
90
|
+
rescue StandardError => ex
|
|
91
|
+
["UNKNOWN(#{ex.class}: #{ex.message})", 0]
|
|
92
|
+
end || ["UNKNOWN", 0]
|
|
93
|
+
|
|
94
|
+
puts [
|
|
95
|
+
c("#{"".rjust(pad)}* "),
|
|
96
|
+
c(name, :blue),
|
|
97
|
+
c("(", :black),
|
|
98
|
+
c(fmt.id.inspect, :green),
|
|
99
|
+
c(")", :black),
|
|
100
|
+
c(" in #{source_location.join(":")}", :black),
|
|
101
|
+
].join
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IParty
|
|
4
|
+
module CLI
|
|
5
|
+
class Application
|
|
6
|
+
class IrbContext
|
|
7
|
+
attr_reader :app, :formatter
|
|
8
|
+
|
|
9
|
+
def initialize(app)
|
|
10
|
+
@app = app
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def to_s
|
|
14
|
+
"IParty::CLI"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def help
|
|
18
|
+
puts "app # application reference"
|
|
19
|
+
puts "exit # exit IRB"
|
|
20
|
+
puts "ip *ips # show summary for IPs"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def ip *ips
|
|
24
|
+
addresses = IParty.expand_hostnames(ips)
|
|
25
|
+
@app.out << if addresses.length > 1
|
|
26
|
+
app.formatter.format_all(addresses){|ip| app.ip_to_data(ip, colorize: app.formatter.colorize?) }
|
|
27
|
+
else
|
|
28
|
+
[app.formatter.format(addresses[0]){|ip| app.ip_to_data(ip, colorize: app.formatter.colorize?) }]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def start
|
|
35
|
+
help
|
|
36
|
+
binding.irb(show_code: false) # rubocop:disable Lint/Debugger -- no comment
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|