diff --git a/lib/puppet/provider/grafana_folder/grafana.rb b/lib/puppet/provider/grafana_folder/grafana.rb new file mode 100644 index 0000000..1fe819e --- /dev/null +++ b/lib/puppet/provider/grafana_folder/grafana.rb @@ -0,0 +1,141 @@ +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 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 + 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 + return unless folders.find { |x| x['title'] == resource[:title] } + + response = send_request('GET', format('%s/folders', resource[:grafana_api_path])) + if response.code != '200' + raise format('Fail to retrieve folders (HTTP response: %s/%s)', response.code, response.body) + end + + begin + folders = JSON.parse(response.body) + folders.each do |folder| + if folder['title'] == resource[:title] + @folder = folder + end + end + rescue JSON::ParserError + raise format('Fail to parse folder %s: %s', resource[:title], response.body) + 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] + } + + 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) + 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 slug + resource[:title].downcase.gsub(%r{[ \+]+}, '-').gsub(%r{[^\w\- ]}, '') + end + + def create + save_folder(resource) + end + + def destroy + if @folder.nil? + find_folder + end + + 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? + find_folder + end +end diff --git a/lib/puppet/type/grafana_folder.rb b/lib/puppet/type/grafana_folder.rb new file mode 100644 index 0000000..5b87440 --- /dev/null +++ b/lib/puppet/type/grafana_folder.rb @@ -0,0 +1,54 @@ +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' + defaultto 1 + end + + autorequire(:service) do + 'grafana-server' + end +end diff --git a/spec/unit/puppet/type/grafana_folder_type_spec.rb b/spec/unit/puppet/type/grafana_folder_type_spec.rb new file mode 100644 index 0000000..6c7fb9a --- /dev/null +++ b/spec/unit/puppet/type/grafana_folder_type_spec.rb @@ -0,0 +1,56 @@ +# Copyright 2015 Mirantis, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +require 'spec_helper' + +describe Puppet::Type.type(:grafana_folder) do + let(:gfolder) do + described_class.new name: 'foo', grafana_url: 'http://example.com/', ensure: :present + end + + context 'when setting parameters' do + it "fails if grafana_url isn't HTTP-based" do + expect do + described_class.new name: 'foo', grafana_url: 'example.com', ensure: :present + end.to raise_error(Puppet::Error, %r{not a valid URL}) + end + + it "fails if grafana_api_path isn't properly formed" do + expect do + described_class.new name: 'foo', grafana_url: 'http://example.com', grafana_api_path: '/invalidpath', ensure: :present + end.to raise_error(Puppet::Error, %r{not a valid API path}) + end + + # rubocop:disable RSpec/MultipleExpectations + it 'accepts valid parameters' do + expect(gfolder[:name]).to eq('foo') + expect(gfolder[:grafana_url]).to eq('http://example.com/') + end + it 'autorequires the grafana-server for proper ordering' do + catalog = Puppet::Resource::Catalog.new + service = Puppet::Type.type(:service).new(name: 'grafana-server') + catalog.add_resource service + catalog.add_resource gfolder + + relationship = gfolder.autorequire.find do |rel| + (rel.source.to_s == 'Service[grafana-server]') && (rel.target.to_s == gfolder.to_s) + end + expect(relationship).to be_a Puppet::Relationship + end + it 'does not autorequire the service it is not managed' do + catalog = Puppet::Resource::Catalog.new + catalog.add_resource gfolder + expect(gfolder.autorequire).to be_empty + end + end +end