class QualityAnalysisController < ApplicationController before_filter :find_project_by_project_id#, :except => [:getattachtype] before_filter :find_quality_analysis, :only => [:edit, :update_jenkins_job] before_filter :authorize before_filter :connect_jenkins, :only => [:create, :edit, :update_jenkins_job, :index, :delete] layout "base_projects" include ApplicationHelper include QualityAnalysisHelper require 'jenkins_api_client' require 'nokogiri' require 'json' require 'open-uri' def show end # params 说明:{identifier:版本库名} # type: 1 新的分析 2 重新分析 def create begin user_name = User.find(params[:user_id]).try(:login) identifier = params[:identifier] rep_id = params[:rep_id] # job_name and sonar_name 前者为job名字,后者为jenkins配置名 job_name = "#{user_name}-#{rep_id}" sonar_name = "#{user_name}:#{rep_id}" # 考虑到历史数据:有些用户创建类job但是build失败,即sonar没有结果,这个时候需要把job删除,并且删掉quality_analyses表数据 # 如果不要这句则需要迁移数据 @sonar_address = Redmine::Configuration['sonar_address'] # projects_date = open(@sonar_address + "/api/projects/index").read # arr = JSON.parse(projects_date).map {|m| m["nm"]} # eg: ["Hjqreturn:cc_rep", "Hjqreturn:putong", "Hjqreturn:sonar_rep2", "shitou:sonar_rep"] quality_an = QualityAnalysis.where(:sonar_name => sonar_name) # if @client_jenkins.job.exists?(job_name) && QualityAnalysis.where(:sonar_name => sonar_name).select{|qa| arr.include?(qa.sonar_name)}.blank? # aa = @client_jenkins.job.delete("#{job_name}") # quality_an.delete unless quality_an.blank? # end # type 1的时候之所以判断job是否存在,为了防止特殊情况,正常情况是不会出现的 # 重新分析的时候需要删除以前的分析结果 @client_jenkins.job.delete("#{job_name}") if @client_jenkins.job.exists?(job_name) quality_an.delete_all unless quality_an.blank? # Checks if the given job exists in Jenkins. @g = Gitlab.client branch = params[:branch] language = swith_language_type(params[:language]) path = params[:path].blank? ? "./" : params[:path] # qa = QualityAnalysis.where(:project_id => @project.id, :author_login => user_name).first version = 1 properties = "sonar.projectKey=#{sonar_name} sonar.projectName=#{sonar_name} sonar.projectVersion=#{version} sonar.sources=#{path} sonar.language=#{language.downcase} sonar.sourceEncoding=utf-8" git_url = @gitlab_address.to_s+"/"+@project.owner.to_s+"/"+ identifier + "."+"git" # 替换配置文件 @doc = Nokogiri::XML(File.open(File.join(Rails.root, 'tmp', 'config.xml'))) @doc.at_xpath("//hudson.plugins.git.UserRemoteConfig/url").content = git_url @doc.at_xpath("//hudson.plugins.git.BranchSpec/name").content = "*/#{branch}" @doc.at_xpath("//hudson.plugins.sonar.SonarRunnerBuilder/properties").content = properties # sonar-properties # jenkins job创建 jenkins_job = @client_jenkins.job.create("#{job_name}", @doc.to_xml) # 将地址作为hook值添加到gitlab # @g.add_project_hook(@project.gpid, @jenkins_address + "/project/#{job_name}") # job创建完成后自动运行job,如果运行成功则返回‘200’ code = @client_jenkins.job.build("#{job_name}") # 判断调用sonar分析是否成功 # 等待启动时间处理, 最长时间为30分钟 for i in 0..360 do sleep(5) @current_build_status = @client_jenkins.job.get_current_build_status("#{job_name}") if (@current_build_status == "success" || @current_build_status == "failure") break if i == 360 @build_console_result = false break end end end # sonar 缓冲,sonar生成数据 sleep(10) # 获取sonar output结果 console_build = @client_jenkins.job.get_console_output("#{job_name}", build_num = 0, start = 0, mode = 'text')["output"] logger.info("@current_build_status is ==> #{@current_build_status}") # 两种情况需要删除job: # 1/创建成功但是build失败则删除job # 2/creat和build成功,调用sonar启动失败则删除job # 错误信息存储需存到Trustie数据库,否则一旦job删除则无法获取这些信息 if jenkins_job == '200' && code != '201' @client_jenkins.job.delete("#{job_name}") else if @current_build_status == "failure" reg_console = /Exception:.*?\r/.match(console_build) output = reg_console[0].gsub("\r", "") unless reg_console.nil? se = SonarError.where(:jenkins_job_name => job_name).first se.nil? ? SonarError.create(:project_id => @project.id, :jenkins_job_name => job_name, :output => output) : se.update_column(:output, output) @client_jenkins.job.delete("#{job_name}") elsif @current_build_status == "success" if quality_an.blank? QualityAnalysis.create(:project_id => @project.id, :author_login => user_name, :rep_identifier => identifier, :sonar_version => version, :path => path, :branch => branch, :language => language, :sonar_name => "#{user_name}:#{rep_id}") end end end respond_to do |format| if @current_build_status == "success" format.html{redirect_to project_quality_analysis_path(:project_id => @project.id, :resource_id => sonar_name, :branch => branch, :current_build_status => @current_build_status, :job_name => job_name)} elsif @current_build_status == "failure" format.html{redirect_to error_list_project_quality_analysi_path(:project_id => @project.id, :job_name => job_name)} end end rescue => e @message = e.message logger.error("######################====>#{e.message}") respond_to do |format| format.html{redirect_to error_list_project_quality_analysi_path(:project_id => @project.id, :job_name => job_name, :message => @message)} # format.html{redirect_to :controller => 'repositories', :action => 'show', :id => @project, :repository_id => identifier} end end end def error_list @error_list = SonarError.where(:jenkins_job_name => params[:job_name]).first respond_to do |format| format.html end end # get language type def swith_language_type language if language == "c#" "cs" elsif language == "python" "py" elsif language == "c" "c++" else language end end def edit @g = Gitlab.client gitlab_branches = @g.branches(@project.gpid) @branch_names = gitlab_branches.map{|b| b.name} @gitlab_default_branch = @g.project(@project.gpid).default_branch end # 删除的时候主要删除三方面数据:1/Trustie数据 2/jenkins数据 3/sonar数据 # 如果只删除数据1,则新建的时候会有冲突 def delete begin qa = QualityAnalysis.find(params[:id]) rep_id = Repository.where(:project_id => @project.id, :identifier => qa.rep_identifier).first.try(:id) job_name = "#{qa.author_login}-#{rep_id}" logger.info("result: job_name ###################==>#{job_name}") logger.info("result: @client_jenkins.job ###################==>#{@client_jenkins.job}") d_job = @client_jenkins.job.delete(job_name) logger.info("result: delete job ###################==>#{d_job}") qa.delete respond_to do |format| format.html{redirect_to :controller => 'repositories', :action => 'show', :id => @project, :repository_id => gitlab_repository(@project).identifier} end rescue Exception => e puts e end end # 更新Jenkins job,主要包括相关配置文件参数的更新,Trustie平台数据的更新 def update_jenkins_job begin rep_id = Repository.where(:project_id => @project.id).first.try(:id) sonar_name = @quality_analysis.sonar_name job_name = "#{@quality_analysis.author_login}-#{rep_id}" version = @quality_analysis.sonar_version path = params[:path].blank? ? "./" : params[:path] language = swith_language_type(params[:language]) branch = params[:branch] identifier = @quality_analysis.rep_identifier properties = "sonar.projectKey=#{sonar_name} sonar.projectName=#{sonar_name} sonar.projectVersion=#{version} sonar.sources=#{path} sonar.language=#{language.downcase} sonar.sourceEncoding=utf-8" git_url = @gitlab_address.to_s+"/"+@project.owner.to_s+"/"+ identifier + "."+"git" # modify config.yml @doc = Nokogiri::XML(File.open(File.join(Rails.root, 'tmp', 'config.xml'))) @doc.at_xpath("//hudson.plugins.git.UserRemoteConfig/url").content = git_url @doc.at_xpath("//hudson.plugins.git.BranchSpec/name").content = "*/#{branch}" @doc.at_xpath("//hudson.plugins.sonar.SonarRunnerBuilder/properties").content = properties # sonar-properties # update成功则返回 ‘200’ jenkins_job = @client_jenkins.job.update("#{job_name}", @doc.to_xml) # 数据更新到Trustie数据 if jenkins_job == '200' logger.info("quality_ananlysis will be updated: ==> #{jenkins_job}") @quality_analysis.path = path @quality_analysis.language = language @quality_analysis.branch = branch @quality_analysis.save end rescue Exception => e logger.error("Update jenkins job: #{e}") end respond_to do |format| format.html{redirect_to project_quality_analysis_path(:project_id => @project.id)} format.js end end # resource_id: login + @repository.id def index begin @branch = params[:branch] @resource_id = params[:resource_id] @sonar_address = Redmine::Configuration['sonar_address'] if params[:resource_id].nil? @name_flag = true projects_date = open(@sonar_address + "/api/projects/index").read arr = JSON.parse(projects_date).map {|m| m["nm"]} # eg: ["Hjqreturn:cc_rep", "Hjqreturn:putong", "Hjqreturn:sonar_rep2", "shitou:sonar_rep"] @quality_analyses = QualityAnalysis.where(:project_id => @project.id).select{|qa| arr.include?(qa.sonar_name)} else filter = "sqale_rating,function_complexity,duplicated_lines_density,comment_lines_density,sqale_index,lines,files,functions,classes,directories,blocker_violations,critical_violations,major_violations,minor_violations,info_violations,violations" complexity_date = open(@sonar_address + "/api/resources/index?resource=#{@resource_id}&depth=0&metrics=#{filter}").read @complexity =JSON.parse(complexity_date).first # 获取排名结果 @g = Gitlab.client @author_infos = @g.rep_user_stats(@project.gpid, :rev => @branch) @user_quality_infos = [] @author_infos.each do |author_info| email = author_info.email changes = author_info.changes.to_i unresolved_issues = open(@sonar_address + "/api/issues/search?projectKeys=#{@resource_id}&authors=#{email}&resolved=false").read unresolved_issue_count = JSON.parse(unresolved_issues)["total"].to_i all_issues = open(@sonar_address + "/api/issues/search?projectKeys=#{@resource_id}&authors=#{email}").read all_issue_count = JSON.parse(all_issues)["total"].to_i ratio = ((changes == 0 || all_issue_count == 0) ? 0 : format("%0.4f",all_issue_count.to_f/changes.to_f)) @user_quality_infos << {:email => email, :changes => changes, :unresolved_issue_count => unresolved_issue_count, :ratio => ratio, :all_issue_count => all_issue_count} end # 按名称转换成hash键值对 @ha = {} @complexity["msr"].each do |com| key = com["key"] if key == "sqale_index" value = com["frmt_val"] else value = com["val"] end @ha.store(key,value) end end rescue => e puts e end end # Find project of id params[:project_id] def find_project_by_project_id @project = Project.find(params[:project_id]) rescue ActiveRecord::RecordNotFound render_404 end def find_quality_analysis begin @quality_analysis = QualityAnalysis.find(params[:id]) rescue render_404 end end # Authorize the user for the requested action def authorize(ctrl = params[:controller], action = params[:action], global = false) unless @project.archived? && @project.gpid.nil? true else render_403 :message => :notice_not_authorized_archived_project end end def connect_jenkins @gitlab_address = Redmine::Configuration['gitlab_address'] @jenkins_address = Redmine::Configuration['jenkins_address'] jenkins_username = Redmine::Configuration['jenkins_username'] jenkins_password = Redmine::Configuration['jenkins_password'] # connect jenkins @client_jenkins = JenkinsApi::Client.new(:server_url => @jenkins_address, :username => jenkins_username, :password => jenkins_password) rescue => e logger.error("failed to connect Jenkins ==> #{e}") end end