diff --git a/lib/puppet/type/grafana_plugin.rb b/lib/puppet/type/grafana_plugin.rb index c8e85ab..6e92801 100644 --- a/lib/puppet/type/grafana_plugin.rb +++ b/lib/puppet/type/grafana_plugin.rb @@ -1,62 +1,61 @@ Puppet::Type.newtype(:grafana_plugin) do desc <<-DESC manages grafana plugins @example Install a grafana plugin grafana_plugin { 'grafana-simple-json-datasource': } @example Install a grafana plugin from different repo grafana_plugin { 'grafana-simple-json-datasource': ensure => 'present', repo => 'https://nexus.company.com/grafana/plugins', } @example Install a grafana plugin from a plugin url grafana_plugin { 'grafana-example-custom-plugin': ensure => 'present', plugin_url => 'https://github.com/example/example-custom-plugin/zipball/v1.0.0' } @example Uninstall a grafana plugin grafana_plugin { 'grafana-simple-json-datasource': ensure => 'absent', } @example Show resources $ puppet resource grafana_plugin DESC ensurable do defaultto(:present) newvalue(:present) do provider.create end newvalue(:absent) do provider.destroy end end newparam(:name, namevar: true) do desc 'The name of the plugin to enable' newvalues(%r{^\S+$}) end newparam(:repo) do desc 'The URL of an internal plugin server' validate do |value| unless value =~ %r{^https?://} raise ArgumentError, format('%s is not a valid URL', value) end end end newparam(:plugin_url) do desc 'Full url to the plugin zip file' validate do |value| unless value =~ %r{^https?://} raise ArgumentError, format('%s is not a valid URL', value) end end end - end diff --git a/spec/acceptance/class_spec.rb b/spec/acceptance/class_spec.rb index 3c25517..0b42ddd 100644 --- a/spec/acceptance/class_spec.rb +++ b/spec/acceptance/class_spec.rb @@ -1,169 +1,168 @@ require 'spec_helper_acceptance' describe 'grafana class' do # Create dummy module directorty shell('mkdir -p /etc/puppetlabs/code/environments/production/modules/my_custom_module/files/dashboards') context 'default parameters' do before do install_module_from_forge('puppetlabs/apt', '>= 7.5.0 < 8.0.0') end # Using puppet_apply as a helper it 'works idempotently with no errors' do pp = <<-EOS class { 'grafana': } EOS # Run it twice and test for idempotency apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end describe package('grafana') do it { is_expected.to be_installed } end describe service('grafana-server') do it { is_expected.to be_enabled } it { is_expected.to be_running } end end context 'with fancy dashboard config' do it 'works idempotently with no errors' do pp = <<-EOS class { 'grafana': provisioning_datasources => { apiVersion => 1, datasources => [ { name => 'Prometheus', type => 'prometheus', access => 'proxy', url => 'http://localhost:9090/prometheus', isDefault => false, }, ], }, provisioning_dashboards => { apiVersion => 1, providers => [ { name => 'default', orgId => 1, folder => '', type => 'file', disableDeletion => true, options => { path => '/var/lib/grafana/dashboards', puppetsource => 'puppet:///modules/my_custom_module/dashboards', }, }, ], } } EOS # Run it twice and test for idempotency apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end end context 'with fancy dashboard config and custom target file' do it 'works idempotently with no errors' do pp = <<-EOS class { 'grafana': provisioning_datasources => { apiVersion => 1, datasources => [ { name => 'Prometheus', type => 'prometheus', access => 'proxy', url => 'http://localhost:9090/prometheus', isDefault => false, }, ], }, provisioning_dashboards => { apiVersion => 1, providers => [ { name => 'default', orgId => 1, folder => '', type => 'file', disableDeletion => true, options => { path => '/var/lib/grafana/dashboards', puppetsource => 'puppet:///modules/my_custom_module/dashboards', }, }, ], }, provisioning_dashboards_file => '/etc/grafana/provisioning/dashboards/dashboard.yaml', provisioning_datasources_file => '/etc/grafana/provisioning/datasources/datasources.yaml' } EOS # Run it twice and test for idempotency apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end end context 'update to beta release' do it 'works idempotently with no errors' do case fact('os.family') when 'Debian' pp = <<-EOS class { 'grafana': version => 'latest', repo_name => 'beta', } EOS when 'RedHat' pp = <<-EOS class { 'grafana': version => 'latest', repo_name => 'beta', } EOS end # Run it twice and test for idempotency apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) end describe package('grafana') do it { is_expected.to be_installed } end end context 'revert back to stable' do it 'works idempotently with no errors' do case fact('os.family') when 'Debian' pp = <<-EOS class { 'grafana': version => 'latest', } EOS # Run it twice and test for idempotency apply_manifest(pp, catch_failures: true) apply_manifest(pp, catch_changes: true) when 'RedHat' shell('/bin/rm /etc/yum.repos.d/grafana-beta.repo') shell('yum -y downgrade grafana') # No manifest to apply here end - end describe package('grafana') do it { is_expected.to be_installed } end end end diff --git a/spec/classes/grafana_spec.rb b/spec/classes/grafana_spec.rb index bde18fd..aff6bde 100644 --- a/spec/classes/grafana_spec.rb +++ b/spec/classes/grafana_spec.rb @@ -1,471 +1,470 @@ require 'spec_helper' describe 'grafana' do on_supported_os.each do |os, facts| context "on #{os}" do let(:facts) do facts end context 'with default values' do it { is_expected.to compile.with_all_deps } it { is_expected.to contain_class('grafana') } it { is_expected.to contain_class('grafana::install').that_comes_before('Class[grafana::config]') } it { is_expected.to contain_class('grafana::config').that_notifies('Class[grafana::service]') } it { is_expected.to contain_class('grafana::service') } end context 'with parameter install_method is set to package' do let(:params) do { install_method: 'package', version: '5.4.2' } end case facts[:osfamily] when 'Debian' download_location = '/tmp/grafana.deb' describe 'use archive to fetch the package to a temporary location' do it do is_expected.to contain_archive('/tmp/grafana.deb').with_source( 'https://dl.grafana.com/oss/release/grafana_5.4.2_amd64.deb' ) end it { is_expected.to contain_archive('/tmp/grafana.deb').that_comes_before('Package[grafana]') } end describe 'install dependencies first' do it { is_expected.to contain_package('libfontconfig1').with_ensure('present').that_comes_before('Package[grafana]') } end describe 'install the package' do it { is_expected.to contain_package('grafana').with_provider('dpkg') } it { is_expected.to contain_package('grafana').with_source(download_location) } end when 'RedHat' describe 'install dependencies first' do it { is_expected.to contain_package('fontconfig').with_ensure('present').that_comes_before('Package[grafana]') } end describe 'install the package' do it { is_expected.to contain_package('grafana').with_provider('rpm') } end end end context 'with some plugins passed in' do let(:params) do { plugins: { 'grafana-wizzle' => { 'ensure' => 'present' }, 'grafana-woozle' => { 'ensure' => 'absent' }, 'grafana-plugin' => { 'ensure' => 'present', 'repo' => 'https://nexus.company.com/grafana/plugins' }, 'grafana-plugin-url' => { 'ensure' => 'present', 'plugin_url' => 'https://grafana.com/api/plugins/grafana-simple-json-datasource/versions/latest/download' } } } end it { is_expected.to contain_grafana_plugin('grafana-wizzle').with(ensure: 'present') } it { is_expected.to contain_grafana_plugin('grafana-woozle').with(ensure: 'absent').that_notifies('Class[grafana::service]') } describe 'install plugin with plugin repo' do it { is_expected.to contain_grafana_plugin('grafana-plugin').with(ensure: 'present', repo: 'https://nexus.company.com/grafana/plugins') } end describe 'install plugin with plugin url' do it { is_expected.to contain_grafana_plugin('grafana-plugin-url').with(ensure: 'present', plugin_url: 'https://grafana.com/api/plugins/grafana-simple-json-datasource/versions/latest/download') } end - end context 'with parameter install_method is set to repo' do let(:params) do { install_method: 'repo' } end case facts[:osfamily] when 'Debian' describe 'install apt repo dependencies first' do it { is_expected.to contain_class('apt') } it { is_expected.to contain_apt__source('grafana').with(release: 'stable', repos: 'main', location: 'https://packages.grafana.com/oss/deb') } it { is_expected.to contain_apt__source('grafana').that_comes_before('Package[grafana]') } end describe 'install dependencies first' do it { is_expected.to contain_package('libfontconfig1').with_ensure('present').that_comes_before('Package[grafana]') } end describe 'install the package' do it { is_expected.to contain_package('grafana').with_ensure('installed') } end when 'RedHat' describe 'yum repo dependencies first' do it { is_expected.to contain_yumrepo('grafana-stable').with(baseurl: 'https://packages.grafana.com/oss/rpm', gpgkey: 'https://packages.grafana.com/gpg.key', enabled: 1) } it { is_expected.to contain_yumrepo('grafana-stable').that_comes_before('Package[grafana]') } end describe 'install dependencies first' do it { is_expected.to contain_package('fontconfig').with_ensure('present').that_comes_before('Package[grafana]') } end describe 'install the package' do it { is_expected.to contain_package('grafana').with_ensure('installed') } end end end context 'with parameter install_method is set to repo and manage_package_repo is set to false' do let(:params) do { install_method: 'repo', manage_package_repo: false, version: 'present' } end case facts[:osfamily] when 'Debian' describe 'install dependencies first' do it { is_expected.to contain_package('libfontconfig1').with_ensure('present').that_comes_before('Package[grafana]') } end describe 'install the package' do it { is_expected.to contain_package('grafana').with_ensure('present') } end when 'RedHat' describe 'install dependencies first' do it { is_expected.to contain_package('fontconfig').with_ensure('present').that_comes_before('Package[grafana]') } end describe 'install the package' do it { is_expected.to contain_package('grafana').with_ensure('present') } end when 'Archlinux' describe 'install the package' do it { is_expected.to contain_package('grafana').with_ensure('present') } end end end context 'with parameter install_method is set to archive' do let(:params) do { install_method: 'archive', version: '5.4.2' } end install_dir = '/usr/share/grafana' service_config = '/usr/share/grafana/conf/custom.ini' archive_source = 'https://dl.grafana.com/oss/release/grafana-5.4.2.linux-amd64.tar.gz' describe 'extract archive to install_dir' do it { is_expected.to contain_archive('/tmp/grafana.tar.gz').with_ensure('present') } it { is_expected.to contain_archive('/tmp/grafana.tar.gz').with_source(archive_source) } it { is_expected.to contain_archive('/tmp/grafana.tar.gz').with_extract_path(install_dir) } end describe 'create grafana user' do it { is_expected.to contain_user('grafana').with_ensure('present').with_home(install_dir) } it { is_expected.to contain_user('grafana').that_comes_before('File[/usr/share/grafana]') } end case facts[:osfamily] when 'Archlinux' describe 'create data_dir' do it { is_expected.to contain_file('/var/lib/grafana').with_ensure('directory') } end when 'Debian' describe 'create data_dir' do it { is_expected.to contain_file('/var/lib/grafana').with_ensure('directory') } end when 'FreBSD' describe 'create data_dir' do it { is_expected.to contain_file('/var/db/grafana').with_ensure('directory') } end when 'RedHat' describe 'create data_dir' do it { is_expected.to contain_file('/var/lib/grafana').with_ensure('directory') } end end describe 'manage install_dir' do it { is_expected.to contain_file(install_dir).with_ensure('directory') } it { is_expected.to contain_file(install_dir).with_group('grafana').with_owner('grafana') } end describe 'configure grafana' do it { is_expected.to contain_file(service_config).with_ensure('file') } end describe 'run grafana as service' do it { is_expected.to contain_service('grafana').with_ensure('running').with_provider('base') } it { is_expected.to contain_service('grafana').with_hasrestart(false).with_hasstatus(false) } end context 'when user already defined' do let(:pre_condition) do 'user{"grafana": ensure => present, }' end describe 'do NOT create grafana user' do it { is_expected.not_to contain_user('grafana').with_ensure('present').with_home(install_dir) } end end context 'when service already defined' do let(:pre_condition) do 'service{"grafana": ensure => running, name => "grafana-server", hasrestart => true, hasstatus => true, }' end describe 'do NOT run service' do it { is_expected.not_to contain_service('grafana').with_hasrestart(false).with_hasstatus(false) } end end end context 'invalid parameters' do context 'cfg' do describe 'should not raise an error when cfg parameter is a hash' do let(:params) do { cfg: {} } end it { is_expected.to contain_package('grafana') } end end end context 'configuration file' do describe 'should not contain any configuration when cfg param is empty' do it { is_expected.to contain_file('grafana.ini').with_content("# This file is managed by Puppet, any changes will be overwritten\n\n") } end describe 'should correctly transform cfg param entries to Grafana configuration' do let(:params) do { cfg: { 'app_mode' => 'production', 'section' => { 'string' => 'production', 'number' => 8080, 'boolean' => false, 'empty' => '' } }, ldap_cfg: { 'servers' => [ { 'host' => 'server1', 'use_ssl' => true, 'search_filter' => '(sAMAccountName=%s)', 'search_base_dns' => ['dc=domain1,dc=com'] } ], 'servers.attributes' => { 'name' => 'givenName', 'surname' => 'sn', 'username' => 'sAMAccountName', 'member_of' => 'memberOf', 'email' => 'email' } } } end expected = "# This file is managed by Puppet, any changes will be overwritten\n\n"\ "app_mode = production\n\n"\ "[section]\n"\ "boolean = false\n"\ "empty = \n"\ "number = 8080\n"\ "string = production\n" it { is_expected.to contain_file('grafana.ini').with_content(expected) } ldap_expected = "\n[[servers]]\n"\ "host = \"server1\"\n"\ "search_base_dns = [\"dc=domain1,dc=com\"]\n"\ "search_filter = \"(sAMAccountName=%s)\"\n"\ "use_ssl = true\n"\ "\n"\ "[servers.attributes]\n"\ "email = \"email\"\n"\ "member_of = \"memberOf\"\n"\ "name = \"givenName\"\n"\ "surname = \"sn\"\n"\ "username = \"sAMAccountName\"\n"\ "\n" it { is_expected.to contain_file('/etc/grafana/ldap.toml').with_content(ldap_expected) } end end context 'multiple ldap configuration' do describe 'should correctly transform ldap config param into Grafana ldap.toml' do let(:params) do { cfg: {}, ldap_cfg: [ { 'servers' => [ { 'host' => 'server1a server1b', 'use_ssl' => true, 'search_filter' => '(sAMAccountName=%s)', 'search_base_dns' => ['dc=domain1,dc=com'] } ], 'servers.attributes' => { 'name' => 'givenName', 'surname' => 'sn', 'username' => 'sAMAccountName', 'member_of' => 'memberOf', 'email' => 'email' } }, { 'servers' => [ { 'host' => 'server2a server2b', 'use_ssl' => true, 'search_filter' => '(sAMAccountName=%s)', 'search_base_dns' => ['dc=domain2,dc=com'] } ], 'servers.attributes' => { 'name' => 'givenName', 'surname' => 'sn', 'username' => 'sAMAccountName', 'member_of' => 'memberOf', 'email' => 'email' } } ] } end ldap_expected = "\n[[servers]]\n"\ "host = \"server1a server1b\"\n"\ "search_base_dns = [\"dc=domain1,dc=com\"]\n"\ "search_filter = \"(sAMAccountName=%s)\"\n"\ "use_ssl = true\n"\ "\n"\ "[servers.attributes]\n"\ "email = \"email\"\n"\ "member_of = \"memberOf\"\n"\ "name = \"givenName\"\n"\ "surname = \"sn\"\n"\ "username = \"sAMAccountName\"\n"\ "\n"\ "\n[[servers]]\n"\ "host = \"server2a server2b\"\n"\ "search_base_dns = [\"dc=domain2,dc=com\"]\n"\ "search_filter = \"(sAMAccountName=%s)\"\n"\ "use_ssl = true\n"\ "\n"\ "[servers.attributes]\n"\ "email = \"email\"\n"\ "member_of = \"memberOf\"\n"\ "name = \"givenName\"\n"\ "surname = \"sn\"\n"\ "username = \"sAMAccountName\"\n"\ "\n" it { is_expected.to contain_file('/etc/grafana/ldap.toml').with_content(ldap_expected) } end end context 'provisioning_dashboards defined' do let(:params) do { version: '6.0.0', provisioning_dashboards: { apiVersion: 1, providers: [ { name: 'default', orgId: 1, folder: '', type: 'file', disableDeletion: true, options: { path: '/var/lib/grafana/dashboards', puppetsource: 'puppet:///modules/my_custom_module/dashboards' } } ] } } end it do is_expected.to contain_file('/var/lib/grafana/dashboards').with( ensure: 'directory', owner: 'grafana', group: 'grafana', mode: '0750', recurse: true, purge: true, source: 'puppet:///modules/my_custom_module/dashboards' ) end context 'without puppetsource defined' do let(:params) do { version: '6.0.0', provisioning_dashboards: { apiVersion: 1, providers: [ { name: 'default', orgId: 1, folder: '', type: 'file', disableDeletion: true, options: { path: '/var/lib/grafana/dashboards' } } ] } } end it { is_expected.not_to contain_file('/var/lib/grafana/dashboards') } end end context 'sysconfig environment variables' do let(:params) do { install_method: 'repo', sysconfig: { http_proxy: 'http://proxy.example.com/' } } end case facts[:osfamily] when 'Debian' describe 'Add the environment variable to the config file' do it { is_expected.to contain_augeas('sysconfig/grafana-server').with_context('/files/etc/default/grafana-server') } it { is_expected.to contain_augeas('sysconfig/grafana-server').with_changes(['set http_proxy http://proxy.example.com/']) } end when 'RedHat' describe 'Add the environment variable to the config file' do it { is_expected.to contain_augeas('sysconfig/grafana-server').with_context('/files/etc/sysconfig/grafana-server') } it { is_expected.to contain_augeas('sysconfig/grafana-server').with_changes(['set http_proxy http://proxy.example.com/']) } end end end end end end diff --git a/spec/unit/puppet/provider/grafana_plugin/grafana_cli_spec.rb b/spec/unit/puppet/provider/grafana_plugin/grafana_cli_spec.rb index f7223f1..af34ab1 100644 --- a/spec/unit/puppet/provider/grafana_plugin/grafana_cli_spec.rb +++ b/spec/unit/puppet/provider/grafana_plugin/grafana_cli_spec.rb @@ -1,90 +1,87 @@ require 'spec_helper' provider_class = Puppet::Type.type(:grafana_plugin).provider(:grafana_cli) describe provider_class do let(:resource) do Puppet::Type::Grafana_plugin.new( name: 'grafana-wizzle' ) end let(:provider) { provider_class.new(resource) } describe '#instances' do let(:plugins_ls_two) do - # rubocop:disable Layout/TrailingWhitespace <<-PLUGINS installed plugins: grafana-simple-json-datasource @ 1.3.4 jdbranham-diagram-panel @ 1.4.0 Restart grafana after installing plugins . PLUGINS # rubocop:enable Layout/TrailingWhitespace end let(:plugins_ls_none) do <<-PLUGINS Restart grafana after installing plugins . PLUGINS end it 'has the correct names' do allow(provider_class).to receive(:grafana_cli).with('plugins', 'ls').and_return(plugins_ls_two) expect(provider_class.instances.map(&:name)).to match_array(['grafana-simple-json-datasource', 'jdbranham-diagram-panel']) expect(provider_class).to have_received(:grafana_cli) end it 'does not match if there are no plugins' do allow(provider_class).to receive(:grafana_cli).with('plugins', 'ls').and_return(plugins_ls_none) expect(provider_class.instances.size).to eq(0) expect(provider.exists?).to eq(false) expect(provider_class).to have_received(:grafana_cli) end # rubocop:enable RSpec/MultipleExpectations end it '#create' do allow(provider).to receive(:grafana_cli) provider.create expect(provider).to have_received(:grafana_cli).with('plugins', 'install', 'grafana-wizzle') end it '#destroy' do allow(provider).to receive(:grafana_cli) provider.destroy expect(provider).to have_received(:grafana_cli).with('plugins', 'uninstall', 'grafana-wizzle') end describe 'create with repo' do let(:resource) do Puppet::Type::Grafana_plugin.new( name: 'grafana-plugin', repo: 'https://nexus.company.com/grafana/plugins' ) end it '#create with repo' do allow(provider).to receive(:grafana_cli) provider.create expect(provider).to have_received(:grafana_cli).with('--repo https://nexus.company.com/grafana/plugins', 'plugins', 'install', 'grafana-plugin') end end describe 'create with plugin url' do let(:resource) do Puppet::Type::Grafana_plugin.new( name: 'grafana-simple-json-datasource', plugin_url: 'https://grafana.com/api/plugins/grafana-simple-json-datasource/versions/latest/download' ) end it '#create with plugin url' do allow(provider).to receive(:grafana_cli) provider.create expect(provider).to have_received(:grafana_cli).with('--pluginUrl', 'https://grafana.com/api/plugins/grafana-simple-json-datasource/versions/latest/download', 'plugins', 'install', 'grafana-simple-json-datasource') end end - - end diff --git a/spec/unit/puppet/type/grafana_plugin_spec.rb b/spec/unit/puppet/type/grafana_plugin_spec.rb index 9db9d4d..7ba9114 100644 --- a/spec/unit/puppet/type/grafana_plugin_spec.rb +++ b/spec/unit/puppet/type/grafana_plugin_spec.rb @@ -1,26 +1,25 @@ require 'spec_helper' describe Puppet::Type.type(:grafana_plugin) do let(:plugin) do Puppet::Type.type(:grafana_plugin).new(name: 'grafana-whatsit') end it 'accepts a plugin name' do plugin[:name] = 'plugin-name' expect(plugin[:name]).to eq('plugin-name') end it 'requires a name' do expect do Puppet::Type.type(:grafana_plugin).new({}) end.to raise_error(Puppet::Error, 'Title or name must be provided') end it 'accepts a plugin repo' do plugin[:repo] = 'https://nexus.company.com/grafana/plugins' expect(plugin[:repo]).to eq('https://nexus.company.com/grafana/plugins') end it 'accepts a plugin url' do plugin[:plugin_url] = 'https://grafana.com/api/plugins/grafana-simple-json-datasource/versions/latest/download' expect(plugin[:plugin_url]).to eq('https://grafana.com/api/plugins/grafana-simple-json-datasource/versions/latest/download') end - end