diff --git a/lib/puppet/provider/grafana_dashboard/grafana.rb b/lib/puppet/provider/grafana_dashboard/grafana.rb index 0323809..d01e892 100644 --- a/lib/puppet/provider/grafana_dashboard/grafana.rb +++ b/lib/puppet/provider/grafana_dashboard/grafana.rb @@ -1,175 +1,175 @@ # Copyright 2015 Mirantis, Inc. # require 'json' require File.expand_path(File.join(File.dirname(__FILE__), '..', 'grafana')) # Note: this class doesn't implement the self.instances and self.prefetch # methods because the Grafana API doesn't allow to retrieve the dashboards and # all their properties in a single call. Puppet::Type.type(:grafana_dashboard).provide(:grafana, parent: Puppet::Provider::Grafana) do desc 'Support for Grafana dashboards stored into Grafana' defaultfor kernel: 'Linux' def organization resource[:organization] end def grafana_api_path resource[:grafana_api_path] end def fetch_organizations response = send_request('GET', format('%s/orgs', resource[:grafana_api_path])) if response.code != '200' raise format('Fail to retrieve organizations (HTTP response: %s/%s)', response.code, response.body) end begin fetch_organizations = JSON.parse(response.body) fetch_organizations.map { |x| x['id'] }.map do |id| response = send_request 'GET', format('%s/orgs/%s', resource[:grafana_api_path], id) if response.code != '200' raise format('Failed to retrieve organization %d (HTTP response: %s/%s)', id, response.code, response.body) end fetch_organization = JSON.parse(response.body) { id: fetch_organization['id'], name: fetch_organization['name'] } end rescue JSON::ParserError raise format('Failed to parse response: %s', response.body) end end def fetch_organization unless @fetch_organization @fetch_organization = if resource[:organization].is_a?(Numeric) || resource[:organization].match(%r{^[0-9]*$}) fetch_organizations.find { |x| x[:id] == resource[:organization] } else fetch_organizations.find { |x| x[:name] == resource[:organization] } end end @fetch_organization end def folders response = send_request('GET', format('%s/folders', resource[:grafana_api_path])) if response.code != '200' raise format('Fail to retrieve the folders (HTTP response: %s/%s)', response.code, response.body) end begin @folders = JSON.parse(response.body) rescue JSON::ParserError raise format('Fail to parse folders (HTTP response: %s/%s)', response.code, response.body) end end def find_folder folders unless @folders begin @folder = @folders.find { |folder| folder['title'] == resource[:folder] } raise format('Folder not found: %s', resource[:folder]) unless @folder rescue JSON::ParserError raise format('Fail to parse folder %s: %s', resource[:folder], response.body) end end # Return the list of dashboards def dashboards # change organizations response = send_request 'POST', format('%s/user/using/%s', resource[:grafana_api_path], fetch_organization[:id]) unless response.code == '200' raise format('Failed to switch to org %s (HTTP response: %s/%s)', fetch_organization[:id], response.code, response.body) end response = send_request('GET', format('%s/search', resource[:grafana_api_path]), nil, q: '', starred: false) if response.code != '200' raise format('Fail to retrieve the dashboards (HTTP response: %s/%s)', response.code, response.body) end begin JSON.parse(response.body) rescue JSON::ParserError raise format('Fail to parse dashboards (HTTP response: %s/%s)', response.code, response.body) end end # Return the dashboard matching with the resource's title def find_dashboard db = dashboards.find { |x| x['title'] == resource[:title] } return if db.nil? response = send_request('GET', format('%s/dashboards/uid/%s', resource[:grafana_api_path], db['uid'])) if response.code != '200' raise format('Fail to retrieve dashboard %s by uid %s (HTTP response: %s/%s)', resource[:title], db['uid'], response.code, response.body) end begin # Cache the dashboard's content @dashboard = JSON.parse(response.body)['dashboard'] rescue JSON::ParserError raise format('Fail to parse dashboard %s: %s', resource[:title], response.body) end end def save_dashboard(dashboard) find_folder if resource[:folder] # change organizations response = send_request 'POST', format('%s/user/using/%s', resource[:grafana_api_path], fetch_organization[:id]) unless response.code == '200' raise format('Failed to switch to org %s (HTTP response: %s/%s)', fetch_organization[:id], response.code, response.body) end data = { dashboard: dashboard.merge('title' => resource[:title], 'id' => @dashboard ? @dashboard['id'] : nil, - 'uid' => @dashboard ? @dashboard['uid'] : nil, + 'uid' => @dashboard ? @dashboard['uid'] : slug, 'version' => @dashboard ? @dashboard['version'] + 1 : 0), folderId: @folder ? @folder['id'] : nil, overwrite: !@dashboard.nil? } response = send_request('POST', format('%s/dashboards/db', resource[:grafana_api_path]), data) return unless (response.code != '200') && (response.code != '412') raise format('Fail to save dashboard %s (HTTP response: %s/%s)', resource[:name], response.code, response.body) end def slug resource[:title].downcase.gsub(%r{[ \+]+}, '-').gsub(%r{[^\w\- ]}, '') end def content @dashboard.reject { |k, _| k =~ %r{^id|uid|version|title$} } end def content=(value) save_dashboard(value) end def create save_dashboard(resource[:content]) end def destroy db = dashboards.find { |x| x['title'] == resource[:title] } raise Puppet::Error, format('Failed to delete dashboard %s, dashboard not found', resource[:title]) if db.nil? response = send_request('DELETE', format('%s/dashboards/uid/%s', resource[:grafana_api_path], db['uid'])) return unless response.code != '200' raise Puppet::Error, format('Failed to delete dashboard %s (HTTP response: %s/%s)', resource[:title], response.code, response.body) end def exists? find_dashboard end end diff --git a/spec/acceptance/grafana_folder_spec.rb b/spec/acceptance/grafana_folder_spec.rb index 3cc532d..bb951e1 100644 --- a/spec/acceptance/grafana_folder_spec.rb +++ b/spec/acceptance/grafana_folder_spec.rb @@ -1,201 +1,201 @@ require 'spec_helper_acceptance' supported_versions.each do |grafana_version| describe "grafana_folder with Grafana version #{grafana_version}" do context 'setup grafana server' do it 'runs successfully' do pp = <<-EOS class { 'grafana': version => "#{grafana_version}", cfg => { security => { admin_user => 'admin', admin_password => 'admin' } } } EOS prepare_host apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end end context 'create folder resource' do it 'creates the folders' do pp = <<-EOS include grafana::validator grafana_folder { 'example-folder': ensure => present, uid => 'example-folder', grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', permissions => [ {'permission' => 2, 'role' => 'Editor'}, {'permission' => 1, 'role' => 'Viewer'}, ], } grafana_folder { 'editor-folder': ensure => present, uid => 'editor-folder', grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', permissions => [ {'permission' => 1, 'role' => 'Editor'}, ], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has created the example folder' do shell('curl --user admin:admin http://localhost:3000/api/folders') do |f| expect(f.stdout).to match(%r{example-folder}) end end it 'has created the editor folder' do shell('curl --user admin:admin http://localhost:3000/api/folders') do |f| expect(f.stdout).to match(%r{editor-folder}) end end it 'has created the example folder permissions' do shell('curl --user admin:admin http://localhost:3000/api/folders/example-folder/permissions') do |f| data = JSON.parse(f.stdout) expect(data).to include(hash_including('permission' => 2, 'role' => 'Editor'), hash_including('permission' => 1, 'role' => 'Viewer')) end end it 'has created the editor folder permissions' do shell('curl --user admin:admin http://localhost:3000/api/folders/editor-folder/permissions') do |f| data = JSON.parse(f.stdout) expect(data).to include(hash_including('permission' => 1, 'role' => 'Editor')) end end end context 'updates folder resource' do it 'updates the folders' do pp = <<-EOS grafana_folder { 'example-folder': ensure => present, uid => 'example-folder', grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', permissions => [ {'permission' => 2, 'role' => 'Editor'}, ], } grafana_folder { 'editor-folder': ensure => present, uid => 'editor-folder', grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', permissions => [ {'permission' => 1, 'role' => 'Viewer'}, ], } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has updated the example folder permissions' do shell('curl --user admin:admin http://localhost:3000/api/folders/example-folder/permissions') do |f| data = JSON.parse(f.stdout) expect(data).to include(hash_including('permission' => 2, 'role' => 'Editor')) # expect(data.size).to eq(1) # expect(data[0]['permission']).to eq(2) # expect(data[0]['role']).to eq('Editor') end end it 'has updated the editor folder permissions' do shell('curl --user admin:admin http://localhost:3000/api/folders/editor-folder/permissions') do |f| data = JSON.parse(f.stdout) expect(data).to include(hash_including('permission' => 1, 'role' => 'Viewer')) end end end context 'create folder containing dashboard' do it 'creates an example dashboard in the example folder' do pp = <<-EOS include grafana::validator grafana_dashboard { 'example-dashboard': ensure => present, grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', content => '{"description": "example dashboard"}', folder => 'example-folder' } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'folder contains dashboard' do - shell('curl --user admin:admin http://localhost:3000/api/dashboards/uid/example-dashboard') do |f| + shell('curl --user admin:admin http://localhost:3000/api/search?query=example-dashboard') do |f| expect(f.stdout).to match(%r{"folderId":1}) end end end context 'destroy resources' do it 'destroys the folders and dashboard' do pp = <<-EOS include grafana::validator grafana_folder { 'example-folder': ensure => absent, grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', } grafana_folder { 'editor-folder': ensure => absent, grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', } grafana_folder { 'nomatch-folder': ensure => absent, grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', } grafana_dashboard { 'example-dashboard': ensure => absent, grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end it 'has no example-folder' do shell('curl --user admin:admin http://localhost:3000/api/folders') do |f| expect(f.stdout).not_to match(%r{example-folder}) end end it 'has no editor-folder' do shell('curl --user admin:admin http://localhost:3000/api/folders') do |f| expect(f.stdout).not_to match(%r{editor-folder}) end end it 'has no nomatch-folder' do shell('curl --user admin:admin http://localhost:3000/api/folders') do |f| expect(f.stdout).not_to match(%r{nomatch-folder}) end end end end end