module Rubix # A base class for all Zabbix models to subclass. # # It might be worth using ActiveModel -- but maybe not. The goal is # to keep dependencies low while still retaining expressiveness. class Model # @return [Hash]] the properties this model was initialized with attr_accessor :properties # @return [Fixnum, nil] the ID of this model attr_accessor :id extend Logs include Logs # # == Identifiers == # # This is the name of the resource as used inside Rubix -- Host, # HostGroup, UserMacro, &c. # # @return [String] def self.resource_name self.to_s.split('::').last end # This is the name of *this* resource instance, using this # object's 'name' property if possible. # # @return [String] def resource_name "#{self.class.resource_name} #{respond_to?(:name) ? self.name : self.id}" end # This is the name of the resource as used by Zabbix -- host, # hostgroup, usermacro, &c. # # @return [String] def self.zabbix_name resource_name.downcase end # This is the name of the id field returned in Zabbix responses -- # +hostid+, +groupid+, +hostmacroid+, &c. # # @return [String] def self.id_field "#{zabbix_name}id" end # This is the name of the id field returned in Zabbix responses -- # +hostid+, +groupid+, +hostmacroid+, &c. # # @return [String] def id_field self.class.id_field end # # == Initialization == # # Create a new model instance. This may represent a new or # existing Zabbix resource. # # @param [Hash] properties # @option properties [Fixnum] id the ID of the resource in Zabbix (typically blank for a new resource) def initialize properties={} @properties = properties self.class.properties.keys.each do |property| self.send("#{property}=", properties[property]) end @id = properties[:id] end # Send a request to the Zabbix API. This is just a convenience # method for Rubix::Connection#request. # # @param [String] method # @param [Hash,Array] params # @return [Rubix::Response] def request method, params self.class.request(method, params) end # Send a request to the Zabbix API. This is just a convenience # method for Rubix::Connection#request. # # @param [String] method # @param [Hash,Array] params # @return [Rubix::Response] def self.request method, params Rubix.connection && Rubix.connection.request(method, params) end # Send a web request to the Zabbix web application. This is just # a convenience method for Rubix::Connection#web_request. # # @param [String] verb one of "GET" or "POST" # @param [String] path the path to send the request to # @param [Hash] data the data to include with the request def self.web_request verb, path, data={} Rubix.connection && Rubix.connection.web_request(verb, path, data) end # Is this a new record? We can tell because the ID must be blank. # # @return [true, false] def new_record? @id.nil? end # Save this record. # # Will create new records and update old ones. # # @return [true, false] def save new_record? ? create : update end # Validate this record. # # Override this method in a subclass and have it raise a # Rubix::ValidationError if validation fails. # # @return [true, false] def validate self.class.properties.each_pair do |property, options| raise ValidationError.new("A #{self.class.resource_name} must have a #{property}") if options[:required] && (self.send(property).nil? || self.send(property).empty?) end true end # Return this object as a Hash. # # @return [Hash] def to_hash update_params end # # == Create == # # Parameters for creating a new resource of this type. # # @return [Hash] def create_params {} end # Send a request to create this resource. # # @return [Rubix::Response] def create_request request("#{self.class.zabbix_name}.create", create_params) end # Create this resource. # # @return [true, false] def create return false unless validate response = create_request if response.has_data? @id = response.result[id_field + 's'].first.to_i info("Created Zabbix #{resource_name}") true else error("Error creating Zabbix #{resource_name}: #{response.error_message}") return false end after_create end # Run this hook after creating a new resource. def after_create true end # # == Update == # # Parameters for updating a resource of this type. # # @return [Hash] def update_params if id create_params.merge({id_field => id}) else create_params end end # Send a request to update this resource. # # @return [Rubix::Response] def update_request request("#{self.class.zabbix_name}.update", update_params) end # Update this resource. # # @return [true, false] def update return false unless validate return create if new_record? return false unless before_update response = update_request case when response.has_data? && (response.result.values.first == true || response.result.values.first.map(&:to_i).include?(id)) info("Updated Zabbix #{resource_name}") true when response.has_data? error("No error, but failed to update Zabbix #{resource_name}") false else error("Error updating Zabbix #{resource_name}: #{response.error_message}") false end end # A hook that will be run before this resource is updated. # # Override this in a subclass to implement any desired # before-update functionality. Must return +true+ or +false+. # # @return [true, false] def before_update true end # # == Destroy == # # Parameters for destroying this resource. # # @return [Array] def destroy_params [id] end # Send a request to destroy this resource. # # @return [Rubix::Response] def destroy_request request("#{self.class.zabbix_name}.delete", destroy_params) end # Destroy this resource. # # @return [true, false] def destroy return true if new_record? return false unless before_destroy response = destroy_request case when response.has_data? && response.result.values.first.first.to_i == id info("Destroyed Zabbix #{resource_name}") true when response.zabbix_error? && response.error_message =~ /does not exist/i # was never there true else error("Could not destroy Zabbix #{resource_name}: #{response.error_message}") false end end # A hook that will be run before this resource is destroyed. # # Override this in a subclass to implement any desired # before-destroy functionality. Must return +true+ or +false+. # # @return [true, false] def before_destroy true end # # == Index == # # Parameters for 'get'-type requests for this resource's type. # # @return [Hash] def self.get_params { :output => :extend } end # Parameters to list all the objects of this resource's type. # # @param [Hash] options options for filtering the list of all resources. # @return [Hash] def self.all_params options={} get_params.merge(options) end # Send a request to list all objects of this resource's type. # # @param [Hash] options options for filtering the list of all resources. # @return [Rubix::Response] def self.all_request options={} request("#{zabbix_name}.get", all_params(options)) end # List all objects of this resource's type. # # @param [Hash] options options for filtering the list of all resources. # @return [Array] def self.all options={} response = all_request(options) if response.has_data? response.result.map { |obj_data| build(obj_data) } else error("Error listing all Zabbix #{resource_name}s: #{response.error_message}") unless response.success? [] end end # Execute block once for each element of the result set. # # @param [Hash] options options for filtering the list of all resources. # @return [Array] def self.each options={}, &block all(options).each(&block) end # # == Show == # # Parameters for finding a specific resource. # # @param [Hash] options specify properties about the object to find # @return [Hash] def self.find_params options={} get_params.merge(options) end # Send a find request for a specific resource. # # @param [Hash] options specify properties about the object to find # @return [Rubix::Response] def self.find_request options={} request("#{zabbix_name}.get", find_params(options)) end # Find a resource using the given +options+ or return +nil+ if # none is found. # # @param [Hash] options specify properties about the object to find # @return [Rubix::Model, nil] def self.find options={} response = find_request(options) case when response.has_data? build(response.result.first) when response.success? # a successful but empty response means it wasn't found else error("Error finding Zabbix #{resource_name} using #{options.inspect}: #{response.error_message}") nil end end # Find a resource using the given +options+ or create one if none # can be found. Will return +false+ if the object cannot be found # and cannot be created. # # @param [Hash] options specify properties about the object to find # @return [Rubix::Model, false] def self.find_or_create options={} response = find_request(options) case when response.has_data? build(response.result.first) when response.success? # doesn't exist obj = new(options) if obj.save obj else false end else error("Error creating Zabbix #{resource_name} using #{options.inspect}: #{response.error_message}") false end end # # == List == # def self.list ids return [] if ids.nil? || ids.empty? response = request("#{zabbix_name}.get", get_params.merge((id_field + 's') => ids)) case when response.has_data? response.result.map do |obj| build(obj) end when response.success? [] else error("Error listing Zabbix #{resource_name}s: #{response.error_message}") end end # # == Helpers == # def self.properties @properties ||= {} end def self.zabbix_attr name, options={} name = name.to_s.to_sym @properties ||= {} @properties[name] = options if options[:default].nil? attr_accessor name else attr_writer name define_method name do current_value = instance_variable_get("@#{name}") return current_value unless current_value.nil? instance_variable_set("@#{name}", options[:default]) end end end end end