zabcon 0.0.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.
data/libs/printer.rb ADDED
@@ -0,0 +1,383 @@
1
+ #GPL 2.0 http://www.gnu.org/licenses/gpl-2.0.html
2
+ #Zabbix CLI Tool and associated files
3
+ #Copyright (C) 2009,2010 Andrew Nelson nelsonab(at)red-tux(dot)net
4
+ #
5
+ #This program is free software; you can redistribute it and/or
6
+ #modify it under the terms of the GNU General Public License
7
+ #as published by the Free Software Foundation; either version 2
8
+ #of the License, or (at your option) any later version.
9
+ #
10
+ #This program is distributed in the hope that it will be useful,
11
+ #but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ #GNU General Public License for more details.
14
+ #
15
+ #You should have received a copy of the GNU General Public License
16
+ #along with this program; if not, write to the Free Software
17
+ #Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
+
19
+
20
+ ##########################################
21
+ # Subversion information
22
+ # $Id: printer.rb 258 2010-12-28 22:49:21Z nelsonab $
23
+ # $Revision: 258 $
24
+ ##########################################
25
+
26
+ require 'libs/zdebug'
27
+ require 'libs/zabcon_globals'
28
+
29
+ if RUBY_PLATFORM =~ /.*?mswin.*?/
30
+ require 'Win32API'
31
+
32
+ Kbhit = Win32API.new("crtdll", "_kbhit", 'V', 'L')
33
+ Getch = Win32API.new("crtdll", "_getch", 'V', 'L')
34
+
35
+ def getch
36
+ Getch.call
37
+ end
38
+
39
+ def kbhit
40
+ Kbhit.call
41
+ end
42
+ end
43
+
44
+ require 'highline/system_extensions'
45
+
46
+ class OutputPrinter
47
+
48
+ include ZDebug
49
+ include HighLine::SystemExtensions
50
+
51
+ NILCHAR="--"
52
+
53
+ # attr_accessor :sheight
54
+
55
+ # Class initializer
56
+ # Interactive mode will not be implemented for a while so the variable
57
+ # is more of a place holder for now. Interactive
58
+ def initialize(interactive=true)
59
+ @swidth=80 # screen width
60
+ @interactive=interactive # mode for output
61
+ @lines=0 # how many lines have been displayed thus far?
62
+
63
+ # Check the environment variables to see if screen height has been set
64
+ EnvVars.instance["sheight"]=25 if EnvVars.instance["sheight"].nil?
65
+ end
66
+
67
+ def hash_width(item)
68
+ w=0
69
+ item.each do |value, index|
70
+ w+= value.length + index.length + 6 # 6 is for " => " and ", "
71
+ end
72
+ w-=2 # subtract out last comma and space
73
+ return w
74
+ end
75
+
76
+ def array_width(item)
77
+ w=0
78
+ item.each do |value|
79
+ w+=value.length + 2 # 2 is for ", "
80
+ end
81
+ w-=2 # remove last comma and space
82
+ return w
83
+ end
84
+
85
+ def getitemwidth(item)
86
+ retval=0
87
+ return NILCHAR.length if item.nil?
88
+ case item.class.to_s
89
+ when "String"
90
+ retval=item.length
91
+ when "Fixnum"
92
+ retval=item.to_s.length
93
+ when "Float"
94
+ retval=item.to_s.length
95
+ when "Hash"
96
+ retval=hash_width(item)
97
+ when "Array"
98
+ retval=array_width(item)
99
+ else
100
+ p item
101
+ raise "getitemwidth - item.class: #{item.class} not supported"
102
+ end
103
+ retval
104
+ end
105
+
106
+ # determines the max col width for each colum
107
+ # may need to be optimized in the future
108
+ # possible optimization may include randomly iterating through list for large lists
109
+ # if dataset is an array headers is ignored, also an integer is returned not an array
110
+ # of widths
111
+ def getcolwidth(dataset,headers=nil)
112
+ if dataset.class==Array then
113
+ widths=headers.collect {|x| 0} # setup our resultant array of widths
114
+
115
+ # check the widths for the headers
116
+ headers.each_with_index { |value, index| widths[index] = value.length }
117
+
118
+ if (dataset.length>0) and (dataset[0].class!=Hash) then
119
+ width=0
120
+ dataset.each do |item|
121
+ w=getitemwidth(item)
122
+ widths[0] = widths[0]<w ? w : widths[0] # 0 because there's one column
123
+ end
124
+ return widths
125
+ elsif dataset[0].class==Hash then
126
+ raise "getcolwidth headers are nil" if headers.nil?
127
+ dataset.each do |row|
128
+ headers.each_with_index do |value, index|
129
+ width=getitemwidth(row[value])
130
+ val= widths[index] # storing value for efficiency, next statement might have two of this call
131
+ widths[index]= val < width ? width : val
132
+ end
133
+ end
134
+
135
+ return widths
136
+ else
137
+ raise "getcolwidth Unknown internal data type"
138
+ end
139
+ else
140
+ raise "getcolwidth - dataset type not supported: #{dataset.class}" # need to raise an error
141
+ end
142
+ end
143
+
144
+ def format_hash_for_print(item)
145
+ s = ""
146
+ item.each do |value, index|
147
+ s << ", " if !s.empty?
148
+ s << index << " => " << value
149
+ end
150
+ return s
151
+ end
152
+
153
+ def format_for_print(item)
154
+ if item.nil? || item==[]
155
+ return NILCHAR
156
+ else
157
+ case item.class.to_s
158
+ when "Hash"
159
+ return format_hash_for_print(item)
160
+ else
161
+ return item
162
+ end
163
+ end
164
+ end
165
+
166
+ # Pause output function
167
+ # This function will pause output after n lines have been printed
168
+ # n is defined by the lines parameter
169
+ # If interactive output has been disabled pause will not stop
170
+ # after n lines have been printed
171
+ # If @lines is set to -1 a side effect is created where pause is disabled.
172
+ def pause? (lines=1)
173
+ if @interactive and EnvVars.instance["sheight"]>0 and (@lines>-1) then
174
+ @lines += lines
175
+ if @lines>=(EnvVars.instance["sheight"]-1) then
176
+ pause_msg = "Pause, q to quit, a to stop pausing output"
177
+ Kernel.print pause_msg
178
+ if RUBY_PLATFORM =~ /.*?mswin.*?/
179
+ while kbhit==0
180
+ sleep 0.3
181
+ # putc('.')
182
+ end
183
+ chr=getch
184
+ puts chr
185
+ else
186
+ begin
187
+ chr=get_character
188
+ rescue Interrupt # trap ctrl-c and create side effect to behave like "q" was pressed
189
+ chr=113
190
+ end
191
+
192
+ # erase characters on the current line, and move the cursor left the size of pause_msg
193
+ Kernel.print "\033[2K\033[#{pause_msg.length}D"
194
+
195
+ if (chr==113) or (chr==81) then # 113="q" 81="Q"
196
+ raise "quit"
197
+ end
198
+ if (chr==65) or (chr==97) then # 65="A" 97="a
199
+ @lines=-1
200
+ end
201
+ end
202
+ @lines= (@lines==-1) ? -1:0 # if we set @lines to -1 make sure the side effect propagates
203
+ end
204
+ end
205
+ end
206
+
207
+ def printline(widths)
208
+ output="+"
209
+ widths.each { |width| output+="-"+("-"*width)+"-+" }
210
+ pause? 1
211
+ puts output
212
+ end
213
+
214
+ #Prints the table header
215
+ #header: Array of strings in the print order for the table header
216
+ #order: Array of strings denoting output order
217
+ #widths: (optional) Array of numbers denoting the width of each field
218
+ #separator: (optional) Separator character
219
+ def printheader(header, widths=nil, separator=",")
220
+ if widths.nil?
221
+ output=""
222
+ header.each do |value|
223
+ output+="#{value}#{separator}"
224
+ end
225
+ separator.length.times {output.chop!}
226
+ else
227
+ output="|"
228
+ header.each_with_index do |value, index|
229
+ output+=" %-#{widths[index]}s |" % value
230
+ end
231
+ end
232
+ puts output
233
+ pause? 1
234
+ end
235
+
236
+ #Requires 2 arguments and 2 optional arguments
237
+ #row: The Row of data
238
+ #order: An array of field names with the order in which they are to be printed
239
+ #Optional arguments
240
+ #widths: An array denoting the width of each field, if nul a table separated by separator will be printed
241
+ #separator: the separator character to be used
242
+ def printrow(row, order, widths=nil, separator=',')
243
+ if widths.nil?
244
+ output=""
245
+ order.each_with_index do |value, index|
246
+ output+="#{row[value]}#{separator}"
247
+ end
248
+ separator.length.times { output.chop! } #remove the last separator
249
+ puts output
250
+ else
251
+ output="|"
252
+ order.each_with_index do |value, index|
253
+ output+=" %-#{widths[index]}s |" % format_for_print(row[value])
254
+ end
255
+ puts output
256
+ end
257
+ pause? 1
258
+ end
259
+
260
+ def print_array(dataset,cols)
261
+ debug(6,dataset,"dataset",150)
262
+ debug(6,cols,"cols",50)
263
+ count=0
264
+ type=dataset[:class]
265
+ results=dataset[:result]
266
+
267
+ debug(6,type,"Array type")
268
+
269
+ puts "#{dataset[:class].to_s.capitalize} result set" if EnvVars.instance["echo"]
270
+
271
+ if results.length==0
272
+ puts "Result set empty"
273
+ elsif results[0].class==Hash then
274
+ debug(7,"Results type is Hash")
275
+ header=[]
276
+ if cols.nil? then
277
+ case type
278
+ when :user
279
+ header=["userid","alias"]
280
+ when :host
281
+ header=["hostid","host"]
282
+ when :item
283
+ header=["itemid","description","key_"]
284
+ when :hostgroup
285
+ header=["groupid","name"]
286
+ when :hostgroupid
287
+ header=["name", "groupid", "internal"]
288
+ when :raw
289
+ header=results[0].keys
290
+ when nil
291
+ header=results[0].keys
292
+ end
293
+ elsif cols.class==Array
294
+ header=cols
295
+ elsif cols=="all" then
296
+ puts "all cols"
297
+ results[0].each_key { |key| header<<key }
298
+ else
299
+ header=cols.split(',')
300
+ end
301
+
302
+ debug(6,header,"header")
303
+
304
+ widths=getcolwidth(results,header)
305
+
306
+ if EnvVars.instance["table_output"]
307
+ if EnvVars.instance["table_header"]
308
+ printline(widths)
309
+ printheader(header,widths)
310
+ end
311
+ printline(widths)
312
+ results.each { |row| printrow(row,header,widths) }
313
+ printline(widths)
314
+ puts "#{results.length} rows total"
315
+ else
316
+ printheader(header,nil,EnvVars.instance["table_separator"]) if EnvVars.instance["table_header"]
317
+ results.each { | row| printrow(row,header,nil,EnvVars.instance["table_separator"]) }
318
+ end
319
+
320
+
321
+ else
322
+ debug(7,"Results type is not Hash, assuming array")
323
+ widths = getcolwidth(results,["id"]) # always returns an array of widths
324
+
325
+ printline(widths) # hacking parameters to overload functions
326
+ printheader(["id"],widths)
327
+ printline(widths)
328
+
329
+ results.each { |item| printrow({"id"=>item},["id"],widths) }
330
+ printline(widths)
331
+ puts "#{results.length} rows total"
332
+ end
333
+ end
334
+
335
+ def print_hash(dataset,cols)
336
+ puts "Hash object printing not implemented, here is the raw result"
337
+ p dataset
338
+ end
339
+
340
+
341
+ def print(dataset,cols)
342
+ begin
343
+ debug(6,dataset,"Dataset",200)
344
+ debug(6,cols,"Cols",40)
345
+ @lines=0
346
+ if !cols #cols==nil
347
+ cols_to_show=nil
348
+ else
349
+ cols_to_show=cols.empty? ? nil : cols[:show]
350
+ end
351
+
352
+ puts dataset[:message] if dataset[:message]
353
+
354
+ # p dataset[:result].class
355
+ if dataset[:result].class==Array then
356
+ print_array(dataset,cols_to_show)
357
+ elsif dataset[:result].class==Hash then
358
+ print_hash(dataset,cols_to_show)
359
+ elsif dataset[:result].class!=NilClass then
360
+ puts "Unknown object received by the print routint"
361
+ puts "Class type: #{dataset[:result].class}"
362
+ puts "Data:"
363
+ p dataset[:result]
364
+ end
365
+ rescue TypeError
366
+ puts "***********************************************************"
367
+ puts "Whoops!"
368
+ puts "Looks like we got some data we didn't know how to print."
369
+ puts "This may be worth submitting as a bug. If you submit a bug"
370
+ puts "report be sure to include this output and the command you"
371
+ puts "executed to get this message. http://trac.red-tux.net"
372
+ puts "data received:"
373
+ p dataset
374
+ rescue RuntimeError => e
375
+ if e.message=="quit" then
376
+ puts "Output stopped"
377
+ else
378
+ raise e
379
+ end
380
+ end
381
+ end
382
+
383
+ end
@@ -0,0 +1,742 @@
1
+ #!/usr/bin/ruby
2
+
3
+ #GPL 2.0 http://www.gnu.org/licenses/gpl-2.0.html
4
+ #Zabbix CLI Tool and associated files
5
+ #Copyright (C) 2009,2010 Andrew Nelson nelsonab(at)red-tux(dot)net
6
+ #
7
+ #This program is free software; you can redistribute it and/or
8
+ #modify it under the terms of the GNU General Public License
9
+ #as published by the Free Software Foundation; either version 2
10
+ #of the License, or (at your option) any later version.
11
+ #
12
+ #This program is distributed in the hope that it will be useful,
13
+ #but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ #MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ #GNU General Public License for more details.
16
+ #
17
+ #You should have received a copy of the GNU General Public License
18
+ #along with this program; if not, write to the Free Software
19
+ #Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
+
21
+ ##########################################
22
+ # Subversion information
23
+ # $Id: $
24
+ # $Revision: 258 $
25
+ ##########################################
26
+
27
+ require 'parseconfig'
28
+ require 'ostruct'
29
+ require 'rexml/document'
30
+ require 'libs/zbxcliserver'
31
+ require 'libs/printer'
32
+ require 'libs/zdebug'
33
+ require 'libs/input'
34
+ require 'libs/defines'
35
+ require 'libs/command_tree'
36
+ require 'libs/argument_processor'
37
+ require 'libs/command_help'
38
+ require 'libs/zabcon_globals'
39
+
40
+
41
+ class ZabconCore
42
+
43
+ include ZDebug
44
+
45
+ def initialize()
46
+ @env = EnvVars.instance # make it easier to call the global EnvVars singleton
47
+
48
+ # This must be set first or the debug module will throw an error
49
+ set_debug_level(@env["debug"])
50
+
51
+ @env.register_notifier("debug",self.method(:set_debug_level))
52
+ @env.register_notifier("api_debug",self.method(:set_debug_api_level))
53
+
54
+ @server = nil
55
+ @callbacks={}
56
+ @printer=OutputPrinter.new
57
+ @commands=nil
58
+ @setvars={}
59
+ debug(5,"Setting up help")
60
+ @cmd_help=CommandHelp.new("english") # Setup help functions, determine default language to use
61
+ debug(5,"Setting up ArgumentProcessor")
62
+ @arg_processor=ArgumentProcessor.new # Need to instantiate for debug routines
63
+
64
+ if !@env["server"].nil? and !@env["username"].nil? and !@env["password"].nil? then
65
+ puts "Found valid login credentials, attempting login" if @env["echo"]
66
+ begin
67
+ do_login({:server=>@env["server"], :username=>@env["username"],:password=>@env["password"]})
68
+ rescue ZbxAPI_ExceptionLoginPermission
69
+ puts "Error Invalid login or no API permissions."
70
+ end
71
+ end
72
+
73
+ debug(5,"Setting up prompt")
74
+ @debug_prompt=false
75
+ if @env["have_tty"]
76
+ prc=Proc.new do
77
+ debug_part = @debug_prompt ? " #{debug_level}" : ""
78
+ if @server.nil?
79
+ " #{debug_part}-> "
80
+ else
81
+ @server.login? ? " #{debug_part}+> " : " #{debug_part}-> "
82
+ end
83
+ end
84
+ @input=Readline_Input.new
85
+ @input.set_prompt_func(prc)
86
+ else
87
+ @input=STDIN_Input.new
88
+ end
89
+ debug(5,"Setup complete")
90
+ end
91
+
92
+ # Argument logged in is used to determine which set of commands to load. If loggedin is true then commands which
93
+ # require a valid login are loaded
94
+ def setupcommands(loggedin)
95
+ debug(5,loggedin,"Starting setupcommands (loggedin)")
96
+
97
+ @commands=Parser.new(@arg_processor.default)
98
+
99
+ no_cmd=nil
100
+ no_args=nil
101
+ no_help=nil
102
+ no_verify=nil
103
+
104
+ login_required = lambda {
105
+ debug(6,"Lambda 'login_required'")
106
+ puts "Login required"
107
+ }
108
+
109
+ # parameters for insert: insert_path, command, commandproc, arguments=[], helpproc=nil, verify_func=nil, options
110
+
111
+ # These commands do not require a valid login
112
+ @commands.insert "", "quit", :exit
113
+ @commands.insert "", "exit", :exit
114
+ @commands.insert "", "help", :help,no_args,no_help,@arg_processor.help, :suppress_printer
115
+
116
+ @commands.insert "", "hisotry", self.method(:do_history),no_args,no_help,no_verify,:suppress_printer
117
+ @commands.insert "", "info", self.method(:do_info),no_args,no_help,no_verify,:suppress_printer
118
+ @commands.insert "", "load", no_cmd,no_args,no_help,no_verify,:suppress_printer
119
+ @commands.insert "", "login", self.method(:do_login),nil,nil,@arg_processor.method(:login), :suppress_printer
120
+ @commands.insert "", "set", no_cmd
121
+ @commands.insert "", "show", no_cmd
122
+ @commands.insert "", "unset", no_cmd
123
+ @commands.insert "load", "config", @env.method(:load_config),no_args,no_help,no_verify,:suppress_printer
124
+ @commands.insert "set", "debug", self.method(:set_debug),no_args,no_help,no_verify,:suppress_printer
125
+ @commands.insert "set", "lines", self.method(:set_lines),no_args,no_help,no_verify,:suppress_printer
126
+ @commands.insert "set", "pause", self.method(:set_pause),no_args,no_help,no_verify,:suppress_printer
127
+ @commands.insert "set", "var", self.method(:set_var), no_args, no_help, @arg_processor.method(:simple_processor),:suppress_printer
128
+ @commands.insert "set", "env", self.method(:set_env), no_args, no_help, @arg_processor.method(:simple_processor), :suppress_printer
129
+ @commands.insert "show", "var", self.method(:show_var), no_args, no_help, @arg_processor.method(:array_processor), :suppress_printer
130
+ @commands.insert "show", "env", self.method(:show_env), no_args, no_help, @arg_processor.method(:array_processor), :suppress_printer
131
+ @commands.insert "unset", "var", self.method(:unset_var), no_args, no_help, @arg_processor.method(:array_processor), :suppress_printer
132
+
133
+ if loggedin then
134
+ debug(5,"Inserting commands which require login")
135
+ # This command tree is for a valid login
136
+ @commands.insert "", "raw", no_cmd
137
+ @commands.insert "", "add", no_cmd
138
+ @commands.insert "", "delete", no_cmd
139
+ @commands.insert "", "get", no_cmd, no_args, @cmd_help.method(:get)
140
+ @commands.insert "", "import", self.method(:do_import),no_args,no_help,no_verify
141
+ @commands.insert "", "update", no_cmd
142
+
143
+ @commands.insert "add", "app", @server.method(:addapp),no_args,no_help,no_verify
144
+ @commands.insert "add app", "id", @server.method(:getappid),no_args,no_help,no_verify
145
+ @commands.insert "add", "host", @server.method(:addhost), no_args, @cmd_help.method(:add_host), @arg_processor.method(:add_host), :server => @server
146
+ @commands.insert "add host", "group", @server.method(:addhostgroup),no_args,no_help,no_verify
147
+ @commands.insert "add", "item", @server.method(:additem), no_args, @cmd_help.method(:add_item), @arg_processor.method(:add_item)
148
+ @commands.insert "add", "link", @server.method(:addlink),no_args,no_help,no_verify
149
+ @commands.insert "add link", "trigger", @server.method(:addlinktrigger),no_args,no_help,no_verify
150
+ @commands.insert "add", "sysmap", @server.method(:addsysmap),no_args,no_help,no_verify
151
+ @commands.insert "add sysmap", "element", @server.method(:addelementtosysmap),no_args,no_help,no_verify
152
+ @commands.insert "add", "trigger", @server.method(:addtrigger),no_args,no_help,no_verify
153
+ @commands.insert "add", "user", @server.method(:adduser), no_args, @cmd_help.method(:add_user), @arg_processor.method(:add_user)
154
+ @commands.insert "add user", "media", @server.method(:addusermedia),no_args,@cmd_help.method(:add_user_media),no_verify
155
+
156
+ @commands.insert "get", "app", @server.method(:getapp), no_args, no_help, @arg_processor.default_get
157
+ @commands.insert "get", "host", @server.method(:gethost), no_args, no_help, @arg_processor.default_get
158
+ @commands.insert "get host", "group", @server.method(:gethostgroup), no_args, no_help, @arg_processor.default_get
159
+ @commands.insert "get host group", "id", @server.method(:gethostgroupid), no_args, no_help, @arg_processor.method(:get_group_id)
160
+ @commands.insert "get", "item", @server.method(:getitem),
161
+ ['itemids','hostids','groupids', 'triggerids','applicationids','status','templated_items','editable','count','pattern','limit','order', 'show'],
162
+ @cmd_help.method(:get_item), @arg_processor.default_get
163
+ @commands.insert "get", "seid", @server.method(:getseid), no_args, no_help, @arg_processor.default_get
164
+ @commands.insert "get", "trigger", @server.method(:gettrigger), no_args, no_help, @arg_processor.default_get
165
+ @commands.insert "get", "user", @server.method(:getuser),['show'], @cmd_help.method(:get_user), @arg_processor.method(:get_user)
166
+
167
+ @commands.insert "delete", "user", @server.method(:deleteuser), ['id'], @cmd_help.method(:delete_user), @arg_processor.method(:delete_user)
168
+ @commands.insert "delete", "host", @server.method(:deletehost), no_args, @cmd_help.method(:delete_host), @arg_processor.method(:delete_host)
169
+ @commands.insert "delete", "item", @server.method(:deleteitem), ['itemid'], @cmd_help.method(:delete_item), @arg_processor.default
170
+
171
+ @commands.insert "raw", "api", @server.method(:raw_api), no_args, @cmd_help.method(:raw_api), @arg_processor.method(:raw_api)
172
+ @commands.insert "raw", "json", @server.method(:raw_json), no_args, @cmd_help.method(:raw_json), @arg_processor.method(:raw_processor)
173
+
174
+ @commands.insert "update", "user", @server.method(:updateuser), no_args, no_help, no_verify
175
+ else
176
+ debug(5,"Inserting commands which do not require login")
177
+ # This command tree is for no login
178
+ @commands.insert "", "add", no_cmd
179
+ @commands.insert "", "delete", no_cmd
180
+ @commands.insert "", "get", no_cmd
181
+ @commands.insert "", "import", no_cmd,no_args,no_help
182
+ @commands.insert "", "update", no_cmd
183
+
184
+ @commands.insert "add", "app", login_required,no_args,no_help
185
+ @commands.insert "add app", "id", login_required,no_args,no_help
186
+ @commands.insert "add", "host", login_required, no_args, @cmd_help.method(:add_host)
187
+ @commands.insert "add host", "group", login_required,no_args,no_help
188
+ @commands.insert "add", "item", login_required, no_args, no_help
189
+ @commands.insert "add", "link", login_required,no_args,no_help
190
+ @commands.insert "add link", "trigger", login_required,no_args,no_help
191
+ @commands.insert "add", "sysmap", login_required,no_args,no_help
192
+ @commands.insert "add sysmap", "element", login_required,no_args,no_help
193
+ @commands.insert "add", "trigger", login_required,no_args,no_help
194
+ @commands.insert "add", "user", login_required, no_args, @cmd_help.method(:add_user)
195
+ @commands.insert "add user", "media", login_required,no_args,no_help
196
+
197
+ @commands.insert "get", "app", login_required, no_args, no_help
198
+ @commands.insert "get", "host", login_required, no_args, no_help
199
+ @commands.insert "get host", "group", login_required, no_args, no_help
200
+ @commands.insert "get host group", "id", login_required, no_args, no_help
201
+ @commands.insert "get", "item", login_required, no_args, no_help
202
+ @commands.insert "get", "seid", login_required, no_args, no_help
203
+ @commands.insert "get", "trigger", login_required, no_args, no_help
204
+ @commands.insert "get", "user", login_required,no_args, @cmd_help.method(:get_user)
205
+
206
+ @commands.insert "delete", "user", login_required, no_args, @cmd_help.method(:delete_user)
207
+ @commands.insert "delete", "host", login_required, no_args, @cmd_help.method(:delete_host)
208
+
209
+ @commands.insert "update", "user", login_required, no_args, no_help
210
+ end
211
+ end
212
+
213
+ def start
214
+ debug(5,"Entering main zabcon start routine")
215
+ puts "Welcome to Zabcon." if @env["echo"]
216
+ puts "Use the command 'help' to get help on commands" if @env["have_tty"] || @env["echo"]
217
+
218
+ setupcommands(!@server.nil?) # If we don't have a valid server we're not logged in'
219
+ begin
220
+ while line=@input.get_line()
221
+ line=@arg_processor.strip_comments(line) # use the argument processor's comment stripper'
222
+ next if line.nil?
223
+ next if line.strip.length==0 # don't bother parsing an empty line'
224
+ debug(6, line, "Input from user")
225
+
226
+ # this statement calls the command tree parser and sets up rhash
227
+ # for later use and function calls
228
+ rhash=@commands.parse(line, @setvars)
229
+
230
+ debug(6, rhash, "Results from parse")
231
+
232
+ next if rhash.nil?
233
+ case rhash[:proc]
234
+ when :exit
235
+ break
236
+ when :help
237
+ @cmd_help.help(@commands,line)
238
+ else
239
+ if !rhash[:proc].nil?
240
+ debug(4,rhash,"Calling function",250)
241
+ results=rhash[:proc].call(rhash[:api_params])
242
+ printing = rhash[:options].nil? ? true : rhash[:options][:suppress_printer].nil? ? true : false
243
+ @printer.print(results,rhash[:show_params]) if !results.nil? if printing
244
+ end
245
+ end # case
246
+
247
+ end # while
248
+ rescue ParseError => e #catch the base exception class
249
+ e.show_message
250
+ retry if e.retry?
251
+ rescue ZbxAPI_ExceptionVersion => e
252
+ puts e
253
+ retry # We will allow for graceful recover from Version exceptions
254
+ rescue ZbxAPI_ExceptionLoginPermission
255
+ puts "No login permissions"
256
+ retry
257
+ rescue ZbxAPI_ExceptionPermissionError
258
+ puts "You do not have permission to perform that operation"
259
+ retry
260
+ rescue ZbxAPI_GeneralError => e
261
+ puts "An error was received from the Zabbix server"
262
+ if e.message.class==Hash
263
+ puts "Error code: #{e.message["code"]}"
264
+ puts "Error message: #{e.message["message"]}"
265
+ puts "Error data: #{e.message["data"]}"
266
+ retry
267
+ else
268
+ e.show_message
269
+ # ddputs "Error: #{e.message}"
270
+ retry if e.retry?
271
+ end
272
+ rescue ZError => e
273
+ puts
274
+ if e.retry?
275
+ puts "A non-fatal error occurred."
276
+ else
277
+ puts "A fatal error occurred."
278
+ end
279
+ e.show_message
280
+ e.show_backtrace
281
+ retry if e.retry?
282
+ end #end of exception block
283
+ end # def
284
+
285
+ def getprompt
286
+ debug_part = @debug_prompt ? " #{debug_level}" : ""
287
+ if @server.nil?
288
+ return " #{debug_part}-> "
289
+ end
290
+ return @server.login? ? " #{debug_part}+> " : " #{debug_part}-> "
291
+ end
292
+
293
+ def do_history(input)
294
+ history = @input.history.to_a
295
+ history.each_index do |index|
296
+ puts "#{index}: #{history[index]}"
297
+ end
298
+ end
299
+
300
+ # set_debug is for the callback to set the debug level
301
+ # todo
302
+ # This command is now deprecated for "set env debug=n"
303
+ def set_debug(input)
304
+ if input["prompt"].nil? then
305
+ puts "This command is deprecated, please use \"set env debug=n\""
306
+ @env["debug"]=input.keys[0].to_i
307
+ else
308
+ @debug_prompt=!@debug_prompt
309
+ end
310
+ end
311
+
312
+ def set_debug_api_level(value)
313
+ puts "inside set_debug_api_level"
314
+ set_facility_debug_level(:api,value)
315
+ end
316
+
317
+ def set_lines(input)
318
+ @printer.sheight=input.keys[0].to_i
319
+ end
320
+
321
+ def set_pause(input)
322
+ if input.nil? then
323
+ puts "set pause requires either Off or On"
324
+ return
325
+ end
326
+
327
+ if input.keys[0].upcase=="OFF"
328
+ @printer.sheight=@printer.sheight.abs*(-1)
329
+ elsif input.keys[0].upcase=="ON"
330
+ @printer.sheight=@printer.sheight.abs
331
+ else
332
+ puts "set pause requires either Off or On"
333
+ end
334
+ @printer.sheight = 24 if @printer.sheight==0
335
+ end
336
+
337
+ def set_var(input)
338
+ debug(6,input)
339
+ input.each {|key,val|
340
+ GlobalVars.instance[key]=val
341
+ puts "#{key} : #{val.inspect}"
342
+ }
343
+ end
344
+
345
+ def show_var(input)
346
+ if input.empty?
347
+ if GlobalVars.instance.empty?
348
+ puts "No variables defined"
349
+ else
350
+ GlobalVars.instance.each { |key,val|
351
+ puts "#{key} : #{val.inspect}"
352
+ }
353
+ end
354
+ else
355
+ input.each { |item|
356
+ if GlobalVars.instance[item].nil?
357
+ puts "#{item} *** Not Defined ***"
358
+ else
359
+ puts "#{item} : #{GlobalVars.instance[item].inspect}"
360
+ end
361
+ }
362
+ end
363
+ end
364
+
365
+ def unset_var(input)
366
+ if input.empty?
367
+ puts "No variables given to unset"
368
+ else
369
+ input.each {|item|
370
+ if GlobalVars.instance[item].nil?
371
+ puts "#{item} *** Not Defined ***"
372
+ else
373
+ GlobalVars.instance.delete(item)
374
+ puts "#{item} Deleted"
375
+ end
376
+ }
377
+ end
378
+ end
379
+
380
+ def set_env(input)
381
+ input.each{|key,val|
382
+ @env[key]=val
383
+ puts "#{key} : #{val.inspect}"
384
+ }
385
+ end
386
+
387
+ def show_env(input)
388
+ if input.empty?
389
+ if @env.empty?
390
+ puts "No variables defined"
391
+ else
392
+ @env.each { |key,val|
393
+ puts "#{key} : #{val.inspect}"
394
+ }
395
+ end
396
+ else
397
+ input.each { |item|
398
+ if @env[item].nil?
399
+ puts "#{item} *** Not Defined ***"
400
+ else
401
+ puts "#{item} : #{@env[item].inspect}"
402
+ end
403
+ }
404
+ end
405
+ end
406
+
407
+ # Load a configuration stored in a file
408
+ # many things can be passed in
409
+ # 1) an OpenStruct list of command line parameters which will overload the parameters
410
+ # stored in the file
411
+ # 2) A Hash with a key :filename
412
+ # 3) If nil or empty the class variable @conffile will be used
413
+
414
+ def do_login(params)
415
+ url = params[:server]
416
+ username = params[:username]
417
+ password = params[:password]
418
+
419
+ begin
420
+ @server = ZbxCliServer.new(url,username,password,debug_level)
421
+ puts "#{url} connected" if @env["echo"]
422
+ puts "API Version: #{@server.version}" if @env["echo"]
423
+
424
+ setupcommands(true)
425
+ return true
426
+ rescue ZbxAPI_ExceptionBadAuth
427
+ puts "Login error, incorrect login information"
428
+ puts "Server: #{url} User: #{username} password: #{password}" # will need to remove password in later versions
429
+ return false
430
+ rescue ZbxAPI_ExceptionBadServerUrl
431
+ puts "Login error, unable to connect to host or bad host name: '#{url}'"
432
+ # rescue ZbxAPI_ExceptionConnectionRefused
433
+ # puts "Server refused connection, is url correct?"
434
+ end
435
+ end
436
+
437
+ def do_info(input)
438
+ puts "Current settings"
439
+ puts "Server"
440
+ if @server.nil?
441
+ puts "Not connected"
442
+ else
443
+ puts " Server Name: %s" % @server.server_url
444
+ puts " Username: %-15s Password: %-12s" % [@server.user, Array.new(@server.password.length,'*')]
445
+ end
446
+ puts "Display"
447
+ puts " Current screen length #{@env["sheight"]}"
448
+ puts "Other"
449
+ puts " Debug level %d" % @env["debug"]
450
+ end
451
+
452
+ #
453
+ # Import config from an XML file:
454
+ #
455
+ def do_import(input)
456
+ if input.nil?
457
+ puts "Run requires a file name as argument."
458
+ return
459
+ end
460
+
461
+ begin
462
+ xml_import = REXML::Document.new File.new(input[:filename])
463
+ rescue Errno::ENOENT
464
+ puts "Failed to open import file #{input[:filename]}."
465
+ return
466
+ end
467
+
468
+ if xml_import.nil?
469
+ puts "Failed to parse import file #{input[:filename]}."
470
+ return
471
+ end
472
+
473
+ host=xml_import.elements['import/hosts']
474
+ if !host.nil?
475
+ host = host[1]
476
+ end
477
+
478
+ # Loop for the host tags:
479
+ while !host.nil?
480
+ host_params = { 'host' => host.attributes["name"],
481
+ 'port' => host.elements["port"].text }
482
+ if host.elements["useip"].text.to_i == 0 # This is broken in Zabbix export (always 0).
483
+ host_params['dns']=host.elements["dns"].text
484
+ else
485
+ host_params['ip']=host.elements["ip"].text
486
+ end
487
+ # Loop through the groups:
488
+ group = host.elements['groups/']
489
+ if !group.nil?
490
+ group = group[1]
491
+ end
492
+ groupids = Array.new
493
+ while !group.nil?
494
+ result = @server.gethostgroupid({ 'name' => group.text })
495
+ groupid = result[:result].to_i
496
+ if groupid == 0
497
+ puts "The host group " + group.text + " doesn't exist. Attempting to add it."
498
+ result = @server.addhostgroup(['name' => group.text])
499
+ groupid = result[:result].to_a[0][1].to_i
500
+ if groupid == 0
501
+ puts "The group \"" + group.text + "\" doesn't exist and couldn't be added. Terminating import."
502
+ return
503
+ end
504
+ end
505
+ groupids << groupid
506
+ group = group.next_element
507
+ end
508
+ host_params['groupids'] = groupids;
509
+
510
+ # Add the host
511
+ result = @server.addhost(host_params)[:result]
512
+ hostid = @server.gethost( { 'pattern' => host.attributes['name'] } )[:result].to_a[0][1]
513
+ if result.nil? # Todo: result is nil when the host is added. I'm not sure if I buggered it up or not.
514
+ puts "Added host " + host.attributes['name'] + ": " + hostid.to_s
515
+ else
516
+ puts "Failed to add host " + host.attributes['name']
517
+ end
518
+
519
+ # Item loop (within host loop)
520
+ item = host.elements['items/']
521
+ if !item.nil?
522
+ item = item[1]
523
+ item_params = Array.new
524
+ appids = Array.new
525
+ while !item.nil?
526
+ # Application loop:
527
+ app = item.elements['applications/']
528
+ if !app.nil?
529
+ app = app[1]
530
+ if hostid != 0
531
+ while !app.nil?
532
+ appid = @server.getappid({'name' => app.text, 'hostid' => hostid})[:result]
533
+ if appid == 0
534
+ result = @server.addapp([{'name' => app.text, 'hostid' => hostid}])
535
+ appid = result[:result].to_a[0][1].to_i
536
+ puts "Application " + app.text + " added: " + appid.to_s
537
+ end
538
+ appids << appid
539
+ app = app.next_element
540
+ end
541
+ else
542
+ puts "There is no hostname associated with the application " + app.text
543
+ puts "An application must be associated with a host. It has not been added."
544
+ end
545
+ end
546
+
547
+ item_params = { 'description' => item.elements["description"].text,
548
+ 'key_' => item.attributes["key"],
549
+ 'hostid' => hostid,
550
+ 'delay' => item.elements['delay'].text.to_s.to_i,
551
+ 'history' => item.elements['history'].text.to_s.to_i,
552
+ 'status' => item.elements['status'].text.to_s.to_i,
553
+ 'type' => item.attributes['type'].to_i,
554
+ 'snmp_community' => item.elements['snmp_community'].text.to_s,
555
+ 'snmp_oid' => item.elements['snmp_oid'].text.to_s,
556
+ 'value_type' => item.attributes['value_type'].to_i,
557
+ 'data_type' => item.elements['data_type'].text.to_s.to_i,
558
+ 'trapper_hosts' => 'localhost',
559
+ 'snmp_port' => item.elements['snmp_port'].text.to_s.to_i,
560
+ 'units' => item.elements['units'].text.to_s,
561
+ 'multiplier' => item.elements['multiplier'].text.to_s.to_i,
562
+ 'delta' => item.elements['delta'].text.to_s.to_i,
563
+ 'snmpv3_securityname' => item.elements['snmpv3_securityname'].text.to_s,
564
+ 'snmpv3_securitylevel' => item.elements['snmpv3_securitylevel'].text.to_s.to_i,
565
+ 'snmpv3_authpassphrase' => item.elements['snmpv3_authpassphrase'].text.to_s,
566
+ 'snmpv3_privpassphrase' => item.elements['snmpv3_privpassphrase'].text.to_s,
567
+ 'formula' => item.elements['formula'].text.to_s.to_i,
568
+ 'trends' => item.elements['trends'].text.to_s.to_i,
569
+ 'logtimefmt' => item.elements['logtimefmt'].text.to_s,
570
+ 'valuemapid' => 0,
571
+ 'delay_flex' => item.elements['delay_flex'].text.to_s,
572
+ 'params' => item.elements['params'].text.to_s,
573
+ 'ipmi_sensor' => item.elements['ipmi_sensor'].text.to_s.to_i,
574
+ 'applications' => appids,
575
+ 'templateid' => 0 }
576
+ added_item = @server.additem([item_params])
577
+ puts "Added item " + item.elements["description"].text + ": " + added_item[0]
578
+ item = item.next_element
579
+ end # End of item loop (within host loop)
580
+ end
581
+
582
+ host = host.next_element
583
+ end # End of loop for host tags
584
+
585
+ # Trigger loop
586
+ trigger=xml_import.elements['import/triggers']
587
+ if !trigger.nil?
588
+ trigger = trigger[1]
589
+ end
590
+ while !trigger.nil?
591
+ trigger_params = { 'description' => trigger.elements['description'].text,
592
+ 'type' => trigger.elements['type'].text.to_i,
593
+ 'expression' => trigger.elements['expression'].text,
594
+ 'url' => '', # trigger.elements['url'].text,
595
+ 'status' => trigger.elements['status'].text.to_i,
596
+ 'priority' => trigger.elements['priority'].text.to_i,
597
+ 'comments' => 'No comments.' } # trigger.elements['comments'].text }
598
+ result = @server.addtrigger( trigger_params )
599
+ puts "Added trigger " + result[:result][0]['triggerid'] + ": " + trigger.elements['description'].text
600
+ trigger = trigger.next_element
601
+ end
602
+
603
+ # Sysmap loop
604
+ sysmap = xml_import.elements['import/sysmaps/']
605
+ if !sysmap.nil?
606
+ sysmap = sysmap[1]
607
+ end
608
+ while !sysmap.nil?
609
+ sysmap_params = { 'name' => sysmap.attributes['name'],
610
+ 'width' => sysmap.elements['width'].text.to_i,
611
+ 'height' => sysmap.elements['height'].text.to_i,
612
+ 'backgroundid' => sysmap.elements['backgroundid'].text.to_i,
613
+ 'label_type' => sysmap.elements['label_type'].text.to_i,
614
+ 'label_location' => sysmap.elements['label_location'].text.to_i }
615
+ sysmapid = 0
616
+ result = @server.addsysmap([sysmap_params])
617
+ # Get sysmapid from the result code
618
+ sysmapid = result[:result][0]['sysmapid'].to_i
619
+ puts "Added sysmap " + sysmap.attributes['name'] + ": " + sysmapid.to_s
620
+
621
+ if sysmapid != 0 # We must have a sysmap ID to add elements
622
+
623
+ # Element loop (within the sysmap loop)
624
+ element = sysmap.elements['/import/sysmaps/sysmap/elements/']
625
+ if !element.nil?
626
+ element = element[1]
627
+ end
628
+ while !element.nil?
629
+ # Todo: change to use case.
630
+ elementtype = element.elements['elementtype'].text.to_i
631
+ if elementtype != ME_IMAGE
632
+ hostid = @server.gethost( { 'pattern' => element.elements['hostname'].text } )[:result].to_a[0][1].to_i
633
+ end
634
+ if elementtype == ME_HOST
635
+ elementid = hostid
636
+ elsif elementtype == ME_TRIGGER
637
+ elementid = @server.gettrigger({'hostids' => hostid, 'pattern' => element.elements['tdesc'].text})
638
+ elementid = elementid[:result].to_a[0][1].to_i
639
+ else # ME_IMAGE for now.
640
+ elementid = 0
641
+ end
642
+ element_params = { 'label' => element.attributes['label'],
643
+ 'sysmapid' => sysmapid,
644
+ 'elementid' => elementid,
645
+ 'elementtype' => element.elements['elementtype'].text.to_i,
646
+ 'iconid_off' => element.elements['iconid_off'].text.to_i,
647
+ 'iconid_on' => element.elements['iconid_on'].text.to_i,
648
+ 'iconid_unknown' => element.elements['iconid_unknown'].text.to_i,
649
+ 'iconid_disabled' => element.elements['iconid_disabled'].text.to_i,
650
+ 'label_location' => element.elements['label_location'].text.to_i,
651
+ 'x' => element.elements['x'].text.to_i,
652
+ 'y' => element.elements['y'].text.to_i }
653
+ # 'url' => element.elements['url'].text }
654
+ result = @server.addelementtosysmap([element_params])
655
+ puts "Added map element " + element.attributes['label'] + ": " + result[:result]
656
+ element = element.next_element
657
+ end # End of element loop (within the sysmap loop)
658
+
659
+ # Sysmap link loop (within the sysmap loop)
660
+ syslink = sysmap.elements['/import/sysmaps/sysmap/sysmaplinks/']
661
+ if !syslink.nil?
662
+ syslink = syslink[1]
663
+ end
664
+ while !syslink.nil?
665
+ # The code down to "link_params = {" is a mess and needs to be rewritten.
666
+ # elementid = hostid or triggerid depending on element type.
667
+ if syslink.elements['type1'].text.to_i == ME_HOST
668
+ hostid1 = @server.gethost( { 'pattern' => syslink.elements['host1'].text } )[:result].to_a[0][1]
669
+ selementid1 = @server.getseid({'elementid' => hostid1, 'sysmapid' => sysmapid})[:result].to_a[0][1].to_i
670
+ elsif syslink.elements['type1'].text.to_i == ME_TRIGGER # The first element is a trigger
671
+ hostid1 = @server.gethost( { 'pattern' => syslink.elements['host1'].text } )[:result].to_a[0][1]
672
+ triggerid1 = @server.gettrigger({'hostids' => hostid1, 'pattern' => syslink.elements['tdesc1'].text})
673
+ hostid1 = triggerid1[:result].to_a[0][1].to_i
674
+ selementid1 = @server.getseid({'elementid' => hostid1, 'sysmapid' => sysmapid})[:result].to_a[0][1].to_i
675
+ elsif syslink.elements['type1'].text.to_i == ME_IMAGE
676
+ label = syslink.elements['label1'].text
677
+ selementid1 = @server.getseid({'label' => label, 'sysmapid' => sysmapid})[:result].to_a[0][1].to_i
678
+ end
679
+ # The other end of the link:
680
+ if syslink.elements['type2'].text.to_i == ME_HOST
681
+ hostid2 = @server.gethost( { 'pattern' => syslink.elements['host2'].text } )[:result].to_a[0][1]
682
+ selementid2 = @server.getseid({'elementid' => hostid2, 'sysmapid' => sysmapid})[:result].to_a[0][1].to_i
683
+ elsif syslink.elements['type2'].text.to_i == ME_TRIGGER # The second element is a trigger
684
+ hostid2 = @server.gethost( { 'pattern' => syslink.elements['host2'].text } )[:result].to_a[0][1]
685
+ triggerid2 = @server.gettrigger({'hostids' => hostid2, 'pattern' => syslink.elements['tdesc2'].text})
686
+ triggerid2 = triggerid2[:result].to_a[0][1].to_i
687
+ selementid2 = @server.getseid({'elementid' => triggerid2, 'sysmapid' => sysmapid})[:result].to_a[0][1].to_i
688
+ elsif syslink.elements['type2'].text.to_i == ME_IMAGE
689
+ label = syslink.elements['label2'].text
690
+ selementid2 = @server.getseid({'pattern' => label, 'sysmapid' => sysmapid})[:result].to_a[0][1].to_i
691
+ end
692
+ link_params = { 'sysmapid' => sysmapid,
693
+ 'selementid1' => selementid1,
694
+ 'selementid2' => selementid2,
695
+ 'triggers' => [], # The triggers require linkid, so this is a catch 22
696
+ 'drawtype' => syslink.elements['drawtype'].text.to_i,
697
+ 'color' => syslink.elements['color'].text.tr('"','') }
698
+ result = @server.addlink([link_params])
699
+ linkid = result[:result].to_i
700
+ puts "Link added: " + link_params.inspect
701
+ #puts "Added map link " + linkid.to_s + " (" + syslink.elements['host1'].text + "(" +
702
+ # hostid1.to_s + ") <-> " + syslink.elements['host2'].text + "(" + hostid2.to_s + "))."
703
+
704
+ if !linkid.nil? # Link triggers require the associated link
705
+ # Sysmap link trigger loop (within the sysmap and syslink loop)
706
+ linktrigger = syslink.elements['linktriggers/']
707
+ if !linktrigger.nil?
708
+ linktrigger = linktrigger[1]
709
+ end
710
+ i = 0
711
+ linktrigger_params = Array.new
712
+ while !linktrigger.nil?
713
+ # Add hostname and tdesc field in the XML to identify the link:
714
+ hostid = @server.gethost( { 'pattern' => linktrigger.elements['host'].text } )[:result].to_a[0][1].to_i
715
+ triggerid = @server.gettrigger({'hostids' => hostid, 'pattern' => linktrigger.elements['tdesc'].text})
716
+ triggerid = triggerid[:result].to_a[0][1].to_i
717
+ if triggerid.nil?
718
+ puts "Failed to find trigger for host " + host + " and description \"" + tdesc + "\"."
719
+ else
720
+ linktrigger_params[i] = { 'linkid' => linkid,
721
+ 'triggerid' => triggerid,
722
+ 'drawtype' => linktrigger.elements['drawtype'].text.to_i,
723
+ 'color' => linktrigger.elements['color'].text.tr('"', '') }
724
+ i = i + 1
725
+ end
726
+ linktrigger = linktrigger.next_element
727
+ end # End linktrigger loop (within sysmap and syslink loop)
728
+ puts "Adding link trigger(s): " + linktrigger_params.inspect
729
+ result = @server.addlinktrigger(linktrigger_params);
730
+ end # If !linkid.nil? (linktrigger)
731
+
732
+ syslink = syslink.next_element
733
+ end # End syslink loop
734
+
735
+ end # End If sysmap
736
+ sysmap = sysmap.next_element
737
+ end # End Sysmap loop
738
+
739
+ end # end do_import
740
+
741
+ end #end class
742
+