icinga2 0.9.0.1 → 0.9.2.1

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.
@@ -0,0 +1,431 @@
1
+
2
+ require_relative 'version'
3
+ require_relative 'network'
4
+ require_relative 'statistics'
5
+ require_relative 'converts'
6
+ require_relative 'tools'
7
+ require_relative 'downtimes'
8
+ require_relative 'notifications'
9
+ require_relative 'hosts'
10
+ require_relative 'hostgroups'
11
+ require_relative 'services'
12
+ require_relative 'servicegroups'
13
+ require_relative 'users'
14
+ require_relative 'usergroups'
15
+
16
+ # -------------------------------------------------------------------------------------------------------------------
17
+ #
18
+ # @abstract # Namespace for classes and modules that handle all Icinga2 API calls
19
+ #
20
+ # @author Bodo Schulz <bodo@boone-schulz.de>
21
+ #
22
+ #
23
+ module Icinga2
24
+
25
+ # static variable for hosts down
26
+ HOSTS_DOWN = 1
27
+ # static variable for hosts critical
28
+ HOSTS_CRITICAL = 2
29
+ # static variable for hosts unknown
30
+ HOSTS_UNKNOWN = 3
31
+
32
+ # static variables for handled warning
33
+ SERVICE_STATE_WARNING = 1
34
+ # static variables for handled critical
35
+ SERVICE_STATE_CRITICAL = 2
36
+ # static variables for handled unknown
37
+ SERVICE_STATE_UNKNOWN = 3
38
+
39
+ # Abstract base class for the API calls.
40
+ # Provides some helper methods
41
+ #
42
+ # @author Bodo Schulz
43
+ #
44
+ class Client
45
+
46
+ include Logging
47
+
48
+ include Icinga2::Network
49
+ include Icinga2::Statistics
50
+ include Icinga2::Converts
51
+ include Icinga2::Tools
52
+ include Icinga2::Downtimes
53
+ include Icinga2::Notifications
54
+ include Icinga2::Hosts
55
+ include Icinga2::Hostgroups
56
+ include Icinga2::Services
57
+ include Icinga2::Servicegroups
58
+ include Icinga2::Users
59
+ include Icinga2::Usergroups
60
+
61
+ # Returns a new instance of Client
62
+ #
63
+ # @param [Hash, #read] settings the settings for Icinga2
64
+ # @option settings [String] :host ('localhost') the Icinga2 Hostname
65
+ # @option settings [Integer] :port (5665) the Icinga2 API Port
66
+ # @option settings [String] :user the Icinga2 API User
67
+ # @option settings [String] :password the Icinga2 API Password
68
+ # @option settings [Integer] :version (1) the Icinga2 API Version
69
+ # @option settings [Bool] :cluster Icinga2 Cluster Mode
70
+ # @option settings [Bool] :notifications (false) enable Icinga2 Host Notifications
71
+ #
72
+ # @example to create an new Instance
73
+ # config = {
74
+ # icinga: {
75
+ # host: '192.168.33.5',
76
+ # api: {
77
+ # port: 5665,
78
+ # user: 'root',
79
+ # password: 'icinga',
80
+ # version: 1
81
+ # },
82
+ # cluster: false,
83
+ # satellite: true
84
+ # }
85
+ # }
86
+ #
87
+ # @icinga = Icinga2::Client.new(config)
88
+ #
89
+ # @return [instance, #read]
90
+ #
91
+ def initialize( settings )
92
+
93
+ raise ArgumentError.new('only Hash are allowed') unless( settings.is_a?(Hash) )
94
+ raise ArgumentError.new('missing settings') if( settings.size.zero? )
95
+
96
+ icinga_host = settings.dig(:icinga, :host) || 'localhost'
97
+ icinga_api_port = settings.dig(:icinga, :api, :port) || 5665
98
+ icinga_api_user = settings.dig(:icinga, :api, :user)
99
+ icinga_api_pass = settings.dig(:icinga, :api, :password)
100
+ icinga_api_version = settings.dig(:icinga, :api, :version) || 1
101
+ icinga_api_pki_path = settings.dig(:icinga, :api, :pki_path)
102
+ icinga_api_node_name = settings.dig(:icinga, :api, :node_name)
103
+ @icinga_cluster = settings.dig(:icinga, :cluster) || false
104
+ @icinga_satellite = settings.dig(:icinga, :satellite)
105
+ @icinga_notifications = settings.dig(:icinga, :notifications) || false
106
+
107
+ @last_call_timeout = 320
108
+ @last_cib_data_called = 0
109
+ @last_status_data_called = 0
110
+ @last_application_data_called = 0
111
+ @last_service_objects_called = 0
112
+ @last_host_objects_called = 0
113
+
114
+ @icinga_api_url_base = format( 'https://%s:%d/v%s', icinga_host, icinga_api_port, icinga_api_version )
115
+
116
+ _has_cert, @options = cert?(
117
+ pki_path: icinga_api_pki_path,
118
+ node_name: icinga_api_node_name,
119
+ user: icinga_api_user,
120
+ password: icinga_api_pass
121
+ )
122
+
123
+ @headers = { 'Content-Type' => 'application/json', 'Accept' => 'application/json' }
124
+
125
+ self
126
+ end
127
+
128
+ # create a HTTP Header based on a Icinga2 Certificate or an User API Login
129
+ #
130
+ # @param [Hash, #read] params
131
+ # @option params [String] :pki_path the location of the Certificate Files
132
+ # @option params [String] :node_name the Icinga2 Hostname
133
+ # @option params [String] :user the Icinga2 API User
134
+ # @option params [String] :password the Icinga2 API Password
135
+ #
136
+ # @example with Certificate
137
+ # @icinga.cert?(pki_path: '/etc/icinga2', node_name: 'icinga2-dashing')
138
+ #
139
+ # @example with User
140
+ # @icinga.cert?(user: 'root', password: 'icinga')
141
+ #
142
+ # @return [Bool, #read]
143
+ #
144
+ def cert?( params )
145
+
146
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
147
+ raise ArgumentError.new('missing params') if( params.size.zero? )
148
+
149
+ pki_path = params.dig(:pki_path)
150
+ node_name = params.dig(:node_name)
151
+ user = params.dig(:user)
152
+ password = params.dig(:password)
153
+
154
+ if( node_name.nil? )
155
+ begin
156
+ node_name = Socket.gethostbyname(Socket.gethostname).first
157
+ logger.debug(format('node name: %s', node_name))
158
+ rescue SocketError => e
159
+
160
+ raise format("can't resolve hostname (%s)", e)
161
+ end
162
+ end
163
+
164
+ ssl_cert_file = format( '%s/%s.crt', pki_path, node_name )
165
+ ssl_key_file = format( '%s/%s.key', pki_path, node_name )
166
+ ssl_ca_file = format( '%s/ca.crt', pki_path )
167
+
168
+ if( File.file?( ssl_cert_file ) && File.file?( ssl_key_file ) && File.file?( ssl_ca_file ) )
169
+
170
+ logger.debug( 'PKI found, using client certificates for connection to Icinga 2 API' )
171
+
172
+ ssl_cert_file = File.read( ssl_cert_file )
173
+ ssl_key_file = File.read( ssl_key_file )
174
+ ssl_ca_file = File.read( ssl_ca_file )
175
+
176
+ cert = OpenSSL::X509::Certificate.new( ssl_cert_file )
177
+ key = OpenSSL::PKey::RSA.new( ssl_key_file )
178
+
179
+ [true, {
180
+ ssl_client_cert: cert,
181
+ ssl_client_key: key,
182
+ ssl_ca_file: ssl_ca_file,
183
+ verify_ssl: OpenSSL::SSL::VERIFY_NONE
184
+ } ]
185
+
186
+ else
187
+
188
+ logger.debug( 'PKI not found, using basic auth for connection to Icinga 2 API' )
189
+
190
+ raise ArgumentError.new('Missing user_name') if( user.nil? )
191
+ raise ArgumentError.new('Missing password') if( password.nil? )
192
+
193
+ [false, {
194
+ user: user,
195
+ password: password,
196
+ verify_ssl: OpenSSL::SSL::VERIFY_NONE
197
+ } ]
198
+ end
199
+
200
+ end
201
+
202
+ # return Icinga2 Application data
203
+ #
204
+ # @example
205
+ # @icinga.application_data
206
+ #
207
+ # @return [Hash]
208
+ #
209
+ def application_data
210
+
211
+ data = icinga_application_data(
212
+ url: format( '%s/status/IcingaApplication', @icinga_api_url_base ),
213
+ headers: @headers,
214
+ options: @options
215
+ )
216
+
217
+ #puts '---'
218
+ #puts data
219
+ #puts data.class.to_s
220
+ #puts '---'
221
+
222
+ return nil if( data.nil? )
223
+ # return data if( data.is_a?(Hash) && data.dig('code').to_i != 200 )
224
+ #puts '.'
225
+
226
+ app_data = data.dig('icingaapplication','app')
227
+
228
+ #puts '---'
229
+ #puts app_data
230
+ #puts app_data.class.to_s
231
+ #puts '---'
232
+
233
+ # version and revision
234
+ @version, @revision = parse_version(app_data.dig('version'))
235
+
236
+ # - node_name
237
+ @node_name = app_data.dig('node_name')
238
+
239
+ # - start_time
240
+ @start_time = Time.at(app_data.dig('program_start').to_f)
241
+
242
+ data
243
+ end
244
+
245
+ # return Icinga2 CIB
246
+ #
247
+ # @example
248
+ # @icinga.cib_data
249
+ #
250
+ # @return [Hash]
251
+ #
252
+ def cib_data
253
+
254
+ data = icinga_application_data(
255
+ url: format( '%s/status/CIB', @icinga_api_url_base ),
256
+ headers: @headers,
257
+ options: @options
258
+ )
259
+
260
+ return nil if( data.nil? )
261
+
262
+ @last_cib_data_called = 0 #Time.now.to_i
263
+
264
+ if( data.is_a?(Hash))
265
+
266
+ cib_data = data.clone
267
+
268
+ # extract
269
+ # - uptime
270
+ uptime = cib_data.dig('uptime').round(2)
271
+ @uptime = Time.at(uptime).utc.strftime('%H:%M:%S')
272
+ # - avg_latency / avg_execution_time
273
+ @avg_latency = cib_data.dig('avg_latency').round(2)
274
+ @avg_execution_time = cib_data.dig('avg_execution_time').round(2)
275
+
276
+ # - hosts
277
+ @hosts_up = cib_data.dig('num_hosts_up').to_i
278
+ @hosts_down = cib_data.dig('num_hosts_down').to_i
279
+ @hosts_pending = cib_data.dig('num_hosts_pending').to_i
280
+ @hosts_unreachable = cib_data.dig('num_hosts_unreachable').to_i
281
+ @hosts_in_downtime = cib_data.dig('num_hosts_in_downtime').to_i
282
+ @hosts_acknowledged = cib_data.dig('num_hosts_acknowledged').to_i
283
+
284
+ # - services
285
+ @services_ok = cib_data.dig('num_services_ok').to_i
286
+ @services_warning = cib_data.dig('num_services_warning').to_i
287
+ @services_critical = cib_data.dig('num_services_critical').to_i
288
+ @services_unknown = cib_data.dig('num_services_unknown').to_i
289
+ @services_pending = cib_data.dig('num_services_pending').to_i
290
+ @services_in_downtime = cib_data.dig('num_services_in_downtime').to_i
291
+ @services_acknowledged = cib_data.dig('num_services_acknowledged').to_i
292
+
293
+ # - check stats
294
+ @hosts_active_checks_1min = cib_data.dig('active_host_checks_1min')
295
+ @hosts_passive_checks_1min = cib_data.dig('passive_host_checks_1min')
296
+ @services_active_checks_1min = cib_data.dig('active_service_checks_1min')
297
+ @services_passive_checks_1min = cib_data.dig('passive_service_checks_1min')
298
+
299
+ end
300
+
301
+ data
302
+ end
303
+
304
+ # return Icinga2 Status Data
305
+ #
306
+ # @example
307
+ # @icinga.status_data
308
+ #
309
+ # @return [Hash]
310
+ #
311
+ def status_data
312
+
313
+ @last_status_data_called = Time.now.to_i
314
+
315
+ icinga_application_data(
316
+ url: format( '%s/status', @icinga_api_url_base ),
317
+ headers: @headers,
318
+ options: @options
319
+ )
320
+ end
321
+
322
+ # return Icinga2 API Listener
323
+ #
324
+ # @example
325
+ # @icinga.api_listener
326
+ #
327
+ # @return [Hash]
328
+ #
329
+ def api_listener
330
+
331
+ @last_application_data_called = Time.now.to_i
332
+
333
+ icinga_application_data(
334
+ url: format( '%s/status/ApiListener', @icinga_api_url_base ),
335
+ headers: @headers,
336
+ options: @options
337
+ )
338
+ end
339
+
340
+ # check the availability of a Icinga network connect
341
+ #
342
+ # @example
343
+ # @icinga.available?
344
+ #
345
+ # @return [Bool]
346
+ #
347
+ def available?
348
+
349
+ data = application_data
350
+
351
+ return true unless( data.nil? )
352
+
353
+ false
354
+ end
355
+
356
+ # return Icinga2 version and revision
357
+ #
358
+ # @example
359
+ # @icinga.version.values
360
+ #
361
+ # v = @icinga.version
362
+ # version = v.dig(:version)
363
+ #
364
+ # @return [Hash]
365
+ # * version
366
+ # * revision
367
+ #
368
+ def version
369
+
370
+ application_data if((Time.now.to_i - @last_application_data_called).to_i > @last_call_timeout)
371
+
372
+ version = @version.nil? ? 0 : @version
373
+ revision = @revision.nil? ? 0 : @revision
374
+
375
+ {
376
+ version: version.to_s,
377
+ revision: revision.to_s
378
+ }
379
+ end
380
+
381
+ # return Icinga2 node_name
382
+ #
383
+ # @example
384
+ # @icinga.node_name
385
+ #
386
+ # @return [String]
387
+ #
388
+ def node_name
389
+
390
+ application_data if((Time.now.to_i - @last_application_data_called).to_i > @last_call_timeout)
391
+
392
+ return @node_name if( @node_name )
393
+
394
+ nil
395
+ end
396
+
397
+ # return Icinga2 start time
398
+ #
399
+ # @example
400
+ # @icinga.start_time
401
+ #
402
+ # @return [String]
403
+ #
404
+ def start_time
405
+
406
+ application_data if((Time.now.to_i - @last_application_data_called).to_i > @last_call_timeout)
407
+
408
+ return @start_time if( @start_time )
409
+
410
+ nil
411
+ end
412
+
413
+ # return Icinga2 uptime
414
+ #
415
+ # @example
416
+ # @icinga.cib_data
417
+ # @icinga.uptime
418
+ #
419
+ # @return [String]
420
+ #
421
+ def uptime
422
+
423
+ cib_data if((Time.now.to_i - @last_cib_data_called).to_i > @last_call_timeout)
424
+
425
+ return @uptime if( @uptime )
426
+
427
+ nil
428
+ end
429
+
430
+ end
431
+ end
@@ -10,7 +10,7 @@ module Icinga2
10
10
  #
