Skip to content

Instantly share code, notes, and snippets.

@aaronkaka
Last active March 8, 2020 23:25
Show Gist options
  • Save aaronkaka/8491321 to your computer and use it in GitHub Desktop.
Save aaronkaka/8491321 to your computer and use it in GitHub Desktop.
Sonar Secured Widget for Dashing Framework

Preview

Screenshot

Description

Dashing widget to display Sonar metrics, connecting to either a secured or unsecured Sonar repository.

I created this widget after originally attempting to use EHadoux's simple Sonar widget, which did not allow access to my company's secured Sonar instance. Additionally, I implemented the ability to set up the widget using an external configuration file.

Please note that this is technically a job versus a widget, in that it periodically requests data from Sonar and pushes that to the list widget as the view.

Dependencies

The list widget packaged with the Dashing framework.

##Usage

Copy sonar.rb and sonar.cfg into the jobs directory.

Add the following snippet to the dashboard layout file:

<li data-row="1" data-col="1" data-sizex="1" data-sizey="1">
 <div data-id="sonar" data-view="List" data-title="sonar stats" data-unordered="true" data-moreinfo="values as %">
 </div>
</li>

To display more metrics, increase the data-sizey attribute.

Configuration

Change the values for the configuration in sonar.cfg. All values are required except for username and password. If no username is provided, it is assumed the Sonar instance is unsecured. Order of display in the widget is dictated by the order of configured metrics.

If sonar.cfg is not found, the dashboard will fail to start with a "file not found" message.

Job implementation requires restarting Dashing if you change the configuration file.

server = "https://something.com/sonar"
key = "key"
widget_id = "sonar"
interval = "3600s"
metrics = "coverage,test_success_density"
username = "username"
password = "password"
require 'net/http'
require 'json'
require 'openssl'
require 'uri'
config_path = File.expand_path(File.join(File.dirname(__FILE__), "sonar.cfg"))
puts "Sonar widget configuration: " + config_path
configuration = Hash[File.read(config_path).scan(/(\S+)\s*=\s*"([^"]+)/)]
# Required config
server = "#{configuration['server']}".strip
key = "#{configuration['key']}".strip
id = "#{configuration['widget_id']}".strip
interval = "#{configuration['interval']}".strip
metrics = "#{configuration['metrics']}".strip
# Optional config for secured instances
username = "#{configuration['username']}".strip
password = "#{configuration['password']}"
if id.empty?
abort("MISSING widget id configuration!")
end
if interval.empty?
abort("MISSING interval configuration!")
end
if server.empty?
abort("MISSING server configuration!")
end
if key.empty?
abort("MISSING key configuration!")
end
if metrics.empty?
abort("MISSING metrics configuration!")
end
def get_val(json, key)
returnval = ''
json['msr'].find do |item|
returnval = item['val']
key == item['key']
end
returnval
end
SCHEDULER.every interval, :first_in => 0 do |job|
res = nil
uri = URI("#{server}/api/resources?resource=#{key}&metrics=#{metrics}")
# if no username is provided, assume sonar server is unsecured
if username.empty?
res = Net::HTTP.get(uri)
else
Net::HTTP.start(uri.host, uri.port,
:use_ssl => uri.scheme == 'https', :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|
request = Net::HTTP::Get.new uri.request_uri
request.basic_auth username, password
res = http.request request
end
end
# Order of display is dictated by order of metrics in sonar.cfg
metricsArray = metrics.split(',')
hashArray = []
metricsArray.each { |x| hashArray.push({:label => x.gsub("_", " "), :value => get_val(JSON[res.body][0], x) }) }
send_event(id, { items: hashArray })
end
@centic9
Copy link

centic9 commented Feb 26, 2014

Thanks for this, it's quite useful. One note though: the Sonar API returns the elements in arbitrary order, so it would be better to not use index-access, but rather look for the key. Also having the display names in the code and not in the config seems strange, ideally I would have a list of metrics and a corresponding list of names for those metric, both in the config.

Unfortunately my Ruby is not good enough (yet) to propose a patch here...

@munkius
Copy link

munkius commented Feb 27, 2014

@centic9, started working on sonar integration today and I made a small function to get a value in a slightly better way.

def get_val(json, key)
  json['msr'].find do |item|
    key == item['key']
  end['val'] rescue nil
end

Which you can call using

coverage = get_val json, 'coverage'

This helps not to rely on an index in the resulting array. And makes it easier using other metrics.

@aaronkaka
Copy link
Author

Thanks guys! I had no experience in Ruby before this effort, so I had learned enough to accomplish what I needed. I will incorporate your insights and update the job.

UPDATE (3/6/2014): sonar.rb enhanced to address the concerns stated by @centic9 and @munkius.

@christianhau
Copy link

Hi! I have installed the files and added the div to the dashboard, but I am getting this error, anything you have experienced before?

scheduler caught exception:
A JSON text must at least contain two octets!
/usr/lib/ruby/2.3.0/json/common.rb:156:in `initialize'
/usr/lib/ruby/2.3.0/json/common.rb:156:in `new'
/usr/lib/ruby/2.3.0/json/common.rb:156:in `parse'
/usr/lib/ruby/2.3.0/json/common.rb:15:in `[]'
/home/christian/smashing-dashboards/elhubdashboard/jobs/sonar.rb:69:in `block (2                                                                            levels) in <top (required)>'
/home/christian/smashing-dashboards/elhubdashboard/jobs/sonar.rb:69:in `each'
/home/christian/smashing-dashboards/elhubdashboard/jobs/sonar.rb:69:in `block in                                                                            <top (required)>'
/var/lib/gems/2.3.0/gems/rufus-scheduler-2.0.24/lib/rufus/sc/jobs.rb:230:in `tri                                                                           gger_block'
/var/lib/gems/2.3.0/gems/rufus-scheduler-2.0.24/lib/rufus/sc/jobs.rb:204:in `blo                                                                           ck in trigger'
/var/lib/gems/2.3.0/gems/rufus-scheduler-2.0.24/lib/rufus/sc/scheduler.rb:430:in                                                                            `block in trigger_job'

@SR-G
Copy link

SR-G commented Mar 8, 2020

Not sure if someone is still using this, i was willing to give a try to see what can be done with Smashing, but :

  • the SonarQube API has changed in the meanwhile (for example, /api/resources/ is now deprecated + JSON output has changed)
  • debugging the whole thing is not really easy (Smashing is not helping a lot and URI, JSON results ... should really be dumped / debugged out-of-the-box, IMHO)

Anyway the working GIST should now be (tested today on SonarQube 7.9.2) :

require 'net/http'
require 'json'
require 'openssl'
require 'uri'

config_path = File.expand_path(File.join(File.dirname(__FILE__), "sonar.cfg"))
puts "Sonar widget configuration: " + config_path
configuration = Hash[File.read(config_path).scan(/(\S+)\s*=\s*"([^"]+)/)]

