Providers/Modules/Plugins/PatchManagement/plugin/patch_management_lib.rb (250 lines of code) (raw):

require 'rexml/document' require 'cgi' require 'digest' require 'set' require 'securerandom' require_relative 'oms_common' require_relative 'oms_configuration' require_relative 'omslog' class LinuxUpdates @@prev_hash = "" @@delimiter = "_" @@force_send_last_upload = Time.now @@os_details = nil MAJOR_MINOR_VERSION_REGEX = /([^\.]+)\.([^\.]+).*/ OMS_ADMIN_FILE = "/etc/opt/microsoft/omsagent/conf/omsadmin.conf" SCX_RELEASE_FILE = "/etc/opt/microsoft/scx/conf/scx-release" SCHEDULE_NAME_VARIABLE = "SCHEDULE_NAME" APT_GET_START_DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S" def getAgentId() return OMS::Configuration.agent_id end def getHostOSDetails() if(@@os_details.nil?) if File.exist?(SCX_RELEASE_FILE) # If file exists File.open(SCX_RELEASE_FILE, "r") do |f| # Open file @@os_details={} f.each_line do |line| # Split each line and line.split(/\r?\n/).reject{ |l| # reject all those line !l.include? "=" }.map! {|s| # which do not s.split("=")}.map! {|key, value| # have "="; split @@os_details[key] = value # by "=" and add to ret. } end end else OMS::Log.info_once("Could not find the file #{SCX_RELEASE_FILE}") @@os_details = {} end end return @@os_details end # This temporary fix is for version management for cache lookup. def getOSShortName(os_short_name = nil, os_version=nil) version = "" hostOSDetailsMap = getHostOSDetails() #os short name is not proper for oracle linux at /etc/opt/microsoft/scx/conf/scx-release. this is to return proper short name till scx fixes the issue. if hostOSDetailsMap.key?("OSFullName") && hostOSDetailsMap.key?("OSShortName") osFullName = hostOSDetailsMap["OSFullName"] osShortName = hostOSDetailsMap["OSShortName"] if osFullName.downcase.include?("oracle") && ! osShortName.downcase.include?("oracle") os_short_name = "Oracle" end end # match string of the form (1 or more non . chars)- followed by a . - (1 or more non . chars) - followed by anything if hostOSDetailsMap.key?("OSShortName") osName = (os_short_name.nil?) ? hostOSDetailsMap["OSShortName"].split("_")[0] : os_short_name.split("_")[0] else osName = (os_short_name.nil?) ? hostOSDetailsMap["OSFullName"] : os_short_name.split("_")[0] end if (os_version.nil?) @os_major_version = hostOSDetailsMap["OSVersion"][MAJOR_MINOR_VERSION_REGEX, 1] unless hostOSDetailsMap["OSVersion"].nil? @os_minor_version = hostOSDetailsMap["OSVersion"][MAJOR_MINOR_VERSION_REGEX, 2] unless hostOSDetailsMap["OSVersion"].nil? @default_version = hostOSDetailsMap["OSVersion"] else @os_major_version = os_version[MAJOR_MINOR_VERSION_REGEX, 1] unless os_version.nil? @os_minor_version = os_version[MAJOR_MINOR_VERSION_REGEX, 2] unless os_version.nil? @default_version = os_version end case osName when "Ubuntu" if @os_major_version == "12" || @os_major_version == "13" version = "12.04" elsif @os_major_version == "14" || @os_major_version == "15" version = "14.04" elsif @os_major_version == "16" || @os_major_version == "17" version = "16.04" else version = @default_version end when "CentOS" if @os_major_version == "5" version = "5.0" elsif @os_major_version == "6" version = "6.0" elsif @os_major_version == "7" version = "7.0" elsif @os_major_version == "8" version = "8.0" else version = @default_version end when "RHEL" if @os_major_version == "5" version = "5.0" elsif @os_major_version == "6" version = "6.0" elsif @os_major_version == "7" version = "7.0" elsif @os_major_version == "8" version = "8.0" else version = @default_version end when "Oracle" version = "6.0" when "SUSE" if @os_major_version == "11" version = "11.0" elsif @os_major_version == "12" version = "12.0" elsif @os_major_version == "15" version = "15.0" else version = @default_version end else version = @default_version end return osName + @@delimiter + version end def self.prev_hash= (value) @@prev_hash = value end def isInstalledPackageInstanceXML(instanceXML) instanceXML.attributes["CLASSNAME"] == "MSFT_nxPackageResource" end def isAvailableUpdateInstanceXML(instanceXML) instanceXML.attributes["CLASSNAME"] == "MSFT_nxAvailableUpdatesResource" end def instanceXMLtoHash(instanceXML) ret = {} propertyXPath = "PROPERTY" instanceXML.elements.each(propertyXPath) { |inst| #AnonFunc in rb. name = inst.attributes['NAME'] rexmlText = REXML::XPath.first(inst, 'VALUE').get_text # TODO escape unicode chars like "&amp;" value = rexmlText ? rexmlText.value.strip : '' ret[name] = value } ret end def availableUpdatesXMLtoHash(availableUpdatesXML, os_short_name) availableUpdatesHash = instanceXMLtoHash(availableUpdatesXML) ret = {} ret["CollectionName"] = availableUpdatesHash["Name"] + @@delimiter + availableUpdatesHash["Version"] + @@delimiter + os_short_name ret["PackageName"] = availableUpdatesHash["Name"] ret["Architecture"] = availableUpdatesHash.key?("Architecture") ? availableUpdatesHash["Architecture"] : nil ret["PackageVersion"] = availableUpdatesHash["Version"] ret["Repository"] = availableUpdatesHash.key?("Repository") ? availableUpdatesHash["Repository"] : nil ret["Installed"] = false ret["UpdateState"] = "Needed" if (Integer(availableUpdatesHash["BuildDate"]) rescue false) ret["Timestamp"] = OMS::Common.format_time(availableUpdatesHash["BuildDate"].to_i) end ret end def availableHeartbeatItem() ret = {} ret["CollectionName"] = "HeartbeatData" + @@delimiter + "0.0.UpdateManagement.0"+ @@delimiter + "Heartbeat" ret["PackageName"] = "UpdateManagementHeartbeat" ret["Architecture"] = "all" ret["PackageVersion"] = nil ret["Repository"] = nil ret["Installed"] = false ret["UpdateState"] = "NotNeeded" ret end def installedPackageXMLtoHash(packageXML, os_short_name) packageHash = instanceXMLtoHash(packageXML) ret = {} ret["CollectionName"] = packageHash["Name"] + @@delimiter + packageHash["Version"] + @@delimiter + os_short_name ret["PackageName"] = packageHash["Name"] ret["Architecture"] = packageHash.key?("Architecture") ? packageHash["Architecture"] : nil ret["PackageVersion"] = packageHash["Version"] ret["Size"] = packageHash["Size"] ret["Repository"] = packageHash.key?("Repository") ? packageHash["Repository"] : nil ret["Installed"] = true ret["UpdateState"] = "NotNeeded" if (Integer(packageHash["InstalledOn"]) rescue false) ret["Timestamp"] = OMS::Common.format_time(packageHash["InstalledOn"].to_i) end ret end def strToXML(xml_string) xml_unescaped_string = CGI::unescapeHTML(xml_string) REXML::Document.new xml_unescaped_string end # Returns an array of xml instances (all types) def getInstancesXML(inventoryXML) instances = [] xpathFilter = "INSTANCE/PROPERTY.ARRAY/VALUE.ARRAY/VALUE/INSTANCE" inventoryXML.elements.each(xpathFilter) { |inst| instances << inst } instances end def removeDuplicateCollectionNames(data_items) collection_names = Set.new data_items.select { |data_item| collection_names.add?(data_item["CollectionName"]) && true || false } end def transform_and_wrap( inventoryXMLstr, host, time, force_send_run_interval = 86400, osNameParam = nil, osFullNameParam = nil, osVersionParam = nil, osShortNameParam = nil) agentId = getAgentId() hostOSDetailsMap = getHostOSDetails() # Taking the parameters - Helps in mocking, in the case of tests. osName = (osNameParam == nil) ? hostOSDetailsMap["OSName"] : osNameParam osVersion = (osVersionParam == nil) ? hostOSDetailsMap["OSVersion"] : osVersionParam osFullName = (osFullNameParam == nil) ? hostOSDetailsMap["OSFullName"] : osFullNameParam osShortName = getOSShortName(osShortNameParam, osVersion) # Do not send duplicate data if we are not forced to hash = Digest::SHA256.hexdigest(inventoryXMLstr) OMS::Log.info_once("LinuxUpdates : Sending available updates information data. Hash=#{hash[0..5]}") # Extract the instances in xml format inventoryXML = strToXML(inventoryXMLstr) instancesXML = getInstancesXML(inventoryXML) # Split installedPackages from services installedPackagesXML = instancesXML.select { |instanceXML| isInstalledPackageInstanceXML(instanceXML) } availableUpdatesXML = instancesXML.select { |instanceXML| isAvailableUpdateInstanceXML(instanceXML) } # Convert to xml to hash/json representation installedPackages = installedPackagesXML.map! { |installedPackage| installedPackageXMLtoHash(installedPackage, osShortName)} availableUpdates = availableUpdatesXML.map! { |availableUpdate| availableUpdatesXMLtoHash(availableUpdate, osShortName)} # Remove duplicate services because duplicate CollectionNames are not supported. TODO implement ordinal solution installedPackages = removeDuplicateCollectionNames(installedPackages) availableUpdates = removeDuplicateCollectionNames(availableUpdates) #Today the agent doesn't send any data if the machine is not missing any available updates. #This leads to uncertainty whether the machine is really up to date or it is not sending data eventhough updates are missing. #with this change,the agent will send heartbeat item whether the machine is missing updates or not. heartbeatItem = availableHeartbeatItem() collections = [heartbeatItem] if (installedPackages.size > 0) collections += installedPackages end if (availableUpdates.size > 0) collections += availableUpdates end timestamp = OMS::Common.format_time(time) wrapper = { "DataType"=>"LINUX_UPDATES_SNAPSHOT_BLOB", "IPName"=>"Updates", "DataItems"=>[{ "Timestamp" => timestamp, "Host" => host, "AgentId" => agentId, "OSType" => "Linux", "OSName" => osName, "OSVersion" => osVersion, "OSFullName" => osFullName, "Collections"=> collections }]} OMS::Log.info_once("LinuxUpdates : installedPackages x #{installedPackages.size}, availableUpdates x #{availableUpdates.size}") return wrapper end end