11
11
  # @param [Hash] params
12
12
  # @option params [String] :name
13
- # @option params [String] :host
13
+ # @option params [String] :host_name
14
14
  # @option params [String] :host_group
15
15
  # @option params [Integer] :start_time (Time.new.to_i)
16
16
  # @option params [Integer] :end_time
@@ -22,7 +22,7 @@ module Icinga2
22
22
  # param = {
23
23
  # name: 'test',
24
24
  # type: 'service',
25
- # host: 'icinga2',
25
+ # host_name: 'icinga2',
26
26
  # comment: 'test downtime',
27
27
  # author: 'icingaadmin',
28
28
  # start_time: Time.now.to_i,
@@ -34,17 +34,24 @@ module Icinga2
34
34
  #
35
35
  def add_downtime( params )
36
36
 
37
- raise ArgumentError.new('only Hash are allowed') unless( params.is_a?(Hash) )
38
- raise ArgumentError.new('missing params') if( params.size.zero? )
37
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
38
+ raise ArgumentError.new('missing \'params\'') if( params.size.zero? )
39
39
 
40
40
  name = params.dig(:name)
41
- host_name = params.dig(:host)
41
+ host_name = params.dig(:host_name)
42
42
  host_group = params.dig(:host_group)
43
43
  start_time = params.dig(:start_time) || Time.now.to_i
