# -*- coding: utf-8 -*-
# Copyright (c) 2013-2016 SUSE LLC
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 3 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, contact SUSE LLC.
#
# To contact SUSE about this file by physical or electronic mail,
# you may find current contact information at www.suse.com
# Rack::TryStatic was taken from the rack-contrib repository (https://github.com/rack/rack-contrib)
module Rack
class TryStatic
def initialize(app, options)
@app = app
@try = ["", *options[:try]]
@static = ::Rack::Static.new(->(_) { [404, {}, []] }, options)
end
def call(env)
orig_path = env["PATH_INFO"]
found = nil
@try.each do |path|
resp = @static.call(env.merge!("PATH_INFO" => orig_path + path))
break if !(403..405).cover?(resp[0]) && found = resp
end
found || @app.call(env.merge!("PATH_INFO" => orig_path))
end
end
end
class Server < Sinatra::Base
module Helpers
def render_partial(partial, locals = {})
source = File.read(File.join(Machinery::ROOT, "html/partials/#{partial}.html.haml"))
haml source, locals: locals
end
def render_scope(scope)
render_partial scope, scope => @description[scope]
end
def scope_meta_info(scope)
return "" if !@description[scope]
" (" \
"inspected host: '#{@description[scope].meta.hostname}', " \
"at: #{DateTime.parse(@description[scope].meta.modified).strftime("%F %T")})"
end
def scope_help(scope)
text = scope_info(scope)[:description]
Kramdown::Document.new(text).to_html
end
def scope_info(scope)
YAML.load(File.read(File.join(Machinery::ROOT, "plugins", "#{scope}/#{scope}.yml")))
end
def scope_title(scope)
scope_info(scope)[:name]
end
def scope_initials(scope)
scope_info(scope)[:initials].upcase
end
def nav_class(scope)
if @description
return @description[scope] ? "" : "disabled"
elsif @description_a && @description_b
return @description_a[scope] && @description_b[scope] ? "" : "disabled"
end
end
def safe_length(object, attribute)
if collection = object.try(attribute)
collection.length
else
0
end
end
def only_in_a
"
Only in '#{@description_a.name}':
"
end
def only_in_b
"Only in '#{@description_b.name}':
"
end
def in_both
"In both descriptions:
"
end
def changed
"In both with different attributes:
"
end
def pluralize_scope(object, singular, plural)
object.length.to_s + " " + Machinery.pluralize(object.length, singular, plural)
end
def repository_changes
klass = @diff["repositories"].changed.first.first.class
changed_elements("repositories", attributes: klass.attributes, key: klass.key)
end
def changed_elements(scope, opts)
optional_attributes = opts[:optional_attributes] || []
changed = []
@diff[scope].changed.each do |change|
changes = []
relevant_attributes = if opts[:attributes]
opts[:attributes].dup
else
change[0].attributes.keys & change[1].attributes.keys
end
(1..optional_attributes.length).each do |i|
if change[0][optional_attributes[i - 1]] ==
change[1][optional_attributes[i - 1]]
relevant_attributes.push(optional_attributes[i])
else
break
end
end
relevant_attributes.each do |attribute|
if change[0][attribute] != change[1][attribute]
changes.push(
attribute + ": " + human_readable_attribute(change[0], attribute) + " ↔ " +
human_readable_attribute(change[1], attribute)
)
end
end
changed.push(
id: change[0][opts[:key]],
change: "(" + changes.join(", ") + ")",
diffable: change[0].is_a?(UnmanagedFile) && change[0].is_a?(UnmanagedFile) &&
change[0].file? && change[1].file? &&
@diff[scope].try(:common).try(:attributes).try(:[], "extracted")
)
end
changed
end
def human_readable_attribute(object, attribute)
value = object[attribute]
case object
when Machinery::SystemFile
value = number_to_human_size(value) if attribute == "size"
end
value.to_s
end
def diff_to_object(diff)
diff = Machinery.scrub(diff)
lines = diff.lines[2..-1]
diff_object = {
file: diff[/--- a(.*)/, 1],
additions: lines.select { |l| l.start_with?("+") }.length,
deletions: lines.select { |l| l.start_with?("-") }.length
}
original_line_number = 0
new_line_number = 0
diff_object[:lines] = lines.map do |line|
line = ERB::Util.html_escape(line.chomp).
gsub("\\", "\").
gsub("\t", " " * 8)
case line
when /^@.*/
entry = {
type: "header",
content: line
}
original_line_number = line[/-(\d+)/, 1].to_i
new_line_number = line[/\+(\d+)/, 1].to_i
when /^ .*/, ""
entry = {
type: "common",
new_line_number: new_line_number,
original_line_number: original_line_number,
content: line[1..-1]
}
new_line_number += 1
original_line_number += 1
when /^\+.*/
entry = {
type: "addition",
new_line_number: new_line_number,
content: line[1..-1]
}
new_line_number += 1
when /^\-.*/
entry = {
type: "deletion",
original_line_number: original_line_number,
content: line[1..-1]
}
original_line_number += 1
end
entry
end
diff_object
end
end
helpers Helpers
# Serve the 'manual/site' directory under /site using Rack::TryStatic
use Rack::TryStatic,
root: File.join(Machinery::ROOT, "manual"),
urls: %w[/site],
try: ["index.html"]
get "/descriptions/:id/files/:scope/*" do
description = SystemDescription.load(params[:id], settings.system_description_store)
filename = File.join("/", params["splat"].first)
file = description[params[:scope]].find { |f| f.name == filename }
if request.accept.first.to_s == "text/plain" && file.binary?
status 406
return "binary file"
end
content = file.content
type = MimeMagic.by_path(filename) || MimeMagic.by_magic(content) || "text/plain"
content_type type
attachment File.basename(filename)
content
end
get "/" do
descriptions = settings.system_description_store.list
@all_descriptions = Hash.new
descriptions.each do |name|
scopes = []
begin
system_description = SystemDescription.load(
name, settings.system_description_store, skip_validation: true
)
@all_descriptions[name] = Hash.new
@all_descriptions[name]["date"] = system_description.latest_update
@all_descriptions[name]["host"] = system_description.host
system_description.scopes.each do |scope|
entry = Machinery::Ui.internal_scope_list_to_string(scope)
if SystemDescription::EXTRACTABLE_SCOPES.include?(scope)
if system_description.scope_extracted?(scope)
entry += " (extracted)"
else
entry += " (not extracted)"
end
end
scopes << entry
end
@all_descriptions[name]["scopes"] = scopes
rescue Machinery::Errors::SystemDescriptionIncompatible,
Machinery::Errors::SystemDescriptionError => e
@errors ||= Array.new
@errors.push(e)
end
end
haml File.read(File.join(Machinery::ROOT, "html/landing_page.html.haml"))
end
get "/compare/:a/:b" do
@description_a = SystemDescription.load(params[:a], settings.system_description_store)
@description_b = SystemDescription.load(params[:b], settings.system_description_store)
@meta = {}
@diff = {}
Inspector.all_scopes.each do |scope|
if @description_a[scope] && @description_b[scope]
@diff[scope] = Comparison.compare_scope(@description_a, @description_b, scope)
elsif @description_a[scope] || @description_b[scope]
@meta[:uninspected] ||= Hash.new
if !@description_a[scope]
@meta[:uninspected][@description_a.name] ||= Array.new
@meta[:uninspected][@description_a.name] << scope
end
if !@description_b[scope]
@meta[:uninspected][@description_b.name] ||= Array.new
@meta[:uninspected][@description_b.name] << scope
end
end
end
haml File.read(File.join(Machinery::ROOT, "html/comparison.html.haml"))
end
get "/compare/:a/:b/files/:scope/*" do
description1 = SystemDescription.load(params[:a], settings.system_description_store)
description2 = SystemDescription.load(params[:b], settings.system_description_store)
filename = File.join("/", params["splat"].first)
begin
diff = FileDiff.diff(description1, description2, params[:scope], filename)
rescue Machinery::Errors::BinaryDiffError
status 406
return "binary file"
end
diff.to_s(:html)
end
get "/:id" do
@description = SystemDescription.load(params[:id], settings.system_description_store)
diffs_dir = @description.scope_file_store("analyze/config_file_diffs").path
if @description.config_files && diffs_dir
# Enrich description with the config file diffs
@description.config_files.each do |file|
path = File.join(diffs_dir, file.name + ".diff")
file.diff = diff_to_object(File.read(path)) if File.exists?(path)
end
end
haml File.read(File.join(Machinery::ROOT, "html/index.html.haml"))
end
end