# -*- 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 include HamlHelpers 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 offset_class(first_col) return "" if first_col "col-md-offset-6" 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 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"] enable :sessions 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 def all_descriptions check_session_for_error 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 end get "/" do all_descriptions haml File.read(File.join(Machinery::ROOT, "html/homepage.html.haml")) end get "/fonts/:font" do File.read(File.join(Machinery::ROOT, "html/assets/fonts/#{params[:font]}")) end get "/compare/:a/:b" do all_descriptions @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 unless @description_a[scope] @meta[:uninspected][@description_a.name] ||= Array.new @meta[:uninspected][@description_a.name] << scope end unless @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 all_descriptions begin @description = SystemDescription.load(params[:id], settings.system_description_store) rescue Machinery::Errors::SystemDescriptionNotFound => e session[:error] = e.to_s redirect "/" rescue Machinery::Errors::SystemDescriptionIncompatible, \ Machinery::Errors::SystemDescriptionError => e @error = e haml File.read(File.join(Machinery::ROOT, "html/exception.html.haml")) else @description.load_existing_diffs haml File.read(File.join(Machinery::ROOT, "html/index.html.haml")) end end private def check_session_for_error if session[:error] @errors ||= Array.new @errors.push(session[:error]) session.clear end end def render_exception_title case @error when Machinery::Errors::SystemDescriptionIncompatible return "System Description incompatible!" when Machinery::Errors::SystemDescriptionError return "System Description broken!" end end end