44
44
  end_time = params.dig(:end_time)
45
45
  author = params.dig(:author)
46
46
  comment = params.dig(:comment)
47
47
  type = params.dig(:type)
48
+ fixed = params.dig(:fixed) || true
49
+ duration = params.dig(:duration) || 30
50
+ entry_time = params.dig(:entry_time)
51
+ scheduled_by = params.dig(:scheduled_by)
52
+ service_name = params.dig(:service_name)
53
+ triggered_by = params.dig(:triggered_by)
54
+ config_owner = params.dig(:config_owner)
48
55
  filter = nil
49
56
 
50
57
  # sanitychecks
@@ -53,11 +60,18 @@ module Icinga2
53
60
  raise ArgumentError.new("wrong downtype type. only 'host' or' service' allowed ('#{type}' giving)") if( %w[host service].include?(type.downcase) == false )
54
61
  raise ArgumentError.new('choose host or host_group, not both') if( !host_group.nil? && !host_name.nil? )
55
62
  raise ArgumentError.new('Missing downtime author') if( author.nil? )
56
- raise ArgumentError.new("these author ar not exists: #{author}") unless( exists_user?( author ) )
63
+ raise ArgumentError.new("these author are not exists: #{author}") unless( exists_user?( author ) )
57
64
  raise ArgumentError.new('Missing downtime comment') if( comment.nil? )
