Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/tools/dev/find_release_notes.rb
Views: 11766
#!/usr/bin/env ruby12##3# This module requires Metasploit: https://metasploit.com/download4# Current source: https://github.com/rapid7/metasploit-framework5##67require 'net/http'8require 'nokogiri'9require 'thread'1011module ReleaseNotesFinder12# This finds the release notes information based on either:13# 1. A PR number. In release notes, PR numbers are for bug fixes and notable changes.14# 2. A module short name. For example: ms08_067_netapi15class Client16attr_accessor :release_notes1718RELEASE_NOTES_PAGE = 'https://community.rapid7.com/docs/DOC-2918'.freeze1920def initialize21init_release_notes22@mutex = Mutex.new23end2425def add_release_notes_entry(row)26td = row.search('td')27release_notes_link = td[0] && td[0].at('a') ? td[0].at('a').attributes['href'].value : ''28release_notes_num = td[0] && td[0].at('a') ? td[0].at('a').text.scan(/\d{10}/).flatten.first || '' : ''29highlights = td[1] ? (td[1].search('span') || []).map { |e| e.text } * " " : ''30update_link = td[2] && td[2].at('a') ? td[2].at('a').attributes['href'].value : ''3132@release_notes << {33release_notes_link: release_notes_link,34release_notes_num: release_notes_num,35highlights: highlights,36update_link: update_link,37pull_requests: [],38new_modules: []39}40end4142def init_release_notes43self.release_notes = []4445html = send_http_request(RELEASE_NOTES_PAGE)46table_rows_pattern = 'div[@id="jive-body-main"]//div//section//div//div[@class="j-rte-table"]//table//tbody//tr'47rows = html.search(table_rows_pattern)48rows.each do |row|49add_release_notes_entry(row)50end51end5253def update_pr_list(n, text)54pr_num, desc = text.scan(/#(\d+).\x20*(.+)/).flatten55return unless pr_num56n[:pull_requests] << { id: pr_num, description: desc }57end5859def update_module_list(n, li)60li.search('a').each do |a|61next if a.attributes['href'].nil?62n[:new_modules] << { link: a.attributes['href'].value }63end64end6566def update_release_notes_entry(n)67html = send_http_request(n[:release_notes_link])68pattern = '//div[@class="jive-rendered-content"]//ul//li'69html.search(pattern).each do |li|70@mutex.synchronize do71update_pr_list(n, li.text)72update_module_list(n, li)73end74end75end7677def get_release_notes(input)78release_notes.each do |n|79if n[:pull_requests].empty?80update_release_notes_entry(n)81end8283input_type = guess_input_type(input)8485case input_type86when :pr87m = get_release_notes_from_pr(n, input)88when :module_name89m = get_release_notes_from_module_name(n, input)90end9192return m if m93end9495nil96end9798def guess_input_type(input)99input =~ /^\d+/ ? :pr : :module_name100end101102def get_release_notes_from_module_name(n, input)103n[:new_modules].each do |m|104return n if m[:link] && m[:link].include?(input)105end106107nil108end109110def get_release_notes_from_pr(n, pr)111n[:pull_requests].each do |p|112return n if p[:id] && pr == p[:id]113end114115nil116end117118def send_http_request(uri)119url = URI.parse(uri)120cli = Net::HTTP.new(url.host, url.port)121cli.use_ssl = true122req = Net::HTTP::Get.new(url.request_uri)123res = cli.request(req)124Nokogiri::HTML(res.body)125end126end127end128129def main130inputs = []131132ARGV.length.times { inputs << ARGV.shift }133puts "[*] Enumerating release notes..."134cli = ReleaseNotesFinder::Client.new135puts "[*] Finding release notes for items: #{inputs * ', '}"136threads = []137begin138inputs.each do |input|139t = Thread.new do140n = cli.get_release_notes(input)141puts "\n"142143if n144puts "[*] Found release notes for: #{input}"145puts "Release Notes Number: #{n[:release_notes_num]}"146puts "Release Notes Link: #{n[:release_notes_link] || 'N/A'}"147puts "Update Link: #{n[:update_link] || 'N/A'}"148puts "Highlights:\n#{n[:highlights]}"149else150puts "[*] Unable to find release notes for: #{input}"151end152end153threads << t154end155threads.each { |t| t.join }156ensure157threads.each { |t| t.kill }158end159end160161if __FILE__ == $PROGRAM_NAME162main163end164165166