# # wbem/wsman.rb # WsmanClient implementation for ruby-wbem # # Copyright (c) SUSE Linux Products GmbH 2011 # Written by Klaus Kämpf # # Licensed under the MIT license # require "wbem/wbem" require "openwsman" class AuthError < StandardError end module Openwsman class Transport def Transport.auth_request_callback( client, auth_type ) STDERR.puts "\t *** Transport.auth_request_callback" return nil end end # # Capture Fault as Exception # class Exception < ::RuntimeError def initialize fault unless fault.is_a? Openwsman::Fault raise "#{fault} is not a fault" unless fault.fault? fault = Openwsman::Fault.new fault end @fault = fault end def to_s "Fault code #{@fault.code}, subcode #{@fault.subcode}" + "\n\treason #{@fault.reason}" + "\n\tdetail #{@fault.detail}" end end # # Capture namespace, classname, and properties as ObjectPath # class ObjectPath attr_reader :namespace, :classname, :properties def initialize namespace, classname = nil, properties = {} @namespace = namespace @classname = classname @properties = properties end end # # Provide Cim::ObjectPath like accessors for EndPointReference # class EndPointReference alias :keys :selector_names alias :key_count :selector_count alias :add_key :add_selector def each_key keys.each { |key| yield key } end end # # Capture XmlDoc + WsmanClient as Instance # class Instance def initialize node, client, epr_or_uri @node = (node.is_a? Openwsman::XmlDoc) ? node.body : node @epr = (epr_or_uri.is_a? Openwsman::EndPointReference) ? epr_or_uri : Openwsman::EndPointReference.new(epr_or_uri) @client = client end # # # def to_s "Instance #{@client}\n\t#{@epr}\n\t#{@node.to_xml}" end # # Instance# # Instance#() # def method_missing name, *args if args.empty? # try property first res = @node.send name return res.text if res end # try as method invocation options = Openwsman::ClientOptions.new options.set_dump_request selectors = {} @epr.each do |k,v| selectors[k] = v end options.selectors = selectors # instance key properties uri = @epr.resource_uri # # get method input parameter information # classname = @epr.classname s = "mof/#{classname}" begin require s rescue LoadError raise RuntimeError.new "Cannot load #{s} for type information" end methods = MOF.class_eval "#{classname}::METHODS" method = methods[name.to_s] raise RuntimeError.new("Unknown method #{name} for #{classname}") unless method result_type = method[:type] parameters = method[:parameters] || {} input = parameters[:in] output = parameters[:out] argsin = {} i = 0 if input while i < input.size value = args.shift raise "Argument for #{input[i]} is nil, not allowed !" unless value argsin[input[i]] = value # FIXME more typecheck of args ? i += 2 end end STDERR.puts "\n\tproperties #{argsin.inspect}\n" options.properties = argsin # # output parameters ? # argsout = nil if output if args.empty? raise "Function #{name} has output arguments, please add an empty hash to the call" end argsout = args.shift unless argsout.kind_of? Hash raise "Function #{name} has output arguments, last argument must be a Hash" end unless args.empty? raise "Function call to #{name} has excess arguments" end end STDERR.puts "\n\targsout #{argsout.inspect}\n" Openwsman.debug = -1 STDERR.puts "\n\tinvoke #{uri}.#{name}\n" res = @client.client.invoke(options, uri, name.to_s) raise Openwsman::Exception.new(res) if res.fault? STDERR.puts "\n\tresult #{res.to_xml}\n" result = res.find(uri, "#{name}_OUTPUT").find(uri, "ReturnValue").text case result_type when :boolean result == "true" ? true : false when :uint8, :uint16, :uint32, :uint64 result.to_i when :string result when :float result.to_f when :datetime else raise "Unsupported result_type #{result_type.inspect}" end end end end module Wbem class WsmanClient < WbemClient private # # create end point reference URI # def epr_uri_for(namespace,classname) case @product when :winrm # winrm embeds namespace in resource URI Openwsman::epr_uri_for(namespace,classname) rescue "http://schema.suse.com/wbem/wscim/1/cim-schema/2/#{namespace}/#{classname}" else (Openwsman::epr_prefix_for(classname)+"/#{classname}") rescue "http://schema.suse.com/wbem/wscim/1/cim-schema/2/#{classname}" end end # # Handle client connection or protocol fault # # @return: true if fault # def _handle_fault client, result if result.nil? STDERR.puts "Client connection failed:\n\tResult code #{client.response_code}, Fault: #{client.fault_string}" if Wbem.debug return true end if result.fault? fault = Openwsman::Fault.new result if Wbem.debug STDERR.puts "Client protocol failed for (#{client})" STDERR.puts "\tFault code #{fault.code}, subcode #{fault.subcode}" STDERR.puts "\t\treason #{fault.reason}" STDERR.puts "\t\tdetail #{fault.detail}" end return true end false end # # WS-Identify # returns Openwsman::XmlDoc # def _identify STDERR.puts "Identify client #{@client} with #{@options}" if Wbem.debug doc = @client.identify( @options ) unless doc raise RuntimeError.new "Identify failed: HTTP #{@client.response_code}, Err #{@client.last_error}:#{@client.fault_string}" end if doc.fault? fault = doc.fault STDERR.puts "Fault: #{fault.to_xml}" if Wbem.debug raise Openwsman::Exception.new fault end # STDERR.puts "Return #{doc.to_xml}" doc end public attr_reader :client def initialize uri, auth_scheme = nil super uri, auth_scheme @url.path = "/wsman" if @url.path.nil? || @url.path.empty? # Openwsman::debug = -1 STDERR.puts "WsmanClient connecting to #{uri}, auth #{@auth_scheme.inspect}" if Wbem.debug @client = Openwsman::Client.new @url.to_s raise "Cannot create Openwsman client" unless @client @client.transport.timeout = 5 @client.transport.verify_peer = 0 @client.transport.verify_host = 0 case @auth_scheme.to_s when nil, "" @client.transport.auth_method = nil # negotiate when /none/i @client.transport.auth_method = Openwsman::NO_AUTH_STR when /basic/i @client.transport.auth_method = Openwsman::BASIC_AUTH_STR when /digest/i @client.transport.auth_method = Openwsman::DIGEST_AUTH_STR when /pass/i @client.transport.auth_method = Openwsman::PASS_AUTH_STR when /ntlm/i @client.transport.auth_method = Openwsman::NTLM_AUTH_STR when /gss/i @client.transport.auth_method = Openwsman::GSSNEGOTIATE_AUTH_STR else raise "Unknown auth_scheme #{@auth_scheme.inspect}" end @options = Openwsman::ClientOptions.new # STDERR.puts "auth #{@auth_scheme.inspect} -> #{@client.transport.auth_method}" doc = _identify # STDERR.puts doc.to_xml @protocol_version = doc.ProtocolVersion.text @product_vendor = doc.ProductVendor.text @product_version = doc.ProductVersion.text if Wbem.debug STDERR.puts "Protocol_version '#{@protocol_version}'" STDERR.puts "Product vendor '#{@product_vendor}'" STDERR.puts "Product version '#{@product_version}'" end # # Windows winrm 2.0 # Protocol "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" # Vendor "Microsoft Corporation" # Version "OS: 5.2.3790 SP: 2.0 Stack: 2.0" # # Windows winrm 1.1 # Protocol "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" # Vendor "Microsoft Corporation" # Version "OS: 5.1.2600 SP: 3.0 Stack: 1.1" # # Openwsman 2.2 # Protocol "http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd" # Vendor "Openwsman Project" # Version "2.2" # unless @protocol_version == Openwsman::XML_NS_WS_MAN raise "Unsupported WS-Management protocol '#{@protocol_version}'" end case @product_vendor when /Microsoft/i @prefix = "http://schemas.microsoft.com/wbem/wsman/1/wmi/" if @product_version =~ /^OS:\s([\d\.]+)\sSP:\s([\d\.]+)\sStack:\s([\d\.]+)$/ @product_version = $3 else STDERR.puts "Unrecognized product version #{@product_version}" end @product = :winrm when /Openwsman/i @prefix = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/" @product = :openwsman when /Intel/i @prefix = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/" @product = :iamt if @product_version =~ /^AMT\s(\d\.\d)$/ @product_version = $1 end else STDERR.puts "Unsupported WS-Management vendor #{@product_vendor}" @prefix = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/" @product = :unknown end STDERR.puts "Connected to vendor '#{@product_vendor}', Version #{@product_version}" if Wbem.debug end public # # return list of namespaces # def namespaces ns = "root", cn = "__Namespace" result = [] each_instance( ns, cn ) do |inst| name = "#{ns}/#{inst.Name}" result << name result.concat namespaces name, cn end result.uniq end # # Create ObjectPath from namespace, classname, and properties # def objectpath namespace, classname = nil, properties = {} Openwsman::ObjectPath.new namespace, classname, properties end # # Enumerate instances # def each_instance( namespace_or_objectpath, classname = nil ) op = if namespace_or_objectpath.is_a? Openwsman::ObjectPath namespace_or_objectpath else objectpath namespace_or_objectpath, classname end @options.flags = Openwsman::FLAG_ENUMERATION_OPTIMIZATION @options.max_elements = 999 uri = epr_uri_for op.namespace, op.classname result = @client.enumerate( @options, nil, uri ) loop do if _handle_fault @client, result break end items = result.Items rescue nil if items items.each do |inst| yield Openwsman::Instance.new(inst, self, uri) end end context = result.context break unless context result = @client.pull( @options, nil, uri, context ) end end # # Enumerate class names # def class_names op, deep_inheritance = false @options.flags = Openwsman::FLAG_ENUMERATION_OPTIMIZATION @options.max_elements = 999 namespace = (op.is_a? Sfcc::ObjectPath) ? op.namespace : op classname = (op.is_a? Sfcc::ObjectPath) ? op.classname : nil case @product when :openwsman if @product_version < "2.2" STDERR.puts "ENUMERATE_CLASS_NAMES unsupported for #{@product_vendor} #{@product_version}, please upgrade" return [] end method = Openwsman::CIM_ACTION_ENUMERATE_CLASS_NAMES uri = Openwsman::XML_NS_CIM_INTRINSIC @options.cim_namespace = namespace @options.add_selector("DeepInheritance", "True") if deep_inheritance result = @client.invoke( @options, uri, method ) when :winrm # see https://github.com/kkaempf/openwsman/blob/master/bindings/ruby/tests/winenum.rb filter = Openwsman::Filter.new query = "select * from meta_class" query << " where __SuperClass is #{classname?classname:'null'}" unless deep_inheritance filter.wql query uri = "#{@prefix}#{namespace}/*" result = @client.enumerate( @options, filter, uri ) else raise "Unsupported for WSMAN product #{@product}" end if _handle_fault @client, result return [] end classes = [] case @product when :openwsman # extract invoke result output = result.body[method] output.each do |c| classes << c.to_s end when :winrm # extract enumerate/pull result loop do output = result.Items output.each do |node| classes << node.name.to_s end if output context = result.context break unless context # get the next chunk result = @client.pull( @options, nil, uri, context) break if _handle_fault @client, result end end return classes end # # Return list of Wbem::EndpointReference (object pathes) for instances # of namespace, classname # @param namespace : String or Sfcc::Cim::ObjectPath # @param classname : String (optional) # @param properties : Hash (optional) # def instance_names namespace, classname=nil, properties = {} case namespace when Openwsman::ObjectPath classname = namespace.classname properties = namespace.properties namespace = namespace.namespace uri = epr_uri_for(namespace,classname) when Openwsman::EndPointReference namespace.each do |k,v| properties[k] = v end classname = namespace.classname uri = namespace.resource_uri namespace = namespace.namespace else uri = epr_uri_for(namespace, classname) end @options.flags = Openwsman::FLAG_ENUMERATION_ENUM_EPR # get EPRs @options.flags = Openwsman::FLAG_ENUMERATION_OPTIMIZATION @options.max_elements = 999 @options.cim_namespace = namespace if @product == :openwsman @options.set_dump_request @options.selectors = properties unless properties.empty? start = Time.now STDERR.puts "instance_names enumerate (#{uri})" result = @client.enumerate( @options, nil, uri ) names = [] loop do if _handle_fault @client, result break end items = result.Items if items # expect ... items.each do |epr| names << Openwsman::EndPointReference.new(epr) end end context = result.context break unless context result = @client.pull( @options, nil, uri, context ) end STDERR.puts "Enumerated #{names.size} items in #{Time.now-start} seconds" return names end # # Return matching Wbem::Instance for first instance # matching namespace, classname, properties # @param namespace : String or Sfcc::Cim::ObjectPath # @param classname : String (optional) # @param properties : Hash (optional) # def get_instance namespace, classname=nil, properties={} case namespace when Openwsman::ObjectPath classname = namespace.classname properties = namespace.properties namespace = namespace.namespace uri = epr_uri_for(namespace, classname) when Openwsman::EndPointReference namespace.each do |k,v| properties[k] = v end classname = namespace.classname uri = namespace.resource_uri namespace = namespace.namespace else uri = epr_uri_for(namespace, classname) end @options.set_dump_request @options.cim_namespace = namespace if @product == :openwsman @options.selectors = properties unless properties.empty? STDERR.puts "@client.get(namepace '#{@options.cim_namespace}', props #{properties.inspect}, uri #{uri}" res = @client.get(@options, uri) raise Openwsman::Exception.new res if res.fault? Openwsman::Instance.new res, self, Openwsman::EndPointReference.new(uri, "", properties) end end end # module