class Chef::Knife::TidyNotify
Public Instance Methods
run()
click to toggle source
# File lib/chef/knife/tidy_notify.rb, line 53 def run reports_dir = tidy.reports_dir report_file_suffixes = ["_unused_cookbooks.json", "_cookbook_count.json", "_stale_nodes.json"] # Only grab the files matching the report_file_suffixes report_files = Dir["#{reports_dir}/*{#{report_file_suffixes.join(",")}}"] ui.info "Reading from #{tidy.reports_dir} directory" # Fetch list of organization names from reports directory begin org_names = report_files.map { |r_file| r_file.match("#{reports_dir}\/(.*)(#{report_file_suffixes.join("|")})").captures.first }.uniq rescue NoMethodError ui.stderr.puts "Failed to parse json reports files. Please ensure your reports are valid." return end if config[:org_list] filter_orgs = config[:org_list].split(",") # Take the intersection of org_names and filter_orgs org_names &= filter_orgs end reports = {} # Iterate through list of collected organizations and parse any report files into JSON objects unless org_names ui.std.puts "No valid org reports found to send notifications. Exiting." return end org_names.each do |org| ui.info("Fetching report data for organization #{org}") reports[org] = {} report_file_suffixes.each do |report| begin file_name = "#{reports_dir}/#{org}#{report}" ui.info(" Parsing file #{file_name}") json_string = File.read(file_name) reports[org][report] = tidy.json_file_to_hash(json_string, symbolize_names: false) rescue Errno::ENOENT ui.info(" Skipping file #{file_name} - not found for organization #{org}") reports[org][report] = {} end end # Fetch a list of admin users for the current organization ui.info("Fetching admins users for organization #{org}") begin admins = org_admins(org) reports[org]["admins"] = admins.map { |name, _data| org_user(org, name) unless name == "pivotal" } rescue Net::HTTPServerException ui.info(" Cannot fetch admin users for organization #{org} as it does not exist on the server") end # Build list of email recipients from the collected admin users (display name and email address of each) email_recipients = reports[org]["admins"].map { |admin| { name: admin["display_name"], email: admin["email"] } unless admin.nil? }.compact # Send a report email to all admin users of the organization ui.info "Sending email reports for organization #{org}" email_content = generate_email(reports, org, email_recipients, report_file_suffixes) send_email(email_content, email_recipients) end end
Private Instance Methods
generate_email(report_data, organization, recipients, report_file_suffixes)
click to toggle source
# File lib/chef/knife/tidy_notify.rb, line 119 def generate_email(report_data, organization, recipients, report_file_suffixes) mime_boundary = "==Multipart_Boundary_x#{srand}x" message = <<~MESSAGE_END From: Knife Tidy <#{config[:smtp_from]}> To: #{recipients.map { |recipient| "#{recipient[:name]} <#{recipient[:email]}>" }.join(", ")} MIME-Version: 1.0 Subject: Knife Tidy Cleanup Report for Organization "#{organization}" Content-Type: multipart/mixed; boundary="#{mime_boundary}"; --#{mime_boundary} Content-type: text/html Content-Transfer-Encoding: 7bit The following reports were generated by <a href="https://github.com/chef-customers/knife-tidy">knife-tidy</a>, and contain a list of unused cookbooks and stale nodes for the Chef server organization "#{organization}" #{generate_total_cookbooks_table(report_data, organization)} #{generate_unused_cookbooks_table(report_data, organization)} #{generate_node_table(report_data, organization)} MESSAGE_END report_file_suffixes.each do |suffix| message += <<~MESSAGE_END --#{mime_boundary} Content-Transfer-Encoding:7bit Content-Type: plain/text;name="#{organization}#{suffix}";charset="UTF-8" Content-Disposition: attachment;filename="#{organization}#{suffix}" #{report_data[organization][suffix].to_json} MESSAGE_END end message += <<~MESSAGE_END --#{mime_boundary}-- MESSAGE_END puts message message end
generate_node_table(report_data, organization)
click to toggle source
# File lib/chef/knife/tidy_notify.rb, line 180 def generate_node_table(report_data, organization) table_start = "<h2>Stale Nodes</h2><p>This table contains nodes that have not checked in to the Chef Server in #{report_data[organization]["_stale_nodes.json"]["threshold_days"]} days.<p><table border='1' cellpadding='1' cellspacing='0'>" table_end = "</table>" header_string = "<tr><th>Node Name</th></tr>" table_body = if report_data[organization]["_stale_nodes.json"].empty? || report_data[organization]["_stale_nodes.json"]["count"] == 0 "<tr><td colspan='2'>No stale nodes</td></tr>" else report_data[organization]["_stale_nodes.json"]["list"].map { |node_name| "<tr><td>#{node_name}</td></tr>" }.join("\n") end table_start + header_string + table_body + table_end end
generate_total_cookbooks_table(report_data, organization)
click to toggle source
# File lib/chef/knife/tidy_notify.rb, line 156 def generate_total_cookbooks_table(report_data, organization) table_start = "<h2>Total Versions by Cookbook</h2><p>This table contains the count of versions of each cookbook stored on the Chef Server.<p><table border='1' cellpadding='1' cellspacing='0'>" table_end = "</table><br/>" header_string = "<tr><th>Cookbook Name</th><th>Total Version Count</th></tr>" table_body = if report_data[organization]["_cookbook_count.json"].empty? "<tr><td colspan='2'>No cookbook versions</td></tr>" else report_data[organization]["_cookbook_count.json"].map { |cookbook_name, cookbook_count| "<tr><td>#{cookbook_name}</td><td>#{cookbook_count}</td></tr>" }.join("\n") end table_start + header_string + table_body + table_end end
generate_unused_cookbooks_table(report_data, organization)
click to toggle source
# File lib/chef/knife/tidy_notify.rb, line 168 def generate_unused_cookbooks_table(report_data, organization) table_start = "<h2>Unused Cookbooks</h2><p>This table contains cookbook names and the count of their versions that are not currently in the runlists of any nodes.<p><table border='1' cellpadding='1' cellspacing='0'>" table_end = "</table><br/>" header_string = "<tr><th>Cookbook Name</th><th>Unused Versions</th></tr>" table_body = if report_data[organization]["_unused_cookbooks.json"].empty? "<tr><td colspan='2'>No unused cookbook versions</td></tr>" else report_data[organization]["_unused_cookbooks.json"].map { |cookbook_name, cookbook_versions| "<tr><td>#{cookbook_name}</td><td>#{cookbook_versions.join("<br>")}</td></tr>" }.join("\n") end table_start + header_string + table_body + table_end end
org_admins(org)
click to toggle source
# File lib/chef/knife/tidy_notify.rb, line 200 def org_admins(org) admins = {} rest.get("/organizations/#{org}/groups/admins")["users"].each do |name| admins[name] = {} end admins end
org_user(org, username)
click to toggle source
# File lib/chef/knife/tidy_notify.rb, line 208 def org_user(org, username) rest.get("/organizations/#{org}/users/#{username}") end
send_email(mail_content, recipients)
click to toggle source
# File lib/chef/knife/tidy_notify.rb, line 192 def send_email(mail_content, recipients) smtp = Net::SMTP.new(config[:smtp_server], config[:smtp_port]) smtp.enable_starttls if config[:smtp_use_tls] smtp.start(config[:smtp_helo], config[:smtp_username], config[:smtp_password], :login) do |server| server.send_message(mail_content, config[:smtp_from], recipients.map { |recipient| recipient[:email] }) end end