# Required config
server = "#{configuration['server']}".strip
key = "#{configuration['key']}".strip
id = "#{configuration['widget_id']}".strip
interval = "#{configuration['interval']}".strip
metrics = "#{configuration['metrics']}".strip

# Optional config for secured instances
username = "#{configuration['username']}".strip
password = "#{configuration['password']}"

if id.empty?
    abort("MISSING widget id configuration!")
end
if interval.empty?
    abort("MISSING interval configuration!")
end
if server.empty?
    abort("MISSING server configuration!")
end
if key.empty?
    abort("MISSING key configuration!")
end
if metrics.empty?
    abort("MISSING metrics configuration!")
end

def get_val(json, key)
  returnval = ''
  json['measures'].find do |item|
    returnval = item['value']
    key == item['metric']
  end
  returnval
end

SCHEDULER.every interval, :first_in => 0 do |job|

    res = nil
    puts "server: " + server
    puts "#{server}/api/measures/search?projectKeys=#{key}&metricKeys=#{metrics}"
    uri = URI("#{server}/api/measures/search?projectKeys=#{key}&metricKeys=#{metrics}")

    # if no username is provided, assume sonar server is unsecured
    if username.empty?
        res = Net::HTTP.get(uri)

    else
        Net::HTTP.start(uri.host, uri.port,
          :use_ssl => uri.scheme == 'https', :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |http|

          request = Net::HTTP::Get.new uri.request_uri
          request.basic_auth username, password

          res = http.request request
        end
    end

    # Order of display is dictated by order of metrics in sonar.cfg
    metricsArray = metrics.split(',')
    hashArray = []
    metricsArray.each { |x| hashArray.push({:label => x.gsub("_", " "), :value => get_val(JSON[res.body], x) }) }

    send_event(id, { items: hashArray })
end

No changes expected in sonar.cfg (for example, for metrics i was using, in case keys have changed : duplicated_lines,coverage,blocker_violations,major_violations)
By the way, list of available metrics can now be retrieved (for debugging purpose) through /api/metrics/search

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment