diff --git a/lib/puppet/provider/grafana_folder/grafana.rb b/lib/puppet/provider/grafana_folder/grafana.rb index ac9621b..6e1f81d 100644 --- a/lib/puppet/provider/grafana_folder/grafana.rb +++ b/lib/puppet/provider/grafana_folder/grafana.rb @@ -1,132 +1,179 @@ require 'json' require File.expand_path(File.join(File.dirname(__FILE__), '..', 'grafana')) Puppet::Type.type(:grafana_folder).provide(:grafana, parent: Puppet::Provider::Grafana) do desc 'Support for Grafana folders stored into Grafana' defaultfor kernel: 'Linux' def organization resource[:organization] end def grafana_api_path resource[:grafana_api_path] end + def permissions + find_folder unless @folder + response = send_request('GET', format('%s/folders/%s/permissions', resource[:grafana_api_path], @folder['uid'])) + + if response.code != '200' + raise format('Failed to retrieve permissions %s (HTTP response: %s/%s)', resource[:title], response.code, response.body) + end + begin + permissions = JSON.parse(response.body) + rescue JSON::ParserError + raise format('Fail to parse permissions (HTTP response: %s/%s)', response.code, response.body) + end + parsed_permissions = [] + # These are the only configurable keys, many others are returned + keep_keys = %w[permission role teamId userId] + permissions.each do |permission| + parsed_permission = {} + permission.each_pair do |key, value| + parsed_permission[key] = value if keep_keys.include?(key) && value != 0 + end + parsed_permissions << parsed_permission unless parsed_permission.empty? + end + parsed_permissions + end + + def permissions=(value) + save_permissions(value) + 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 @folders.each do |folder| @folder = folder if folder['title'] == resource[:title] end end def save_folder(folder) 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 # if folder exists, update object based on uid # else, create object if @folder.nil? data = { - title: resource[:title] + title: resource[:title], + uid: resource[:uid] } response = send_request('POST', format('%s/folders', resource[:grafana_api_path]), data) - return unless (response.code != '200') && (response.code != '412') - raise format('Failed to create folder %s (HTTP response: %s/%s)', resource[:title], response.code, response.body) + if (response.code != '200') && (response.code != '412') + raise format('Failed to create folder %s (HTTP response: %s/%s)', resource[:title], response.code, response.body) + end + folders + find_folder + save_permissions(resource[:permissions]) else data = { folder: folder.merge('title' => resource[:title], 'uid' => @folder['uid']), overwrite: !@folder.nil? } response = send_request('POST', format('%s/folders/%s', resource[:grafana_api_path], @folder['uid']), data) return unless (response.code != '200') && (response.code != '412') raise format('Failed to update folder %s (HTTP response: %s/%s)', resource[:title], response.code, response.body) end end + def save_permissions(value) + return if value.nil? + 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 + find_folder unless @folder + data = { 'items' => value } + response = send_request 'POST', format('%s/folders/%s/permissions', resource[:grafana_api_path], @folder['uid']), data + return if response.code == '200' + raise format('Failed to update permissions %s (HTTP response: %s/%s)', fetch_organization[:id], response.code, response.body) + end + def slug resource[:title].downcase.gsub(%r{[ \+]+}, '-').gsub(%r{[^\w\- ]}, '') end def create save_folder(resource) end def destroy find_folder unless @folder response = send_request('DELETE', format('%s/folders/%s', resource[:grafana_api_path], @folder['uid'])) return unless response.code != '200' raise Puppet::Error, format('Failed to delete folder %s (HTTP response: %s/%s)', resource[:title], response.code, response.body) end def exists? folders unless @folders @folders.each do |folder| return true if folder['title'] == resource[:title] end false end end diff --git a/lib/puppet/type/grafana_folder.rb b/lib/puppet/type/grafana_folder.rb index 5b87440..396e736 100644 --- a/lib/puppet/type/grafana_folder.rb +++ b/lib/puppet/type/grafana_folder.rb @@ -1,54 +1,58 @@ require 'json' Puppet::Type.newtype(:grafana_folder) do @doc = 'Manage folders in Grafana' ensurable newparam(:title, namevar: true) do desc 'The title of the folder' end newparam(:uid) do desc 'UID of the folder' end newparam(:grafana_url) do desc 'The URL of the Grafana server' defaultto '' validate do |value| unless value =~ %r{^https?://} raise ArgumentError, format('%s is not a valid URL', value) end end end newparam(:grafana_user) do desc 'The username for the Grafana server (optional)' end newparam(:grafana_password) do desc 'The password for the Grafana server (optional)' end newparam(:grafana_api_path) do desc 'The absolute path to the API endpoint' defaultto '/api' validate do |value| unless value =~ %r{^/.*/?api$} raise ArgumentError, format('%s is not a valid API path', value) end end end newparam(:organization) do - desc 'The organization name to create the datasource on' + desc 'The organization name to create the folder on' defaultto 1 end + newproperty(:permissions, array_matching: :all) do + desc 'The permissions of the folder' + end + autorequire(:service) do 'grafana-server' end end diff --git a/spec/acceptance/grafana_folder_spec.rb b/spec/acceptance/grafana_folder_spec.rb index 2ef3ee7..b5b0746 100644 --- a/spec/acceptance/grafana_folder_spec.rb +++ b/spec/acceptance/grafana_folder_spec.rb @@ -1,104 +1,193 @@ require 'spec_helper_acceptance' describe 'grafana_folder' do context 'setup grafana server' do it 'runs successfully' do pp = <<-EOS class { 'grafana': cfg => { security => { admin_user => 'admin', admin_password => 'admin' } } } EOS apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end end context 'create folder resource' do - it 'creates the folder' do + it 'creates 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' + 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 the folder' do + 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 grafana_dashboard { 'example-dashboard': ensure => present, grafana_url => 'http://localhost:3000', grafana_user => 'admin', grafana_password => 'admin', content => '{"uid": "abc123xy"}', 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/db/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 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