smart_proxy_ipam 0.0.16 → 0.0.17
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0624875b34dd0cd44fcaa5446e22b5a9f4c2d9ee6426445ba8605fd80ee9df6
|
4
|
+
data.tar.gz: 9cecc6b2070e5fd02d1f42a7aa849b79a92d05dc18a660f2f5baf45af97bf368
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 10ec0acd80412ff6cd523f99701cc1f57a61d67c6cf2e30b4c986c4cd61553443310ab32aa8f5116d8cbce8f5245c46c2a422f719f27d25cc5297550f5c465cf
|
7
|
+
data.tar.gz: c9324940694102cfccbf3088091f2a2fb6a978261d3ab2bb3d65fba88c7cd6d678dfc67a3b24f785035db4edaebd0b12729c4bb2df54d4c811bb7a58a11a0091
|
@@ -31,10 +31,15 @@ module Proxy::Phpipam
|
|
31
31
|
|
32
32
|
mac = params[:mac]
|
33
33
|
cidr = params[:address] + '/' + params[:prefix]
|
34
|
+
section_name = params[:group]
|
35
|
+
|
34
36
|
phpipam_client = PhpipamClient.new
|
35
|
-
subnet = JSON.parse(phpipam_client.get_subnet(cidr))
|
37
|
+
subnet = JSON.parse(phpipam_client.get_subnet(cidr, section_name))
|
38
|
+
|
36
39
|
return {:code => subnet['code'], :error => subnet['message']}.to_json if no_subnets_found(subnet)
|
37
|
-
|
40
|
+
|
41
|
+
ipaddr = phpipam_client.get_next_ip(subnet['data']['id'], mac, cidr, section_name)
|
42
|
+
|
38
43
|
return {:code => subnet['code'], :error => ipaddr['message']}.to_json if no_free_ip_found(JSON.parse(ipaddr))
|
39
44
|
|
40
45
|
ipaddr
|
@@ -95,7 +100,7 @@ module Proxy::Phpipam
|
|
95
100
|
# ]
|
96
101
|
# Response if :error =>
|
97
102
|
# {"error":"Unable to connect to phpIPAM server"}
|
98
|
-
get '/
|
103
|
+
get '/groups' do
|
99
104
|
content_type :json
|
100
105
|
|
101
106
|
begin
|
@@ -107,6 +112,37 @@ module Proxy::Phpipam
|
|
107
112
|
end
|
108
113
|
end
|
109
114
|
|
115
|
+
# Get a single sections from external ipam
|
116
|
+
#
|
117
|
+
# Input: Section name
|
118
|
+
# Returns: A JSON section on success, hash with "error" key otherwise
|
119
|
+
# Examples
|
120
|
+
# Response if success:
|
121
|
+
# {"code":200,"success":true,"data":{"id":"80","name":"Development #1","description":null,
|
122
|
+
# "masterSection":"0","permissions":"[]","strictMode":"1","subnetOrdering":"default","order":null,
|
123
|
+
# "editDate":"2020-02-11 10:57:01","showVLAN":"1","showVRF":"1","showSupernetOnly":"1","DNS":null},"time":0.004}
|
124
|
+
# Response if not found:
|
125
|
+
# {"code":404,"error":"Not Found"}
|
126
|
+
# Response if :error =>
|
127
|
+
# {"error":"Unable to connect to phpIPAM server"}
|
128
|
+
get '/groups/:group_name' do
|
129
|
+
content_type :json
|
130
|
+
|
131
|
+
begin
|
132
|
+
err = validate_required_params(["group_name"], params)
|
133
|
+
return err if err.length > 0
|
134
|
+
|
135
|
+
phpipam_client = PhpipamClient.new
|
136
|
+
section = JSON.parse(phpipam_client.get_section(params[:group_name]))
|
137
|
+
|
138
|
+
return {:code => section['code'], :error => section['message']}.to_json if no_section_found(section)
|
139
|
+
section.to_json
|
140
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
141
|
+
logger.debug(errors[:no_connection])
|
142
|
+
raise
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
110
146
|
# Get a list of subnets for given external ipam section/group
|
111
147
|
#
|
112
148
|
# Input: section_name(string). The name of the external ipam section/group
|
@@ -162,16 +198,15 @@ module Proxy::Phpipam
|
|
162
198
|
# }
|
163
199
|
# Response if :error =>
|
164
200
|
# {"error":"Unable to connect to External IPAM server"}
|
165
|
-
get '/
|
201
|
+
get '/groups/:group_name/subnets' do
|
166
202
|
content_type :json
|
167
203
|
|
168
204
|
begin
|
169
|
-
err = validate_required_params(["
|
205
|
+
err = validate_required_params(["group_name"], params)
|
170
206
|
return err if err.length > 0
|
171
|
-
|
172
|
-
section_name = CGI.unescape(params[:section_name])
|
207
|
+
|
173
208
|
phpipam_client = PhpipamClient.new
|
174
|
-
section = JSON.parse(phpipam_client.get_section(
|
209
|
+
section = JSON.parse(phpipam_client.get_section(params[:group_name]))
|
175
210
|
|
176
211
|
return {:code => section['code'], :error => section['message']}.to_json if no_section_found(section)
|
177
212
|
|
@@ -204,12 +239,13 @@ module Proxy::Phpipam
|
|
204
239
|
|
205
240
|
ip = params[:ip]
|
206
241
|
cidr = params[:address] + '/' + params[:prefix]
|
242
|
+
section_name = params[:group]
|
207
243
|
phpipam_client = PhpipamClient.new
|
208
|
-
subnet = JSON.parse(phpipam_client.get_subnet(cidr))
|
244
|
+
subnet = JSON.parse(phpipam_client.get_subnet(cidr, section_name))
|
209
245
|
|
210
246
|
return {:code => subnet['code'], :error => subnet['message']}.to_json if no_subnets_found(subnet)
|
211
247
|
|
212
|
-
phpipam_client.ip_exists(ip, subnet['data'][
|
248
|
+
phpipam_client.ip_exists(ip, subnet['data']['id'])
|
213
249
|
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
214
250
|
logger.debug(errors[:no_connection])
|
215
251
|
raise
|
@@ -239,12 +275,14 @@ module Proxy::Phpipam
|
|
239
275
|
|
240
276
|
ip = params[:ip]
|
241
277
|
cidr = params[:address] + '/' + params[:prefix]
|
278
|
+
section_name = URI.escape(params[:group])
|
279
|
+
|
242
280
|
phpipam_client = PhpipamClient.new
|
243
|
-
subnet = JSON.parse(phpipam_client.get_subnet(cidr))
|
281
|
+
subnet = JSON.parse(phpipam_client.get_subnet(cidr, section_name))
|
244
282
|
|
245
283
|
return {:code => subnet['code'], :error => subnet['message']}.to_json if no_subnets_found(subnet)
|
246
284
|
|
247
|
-
phpipam_client.add_ip_to_subnet(ip, subnet['data'][
|
285
|
+
phpipam_client.add_ip_to_subnet(ip, subnet['data']['id'], 'Address auto added by Foreman')
|
248
286
|
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
249
287
|
logger.debug(errors[:no_connection])
|
250
288
|
raise
|
@@ -272,14 +310,46 @@ module Proxy::Phpipam
|
|
272
310
|
|
273
311
|
ip = params[:ip]
|
274
312
|
cidr = params[:address] + '/' + params[:prefix]
|
313
|
+
section_name = URI.escape(params[:group])
|
275
314
|
phpipam_client = PhpipamClient.new
|
276
|
-
subnet = JSON.parse(phpipam_client.get_subnet(cidr))
|
315
|
+
subnet = JSON.parse(phpipam_client.get_subnet(cidr, section_name))
|
277
316
|
|
278
317
|
return {:code => subnet['code'], :error => subnet['message']}.to_json if no_subnets_found(subnet)
|
279
318
|
|
280
|
-
phpipam_client.delete_ip_from_subnet(ip, subnet['data'][
|
319
|
+
phpipam_client.delete_ip_from_subnet(ip, subnet['data']['id'])
|
281
320
|
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
282
|
-
|
321
|
+
logger.debug(errors[:no_connection])
|
322
|
+
raise
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Checks whether a subnet exists in a specific section.
|
327
|
+
#
|
328
|
+
# Params: 1. address: The network address of the IPv4 or IPv6 subnet.
|
329
|
+
# 2. prefix: The subnet prefix(e.g. 24)
|
330
|
+
# 3. group: The name of the section
|
331
|
+
#
|
332
|
+
# Returns: JSON object with 'data' field is exists, otherwise field with 'error'
|
333
|
+
#
|
334
|
+
# Example:
|
335
|
+
# Response if exists:
|
336
|
+
# {"code":200,"success":true,"data":{"id":"147","subnet":"172.55.55.0","mask":"29","sectionId":"84","description":null,"linked_subnet":null,"firewallAddressObject":null,"vrfId":"0","masterSubnetId":"0","allowRequests":"0","vlanId":"0","showName":"0","device":"0","permissions":"[]","pingSubnet":"0","discoverSubnet":"0","resolveDNS":"0","DNSrecursive":"0","DNSrecords":"0","nameserverId":"0","scanAgent":"0","customer_id":null,"isFolder":"0","isFull":"0","tag":"2","threshold":"0","location":"0","editDate":null,"lastScan":null,"lastDiscovery":null,"calculation":{"Type":"IPv4","IP address":"\/","Network":"172.55.55.0","Broadcast":"172.55.55.7","Subnet bitmask":"29","Subnet netmask":"255.255.255.248","Subnet wildcard":"0.0.0.7","Min host IP":"172.55.55.1","Max host IP":"172.55.55.6","Number of hosts":"6","Subnet Class":false}},"time":0.009}
|
337
|
+
# Response if not exists:
|
338
|
+
# {"code":404,"error":"No subnet 172.66.66.0/29 found in section :group"}
|
339
|
+
get '/group/:group/subnet/:address/:prefix' do
|
340
|
+
content_type :json
|
341
|
+
|
342
|
+
begin
|
343
|
+
err = validate_required_params(["address", "prefix", "group"], params)
|
344
|
+
return err if err.length > 0
|
345
|
+
|
346
|
+
cidr = params[:address] + '/' + params[:prefix]
|
347
|
+
|
348
|
+
phpipam_client = PhpipamClient.new
|
349
|
+
phpipam_client.get_subnet_by_section(cidr, params[:group])
|
350
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET
|
351
|
+
logger.debug(errors[:no_connection])
|
352
|
+
raise
|
283
353
|
end
|
284
354
|
end
|
285
355
|
end
|
@@ -4,6 +4,7 @@ require 'net/http'
|
|
4
4
|
require 'monitor'
|
5
5
|
require 'concurrent'
|
6
6
|
require 'time'
|
7
|
+
require 'uri'
|
7
8
|
require 'smart_proxy_ipam/ipam'
|
8
9
|
require 'smart_proxy_ipam/ipam_main'
|
9
10
|
require 'smart_proxy_ipam/phpipam/phpipam_helper'
|
@@ -14,7 +15,7 @@ module Proxy::Phpipam
|
|
14
15
|
include PhpipamHelper
|
15
16
|
|
16
17
|
MAX_RETRIES = 5
|
17
|
-
DEFAULT_CLEANUP_INTERVAL =
|
18
|
+
DEFAULT_CLEANUP_INTERVAL = 60 # 2 mins
|
18
19
|
@@ip_cache = nil
|
19
20
|
@@timer_task = nil
|
20
21
|
|
@@ -27,13 +28,35 @@ module Proxy::Phpipam
|
|
27
28
|
start_cleanup_task if @@timer_task.nil?
|
28
29
|
end
|
29
30
|
|
30
|
-
def get_subnet(cidr)
|
31
|
+
def get_subnet(cidr, section_name = nil)
|
32
|
+
if section_name.nil? || section_name.empty?
|
33
|
+
get_subnet_by_cidr(cidr)
|
34
|
+
else
|
35
|
+
get_subnet_by_section(cidr, section_name)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_subnet_by_section(cidr, section_name)
|
40
|
+
section = JSON.parse(get_section(section_name))
|
41
|
+
subnets = JSON.parse(get_subnets(section['data']['id']))
|
42
|
+
subnet_id = nil
|
43
|
+
|
44
|
+
subnets['data'].each do |subnet|
|
45
|
+
subnet_cidr = subnet['subnet'] + '/' + subnet['mask']
|
46
|
+
subnet_id = subnet['id'] if subnet_cidr == cidr
|
47
|
+
end
|
48
|
+
|
49
|
+
return {:code => 404, :error => "No subnet #{cidr} found in section #{URI.unescape(section_name)}"}.to_json if subnet_id.nil?
|
50
|
+
|
51
|
+
response = get("subnets/#{subnet_id.to_s}/")
|
52
|
+
response.body
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_subnet_by_cidr(cidr)
|
31
56
|
response = get("subnets/cidr/#{cidr.to_s}")
|
32
57
|
json_body = JSON.parse(response.body)
|
33
|
-
|
34
58
|
return response.body if no_subnets_found(json_body)
|
35
|
-
|
36
|
-
json_body['data'] = filter_fields(json_body, [:id, :subnet, :description, :mask])
|
59
|
+
json_body['data'] = filter_fields(json_body, [:id, :subnet, :description, :mask])[0]
|
37
60
|
response.body = json_body.to_json
|
38
61
|
response.header['Content-Length'] = json_body.to_s.length
|
39
62
|
response.body
|
@@ -78,15 +101,17 @@ module Proxy::Phpipam
|
|
78
101
|
response.body
|
79
102
|
end
|
80
103
|
|
81
|
-
def get_next_ip(subnet_id, mac, cidr)
|
104
|
+
def get_next_ip(subnet_id, mac, cidr, section_name)
|
82
105
|
response = get("subnets/#{subnet_id.to_s}/first_free/")
|
83
106
|
json_body = JSON.parse(response.body)
|
84
|
-
|
107
|
+
section = section_name.nil? ? "" : section_name
|
108
|
+
@@ip_cache[section.to_sym] = {} if @@ip_cache[section.to_sym].nil?
|
109
|
+
subnet_hash = @@ip_cache[section.to_sym][cidr.to_sym]
|
85
110
|
|
86
111
|
return {:code => json_body['code'], :error => json_body['message']}.to_json if json_body['message']
|
87
112
|
|
88
113
|
if subnet_hash && subnet_hash.key?(mac.to_sym)
|
89
|
-
json_body['data'] = @@ip_cache[cidr.to_sym][mac.to_sym][:ip]
|
114
|
+
json_body['data'] = @@ip_cache[section_name.to_sym][cidr.to_sym][mac.to_sym][:ip]
|
90
115
|
else
|
91
116
|
next_ip = nil
|
92
117
|
new_ip = json_body['data']
|
@@ -94,9 +119,9 @@ module Proxy::Phpipam
|
|
94
119
|
|
95
120
|
if ip_not_in_cache
|
96
121
|
next_ip = new_ip.to_s
|
97
|
-
add_ip_to_cache(new_ip, mac, cidr)
|
122
|
+
add_ip_to_cache(new_ip, mac, cidr, section)
|
98
123
|
else
|
99
|
-
next_ip = find_new_ip(subnet_id, new_ip, mac, cidr)
|
124
|
+
next_ip = find_new_ip(subnet_id, new_ip, mac, cidr, section)
|
100
125
|
end
|
101
126
|
|
102
127
|
return {:code => 404, :error => "Unable to find another available IP address in subnet #{cidr}"}.to_json if next_ip.nil?
|
@@ -119,49 +144,68 @@ module Proxy::Phpipam
|
|
119
144
|
private
|
120
145
|
|
121
146
|
# @@ip_cache structure
|
147
|
+
#
|
148
|
+
# Groups of subnets are cached under the External IPAM Group name. For example,
|
149
|
+
# "IPAM Group Name" would be the section name in phpIPAM. All IP's cached for subnets
|
150
|
+
# that do not have an External IPAM group specified, they are cached under the "" key.
|
151
|
+
#
|
122
152
|
# {
|
123
|
-
# "
|
124
|
-
#
|
153
|
+
# "": {
|
154
|
+
# "100.55.55.0/24":{
|
155
|
+
# "00:0a:95:9d:68:10": {"ip": "100.55.55.1", "timestamp": "2019-09-17 12:03:43 -D400"}
|
156
|
+
# },
|
157
|
+
# },
|
158
|
+
# "IPAM Group Name": {
|
159
|
+
# "123.11.33.0/24":{
|
160
|
+
# "00:0a:95:9d:68:33": {"ip": "123.11.33.1", "timestamp": "2019-09-17 12:04:43 -0400"},
|
161
|
+
# "00:0a:95:9d:68:34": {"ip": "123.11.33.2", "timestamp": "2019-09-17 12:05:48 -0400"},
|
162
|
+
# "00:0a:95:9d:68:35": {"ip": "123.11.33.3", "timestamp:: "2019-09-17 12:06:50 -0400"}
|
163
|
+
# }
|
125
164
|
# },
|
126
|
-
# "
|
127
|
-
#
|
128
|
-
#
|
129
|
-
#
|
165
|
+
# "Another IPAM Group": {
|
166
|
+
# "185.45.39.0/24":{
|
167
|
+
# "00:0a:95:9d:68:55": {"ip": "185.45.39.1", "timestamp": "2019-09-17 12:04:43 -0400"},
|
168
|
+
# "00:0a:95:9d:68:56": {"ip": "185.45.39.2", "timestamp": "2019-09-17 12:05:48 -0400"}
|
169
|
+
# }
|
130
170
|
# }
|
131
171
|
# }
|
132
172
|
def init_cache
|
133
|
-
logger.debug("Clearing ip cache.")
|
134
173
|
@@m.synchronize do
|
135
174
|
if @@ip_cache and not @@ip_cache.empty?
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
175
|
+
logger.debug("Processing ip cache.")
|
176
|
+
@@ip_cache.each do |section, subnets|
|
177
|
+
subnets.each do |cidr, macs|
|
178
|
+
macs.each do |mac, ip|
|
179
|
+
if Time.now - Time.parse(ip[:timestamp]) > DEFAULT_CLEANUP_INTERVAL
|
180
|
+
@@ip_cache[section][cidr].delete(mac)
|
181
|
+
end
|
140
182
|
end
|
183
|
+
@@ip_cache[section].delete(cidr) if @@ip_cache[section][cidr].nil? or @@ip_cache[section][cidr].empty?
|
141
184
|
end
|
142
|
-
@@ip_cache.delete(key) if @@ip_cache[key].nil? or @@ip_cache[key].empty?
|
143
185
|
end
|
144
186
|
else
|
145
|
-
|
187
|
+
logger.debug("Clearing ip cache.")
|
188
|
+
@@ip_cache = {:"" => {}}
|
146
189
|
end
|
147
190
|
end
|
148
191
|
end
|
149
192
|
|
150
|
-
def add_ip_to_cache(ip, mac, cidr)
|
151
|
-
logger.debug("Adding IP #{ip} to cache for subnet #{cidr}")
|
193
|
+
def add_ip_to_cache(ip, mac, cidr, section_name)
|
194
|
+
logger.debug("Adding IP #{ip} to cache for subnet #{cidr} in section #{section_name}")
|
152
195
|
@@m.synchronize do
|
153
196
|
# Clear cache data which has the same mac and ip with the new one
|
154
|
-
@@ip_cache.
|
197
|
+
section_hash = @@ip_cache[section_name.to_sym]
|
198
|
+
section_hash.each do |key, values|
|
155
199
|
if values.keys.include? mac.to_sym
|
156
|
-
@@ip_cache[key].delete(mac.to_sym)
|
200
|
+
@@ip_cache[section_name.to_sym][key].delete(mac.to_sym)
|
157
201
|
end
|
158
|
-
@@ip_cache.delete(key) if @@ip_cache[key].nil? or @@ip_cache[key].empty?
|
202
|
+
@@ip_cache[section_name.to_sym].delete(key) if @@ip_cache[section_name.to_sym][key].nil? or @@ip_cache[section_name.to_sym][key].empty?
|
159
203
|
end
|
160
|
-
|
161
|
-
if
|
162
|
-
@@ip_cache[cidr.to_sym][mac.to_sym] = {:ip => ip.to_s, :timestamp => Time.now.to_s}
|
204
|
+
|
205
|
+
if section_hash.key?(cidr.to_sym)
|
206
|
+
@@ip_cache[section_name.to_sym][cidr.to_sym][mac.to_sym] = {:ip => ip.to_s, :timestamp => Time.now.to_s}
|
163
207
|
else
|
164
|
-
@@ip_cache = @@ip_cache.merge({cidr.to_sym => {mac.to_sym => {:ip => ip.to_s, :timestamp => Time.now.to_s}}})
|
208
|
+
@@ip_cache = @@ip_cache.merge({section_name.to_sym => {cidr.to_sym => {mac.to_sym => {:ip => ip.to_s, :timestamp => Time.now.to_s}}}})
|
165
209
|
end
|
166
210
|
end
|
167
211
|
end
|
@@ -169,7 +213,7 @@ module Proxy::Phpipam
|
|
169
213
|
# Called when next available IP from external IPAM has been cached by another user/host, but
|
170
214
|
# not actually persisted in external IPAM. Will increment the IP(MAX_RETRIES times), and
|
171
215
|
# see if it is available in external IPAM.
|
172
|
-
def find_new_ip(subnet_id, ip, mac, cidr)
|
216
|
+
def find_new_ip(subnet_id, ip, mac, cidr, section_name)
|
173
217
|
found_ip = nil
|
174
218
|
temp_ip = ip
|
175
219
|
retry_count = 0
|
@@ -179,9 +223,9 @@ module Proxy::Phpipam
|
|
179
223
|
verify_ip = JSON.parse(ip_exists(new_ip, subnet_id))
|
180
224
|
|
181
225
|
# If new IP doesn't exist in IPAM and not in the cache
|
182
|
-
if ip_not_found_in_ipam(verify_ip) && !ip_exists_in_cache(new_ip, cidr, mac)
|
226
|
+
if ip_not_found_in_ipam(verify_ip) && !ip_exists_in_cache(new_ip, cidr, mac, section_name)
|
183
227
|
found_ip = new_ip.to_s
|
184
|
-
add_ip_to_cache(found_ip, mac, cidr)
|
228
|
+
add_ip_to_cache(found_ip, mac, cidr, section_name)
|
185
229
|
break
|
186
230
|
end
|
187
231
|
|
@@ -200,8 +244,8 @@ module Proxy::Phpipam
|
|
200
244
|
IPAddr.new(ip.to_s).succ.to_s
|
201
245
|
end
|
202
246
|
|
203
|
-
def ip_exists_in_cache(ip, cidr, mac)
|
204
|
-
@@ip_cache[cidr.to_sym] && @@ip_cache[cidr.to_sym].to_s.include?(ip.to_s)
|
247
|
+
def ip_exists_in_cache(ip, cidr, mac, section_name)
|
248
|
+
@@ip_cache[section_name.to_sym][cidr.to_sym] && @@ip_cache[section_name.to_sym][cidr.to_sym].to_s.include?(ip.to_s)
|
205
249
|
end
|
206
250
|
|
207
251
|
# Checks if given IP is within a subnet. Broadcast address is considered unusable
|
@@ -10,7 +10,7 @@ module PhpipamHelper
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def no_subnets_found(subnet)
|
13
|
-
|
13
|
+
subnet['message'] && subnet['message'].downcase == "no subnets found"
|
14
14
|
end
|
15
15
|
|
16
16
|
def no_section_found(section)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: smart_proxy_ipam
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christopher Smith
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-02-17 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Smart proxy plugin for IPAM integration with various IPAM providers
|
14
14
|
email: chrisjsmith001@gmail.com
|