smart_proxy_dhcp_bluecat 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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