module Zabbix module Api require 'faraday/net_http' require 'json' require 'set' require 'amazing_print' require 'pry' module FaradayMiddleware ## # this middleware adapter does zabbix api essential things # at the REST level, and hendles serializing/de-serializing # ruby objects. class ZabbixApiRequest < Faraday::Middleware def initialize(app) super(app) @app=app end def on_request(env) env[:request_body][:jsonrpc] = "2.0" env[:request_body][:id] = "1" env[:request_body] = env[:request_body].to_json end def on_complete(env) begin env[:response_body] = JSON.parse(env[:response_body]) rescue JSON::ParserError => e env[:response_body] = {error: e,note: 'Check api web configuration (e.g. url, PHP memory, etc)'} end end end Faraday::Request.register_middleware(zabbix_api_request: ZabbixApiRequest) end # module FaradayMiddleware ## # Subclass of OpenStruct adds some niceties for REPL etc # class OpenStruct < OpenStruct ## # If true, inspect returns awesome_inspect instead of default @@prettymode = false class << self ## # returns current value of prettymode # def prettymode @@prettymode end ## # sets prettymod to aBool # def prettymode=(aBool) @@prettymode = aBool end end def inspect if @@prettymode awesome_inspect else super end end end ## # Each instance of this class acts as a connection Zabbix's API class Client class << self attr_accessor :last end @@apiurl = 'api_jsonrpc.php' ## # This is a list (as of zabbix 5.0) of top-level zabbix API calls # that the client will understand via method_missing. If they # add new things to the api they need to be added here as well # *if* you intend on using the method_missing syntax. @@zabbix_objects = [:action,:alert,:apiinfo,:application,:configuration, :correlation,:dashboard,:dhost,:dservice,:dcheck, :drule,:event,:graph,:graphitem,:graphprototype, :history,:host,:hostgroup,:hostinterface, :hostprototype,:iconmap,:image,:item,:itemprototype, :discoveryrule,:maintenance,:map,:mediatype,:problem, :proxy,:screen,:screenitem,:script,:service,:task, :template,:templatescreen,:templatescreenitem, :trend,:trigger,:triggerprototype,:user,:usergroup, :usermacro,:valuemap,:httptest].to_set attr_reader :conn,:token attr_accessor :zabobject ## # :url is required. You do not need to add 'api_jsonrpc.php' - this will # happen automagically. You can alter request timeout if needed by passing # :timeout - the default is 60 secs def initialize(url: nil,timeout: 60) Faraday.default_adapter = :net_http @conn = Faraday.new( url: url, headers: {'Content-Type' => 'application/json-rpc'}, request: { timeout: timeout } ) do |conn| conn.request :zabbix_api_request end @zabobject = nil end ## # This method posts a list of params to @conn/@@apiurl. You likely won't # have need to call this directly. def post(args) args[:params] = [] if not args.has_key?(:params) or args[:params].nil? last = @conn.post(@@apiurl, args) Client.last = last return last end ## # both :user and :pass are required. This method calls user.logic # abd stores the returned auth token for future calls to the api def login(user: nil,pass: nil) res =post(method: 'user.login', params: {user: user, password:pass}, auth:nil) @token = res.body['result'] OpenStruct.new(res.body) end ## # calls user.logout for the @token session. You really should pay # attention to ensuring that this gets called, else you'll find over time # that your sessions table is getting quite large and will start slowing # down lots of stuff in zabbix. def logout OpenStruct.new(post(method: 'user.logout', params: [], auth: @token).body) end ## # this is the method that method_missing calls to peform the actual work. # The first parameter is the top-level api call (e.g. those listed in # @@zabbix_objects. Args is a hash containing the particulars for the call. # You can call this directly if you don't want to rely on method_missing. # # results are returned as instances of OpenStruct. If you need this to be # a hash just do to_h to the returned object. def call(name, *args, &block) res = nil if name == 'apiinfo.version' res = post(method: "#{name}", params: args.first, id: '1').body else res = post(method: "#{name}", params: args.first, id: '1', auth: @token).body end raise res['error'].awesome_inspect(plain: true) if res.has_key?('error') if res.has_key?('result') and res['result'].class == Array res = res['result'].collect{|each| OpenStruct.new(each)} else res = OpenStruct.new(res) end return res end ## # just handy if you're doing a REPL (e.g. zapishell.rb) Returns # list of zabbix objects the api currently is aware of # def known_objects @@zabbix_objects end ## # this override of method_missing is the trick that lets zabbix-api-simple # look quite a lot like the zabbix api documentation. If it finds that the # method name you were trying to call is in @@zabbix_objects, then it constructs # a call to that top level api entity using the parameters as arguments to the call. # # this is really just here as syntactical sugar - you don't *have* to use it, but # if you do you'll find that you need do essentially zero mental translation between # the zabbix api documentation and your code. def method_missing(name, *args, &block) if @@zabbix_objects.include?(name) # Clone self cuz we want to be thread safe/recursable. This will pop off the # stack after it's no longer referenced (and @zabobject will never change in the # original client instance) newcli = self.clone newcli.zabobject = name return newcli elsif @zabobject return call("#{@zabobject}.#{name}",args.first) else raise "Unknown zabbix object given: #{name}" end end ## # returns the last response the client got from the server def last Client.last end end end # module Api end # module Zabbix