58
65
  raise ArgumentError.new('Missing downtime end_time') if( end_time.nil? )
59
66
  raise ArgumentError.new('end_time are equal or smaller then start_time') if( end_time.to_i <= start_time )
60
67
 
68
+ raise ArgumentError.new(format('wrong type. \'duration\' must be Integer, given \'%s\'', duration.class.to_s)) unless( duration.is_a?(Integer) )
69
+ raise ArgumentError.new(format('wrong type. \'fixed\' must be True or False, given \'%s\'', fixed.class.to_s)) unless( fixed.is_a?(TrueClass) || fixed.is_a?(FalseClass) )
70
+
71
+ # TODO
72
+ # check if host_name exists!
73
+
74
+
61
75
  if( !host_name.nil? )
62
76
 
63
77
  filter = format( 'host.name=="%s"', host_name )
@@ -69,17 +83,27 @@ module Icinga2
69
83
  end
70
84
 
71
85
  payload = {
72
- 'type' => type.capitalize, # we need the first char as Uppercase
73
- 'start_time' => start_time,
74
- 'end_time' => end_time,
75
- 'author' => author,
76
- 'comment' => comment,
77
- 'fixed' => true,
78
- 'duration' => 30,
79
- 'filter' => filter
86
+ type: type.capitalize, # we need the first char as Uppercase
87
+ start_time: start_time,
88
+ end_time: end_time,
89
+ author: author,
90
+ comment: comment,
91
+ fixed: fixed,
92
+ duration: duration,
93
+ filter: filter,
94
+ entry_time: entry_time,
95
+ scheduled_by: scheduled_by,
96
+ host_name: host_name,
97
+ host_group: host_group,
98
+ service_name: service_name,
99
+ triggered_by: triggered_by,
100
+ config_owner: config_owner
80
101
  }
81
102
 
82
- Network.post(
103
+ # remove all empty attrs
104
+ payload.reject!{ |_k, v| v.nil? }
105
+
106
+ post(
83
107
  url: format( '%s/actions/schedule-downtime', @icinga_api_url_base ),
84
108
  headers: @headers,
85
109
  options: @options,
@@ -96,15 +120,11 @@ module Icinga2
96
120
  #
97
121
  def downtimes
98
122
 
99
- data = Network.api_data(
123
+ api_data(
100
124
  url: format( '%s/objects/downtimes' , @icinga_api_url_base ),
101
125
  headers: @headers,
102
126
  options: @options
103
127
  )
104
-
105
- return data.dig('results') if( data.dig(:status).nil? )
106
-
107
- nil
108
128
  end
109
129
 
110
130
  end