smart_proxy_dhcp_bluecat 0.1.5 → 0.1.6

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.
@@ -1,386 +1,438 @@
1
- require 'httparty'
2
- require 'ipaddress'
3
- require 'json'
4
-
5
- module Proxy
6
- module DHCP
7
- module BlueCat
8
- ##
9
- # This Class handles all commuincation to the bluecat address manager
10
- class BlueCatAPI
11
- include ::Proxy::Log
12
-
13
- # connection mode to the address manager. http or https
14
- attr_reader :scheme
15
-
16
- # validate ssl connection. true or false
17
- attr_reader :verify
18
-
19
- # fqdn or ip of your bluecat address manager
20
- attr_reader :host
21
-
22
- # id of the parent_block that holds the subnets that you want to use
23
- attr_reader :parent_block
24
-
25
- # name of your dns view
26
- attr_reader :view_name
27
-
28
- # Name of your Bluecat configuration
29
- attr_reader :config_name
30
-
31
- # id of your Bluecat configuration
32
- attr_reader :config_id
33
-
34
- # id of the server that holds your dhcp
35
- attr_reader :server_id
36
-
37
- # credentials of your api user
38
- attr_reader :username
39
-
40
- # credentials of your api user
41
- attr_reader :password
42
-
43
- class << self
44
- # contains the bluecat api token
45
- attr_accessor :token
46
- end
47
-
48
- def initialize(scheme, verify, host, parent_block, view_name, config_name, config_id, server_id, username, password)
49
- @scheme = scheme
50
- @verify = verify
51
- @host = host
52
- @parent_block = parent_block
53
- @view_name = view_name
54
- @config_name = config_name
55
- @config_id = config_id
56
- @server_id = server_id
57
- @username = username
58
- @password = password
59
- end
60
-
61
- # login to bam, parse the session token
62
- def rest_login
63
- logger.debug('BAM Login ' + @scheme + ' ' + @host + ' ')
64
- response = HTTParty.get(format('%s://%s/Services/REST/v1/login?username=%s&password=%s', @scheme, @host, @username, @password),
65
- headers: { 'Content-Type' => 'text/plain' },
66
- verify => @verify)
67
- if response.code != 200
68
- logger.error('BAM Login Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
69
- end
70
- body = response.body.to_s
71
- token = body.match(/BAMAuthToken:\s+(\S+)/).captures
72
-
73
- logger.debug('BAM Login Body ' + response.body)
74
- logger.debug('BAM Login Token ' + token[0].to_s)
75
- self.class.token = token[0].to_s
76
- end
77
-
78
- # logout from bam
79
- def rest_logout
80
- logger.debug('BAM Logout ')
81
- response = HTTParty.get(format('%s://%s/Services/REST/v1/logout', @scheme, @host),
82
- headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
83
- verify: @verify)
84
- logger.error('BAM Logout Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s) if response.code != 200
85
- end
86
-
87
- # wrapper function to for rest get requests
88
- def rest_get(endpoint, querystring)
89
- rest_login if self.class.token.nil?
90
-
91
- logger.debug('BAM GET ' + endpoint + '?' + querystring)
92
-
93
- response = HTTParty.get(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
94
- headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
95
- verify: @verify)
96
- # Session propably expired, refresh it and do the request again
97
- if response.code == 401
98
- rest_login
99
- response = HTTParty.get(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
100
- headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
101
- verify: @verify)
102
- end
103
-
104
- return response.body if response.code == 200
105
- logger.error('BAM GET Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
106
- end
107
-
108
- # wrapper function to for rest post requests
109
- def rest_post(endpoint, querystring)
110
- logger.debug('BAM POST ' + endpoint + '?' + querystring)
111
- response = HTTParty.post(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
112
- headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
113
- verify: @verify
114
- )
115
- # Session propably expired, refresh it and do the request again
116
- if response.code == 401
117
- rest_login
118
- response = HTTParty.post(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
119
- headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
120
- verify: @verify)
121
- end
122
- return response.body if response.code == 200
123
- logger.error('BAM POST Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
124
- end
125
-
126
- # wrapper function to for rest put requests
127
- def rest_put(endpoint, querystring)
128
- logger.debug('BAM PUT ' + endpoint + '?' + querystring)
129
- response = HTTParty.put(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
130
- headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
131
- verify: @verify)
132
- # Session propably expired, refresh it and do the request again
133
- if response.code == 401
134
- rest_login
135
- response = HTTParty.put(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
136
- headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
137
- verify: @verify)
138
- end
139
- return response.body if response.code == 200
140
- logger.error('BAM PUT Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
141
- end
142
-
143
- # wrapper function to for rest delete requests
144
- def rest_delete(endpoint, querystring)
145
- logger.debug('BAM DELETE ' + endpoint + '?' + querystring)
146
- response = HTTParty.delete(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
147
- headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
148
- verify: @verify)
149
-
150
- # Session propably expired, refresh it and do the request again
151
- if response.code == 401
152
- rest_login
153
- response = HTTParty.delete(format('%s://%s/Services/REST/v1/%s?%s', @scheme, @host, endpoint, querystring),
154
- headers: { 'Authorization' => 'BAMAuthToken: ' + self.class.token, 'Content-Type' => 'application/json' },
155
- verify: @verify)
156
- end
157
- return response.body if response.code == 200
158
- logger.error('BAM DELETE Failed. HTTP' + response.code.to_s + ' ' + response.body.to_s)
159
- end
160
-
161
- # helper function to get the object id of a ip by an ip address
162
- def get_addressid_by_ip(ip)
163
- json = rest_get('getIP4Address', 'containerId=' + @config_id.to_s + '&address=' + ip)
164
- result = JSON.parse(json)
165
- return nil if result.empty?
166
- result['id'].to_s
167
- end
168
-
169
- # helper function to get the object id of a subnet by an ip address
170
- def get_networkid_by_ip(ip)
171
- logger.debug('BAM get_networkid_by_ip ' + ip)
172
- querystring = 'containerId=' + @config_id.to_s + '&type=IP4Network' + '&address=' + ip.to_s
173
- json = rest_get('getIPRangedByIP', querystring)
174
- result = JSON.parse(json)
175
- return nil if result.empty?
176
- result['id'].to_s
177
- end
178
-
179
- # helper function to get the whole subnet informarions by an ip address
180
- def get_network_by_ip(ip)
181
- logger.debug('BAM get_network_by_ip ' + ip)
182
- querystring = 'containerId=' + @config_id.to_s + '&type=IP4Network' + '&address=' + ip.to_s
183
- json = rest_get('getIPRangedByIP', querystring)
184
- result = JSON.parse(json)
185
- properties = parse_properties(result['properties'])
186
- properties['CIDR'].to_s
187
- end
188
-
189
- # helper function to parse the properties scheme of bluecat into a hash
190
- #
191
- # properies: a string that contains properties for the object in attribute=value format, with each separated by a | (pipe) character.
192
- # For example, a host record object may have a properties field such as ttl=123|comments=my comment|.
193
- def parse_properties(properties)
194
- properties = properties.split('|')
195
- h = {}
196
- properties.each do |property|
197
- h[property.split('=').first.to_s] = property.split('=').last.to_s
198
- end
199
- h
200
- end
201
-
202
- # public
203
- # wrapper function to add the dhcp reservation and dns records
204
- def add_host(options)
205
- # add the ip and hostname and mac as static
206
- rest_post('addDeviceInstance', 'configName=' + @config_name +
207
- '&ipAddressMode=PASS_VALUE' \
208
- '&ipEntity=' + options['ip'] +
209
- '&viewName=' + @view_name +
210
- '&zoneName=' + options['hostname'].split('.', 2).last +
211
- '&deviceName=' + options['hostname'] +
212
- '&recordName=' + options['hostname'] +
213
- '&macAddressMode=PASS_VALUE' \
214
- '&macEntity=' + options['mac'] +
215
- '&options=AllowDuplicateHosts=true%7C')
216
-
217
- address_id = get_addressid_by_ip(options['ip'])
218
-
219
- # update the state of the ip from static to dhcp reserved
220
- rest_put('changeStateIP4Address', 'addressId=' + address_id +
221
- '&targetState=MAKE_DHCP_RESERVED' \
222
- '&macAddress=' + options['mac'])
223
-
224
- unless options['nextServer'].nil? or options['filename'].nil?
225
- rest_post('addDHCPClientDeploymentOption', 'entityId=' + address_id.to_s + '&name=tftp-server-name' + "&value=" + options['nextServer'].to_s)
226
- rest_post('addDHCPClientDeploymentOption', 'entityId=' + address_id.to_s + '&name=boot-file-name' + "&value=" + options['filename'].to_s)
227
- end
228
-
229
- # deploy the config
230
- rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DHCP')
231
- # lets wait a little bit for the complete dhcp deploy
232
- sleep 3
233
- rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DNS')
234
- nil
235
- end
236
-
237
- # public
238
- # wrapper function to remove a ip record and depending dns records
239
- def remove_host(ip)
240
- ipid = get_addressid_by_ip(ip)
241
- json = rest_get('getLinkedEntities', 'entityId=' + ipid.to_s + '&type=HostRecord&start=0&count=2')
242
- results = JSON.parse(json)
243
-
244
- hosts = results.map do |result|
245
- rest_delete('delete', 'objectId=' + result['id'].to_s)
246
- end
247
- rest_delete('delete', 'objectId=' + ipid.to_s)
248
-
249
- rest_post('deployServerConfig', 'serverId=' + @server_id.to_s + '&properties=services=DHCP,DNS')
250
- end
251
-
252
- # public
253
- # fetches the next free address in a subnet
254
- # +end_ip not implemented+
255
- def next_ip(netadress, start_ip, end_ip)
256
- networkid = get_networkid_by_ip(netadress)
257
-
258
- start_ip = IPAddress.parse(netadress).first if start_ip.to_s.empty?
259
-
260
- properties = 'offset=' + start_ip.to_s + '%7CexcludeDHCPRange=false'
261
- result = rest_get('getNextIP4Address', 'parentId=' + networkid.to_s + '&properties=' + properties)
262
- return if result.empty?
263
- result.tr('"', '')
264
- end
265
-
266
- # public
267
- # fetches all subnets under the parent_block
268
- def subnets
269
- json = rest_get('getEntities', 'parentId=' + @parent_block.to_s + '&type=IP4Network&start=0&count=10000')
270
- results = JSON.parse(json)
271
- subnets = results.map do |result|
272
- properties = parse_properties(result['properties'])
273
- net = IPAddress.parse(properties['CIDR'])
274
- opts = { routers: [properties['gateway']] }
275
-
276
- if properties['gateway'].nil?
277
- logger.error("subnet issue: " + properties['CIDR'] + " skipped, due missing gateway in bluecat")
278
- next
279
- end
280
-
281
- ::Proxy::DHCP::Subnet.new(net.address, net.netmask, opts)
282
- end
283
- subnets.compact
284
- end
285
-
286
- # public
287
- # fetches a subnet by its network address
288
- def find_mysubnet(subnet_address)
289
- net = IPAddress.parse(get_network_by_ip(subnet_address))
290
- subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
291
- subnet
292
- end
293
-
294
- # public
295
- # fetches all dhcp reservations in a subnet
296
- def hosts(network_address)
297
- netid = get_networkid_by_ip(network_address)
298
- net = IPAddress.parse(get_network_by_ip(network_address))
299
- subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
300
-
301
- json = rest_get('getNetworkLinkedProperties', 'networkId=' + netid.to_s)
302
- results = JSON.parse(json)
303
-
304
- hosts = results.map do |result|
305
- properties = parse_properties(result['properties'])
306
-
307
- ## Static Addresses and Gateway are not needed here
308
- ## But lets keep the logic to identify them
309
- # if properties.length() >= 4
310
- # if properties["state"] == "Gateway" or properties["state"] == "Static"
311
- # address = properties[0].split("=").last()
312
- # macAddress = "00:00:00:00:00:00"
313
- # hosttag = properties[3].split("=").last().split(":")
314
- # name = hosttag[1] + "." + hosttag[3]
315
- # opts = {:hostname => name}
316
- # ::Proxy::DHCP::Reservation.new(name, address, macAddress, subnet, opts)
317
- # end
318
- # end
319
- next unless properties.length >= 5
320
- next unless properties['state'] == 'DHCP Reserved'
321
- hosttag = properties['host'].split(':')
322
- name = hosttag[1] + '.' + hosttag[3]
323
- opts = { hostname: name }
324
- ::Proxy::DHCP::Reservation.new(name, properties['address'], properties['macAddress'].tr('-', ':'), subnet, opts)
325
- end
326
- hosts.compact
327
- end
328
-
329
- # public
330
- # fetches a host by its ip
331
- def hosts_by_ip(ip)
332
- hosts = []
333
- net = IPAddress.parse(get_network_by_ip(ip))
334
- subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
335
- ipid = get_addressid_by_ip(ip)
336
- return nil if ipid.to_s == '0'
337
- json = rest_get('getLinkedEntities', 'entityId=' + ipid + '&type=HostRecord&start=0&count=2')
338
- results = JSON.parse(json)
339
-
340
- if results.empty? || (results == "Link request is not supported")
341
- # no host record on ip, fetch mac only
342
- json2 = rest_get('getIP4Address', 'containerId=' + @config_id.to_s + '&address=' + ip)
343
- result2 = JSON.parse(json2)
344
- properties2 = parse_properties(result2['properties'])
345
- unless properties2['macAddress'].nil?
346
- mac_address = properties2['macAddress'].tr('-', ':')
347
- hosts.push(Proxy::DHCP::Reservation.new("", ip, mac_address, subnet, {}))
348
- end
349
- else
350
- # host record on ip, return more infos
351
- results.each do |result|
352
- properties = parse_properties(result['properties'])
353
- opts = { hostname: properties['absoluteName'] }
354
-
355
- next unless properties['reverseRecord'].to_s == 'true'.to_s
356
- json2 = rest_get('getEntityById', 'id=' + ipid)
357
- result2 = JSON.parse(json2)
358
- properties2 = parse_properties(result2['properties'])
359
- unless properties2['macAddress'].nil?
360
- mac_address = properties2['macAddress'].tr('-', ':')
361
- hosts.push(Proxy::DHCP::Reservation.new(properties['absoluteName'], ip, mac_address, subnet, opts))
362
- end
363
- end
364
- end
365
- hosts.compact
366
- end
367
-
368
- # public
369
- # fetches all dhcp reservations by a mac
370
- def host_by_mac(mac)
371
- json = rest_get('getMACAddress', 'configurationId=' + @config_id.to_s + '&macAddress=' + mac.to_s)
372
- result = JSON.parse(json)
373
- macid = result['id'].to_s
374
- return if macid == '0'
375
- json2 = rest_get('getLinkedEntities', 'entityId=' + macid + '&type=IP4Address&start=0&count=1')
376
- result2 = JSON.parse(json2)
377
- return if result2.empty?
378
- properties = parse_properties(result2[0]['properties'])
379
- host = hosts_by_ip(properties['address'])
380
- return if host.nil?
381
- host[0]
382
- end
383
- end
384
- end
385
- end
386
- end
1
+ require "httparty"
2
+ require "ipaddress"
3
+ require "json"
4
+
5
+ module Proxy
6
+ module DHCP
7
+ module BlueCat
8
+ ##
9
+ # This Class handles all commuincation to the bluecat address manager
10
+ class BlueCatAPI
11
+ include ::Proxy::Log
12
+
13
+ # connection mode to the address manager. http or https
14
+ attr_reader :scheme
15
+
16
+ # validate ssl connection. true or false
17
+ attr_reader :verify
18
+
19
+ # fqdn or ip of your bluecat address manager
20
+ attr_reader :host
21
+
22
+ # id of the parent_block that holds the subnets that you want to use
23
+ attr_reader :parent_block
24
+
25
+ # name of your dns view
26
+ attr_reader :view_name
27
+
28
+ # Name of your Bluecat configuration
29
+ attr_reader :config_name
30
+
31
+ # id of your Bluecat configuration
32
+ attr_reader :config_id
33
+
34
+ # id of the server that holds your dhcp
35
+ attr_reader :server_id
36
+
37
+ # credentials of your api user
38
+ attr_reader :username
39
+
40
+ # credentials of your api user
41
+ attr_reader :password
42
+
43
+ class << self
44
+ # contains the bluecat api token
45
+ attr_accessor :token
46
+ end
47
+
48
+ def initialize(scheme, verify, host, parent_block, view_name, config_name, config_id, server_id, username, password)
49
+ @scheme = scheme
50
+ @verify = verify
51
+ @host = host
52
+ @parent_block = parent_block
53
+ @view_name = view_name
54
+ @config_name = config_name
55
+ @config_id = config_id
56
+ @server_id = server_id
57
+ @username = username
58
+ @password = password
59
+ end
60
+
61
+ # login to bam, parse the session token
62
+ def rest_login
63
+ logger.debug("BAM Login #{@scheme} #{@host} ")
64
+ response = HTTParty.get(format("%s://%s/Services/REST/v1/login?username=%s&password=%s",
65
+ @scheme,
66
+ @host,
67
+ @username,
68
+ @password),
69
+ headers: { "Content-Type" => "text/plain" },
70
+ verify => @verify)
71
+ logger.error("BAM Login Failed. HTTP#{response.code} #{response.body}") if response.code != 200
72
+ body = response.body.to_s
73
+ token = body.match(/BAMAuthToken:\s+(\S+)/).captures
74
+
75
+ logger.debug("BAM Login Body #{response.body}")
76
+ logger.debug("BAM Login Token #{token[0]}")
77
+ self.class.token = token[0].to_s
78
+ end
79
+
80
+ # logout from bam
81
+ def rest_logout
82
+ logger.debug("BAM Logout ")
83
+ response = HTTParty.get(format("%s://%s/Services/REST/v1/logout", @scheme, @host),
84
+ headers: { "Authorization" => "BAMAuthToken: #{self.class.token}",
85
+ "Content-Type" => "application/json" },
86
+ verify: @verify)
87
+ logger.error("BAM Logout Failed. HTTP#{response.code} #{response.body}") if response.code != 200
88
+ end
89
+
90
+ # wrapper function to for rest get requests
91
+ def rest_get(endpoint, querystring)
92
+ rest_login if self.class.token.nil?
93
+
94
+ logger.debug("BAM GET #{endpoint}?#{querystring}")
95
+
96
+ response = HTTParty.get(format("%s://%s/Services/REST/v1/%s?%s", @scheme, @host, endpoint, querystring),
97
+ headers: { "Authorization" => "BAMAuthToken: #{self.class.token}",
98
+ "Content-Type" => "application/json" },
99
+ verify: @verify)
100
+ # Session propably expired, refresh it and do the request again
101
+ if response.code == 401
102
+ rest_login
103
+ response = HTTParty.get(format("%s://%s/Services/REST/v1/%s?%s", @scheme, @host, endpoint, querystring),
104
+ headers: { "Authorization" => "BAMAuthToken: #{self.class.token}",
105
+ "Content-Type" => "application/json" },
106
+ verify: @verify)
107
+ end
108
+
109
+ return response.body if response.code == 200
110
+
111
+ logger.error("BAM GET Failed. HTTP#{response.code} #{response.body}")
112
+ end
113
+
114
+ # wrapper function to for rest post requests
115
+ def rest_post(endpoint, querystring)
116
+ logger.debug("BAM POST #{endpoint}?#{querystring}")
117
+ response = HTTParty.post(format("%s://%s/Services/REST/v1/%s?%s", @scheme, @host, endpoint, querystring),
118
+ headers: { "Authorization" => "BAMAuthToken: #{self.class.token}",
119
+ "Content-Type" => "application/json" },
120
+ verify: @verify)
121
+ # Session propably expired, refresh it and do the request again
122
+ if response.code == 401
123
+ rest_login
124
+ response = HTTParty.post(format("%s://%s/Services/REST/v1/%s?%s", @scheme, @host, endpoint, querystring),
125
+ headers: { "Authorization" => "BAMAuthToken: #{self.class.token}",
126
+ "Content-Type" => "application/json" },
127
+ verify: @verify)
128
+ end
129
+ return response.body if response.code == 200
130
+
131
+ logger.error("BAM POST Failed. HTTP#{response.code} #{response.body}")
132
+ end
133
+
134
+ # wrapper function to for rest put requests
135
+ def rest_put(endpoint, querystring)
136
+ logger.debug("BAM PUT #{endpoint}?#{querystring}")
137
+ response = HTTParty.put(format("%s://%s/Services/REST/v1/%s?%s", @scheme, @host, endpoint, querystring),
138
+ headers: { "Authorization" => "BAMAuthToken: #{self.class.token}",
139
+ "Content-Type" => "application/json" },
140
+ verify: @verify)
141
+ # Session propably expired, refresh it and do the request again
142
+ if response.code == 401
143
+ rest_login
144
+ response = HTTParty.put(format("%s://%s/Services/REST/v1/%s?%s", @scheme, @host, endpoint, querystring),
145
+ headers: { "Authorization" => "BAMAuthToken: #{self.class.token}",
146
+ "Content-Type" => "application/json" },
147
+ verify: @verify)
148
+ end
149
+ return response.body if response.code == 200
150
+
151
+ logger.error("BAM PUT Failed. HTTP#{response.code} #{response.body}")
152
+ end
153
+
154
+ # wrapper function to for rest delete requests
155
+ def rest_delete(endpoint, querystring)
156
+ logger.debug("BAM DELETE #{endpoint}?#{querystring}")
157
+ response = HTTParty.delete(format("%s://%s/Services/REST/v1/%s?%s", @scheme, @host, endpoint, querystring),
158
+ headers: { "Authorization" => "BAMAuthToken: #{self.class.token}",
159
+ "Content-Type" => "application/json" },
160
+ verify: @verify)
161
+
162
+ # Session propably expired, refresh it and do the request again
163
+ if response.code == 401
164
+ rest_login
165
+ response = HTTParty.delete(format("%s://%s/Services/REST/v1/%s?%s", @scheme, @host, endpoint, querystring),
166
+ headers: { "Authorization" => "BAMAuthToken: #{self.class.token}",
167
+ "Content-Type" => "application/json" },
168
+ verify: @verify)
169
+ end
170
+ return response.body if response.code == 200
171
+
172
+ logger.error("BAM DELETE Failed. HTTP#{response.code} #{response.body}")
173
+ end
174
+
175
+ # helper function to get the object id of a ip by an ip address
176
+ def get_addressid_by_ip(ip)
177
+ json = rest_get("getIP4Address", "containerId=#{@config_id}&address=#{ip}")
178
+ result = JSON.parse(json)
179
+ return nil if result.empty?
180
+
181
+ result["id"].to_s
182
+ end
183
+
184
+ # helper function to get the object id of a subnet by an ip address
185
+ def get_networkid_by_ip(ip)
186
+ logger.debug("BAM get_networkid_by_ip #{ip}")
187
+ querystring = "containerId=#{@config_id}&type=IP4Network&address=#{ip}"
188
+ json = rest_get("getIPRangedByIP", querystring)
189
+ result = JSON.parse(json)
190
+ return nil if result.empty?
191
+
192
+ result["id"].to_s
193
+ end
194
+
195
+ # helper function to get the whole subnet informarions by an ip address
196
+ def get_network_by_ip(ip)
197
+ logger.debug("BAM get_network_by_ip #{ip}")
198
+ querystring = "containerId=#{@config_id}&type=IP4Network&address=#{ip}"
199
+ json = rest_get("getIPRangedByIP", querystring)
200
+ result = JSON.parse(json)
201
+ properties = parse_properties(result["properties"])
202
+ properties["CIDR"].to_s
203
+ end
204
+
205
+ # helper function to parse the properties scheme of bluecat into a hash
206
+ #
207
+ # properies: a string that contains properties for the object in attribute=value format,
208
+ # with each separated by a | (pipe) character.
209
+ # For example, a host record object may have a properties field such as ttl=123|comments=my comment|.
210
+ def parse_properties(properties)
211
+ properties = properties.split("|")
212
+ h = {}
213
+ properties.each do |property|
214
+ h[property.split("=").first.to_s] = property.split("=").last.to_s
215
+ end
216
+ h
217
+ end
218
+
219
+ # public
220
+ # wrapper function to add the dhcp reservation and dns records
221
+ def add_host(options)
222
+ # add the ip and hostname and mac as static
223
+ rest_post("addDeviceInstance",
224
+ "configName=#{@config_name} \
225
+ &ipAddressMode=PASS_VALUE \
226
+ &ipEntity=#{options["ip"]} \
227
+ &viewName=#{@view_name} \
228
+ &zoneName=#{options["hostname"].split(".", 2).last} \
229
+ &deviceName=#{options["hostname"]} \
230
+ &recordName=#{options["hostname"]} \
231
+ &macAddressMode=PASS_VALUE \
232
+ &macEntity=#{options["mac"]} \
233
+ &options=AllowDuplicateHosts=true%7C")
234
+
235
+ address_id = get_addressid_by_ip(options["ip"])
236
+
237
+ # update the state of the ip from static to dhcp reserved
238
+ rest_put("changeStateIP4Address",
239
+ "addressId=#{address_id} \
240
+ &targetState=MAKE_DHCP_RESERVED \
241
+ &macAddress=#{options["mac"]}")
242
+
243
+ unless options["nextServer"].nil? || options["filename"].nil?
244
+ rest_post("addDHCPClientDeploymentOption",
245
+ "entityId=#{address_id}&name=tftp-server-name&value=#{options["nextServer"]}")
246
+ rest_post("addDHCPClientDeploymentOption",
247
+ "entityId=#{address_id}&name=boot-file-name&value=#{options["filename"]}")
248
+ end
249
+
250
+ # deploy the config
251
+ rest_post("deployServerConfig", "serverId=#{@server_id}&properties=services=DHCP")
252
+ # lets wait a little bit for the complete dhcp deploy
253
+ sleep 3
254
+ rest_post("deployServerConfig", "serverId=#{@server_id}&properties=services=DNS")
255
+ nil
256
+ end
257
+
258
+ # public
259
+ # wrapper function to remove a ip record and depending dns records
260
+ def remove_host(ip)
261
+ ipid = get_addressid_by_ip(ip)
262
+ json = rest_get("getLinkedEntities", "entityId=#{ipid}&type=HostRecord&start=0&count=2")
263
+ results = JSON.parse(json)
264
+
265
+ results.map do |result|
266
+ rest_delete("delete", "objectId=#{result["id"]}")
267
+ end
268
+ rest_delete("delete", "objectId=#{ipid}")
269
+
270
+ rest_post("deployServerConfig", "serverId=#{@server_id}&properties=services=DHCP,DNS")
271
+ end
272
+
273
+ # public
274
+ # fetches the next free address in a subnet
275
+ # +end_ip not implemented+
276
+ def next_ip(netadress, start_ip, _end_ip)
277
+ networkid = get_networkid_by_ip(netadress)
278
+
279
+ start_ip = IPAddress.parse(netadress).first if start_ip.to_s.empty?
280
+
281
+ properties = "offset=#{start_ip}%7CexcludeDHCPRange=false"
282
+ result = rest_get("getNextIP4Address", "parentId=#{networkid}&properties=#{properties}")
283
+ return if result.empty?
284
+
285
+ result.tr('"', "")
286
+ end
287
+
288
+ # public
289
+ # fetches all subnets under the parent_block
290
+ def subnets
291
+ json = rest_get("getEntities", "parentId=#{@parent_block}&type=IP4Network&start=0&count=10000")
292
+ results = JSON.parse(json)
293
+ subnets = results.map do |result|
294
+ properties = parse_properties(result["properties"])
295
+ net = IPAddress.parse(properties["CIDR"])
296
+ opts = { routers: [properties["gateway"]] }
297
+
298
+ if properties["gateway"].nil?
299
+ logger.error("subnet issue: #{properties["CIDR"]} skipped, due missing gateway in bluecat")
300
+ next
301
+ end
302
+
303
+ ::Proxy::DHCP::Subnet.new(net.address, net.netmask, opts)
304
+ end
305
+ subnets.compact
306
+ end
307
+
308
+ # public
309
+ # fetches a subnet by its network address
310
+ def find_mysubnet(subnet_address)
311
+ net = IPAddress.parse(get_network_by_ip(subnet_address))
312
+ ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
313
+ end
314
+
315
+ # public
316
+ # fetches all dhcp reservations in a subnet
317
+ def hosts(network_address)
318
+ netid = get_networkid_by_ip(network_address)
319
+ net = IPAddress.parse(get_network_by_ip(network_address))
320
+ subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
321
+
322
+ json = rest_get("getNetworkLinkedProperties", "networkId=#{netid}")
323
+ results = JSON.parse(json)
324
+
325
+ hosts = results.map do |result|
326
+ properties = parse_properties(result["properties"])
327
+
328
+ ## Static Addresses and Gateway are not needed here
329
+ ## But lets keep the logic to identify them
330
+ # if properties.length() >= 4
331
+ # if properties["state"] == "Gateway" or properties["state"] == "Static"
332
+ # address = properties[0].split("=").last()
333
+ # macAddress = "00:00:00:00:00:00"
334
+ # hosttag = properties[3].split("=").last().split(":")
335
+ # name = hosttag[1] + "." + hosttag[3]
336
+ # opts = {:hostname => name}
337
+ # ::Proxy::DHCP::Reservation.new(name, address, macAddress, subnet, opts)
338
+ # end
339
+ # end
340
+ next unless properties.length >= 5
341
+ next unless properties["state"] == "DHCP Reserved"
342
+
343
+ hosttag = properties["host"].split(":")
344
+ name = "#{hosttag[1]}.#{hosttag[3]}"
345
+ opts = { hostname: name }
346
+ ::Proxy::DHCP::Reservation.new(name, properties["address"], properties["macAddress"].tr("-", ":"), subnet, opts)
347
+ end
348
+ hosts.compact
349
+ end
350
+
351
+ # public
352
+ # fetches a host by its ip
353
+ def hosts_by_ip(ip)
354
+ hosts = []
355
+ net = IPAddress.parse(get_network_by_ip(ip))
356
+ subnet = ::Proxy::DHCP::Subnet.new(net.address, net.netmask)
357
+ ipid = get_addressid_by_ip(ip)
358
+ return nil if ipid.to_s == "0"
359
+
360
+ json = rest_get("getLinkedEntities", "entityId=#{ipid}&type=HostRecord&start=0&count=2")
361
+ results = JSON.parse(json)
362
+
363
+ if results.empty? || (results == "Link request is not supported")
364
+ # no host record on ip, fetch mac only
365
+ json2 = rest_get("getIP4Address", "containerId=#{@config_id}&address=#{ip}")
366
+ result2 = JSON.parse(json2)
367
+ properties2 = parse_properties(result2["properties"])
368
+ unless properties2["macAddress"].nil?
369
+ mac_address = properties2["macAddress"].tr("-", ":")
370
+ hosts.push(Proxy::DHCP::Reservation.new("", ip, mac_address, subnet, {}))
371
+ end
372
+ else
373
+ # host record on ip, return more infos
374
+ results.each do |result|
375
+ properties = parse_properties(result["properties"])
376
+ opts = { hostname: properties["absoluteName"] }
377
+
378
+ next unless properties["reverseRecord"].to_s == "true".to_s
379
+
380
+ json2 = rest_get("getEntityById", "id=#{ipid}")
381
+ result2 = JSON.parse(json2)
382
+ properties2 = parse_properties(result2["properties"])
383
+ unless properties2["macAddress"].nil?
384
+ mac_address = properties2["macAddress"].tr("-", ":")
385
+ hosts.push(Proxy::DHCP::Reservation.new(properties["absoluteName"], ip, mac_address, subnet, opts))
386
+ end
387
+ end
388
+ end
389
+ hosts.compact
390
+ end
391
+
392
+ # public
393
+ # fetches all dhcp reservations by a mac
394
+ def host_by_mac_and_subnet(subnet_of_host, mac)
395
+ logger.debug("subnet_of_host #{subnet_of_host}")
396
+
397
+ net = get_network_by_ip(subnet_of_host)
398
+
399
+ logger.debug("net #{net}")
400
+
401
+ subnet = IPAddress.parse(net)
402
+
403
+ logger.debug("subnet #{subnet} ")
404
+
405
+ json = rest_get("getMACAddress", "configurationId=#{@config_id}&macAddress=#{mac}")
406
+ result = JSON.parse(json)
407
+ macid = result["id"].to_s
408
+ return if macid == "0"
409
+
410
+ json2 = rest_get("getLinkedEntities", "entityId=#{macid}&type=IP4Address&start=0&count=9999")
411
+ result2 = JSON.parse(json2)
412
+ return nil if result2.empty?
413
+
414
+ logger.debug("das #{result2}")
415
+
416
+ result2.each do |record|
417
+ properties = parse_properties(record["properties"])
418
+ ip = IPAddress(properties["address"])
419
+
420
+ logger.debug("hier #{subnet.to_string} #{ip.to_string}")
421
+
422
+ next unless subnet.include?(ip)
423
+
424
+ logger.debug("hier #{record}")
425
+
426
+ host = hosts_by_ip(properties["address"])
427
+ return nil if host.nil?
428
+
429
+ logger.debug("da #{host}")
430
+ return host[0]
431
+ end
432
+
433
+ nil
434
+ end
435
+ end
436
+ end
437
+ end
438
+ end