From e064b4c7d8096f2d71fd78a79ed159e5c6edf097 Mon Sep 17 00:00:00 2001 From: Adam Ward Date: Wed, 20 Sep 2017 16:58:36 +1000 Subject: [PATCH] grafana_user custom resource --- README.md | 16 ++ lib/puppet/provider/grafana_user/grafana.rb | 161 ++++++++++++++++++ lib/puppet/type/grafana_user.rb | 66 +++++++ .../puppet/type/grafana_user_type_spec.rb | 50 ++++++ 4 files changed, 293 insertions(+) create mode 100644 lib/puppet/provider/grafana_user/grafana.rb create mode 100644 lib/puppet/type/grafana_user.rb create mode 100644 spec/unit/puppet/type/grafana_user_type_spec.rb diff --git a/README.md b/README.md index 9b8a5a77a..6f6973968 100644 --- a/README.md +++ b/README.md @@ -379,6 +379,22 @@ completely missing. Example usage: ```puppet grafana::plugin{'grafana-simple-json-datasource':} ``` + +##### `grafana::user` + +Creates and manages a global grafana user via the API. + +```puppet +grafana_user { 'username': + grafana_url => 'http://localhost:3000', + grafana_user => 'admin', + grafana_password => '5ecretPassw0rd', + full_name => 'John Doe', + password => 'Us3r5ecret', + email => 'john@example.com', +} +``` + ## Limitations This module has been tested on Ubuntu 14.04, using each of the 'archive', 'docker' diff --git a/lib/puppet/provider/grafana_user/grafana.rb b/lib/puppet/provider/grafana_user/grafana.rb new file mode 100644 index 000000000..da0eeffdf --- /dev/null +++ b/lib/puppet/provider/grafana_user/grafana.rb @@ -0,0 +1,161 @@ +# 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 'json' + +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'grafana')) + +Puppet::Type.type(:grafana_user).provide(:grafana, parent: Puppet::Provider::Grafana) do + desc 'Support for Grafana users' + + defaultfor kernel: 'Linux' + + def users + response = send_request('GET', '/api/users') + if response.code != '200' + raise format('Fail to retrieve users (HTTP response: %s/%s)', response.code, response.body) + end + + begin + users = JSON.parse(response.body) + + users.map { |x| x['id'] }.map do |id| + response = send_request 'GET', format('/api/users/%s', id) + if response.code != '200' + raise format('Fail to retrieve user %d (HTTP response: %s/%s)', id, response.code, response.body) + end + + user = JSON.parse(response.body) + { + id: id, + name: user['login'], + full_name: user['name'], + email: user['email'], + theme: user['theme'], + password: nil, + is_admin: user['isGrafanaAdmin'] ? :true : :false + } + end + rescue JSON::ParserError + raise format('Fail to parse response: %s', response.body) + end + end + + def user + @user = users.find { |x| x[:name] == resource[:name] } unless @user + @user + end + + attr_writer :user + + def name + user[:name] + end + + def name=(value) + resource[:name] = value + save_user + end + + def full_name + user[:full_name] + end + + def full_name=(value) + resource[:full_name] = value + save_user + end + + def email + user[:email] + end + + def email=(value) + resource[:email] = value + save_user + end + + def theme + user[:theme] + end + + def theme=(value) + resource[:theme] = value + save_user + end + + def password + user[:password] + end + + def password=(value) + resource[:password] = value + save_user + end + + # rubocop:disable Style/PredicateName + def is_admin + user[:is_admin] + end + + def is_admin=(value) + resource[:is_admin] = value + save_user + end + # rubocop:enable Style/PredicateName + + def save_user + data = { + login: resource[:name], + name: resource[:full_name], + email: resource[:email], + password: resource[:password], + theme: resource[:theme], + isGrafanaAdmin: (resource[:is_admin] == :true) + } + + if user.nil? + response = send_request('POST', '/api/admin/users', data) + else + data[:id] = user[:id] + send_request 'PUT', format('/api/admin/users/%s/password', user[:id]), password: data.delete(:password) + send_request 'PUT', format('/api/admin/users/%s/permissions', user[:id]), isGrafanaAdmin: data.delete(:isGrafanaAdmin) + response = send_request 'PUT', format('/api/users/%s', user[:id]), data + end + + if response.code != '200' + raise format('Failed to create user %s (HTTP response: %s/%s)', resource[:name], response.code, response.body) + end + self.user = nil + end + + def delete_user + response = send_request 'DELETE', format('/api/admin/users/%s', user[:id]) + + if response.code != '200' + raise format('Failed to delete user %s (HTTP response: %s/%s', resource[:name], response.code, response.body) + end + self.user = nil + end + + def create + save_user + end + + def destroy + delete_user + end + + def exists? + user + end +end diff --git a/lib/puppet/type/grafana_user.rb b/lib/puppet/type/grafana_user.rb new file mode 100644 index 000000000..8b15bb010 --- /dev/null +++ b/lib/puppet/type/grafana_user.rb @@ -0,0 +1,66 @@ +# 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. +# +Puppet::Type.newtype(:grafana_user) do + @doc = 'Manage users in Grafana' + + ensurable + + newparam(:name, namevar: true) do + desc 'The username of the user.' + 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' + end + + newparam(:grafana_password) do + desc 'The password for the Grafana server' + end + + newparam(:full_name) do + desc 'The full name of the user.' + end + + newproperty(:password) do + desc 'The password for the user' + end + + newproperty(:email) do + desc 'The email for the user' + end + + newproperty(:theme) do + desc 'The theme for the user' + end + + newproperty(:is_admin) do + desc 'Whether the user is a grafana admin' + newvalues(:true, :false) + defaultto :false + end + + autorequire(:service) do + 'grafana-server' + end +end diff --git a/spec/unit/puppet/type/grafana_user_type_spec.rb b/spec/unit/puppet/type/grafana_user_type_spec.rb new file mode 100644 index 000000000..f8f63b999 --- /dev/null +++ b/spec/unit/puppet/type/grafana_user_type_spec.rb @@ -0,0 +1,50 @@ +# 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_user) do + let(:guser) do + described_class.new name: 'test', full_name: 'Mr tester', password: 't3st', grafana_url: 'http://example.com/' + end + + context 'when setting parameters' do + it "fails if grafana_url isn't HTTP-based" do + expect do + described_class.new name: 'test', grafana_url: 'example.com' + end.to raise_error(Puppet::Error, %r{not a valid URL}) + end + + # rubocop:disable RSpec/MultipleExpectations + it 'accepts valid parameters' do + expect(guser[:name]).to eq('test') + expect(guser[:full_name]).to eq('Mr tester') + expect(guser[:password]).to eq('t3st') + expect(guser[: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 guser + + relationship = guser.autorequire.find do |rel| + (rel.source.to_s == 'Service[grafana-server]') && (rel.target.to_s == guser.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 guser + expect(guser.autorequire).to be_empty + end + end +end