From 8451873c67f29ad0a46bfd4073b17ed376394995 Mon Sep 17 00:00:00 2001 From: bernhard Date: Fri, 25 Nov 2016 10:02:22 +0100 Subject: [PATCH 01/52] initail version --- Gemfile | 18 +++++ README.md | 83 ++++++++++++++++++++++++ Rakefile | 32 +++++++++ examples/init.pp | 12 ++++ lib/puppet/provider/kdbkey/kdb.rb | 44 +++++++++++++ lib/puppet/provider/kdbkey/libelektra.rb | 69 ++++++++++++++++++++ lib/puppet/type/kdbkey.rb | 30 +++++++++ manifests/init.pp | 48 ++++++++++++++ metadata.json | 15 +++++ spec/classes/init_spec.rb | 6 ++ spec/spec_helper.rb | 1 + 11 files changed, 358 insertions(+) create mode 100644 Gemfile create mode 100644 README.md create mode 100644 Rakefile create mode 100644 examples/init.pp create mode 100644 lib/puppet/provider/kdbkey/kdb.rb create mode 100644 lib/puppet/provider/kdbkey/libelektra.rb create mode 100644 lib/puppet/type/kdbkey.rb create mode 100644 manifests/init.pp create mode 100644 metadata.json create mode 100644 spec/classes/init_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..4f662dc --- /dev/null +++ b/Gemfile @@ -0,0 +1,18 @@ +source ENV['GEM_SOURCE'] || 'https://rubygems.org' + +puppetversion = ENV.key?('PUPPET_VERSION') ? ENV['PUPPET_VERSION'] : ['>= 3.3'] +gem 'metadata-json-lint' +gem 'puppet', puppetversion +gem 'puppetlabs_spec_helper', '>= 1.0.0' +gem 'puppet-lint', '>= 1.0.0' +gem 'facter', '>= 1.7.0' +gem 'rspec-puppet' + +# rspec must be v2 for ruby 1.8.7 +if RUBY_VERSION >= '1.8.7' && RUBY_VERSION < '1.9' + gem 'rspec', '~> 2.0' + gem 'rake', '~> 10.0' +else + # rubocop requires ruby >= 1.9 + gem 'rubocop' +end diff --git a/README.md b/README.md new file mode 100644 index 0000000..7920a68 --- /dev/null +++ b/README.md @@ -0,0 +1,83 @@ +# libelektra + +#### Table of Contents + +1. [Description](#description) +1. [Setup - The basics of getting started with libelektra](#setup) + * [What libelektra affects](#what-libelektra-affects) + * [Setup requirements](#setup-requirements) + * [Beginning with libelektra](#beginning-with-libelektra) +1. [Usage - Configuration options and additional functionality](#usage) +1. [Reference - An under-the-hood peek at what the module is doing and how](#reference) +1. [Limitations - OS compatibility, etc.](#limitations) +1. [Development - Guide for contributing to the module](#development) + +## Description + +Start with a one- or two-sentence summary of what the module does and/or what +problem it solves. This is your 30-second elevator pitch for your module. +Consider including OS/Puppet version it works with. + +You can give more descriptive information in a second paragraph. This paragraph +should answer the questions: "What does this module *do*?" and "Why would I use +it?" If your module has a range of functionality (installation, configuration, +management, etc.), this is the time to mention it. + +## Setup + +### What libelektra affects **OPTIONAL** + +If it's obvious what your module touches, you can skip this section. For +example, folks can probably figure out that your mysql_instance module affects +their MySQL instances. + +If there's more that they should know about, though, this is the place to mention: + +* A list of files, packages, services, or operations that the module will alter, + impact, or execute. +* Dependencies that your module automatically installs. +* Warnings or other important notices. + +### Setup Requirements **OPTIONAL** + +If your module requires anything extra before setting up (pluginsync enabled, +etc.), mention it here. + +If your most recent release breaks compatibility or requires particular steps +for upgrading, you might want to include an additional "Upgrading" section +here. + +### Beginning with libelektra + +The very basic steps needed for a user to get the module up and running. This +can include setup steps, if necessary, or it can be an example of the most +basic use of the module. + +## Usage + +This section is where you describe how to customize, configure, and do the +fancy stuff with your module here. It's especially helpful if you include usage +examples and code samples for doing things with your module. + +## Reference + +Here, include a complete list of your module's classes, types, providers, +facts, along with the parameters for each. Users refer to this section (thus +the name "Reference") to find specific details; most users don't read it per +se. + +## Limitations + +This is where you list OS compatibility, version compatibility, etc. If there +are Known Issues, you might want to include them under their own heading here. + +## Development + +Since your module is awesome, other users will want to play with it. Let them +know what the ground rules for contributing are. + +## Release Notes/Contributors/Etc. **Optional** + +If you aren't using changelog, put your release notes here (though you should +consider using changelog). You can also add any additional sections you feel +are necessary or important to include here. Please use the `## ` header. diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..02609e3 --- /dev/null +++ b/Rakefile @@ -0,0 +1,32 @@ +require 'puppetlabs_spec_helper/rake_tasks' +require 'puppet-lint/tasks/puppet-lint' +require 'metadata-json-lint/rake_task' + +if RUBY_VERSION >= '1.9' + require 'rubocop/rake_task' + RuboCop::RakeTask.new +end + +PuppetLint.configuration.send('disable_80chars') +PuppetLint.configuration.relative = true +PuppetLint.configuration.ignore_paths = ['spec/**/*.pp', 'pkg/**/*.pp'] + +desc 'Validate manifests, templates, and ruby files' +task :validate do + Dir['manifests/**/*.pp'].each do |manifest| + sh "puppet parser validate --noop #{manifest}" + end + Dir['spec/**/*.rb', 'lib/**/*.rb'].each do |ruby_file| + sh "ruby -c #{ruby_file}" unless ruby_file =~ %r{spec/fixtures} + end + Dir['templates/**/*.erb'].each do |template| + sh "erb -P -x -T '-' #{template} | ruby -c" + end +end + +desc 'Run metadata_lint, lint, validate, and spec tests.' +task :test do + [:metadata_lint, :lint, :validate, :spec].each do |test| + Rake::Task[test].invoke + end +end diff --git a/examples/init.pp b/examples/init.pp new file mode 100644 index 0000000..c062f06 --- /dev/null +++ b/examples/init.pp @@ -0,0 +1,12 @@ +# The baseline for module testing used by Puppet Labs is that each manifest +# should have a corresponding test manifest that declares that class or defined +# type. +# +# Tests are then run by using puppet apply --noop (to check for compilation +# errors and view a log of events) or by fully applying the test in a virtual +# environment (to compare the resulting system state to the desired state). +# +# Learn more about module testing here: +# https://docs.puppet.com/guides/tests_smoke.html +# +include ::libelektra diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb new file mode 100644 index 0000000..33223c2 --- /dev/null +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -0,0 +1,44 @@ + +module Puppet + Type.type(:kdbkey).provide :kdb do + desc "kdb through kdb command" + + #require 'kdb' + + puts "kdb provider" + + #commands :kdb => "kdb" + confine :true => false + + def create + #puts "kdb create" + self.value=(@resource[:value]) + end + + def destroy + #puts "kdb destroy" + kdb ["rm", @resource[:name]] + end + + def exists? + #puts "kdb exists? #{self.name}" + output = execute([command(:kdb), "get", @resource[:name]], + :failonfail => false) + #puts "output: #{output}, #{output.exitstatus}" + output.exitstatus == 0 + end + + def value + #puts "getting value" + output = kdb ["get", @resource[:name]] + output[-1] = '' + output + end + + def value=(value) + #puts "setting value to #{value}" + kdb(["set", @resource[:name], value]) + end + + end +end diff --git a/lib/puppet/provider/kdbkey/libelektra.rb b/lib/puppet/provider/kdbkey/libelektra.rb new file mode 100644 index 0000000..42c4321 --- /dev/null +++ b/lib/puppet/provider/kdbkey/libelektra.rb @@ -0,0 +1,69 @@ + +module Puppet + Type.type(:kdbkey).provide :libelektra do + desc "kdb through libelektra Ruby API" + + require 'kdb' + + puts "libelektra provider" + + #confine :true => true + #confine :exists => "/etc/debian_version" + confine :true => true + + def create + puts "libelektra create" + Kdb.open do |db| + ks = Kdb::KeySet.new + db.get ks, "/" + key = Kdb::Key.new @resource[:name], value: @resource[:value] + ks << key + db.set ks, "/" + end + end + + def destroy + puts "libelektra destroy" + Kdb.open do |db| + ks = Kdb::KeySet.new + db.get ks, "/" + ks.delete @resource[:name] + db.set ks, "/" + end + end + + def exists? + puts "libelektra exists?" + Kdb.open do |db| + ks = Kdb::KeySet.new + db.get ks, "/" + key = ks.lookup @resource[:name] + return !key.nil? + end + end + + def value + puts "getting value" + Kdb.open do |db| + ks = Kdb::KeySet.new + db.get ks, "/" + key = ks.lookup @resource[:name] + return key.value unless key.nil? + end + end + + def value=(value) + puts "setting value to #{value}" + Kdb.open do |db| + ks = Kdb::KeySet.new + db.get ks, "/" + key = ks.lookup @resource[:name] + if !key.nil? + key.value= value + db.set ks, "/" + end + end + end + + end +end diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb new file mode 100644 index 0000000..418c54f --- /dev/null +++ b/lib/puppet/type/kdbkey.rb @@ -0,0 +1,30 @@ + + +Puppet::Type.newtype(:kdbkey) do + @doc = %q{Manipulate libelekra keys + TODO: finish this docu + } + + ensurable + + newproperty(:value) do + desc "The value of the key" + puts "Type: property value" + + #validate do |value| + # puts "Type: property value, validate: #{value}" + # super + #end + end + + newparam(:name) do + desc "The fully qualified name of the key" + + puts "Type: param name" + + #validate do |value| + # puts "Type: param name, validate: #{value}" + # super + #end + end +end diff --git a/manifests/init.pp b/manifests/init.pp new file mode 100644 index 0000000..200bc48 --- /dev/null +++ b/manifests/init.pp @@ -0,0 +1,48 @@ +# Class: libelektra +# =========================== +# +# Full description of class libelektra here. +# +# Parameters +# ---------- +# +# Document parameters here. +# +# * `sample parameter` +# Explanation of what this parameter affects and what it defaults to. +# e.g. "Specify one or more upstream ntp servers as an array." +# +# Variables +# ---------- +# +# Here you should define a list of variables that this module would require. +# +# * `sample variable` +# Explanation of how this variable affects the function of this class and if +# it has a default. e.g. "The parameter enc_ntp_servers must be set by the +# External Node Classifier as a comma separated list of hostnames." (Note, +# global variables should be avoided in favor of class parameters as +# of Puppet 2.6.) +# +# Examples +# -------- +# +# @example +# class { 'libelektra': +# servers => [ 'pool.ntp.org', 'ntp.local.company.com' ], +# } +# +# Authors +# ------- +# +# Author Name +# +# Copyright +# --------- +# +# Copyright 2016 Your name here, unless otherwise noted. +# +class libelektra { + + +} diff --git a/metadata.json b/metadata.json new file mode 100644 index 0000000..bbce79f --- /dev/null +++ b/metadata.json @@ -0,0 +1,15 @@ +{ + "name": "libelektra-libelektra", + "version": "0.1.0", + "author": "Bernhard Denner", + "summary": "manage your configuration through libelektra", + "license": "BSD License", + "source": "github", + "project_page": "github doc", + "issues_url": "github issues", + "dependencies": [ + {"name":"puppetlabs-stdlib","version_requirement":">= 1.0.0"} + ], + "data_provider": null +} + diff --git a/spec/classes/init_spec.rb b/spec/classes/init_spec.rb new file mode 100644 index 0000000..ace36ed --- /dev/null +++ b/spec/classes/init_spec.rb @@ -0,0 +1,6 @@ +require 'spec_helper' +describe 'libelektra' do + context 'with default values for all parameters' do + it { should contain_class('libelektra') } + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..2c6f566 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1 @@ +require 'puppetlabs_spec_helper/module_spec_helper' From dc643f8c4873b21ef8065b1a8e0f02ceafb70ae4 Mon Sep 17 00:00:00 2001 From: bernhard Date: Fri, 25 Nov 2016 10:09:29 +0100 Subject: [PATCH 02/52] rename kdbkey/libelektra provider to kdbkey/ruby --- lib/puppet/provider/kdbkey/{libelektra.rb => ruby.rb} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename lib/puppet/provider/kdbkey/{libelektra.rb => ruby.rb} (88%) diff --git a/lib/puppet/provider/kdbkey/libelektra.rb b/lib/puppet/provider/kdbkey/ruby.rb similarity index 88% rename from lib/puppet/provider/kdbkey/libelektra.rb rename to lib/puppet/provider/kdbkey/ruby.rb index 42c4321..2778857 100644 --- a/lib/puppet/provider/kdbkey/libelektra.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -1,18 +1,18 @@ module Puppet - Type.type(:kdbkey).provide :libelektra do + Type.type(:kdbkey).provide :ruby do desc "kdb through libelektra Ruby API" require 'kdb' - puts "libelektra provider" + puts "ruby provider" #confine :true => true #confine :exists => "/etc/debian_version" confine :true => true def create - puts "libelektra create" + puts "ruby create" Kdb.open do |db| ks = Kdb::KeySet.new db.get ks, "/" @@ -23,7 +23,7 @@ def create end def destroy - puts "libelektra destroy" + puts "ruby destroy" Kdb.open do |db| ks = Kdb::KeySet.new db.get ks, "/" @@ -33,7 +33,7 @@ def destroy end def exists? - puts "libelektra exists?" + puts "ruby exists?" Kdb.open do |db| ks = Kdb::KeySet.new db.get ks, "/" From 70edff467a5bd4b30da339e7eaa9261877ba6f76 Mon Sep 17 00:00:00 2001 From: bernhard Date: Fri, 25 Nov 2016 10:10:41 +0100 Subject: [PATCH 03/52] gitignore: add vim swap files --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5e1422c..4a9d6c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +**/*.swp + *.gem *.rbc /.config From 60e5d4639ee814869b196269006d68e77b055df6 Mon Sep 17 00:00:00 2001 From: bernhard Date: Sat, 3 Dec 2016 17:53:05 +0100 Subject: [PATCH 04/52] ruby provider: meta data and comments handling --- examples/init.pp | 60 +++++++++++++- lib/puppet/provider/kdbkey/kdb.rb | 9 +- lib/puppet/provider/kdbkey/ruby.rb | 129 ++++++++++++++++++++--------- lib/puppet/type/kdbkey.rb | 17 ++++ 4 files changed, 166 insertions(+), 49 deletions(-) diff --git a/examples/init.pp b/examples/init.pp index c062f06..a98ee66 100644 --- a/examples/init.pp +++ b/examples/init.pp @@ -9,4 +9,62 @@ # Learn more about module testing here: # https://docs.puppet.com/guides/tests_smoke.html # -include ::libelektra + +#include ::libelektra + +$ns = 'user/test/puppet' + +kdbkey { "${ns}/x1": + ensure => present, + value => 'hello world x1 ...' +} + +kdbkey { "${ns}/x2": + ensure => absent +} + +kdbkey { "${ns}/x3": + ensure => present +} + +kdbkey { "${ns}/x4": + ensure => present, + value => 'x4 value ...', + meta => { + 'meta1' => 'm1 value', + 'meta2' => 'm2 value' + } +} + +kdbkey { "${ns}-test/section1/setting1": + ensure => present, + value => 'hello ini world ...', + meta => { + 'comments' => '#1', + 'comments/#0' => '# this is the first comment line', + 'comments/#1' => '# this is the second comment line' + } +} + +kdbkey { "${ns}-test/section1/setting2": + ensure => present, + value => 'some value ...', + comments => ' +this setting will do the most important stuff +with a multi line comment + +here comes the setting' +} + +#kdbkey { "${ns}-test/section2": +# comments => " +#this is just a section key with +#some comments +#not really very important stuff, but +#...ok" +#} + +kdbkey { "${ns}-test/section2/setting1": + value => "asdf", + # before => Kdbkey["${ns}-test/section2"] +} diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 33223c2..4df18ae 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -3,12 +3,9 @@ module Puppet Type.type(:kdbkey).provide :kdb do desc "kdb through kdb command" - #require 'kdb' - puts "kdb provider" - #commands :kdb => "kdb" - confine :true => false + commands :kdb => "kdb" def create #puts "kdb create" @@ -30,9 +27,7 @@ def exists? def value #puts "getting value" - output = kdb ["get", @resource[:name]] - output[-1] = '' - output + kdb ["sget", "--color=never", @resource[:name], "''"] end def value=(value) diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 2778857..17b18f2 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -3,67 +3,114 @@ module Puppet Type.type(:kdbkey).provide :ruby do desc "kdb through libelektra Ruby API" - require 'kdb' + @@have_kdb = true + + begin + require 'kdb' + rescue LoadError + @@have_kdb = false + end puts "ruby provider" - #confine :true => true - #confine :exists => "/etc/debian_version" - confine :true => true + # make this provider default for Linux systems + defaultfor :kernel => :Linux + # if we can load the 'kdb' extension + confine :true => @@have_kdb + + if @@have_kdb + puts "open kdb" + @@db = Kdb.open + @@ks = Kdb::KeySet.new + @@db.get @@ks, "/" + end + + @resource_key = nil def create - puts "ruby create" - Kdb.open do |db| - ks = Kdb::KeySet.new - db.get ks, "/" - key = Kdb::Key.new @resource[:name], value: @resource[:value] - ks << key - db.set ks, "/" - end + puts "ruby create #{@resource[:name]}" + @resource_key = Kdb::Key.new @resource[:name], value: @resource[:value] + @@ks << @resource_key end def destroy - puts "ruby destroy" - Kdb.open do |db| - ks = Kdb::KeySet.new - db.get ks, "/" - ks.delete @resource[:name] - db.set ks, "/" - end + puts "ruby destroy #{@resource[:name]}" + @@ks.delete @resource[:name] end def exists? - puts "ruby exists?" - Kdb.open do |db| - ks = Kdb::KeySet.new - db.get ks, "/" - key = ks.lookup @resource[:name] - return !key.nil? - end + puts "ruby exists? #{@resource[:name]}" + @resource_key = @@ks.lookup @resource[:name] + return !@resource_key.nil? end def value - puts "getting value" - Kdb.open do |db| - ks = Kdb::KeySet.new - db.get ks, "/" - key = ks.lookup @resource[:name] - return key.value unless key.nil? - end + puts "getting value #{@resource[:name]}" + return @resource_key.value unless @resource_key.nil? end def value=(value) - puts "setting value to #{value}" - Kdb.open do |db| - ks = Kdb::KeySet.new - db.get ks, "/" - key = ks.lookup @resource[:name] - if !key.nil? - key.value= value - db.set ks, "/" + puts "setting value of #{@resource[:name]} to #{value}" + @resource_key.value= value unless @resource_key.nil? + end + + def meta + puts "get meta value #{@resource[:name]}" + #key.meta.to_h unless key.nil? ruby 1.9 does not have Enumerable.to_h :( + res = Hash.new + @resource_key.meta.each { |e| res[e.name] = e.value } unless @resource_key.nil? + puts "meta: #{res}" + return res + end + + def meta=(value) + puts "set meta of #{@resource[:name]}: #{value}" + value.each { |k, v| @resource_key.set_meta k, v } unless @resource_key.nil? + end + + def comments + comments = "" + first = true + @resource_key.meta.each do |e| + if e.name.start_with? "comments/#" + comments << "\n" unless first + comments << e.value.sub(/^# /, '') + first = false + end + end + return comments + end + + def comments=(value) + if value.size == 0 + cm = @resource_key.meta.find_all do |e| + e.name.start_with? "comments" + end + cm.each { |e| @resource_key.set_meta e.name, "" } + else + comment_lines = value.split "\n" + puts "comment has #{comment_lines.size} lines" + @resource_key.set_meta "comments", "##{comment_lines.size - 1}" + comment_lines.each_with_index do |line, i| + @resource_key.set_meta "comments/##{i}", "# #{line}" end end end + #def clear + # super + # puts "ruby clear" + #end + + def flush + puts "ruby flush #{@resource[:name]}" + @@db.set @@ks, "/" + end + + def self.post_resource_eval + puts "ruby post resource eval" + @@db.close if @@have_kdb + end + end end diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index 418c54f..24e10b7 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -17,6 +17,23 @@ #end end + newproperty(:meta) do + desc "metadata of this key" + + validate do |meta| + if !meta.is_a? Hash + raise ArgumentError, "Hash required" + else + super meta + end + end + end + + newproperty(:comments) do + desc "comments for this key" + + end + newparam(:name) do desc "The fully qualified name of the key" From b81a31501139dc796558073faea1edddb5228120 Mon Sep 17 00:00:00 2001 From: bernhard Date: Sat, 3 Dec 2016 17:56:07 +0100 Subject: [PATCH 05/52] ruby provider: add code comments --- lib/puppet/provider/kdbkey/ruby.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 17b18f2..8ff7c9c 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -38,6 +38,8 @@ def destroy @@ks.delete @resource[:name] end + # is called first for each managed resource + # stores the queried key for later modifications def exists? puts "ruby exists? #{@resource[:name]}" @resource_key = @@ks.lookup @resource[:name] @@ -97,16 +99,17 @@ def comments=(value) end end - #def clear - # super - # puts "ruby clear" - #end + # flush is call if a resource was modified + # thus this method is perfectly suitable for our db.set method which will + # finally bring the changes to disk def flush puts "ruby flush #{@resource[:name]}" @@db.set @@ks, "/" end + # this is the provider de-init hook + # so lets close our kdb db now def self.post_resource_eval puts "ruby post resource eval" @@db.close if @@have_kdb From a89a351952966e278a01a3b5b9d97840be83d175 Mon Sep 17 00:00:00 2001 From: bernhard Date: Mon, 5 Dec 2016 15:58:25 +0100 Subject: [PATCH 06/52] new attr `purge_meta_keys`, correctely handle comments The new `purge_meta_keys` attribue allows to fully manage metadata keys, which means, that unspecified metadata keys are now removed, this attribue is set to true. Comments are now managed in a much better way, which allows adding and removing comments. Still an open issue: when and which comment character should be used? Currently hard coded to '#' --- examples/init.pp | 27 +++---- lib/puppet/provider/kdbkey/kdb.rb | 11 ++- lib/puppet/provider/kdbkey/ruby.rb | 92 +++++++++++++++++------- lib/puppet/type/kdbkey.rb | 111 ++++++++++++++++++++++------- 4 files changed, 175 insertions(+), 66 deletions(-) diff --git a/examples/init.pp b/examples/init.pp index a98ee66..7132f21 100644 --- a/examples/init.pp +++ b/examples/init.pp @@ -28,19 +28,20 @@ } kdbkey { "${ns}/x4": - ensure => present, - value => 'x4 value ...', - meta => { + ensure => present, + value => 'x4 value ...', + purge_meta_keys => true, + metadata => { 'meta1' => 'm1 value', - 'meta2' => 'm2 value' + 'meta2' => 'm2 value', + #'meta3' => 'm3 value' } } kdbkey { "${ns}-test/section1/setting1": - ensure => present, - value => 'hello ini world ...', - meta => { - 'comments' => '#1', + ensure => present, + value => 'hello ini world ...', + metadata => { 'comments/#0' => '# this is the first comment line', 'comments/#1' => '# this is the second comment line' } @@ -52,19 +53,21 @@ comments => ' this setting will do the most important stuff with a multi line comment - -here comes the setting' +here comes the setting +m line1 +m line2' } #kdbkey { "${ns}-test/section2": # comments => " -#this is just a section key with +#this is just a section key with #some comments #not really very important stuff, but #...ok" #} kdbkey { "${ns}-test/section2/setting1": - value => "asdf", + value => 'asdf', + comments => '' # before => Kdbkey["${ns}-test/section2"] } diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 4df18ae..100b743 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -1,10 +1,17 @@ +# encoding: UTF-8 +## +# @file +# +# @brief Kdb provider for type kdbkey for managing libelektra keys +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# +# module Puppet Type.type(:kdbkey).provide :kdb do desc "kdb through kdb command" - puts "kdb provider" - commands :kdb => "kdb" def create diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 8ff7c9c..35c429a 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -1,25 +1,34 @@ +# encoding: UTF-8 +## +# @file +# +# @brief Ruby provider for type kdbkey for managing libelektra keys +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# +# module Puppet Type.type(:kdbkey).provide :ruby do desc "kdb through libelektra Ruby API" + # static instance var for checking if we are able to use this provider @@have_kdb = true begin + # load libelektra Ruby binding extension require 'kdb' rescue LoadError @@have_kdb = false end - puts "ruby provider" - # make this provider default for Linux systems defaultfor :kernel => :Linux # if we can load the 'kdb' extension confine :true => @@have_kdb if @@have_kdb - puts "open kdb" + Puppet.debug "kdbkey/ruby: open kdb db" @@db = Kdb.open @@ks = Kdb::KeySet.new @@db.get @@ks, "/" @@ -28,51 +37,69 @@ module Puppet @resource_key = nil def create - puts "ruby create #{@resource[:name]}" + #puts "ruby create #{@resource[:name]}" @resource_key = Kdb::Key.new @resource[:name], value: @resource[:value] @@ks << @resource_key end def destroy - puts "ruby destroy #{@resource[:name]}" + #puts "ruby destroy #{@resource[:name]}" @@ks.delete @resource[:name] end # is called first for each managed resource # stores the queried key for later modifications def exists? - puts "ruby exists? #{@resource[:name]}" + Puppet.debug "kdbkey/ruby exists? #{@resource[:name]}" + #puts "kdbkey/ruby @should: #{value(:metadata)}" @resource_key = @@ks.lookup @resource[:name] return !@resource_key.nil? end def value - puts "getting value #{@resource[:name]}" + #puts "getting value #{@resource[:name]}" return @resource_key.value unless @resource_key.nil? end def value=(value) - puts "setting value of #{@resource[:name]} to #{value}" + #puts "setting value of #{@resource[:name]} to #{value}" @resource_key.value= value unless @resource_key.nil? end - def meta - puts "get meta value #{@resource[:name]}" + def metadata #key.meta.to_h unless key.nil? ruby 1.9 does not have Enumerable.to_h :( res = Hash.new - @resource_key.meta.each { |e| res[e.name] = e.value } unless @resource_key.nil? - puts "meta: #{res}" + @resource_key.meta.each { + |e| res[e.name] = e.value + } unless @resource_key.nil? + + # if purge_meta_keys is NOT set to true, remove all unspecified keys + if ! @resource.purge_meta_keys? + res.keep_if { |k,v| @resource[:metadata].include? k } + end + return res end - def meta=(value) - puts "set meta of #{@resource[:name]}: #{value}" - value.each { |k, v| @resource_key.set_meta k, v } unless @resource_key.nil? + def metadata=(value) + # update metadata + value.each { |k, v| + @resource_key.set_meta k, v + } unless @resource_key.nil? + + # do we have to purge all unspecified keys? + if @resource.purge_meta_keys? + @resource_key.meta.each do |metakey| + @resource_key.del_meta metakey.name unless value.include? metakey.name + end + end end def comments comments = "" - first = true + first = true # used for splitting lines + # search for all meta keys which names starts with 'comments/#' + # and concat its values line by line @resource_key.meta.each do |e| if e.name.start_with? "comments/#" comments << "\n" unless first @@ -84,17 +111,28 @@ def comments end def comments=(value) - if value.size == 0 - cm = @resource_key.meta.find_all do |e| - e.name.start_with? "comments" - end - cm.each { |e| @resource_key.set_meta e.name, "" } + # split specified comment into lines + comment_lines = value.split "\n" + # if we do not have any comments, remove them + if comment_lines.size == 0 + @resource_key.del_meta "comments" else - comment_lines = value.split "\n" - puts "comment has #{comment_lines.size} lines" @resource_key.set_meta "comments", "##{comment_lines.size - 1}" - comment_lines.each_with_index do |line, i| - @resource_key.set_meta "comments/##{i}", "# #{line}" + end + + # update all comment lines + comment_lines.each_with_index do |line, index| + @resource_key.set_meta "comments/##{index}", "# #{line}" + end + + # iterate over all meta keys and remove all comment keys which + # represent a comment line, which does not exist any more + @resource_key.meta.each do |e| + if e.name.match(/^comments\/#(\d+)$/) + index = $~[1].to_i + if comment_lines[index].nil? + @resource_key.del_meta e.name + end end end end @@ -104,14 +142,14 @@ def comments=(value) # thus this method is perfectly suitable for our db.set method which will # finally bring the changes to disk def flush - puts "ruby flush #{@resource[:name]}" + Puppet.debug "kdbkey/ruby: flush #{@resource[:name]}" @@db.set @@ks, "/" end # this is the provider de-init hook # so lets close our kdb db now def self.post_resource_eval - puts "ruby post resource eval" + Puppet.debug "kdbkey/ruby: closing kdb db" @@db.close if @@have_kdb end diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index 24e10b7..bf036a0 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -1,47 +1,108 @@ - +# encoding: UTF-8 +## +# @file +# +# @brief Custom puppet type kdbkey for managing libelektra keys +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# +# +require 'puppet/parameter/boolean' Puppet::Type.newtype(:kdbkey) do - @doc = %q{Manipulate libelekra keys - TODO: finish this docu - } + @doc = <<-EOT + Manage libelekra keys. + + This resource type allows to define and manipulate keys of libelektra's + key database. + EOT ensurable - newproperty(:value) do - desc "The value of the key" - puts "Type: property value" + newparam(:name) do + desc <<-EOT + The fully qualified name of the key + + TODO: describe if it is safe or not to use cascading keys? + EOT - #validate do |value| - # puts "Type: property value, validate: #{value}" - # super - #end + isnamevar end - newproperty(:meta) do - desc "metadata of this key" + newproperty(:value) do + desc <<-EOT + Desired value of the key. + EOT + end + + newproperty(:metadata) do + desc <<-EOT + Metadata for this key supplied as Hash of key-value pairs. The concret + behaviour is defined by the parameter `purge_meta_keys`. The default + case (`purge_meta_keys` => false) is to manage the specified metadata + keys only. Already present but not specified metadata keys will not be + removed. If `purge_meta_keys` is set to true, already present but not + specified metadata keys will be removed. + + Examples: + kdbkey { 'system/sw/app/s1': + metadata => { + 'owner' => 'me', + 'other meta' => 'you' + } + } + EOT - validate do |meta| - if !meta.is_a? Hash + validate do |metadata| + if !metadata.is_a? Hash raise ArgumentError, "Hash required" else - super meta + super metadata end end end - newproperty(:comments) do - desc "comments for this key" + newparam(:purge_meta_keys, + :boolean => true, + :parent => Puppet::Parameter::Boolean) do + desc <<-EOT + manage complete set of metadata keys + If set to true, kdbkey will remove all unspecifed metadata keys, ensuring + only the specified set of metadata keys will exist. Otherwise, + unspecified metadata keys will not be touched. + EOT end - newparam(:name) do - desc "The fully qualified name of the key" + newproperty(:comments) do + desc <<-EOT + comments for this key + + Comments form a critical part of documentation. May configuration file + formats support adding comment lines. Libelektra plugins parse comments + and add them as metadata keys to the corresponding keys. This attribute + allows to manage those comment lines. + + TODO finish this docu + + EOT - puts "Type: param name" + def change_to_s(current_value, new_value) + # limit max string length + current_value = "#{current_value[0,20]}..." if current_value.size > 24 + new_value = "#{new_value[0,20]}..." if new_value.size > 24 + # replace new lines with $ + current_value.gsub! "\n", '$ ' + new_value.gsub! "\n", '$ ' - #validate do |value| - # puts "Type: param name, validate: #{value}" - # super - #end + if current_value.empty? + return "comments defined to '#{new_value}'" + elsif new_value.empty? + return "comments removed" + else + return "comments changed '#{current_value}' to '#{new_value}'" + end + end end + end From 9132586b57deab43f7aa5aeca2696b0bb3002444 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 6 Dec 2016 10:37:15 +0100 Subject: [PATCH 07/52] add first rspec test cases for both: Type kdbkey and provider kdbkey/ruby --- lib/puppet/provider/kdbkey/ruby.rb | 14 ++- lib/puppet/type/kdbkey.rb | 8 +- spec/fixtures/manifests/site.pp | 0 spec/spec_helper.rb | 4 + spec/unit/provider/kdbkey_ruby_spec.rb | 137 +++++++++++++++++++++++++ spec/unit/type/kdbkey_spec.rb | 122 ++++++++++++++++++++++ 6 files changed, 281 insertions(+), 4 deletions(-) create mode 100644 spec/fixtures/manifests/site.pp create mode 100644 spec/unit/provider/kdbkey_ruby_spec.rb create mode 100644 spec/unit/type/kdbkey_spec.rb diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 35c429a..9f181ca 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -12,7 +12,7 @@ module Puppet Type.type(:kdbkey).provide :ruby do desc "kdb through libelektra Ruby API" - # static instance var for checking if we are able to use this provider + # static class var for checking if we are able to use this provider @@have_kdb = true begin @@ -34,17 +34,25 @@ module Puppet @@db.get @@ks, "/" end - @resource_key = nil + # just used during testing to inject a mock + def self.use_fake_ks(ks) + @@ks = ks + end + + # allow access to internal key, used during testing + attr_reader :resource_key def create #puts "ruby create #{@resource[:name]}" @resource_key = Kdb::Key.new @resource[:name], value: @resource[:value] + self.metadata= @resource[:metadata] unless @resource[:metadata].nil? + self.comments= @resource[:comments] unless @resource[:comments].nil? @@ks << @resource_key end def destroy #puts "ruby destroy #{@resource[:name]}" - @@ks.delete @resource[:name] + @@ks.delete @resource[:name] unless @resource_key.nil? end # is called first for each managed resource diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index bf036a0..89957ee 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -26,6 +26,12 @@ TODO: describe if it is safe or not to use cascading keys? EOT + validate do |name| + unless name =~ /^(spec|proc|dir|user|system)?\/.+/ + raise ArgumentError, "%s is not a valid libelektra key name" % name + end + end + isnamevar end @@ -52,7 +58,7 @@ } } EOT - + validate do |metadata| if !metadata.is_a? Hash raise ArgumentError, "Hash required" diff --git a/spec/fixtures/manifests/site.pp b/spec/fixtures/manifests/site.pp new file mode 100644 index 0000000..e69de29 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2c6f566..2d0dc05 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1 +1,5 @@ require 'puppetlabs_spec_helper/module_spec_helper' + +RSpec.configure do |config| + config.mock_framework = :rspec +end diff --git a/spec/unit/provider/kdbkey_ruby_spec.rb b/spec/unit/provider/kdbkey_ruby_spec.rb new file mode 100644 index 0000000..502740f --- /dev/null +++ b/spec/unit/provider/kdbkey_ruby_spec.rb @@ -0,0 +1,137 @@ +# encoding: UTF-8 +## +# @file +# +# @brief +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# + +require 'spec_helper' +require 'kdb' + +TEST_NS = 'user/test/puppet-rspec/' + +def create_resource(params) + Puppet::Type.type(:kdbkey).new(params) +end + + + +describe Puppet::Type.type(:kdbkey).provider(:ruby) do + + let(:name) { "#{TEST_NS}x1" } + let(:ks) { double("ks") } + let(:provider) { described_class.new } + + before :example do + described_class.use_fake_ks ks + provider.resource = create_resource :name => name + end + + + it "should be a child of Puppet::Provider" do + expect(described_class.new).to be_a_kind_of(Puppet::Provider) + end + + context "should check if resource exists" do + it "should return false on exists? if resource does not exist'" do + allow(ks).to receive(:lookup).and_return nil + + expect(ks).to receive(:lookup) { name } + # for some very strange reason the first call inside expect(..) is true + provider.exists? + expect(provider.exists?).to eq(false) + expect(provider.resource_key).to be_nil + end + + it "should return true on exists? if resource exists'" do + key = Kdb::Key.new name + allow(ks).to receive(:lookup).and_return key + + expect(ks).to receive(:lookup) { name } + provider.exists? + expect(provider.exists?).to eq(true) + expect(provider.resource_key).to be(key) + end + + end + + context "should create key" do + before :example do + allow(ks).to receive(:<<) + expect(ks).to receive(:<<) { provider.resource_key } + end + + it "with defined name" do + provider.create + key = provider.resource_key + + expect(key.name).to eq(name) + end + + it "with defined name and value" do + value = "my value" + provider.resource = create_resource :name => name, :value => value + + provider.create + + expect(provider.resource_key.name).to eq(name) + expect(provider.resource_key.value).to eq(value) + end + + it "with defined name, value and metadata" do + value = "my value" + meta = {'meta1' => 'v1', 'meta2' => 'v2' } + provider.resource = create_resource :name => name, + :value => value, + :metadata => meta + + provider.create + + expect(provider.resource_key.name).to eq(name) + expect(provider.resource_key.value).to eq(value) + meta.each do |k, v| + expect(provider.resource_key.get_meta k).to eq(v) + end + end + + it "with defined name, value, metadata and comments" do + value = "my value" + meta = {'meta1' => 'v1', 'meta2' => 'v2' } + comments = "my comment" + + provider.resource = create_resource :name => name, + :value => value, + :metadata => meta, + :comments => comments + + provider.create + + expect(provider.resource_key.name).to eq(name) + expect(provider.resource_key.value).to eq(value) + meta.each do |k, v| + expect(provider.resource_key.get_meta k).to eq(v) + end + expect(provider.resource_key['comments']).to eq("#0") + expect(provider.resource_key['comments/#0']).to eq("# #{comments}") + end + + + end + + it "should do nothing on destroy when resource_key is nil" do + provider.destroy + end + + it "should remove key when we have a key" do + # first create the key, a delete on nil-key does not make sense + allow(ks).to receive(:<<) + provider.create + + expect(ks).to receive(:delete) { nil } + provider.destroy + end + + # TODO add much more tests +end diff --git a/spec/unit/type/kdbkey_spec.rb b/spec/unit/type/kdbkey_spec.rb new file mode 100644 index 0000000..02883d3 --- /dev/null +++ b/spec/unit/type/kdbkey_spec.rb @@ -0,0 +1,122 @@ +# encoding: UTF-8 +## +# @file +# +# @brief +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# + +require 'spec_helper' + + +describe Puppet::Type.type(:kdbkey) do + + context "property 'name'" do + let(:name) { "user/test/puppet/x1" } + it "exists and is mandatory" do + expect(described_class.new(:name => name)[:name]).to eq(name) + expect { described_class.new() }.to raise_error(ArgumentError) + end + + RSpec.shared_examples "valid key names" do |name| + it "accepts the key name '#{name}'" do + expect(described_class.new(:name => name)[:name]).to eq(name) + end + end + + context "accepts valid libelektra key names" do + # cascading key name + include_examples "valid key names", "/test/puppet" + # absolute, by namespace key names + include_examples "valid key names", "spec/test/puppet" + include_examples "valid key names", "proc/test/puppet" + include_examples "valid key names", "dir/test/puppet" + include_examples "valid key names", "user/test/puppet" + include_examples "valid key names", "system/test/puppet" + end + + RSpec.shared_examples "invalid key names" do |name| + it "rejects the invalid key name '#{name}'" do + expect { + described_class.new(:name => name) + }.to raise_error(Puppet::ResourceError) + end + end + + context "rejects invalid libelektra key names" do + include_examples "invalid key names", "" + include_examples "invalid key names", "hello/world" + include_examples "invalid key names", "invalid-name-space" + include_examples "invalid key names", "test/xy" + end + end + + context "property 'value'" do + let(:params) { {:name => "user/test/puppet/x1", :value => "some value"} } + it "exists and is optional" do + expect(described_class.new(params)[:value]).to eq(params[:value]) + end + + end + + + context "property 'metadata'" do + let(:params) { {:name => "user/test/puppet/x1", :value => ""} } + it "exists and is optional" do + expect(described_class.new(params)[:metadata]).to be_nil + end + + it "only accepts hash values" do + p1 = params + + p1[:metadata] = "" + expect { described_class.new(p1) }.to raise_error(Puppet::ResourceError) + + p1[:metadata] = "not a hash" + expect { described_class.new(p1) }.to raise_error(Puppet::ResourceError) + + p1[:metadata] = 1 + expect { described_class.new(p1) }.to raise_error(Puppet::ResourceError) + + p1[:metadata] = { + 'meta1' => 'value 1', + 'meta2' => 'value 2' + } + expect(described_class.new(p1)[:metadata]).to eq(p1[:metadata]) + end + end + + + context "parameter 'purge_meta_keys'" do + let(:params) { {:name => "user/test/puppet/x1"} } + it "exists and is default false" do + expect(described_class.new(params)[:purge_meta_keys]).to be_falsy + end + + it "only accepts boolean values" do + p = params + + p[:purge_meta_keys] = "this is not a truth value" + expect { described_class.new(p) }.to raise_error(Puppet::ResourceError) + + p[:purge_meta_keys] = "true" + expect(described_class.new(p)[:purge_meta_keys]).to be true + + p[:purge_meta_keys] = true + expect(described_class.new(p)[:purge_meta_keys]).to be true + + p[:purge_meta_keys] = "false" + expect(described_class.new(p)[:purge_meta_keys]).to be false + end + end + + + context "parameter 'comments'" do + let(:params) { {:name => "user/test/puppet/x1"} } + it "exists and is optional" do + expect(described_class.new(params)[:comments]).to be_nil + end + end + +end From a3dc82d45470701886d1937813f0d5aa03ea397e Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 6 Dec 2016 11:04:02 +0100 Subject: [PATCH 08/52] add stubs for unimplemented test cases --- spec/unit/provider/kdbkey_ruby_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/unit/provider/kdbkey_ruby_spec.rb b/spec/unit/provider/kdbkey_ruby_spec.rb index 502740f..2879e88 100644 --- a/spec/unit/provider/kdbkey_ruby_spec.rb +++ b/spec/unit/provider/kdbkey_ruby_spec.rb @@ -133,5 +133,7 @@ def create_resource(params) provider.destroy end - # TODO add much more tests + it "should update the value" + it "should update the metadata" + it "should update the comments" end From 0a2834a59bd4f42ed2700a4408a168775f75d553 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 6 Dec 2016 21:17:16 +0100 Subject: [PATCH 09/52] kdbmount custom type and provider (kdb) implement the first version of kdbmount resource. The kdb provider uses 'kdb' tool to mount and umount files. Currently is very limited. We only support create, destroy and updates to the file path. Changes to plugin settings are not detected. --- examples/init.pp | 59 ---------- examples/kdbkey.pp | 66 +++++++++++ examples/kdbmount.pp | 20 ++++ lib/puppet/provider/kdbmount/kdb.rb | 112 +++++++++++++++++++ lib/puppet/type/kdbmount.rb | 96 ++++++++++++++++ spec/unit/provider/kdbmount_kdb_spec.rb | 139 ++++++++++++++++++++++++ spec/unit/type/kdbmount_spec.rb | 110 +++++++++++++++++++ 7 files changed, 543 insertions(+), 59 deletions(-) create mode 100644 examples/kdbkey.pp create mode 100644 examples/kdbmount.pp create mode 100644 lib/puppet/provider/kdbmount/kdb.rb create mode 100644 lib/puppet/type/kdbmount.rb create mode 100644 spec/unit/provider/kdbmount_kdb_spec.rb create mode 100644 spec/unit/type/kdbmount_spec.rb diff --git a/examples/init.pp b/examples/init.pp index 7132f21..23f7520 100644 --- a/examples/init.pp +++ b/examples/init.pp @@ -12,62 +12,3 @@ #include ::libelektra -$ns = 'user/test/puppet' - -kdbkey { "${ns}/x1": - ensure => present, - value => 'hello world x1 ...' -} - -kdbkey { "${ns}/x2": - ensure => absent -} - -kdbkey { "${ns}/x3": - ensure => present -} - -kdbkey { "${ns}/x4": - ensure => present, - value => 'x4 value ...', - purge_meta_keys => true, - metadata => { - 'meta1' => 'm1 value', - 'meta2' => 'm2 value', - #'meta3' => 'm3 value' - } -} - -kdbkey { "${ns}-test/section1/setting1": - ensure => present, - value => 'hello ini world ...', - metadata => { - 'comments/#0' => '# this is the first comment line', - 'comments/#1' => '# this is the second comment line' - } -} - -kdbkey { "${ns}-test/section1/setting2": - ensure => present, - value => 'some value ...', - comments => ' -this setting will do the most important stuff -with a multi line comment -here comes the setting -m line1 -m line2' -} - -#kdbkey { "${ns}-test/section2": -# comments => " -#this is just a section key with -#some comments -#not really very important stuff, but -#...ok" -#} - -kdbkey { "${ns}-test/section2/setting1": - value => 'asdf', - comments => '' - # before => Kdbkey["${ns}-test/section2"] -} diff --git a/examples/kdbkey.pp b/examples/kdbkey.pp new file mode 100644 index 0000000..84fc556 --- /dev/null +++ b/examples/kdbkey.pp @@ -0,0 +1,66 @@ +# The baseline for module testing used by Puppet Labs is that each manifest +# should have a corresponding test manifest that declares that class or defined +# type. +# +# Tests are then run by using puppet apply --noop (to check for compilation +# errors and view a log of events) or by fully applying the test in a virtual +# environment (to compare the resulting system state to the desired state). +# +# Learn more about module testing here: +# https://docs.puppet.com/guides/tests_smoke.html +# + +#include ::libelektra + +$ns = 'user/test/puppet' + +kdbkey { "${ns}/x1": + ensure => present, + value => 'hello world x1 ...' +} + +kdbkey { "${ns}/x2": + ensure => absent +} + +kdbkey { "${ns}/x3": + ensure => present +} + +kdbkey { "${ns}/x4": + ensure => present, + value => 'x4 value ...', + purge_meta_keys => true, + metadata => { + 'meta1' => 'm1 value', + 'meta2' => 'm2 value', + #'meta3' => 'm3 value' + } +} + +kdbkey { "${ns}-test/section1/setting1": + ensure => present, + value => 'hello ini world ...', + metadata => { + 'comments/#0' => '# this is the first comment line', + 'comments/#1' => '# this is the second comment line' + } +} + +kdbkey { "${ns}-test/section1/setting2": + ensure => present, + value => 'some value ...', + comments => ' +this setting will do the most important stuff +with a multi line comment +here comes the setting +m line1 +m line2' +} + + +kdbkey { "${ns}-test/section2/setting1": + value => 'asdf', + comments => '' + # before => Kdbkey["${ns}-test/section2"] +} diff --git a/examples/kdbmount.pp b/examples/kdbmount.pp new file mode 100644 index 0000000..7e06ecc --- /dev/null +++ b/examples/kdbmount.pp @@ -0,0 +1,20 @@ + + + +kdbmount { 'system/sw/ssh/sshd': + ensure => present, + file => '/etc/ssh/sshd_config', + plugins => [ + 'ini' => { + 'array' => '', + 'delimiter' => ' ' + } + ] + #plugins => ['sync', 'ini'] +} + +kdbmount { 'system/network/hosts': + ensure => present, + file => '/etc/hosts', + plugins => 'hosts' +} diff --git a/lib/puppet/provider/kdbmount/kdb.rb b/lib/puppet/provider/kdbmount/kdb.rb new file mode 100644 index 0000000..9e106b9 --- /dev/null +++ b/lib/puppet/provider/kdbmount/kdb.rb @@ -0,0 +1,112 @@ +# encoding: UTF-8 +## +# @file +# +# @brief Kdb provider for type kdbmount for managing libelektra key database +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# +# + +module Puppet + Type.type(:kdbmount).provide :kdb do + desc "kdbmount through kdb command" + + commands :kdb => "kdb" + + mk_resource_methods + + def self.instances + mounts = [] + get_active_mountpoints.each do |hash| + if hash + mounts << new(hash) + end + end + return mounts + end + + def self.prefetch(defined_mountpoints) + #defined_mountpoints.each do |name, res| + # puts "defined mp: name: #{name}, file: #{res[:file]}" + #end + instances.each do |prov_inst| + if resource = defined_mountpoints[prov_inst.name] + resource.provider = prov_inst + end + end + end + + def create + cmd_args = ["mount"] + cmd_args << @resource[:file] + cmd_args << @resource[:name] # mountpoint + cmd_args += @resource[:plugins] unless @resource[:plugins].nil? + kdb(cmd_args) + end + + def destroy + kdb ["umount", @resource[:name]] + end + + #def exists? + # this is defined in Type as we use prefetch + #end + + #def file + # puts "getting file" + #end + + def file=(value) + # puts "setting file to #{value}" + # @property_hash[:file] = value + # changing file is simply done via recreation (for NOW) + destroy + create + end + + #def plugins=(value) + # puts "setting plugins #{value}" + #end + + #def flush + # puts "do flush of #{@resource[:name]}" + # puts @property_hash + # puts "plugins: #{@resource[:plugins]}" + #end + + private + + def self.get_active_mountpoints + mp = [] + lines = kdb(["mount"]).split "\n" + lines.each do |mount_line| + mp << parse_mount_line(mount_line) + end + return mp + end + + def self.parse_mount_line(mount_line) + hash = nil + + if /^(.+) on (.+) with name (.+)$/ =~ mount_line + (file, path, _name) = $~[1,3] + #puts "got: file: #{file}, path: #{path}, name: #{name}" + hash = {} + + hash[:provider] = self.name + # assuming path is the correct value for our 'name' var + hash[:name] = path + hash[:file] = file + hash[:ensure] = :present + else + raise Puppet::Error, "'kdb mount' invalid line: #{mount_line}" + end + + return hash + end + + + + end +end diff --git a/lib/puppet/type/kdbmount.rb b/lib/puppet/type/kdbmount.rb new file mode 100644 index 0000000..5bd575a --- /dev/null +++ b/lib/puppet/type/kdbmount.rb @@ -0,0 +1,96 @@ +# encoding: UTF-8 +## +# @file +# +# @brief Custom puppet type kdbkey for managing libelektra keys +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# +# +#require 'puppet/parameter/boolean' + +Puppet::Type.newtype(:kdbmount) do + @doc = <<-EOT + Manage libelekra global key-space. + + This resource type allows to define and manipulate libelektra's global key + database. Libelektra allows to 'mount' external configuration files into + its key database. A specific libelektra backend plugin is for reading and + writing the configuration file. + ... + EOT + + ensurable + + newparam(:name) do + desc <<-EOT + The fully qualified mount path within the libelektra key database. + + TODO: describe if it is safe or not to use cascading keys? + EOT + + validate do |name| + # TODO: which namespaces are safe to use? + #unless name =~ /^(spec|proc|dir|user|system)?\/.+/ + unless name =~ /^(spec|dir|user|system)?\/.+/ + raise ArgumentError, "%s is not a valid libelektra key name" % name + end + end + + isnamevar + end + + newproperty(:file) do + desc <<-EOT + The configuration file to mount into the libelektra key database. + EOT + # TODO: do we have any restrictions on this? + end + + # for now we do not support changing plugins and there settins + # so we use a param for this NOW + #newproperty(:plugins, :array_matching => :all) do + newparam(:plugins) do + desc <<-EOT + A list of libelektra plugins to use for mounting. + TODO: finish this + EOT + + munge do |plugins| + puts "plugins munge: #{value}" + config_args = [] + + if plugins.is_a? String + config_args << plugins + + elsif plugins.is_a? Array + plugins.each do |elem| + if elem.is_a? String + config_args << elem + elsif elem.is_a? Hash + # we've got a config hash for the previous plugin + config_line = '' + elem.each do |plugin_config, value| + config_line << ',' unless config_line.empty? + config_line << "#{plugin_config}=#{value}" + #config_line << plugin_config + #config_line << "=#{value}" unless value.empty? + end + config_args << config_line + end + end + end + + return config_args + end + end + + def exists? + #puts "type kdbmount exists? #{self[:name]}" + @provider.get(:ensure) != :absent + end + + + def self.plugins_to_config_args(plugins) + end +end diff --git a/spec/unit/provider/kdbmount_kdb_spec.rb b/spec/unit/provider/kdbmount_kdb_spec.rb new file mode 100644 index 0000000..2879e88 --- /dev/null +++ b/spec/unit/provider/kdbmount_kdb_spec.rb @@ -0,0 +1,139 @@ +# encoding: UTF-8 +## +# @file +# +# @brief +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# + +require 'spec_helper' +require 'kdb' + +TEST_NS = 'user/test/puppet-rspec/' + +def create_resource(params) + Puppet::Type.type(:kdbkey).new(params) +end + + + +describe Puppet::Type.type(:kdbkey).provider(:ruby) do + + let(:name) { "#{TEST_NS}x1" } + let(:ks) { double("ks") } + let(:provider) { described_class.new } + + before :example do + described_class.use_fake_ks ks + provider.resource = create_resource :name => name + end + + + it "should be a child of Puppet::Provider" do + expect(described_class.new).to be_a_kind_of(Puppet::Provider) + end + + context "should check if resource exists" do + it "should return false on exists? if resource does not exist'" do + allow(ks).to receive(:lookup).and_return nil + + expect(ks).to receive(:lookup) { name } + # for some very strange reason the first call inside expect(..) is true + provider.exists? + expect(provider.exists?).to eq(false) + expect(provider.resource_key).to be_nil + end + + it "should return true on exists? if resource exists'" do + key = Kdb::Key.new name + allow(ks).to receive(:lookup).and_return key + + expect(ks).to receive(:lookup) { name } + provider.exists? + expect(provider.exists?).to eq(true) + expect(provider.resource_key).to be(key) + end + + end + + context "should create key" do + before :example do + allow(ks).to receive(:<<) + expect(ks).to receive(:<<) { provider.resource_key } + end + + it "with defined name" do + provider.create + key = provider.resource_key + + expect(key.name).to eq(name) + end + + it "with defined name and value" do + value = "my value" + provider.resource = create_resource :name => name, :value => value + + provider.create + + expect(provider.resource_key.name).to eq(name) + expect(provider.resource_key.value).to eq(value) + end + + it "with defined name, value and metadata" do + value = "my value" + meta = {'meta1' => 'v1', 'meta2' => 'v2' } + provider.resource = create_resource :name => name, + :value => value, + :metadata => meta + + provider.create + + expect(provider.resource_key.name).to eq(name) + expect(provider.resource_key.value).to eq(value) + meta.each do |k, v| + expect(provider.resource_key.get_meta k).to eq(v) + end + end + + it "with defined name, value, metadata and comments" do + value = "my value" + meta = {'meta1' => 'v1', 'meta2' => 'v2' } + comments = "my comment" + + provider.resource = create_resource :name => name, + :value => value, + :metadata => meta, + :comments => comments + + provider.create + + expect(provider.resource_key.name).to eq(name) + expect(provider.resource_key.value).to eq(value) + meta.each do |k, v| + expect(provider.resource_key.get_meta k).to eq(v) + end + expect(provider.resource_key['comments']).to eq("#0") + expect(provider.resource_key['comments/#0']).to eq("# #{comments}") + end + + + end + + it "should do nothing on destroy when resource_key is nil" do + provider.destroy + end + + it "should remove key when we have a key" do + # first create the key, a delete on nil-key does not make sense + allow(ks).to receive(:<<) + provider.create + + expect(ks).to receive(:delete) { nil } + provider.destroy + end + + it "should update the value" + it "should update the metadata" + it "should update the comments" +end diff --git a/spec/unit/type/kdbmount_spec.rb b/spec/unit/type/kdbmount_spec.rb new file mode 100644 index 0000000..e8a1071 --- /dev/null +++ b/spec/unit/type/kdbmount_spec.rb @@ -0,0 +1,110 @@ +# encoding: UTF-8 +## +# @file +# +# @brief +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# + +require 'spec_helper' + + +describe Puppet::Type.type(:kdbmount) do + + context "property 'name'" do + let(:name) { "user/test/puppet" } + it "exists and is mandatory" do + expect(described_class.new(:name => name)[:name]).to eq(name) + expect { described_class.new() }.to raise_error(ArgumentError) + end + + RSpec.shared_examples "valid key names" do |name| + it "accepts the key name '#{name}'" do + expect(described_class.new(:name => name)[:name]).to eq(name) + end + end + + context "accepts valid libelektra key names" do + # cascading key name + include_examples "valid key names", "/test/puppet" + # absolute, by namespace key names + include_examples "valid key names", "spec/test/puppet" + #include_examples "valid key names", "proc/test/puppet" + include_examples "valid key names", "dir/test/puppet" + include_examples "valid key names", "user/test/puppet" + include_examples "valid key names", "system/test/puppet" + end + + RSpec.shared_examples "invalid key names" do |name| + it "rejects the invalid key name '#{name}'" do + expect { + described_class.new(:name => name) + }.to raise_error(Puppet::ResourceError) + end + end + + context "rejects invalid libelektra key names" do + include_examples "invalid key names", "" + include_examples "invalid key names", "hello/world" + include_examples "invalid key names", "invalid-name-space" + include_examples "invalid key names", "test/xy" + end + end + + context "property 'file'" do + let(:params) { { + :name => "user/test/puppet", + :file => "some value"} + } + it "exists" do + expect(described_class.new(params)[:file]).to eq(params[:file]) + end + + let(:params) { { + :name => "user/test/puppet"} + } + it "is optional" do + expect(described_class.new(params)[:file]).to be_nil + end + end + + + context "property 'plugins'" do + let(:params) { { + :name => "user/test/puppet", + :plugins => { 'plugin1' => 'sync' } + } + } + it "exists" do + expect(described_class.new(params)[:plugins]).to eq(["sync"]) + end + + let(:params) { { + :name => "user/test/puppet" + } + } + it "is optional" do + expect(described_class.new(params)[:plugins]).to be_nil + end + #it "only accepts hash values" do + # p1 = params + + # p1[:metadata] = "" + # expect { described_class.new(p1) }.to raise_error(Puppet::ResourceError) + + # p1[:metadata] = "not a hash" + # expect { described_class.new(p1) }.to raise_error(Puppet::ResourceError) + + # p1[:metadata] = 1 + # expect { described_class.new(p1) }.to raise_error(Puppet::ResourceError) + + # p1[:metadata] = { + # 'meta1' => 'value 1', + # 'meta2' => 'value 2' + # } + # expect(described_class.new(p1)[:metadata]).to eq(p1[:metadata]) + #end + end + +end From 4f53d7d845312ab6317b199b7e1a2b17562f1f8d Mon Sep 17 00:00:00 2001 From: bernhard Date: Wed, 21 Dec 2016 17:54:51 +0100 Subject: [PATCH 10/52] readme stub --- README.md | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7920a68..c413092 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# libelektra +# Puppet module for libelektra #### Table of Contents @@ -14,7 +14,9 @@ ## Description -Start with a one- or two-sentence summary of what the module does and/or what +TODO + +Stub: Start with a one- or two-sentence summary of what the module does and/or what problem it solves. This is your 30-second elevator pitch for your module. Consider including OS/Puppet version it works with. @@ -27,7 +29,7 @@ management, etc.), this is the time to mention it. ### What libelektra affects **OPTIONAL** -If it's obvious what your module touches, you can skip this section. For +stub: If it's obvious what your module touches, you can skip this section. For example, folks can probably figure out that your mysql_instance module affects their MySQL instances. @@ -40,7 +42,7 @@ If there's more that they should know about, though, this is the place to mentio ### Setup Requirements **OPTIONAL** -If your module requires anything extra before setting up (pluginsync enabled, +stub: If your module requires anything extra before setting up (pluginsync enabled, etc.), mention it here. If your most recent release breaks compatibility or requires particular steps @@ -49,35 +51,35 @@ here. ### Beginning with libelektra -The very basic steps needed for a user to get the module up and running. This +Stub: The very basic steps needed for a user to get the module up and running. This can include setup steps, if necessary, or it can be an example of the most basic use of the module. ## Usage -This section is where you describe how to customize, configure, and do the +Stub: This section is where you describe how to customize, configure, and do the fancy stuff with your module here. It's especially helpful if you include usage examples and code samples for doing things with your module. ## Reference -Here, include a complete list of your module's classes, types, providers, +Stub: Here, include a complete list of your module's classes, types, providers, facts, along with the parameters for each. Users refer to this section (thus the name "Reference") to find specific details; most users don't read it per se. ## Limitations -This is where you list OS compatibility, version compatibility, etc. If there +Stub: This is where you list OS compatibility, version compatibility, etc. If there are Known Issues, you might want to include them under their own heading here. ## Development -Since your module is awesome, other users will want to play with it. Let them +Stub: Since your module is awesome, other users will want to play with it. Let them know what the ground rules for contributing are. ## Release Notes/Contributors/Etc. **Optional** -If you aren't using changelog, put your release notes here (though you should +Stub: If you aren't using changelog, put your release notes here (though you should consider using changelog). You can also add any additional sections you feel are necessary or important to include here. Please use the `## ` header. From 776e5d3f2e72c75d3fe9429b3875dbab3f34d4fa Mon Sep 17 00:00:00 2001 From: bernhard Date: Mon, 20 Feb 2017 20:58:45 +0100 Subject: [PATCH 11/52] kdbmount Ruby provider --- examples/kdbmount.pp | 24 ++- lib/puppet/provider/kdbkey/ruby.rb | 4 +- lib/puppet/provider/kdbmount/kdb.rb | 23 +- lib/puppet/provider/kdbmount/ruby.rb | 304 +++++++++++++++++++++++++++ lib/puppet/type/kdbkey.rb | 9 + lib/puppet/type/kdbmount.rb | 105 +++++---- 6 files changed, 428 insertions(+), 41 deletions(-) create mode 100644 lib/puppet/provider/kdbmount/ruby.rb diff --git a/examples/kdbmount.pp b/examples/kdbmount.pp index 7e06ecc..744b069 100644 --- a/examples/kdbmount.pp +++ b/examples/kdbmount.pp @@ -8,7 +8,7 @@ 'ini' => { 'array' => '', 'delimiter' => ' ' - } + }, ] #plugins => ['sync', 'ini'] } @@ -18,3 +18,25 @@ file => '/etc/hosts', plugins => 'hosts' } + +kdbmount { '/test/cascading': + ensure => present, + file => 'test.ini', + plugins => 'ini' +} + +kdbmount { '/test/cas2': + file => '/tmp/test.ini', + resolver => 'noresolver' +} + + +kdbmount { 'system/jenkins': + file => '/tmp/jenkins.xml', + plugins => [ + 'augeas' => { + #'lens' => '/usr/share/augeas/lenses/dist/xml.aug' + 'lens' => 'Xml.lns' + } + ] +} diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 9f181ca..b23072c 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -92,7 +92,7 @@ def metadata def metadata=(value) # update metadata value.each { |k, v| - @resource_key.set_meta k, v + @resource_key.set_meta k, v } unless @resource_key.nil? # do we have to purge all unspecified keys? @@ -108,7 +108,7 @@ def comments first = true # used for splitting lines # search for all meta keys which names starts with 'comments/#' # and concat its values line by line - @resource_key.meta.each do |e| + @resource_key.meta.each do |e| if e.name.start_with? "comments/#" comments << "\n" unless first comments << e.value.sub(/^# /, '') diff --git a/lib/puppet/provider/kdbmount/kdb.rb b/lib/puppet/provider/kdbmount/kdb.rb index 9e106b9..1891d1e 100644 --- a/lib/puppet/provider/kdbmount/kdb.rb +++ b/lib/puppet/provider/kdbmount/kdb.rb @@ -38,14 +38,35 @@ def self.prefetch(defined_mountpoints) end def create + #puts "kdb create" cmd_args = ["mount"] + cmd_args << "-R" + cmd_args << @resource[:resolver] + cmd_args << "-W" if @resource[:add_recommended_plugins] cmd_args << @resource[:file] cmd_args << @resource[:name] # mountpoint - cmd_args += @resource[:plugins] unless @resource[:plugins].nil? + if @resource[:plugins].is_a? Array + @resource[:plugins].each do |e| + # build plugin config cmdline argument + if e.is_a? Hash + config_line = '' + e.each do |k,v| + config_line << "," unless config_line.empty? + config_line << "#{k}=#{v}" + end + cmd_args << config_line + else + # plain plugin name + cmd_args << e + end + end + end + cmd_args.flatten! kdb(cmd_args) end def destroy + puts "kdb destroy" kdb ["umount", @resource[:name]] end diff --git a/lib/puppet/provider/kdbmount/ruby.rb b/lib/puppet/provider/kdbmount/ruby.rb new file mode 100644 index 0000000..ec21ed3 --- /dev/null +++ b/lib/puppet/provider/kdbmount/ruby.rb @@ -0,0 +1,304 @@ +# encoding: UTF-8 +## +# @file +# +# @brief Ruby provider for type kdbmount for managing libelektra key database +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# +# + +module Puppet + Type.type(:kdbmount).provide :ruby do + desc "kdbmount through libelektra Ruby API" + + @@have_kdb = true + + begin + require 'kdbtools' + rescue + @@have_kdb = false + end + + defaultfor :kernel => :Linux + confine :true => @@have_kdb + + # generate getter and setter + mk_resource_methods + + + # find all existing instances + # + def self.instances + mounts = [] + get_active_mountpoints.each do |hash| + if hash + mounts << new(hash) + end + end + return mounts + end + + + def self.prefetch(defined_mountpoints) + #defined_mountpoints.each do |name, res| + # Puppet.debug "defined mp: name: #{name}, file: #{res[:file]}" + #end + instances.each do |prov_inst| + if resource = defined_mountpoints[prov_inst.name] + resource.provider = prov_inst + end + end + end + + + def create + Puppet.debug "kdbmount:ruby: create #{@resource}" + perform_kdb_action Kdbtools::MOUNTPOINTS_PATH, + &method(:set_mount_backend_config) + end + + + def destroy + perform_kdb_action Kdbtools::MOUNTPOINTS_PATH, + &method(:set_unmount_backend_config) + end + + + def recreate + perform_kdb_action Kdbtools::MOUNTPOINTS_PATH, + &method(:reset_backend_config) + end + + + #def exists? + # this is defined in Type as we use prefetch + #end + + + #def file + # puts "getting file" + #end + + def file=(value) + begin + #Kdb.open do |kdb| + backend_root = Kdb::Key.new Kdbtools::MOUNTPOINTS_PATH + backend_root.add_basename @resource[:name] + + # mountconf = Kdb::KeySet.new + # kdb.get mountconf, backend_root + perform_kdb_action backend_root do |mountconf| + + if path_key.nil? + raise Puppet::Error.new "path key not found in backend config" + end + + path_key.value = @resource[:file] + + # kdb.set mountconf, backend_root + end + rescue + Puppet.debug "could not set file within backend, fallback to recreate mountpoint" + # fallback, recreate mountpoint + recreate + end + end + + + def plugins=(value) + # this is pretty the same as building the backend and replace the current + # backend config with the new one + recreate + end + + def resolver=(value) + recreate + end + + #def flush + # puts "do flush of #{@resource[:name]}" + # puts @property_hash + # puts "plugins: #{@resource[:plugins]}" + #end + + private + + # get all active mountpoint + # + def self.get_active_mountpoints + mp = [] + Kdb.open do |kdb| + mountconf = Kdb::KeySet.new + kdb.get mountconf, Kdbtools::MOUNTPOINTS_PATH + + backends = Kdbtools::Backends.get_backend_info mountconf + + backends.each do |mount| + backend_key = Kdb::Key.new Kdbtools::MOUNTPOINTS_PATH + backend_key.add_basename mount.mountpoint + backend_ks = mountconf.cut backend_key + + hash = {} + hash[:provider] = self.name + hash[:name] = mount.mountpoint + hash[:file] = mount.path + hash[:ensure] = :present + hash[:plugins] = get_mountoint_plugin_config backend_ks + mp << hash + end + end + Puppet.debug mp + return mp + end + + + # get configured plugins with their config settings from a + # given backend + # + def self.get_mountoint_plugin_config(backend) + plugins = {} + backend.each do |key| + # we only search for the plugin keys + if /\/(error|get|set)plugins\// =~ key.fullname + # parse the plugin key name + if /^#([0-9]+)#(\w+)(#(\w+)#)?$/ =~ key.basename + plugin_name = $2 + #ref_number = $1 # unused + ref_name = $4 + # skip resolver plugins + next if ref_name == "resolver" + next if plugin_name == "resolver" + next if plugin_name == "sync" # TODO is it save to ignore sync + + #puts "matching plugin: #{$2}, num: #{$1}, refname: #{$4}" + + plugins[plugin_name] = {} unless plugins.include? plugin_name + + # check for config keys + config_ks = backend.cut Kdb::Key.new key.name + "/config" + config_ks.each do |config_key| + # ignore the first dir key + next if config_key == Kdb::Key.new(key.name + "/config") + plugins[plugin_name][config_key.basename] = config_key.value + end + end + end + end + plugins = plugins.to_a.flatten.reject {|e| e.empty? } + #puts "#{plugins}" + plugins + end + + + # convert the Puppet given :plugins value to a more suitable + # hash: + # pluginname => plugin config settings + # e.g: + # ini => { + # delimiter => " " + # array => "" + # }, + # type => { } + # + def convert_plugin_settings(plugins) + config = {} + cur_plugin = nil + if plugins.respond_to? :each + plugins.each do |e| + if e.is_a? String + cur_plugin = e + config[e] = {} + elsif e.is_a? Hash + config[cur_plugin] = e + else + Puppet::Error "invalid plugins configuration given" + end + end + end + return config + end + + + # helper function to modify Elektra key database + # helps to avoid multiple Kdb.open/close sequences + # + def perform_kdb_action path, &block + Kdb.open do |kdb| + mountconf = Kdb::KeySet.new + kdb.get mountconf, path + + yield mountconf + + # write new mount config + kdb.set mountconf, path + end + end + + + # create a new mount point and add it the the existing + # mount config (fetched from system/elektra/mountpoints + # use with perform_kdb_action + # + def set_mount_backend_config(mountconf) + + backend = Kdbtools::MountBackendBuilder.new + + # mountpoint + mpk = Kdb::Key.new @resource[:name] + unless mpk.is_valid? + raise Puppet::Error "invalid mountpoint: #{@resource[:name]}" + end + + # add new mount point, checks for mountpoint validity and + # already existing mountpoint + backend.set_mountpoint mpk, mountconf + + # add the resolver plugin + backend.add_plugin Kdbtools::PluginSpec.new @resource[:resolver] + + backend.use_config_file @resource[:file] + + backend.need_plugin "storage" + + @resource.class.const_get(:RECOMMENDED_PLUGINS).each do |p| + backend.recommend_plugin p + end + + plugin_config = convert_plugin_settings(@resource[:plugins]) + # add user requested plugins + plugin_config.each do |p_name, p_config| + ps = Kdbtools::PluginSpec.new p_name + p_config.each do |k, v| + ps.append_config Kdb::KeySet.new Kdb::Key.new("user/#{k}", value: v) + end + backend.add_plugin ps + end + + # resolv all required plugins (without recommended (false)) + backend.resolve_needs @resource[:add_recommended_plugins] + + # add new backend to mount config + backend.serialize mountconf + end + + + # remove existing mount point from existing mount config + # use with perform_kdb_action + # + def set_unmount_backend_config(mountconf) + Kdbtools::Backends.umount @resource[:name], mountconf + end + + + # recreate mount config + # use with perform_kdb_action + # + def reset_backend_config(mountconf) + set_unmount_backend_config mountconf + set_mount_backend_config mountconf + end + + + end +end diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index 89957ee..ff9256c 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -35,6 +35,15 @@ isnamevar end + # TODO: this would be very handy to set a basepath for multiple keys at once + #newparam(:basename) do + # desc <<-EOT + + # EOT + + # isnamevar + #end + newproperty(:value) do desc <<-EOT Desired value of the key. diff --git a/lib/puppet/type/kdbmount.rb b/lib/puppet/type/kdbmount.rb index 5bd575a..8a58c1a 100644 --- a/lib/puppet/type/kdbmount.rb +++ b/lib/puppet/type/kdbmount.rb @@ -7,7 +7,8 @@ # @copyright BSD License (see LICENSE or http://www.libelektra.org) # # -#require 'puppet/parameter/boolean' +require 'puppet/parameter/boolean' + Puppet::Type.newtype(:kdbmount) do @doc = <<-EOT @@ -16,12 +17,16 @@ This resource type allows to define and manipulate libelektra's global key database. Libelektra allows to 'mount' external configuration files into its key database. A specific libelektra backend plugin is for reading and - writing the configuration file. + writing the configuration file. ... EOT + RECOMMENDED_PLUGINS = ["sync"] + + ensurable + newparam(:name) do desc <<-EOT The fully qualified mount path within the libelektra key database. @@ -40,57 +45,83 @@ isnamevar end + newproperty(:file) do desc <<-EOT - The configuration file to mount into the libelektra key database. + The configuration file to mount into the Elektra key database. EOT # TODO: do we have any restrictions on this? end - # for now we do not support changing plugins and there settins - # so we use a param for this NOW - #newproperty(:plugins, :array_matching => :all) do - newparam(:plugins) do + + #newproperty(:resolver) do + newparam(:resolver) do desc <<-EOT - A list of libelektra plugins to use for mounting. - TODO: finish this + The resolver plugin to use for mounting. + Default: 'resolver' EOT - munge do |plugins| - puts "plugins munge: #{value}" - config_args = [] - - if plugins.is_a? String - config_args << plugins - - elsif plugins.is_a? Array - plugins.each do |elem| - if elem.is_a? String - config_args << elem - elsif elem.is_a? Hash - # we've got a config hash for the previous plugin - config_line = '' - elem.each do |plugin_config, value| - config_line << ',' unless config_line.empty? - config_line << "#{plugin_config}=#{value}" - #config_line << plugin_config - #config_line << "=#{value}" unless value.empty? - end - config_args << config_line - end - end - end + defaultto "resolver" - return config_args + validate do |value| + unless /^\w+$/ =~ value + raise ArgumentError, "'%s' is not a valid plugin name" % value + end end end + + newparam(:add_recommended_plugins, + :boolean => true, + :parent => Puppet::Parameter::Boolean) do + desc <<-EOT + If set to true, Elektra will add recommended plugins to the mounted + backend configuration. + Recommended plugins are: #{RECOMMENDED_PLUGINS.join ', '} + Default: false + EOT + defaultto :false + end + + + # for now we do not support changing plugins and there settings + # so we use a param for this NOW + newproperty(:plugins, :array_matching => :all) do + #newparam(:plugins) do + desc <<-EOT + A list of libelektra plugins with optional configuration settings + use for mounting. + + The following value formats are acceped: + - a string value describing a single plugin name + - an array of string values each defining a single plugin + - a hash of plugin names with corresponding configuration settings + e.g. + [ 'ini' => { + 'delimiter' => " " + 'array' => '' + }, + 'type' + ] + + EOT + + #munge do |value| + # puts "resolver: is nil" if resource[:resolver].nil? + # value << resource[:resolver] + # value + RECOMMENDED_PLUGINS if resource[:add_recommended_plugins] + #end + + # TODO implement this to allow better plugins handling + #def insync? + #end + + end + + def exists? #puts "type kdbmount exists? #{self[:name]}" @provider.get(:ensure) != :absent end - - def self.plugins_to_config_args(plugins) - end end From 7994bf2889ca805437a0a7106fe82c08ee05db09 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 23 Feb 2017 14:37:18 +0100 Subject: [PATCH 12/52] kdbkey type: add prefix parameter --- examples/kdbkey.pp | 25 +++++++++++- lib/puppet/type/kdbkey.rb | 73 ++++++++++++++++++++++++++++++----- spec/unit/type/kdbkey_spec.rb | 66 ++++++++++++++++++++++++++++++- 3 files changed, 151 insertions(+), 13 deletions(-) diff --git a/examples/kdbkey.pp b/examples/kdbkey.pp index 84fc556..3c60f05 100644 --- a/examples/kdbkey.pp +++ b/examples/kdbkey.pp @@ -60,7 +60,28 @@ kdbkey { "${ns}-test/section2/setting1": - value => 'asdf', - comments => '' + value => 'asdf', + comments => '' # before => Kdbkey["${ns}-test/section2"] } + + +# +# prefix tests +# + +# results in "${ns-test}/prefixtest/s1" +kdbkey { '/prefixtest/s1': + prefix => "${ns}-test", + value => 'hello prefix' +} + +# results in "${ns-test}/prefixtest/s12" +# will lead to duplicate resource error if it matches the above one (eaven +# with a different resource title) +kdbkey { 'something else': + name => '/prefixtest/s12', + prefix => "${ns}-test", + value => 'hello prefix' +} + diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index ff9256c..4d5bba3 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -19,6 +19,46 @@ ensurable + # prefix parameter + # + # Note: this has to be defined BEFORE the name parameter, since we reference + # this prefix parameter within the names 'muge' and 'validate' methods + newparam(:prefix) do + desc <<-EOT + Prefix for the key name (optional) + + If given, this value will prefix the given libelektra key name. + e.g.: + + kdbkey { 'puppet/x1': + prefix => 'system/test', + value => 'hello' + } + + This will manage the key 'system/test/puppet/x1'. + + Prefix and name are joined with a '/', if prefix does not end with '/' + or name does not start with '/'. + + Both, name and prefix parameter are used to uniquely identify a + libelektra key. + EOT + + isnamevar + + defaultto "" + + validate do |name| + unless name.nil? or name.empty? + unless name =~ /^(\/|spec|proc|dir|user|system)/ + raise ArgumentError, "'%s' is not a valid basename" % name + end + end + end + end + + + # name parameter newparam(:name) do desc <<-EOT The fully qualified name of the key @@ -26,23 +66,38 @@ TODO: describe if it is safe or not to use cascading keys? EOT + # add the prefix, if given + munge do |value| + if resource[:prefix].nil? + value + else + fullname = resource[:prefix] + fullname += "/" unless fullname[-1] == "/" or value[0] == "/" + fullname += value + fullname.gsub "//", "/" + end + end + + # if no prefix is given, we have to validate the key name validate do |name| - unless name =~ /^(spec|proc|dir|user|system)?\/.+/ - raise ArgumentError, "%s is not a valid libelektra key name" % name + if resource[:prefix].nil? or resource[:prefix].empty? + unless name =~ /^(spec|proc|dir|user|system)?\/.+/ + raise ArgumentError, "'%s' is not a valid libelektra key name" % name + end end end isnamevar end - # TODO: this would be very handy to set a basepath for multiple keys at once - #newparam(:basename) do - # desc <<-EOT - - # EOT + # this is required, since we've defined to parameter as 'namevar' + # it's used to assign the name from the resource title, whereas the default + # implementation will raise an error if two name vars are given + # (see type.rb for details) + def self.title_patterns + [ [ /(.*)/m, [ [:name] ] ] ] + end - # isnamevar - #end newproperty(:value) do desc <<-EOT diff --git a/spec/unit/type/kdbkey_spec.rb b/spec/unit/type/kdbkey_spec.rb index 02883d3..1b4216d 100644 --- a/spec/unit/type/kdbkey_spec.rb +++ b/spec/unit/type/kdbkey_spec.rb @@ -38,8 +38,8 @@ RSpec.shared_examples "invalid key names" do |name| it "rejects the invalid key name '#{name}'" do - expect { - described_class.new(:name => name) + expect { + described_class.new(:name => name) }.to raise_error(Puppet::ResourceError) end end @@ -52,6 +52,68 @@ end end + context "property 'prefix'" do + let(:name) { "/test/puppet/x1" } + let(:prefix) { "user" } + it "exists and is optional with default value ''" do + expect(described_class.new(:name => name, + :prefix => prefix)[:prefix] + ).to eq(prefix) + expect(described_class.new(:name => name)[:prefix]).to eq("") + end + + RSpec.shared_examples "valid key prefix" do |prefix| + it "accepts the key prefix '#{prefix}'" do + expect(described_class.new(:name => "/test", + :prefix => prefix)[:prefix] + ).to eq(prefix) + end + end + + context "accepts valid libelektra key name prefix" do + # cascading key name + include_examples "valid key prefix", "/test/puppet" + # absolute, by namespace key names + include_examples "valid key prefix", "spec/test/puppet" + include_examples "valid key prefix", "proc/test/puppet" + include_examples "valid key prefix", "dir/test/puppet" + include_examples "valid key prefix", "user/test/puppet" + include_examples "valid key prefix", "system/test/puppet" + # without trailing / + include_examples "valid key prefix", "system" + end + + RSpec.shared_examples "invalid key prefix" do |prefix| + it "rejects the invalid key prefix '#{prefix}'" do + expect { + described_class.new(:name => "/test", + :prefix => prefix) + }.to raise_error(Puppet::ResourceError) + end + end + + context "rejects invalid libelektra key names" do + include_examples "invalid key prefix", "hello/world" + include_examples "invalid key prefix", "invalid-name-space" + include_examples "invalid key prefix", "test/xy" + end + + RSpec.shared_examples "prefix + name" do |prefix, name, expected| + it "with '#{prefix}' (name: '#{name}')" do + expect(described_class.new(:name => name, :prefix => prefix)[:name]).to eq(expected) + end + end + + context "prefixes name property" do + include_examples "prefix + name", "user", "/test/puppet/x1", "user/test/puppet/x1" + include_examples "prefix + name", "system", "/test/puppet/x1", "system/test/puppet/x1" + include_examples "prefix + name", "system/test", "/puppet/x1", "system/test/puppet/x1" + include_examples "prefix + name", "system/", "/test/puppet/x1", "system/test/puppet/x1" + include_examples "prefix + name", "system/", "test/puppet/x1", "system/test/puppet/x1" + include_examples "prefix + name", "system", "test/puppet/x1", "system/test/puppet/x1" + end + end + context "property 'value'" do let(:params) { {:name => "user/test/puppet/x1", :value => "some value"} } it "exists and is optional" do From a6d2bc1d3c7db5fa2c9ab8b6b8f1b67fd4b22dc4 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 23 Feb 2017 18:47:45 +0100 Subject: [PATCH 13/52] kdbkey: implement user parameter --- examples/kdbkey.pp | 9 +++++++++ lib/puppet/provider/kdbkey/kdb.rb | 20 ++++++++++++-------- lib/puppet/type/kdbkey.rb | 29 +++++++++++++++++++++++++++++ spec/unit/type/kdbkey_spec.rb | 7 +++++++ 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/examples/kdbkey.pp b/examples/kdbkey.pp index 3c60f05..8a03ba7 100644 --- a/examples/kdbkey.pp +++ b/examples/kdbkey.pp @@ -85,3 +85,12 @@ value => 'hello prefix' } + +# +# set keys in the context of a given user +# +kdbkey { 'user/test/puppet/usertest/x1': + value => 'asdf', + user => 'bernhard', + #provider => 'kdb' +} diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 100b743..fa903b2 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -12,20 +12,26 @@ module Puppet Type.type(:kdbkey).provide :kdb do desc "kdb through kdb command" + has_feature :user + commands :kdb => "kdb" + def run_kdb(args, params = {:combine => true, :failonfail => true}) + cmd_line = [command(:kdb)] + args + params[:uid] = @resource[:user] unless @resource[:user].nil? + execute(cmd_line, params) + end + def create - #puts "kdb create" self.value=(@resource[:value]) end def destroy - #puts "kdb destroy" - kdb ["rm", @resource[:name]] + run_kdb ["rm", @resource[:name]] end def exists? - #puts "kdb exists? #{self.name}" + Puppet.debug "kdbkey/kdb exists? #{@resource[:name]}" output = execute([command(:kdb), "get", @resource[:name]], :failonfail => false) #puts "output: #{output}, #{output.exitstatus}" @@ -33,13 +39,11 @@ def exists? end def value - #puts "getting value" - kdb ["sget", "--color=never", @resource[:name], "''"] + run_kdb ["sget", "--color=never", @resource[:name], "''"] end def value=(value) - #puts "setting value to #{value}" - kdb(["set", @resource[:name], value]) + run_kdb ["set", @resource[:name], value] end end diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index 4d5bba3..b12cdb7 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -17,6 +17,9 @@ key database. EOT + feature :user, "ability to define/modify keys in the context of a specific user" + + ensurable # prefix parameter @@ -175,4 +178,30 @@ def change_to_s(current_value, new_value) end end + # param user + # + # This is currently only supported by Provider 'kdb'. + # However, it seams the 'feature' stuff is evaluated only for once for all + # instances, so we can not really say, use provider 'kdb' for those with + # 'user' set and provider 'ruby' for all other instances. This is not working + # or at least for me it was not working. So we do it manually. + newparam(:user) do #, :required_features => ["user"]) do + desc <<-EOT + define/modify key in the context of given user. + + This is only relevant, if key name referes to a user context, thus is + either cascading (starting with a '/') or is within the 'user' + namespace (starting with 'user/'). + EOT + + # misuse the validate method, to change the provider, if required + validate do |value| + if provider.class.name != :kdb + Puppet.debug "Puppet::Type::Kdbkey: change provider to 'kdb' (required by param 'user')" + @resource.provider= :kdb + end + end + + end + end diff --git a/spec/unit/type/kdbkey_spec.rb b/spec/unit/type/kdbkey_spec.rb index 1b4216d..c2e0012 100644 --- a/spec/unit/type/kdbkey_spec.rb +++ b/spec/unit/type/kdbkey_spec.rb @@ -181,4 +181,11 @@ end end + context "parameter 'user'" do + let(:params) { {:name => "user/test/puppet/x1"} } + it "exists and is optional" do + expect(described_class.new(params)[:user]).to be_nil + end + end + end From 56910177269d84677a7e40260b95576a8c7243b6 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 23 Feb 2017 18:52:03 +0100 Subject: [PATCH 14/52] type kdbkey: keep name and title in sync if prefix is used, otherwise Puppet will display the 'old' unprefixed name in logs only. Can be confusing. --- lib/puppet/type/kdbkey.rb | 3 ++- spec/unit/type/kdbkey_spec.rb | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index b12cdb7..a19eb5d 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -77,7 +77,8 @@ fullname = resource[:prefix] fullname += "/" unless fullname[-1] == "/" or value[0] == "/" fullname += value - fullname.gsub "//", "/" + fullname.gsub! "//", "/" + @resource.title = fullname end end diff --git a/spec/unit/type/kdbkey_spec.rb b/spec/unit/type/kdbkey_spec.rb index c2e0012..628f864 100644 --- a/spec/unit/type/kdbkey_spec.rb +++ b/spec/unit/type/kdbkey_spec.rb @@ -112,6 +112,12 @@ include_examples "prefix + name", "system/", "test/puppet/x1", "system/test/puppet/x1" include_examples "prefix + name", "system", "test/puppet/x1", "system/test/puppet/x1" end + + it "updates 'title' to match new 'name'" do + expect(described_class.new(:prefix => "user/test", + :name => "/puppet/x1").title + ).to eq("user/test/puppet/x1") + end end context "property 'value'" do From 2048883c062f3a357acfd49632377da898c6d192 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 23 Feb 2017 20:00:17 +0100 Subject: [PATCH 15/52] provider kdbkey kdb: implement unit tests --- spec/unit/provider/kdbkey_kdb_spec.rb | 147 ++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 spec/unit/provider/kdbkey_kdb_spec.rb diff --git a/spec/unit/provider/kdbkey_kdb_spec.rb b/spec/unit/provider/kdbkey_kdb_spec.rb new file mode 100644 index 0000000..ba531c7 --- /dev/null +++ b/spec/unit/provider/kdbkey_kdb_spec.rb @@ -0,0 +1,147 @@ +# encoding: UTF-8 +## +# @file +# +# @brief +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# + +require 'spec_helper' +require 'kdb' + +TEST_NS = 'user/test/puppet-rspec/' + +def create_resource(params) + Puppet::Type.type(:kdbkey).new(params) +end + +def do_on_kdb + raise ArgumentError, "block required" unless block_given? + + Kdb.open do |kdb| + ks = Kdb::KeySet.new + kdb.get ks, TEST_NS + yield ks + kdb.set ks, TEST_NS + end +end + +def ensure_key_exists(key, value = "test") + do_on_kdb do |ks| + key = ks.lookup("#{TEST_NS}x1") + if key.nil? + ks << Kdb::Key.new("#{TEST_NS}x1", value: value) + else + key.value = value + end + end +end + +def ensure_key_is_missing(key) + do_on_kdb do |ks| + unless ks.lookup("#{TEST_NS}x1").nil? + ks.delete "#{TEST_NS}x1" + end + end +end + +def check_key_exists(name) + missing = false + do_on_kdb do |ks| + missing = ks.lookup(name).nil? + end + return !missing +end + +def key_get_value(name) + value = nil + do_on_kdb do |ks| + key = ks.lookup name + value = key.value unless key.nil? + end + return value +end + + + + + +describe Puppet::Type.type(:kdbkey).provider(:kdb) do + + let(:provider) { described_class.new } + let(:keyname) { "#{TEST_NS}x1" } + before :example do + provider.resource = create_resource :name => keyname + end + + + it "should be a child of Puppet::Provider" do + expect(described_class.new).to be_a_kind_of Puppet::Provider + end + + context "should check if a key exists" do + it "should return false on exists? if key is missing" do + ensure_key_is_missing keyname + expect(provider.exists?).to eq(false) + end + + it "should return true on exists? if key exists" do + ensure_key_exists keyname + expect(provider.exists?).to eq(true) + end + end + + context "should create key" do + before :example do + ensure_key_is_missing keyname + end + + it "with defined name and no value" do + provider.create + expect(check_key_exists keyname).to eq(true) + expect(key_get_value keyname).to eq("") + end + + it "with defined name and value" do + expect(check_key_exists keyname).to eq(false) + provider.resource = create_resource(:name => keyname, + :value => "create with value") + provider.create + expect(check_key_exists keyname).to eq(true) + expect(key_get_value keyname).to eq("create with value") + end + end + + context "should update the key value" do + before :example do + ensure_key_exists keyname, "test" + end + + it "to an arbitrary string" do + provider.resource[:value] = "some string value" + + expect(key_get_value keyname).to eq("test") + provider.value= "some string value" + expect(key_get_value keyname).to eq("some string value") + end + + it "to an empty string" do + expect(key_get_value keyname).to eq("test") + provider.value= "" + expect(key_get_value keyname).to eq("") + end + end + + context "should remove key" do + before :example do + ensure_key_exists keyname + end + + it "when key exists" do + expect(check_key_exists keyname).to eq true + provider.destroy + expect(check_key_exists keyname).to eq false + end + end +end From 73f7b8cbe8a6c476d9dd9fd7dd3865f607ef680a Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 23 Feb 2017 20:03:18 +0100 Subject: [PATCH 16/52] gitignore: Gemfile.log --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4a9d6c6..c0e41d6 100644 --- a/.gitignore +++ b/.gitignore @@ -44,9 +44,11 @@ build-iPhoneSimulator/ # for a library or gem, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: -# Gemfile.lock +Gemfile.lock # .ruby-version # .ruby-gemset # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: .rvmrc + +/spec/fixtures/modules/** From 9eb313d9c0ff241dd5652de32c13c9d0db2b4b0c Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 23 Feb 2017 21:03:58 +0100 Subject: [PATCH 17/52] kdbkey provider kdb: implement metadata handling --- lib/puppet/provider/kdbkey/kdb.rb | 36 ++++ spec/unit/provider/kdbkey_kdb_spec.rb | 260 +++++++++++++++++++++++++- 2 files changed, 287 insertions(+), 9 deletions(-) diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index fa903b2..eb43e69 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -24,6 +24,7 @@ def run_kdb(args, params = {:combine => true, :failonfail => true}) def create self.value=(@resource[:value]) + self.metadata= @resource[:metadata] unless @resource[:metadata].nil? end def destroy @@ -46,5 +47,40 @@ def value=(value) run_kdb ["set", @resource[:name], value] end + def metadata + @metadata_values = {} + output = run_kdb ["lsmeta", @resource[:name]] + output.split.each do |metaname| + # foreach meta key, fetch its value + gm = run_kdb(["getmeta", @resource[:name], metaname], { + :combine => false, + :failonfail => false} + ) + if gm.exitstatus == 0 + @metadata_values[metaname.strip] = gm.chomp + end + end + return @metadata_values + end + + def metadata=(value) + value.each do |metaname, metavalue| + run_kdb ["setmeta", @resource[:name], metaname, metavalue] + end + # handle purge_meta_keys + if @resource.purge_meta_keys? and @metadata_values.is_a? Hash + @metadata_values.each do |m,v| + next if value.include? m + next if m == "comments" or m.start_with? "comment/", "comments/" + next if m.start_with? "internal/" + next if m == "order" + + # currently there is no rmmeta command for kdb + run_kdb ["setmeta", @resource[:name], m, ''] + end + end + end + + end end diff --git a/spec/unit/provider/kdbkey_kdb_spec.rb b/spec/unit/provider/kdbkey_kdb_spec.rb index ba531c7..08931ba 100644 --- a/spec/unit/provider/kdbkey_kdb_spec.rb +++ b/spec/unit/provider/kdbkey_kdb_spec.rb @@ -11,6 +11,10 @@ require 'kdb' TEST_NS = 'user/test/puppet-rspec/' +# currently metadaa key for comments used by ini +# but hosts uses 'comment' +COMMENT = 'comments' + def create_resource(params) Puppet::Type.type(:kdbkey).new(params) @@ -27,21 +31,71 @@ def do_on_kdb end end -def ensure_key_exists(key, value = "test") +def do_on_kdb_with_key(keyname) + raise ArgumentError, "block required" unless block_given? + do_on_kdb do |ks| + yield ks.lookup(keyname) + end +end + + +def ensure_key_exists(keyname, value = "test") do_on_kdb do |ks| - key = ks.lookup("#{TEST_NS}x1") + key = ks.lookup(keyname) if key.nil? - ks << Kdb::Key.new("#{TEST_NS}x1", value: value) + ks << Kdb::Key.new(keyname, value: value) else key.value = value end end end -def ensure_key_is_missing(key) +def ensure_meta_exists(keyname, meta, value = "test") + do_on_kdb do |ks| + key = ks.lookup keyname + if key.nil? + key = Kdb::Key.new(keyname) + ks << key + end + key.set_meta meta, value + end +end + +def ensure_comment_exists(keyname, comment = "test") + do_on_kdb do |ks| + key = ks.lookup keyname + if key.nil? + key = Kdb::Key.new keyname + ks << key + end + lines = comment.split "\n" + key[COMMENT] = "##{lines.size}" + lines.each_with_index do |line, index| + key[COMMENT+"/##{index}"] = line + end + end +end + +def ensure_key_is_missing(keyname) do_on_kdb do |ks| - unless ks.lookup("#{TEST_NS}x1").nil? - ks.delete "#{TEST_NS}x1" + unless ks.lookup(keyname).nil? + ks.delete keyname + end + end +end + +def ensure_meta_is_missing(keyname, meta) + do_on_kdb_with_key keyname do |key| + key.del_meta meta unless key.nil? + end +end + +def ensure_comment_is_missing(keyname) + do_on_kdb_with_key keyname do |key| + unless key.nil? + key.meta.each do |m| + key.del_meta m if m.name.start_with? COMMENT + end end end end @@ -54,15 +108,59 @@ def check_key_exists(name) return !missing end -def key_get_value(name) +def check_meta_exists(keyname, meta) + exists = false + do_on_kdb_with_key keyname do |key| + exists = key.has_meta? meta unless key.nil? + end + return exists +end + +def check_comment_exists(keyname) + exists = false + do_on_kdb_with_key keyname do |key| + exists = key.has_meta?(COMMENT) or key.has_meta?(COMMENT+"/#0") unless key.nil? + end + return exists +end + +def key_get_value(keyname) value = nil - do_on_kdb do |ks| - key = ks.lookup name + do_on_kdb_with_key keyname do |key| value = key.value unless key.nil? end return value end +def key_get_meta(keyname, meta) + value = nil + do_on_kdb_with_key keyname do |key| + value = key[meta] unless key.nil? + end + return value +end + +def key_get_comment(keyname) + comment = nil + do_on_kdb_with_key keyname do |key| + unless key.nil? + key.meta.find_all do |e| + e.name.start_with? COMMENT+"/#" + end.each do |c| + comment = "" if comment.nil? + if c.value.start_with? "#" + comment += c.value[1..-1] + else + comment += c.value + end + end + end + end + return comment +end + + + @@ -111,6 +209,18 @@ def key_get_value(name) expect(check_key_exists keyname).to eq(true) expect(key_get_value keyname).to eq("create with value") end + + it "with defined name, value and metadata" do + provider.resource[:value] = "my val" + provider.resource[:metadata] = {"m1" => "v1", "m2" => "v2"} + provider.create + expect(check_key_exists keyname).to eq true + expect(key_get_value keyname).to eq "my val" + expect(check_meta_exists keyname, "m1").to eq true + expect(key_get_meta keyname, "m1").to eq "v1" + expect(check_meta_exists keyname, "m2").to eq true + expect(key_get_meta keyname, "m2").to eq "v2" + end end context "should update the key value" do @@ -144,4 +254,136 @@ def key_get_value(name) expect(check_key_exists keyname).to eq false end end + + context "should get metadata values" do + before :example do + ensure_meta_exists keyname, "m1", "test" + ensure_meta_exists keyname, "m2", "test" + end + + it "as a hash" do + values = provider.metadata + expect(values).to be_a_kind_of Hash + expect(values["m1"]).to eq "test" + expect(values["m2"]).to eq "test" + end + end + + context "should update metadata" do + let(:metadata) { {"m1" => "v1", "m2" => "v2"} } + before :example do + ensure_key_exists keyname + end + + it "with missing metadata key" do + ensure_meta_is_missing keyname, "m1" + ensure_meta_is_missing keyname, "m2" + provider.resource[:metadata] = metadata + + provider.metadata= metadata + + expect(check_meta_exists keyname, "m1").to eq true + expect(check_meta_exists keyname, "m2").to eq true + expect(key_get_meta keyname, "m1").to eq "v1" + expect(key_get_meta keyname, "m2").to eq "v2" + end + + it "with existing metadata" do + ensure_meta_exists keyname, "m1", "test" + ensure_meta_exists keyname, "m2", "test" + provider.resource[:metadata] = metadata + + provider.metadata= metadata + + expect(check_meta_exists keyname, "m1").to eq true + expect(check_meta_exists keyname, "m2").to eq true + expect(key_get_meta keyname, "m1").to eq "v1" + expect(key_get_meta keyname, "m2").to eq "v2" + end + end + + context "should purge not specified metadata if 'purge_meta_keys' is set" do + let(:metadata) { {"m1" => "v1", "m2" => "v2"} } + before :example do + provider.resource[:purge_meta_keys] = true + + ensure_meta_is_missing keyname, "m1" + ensure_meta_exists keyname, "m2" + ensure_meta_exists keyname, "r1", "to remove" + ensure_meta_exists keyname, "r2", "to remove" + end + + it "while updating specified" do + provider.metadata + provider.metadata = metadata + + expect(check_meta_exists keyname, "m1").to eq true + expect(check_meta_exists keyname, "m2").to eq true + expect(key_get_meta keyname, "m1").to eq "v1" + expect(key_get_meta keyname, "m2").to eq "v2" + + #expect(check_meta_exists keyname, "r1").to eq false + #expect(check_meta_exists keyname, "r2").to eq false + expect(key_get_meta keyname, "r1").to eq "" + expect(key_get_meta keyname, "r2").to eq "" + end + + # we can't test is this way, as we can not set 'internal' metadata + # values + #it "while ignoring meta keys starting with 'internal/'" do + # ensure_meta_exists keyname, "internal/puppet/test", "keep it" + + # puts provider.metadata + # provider.metadata = metadata + + # expect(check_meta_exists keyname, "m1").to eq true + # expect(check_meta_exists keyname, "m2").to eq true + + # expect(check_meta_exists keyname, "internal/puppet/test").to eq true + # expect(key_get_meta keyname, "internal/puppet/test").to eq "keep it" + + # #expect(check_meta_exists keyname, "r1").to eq false + # #expect(check_meta_exists keyname, "r2").to eq false + # expect(key_get_meta keyname, "r1").to eq "" + # expect(key_get_meta keyname, "r2").to eq "" + #end + + it "while ignoring comments, which are not modified" do + ensure_comment_exists keyname, " some comments" + + provider.metadata + provider.metadata = metadata + + #expect(check_meta_exists keyname, COMMENT+"/#0").to eq true + expect(check_comment_exists keyname).to eq true + + #expect(key_get_meta keyname, COMMENT+"/#0").to eq "# some comments" + expect(key_get_comment keyname).to eq " some comments" + end + + it "while ignoring commens which are added too" do + provider.metadata = metadata + provider.comments = "comment defined by me" + + provider.metadata + provider.metadata = metadata + + expect(check_comment_exists keyname).to eq true + #expect(check_meta_exists keyname, "r1").to eq false + expect(key_get_meta keyname, "r1").to eq "" + end + + it "while ignoring 'order' metadata" do + ensure_meta_exists keyname, "order", "5" + + provider.metadata + provider.metadata = metadata + + expect(check_meta_exists keyname, "order").to eq true + expect(key_get_meta keyname, "order").to eq "5" + #expect(check_meta_exists keyname, "r1").to eq false + expect(key_get_meta keyname, "r1").to eq "" + end + + end end From d6d424197c9238c1c23a3bb5e781ab0c832f3fdd Mon Sep 17 00:00:00 2001 From: bernhard Date: Fri, 24 Feb 2017 13:09:35 +0100 Subject: [PATCH 18/52] kdbkey provider kdb: implement comment handling --- lib/puppet/provider/kdbkey/kdb.rb | 34 +++++++++++++ spec/unit/provider/kdbkey_kdb_spec.rb | 71 ++++++++++++++++++++++++--- 2 files changed, 99 insertions(+), 6 deletions(-) diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index eb43e69..929357b 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -81,6 +81,40 @@ def metadata=(value) end end + def comments + metadata unless @metadata_values.is_a? Hash + comments = "" + @metadata_values.each do |meta, value| + if /^comments?\/#/ =~ meta + comments << "\n" unless comments.empty? + comments << value.sub(/^#/, '') + end + end + return comments + end + + def comments=(value) + metadata unless @metadata_values.is_a? Hash + comment_lines = value.split "\n" + + updated = [] + comment_lines.each_with_index do |line, index| + updated << meta_name = "comments/##{index}" + run_kdb ["setmeta", @resource[:name], meta_name, line] + end + + @metadata_values.each do |k, v| + # update comments count value + if k == "comments" + run_kdb ["setmeta", @resource[:name], k, "##{comment_lines.size}"] + end + + if k.start_with? "comments/#" and !updated.include? k + run_kdb ["setmeta", @resource[:name], k, ''] + end + end + end + end end diff --git a/spec/unit/provider/kdbkey_kdb_spec.rb b/spec/unit/provider/kdbkey_kdb_spec.rb index 08931ba..f47873d 100644 --- a/spec/unit/provider/kdbkey_kdb_spec.rb +++ b/spec/unit/provider/kdbkey_kdb_spec.rb @@ -68,6 +68,10 @@ def ensure_comment_exists(keyname, comment = "test") key = Kdb::Key.new keyname ks << key end + # delete old comment first + key.meta.each do |e| + key.del_meta e if e.name.start_with? COMMENT + end lines = comment.split "\n" key[COMMENT] = "##{lines.size}" lines.each_with_index do |line, index| @@ -86,7 +90,7 @@ def ensure_key_is_missing(keyname) def ensure_meta_is_missing(keyname, meta) do_on_kdb_with_key keyname do |key| - key.del_meta meta unless key.nil? + key.del_meta meta unless key.nil? end end @@ -147,16 +151,16 @@ def key_get_comment(keyname) key.meta.find_all do |e| e.name.start_with? COMMENT+"/#" end.each do |c| - comment = "" if comment.nil? + comment = [] if comment.nil? if c.value.start_with? "#" - comment += c.value[1..-1] + comment << c.value[1..-1] else - comment += c.value + comment << c.value end end end end - return comment + return comment.join "\n" end @@ -203,7 +207,7 @@ def key_get_comment(keyname) it "with defined name and value" do expect(check_key_exists keyname).to eq(false) - provider.resource = create_resource(:name => keyname, + provider.resource = create_resource(:name => keyname, :value => "create with value") provider.create expect(check_key_exists keyname).to eq(true) @@ -384,6 +388,61 @@ def key_get_comment(keyname) #expect(check_meta_exists keyname, "r1").to eq false expect(key_get_meta keyname, "r1").to eq "" end + end + + context "should handle comments" do + it "and fetch the comment string" do + ensure_comment_exists keyname, "some comments" + + comments = provider.comments + + expect(comments).to be_a_kind_of String + expect(comments).to eq "some comments" + end + + it "and fetch a multiline the comment string at once" do + expected_comment = < Date: Sat, 25 Feb 2017 00:29:59 +0100 Subject: [PATCH 19/52] kdbkey provider: implement unit test --- lib/puppet/provider/kdbkey/ruby.rb | 6 +- spec/unit/provider/kdbkey_kdb_spec.rb | 313 +++++++------------------ spec/unit/provider/kdbkey_ruby_spec.rb | 273 +++++++++++++++++---- spec/unit/provider/key_helper.rb | 19 ++ spec/unit/provider/key_kdb_helper.rb | 116 +++++++++ spec/unit/provider/key_ruby_helper.rb | 124 ++++++++++ 6 files changed, 571 insertions(+), 280 deletions(-) create mode 100644 spec/unit/provider/key_helper.rb create mode 100644 spec/unit/provider/key_kdb_helper.rb create mode 100644 spec/unit/provider/key_ruby_helper.rb diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index b23072c..d53bf06 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -98,6 +98,10 @@ def metadata=(value) # do we have to purge all unspecified keys? if @resource.purge_meta_keys? @resource_key.meta.each do |metakey| + next if metakey.name == "order" + next if metakey.name.start_with? "internal/" + next if /^comments?\// =~ metakey.name + next if metakey.name == "comments" @resource_key.del_meta metakey.name unless value.include? metakey.name end end @@ -130,7 +134,7 @@ def comments=(value) # update all comment lines comment_lines.each_with_index do |line, index| - @resource_key.set_meta "comments/##{index}", "# #{line}" + @resource_key.set_meta "comments/##{index}", "##{line}" end # iterate over all meta keys and remove all comment keys which diff --git a/spec/unit/provider/kdbkey_kdb_spec.rb b/spec/unit/provider/kdbkey_kdb_spec.rb index f47873d..0d1666c 100644 --- a/spec/unit/provider/kdbkey_kdb_spec.rb +++ b/spec/unit/provider/kdbkey_kdb_spec.rb @@ -8,169 +8,18 @@ # require 'spec_helper' +require_relative 'key_helper.rb' +require_relative 'key_kdb_helper.rb' require 'kdb' -TEST_NS = 'user/test/puppet-rspec/' -# currently metadaa key for comments used by ini -# but hosts uses 'comment' -COMMENT = 'comments' - - -def create_resource(params) - Puppet::Type.type(:kdbkey).new(params) -end - -def do_on_kdb - raise ArgumentError, "block required" unless block_given? - - Kdb.open do |kdb| - ks = Kdb::KeySet.new - kdb.get ks, TEST_NS - yield ks - kdb.set ks, TEST_NS - end -end - -def do_on_kdb_with_key(keyname) - raise ArgumentError, "block required" unless block_given? - do_on_kdb do |ks| - yield ks.lookup(keyname) - end -end - - -def ensure_key_exists(keyname, value = "test") - do_on_kdb do |ks| - key = ks.lookup(keyname) - if key.nil? - ks << Kdb::Key.new(keyname, value: value) - else - key.value = value - end - end -end - -def ensure_meta_exists(keyname, meta, value = "test") - do_on_kdb do |ks| - key = ks.lookup keyname - if key.nil? - key = Kdb::Key.new(keyname) - ks << key - end - key.set_meta meta, value - end -end - -def ensure_comment_exists(keyname, comment = "test") - do_on_kdb do |ks| - key = ks.lookup keyname - if key.nil? - key = Kdb::Key.new keyname - ks << key - end - # delete old comment first - key.meta.each do |e| - key.del_meta e if e.name.start_with? COMMENT - end - lines = comment.split "\n" - key[COMMENT] = "##{lines.size}" - lines.each_with_index do |line, index| - key[COMMENT+"/##{index}"] = line - end - end -end - -def ensure_key_is_missing(keyname) - do_on_kdb do |ks| - unless ks.lookup(keyname).nil? - ks.delete keyname - end - end -end - -def ensure_meta_is_missing(keyname, meta) - do_on_kdb_with_key keyname do |key| - key.del_meta meta unless key.nil? - end -end - -def ensure_comment_is_missing(keyname) - do_on_kdb_with_key keyname do |key| - unless key.nil? - key.meta.each do |m| - key.del_meta m if m.name.start_with? COMMENT - end - end - end -end - -def check_key_exists(name) - missing = false - do_on_kdb do |ks| - missing = ks.lookup(name).nil? - end - return !missing -end - -def check_meta_exists(keyname, meta) - exists = false - do_on_kdb_with_key keyname do |key| - exists = key.has_meta? meta unless key.nil? - end - return exists -end - -def check_comment_exists(keyname) - exists = false - do_on_kdb_with_key keyname do |key| - exists = key.has_meta?(COMMENT) or key.has_meta?(COMMENT+"/#0") unless key.nil? - end - return exists -end - -def key_get_value(keyname) - value = nil - do_on_kdb_with_key keyname do |key| - value = key.value unless key.nil? - end - return value -end - -def key_get_meta(keyname, meta) - value = nil - do_on_kdb_with_key keyname do |key| - value = key[meta] unless key.nil? - end - return value -end - -def key_get_comment(keyname) - comment = nil - do_on_kdb_with_key keyname do |key| - unless key.nil? - key.meta.find_all do |e| - e.name.start_with? COMMENT+"/#" - end.each do |c| - comment = [] if comment.nil? - if c.value.start_with? "#" - comment << c.value[1..-1] - else - comment << c.value - end - end - end - end - return comment.join "\n" -end - - - +TEST_NS = 'user/test/puppet-rspec/' describe Puppet::Type.type(:kdbkey).provider(:kdb) do + let(:h) { KdbKeyProviderHelperKDB.new } let(:provider) { described_class.new } let(:keyname) { "#{TEST_NS}x1" } before :example do @@ -184,85 +33,85 @@ def key_get_comment(keyname) context "should check if a key exists" do it "should return false on exists? if key is missing" do - ensure_key_is_missing keyname + h.ensure_key_is_missing keyname expect(provider.exists?).to eq(false) end it "should return true on exists? if key exists" do - ensure_key_exists keyname + h.ensure_key_exists keyname expect(provider.exists?).to eq(true) end end context "should create key" do before :example do - ensure_key_is_missing keyname + h.ensure_key_is_missing keyname end it "with defined name and no value" do provider.create - expect(check_key_exists keyname).to eq(true) - expect(key_get_value keyname).to eq("") + expect(h.check_key_exists keyname).to eq(true) + expect(h.key_get_value keyname).to eq("") end it "with defined name and value" do - expect(check_key_exists keyname).to eq(false) + expect(h.check_key_exists keyname).to eq(false) provider.resource = create_resource(:name => keyname, :value => "create with value") provider.create - expect(check_key_exists keyname).to eq(true) - expect(key_get_value keyname).to eq("create with value") + expect(h.check_key_exists keyname).to eq(true) + expect(h.key_get_value keyname).to eq("create with value") end it "with defined name, value and metadata" do provider.resource[:value] = "my val" provider.resource[:metadata] = {"m1" => "v1", "m2" => "v2"} provider.create - expect(check_key_exists keyname).to eq true - expect(key_get_value keyname).to eq "my val" - expect(check_meta_exists keyname, "m1").to eq true - expect(key_get_meta keyname, "m1").to eq "v1" - expect(check_meta_exists keyname, "m2").to eq true - expect(key_get_meta keyname, "m2").to eq "v2" + expect(h.check_key_exists keyname).to eq true + expect(h.key_get_value keyname).to eq "my val" + expect(h.check_meta_exists keyname, "m1").to eq true + expect(h.key_get_meta keyname, "m1").to eq "v1" + expect(h.check_meta_exists keyname, "m2").to eq true + expect(h.key_get_meta keyname, "m2").to eq "v2" end end context "should update the key value" do before :example do - ensure_key_exists keyname, "test" + h.ensure_key_exists keyname, "test" end it "to an arbitrary string" do provider.resource[:value] = "some string value" - expect(key_get_value keyname).to eq("test") + expect(h.key_get_value keyname).to eq("test") provider.value= "some string value" - expect(key_get_value keyname).to eq("some string value") + expect(h.key_get_value keyname).to eq("some string value") end it "to an empty string" do - expect(key_get_value keyname).to eq("test") + expect(h.key_get_value keyname).to eq("test") provider.value= "" - expect(key_get_value keyname).to eq("") + expect(h.key_get_value keyname).to eq("") end end context "should remove key" do before :example do - ensure_key_exists keyname + h.ensure_key_exists keyname end it "when key exists" do - expect(check_key_exists keyname).to eq true + expect(h.check_key_exists keyname).to eq true provider.destroy - expect(check_key_exists keyname).to eq false + expect(h.check_key_exists keyname).to eq false end end context "should get metadata values" do before :example do - ensure_meta_exists keyname, "m1", "test" - ensure_meta_exists keyname, "m2", "test" + h.ensure_meta_exists keyname, "m1", "test" + h.ensure_meta_exists keyname, "m2", "test" end it "as a hash" do @@ -276,33 +125,33 @@ def key_get_comment(keyname) context "should update metadata" do let(:metadata) { {"m1" => "v1", "m2" => "v2"} } before :example do - ensure_key_exists keyname + h.ensure_key_exists keyname end it "with missing metadata key" do - ensure_meta_is_missing keyname, "m1" - ensure_meta_is_missing keyname, "m2" + h.ensure_meta_is_missing keyname, "m1" + h.ensure_meta_is_missing keyname, "m2" provider.resource[:metadata] = metadata provider.metadata= metadata - expect(check_meta_exists keyname, "m1").to eq true - expect(check_meta_exists keyname, "m2").to eq true - expect(key_get_meta keyname, "m1").to eq "v1" - expect(key_get_meta keyname, "m2").to eq "v2" + expect(h.check_meta_exists keyname, "m1").to eq true + expect(h.check_meta_exists keyname, "m2").to eq true + expect(h.key_get_meta keyname, "m1").to eq "v1" + expect(h.key_get_meta keyname, "m2").to eq "v2" end it "with existing metadata" do - ensure_meta_exists keyname, "m1", "test" - ensure_meta_exists keyname, "m2", "test" + h.ensure_meta_exists keyname, "m1", "test" + h.ensure_meta_exists keyname, "m2", "test" provider.resource[:metadata] = metadata provider.metadata= metadata - expect(check_meta_exists keyname, "m1").to eq true - expect(check_meta_exists keyname, "m2").to eq true - expect(key_get_meta keyname, "m1").to eq "v1" - expect(key_get_meta keyname, "m2").to eq "v2" + expect(h.check_meta_exists keyname, "m1").to eq true + expect(h.check_meta_exists keyname, "m2").to eq true + expect(h.key_get_meta keyname, "m1").to eq "v1" + expect(h.key_get_meta keyname, "m2").to eq "v2" end end @@ -311,58 +160,58 @@ def key_get_comment(keyname) before :example do provider.resource[:purge_meta_keys] = true - ensure_meta_is_missing keyname, "m1" - ensure_meta_exists keyname, "m2" - ensure_meta_exists keyname, "r1", "to remove" - ensure_meta_exists keyname, "r2", "to remove" + h.ensure_meta_is_missing keyname, "m1" + h.ensure_meta_exists keyname, "m2" + h.ensure_meta_exists keyname, "r1", "to remove" + h.ensure_meta_exists keyname, "r2", "to remove" end it "while updating specified" do provider.metadata provider.metadata = metadata - expect(check_meta_exists keyname, "m1").to eq true - expect(check_meta_exists keyname, "m2").to eq true - expect(key_get_meta keyname, "m1").to eq "v1" - expect(key_get_meta keyname, "m2").to eq "v2" + expect(h.check_meta_exists keyname, "m1").to eq true + expect(h.check_meta_exists keyname, "m2").to eq true + expect(h.key_get_meta keyname, "m1").to eq "v1" + expect(h.key_get_meta keyname, "m2").to eq "v2" - #expect(check_meta_exists keyname, "r1").to eq false - #expect(check_meta_exists keyname, "r2").to eq false - expect(key_get_meta keyname, "r1").to eq "" - expect(key_get_meta keyname, "r2").to eq "" + #expect(h.check_meta_exists keyname, "r1").to eq false + #expect(h.check_meta_exists keyname, "r2").to eq false + expect(h.key_get_meta keyname, "r1").to eq "" + expect(h.key_get_meta keyname, "r2").to eq "" end # we can't test is this way, as we can not set 'internal' metadata # values #it "while ignoring meta keys starting with 'internal/'" do - # ensure_meta_exists keyname, "internal/puppet/test", "keep it" + # h.ensure_meta_exists keyname, "internal/puppet/test", "keep it" # puts provider.metadata # provider.metadata = metadata - # expect(check_meta_exists keyname, "m1").to eq true - # expect(check_meta_exists keyname, "m2").to eq true + # expect(h.check_meta_exists keyname, "m1").to eq true + # expect(h.check_meta_exists keyname, "m2").to eq true - # expect(check_meta_exists keyname, "internal/puppet/test").to eq true - # expect(key_get_meta keyname, "internal/puppet/test").to eq "keep it" + # expect(h.check_meta_exists keyname, "internal/puppet/test").to eq true + # expect(h.key_get_meta keyname, "internal/puppet/test").to eq "keep it" - # #expect(check_meta_exists keyname, "r1").to eq false - # #expect(check_meta_exists keyname, "r2").to eq false - # expect(key_get_meta keyname, "r1").to eq "" - # expect(key_get_meta keyname, "r2").to eq "" + # #expect(h.check_meta_exists keyname, "r1").to eq false + # #expect(h.check_meta_exists keyname, "r2").to eq false + # expect(h.key_get_meta keyname, "r1").to eq "" + # expect(h.key_get_meta keyname, "r2").to eq "" #end it "while ignoring comments, which are not modified" do - ensure_comment_exists keyname, " some comments" + h.ensure_comment_exists keyname, " some comments" provider.metadata provider.metadata = metadata #expect(check_meta_exists keyname, COMMENT+"/#0").to eq true - expect(check_comment_exists keyname).to eq true + expect(h.check_comment_exists keyname).to eq true - #expect(key_get_meta keyname, COMMENT+"/#0").to eq "# some comments" - expect(key_get_comment keyname).to eq " some comments" + #expect(h.key_get_meta keyname, COMMENT+"/#0").to eq "# some comments" + expect(h.key_get_comment keyname).to eq " some comments" end it "while ignoring commens which are added too" do @@ -372,27 +221,27 @@ def key_get_comment(keyname) provider.metadata provider.metadata = metadata - expect(check_comment_exists keyname).to eq true - #expect(check_meta_exists keyname, "r1").to eq false - expect(key_get_meta keyname, "r1").to eq "" + expect(h.check_comment_exists keyname).to eq true + #expect(h.check_meta_exists keyname, "r1").to eq false + expect(h.key_get_meta keyname, "r1").to eq "" end it "while ignoring 'order' metadata" do - ensure_meta_exists keyname, "order", "5" + h.ensure_meta_exists keyname, "order", "5" provider.metadata provider.metadata = metadata - expect(check_meta_exists keyname, "order").to eq true - expect(key_get_meta keyname, "order").to eq "5" - #expect(check_meta_exists keyname, "r1").to eq false - expect(key_get_meta keyname, "r1").to eq "" + expect(h.check_meta_exists keyname, "order").to eq true + expect(h.key_get_meta keyname, "order").to eq "5" + #expect(h.check_meta_exists keyname, "r1").to eq false + expect(h.key_get_meta keyname, "r1").to eq "" end end context "should handle comments" do it "and fetch the comment string" do - ensure_comment_exists keyname, "some comments" + h.ensure_comment_exists keyname, "some comments" comments = provider.comments @@ -408,7 +257,7 @@ def key_get_comment(keyname) EOT expected_comment.chomp! - ensure_comment_exists keyname, expected_comment + h.ensure_comment_exists keyname, expected_comment comments = provider.comments @@ -416,21 +265,21 @@ def key_get_comment(keyname) end it "and create a new comment" do - ensure_comment_is_missing keyname + h.ensure_comment_is_missing keyname provider.comments= "a new comment" - expect(check_comment_exists keyname).to eq true + expect(h.check_comment_exists keyname).to eq true # for now, as we cannot remove metakeys, we have to live with # empty comment lines - actual_comment = key_get_comment(keyname).gsub(/\n*$/, '') + actual_comment = h.key_get_comment(keyname).gsub(/\n*$/, '') expect(actual_comment).to eq "a new comment" end it "and update a multi line comment" do - ensure_comment_exists keyname + h.ensure_comment_exists keyname expected_comment = < name + provider.resource = create_resource :name => keyname end @@ -36,63 +35,49 @@ def create_resource(params) context "should check if resource exists" do it "should return false on exists? if resource does not exist'" do - allow(ks).to receive(:lookup).and_return nil - - expect(ks).to receive(:lookup) { name } - # for some very strange reason the first call inside expect(..) is true - provider.exists? expect(provider.exists?).to eq(false) - expect(provider.resource_key).to be_nil end it "should return true on exists? if resource exists'" do - key = Kdb::Key.new name - allow(ks).to receive(:lookup).and_return key - - expect(ks).to receive(:lookup) { name } - provider.exists? + h.ensure_key_exists ks, keyname expect(provider.exists?).to eq(true) - expect(provider.resource_key).to be(key) end end context "should create key" do before :example do - allow(ks).to receive(:<<) - expect(ks).to receive(:<<) { provider.resource_key } + h.ensure_key_is_missing ks, keyname end it "with defined name" do provider.create - key = provider.resource_key - - expect(key.name).to eq(name) + expect(h.check_key_exists ks, keyname).to eq true end it "with defined name and value" do value = "my value" - provider.resource = create_resource :name => name, :value => value + provider.resource = create_resource :name => keyname, :value => value provider.create - expect(provider.resource_key.name).to eq(name) - expect(provider.resource_key.value).to eq(value) + expect(h.check_key_exists ks, keyname).to eq true + expect(h.key_get_value ks, keyname).to eq value end it "with defined name, value and metadata" do value = "my value" meta = {'meta1' => 'v1', 'meta2' => 'v2' } - provider.resource = create_resource :name => name, + provider.resource = create_resource :name => keyname, :value => value, :metadata => meta provider.create - expect(provider.resource_key.name).to eq(name) - expect(provider.resource_key.value).to eq(value) + expect(h.check_key_exists ks, keyname).to eq true + expect(h.key_get_value ks, keyname).to eq value meta.each do |k, v| - expect(provider.resource_key.get_meta k).to eq(v) + expect(h.key_get_meta ks, keyname, k).to eq v end end @@ -101,39 +86,233 @@ def create_resource(params) meta = {'meta1' => 'v1', 'meta2' => 'v2' } comments = "my comment" - provider.resource = create_resource :name => name, + provider.resource = create_resource :name => keyname, :value => value, :metadata => meta, :comments => comments provider.create - expect(provider.resource_key.name).to eq(name) - expect(provider.resource_key.value).to eq(value) + expect(h.check_key_exists ks, keyname).to eq true + expect(h.key_get_value ks, keyname).to eq value meta.each do |k, v| - expect(provider.resource_key.get_meta k).to eq(v) + expect(h.key_get_meta ks, keyname, k).to eq(v) end - expect(provider.resource_key['comments']).to eq("#0") - expect(provider.resource_key['comments/#0']).to eq("# #{comments}") + expect(h.key_get_comment ks, keyname).to eq comments end end - it "should do nothing on destroy when resource_key is nil" do + it "should remove key on destroy" do + h.ensure_key_exists ks, keyname + # we have to call exists? first + provider.exists? provider.destroy + + expect(h.check_key_exists ks, keyname).to eq false end - it "should remove key when we have a key" do - # first create the key, a delete on nil-key does not make sense - allow(ks).to receive(:<<) - provider.create + context "with existing key" do + before :example do + h.ensure_key_exists ks, keyname, "test" + provider.exists? + end + + context "should update the key value" do + it "to an arbitrary string" do + expect(h.key_get_value ks, keyname).to eq "test" + provider.value= "some string value" + expect(h.key_get_value ks, keyname).to eq "some string value" + end - expect(ks).to receive(:delete) { nil } - provider.destroy + it "to an empty string" do + expect(h.key_get_value ks, keyname).to eq "test" + provider.value= "" + expect(h.key_get_value ks, keyname).to eq "" + end + end + + context "and existing metadata" do + let(:metadata) { {"m1" => "v1", "m2" => "v2"} } + before :example do + h.ensure_meta_exists ks, keyname, "m1", metadata["m1"] + h.ensure_meta_exists ks, keyname, "m2", metadata["m2"] + provider.resource[:metadata]= metadata + end + + context "should get metadata values" do + it "as a hash" do + got_meta = provider.metadata + + expect(got_meta).to be_a_kind_of Hash + expect(got_meta["m1"]).to eq "v1" + expect(got_meta["m2"]).to eq "v2" + end + end + + context "should update the metadata" do + it "with missing metadata key" do + metadata["m3"] = "v3" + provider.resource[:metadata]= metadata + provider.metadata= metadata + + got_meta = provider.metadata + + expect(got_meta.include? "m3").to eq true + expect(got_meta["m3"]).to eq "v3" + end + + it "with existing metadata" do + got_meta = provider.metadata + + expect(got_meta.include? "m1").to eq true + expect(got_meta.include? "m2").to eq true + expect(got_meta["m1"]).to eq "v1" + expect(got_meta["m2"]).to eq "v2" + end + end + + context "should purge not specified metadata if 'purge_meta_keys' is set" do + before :example do + h.ensure_meta_exists ks, keyname, "r1", "to remove" + h.ensure_meta_exists ks, keyname, "r2", "to remove" + provider.resource[:purge_meta_keys] = true + end + + def has_expected_but_not_specified(got_meta) + expect(got_meta.include? "m1").to eq true + expect(got_meta.include? "m2").to eq true + expect(got_meta.include? "r1").to eq false + expect(got_meta.include? "r2").to eq false + + expect(got_meta["m1"]).to eq "v1" + expect(got_meta["m2"]).to eq "v2" + end + + it "while updating specified" do + h.ensure_meta_exists ks, keyname, "m1", "old value" + provider.metadata= metadata + + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + end + + it "while ignoring comments, which are not modified" do + h.ensure_comment_exists ks, keyname, "some comment" + + provider.metadata= metadata + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_comment_exists ks, keyname).to eq true + expect(h.key_get_comment ks, keyname).to eq "some comment" + end + + it "while ignoring comments, which are added too (before)" do + provider.comments= "some comment" + provider.metadata= metadata + + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_comment_exists ks, keyname).to eq true + expect(h.key_get_comment ks, keyname).to eq "some comment" + end + + it "while ignoring comments, which are added too (after)" do + provider.metadata= metadata + provider.comments= "some comment" + + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_comment_exists ks, keyname).to eq true + expect(h.key_get_comment ks, keyname).to eq "some comment" + end + + it "while ignoring 'internal/' metadata keys" do + h.ensure_meta_exists ks, keyname, "internal/test1", "to keep" + + provider.metadata= metadata + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_meta_exists ks, keyname, "internal/test1").to eq true + expect(h.key_get_meta ks, keyname, "internal/test1").to eq "to keep" + end + + it "while ignoring 'order' metadata" do + h.ensure_meta_exists ks, keyname, "order", "5" + + provider.metadata= metadata + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_meta_exists ks, keyname, "order").to eq true + expect(h.key_get_meta ks, keyname, "order").to eq "5" + end + end + end + + context "should handle comments" do + it "and fetch the comment string" do + h.ensure_comment_exists ks, keyname, "my comment" + + expect(provider.comments).to eq "my comment" + end + + it "and fetch a multiline comment string at once" do + expected_comment = < Date: Sun, 26 Feb 2017 15:24:15 +0100 Subject: [PATCH 20/52] kdbmount type: add additional unit tests --- lib/puppet/provider/kdbmount/ruby.rb | 8 ++++- lib/puppet/type/kdbmount.rb | 25 +++++++++---- spec/unit/type/kdbkey_spec.rb | 48 ++++++++++++------------- spec/unit/type/kdbmount_spec.rb | 52 +++++++++++++++------------- 4 files changed, 78 insertions(+), 55 deletions(-) diff --git a/lib/puppet/provider/kdbmount/ruby.rb b/lib/puppet/provider/kdbmount/ruby.rb index ec21ed3..aa6b109 100644 --- a/lib/puppet/provider/kdbmount/ruby.rb +++ b/lib/puppet/provider/kdbmount/ruby.rb @@ -185,6 +185,7 @@ def self.get_mountoint_plugin_config(backend) end end end + # convert the Hash to an array Puppet gives us plugins = plugins.to_a.flatten.reject {|e| e.empty? } #puts "#{plugins}" plugins @@ -194,6 +195,11 @@ def self.get_mountoint_plugin_config(backend) # convert the Puppet given :plugins value to a more suitable # hash: # pluginname => plugin config settings + # + # Puppet will give us an array of values, combining plugin names and + # config settings. e.g. + # ["ini", {"delimiter" => " ", "setting2" => "aa"}, "type"] + # # e.g: # ini => { # delimiter => " " @@ -228,7 +234,7 @@ def perform_kdb_action path, &block mountconf = Kdb::KeySet.new kdb.get mountconf, path - yield mountconf + yield mountconf # write new mount config kdb.set mountconf, path diff --git a/lib/puppet/type/kdbmount.rb b/lib/puppet/type/kdbmount.rb index 8a58c1a..7fe7aad 100644 --- a/lib/puppet/type/kdbmount.rb +++ b/lib/puppet/type/kdbmount.rb @@ -64,7 +64,7 @@ defaultto "resolver" validate do |value| - unless /^\w+$/ =~ value + unless @resource.class.plugin_name_is_valid? value raise ArgumentError, "'%s' is not a valid plugin name" % value end end @@ -106,14 +106,23 @@ EOT - #munge do |value| - # puts "resolver: is nil" if resource[:resolver].nil? - # value << resource[:resolver] - # value + RECOMMENDED_PLUGINS if resource[:add_recommended_plugins] + validate do |value| + if value.is_a? String + unless @resource.class.plugin_name_is_valid? value + raise ArgumentError, "'%s' is not a vlid plugin name" % value + end + end + end + # this can't be done here, since we get each value at once for + # munge, thus one munge call for each array entry. + #munge do |plugins| + # # convert plugins array to a hash #end # TODO implement this to allow better plugins handling - #def insync? + #def insync?(value) + # puts "insync? #{value}" + # false #end end @@ -124,4 +133,8 @@ def exists? @provider.get(:ensure) != :absent end + def self.plugin_name_is_valid?(name) + /^\w+$/ =~ name + end + end diff --git a/spec/unit/type/kdbkey_spec.rb b/spec/unit/type/kdbkey_spec.rb index 628f864..af18c7c 100644 --- a/spec/unit/type/kdbkey_spec.rb +++ b/spec/unit/type/kdbkey_spec.rb @@ -19,7 +19,7 @@ expect { described_class.new() }.to raise_error(ArgumentError) end - RSpec.shared_examples "valid key names" do |name| + RSpec.shared_examples "kdbkey valid key names" do |name| it "accepts the key name '#{name}'" do expect(described_class.new(:name => name)[:name]).to eq(name) end @@ -27,16 +27,16 @@ context "accepts valid libelektra key names" do # cascading key name - include_examples "valid key names", "/test/puppet" + include_examples "kdbkey valid key names", "/test/puppet" # absolute, by namespace key names - include_examples "valid key names", "spec/test/puppet" - include_examples "valid key names", "proc/test/puppet" - include_examples "valid key names", "dir/test/puppet" - include_examples "valid key names", "user/test/puppet" - include_examples "valid key names", "system/test/puppet" + include_examples "kdbkey valid key names", "spec/test/puppet" + include_examples "kdbkey valid key names", "proc/test/puppet" + include_examples "kdbkey valid key names", "dir/test/puppet" + include_examples "kdbkey valid key names", "user/test/puppet" + include_examples "kdbkey valid key names", "system/test/puppet" end - RSpec.shared_examples "invalid key names" do |name| + RSpec.shared_examples "kdbkey invalid key names" do |name| it "rejects the invalid key name '#{name}'" do expect { described_class.new(:name => name) @@ -45,10 +45,10 @@ end context "rejects invalid libelektra key names" do - include_examples "invalid key names", "" - include_examples "invalid key names", "hello/world" - include_examples "invalid key names", "invalid-name-space" - include_examples "invalid key names", "test/xy" + include_examples "kdbkey invalid key names", "" + include_examples "kdbkey invalid key names", "hello/world" + include_examples "kdbkey invalid key names", "invalid-name-space" + include_examples "kdbkey invalid key names", "test/xy" end end @@ -62,7 +62,7 @@ expect(described_class.new(:name => name)[:prefix]).to eq("") end - RSpec.shared_examples "valid key prefix" do |prefix| + RSpec.shared_examples "kdbkey valid key prefix" do |prefix| it "accepts the key prefix '#{prefix}'" do expect(described_class.new(:name => "/test", :prefix => prefix)[:prefix] @@ -72,18 +72,18 @@ context "accepts valid libelektra key name prefix" do # cascading key name - include_examples "valid key prefix", "/test/puppet" + include_examples "kdbkey valid key prefix", "/test/puppet" # absolute, by namespace key names - include_examples "valid key prefix", "spec/test/puppet" - include_examples "valid key prefix", "proc/test/puppet" - include_examples "valid key prefix", "dir/test/puppet" - include_examples "valid key prefix", "user/test/puppet" - include_examples "valid key prefix", "system/test/puppet" + include_examples "kdbkey valid key prefix", "spec/test/puppet" + include_examples "kdbkey valid key prefix", "proc/test/puppet" + include_examples "kdbkey valid key prefix", "dir/test/puppet" + include_examples "kdbkey valid key prefix", "user/test/puppet" + include_examples "kdbkey valid key prefix", "system/test/puppet" # without trailing / - include_examples "valid key prefix", "system" + include_examples "kdbkey valid key prefix", "system" end - RSpec.shared_examples "invalid key prefix" do |prefix| + RSpec.shared_examples "kdbkey invalid key prefix" do |prefix| it "rejects the invalid key prefix '#{prefix}'" do expect { described_class.new(:name => "/test", @@ -93,9 +93,9 @@ end context "rejects invalid libelektra key names" do - include_examples "invalid key prefix", "hello/world" - include_examples "invalid key prefix", "invalid-name-space" - include_examples "invalid key prefix", "test/xy" + include_examples "kdbkey invalid key prefix", "hello/world" + include_examples "kdbkey invalid key prefix", "invalid-name-space" + include_examples "kdbkey invalid key prefix", "test/xy" end RSpec.shared_examples "prefix + name" do |prefix, name, expected| diff --git a/spec/unit/type/kdbmount_spec.rb b/spec/unit/type/kdbmount_spec.rb index e8a1071..d198ba0 100644 --- a/spec/unit/type/kdbmount_spec.rb +++ b/spec/unit/type/kdbmount_spec.rb @@ -72,39 +72,43 @@ context "property 'plugins'" do let(:params) { { - :name => "user/test/puppet", - :plugins => { 'plugin1' => 'sync' } + :name => "user/test/puppet" } } - it "exists" do - expect(described_class.new(params)[:plugins]).to eq(["sync"]) + it "exists and is optionsl" do + expect(described_class.new(params)[:plugins]).to be_nil end - let(:params) { { - :name => "user/test/puppet" - } - } - it "is optional" do - expect(described_class.new(params)[:plugins]).to be_nil + it "accepts a string" do + params[:plugins] = "ini" + expect(described_class.new(params)[:plugins]).to eq ["ini"] end - #it "only accepts hash values" do - # p1 = params - # p1[:metadata] = "" - # expect { described_class.new(p1) }.to raise_error(Puppet::ResourceError) + it "accepts an array of strings" do + params[:plugins] = ["ini", "type"] + expect(described_class.new(params)[:plugins]).to eq ["ini", "type"] + end - # p1[:metadata] = "not a hash" - # expect { described_class.new(p1) }.to raise_error(Puppet::ResourceError) + it "accepts a plugin name with corresponding configuration settings" do + params[:plugins] = ["ini", {"seperator" => " ", "array" => ""}] + expect(described_class.new(params)[:plugins]).to eq params[:plugins] + end - # p1[:metadata] = 1 - # expect { described_class.new(p1) }.to raise_error(Puppet::ResourceError) + RSpec.shared_examples "invalid plugin names" do |plugins| + it "rejects invalid plugin names '#{plugins}'" do + expect { + described_class.new(:name => params[:name], :plugins => plugins) + }.to raise_error(Puppet::ResourceError) + end + end - # p1[:metadata] = { - # 'meta1' => 'value 1', - # 'meta2' => 'value 2' - # } - # expect(described_class.new(p1)[:metadata]).to eq(p1[:metadata]) - #end + context "rejects invalid plugin names" do + include_examples "invalid plugin names", "invalid plugin" + include_examples "invalid plugin names", " ini" + include_examples "invalid plugin names", [" ini"] + include_examples "invalid plugin names", ["ini", "type "] + include_examples "invalid plugin names", ["ini", "type", "$doesnotexist%"] + end end end From e1a674f430dc50db4b4d9533943c0dc81c7fd72b Mon Sep 17 00:00:00 2001 From: bernhard Date: Sun, 26 Feb 2017 15:39:14 +0100 Subject: [PATCH 21/52] test cases: do not use global variable TEST_NS --- spec/unit/provider/kdbkey_kdb_spec.rb | 8 +- spec/unit/provider/kdbkey_ruby_spec.rb | 7 +- spec/unit/provider/kdbmount_kdb_spec.rb | 123 +----------------------- spec/unit/provider/key_kdb_helper.rb | 7 +- spec/unit/provider/key_ruby_helper.rb | 6 ++ 5 files changed, 16 insertions(+), 135 deletions(-) diff --git a/spec/unit/provider/kdbkey_kdb_spec.rb b/spec/unit/provider/kdbkey_kdb_spec.rb index 0d1666c..6981b7e 100644 --- a/spec/unit/provider/kdbkey_kdb_spec.rb +++ b/spec/unit/provider/kdbkey_kdb_spec.rb @@ -13,15 +13,11 @@ require 'kdb' -TEST_NS = 'user/test/puppet-rspec/' - - - describe Puppet::Type.type(:kdbkey).provider(:kdb) do - let(:h) { KdbKeyProviderHelperKDB.new } + let(:h) { KdbKeyProviderHelperKDB.new 'user/test/puppet-rspec/' } let(:provider) { described_class.new } - let(:keyname) { "#{TEST_NS}x1" } + let(:keyname) { "#{h.test_prefix}x1" } before :example do provider.resource = create_resource :name => keyname end diff --git a/spec/unit/provider/kdbkey_ruby_spec.rb b/spec/unit/provider/kdbkey_ruby_spec.rb index 0aaacfa..9fc0b99 100644 --- a/spec/unit/provider/kdbkey_ruby_spec.rb +++ b/spec/unit/provider/kdbkey_ruby_spec.rb @@ -12,14 +12,11 @@ require_relative 'key_ruby_helper.rb' require 'kdb' -TEST_NS = 'user/test/puppet-rspec/' - - describe Puppet::Type.type(:kdbkey).provider(:ruby) do - let(:h) { KdbKeyProviderHelper.new } - let(:keyname) { "#{TEST_NS}x1" } + let(:h) { KdbKeyProviderHelper.new 'user/test/puppet-rspec/'} + let(:keyname) { "#{h.test_prefix}x1" } let(:ks) { Kdb::KeySet.new } let(:provider) { described_class.new } diff --git a/spec/unit/provider/kdbmount_kdb_spec.rb b/spec/unit/provider/kdbmount_kdb_spec.rb index 2879e88..190fc6a 100644 --- a/spec/unit/provider/kdbmount_kdb_spec.rb +++ b/spec/unit/provider/kdbmount_kdb_spec.rb @@ -10,130 +10,9 @@ require 'spec_helper' require 'kdb' -TEST_NS = 'user/test/puppet-rspec/' -def create_resource(params) - Puppet::Type.type(:kdbkey).new(params) -end - - - -describe Puppet::Type.type(:kdbkey).provider(:ruby) do - - let(:name) { "#{TEST_NS}x1" } - let(:ks) { double("ks") } - let(:provider) { described_class.new } - - before :example do - described_class.use_fake_ks ks - provider.resource = create_resource :name => name - end - - - it "should be a child of Puppet::Provider" do - expect(described_class.new).to be_a_kind_of(Puppet::Provider) - end - - context "should check if resource exists" do - it "should return false on exists? if resource does not exist'" do - allow(ks).to receive(:lookup).and_return nil - - expect(ks).to receive(:lookup) { name } - # for some very strange reason the first call inside expect(..) is true - provider.exists? - expect(provider.exists?).to eq(false) - expect(provider.resource_key).to be_nil - end - - it "should return true on exists? if resource exists'" do - key = Kdb::Key.new name - allow(ks).to receive(:lookup).and_return key - - expect(ks).to receive(:lookup) { name } - provider.exists? - expect(provider.exists?).to eq(true) - expect(provider.resource_key).to be(key) - end - - end - - context "should create key" do - before :example do - allow(ks).to receive(:<<) - expect(ks).to receive(:<<) { provider.resource_key } - end - - it "with defined name" do - provider.create - key = provider.resource_key - - expect(key.name).to eq(name) - end - - it "with defined name and value" do - value = "my value" - provider.resource = create_resource :name => name, :value => value - - provider.create - - expect(provider.resource_key.name).to eq(name) - expect(provider.resource_key.value).to eq(value) - end - - it "with defined name, value and metadata" do - value = "my value" - meta = {'meta1' => 'v1', 'meta2' => 'v2' } - provider.resource = create_resource :name => name, - :value => value, - :metadata => meta - - provider.create - - expect(provider.resource_key.name).to eq(name) - expect(provider.resource_key.value).to eq(value) - meta.each do |k, v| - expect(provider.resource_key.get_meta k).to eq(v) - end - end - - it "with defined name, value, metadata and comments" do - value = "my value" - meta = {'meta1' => 'v1', 'meta2' => 'v2' } - comments = "my comment" - - provider.resource = create_resource :name => name, - :value => value, - :metadata => meta, - :comments => comments - - provider.create - - expect(provider.resource_key.name).to eq(name) - expect(provider.resource_key.value).to eq(value) - meta.each do |k, v| - expect(provider.resource_key.get_meta k).to eq(v) - end - expect(provider.resource_key['comments']).to eq("#0") - expect(provider.resource_key['comments/#0']).to eq("# #{comments}") - end - - - end - - it "should do nothing on destroy when resource_key is nil" do - provider.destroy - end - it "should remove key when we have a key" do - # first create the key, a delete on nil-key does not make sense - allow(ks).to receive(:<<) - provider.create - expect(ks).to receive(:delete) { nil } - provider.destroy - end +describe Puppet::Type.type(:kdbmount).provider(:kdb) do - it "should update the value" - it "should update the metadata" - it "should update the comments" end diff --git a/spec/unit/provider/key_kdb_helper.rb b/spec/unit/provider/key_kdb_helper.rb index 8228f99..f096ce0 100644 --- a/spec/unit/provider/key_kdb_helper.rb +++ b/spec/unit/provider/key_kdb_helper.rb @@ -26,15 +26,18 @@ class KdbKeyProviderHelperKDB < KdbKeyProviderHelper alias ks_key_get_meta key_get_meta alias ks_key_get_comment key_get_comment + def initialize(test_prefix) + super test_prefix + end def do_on_kdb raise ArgumentError, "block required" unless block_given? Kdb.open do |kdb| ks = Kdb::KeySet.new - kdb.get ks, TEST_NS + kdb.get ks, @test_prefix result = yield ks - kdb.set ks, TEST_NS + kdb.set ks, @test_prefix return result end end diff --git a/spec/unit/provider/key_ruby_helper.rb b/spec/unit/provider/key_ruby_helper.rb index a56f39f..8507f6b 100644 --- a/spec/unit/provider/key_ruby_helper.rb +++ b/spec/unit/provider/key_ruby_helper.rb @@ -11,6 +11,12 @@ # helper methods for testing the kdbkey providers class KdbKeyProviderHelper + attr :test_prefix + + def initialize(_test_prefix) + @test_prefix = _test_prefix + end + def ensure_key_exists(ks, keyname, value = "test") key = ks.lookup keyname if key.nil? From 9744732b459da6fe09ff88c4db4f99209c94d340 Mon Sep 17 00:00:00 2001 From: bernhard Date: Sun, 26 Feb 2017 20:01:45 +0100 Subject: [PATCH 22/52] kdbkey: handle metadata updates correctly --- examples/kdbkey.pp | 14 +++--- lib/puppet/provider/kdbkey/common.rb | 29 +++++++++++ lib/puppet/provider/kdbkey/kdb.rb | 10 +++- lib/puppet/provider/kdbkey/ruby.rb | 28 ++++++----- spec/unit/provider/kdbkey_kdb_spec.rb | 66 ++++++++++++++++++++++++-- spec/unit/provider/kdbkey_ruby_spec.rb | 56 ++++++++++++++++++++++ 6 files changed, 179 insertions(+), 24 deletions(-) create mode 100644 lib/puppet/provider/kdbkey/common.rb diff --git a/examples/kdbkey.pp b/examples/kdbkey.pp index 8a03ba7..8721261 100644 --- a/examples/kdbkey.pp +++ b/examples/kdbkey.pp @@ -35,13 +35,15 @@ 'meta1' => 'm1 value', 'meta2' => 'm2 value', #'meta3' => 'm3 value' - } + }, + comments => 'hello world' } kdbkey { "${ns}-test/section1/setting1": - ensure => present, - value => 'hello ini world ...', - metadata => { + ensure => present, + value => 'hello ini world ...', + metadata => { + 'comments' => '#1', 'comments/#0' => '# this is the first comment line', 'comments/#1' => '# this is the second comment line' } @@ -90,7 +92,7 @@ # set keys in the context of a given user # kdbkey { 'user/test/puppet/usertest/x1': - value => 'asdf', - user => 'bernhard', + value => 'asdf', + user => 'bernhard', #provider => 'kdb' } diff --git a/lib/puppet/provider/kdbkey/common.rb b/lib/puppet/provider/kdbkey/common.rb new file mode 100644 index 0000000..9e2bbdb --- /dev/null +++ b/lib/puppet/provider/kdbkey/common.rb @@ -0,0 +1,29 @@ +# encoding: UTF-8 +## +# @file +# +# @brief common functions for all kdbkey provider +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# +# + +class Puppet::Provider::KdbKeyCommon < Puppet::Provider + + + def skip_this_metakey?(metakey, keep_if_specified = false) + # skip modifing these keys at all times (even if user wants to) + return true if metakey.start_with? "internal/" + + # if user specifies these meta keys, let them do so + unless @resource[:metadata].nil? + unless keep_if_specified and @resource[:metadata].include? metakey + return true if metakey == "order" + return true if /^comments?\/#/ =~ metakey or metakey == "comments" + end + end + return false + end + + +end diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 929357b..4f09d53 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -9,7 +9,7 @@ # module Puppet - Type.type(:kdbkey).provide :kdb do + Type.type(:kdbkey).provide :kdb, :parent => Puppet::Provider::KdbKeyCommon do desc "kdb through kdb command" has_feature :user @@ -51,6 +51,14 @@ def metadata @metadata_values = {} output = run_kdb ["lsmeta", @resource[:name]] output.split.each do |metaname| + # skip internal keys + next if skip_this_metakey? metaname, true + # skip this metakey, if purge meta keys is NOT set and this + # key is not specified by the user + unless @resource.purge_meta_keys? or @resource[:metadata].nil? + next unless @resource[:metadata].include? metaname + end + # foreach meta key, fetch its value gm = run_kdb(["getmeta", @resource[:name], metaname], { :combine => false, diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index d53bf06..37a9812 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -9,7 +9,7 @@ # module Puppet - Type.type(:kdbkey).provide :ruby do + Type.type(:kdbkey).provide :ruby, :parent => Puppet::Provider::KdbKeyCommon do desc "kdb through libelektra Ruby API" # static class var for checking if we are able to use this provider @@ -77,14 +77,18 @@ def value=(value) def metadata #key.meta.to_h unless key.nil? ruby 1.9 does not have Enumerable.to_h :( res = Hash.new - @resource_key.meta.each { - |e| res[e.name] = e.value - } unless @resource_key.nil? + @resource_key.meta.each do |e| + next if skip_this_metakey? e.name, true - # if purge_meta_keys is NOT set to true, remove all unspecified keys - if ! @resource.purge_meta_keys? - res.keep_if { |k,v| @resource[:metadata].include? k } - end + # if purge_meta_keys is NOT set to true, remove all unspecified keys + # otherwise, Puppet will think we have to change something, so just + # keep those, which might have to be changed + unless @resource.purge_meta_keys? or @resource[:metadata].nil? + next unless @resource[:metadata].include? e.name + end + + res[e.name] = e.value + end unless @resource_key.nil? return res end @@ -98,10 +102,8 @@ def metadata=(value) # do we have to purge all unspecified keys? if @resource.purge_meta_keys? @resource_key.meta.each do |metakey| - next if metakey.name == "order" - next if metakey.name.start_with? "internal/" - next if /^comments?\// =~ metakey.name - next if metakey.name == "comments" + next if skip_this_metakey? metakey.name + @resource_key.del_meta metakey.name unless value.include? metakey.name end end @@ -115,7 +117,7 @@ def comments @resource_key.meta.each do |e| if e.name.start_with? "comments/#" comments << "\n" unless first - comments << e.value.sub(/^# /, '') + comments << e.value.sub(/^# ?/, '') first = false end end diff --git a/spec/unit/provider/kdbkey_kdb_spec.rb b/spec/unit/provider/kdbkey_kdb_spec.rb index 6981b7e..2a285b7 100644 --- a/spec/unit/provider/kdbkey_kdb_spec.rb +++ b/spec/unit/provider/kdbkey_kdb_spec.rb @@ -105,16 +105,74 @@ end context "should get metadata values" do + let(:metadata) { {"m1" => "v1", "m2" => "v2"} } before :example do - h.ensure_meta_exists keyname, "m1", "test" - h.ensure_meta_exists keyname, "m2", "test" + h.ensure_meta_exists keyname, "m1", metadata["m1"] + h.ensure_meta_exists keyname, "m2", metadata["m2"] + provider.resource[:metadata]= metadata end it "as a hash" do values = provider.metadata expect(values).to be_a_kind_of Hash - expect(values["m1"]).to eq "test" - expect(values["m2"]).to eq "test" + expect(values["m1"]).to eq metadata["m1"] + expect(values["m2"]).to eq metadata["m2"] + end + + # otherwise, Puppet will think we have to update something and + # triggers an update for metadata. + it "but not include unspecified keys if 'purge_meta_keys' is not set" do + h.ensure_meta_exists keyname, "m3", "xxx" + + got_meta = provider.metadata + + expect(got_meta.include? "m1").to eq true + expect(got_meta.include? "m2").to eq true + expect(got_meta.include? "m3").to eq false + end + + it "and ignore 'internal' metakeys" do + h.ensure_meta_exists keyname, "internal/ini/order", "5" + h.ensure_meta_exists keyname, "internal/ini/parent", "xxx" + + got_meta = provider.metadata + + expect(got_meta.include? "internal/ini/order").to eq false + expect(got_meta.include? "internal/ini/parent").to eq false + end + + it "and ignore 'internal' metakeys with 'purge_meta_keys' set" do + h.ensure_meta_exists keyname, "internal/ini/order", "5" + h.ensure_meta_exists keyname, "internal/ini/parent", "xxx" + h.ensure_meta_exists keyname, "comment/#0", "xxx" + h.ensure_meta_exists keyname, "comments/#0", "xxx" + h.ensure_meta_exists keyname, "comments", "#1" + h.ensure_meta_exists keyname, "order", "5" + + provider.resource[:purge_meta_keys] = true + got_meta = provider.metadata + + expect(got_meta.include? "internal/ini/order").to eq false + expect(got_meta.include? "internal/ini/parent").to eq false + expect(got_meta.include? "comment/#0").to eq false + expect(got_meta.include? "comments/#0").to eq false + expect(got_meta.include? "comments").to eq false + expect(got_meta.include? "order").to eq false + end + + # but if user specifies a special metakey, let user win + it "and ignore 'special' metakeys with 'purge_meta_key' unless specified" do + h.ensure_comment_exists keyname, "xxx" + + metadata["comments"] = "#1" + metadata["comments/#0"] = "xxx" + provider.resource[:metadata] = metadata + provider.resource[:purge_meta_keys] = true + + got_meta = provider.metadata + + expect(got_meta.include? "comments/#0").to eq true + expect(got_meta.include? "comments").to eq true end end diff --git a/spec/unit/provider/kdbkey_ruby_spec.rb b/spec/unit/provider/kdbkey_ruby_spec.rb index 9fc0b99..c6d6ce9 100644 --- a/spec/unit/provider/kdbkey_ruby_spec.rb +++ b/spec/unit/provider/kdbkey_ruby_spec.rb @@ -146,6 +146,62 @@ expect(got_meta["m1"]).to eq "v1" expect(got_meta["m2"]).to eq "v2" end + + # otherwise, Puppet will think we have to update something and + # triggers an update for metadata. + it "but not include unspecified keys if 'purge_meta_keys' is not set" do + h.ensure_meta_exists ks, keyname, "m3", "xxx" + + got_meta = provider.metadata + + expect(got_meta.include? "m1").to eq true + expect(got_meta.include? "m2").to eq true + expect(got_meta.include? "m3").to eq false + end + + it "and ignore 'internal' metakeys" do + h.ensure_meta_exists ks, keyname, "internal/ini/order", "5" + h.ensure_meta_exists ks, keyname, "internal/ini/parent", "xxx" + + got_meta = provider.metadata + + expect(got_meta.include? "internal/ini/order").to eq false + expect(got_meta.include? "internal/ini/parent").to eq false + end + + it "and ignore 'internal' metakeys with 'purge_meta_keys' set" do + h.ensure_meta_exists ks, keyname, "internal/ini/order", "5" + h.ensure_meta_exists ks, keyname, "internal/ini/parent", "xxx" + h.ensure_meta_exists ks, keyname, "comment/#0", "xxx" + h.ensure_meta_exists ks, keyname, "comments/#0", "xxx" + h.ensure_meta_exists ks, keyname, "comments", "#1" + h.ensure_meta_exists ks, keyname, "order", "5" + + provider.resource[:purge_meta_keys] = true + got_meta = provider.metadata + + expect(got_meta.include? "internal/ini/order").to eq false + expect(got_meta.include? "internal/ini/parent").to eq false + expect(got_meta.include? "comment/#0").to eq false + expect(got_meta.include? "comments/#0").to eq false + expect(got_meta.include? "comments").to eq false + expect(got_meta.include? "order").to eq false + end + + it "and ignore 'special' metakeys with 'purge_meta_key' unless specified" do + h.ensure_meta_exists ks, keyname, "comments/#0", "xxx" + h.ensure_meta_exists ks, keyname, "comments", "#1" + + metadata["comments/#0"] = "xxx" + metadata["comments"] = "#1" + provider.resource[:metadata] = metadata + provider.resource[:purge_meta_keys] = true + + got_meta = provider.metadata + + expect(got_meta.include? "comments/#0").to eq true + expect(got_meta.include? "comments").to eq true + end end context "should update the metadata" do From 5b62e8ba8c61de748f706525ac524b667cd00a72 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 28 Feb 2017 12:05:44 +0100 Subject: [PATCH 23/52] kdbkey: implement 'check' property (test restructure) we now can add 'check's to a key, whereas we add them to the spec namespace of the corresponding key. Do a restructure of the test cases. Now we use the very same tests for both kdbkey provider. --- examples/kdbkey.pp | 100 ++++- lib/puppet/provider/kdbkey/common.rb | 33 ++ lib/puppet/provider/kdbkey/kdb.rb | 191 ++++++-- lib/puppet/provider/kdbkey/ruby.rb | 59 ++- lib/puppet/type/kdbkey.rb | 113 +++++ spec/unit/provider/kdbkey_common_spec.rb | 38 ++ spec/unit/provider/kdbkey_kdb_spec.rb | 327 +------------- spec/unit/provider/kdbkey_ruby_spec.rb | 347 +-------------- spec/unit/provider/key_helper.rb | 544 +++++++++++++++++++++++ spec/unit/provider/key_kdb_helper.rb | 58 +-- spec/unit/provider/key_ruby_helper.rb | 60 +-- spec/unit/type/kdbkey_spec.rb | 35 ++ 12 files changed, 1122 insertions(+), 783 deletions(-) create mode 100644 spec/unit/provider/kdbkey_common_spec.rb diff --git a/examples/kdbkey.pp b/examples/kdbkey.pp index 8721261..05c4648 100644 --- a/examples/kdbkey.pp +++ b/examples/kdbkey.pp @@ -40,9 +40,9 @@ } kdbkey { "${ns}-test/section1/setting1": - ensure => present, - value => 'hello ini world ...', - metadata => { + ensure => present, + value => 'hello ini world ...', + metadata => { 'comments' => '#1', 'comments/#0' => '# this is the first comment line', 'comments/#1' => '# this is the second comment line' @@ -91,8 +91,94 @@ # # set keys in the context of a given user # -kdbkey { 'user/test/puppet/usertest/x1': - value => 'asdf', - user => 'bernhard', - #provider => 'kdb' +# kdbkey { 'user/test/puppet/usertest/x1': +# value => 'asdf', +# user => 'bernhard', +# #provider => 'kdb' +# } + + +# +# validation +# +$ns_validation = 'user/test/puppet-val' + +# we require some validation plugins activated +# on the mountpoint corresponding to our settings +kdbmount { $ns_validation: + file => 'puppet-val.ini', + plugins => ['ini', 'type', 'enum', 'path', 'validation'], +} + +# ensure our setting is of type 'short' +# (see '$> kdb info type' for other types) +kdbkey { 'spec/x1': + prefix => $ns_validation, + value => 5, + check => { + 'type' => 'short' + }, + # TODO: autorequire?? + require => Kdbmount[$ns_validation] +} + +kdbkey { 'spec/x2': + prefix => $ns_validation, + value => '5', + check => {'type' => 'short' } +} + +# the type plugin is also aware of doing range checks +kdbkey { 'spec/x3': + prefix => $ns_validation, + value => 10, + check => { + 'type' => 'short', + 'type/min' => 0, # lower bound + 'type/max' => 10 # upper bound + } +} + +# enums with array of values +kdbkey { 'spec/enumx': + prefix => $ns_validation, + check => {'enum' => ['low', 'middle', 'high']}, + value => 'low', +} + +# or specify allowed values with on string +# (Note: allowed values have to be enclosed in single quotes and +# seperated by ", ") +kdbkey { 'spec/enum_x2': + prefix => $ns_validation, + check => { 'enum' => "'one', 'two', three'" }, + value => 'one' +} + +# ensure only valid absolute path names are used +kdbkey { 'spec/path_key': + prefix => $ns_validation, + check => 'path', + value => '/this/is/an/abolute/path' +} + +# do regular expression checks on key settings +kdbkey { 'spec/regex_key': + prefix => $ns_validation, + check => { + 'validation' => '^hello (world|master)$', + #'validation/ignorecase' => 0, + }, + value => 'hello world', +} + +kdbkey { 'spec/short2': + comments => " + + this is a simple short value + + no bound checks are performed", + prefix => $ns_validation, + check => { type => short }, + value => 5 } diff --git a/lib/puppet/provider/kdbkey/common.rb b/lib/puppet/provider/kdbkey/common.rb index 9e2bbdb..a48aa33 100644 --- a/lib/puppet/provider/kdbkey/common.rb +++ b/lib/puppet/provider/kdbkey/common.rb @@ -10,6 +10,10 @@ class Puppet::Provider::KdbKeyCommon < Puppet::Provider + # just used for debugging + attr_accessor :verbose + @verbose = false + def skip_this_metakey?(metakey, keep_if_specified = false) # skip modifing these keys at all times (even if user wants to) @@ -25,5 +29,34 @@ def skip_this_metakey?(metakey, keep_if_specified = false) return false end + def is_special_meta_key?(metakey) + return true if metakey.start_with? "internal/" + return true if metakey.start_with? "comment" + return true if metakey == "order" + return false + end + + def get_spec_key_name(keyname = @resource[:name]) + return keyname.gsub(/^\w*\//, "spec/") + end + + def specified_checks_to_meta(value) + # ensure we have a Hash + value = {value => ""} if value.is_a? String + + spec_to_set = {} + value.each do |check_key, check_value| + if check_value.is_a? Array + # if value is an Array define an Elektra array + check_value.each_with_index do |v, index| + spec_to_set["check/#{check_key}/##{index}"] = v + end + else + spec_to_set["check/#{check_key}"] = check_value + end + end + spec_to_set + end + end diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 4f09d53..bdead02 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -7,6 +7,7 @@ # @copyright BSD License (see LICENSE or http://www.libelektra.org) # # +require 'tempfile' module Puppet Type.type(:kdbkey).provide :kdb, :parent => Puppet::Provider::KdbKeyCommon do @@ -25,6 +26,7 @@ def run_kdb(args, params = {:combine => true, :failonfail => true}) def create self.value=(@resource[:value]) self.metadata= @resource[:metadata] unless @resource[:metadata].nil? + self.comments= @resource[:comments] unless @resource[:comments].nil? end def destroy @@ -35,7 +37,6 @@ def exists? Puppet.debug "kdbkey/kdb exists? #{@resource[:name]}" output = execute([command(:kdb), "get", @resource[:name]], :failonfail => false) - #puts "output: #{output}, #{output.exitstatus}" output.exitstatus == 0 end @@ -47,82 +48,178 @@ def value=(value) run_kdb ["set", @resource[:name], value] end - def metadata + def read_metadata_values + read_metadata_values_from_key @resource[:name] + end + + def read_metadata_values_from_key(key_to_read_from) + puts "read meta data from key '#{key_to_read_from}'" if @verbose @metadata_values = {} - output = run_kdb ["lsmeta", @resource[:name]] - output.split.each do |metaname| - # skip internal keys - next if skip_this_metakey? metaname, true - # skip this metakey, if purge meta keys is NOT set and this - # key is not specified by the user - unless @resource.purge_meta_keys? or @resource[:metadata].nil? - next unless @resource[:metadata].include? metaname + Tempfile.open("key") do |file| + run_kdb ["export", key_to_read_from, "ni", file.path] + # reopen file + file.open + metadata_reached = false + file.each do |line| + line.chomp! + puts "read meta: '#{line}'" if @verbose + if line == "[]" + metadata_reached = true + next + end + next if metadata_reached == false + + key_name, key_value = line.split(" = ") + key_name.strip! + + puts "use meta: '#{key_name}' => '#{key_value}'" if @verbose + @metadata_values[key_name] = key_value.to_s # ensure we have a string end + file.close! + end + return @metadata_values + end - # foreach meta key, fetch its value - gm = run_kdb(["getmeta", @resource[:name], metaname], { - :combine => false, - :failonfail => false} + def metadata + read_metadata_values unless @metadata_values.is_a? Hash + ret = @metadata_values.reject do |k,v| + # do not keep this key_name + delete = ( + # if it is an internal key (unless specified) + skip_this_metakey?(k, true) or + # or unless purge_meta_keys == true or k is specified + not( + @resource[:metadata].nil? or @resource.purge_meta_keys? or + @resource[:metadata].include? k + ) ) - if gm.exitstatus == 0 - @metadata_values[metaname.strip] = gm.chomp - end + delete end - return @metadata_values + puts "metadata is: #{ret}" if @verbose + ret end def metadata=(value) - value.each do |metaname, metavalue| - run_kdb ["setmeta", @resource[:name], metaname, metavalue] + read_metadata_values unless @metadata_values.is_a? Hash + puts "having metadata: #{@metadata_values}" if @verbose + value.each do |k,v| + @metadata_values[k] = v end - # handle purge_meta_keys - if @resource.purge_meta_keys? and @metadata_values.is_a? Hash - @metadata_values.each do |m,v| - next if value.include? m - next if m == "comments" or m.start_with? "comment/", "comments/" - next if m.start_with? "internal/" - next if m == "order" - - # currently there is no rmmeta command for kdb - run_kdb ["setmeta", @resource[:name], m, ''] + if @resource.purge_meta_keys? + @metadata_values.keep_if do |k,v| + keep = (@resource[:metadata].include?(k) or is_special_meta_key?(k)) + puts "keep this meta: '#{k}' #{keep}" if @verbose + keep end end + puts "updated metadata: #{@metadata_values}" if @verbose end def comments - metadata unless @metadata_values.is_a? Hash - comments = "" + read_metadata_values unless @metadata_values.is_a? Hash + comments = {} @metadata_values.each do |meta, value| - if /^comments?\/#/ =~ meta - comments << "\n" unless comments.empty? - comments << value.sub(/^#/, '') + if /^comments?\/#(\d+)/ =~ meta + value = value[1..-1] if value[0] == '"' + value = value[0..-2] if value[-1] == '"' + comments[$1] = value.sub(/^#/, '') end end - return comments + # we get a hash, with + # #num => line + # so sort by #num, take the lines and join with newline + comments = comments.sort_by{|k,v| k}.map{|e|e[1]}.join "\n" + comments end def comments=(value) - metadata unless @metadata_values.is_a? Hash + self.metadata unless @metadata_values.is_a? Hash comment_lines = value.split "\n" - updated = [] + # remove all comment meta keys + @metadata_values.delete_if { |k,v| k.start_with? "comment" } + + @metadata_values["comments"] = "##{comment_lines.size}" comment_lines.each_with_index do |line, index| - updated << meta_name = "comments/##{index}" - run_kdb ["setmeta", @resource[:name], meta_name, line] + @metadata_values["comments/##{index}"] = line end + end - @metadata_values.each do |k, v| - # update comments count value - if k == "comments" - run_kdb ["setmeta", @resource[:name], k, "##{comment_lines.size}"] + def check + @spec_meta_values = read_metadata_values_from_key(get_spec_key_name) + specs = {} + @spec_meta_values.each do |k,v| + # we are interested in meta keys starging with 'check/' + if /^check\/(.*)$/ =~ k + check_name = $1 + # if it is an elektra Array, convert it to a Ruby array + # while preserve order + if /^(\w+)\/#(\d+)$/ =~ check_name + check_name, index = $1, $2.to_i + specs[check_name] = [] unless specs[check_name].is_a? Array + specs[check_name][index] = v + else + specs[check_name] = v + end end + end + if specs.size == 1 and specs.values[0].to_s.empty? + specs = specs.keys[0] + end + puts "spec_keys: #{specs}" if @verbose + return specs + end + + def check=(value) + self.check unless @spec_meta_values.is_a? Hash - if k.start_with? "comments/#" and !updated.include? k - run_kdb ["setmeta", @resource[:name], k, ''] + spec_to_set = specified_checks_to_meta value + @spec_meta_values.merge! spec_to_set + @spec_meta_values.delete_if do |k,v| + (k.start_with? "check" and not spec_to_set.include? k) + end + Tempfile.open("speckey") do |file| + file.puts + file.puts " = " + file.puts + file.puts "[]" + @spec_meta_values.each do |k,v| + file.puts " #{k} = #{v}" end + file.flush + begin + file.rewind + file.each do |line| + puts "import spec: #{line}" + end + end if @verbose + file.close + run_kdb ["import", get_spec_key_name, "ni", file.path] + file.unlink end end - + def flush + return unless @metadata_values.is_a? Hash + Tempfile.open("key") do |file| + file.puts + file.puts " = #{@resource[:value]}" + file.puts + file.puts "[]" + @metadata_values.each do |k,v| + file.puts " #{k} = #{v}" + end + file.flush + begin + file.rewind + file.each do |line| + puts "import: #{line}" + end + end if @verbose + file.close + run_kdb ["import", @resource[:name], "ni", file.path] + file.unlink + end + end end end diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 37a9812..fc069db 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -14,6 +14,7 @@ module Puppet # static class var for checking if we are able to use this provider @@have_kdb = true + @@is_fake_ks = false begin # load libelektra Ruby binding extension @@ -37,6 +38,7 @@ module Puppet # just used during testing to inject a mock def self.use_fake_ks(ks) @@ks = ks + @@is_fake_ks = true end # allow access to internal key, used during testing @@ -45,6 +47,7 @@ def self.use_fake_ks(ks) def create #puts "ruby create #{@resource[:name]}" @resource_key = Kdb::Key.new @resource[:name], value: @resource[:value] + self.check= @resource[:check] unless @resource[:check].nil? self.metadata= @resource[:metadata] unless @resource[:metadata].nil? self.comments= @resource[:comments] unless @resource[:comments].nil? @@ks << @resource_key @@ -151,13 +154,65 @@ def comments=(value) end end + def check + spec_hash = {} + spec_key = @@ks.lookup get_spec_key_name + unless spec_key.nil? + spec_key.meta.each do |m| + if /^check\/(.*)$/ =~ m.name + check_name = $1 + if /^(\w+)\/#\d+$/ =~ check_name + spec_hash[$1] = [] unless spec_hash[$1].is_a? Array + spec_hash[$1] << m.value + else + spec_hash[check_name] = m.value + end + end + end + end + # special case: if we get just one key and its value + # is "", return this as a string + if spec_hash.size == 1 and spec_hash.values[0] == "" + spec_hash = spec_hash.keys[0] + end + Puppet.debug "having spec: #{spec_hash}" + return spec_hash + end + + def check=(value) + Puppet.debug "setting spec: #{value}" + spec_key = Kdb::Key.new get_spec_key_name + + if @@ks.lookup(spec_key).nil? + @@ks << spec_key + else + spec_key = @@ks.lookup spec_key + end + + spec_to_set = specified_checks_to_meta value + + # set meta data on spec_key + spec_to_set.each do |spec_name, spec_value| + spec_key[spec_name] = spec_value + end + + # remove all not specified meta keys from spec_key starting with 'check' + spec_key.meta.each do |e| + if e.name.start_with? "check" and !spec_to_set.include? e.name + spec_key.del_meta e.name + end + end + end + # flush is call if a resource was modified # thus this method is perfectly suitable for our db.set method which will # finally bring the changes to disk def flush - Puppet.debug "kdbkey/ruby: flush #{@resource[:name]}" - @@db.set @@ks, "/" + unless @@is_fake_ks + Puppet.debug "kdbkey/ruby: flush #{@resource[:name]}" + @@db.set @@ks, "/" + end end # this is the provider de-init hook diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index a19eb5d..88aefac 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -179,6 +179,119 @@ def change_to_s(current_value, new_value) end end + newproperty(:check) do + desc <<-EOT + Add value validation. + + This property allows to define certain restrictions to be applied on the + key value, which are automatically checked on each key database write. These + validation checks are performed by Elektra itself, so modifications done + by other applications will be also restricted to the defined value + specifications. + + The value for this property can be either a single String or a Hash + of settings. + e.g. path plugin + the 'path' plugin does not require any additional settings + so it is enough to just pass 'path' as 'check' value. + + kdbkey { 'system/sw/myapp/setting1': + check => 'path', + value => '/some/absolute/path/will/pass' + } + + Note: this does not check if the path really exists (instead it just + issues a warning). The check will fail, if the given value is not an + absolute path. + + e.g. network plugin + + The network plugin checks if the supplied value is valid IP address. + + kdbkey { 'system/sw/myapp/myip': + check => 'ipaddr', + value => ${given_myip} + } + + to check for valid IPv4 addresses use + + kdbkey { 'system/sw/myapp/myip': + check => { 'ipaddr' => 'ipv4' }, # works with 'ipv6' too + value => ${given_myip} + } + + e.g. type plugin + + The type plugin checks if the supplied key value conforms to a defined + data type (e.g. numeric value). Additionally, it is able to check if + the supplied key value is within an allowed range. + + kdbkey { 'system/sw/myapp/port': + check => { 'type' => 'unsigned_long' }, + value => ${given_port} + } + + kdbkey { 'system/sw/myapp/num_instances': + check => { + 'type' => 'short', + 'type/min' => 1, + 'type/max' => 20 + }, + value => ${given_num_instance} + } + + e.g. enum plugin + + The enum plugin check it the supplied value is within a predefined set + of values. Two different formats are possible: + + kdbkey { 'system/sw/myapp/scheduler': + check => { 'enum' => "'ondemand', 'performance', 'energy saving'" }, + value => ${given_scheduler} + } + + kdbkey { 'system/sw/myapp/notification': + check => { 'enum' => ['off', 'email', 'slack', 'irc'] }, + value => ${given_notification} + } + + e.g. validation plugin + + The validation plugin checks if the supplied value matches a predefined + regular expression: + + kdbkey { 'system/sw/myapp/email': + check => { + 'validation' => '^[a-z0-9\._]+@mycompany.com$' + 'validation/message' => 'we require an internal email address here', + 'validation/ignorecase' => '', # existence of flag is enough + } + ... + } + + + For further check plugins see the Elektra documentation. + + Note: for each 'check/xxx' metadata, required by the Elektra plugins, just + remove the 'check/' part and add it to the 'check' property here. + (e.g. validation plugin: 'check/validation' => 'validation' ...) + EOT + + validate do |value| + # setting specifications for spec/ keys does not make any sense + # so we do not allow it + if @resource[:name].start_with? "spec/" + raise ArgumentError, "setting specifications on a 'spec' key "\ + "is not allowed and does not make sense" + end + unless value.is_a? Hash or value.is_a? String + raise ArgumentError, "Hash required" + else + super value + end + end + end + # param user # # This is currently only supported by Provider 'kdb'. diff --git a/spec/unit/provider/kdbkey_common_spec.rb b/spec/unit/provider/kdbkey_common_spec.rb new file mode 100644 index 0000000..667b9ac --- /dev/null +++ b/spec/unit/provider/kdbkey_common_spec.rb @@ -0,0 +1,38 @@ +# encoding: UTF-8 +## +# @file +# +# @brief +# +# @copyright BSD License (see LICENSE or http://www.libelektra.org) +# + +require 'spec_helper' +require_relative 'key_helper.rb' + +require 'puppet/provider/kdbkey/common.rb' + +describe Puppet::Provider::KdbKeyCommon do + let(:provider) { described_class.new } + + RSpec.shared_examples "key => spec-key" do |keyname, expected| + it "for key '#{keyname}'" do + provider.resource = create_resource :name => keyname + expect( + provider.get_spec_key_name + ).to eq expected + end + end + + context "should get corresponding spec-key from key name" do + include_examples "key => spec-key", "system/x1/x2", "spec/x1/x2" + include_examples "key => spec-key", "user/x1/x2", "spec/x1/x2" + include_examples "key => spec-key", "/x1/x2", "spec/x1/x2" + include_examples "key => spec-key", "dir/x1/x2", "spec/x1/x2" + include_examples "key => spec-key", "system/x1", "spec/x1" + include_examples "key => spec-key", "system/\\some\\/escaped/x1", + "spec/\\some\\/escaped/x1" + end + + +end diff --git a/spec/unit/provider/kdbkey_kdb_spec.rb b/spec/unit/provider/kdbkey_kdb_spec.rb index 2a285b7..6d500b5 100644 --- a/spec/unit/provider/kdbkey_kdb_spec.rb +++ b/spec/unit/provider/kdbkey_kdb_spec.rb @@ -22,330 +22,5 @@ provider.resource = create_resource :name => keyname end - - it "should be a child of Puppet::Provider" do - expect(described_class.new).to be_a_kind_of Puppet::Provider - end - - context "should check if a key exists" do - it "should return false on exists? if key is missing" do - h.ensure_key_is_missing keyname - expect(provider.exists?).to eq(false) - end - - it "should return true on exists? if key exists" do - h.ensure_key_exists keyname - expect(provider.exists?).to eq(true) - end - end - - context "should create key" do - before :example do - h.ensure_key_is_missing keyname - end - - it "with defined name and no value" do - provider.create - expect(h.check_key_exists keyname).to eq(true) - expect(h.key_get_value keyname).to eq("") - end - - it "with defined name and value" do - expect(h.check_key_exists keyname).to eq(false) - provider.resource = create_resource(:name => keyname, - :value => "create with value") - provider.create - expect(h.check_key_exists keyname).to eq(true) - expect(h.key_get_value keyname).to eq("create with value") - end - - it "with defined name, value and metadata" do - provider.resource[:value] = "my val" - provider.resource[:metadata] = {"m1" => "v1", "m2" => "v2"} - provider.create - expect(h.check_key_exists keyname).to eq true - expect(h.key_get_value keyname).to eq "my val" - expect(h.check_meta_exists keyname, "m1").to eq true - expect(h.key_get_meta keyname, "m1").to eq "v1" - expect(h.check_meta_exists keyname, "m2").to eq true - expect(h.key_get_meta keyname, "m2").to eq "v2" - end - end - - context "should update the key value" do - before :example do - h.ensure_key_exists keyname, "test" - end - - it "to an arbitrary string" do - provider.resource[:value] = "some string value" - - expect(h.key_get_value keyname).to eq("test") - provider.value= "some string value" - expect(h.key_get_value keyname).to eq("some string value") - end - - it "to an empty string" do - expect(h.key_get_value keyname).to eq("test") - provider.value= "" - expect(h.key_get_value keyname).to eq("") - end - end - - context "should remove key" do - before :example do - h.ensure_key_exists keyname - end - - it "when key exists" do - expect(h.check_key_exists keyname).to eq true - provider.destroy - expect(h.check_key_exists keyname).to eq false - end - end - - context "should get metadata values" do - let(:metadata) { {"m1" => "v1", "m2" => "v2"} } - before :example do - h.ensure_meta_exists keyname, "m1", metadata["m1"] - h.ensure_meta_exists keyname, "m2", metadata["m2"] - provider.resource[:metadata]= metadata - end - - it "as a hash" do - values = provider.metadata - expect(values).to be_a_kind_of Hash - expect(values["m1"]).to eq metadata["m1"] - expect(values["m2"]).to eq metadata["m2"] - end - - # otherwise, Puppet will think we have to update something and - # triggers an update for metadata. - it "but not include unspecified keys if 'purge_meta_keys' is not set" do - h.ensure_meta_exists keyname, "m3", "xxx" - - got_meta = provider.metadata - - expect(got_meta.include? "m1").to eq true - expect(got_meta.include? "m2").to eq true - expect(got_meta.include? "m3").to eq false - end - - it "and ignore 'internal' metakeys" do - h.ensure_meta_exists keyname, "internal/ini/order", "5" - h.ensure_meta_exists keyname, "internal/ini/parent", "xxx" - - got_meta = provider.metadata - - expect(got_meta.include? "internal/ini/order").to eq false - expect(got_meta.include? "internal/ini/parent").to eq false - end - - it "and ignore 'internal' metakeys with 'purge_meta_keys' set" do - h.ensure_meta_exists keyname, "internal/ini/order", "5" - h.ensure_meta_exists keyname, "internal/ini/parent", "xxx" - h.ensure_meta_exists keyname, "comment/#0", "xxx" - h.ensure_meta_exists keyname, "comments/#0", "xxx" - h.ensure_meta_exists keyname, "comments", "#1" - h.ensure_meta_exists keyname, "order", "5" - - provider.resource[:purge_meta_keys] = true - got_meta = provider.metadata - - expect(got_meta.include? "internal/ini/order").to eq false - expect(got_meta.include? "internal/ini/parent").to eq false - expect(got_meta.include? "comment/#0").to eq false - expect(got_meta.include? "comments/#0").to eq false - expect(got_meta.include? "comments").to eq false - expect(got_meta.include? "order").to eq false - end - - # but if user specifies a special metakey, let user win - it "and ignore 'special' metakeys with 'purge_meta_key' unless specified" do - h.ensure_comment_exists keyname, "xxx" - - metadata["comments"] = "#1" - metadata["comments/#0"] = "xxx" - provider.resource[:metadata] = metadata - provider.resource[:purge_meta_keys] = true - - got_meta = provider.metadata - - expect(got_meta.include? "comments/#0").to eq true - expect(got_meta.include? "comments").to eq true - end - end - - context "should update metadata" do - let(:metadata) { {"m1" => "v1", "m2" => "v2"} } - before :example do - h.ensure_key_exists keyname - end - - it "with missing metadata key" do - h.ensure_meta_is_missing keyname, "m1" - h.ensure_meta_is_missing keyname, "m2" - provider.resource[:metadata] = metadata - - provider.metadata= metadata - - expect(h.check_meta_exists keyname, "m1").to eq true - expect(h.check_meta_exists keyname, "m2").to eq true - expect(h.key_get_meta keyname, "m1").to eq "v1" - expect(h.key_get_meta keyname, "m2").to eq "v2" - end - - it "with existing metadata" do - h.ensure_meta_exists keyname, "m1", "test" - h.ensure_meta_exists keyname, "m2", "test" - provider.resource[:metadata] = metadata - - provider.metadata= metadata - - expect(h.check_meta_exists keyname, "m1").to eq true - expect(h.check_meta_exists keyname, "m2").to eq true - expect(h.key_get_meta keyname, "m1").to eq "v1" - expect(h.key_get_meta keyname, "m2").to eq "v2" - end - end - - context "should purge not specified metadata if 'purge_meta_keys' is set" do - let(:metadata) { {"m1" => "v1", "m2" => "v2"} } - before :example do - provider.resource[:purge_meta_keys] = true - - h.ensure_meta_is_missing keyname, "m1" - h.ensure_meta_exists keyname, "m2" - h.ensure_meta_exists keyname, "r1", "to remove" - h.ensure_meta_exists keyname, "r2", "to remove" - end - - it "while updating specified" do - provider.metadata - provider.metadata = metadata - - expect(h.check_meta_exists keyname, "m1").to eq true - expect(h.check_meta_exists keyname, "m2").to eq true - expect(h.key_get_meta keyname, "m1").to eq "v1" - expect(h.key_get_meta keyname, "m2").to eq "v2" - - #expect(h.check_meta_exists keyname, "r1").to eq false - #expect(h.check_meta_exists keyname, "r2").to eq false - expect(h.key_get_meta keyname, "r1").to eq "" - expect(h.key_get_meta keyname, "r2").to eq "" - end - - # we can't test is this way, as we can not set 'internal' metadata - # values - #it "while ignoring meta keys starting with 'internal/'" do - # h.ensure_meta_exists keyname, "internal/puppet/test", "keep it" - - # puts provider.metadata - # provider.metadata = metadata - - # expect(h.check_meta_exists keyname, "m1").to eq true - # expect(h.check_meta_exists keyname, "m2").to eq true - - # expect(h.check_meta_exists keyname, "internal/puppet/test").to eq true - # expect(h.key_get_meta keyname, "internal/puppet/test").to eq "keep it" - - # #expect(h.check_meta_exists keyname, "r1").to eq false - # #expect(h.check_meta_exists keyname, "r2").to eq false - # expect(h.key_get_meta keyname, "r1").to eq "" - # expect(h.key_get_meta keyname, "r2").to eq "" - #end - - it "while ignoring comments, which are not modified" do - h.ensure_comment_exists keyname, " some comments" - - provider.metadata - provider.metadata = metadata - - #expect(check_meta_exists keyname, COMMENT+"/#0").to eq true - expect(h.check_comment_exists keyname).to eq true - - #expect(h.key_get_meta keyname, COMMENT+"/#0").to eq "# some comments" - expect(h.key_get_comment keyname).to eq " some comments" - end - - it "while ignoring commens which are added too" do - provider.metadata = metadata - provider.comments = "comment defined by me" - - provider.metadata - provider.metadata = metadata - - expect(h.check_comment_exists keyname).to eq true - #expect(h.check_meta_exists keyname, "r1").to eq false - expect(h.key_get_meta keyname, "r1").to eq "" - end - - it "while ignoring 'order' metadata" do - h.ensure_meta_exists keyname, "order", "5" - - provider.metadata - provider.metadata = metadata - - expect(h.check_meta_exists keyname, "order").to eq true - expect(h.key_get_meta keyname, "order").to eq "5" - #expect(h.check_meta_exists keyname, "r1").to eq false - expect(h.key_get_meta keyname, "r1").to eq "" - end - end - - context "should handle comments" do - it "and fetch the comment string" do - h.ensure_comment_exists keyname, "some comments" - - comments = provider.comments - - expect(comments).to be_a_kind_of String - expect(comments).to eq "some comments" - end - - it "and fetch a multiline the comment string at once" do - expected_comment = < keyname end - - it "should be a child of Puppet::Provider" do - expect(described_class.new).to be_a_kind_of(Puppet::Provider) - end - - context "should check if resource exists" do - it "should return false on exists? if resource does not exist'" do - expect(provider.exists?).to eq(false) - end - - it "should return true on exists? if resource exists'" do - h.ensure_key_exists ks, keyname - expect(provider.exists?).to eq(true) - end - - end - - context "should create key" do - before :example do - h.ensure_key_is_missing ks, keyname - end - - it "with defined name" do - provider.create - expect(h.check_key_exists ks, keyname).to eq true - end - - it "with defined name and value" do - value = "my value" - provider.resource = create_resource :name => keyname, :value => value - - provider.create - - expect(h.check_key_exists ks, keyname).to eq true - expect(h.key_get_value ks, keyname).to eq value - end - - it "with defined name, value and metadata" do - value = "my value" - meta = {'meta1' => 'v1', 'meta2' => 'v2' } - provider.resource = create_resource :name => keyname, - :value => value, - :metadata => meta - - provider.create - - expect(h.check_key_exists ks, keyname).to eq true - expect(h.key_get_value ks, keyname).to eq value - meta.each do |k, v| - expect(h.key_get_meta ks, keyname, k).to eq v - end - end - - it "with defined name, value, metadata and comments" do - value = "my value" - meta = {'meta1' => 'v1', 'meta2' => 'v2' } - comments = "my comment" - - provider.resource = create_resource :name => keyname, - :value => value, - :metadata => meta, - :comments => comments - - provider.create - - expect(h.check_key_exists ks, keyname).to eq true - expect(h.key_get_value ks, keyname).to eq value - meta.each do |k, v| - expect(h.key_get_meta ks, keyname, k).to eq(v) - end - expect(h.key_get_comment ks, keyname).to eq comments - end - - - end - - it "should remove key on destroy" do - h.ensure_key_exists ks, keyname - # we have to call exists? first - provider.exists? - provider.destroy - - expect(h.check_key_exists ks, keyname).to eq false - end - - context "with existing key" do - before :example do - h.ensure_key_exists ks, keyname, "test" - provider.exists? - end - - context "should update the key value" do - it "to an arbitrary string" do - expect(h.key_get_value ks, keyname).to eq "test" - provider.value= "some string value" - expect(h.key_get_value ks, keyname).to eq "some string value" - end - - it "to an empty string" do - expect(h.key_get_value ks, keyname).to eq "test" - provider.value= "" - expect(h.key_get_value ks, keyname).to eq "" - end - end - - context "and existing metadata" do - let(:metadata) { {"m1" => "v1", "m2" => "v2"} } - before :example do - h.ensure_meta_exists ks, keyname, "m1", metadata["m1"] - h.ensure_meta_exists ks, keyname, "m2", metadata["m2"] - provider.resource[:metadata]= metadata - end - - context "should get metadata values" do - it "as a hash" do - got_meta = provider.metadata - - expect(got_meta).to be_a_kind_of Hash - expect(got_meta["m1"]).to eq "v1" - expect(got_meta["m2"]).to eq "v2" - end - - # otherwise, Puppet will think we have to update something and - # triggers an update for metadata. - it "but not include unspecified keys if 'purge_meta_keys' is not set" do - h.ensure_meta_exists ks, keyname, "m3", "xxx" - - got_meta = provider.metadata - - expect(got_meta.include? "m1").to eq true - expect(got_meta.include? "m2").to eq true - expect(got_meta.include? "m3").to eq false - end - - it "and ignore 'internal' metakeys" do - h.ensure_meta_exists ks, keyname, "internal/ini/order", "5" - h.ensure_meta_exists ks, keyname, "internal/ini/parent", "xxx" - - got_meta = provider.metadata - - expect(got_meta.include? "internal/ini/order").to eq false - expect(got_meta.include? "internal/ini/parent").to eq false - end - - it "and ignore 'internal' metakeys with 'purge_meta_keys' set" do - h.ensure_meta_exists ks, keyname, "internal/ini/order", "5" - h.ensure_meta_exists ks, keyname, "internal/ini/parent", "xxx" - h.ensure_meta_exists ks, keyname, "comment/#0", "xxx" - h.ensure_meta_exists ks, keyname, "comments/#0", "xxx" - h.ensure_meta_exists ks, keyname, "comments", "#1" - h.ensure_meta_exists ks, keyname, "order", "5" - - provider.resource[:purge_meta_keys] = true - got_meta = provider.metadata - - expect(got_meta.include? "internal/ini/order").to eq false - expect(got_meta.include? "internal/ini/parent").to eq false - expect(got_meta.include? "comment/#0").to eq false - expect(got_meta.include? "comments/#0").to eq false - expect(got_meta.include? "comments").to eq false - expect(got_meta.include? "order").to eq false - end - - it "and ignore 'special' metakeys with 'purge_meta_key' unless specified" do - h.ensure_meta_exists ks, keyname, "comments/#0", "xxx" - h.ensure_meta_exists ks, keyname, "comments", "#1" - - metadata["comments/#0"] = "xxx" - metadata["comments"] = "#1" - provider.resource[:metadata] = metadata - provider.resource[:purge_meta_keys] = true - - got_meta = provider.metadata - - expect(got_meta.include? "comments/#0").to eq true - expect(got_meta.include? "comments").to eq true - end - end - - context "should update the metadata" do - it "with missing metadata key" do - metadata["m3"] = "v3" - provider.resource[:metadata]= metadata - provider.metadata= metadata - - got_meta = provider.metadata - - expect(got_meta.include? "m3").to eq true - expect(got_meta["m3"]).to eq "v3" - end - - it "with existing metadata" do - got_meta = provider.metadata - - expect(got_meta.include? "m1").to eq true - expect(got_meta.include? "m2").to eq true - expect(got_meta["m1"]).to eq "v1" - expect(got_meta["m2"]).to eq "v2" - end - end - - context "should purge not specified metadata if 'purge_meta_keys' is set" do - before :example do - h.ensure_meta_exists ks, keyname, "r1", "to remove" - h.ensure_meta_exists ks, keyname, "r2", "to remove" - provider.resource[:purge_meta_keys] = true - end - - def has_expected_but_not_specified(got_meta) - expect(got_meta.include? "m1").to eq true - expect(got_meta.include? "m2").to eq true - expect(got_meta.include? "r1").to eq false - expect(got_meta.include? "r2").to eq false - - expect(got_meta["m1"]).to eq "v1" - expect(got_meta["m2"]).to eq "v2" - end - - it "while updating specified" do - h.ensure_meta_exists ks, keyname, "m1", "old value" - provider.metadata= metadata - - got_meta = provider.metadata - - has_expected_but_not_specified got_meta - end - - it "while ignoring comments, which are not modified" do - h.ensure_comment_exists ks, keyname, "some comment" - - provider.metadata= metadata - got_meta = provider.metadata - - has_expected_but_not_specified got_meta - expect(h.check_comment_exists ks, keyname).to eq true - expect(h.key_get_comment ks, keyname).to eq "some comment" - end - - it "while ignoring comments, which are added too (before)" do - provider.comments= "some comment" - provider.metadata= metadata - - got_meta = provider.metadata - - has_expected_but_not_specified got_meta - expect(h.check_comment_exists ks, keyname).to eq true - expect(h.key_get_comment ks, keyname).to eq "some comment" - end - - it "while ignoring comments, which are added too (after)" do - provider.metadata= metadata - provider.comments= "some comment" - - got_meta = provider.metadata - - has_expected_but_not_specified got_meta - expect(h.check_comment_exists ks, keyname).to eq true - expect(h.key_get_comment ks, keyname).to eq "some comment" - end - - it "while ignoring 'internal/' metadata keys" do - h.ensure_meta_exists ks, keyname, "internal/test1", "to keep" - - provider.metadata= metadata - got_meta = provider.metadata - - has_expected_but_not_specified got_meta - expect(h.check_meta_exists ks, keyname, "internal/test1").to eq true - expect(h.key_get_meta ks, keyname, "internal/test1").to eq "to keep" - end - - it "while ignoring 'order' metadata" do - h.ensure_meta_exists ks, keyname, "order", "5" - - provider.metadata= metadata - got_meta = provider.metadata - - has_expected_but_not_specified got_meta - expect(h.check_meta_exists ks, keyname, "order").to eq true - expect(h.key_get_meta ks, keyname, "order").to eq "5" - end - end - end - - context "should handle comments" do - it "and fetch the comment string" do - h.ensure_comment_exists ks, keyname, "my comment" - - expect(provider.comments).to eq "my comment" - end - - it "and fetch a multiline comment string at once" do - expected_comment = < keyname, :value => value + + provider.create + provider.flush + + expect(h.check_key_exists keyname).to eq true + expect(h.key_get_value keyname).to eq value + end + + it "with defined name, value and metadata" do + value = "my value" + meta = {'meta1' => 'v1', 'meta2' => 'v2' } + provider.resource = create_resource :name => keyname, + :value => value, + :metadata => meta + + provider.create + provider.flush + + expect(h.check_key_exists keyname).to eq true + expect(h.key_get_value keyname).to eq value + meta.each do |k, v| + expect(h.key_get_meta keyname, k).to eq v + end + end + + it "with defined name, value, metadata and comments" do + value = "my value" + meta = {'meta1' => 'v1', 'meta2' => 'v2' } + comments = "my comment" + + provider.resource = create_resource :name => keyname, + :value => value, + :metadata => meta, + :comments => comments + + provider.create + provider.flush + + expect(h.check_key_exists keyname).to eq true + expect(h.key_get_value keyname).to eq value + meta.each do |k, v| + expect(h.key_get_meta keyname, k).to eq(v) + end + expect(h.key_get_comment keyname).to eq comments + end + + + end + + it "should remove key on destroy" do + h.ensure_key_exists keyname + # we have to call exists? first + provider.exists? + provider.destroy + provider.flush + + expect(h.check_key_exists keyname).to eq false + end + + context "with existing key" do + before :example do + h.ensure_key_exists keyname, "test" + provider.exists? + end + + context "should update the key value" do + it "to an arbitrary string" do + expect(h.key_get_value keyname).to eq "test" + provider.value= "some string value" + provider.flush + expect(h.key_get_value keyname).to eq "some string value" + end + + it "to an empty string" do + expect(h.key_get_value keyname).to eq "test" + provider.value= "" + provider.flush + expect(h.key_get_value keyname).to eq "" + end + end + + context "and existing metadata" do + let(:metadata) { {"m1" => "v1", "m2" => "v2"} } + before :example do + h.ensure_meta_exists keyname, "m1", metadata["m1"] + h.ensure_meta_exists keyname, "m2", metadata["m2"] + provider.resource[:metadata]= metadata + end + + context "should get metadata values" do + it "as a hash" do + got_meta = provider.metadata + + expect(got_meta).to be_a_kind_of Hash + expect(got_meta["m1"]).to eq "v1" + expect(got_meta["m2"]).to eq "v2" + end + + # otherwise, Puppet will think we have to update something and + # triggers an update for metadata. + it "but not include unspecified keys if 'purge_meta_keys' is not set" do + h.ensure_meta_exists keyname, "m3", "xxx" + + got_meta = provider.metadata + + expect(got_meta.include? "m1").to eq true + expect(got_meta.include? "m2").to eq true + expect(got_meta.include? "m3").to eq false + end + + it "and ignore 'internal' metakeys" do + h.ensure_meta_exists keyname, "internal/ini/order", "5" + h.ensure_meta_exists keyname, "internal/ini/parent", "xxx" + + got_meta = provider.metadata + + expect(got_meta.include? "internal/ini/order").to eq false + expect(got_meta.include? "internal/ini/parent").to eq false + end + + it "and ignore 'internal' metakeys with 'purge_meta_keys' set" do + h.ensure_meta_exists keyname, "internal/ini/order", "5" + h.ensure_meta_exists keyname, "internal/ini/parent", "xxx" + h.ensure_meta_exists keyname, "comment/#0", "xxx" + h.ensure_meta_exists keyname, "comments/#0", "xxx" + h.ensure_meta_exists keyname, "comments", "#1" + h.ensure_meta_exists keyname, "order", "5" + + provider.resource[:purge_meta_keys] = true + got_meta = provider.metadata + + expect(got_meta.include? "internal/ini/order").to eq false + expect(got_meta.include? "internal/ini/parent").to eq false + expect(got_meta.include? "comment/#0").to eq false + expect(got_meta.include? "comments/#0").to eq false + expect(got_meta.include? "comments").to eq false + expect(got_meta.include? "order").to eq false + end + + it "and ignore 'special' metakeys with 'purge_meta_key' unless specified" do + h.ensure_meta_exists keyname, "comments/#0", "xxx" + h.ensure_meta_exists keyname, "comments", "#1" + + metadata["comments/#0"] = "xxx" + metadata["comments"] = "#1" + provider.resource[:metadata] = metadata + provider.resource[:purge_meta_keys] = true + + got_meta = provider.metadata + + expect(got_meta.include? "comments/#0").to eq true + expect(got_meta.include? "comments").to eq true + end + end + + context "should update the metadata" do + it "with missing metadata key" do + metadata["m3"] = "v3" + provider.resource[:metadata]= metadata + provider.metadata= metadata + provider.flush + + got_meta = provider.metadata + + expect(got_meta.include? "m3").to eq true + expect(got_meta["m3"]).to eq "v3" + end + + it "with existing metadata" do + got_meta = provider.metadata + + expect(got_meta.include? "m1").to eq true + expect(got_meta.include? "m2").to eq true + expect(got_meta["m1"]).to eq "v1" + expect(got_meta["m2"]).to eq "v2" + end + end + + context "should purge not specified metadata if 'purge_meta_keys' is set" do + before :example do + h.ensure_meta_exists keyname, "r1", "to remove" + h.ensure_meta_exists keyname, "r2", "to remove" + provider.resource[:purge_meta_keys] = true + end + + def has_expected_but_not_specified(got_meta) + expect(got_meta.include? "m1").to eq true + expect(got_meta.include? "m2").to eq true + expect(got_meta.include? "r1").to eq false + expect(got_meta.include? "r2").to eq false + + expect(got_meta["m1"]).to eq "v1" + expect(got_meta["m2"]).to eq "v2" + end + + it "while updating specified" do + h.ensure_meta_exists keyname, "m1", "old value" + provider.metadata= metadata + provider.flush + + got_meta = provider.metadata + + expect(h.check_meta_exists keyname, "m1").to eq true + expect(h.check_meta_exists keyname, "m2").to eq true + expect(h.check_meta_exists keyname, "r1").to eq false + expect(h.check_meta_exists keyname, "r2").to eq false + has_expected_but_not_specified got_meta + end + + it "while ignoring comments, which are not modified" do + h.ensure_comment_exists keyname, "some comment" + + provider.metadata= metadata + provider.flush + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_comment_exists keyname).to eq true + expect(h.key_get_comment keyname).to eq "some comment" + end + it "while ignoring comments, which are added too (before)" do + provider.comments= "some comment" + provider.metadata= metadata + provider.flush + + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_comment_exists keyname).to eq true + expect(h.key_get_comment keyname).to eq "some comment" + end + + it "while ignoring comments, which are added too (after)" do + provider.metadata= metadata + provider.comments= "some comment" + provider.flush + + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_comment_exists keyname).to eq true + expect(h.key_get_comment keyname).to eq "some comment" + end + + unless not_testable_with_kdb + it "while ignoring 'internal/' metadata keys" do + h.ensure_meta_exists keyname, "internal/test1", "to keep" + + provider.metadata= metadata + provider.flush + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_meta_exists keyname, "internal/test1").to eq true + expect(h.key_get_meta keyname, "internal/test1").to eq "to keep" + end + end + + it "while ignoring 'order' metadata" do + h.ensure_meta_exists keyname, "order", "5" + + provider.metadata= metadata + provider.flush + got_meta = provider.metadata + + has_expected_but_not_specified got_meta + expect(h.check_meta_exists keyname, "order").to eq true + expect(h.key_get_meta keyname, "order").to eq "5" + end + end + end + + context "should handle comments" do + it "and fetch the comment string" do + h.ensure_comment_exists keyname, "my comment" + + expect(provider.comments).to eq "my comment" + end + + it "and fetch a multiline comment string at once" do + expected_comment = < "one", + "check/enum/#1" => "two", + "check/enum/#2" => "three" + } + exp_checks.each do |k,v| + h.ensure_meta_exists provider.get_spec_key_name, k, v + end + + got_check = provider.check + + expect(got_check.include? "enum").to eq true + expect(got_check.include? "enum/#0").to eq false + expect(got_check["enum"]).to eq exp_checks.values + end + + it "set spec for a single String check" do + h.ensure_meta_is_missing provider.get_spec_key_name, "check/path" + + provider.check= "path" + provider.flush + + expect( + h.check_meta_exists provider.get_spec_key_name, "check/path" + ).to eq true + expect( + h.key_get_meta provider.get_spec_key_name, "check/path" + ).to eq "" + end + + it "set spec for a single Hash check" do + h.ensure_meta_is_missing provider.get_spec_key_name, "check/type" + + provider.check= {"type" => "long"} + provider.flush + + expect( + h.check_meta_exists provider.get_spec_key_name, "check/type" + ).to eq true + expect( + h.key_get_meta provider.get_spec_key_name, "check/type" + ).to eq "long" + end + + it "set spec for multiple checks" do + exp_check = { + "type" => "long", + "type/min" => "5", + "type/max" => "10" + } + + exp_check.keys.each do |c| + h.ensure_meta_is_missing provider.get_spec_key_name, "check/#{c}" + end + + provider.check= exp_check + provider.flush + + exp_check.each do |c, v| + expect( + h.key_get_meta provider.get_spec_key_name, "check/#{c}" + ).to eq v + end + end + + context "set spec to/from array values" do + let(:spec_key) { provider.get_spec_key_name } + let(:exp_check) do { + "enum/#0" => "one", + "enum/#1" => "two", + "enum/#2" => "three" + } + end + + it "set spec for a single check with multiple values (array)" do + exp_check.each do |c, v| + h.ensure_meta_is_missing spec_key, "check/#{c}" + end + + provider.check= {"enum" => exp_check.values} + provider.flush + + exp_check.each do |c, v| + expect( + h.key_get_meta spec_key, "check/#{c}" + ).to eq v + end + end + + it "update spec for a single check with multiple values (array)" do + { "enum/#0" => "x0", + "enum/#1" => "x1", + "enum/#2" => "x2", + "enum/#3" => "x3"}.each do |k,v| + h.ensure_meta_exists spec_key, "check/#{k}", v + end + + provider.check= { "enum" => exp_check.values } + provider.flush + + exp_check.each do |c, v| + expect( + h.key_get_meta spec_key, "check/#{c}" + ).to eq v + end + expect( + h.check_meta_exists spec_key, "check/enum/#3" + ).to eq false + end + + it "set spec and removes all other 'check/' meta keys" do + missing_check = { "enum" => "'one', 'two'", + "enum/#0" => "x1", + "enum/#1" => "x2", + "type" => "short" + } + missing_check.each do |k,v| + h.ensure_meta_exists spec_key, "check/#{k}", v + end + # to ensure, non 'check' metakeys are not touched + h.ensure_meta_exists spec_key, "other/xy", "xxx" + + provider.check= "path" + provider.flush + + missing_check.each do |k,v| + expect(h.check_meta_exists spec_key, "check/#{k}").to eq false + end + expect(h.check_meta_exists spec_key, "check/path").to eq true + + expect(h.check_meta_exists spec_key, "other/xy").to eq true + expect(h.key_get_meta spec_key, "other/xy").to eq "xxx" + end + end + end +end diff --git a/spec/unit/provider/key_kdb_helper.rb b/spec/unit/provider/key_kdb_helper.rb index f096ce0..c125afc 100644 --- a/spec/unit/provider/key_kdb_helper.rb +++ b/spec/unit/provider/key_kdb_helper.rb @@ -34,84 +34,86 @@ def do_on_kdb raise ArgumentError, "block required" unless block_given? Kdb.open do |kdb| - ks = Kdb::KeySet.new - kdb.get ks, @test_prefix - result = yield ks - kdb.set ks, @test_prefix + # make cascading + @test_prefix.gsub!(/^\w+\//, '/') + @ks = Kdb::KeySet.new + kdb.get @ks, @test_prefix + result = yield + kdb.set @ks, @test_prefix return result end end def ensure_key_exists(keyname, value = "test") - do_on_kdb do |ks| - ks_ensure_key_exists ks, keyname, value + do_on_kdb do + ks_ensure_key_exists keyname, value end end def ensure_meta_exists(keyname, meta, value = "test") - do_on_kdb do |ks| - ks_ensure_meta_exists ks, keyname, meta, value + do_on_kdb do + ks_ensure_meta_exists keyname, meta, value end end def ensure_comment_exists(keyname, comment = "test") - do_on_kdb do |ks| - ks_ensure_comment_exists ks, keyname, comment + do_on_kdb do + ks_ensure_comment_exists keyname, comment end end def ensure_key_is_missing(keyname) - do_on_kdb do |ks| - ks_ensure_key_is_missing ks, keyname + do_on_kdb do + ks_ensure_key_is_missing keyname end end def ensure_meta_is_missing(keyname, meta) - do_on_kdb do |ks| - ks_ensure_meta_is_missing ks, keyname, meta + do_on_kdb do + ks_ensure_meta_is_missing keyname, meta end end def ensure_comment_is_missing(keyname) - do_on_kdb do |ks| - ks_ensure_comment_is_missing ks, keyname + do_on_kdb do + ks_ensure_comment_is_missing keyname end end def check_key_exists(name) - do_on_kdb do |ks| - ks_check_key_exists ks, name + do_on_kdb do + ks_check_key_exists name end end def check_meta_exists(keyname, meta) - do_on_kdb do |ks| - ks_check_meta_exists ks, keyname, meta + do_on_kdb do + ks_check_meta_exists keyname, meta end end def check_comment_exists(keyname) - do_on_kdb do |ks| - ks_check_comment_exists ks, keyname + do_on_kdb do + ks_check_comment_exists keyname end end def key_get_value(keyname) - do_on_kdb do |ks| - ks_key_get_value ks, keyname + do_on_kdb do + ks_key_get_value keyname end end def key_get_meta(keyname, meta) - do_on_kdb do |ks| - ks_key_get_meta ks, keyname, meta + do_on_kdb do + ks_key_get_meta keyname, meta end end def key_get_comment(keyname) - do_on_kdb do |ks| - ks_key_get_comment ks, keyname + do_on_kdb do + ks_key_get_comment keyname end end diff --git a/spec/unit/provider/key_ruby_helper.rb b/spec/unit/provider/key_ruby_helper.rb index 8507f6b..a128b40 100644 --- a/spec/unit/provider/key_ruby_helper.rb +++ b/spec/unit/provider/key_ruby_helper.rb @@ -12,33 +12,35 @@ class KdbKeyProviderHelper attr :test_prefix + attr :ks - def initialize(_test_prefix) + def initialize(_test_prefix, _ks = Kdb::KeySet.new) @test_prefix = _test_prefix + @ks = _ks end - def ensure_key_exists(ks, keyname, value = "test") - key = ks.lookup keyname + def ensure_key_exists(keyname, value = "test") + key = @ks.lookup keyname if key.nil? - ks << key = Kdb::Key.new(keyname) + @ks << key = Kdb::Key.new(keyname) end key.value = value end - def ensure_meta_exists(ks, keyname, meta, value = "test") - key = ks.lookup keyname + def ensure_meta_exists(keyname, meta, value = "test") + key = @ks.lookup keyname if key.nil? key = Kdb::Key.new(keyname) - ks << key + @ks << key end key.set_meta meta, value end - def ensure_comment_exists(ks, keyname, comment = "test") - key = ks.lookup keyname + def ensure_comment_exists(keyname, comment = "test") + key = @ks.lookup keyname if key.nil? key = Kdb::Key.new keyname - ks << key + @ks << key end # delete old comment first key.meta.each do |e| @@ -51,19 +53,19 @@ def ensure_comment_exists(ks, keyname, comment = "test") end end - def ensure_key_is_missing(ks, keyname) - unless ks.lookup(keyname).nil? - ks.delete keyname + def ensure_key_is_missing(keyname) + unless @ks.lookup(keyname).nil? + @ks.delete keyname end end - def ensure_meta_is_missing(ks, keyname, meta) - key = ks.lookup keyname + def ensure_meta_is_missing(keyname, meta) + key = @ks.lookup keyname key.del_meta meta unless key.nil? end - def ensure_comment_is_missing(ks, keyname) - key = ks.lookup keyname + def ensure_comment_is_missing(keyname) + key = @ks.lookup keyname unless key.nil? key.meta.each do |m| key.del_meta m if m.name.start_with? COMMENT @@ -71,45 +73,45 @@ def ensure_comment_is_missing(ks, keyname) end end - def check_key_exists(ks, name) - !ks.lookup(name).nil? + def check_key_exists(name) + !@ks.lookup(name).nil? end - def check_meta_exists(ks, keyname, meta) - key = ks.lookup keyname + def check_meta_exists(keyname, meta) + key = @ks.lookup keyname unless key.nil? return key.has_meta? meta end false end - def check_comment_exists(ks, keyname) - key = ks.lookup keyname + def check_comment_exists(keyname) + key = @ks.lookup keyname unless key.nil? return (key.has_meta?(COMMENT) or key.has_meta?(COMMENT+"/#0")) end false end - def key_get_value(ks, keyname) - key = ks.lookup keyname + def key_get_value(keyname) + key = @ks.lookup keyname unless key.nil? return key.value end nil end - def key_get_meta(ks, keyname, meta) - key = ks.lookup keyname + def key_get_meta(keyname, meta) + key = @ks.lookup keyname unless key.nil? return key[meta] end nil end - def key_get_comment(ks, keyname) + def key_get_comment(keyname) comment = nil - key = ks.lookup keyname + key = @ks.lookup keyname unless key.nil? key.meta.find_all do |e| e.name.start_with? COMMENT+"/#" diff --git a/spec/unit/type/kdbkey_spec.rb b/spec/unit/type/kdbkey_spec.rb index af18c7c..2e306d6 100644 --- a/spec/unit/type/kdbkey_spec.rb +++ b/spec/unit/type/kdbkey_spec.rb @@ -187,6 +187,41 @@ end end + context "property 'check'" do + let(:params) { {:name => "user/test/puppet/x1"} } + it "exists and is optional" do + expect(described_class.new(params)[:check]).to be_nil + end + + it "accepts a Hash" do + params[:check] = {"type" => "short"} + expect(described_class.new(params)[:check]).to eq params[:check] + end + + it "accepts a String" do + params[:check] = "path" + expect(described_class.new(params)[:check]).to eq params[:check] + end + + it "rejects values for a key within the 'spec' namespace" do + params[:name] = "spec/test/puppet/x1" + params[:check] = "path" + expect { + described_class.new(params) + }.to raise_error(Puppet::Error) + end + + it "rejects values for a key within the 'spec' namespace "\ + "and prefix is used" do + params[:prefix] = "spec/test" + params[:name] = "puppet/x1" + params[:check] = "path" + expect { + described_class.new(params) + }.to raise_error(Puppet::Error) + end + end + context "parameter 'user'" do let(:params) { {:name => "user/test/puppet/x1"} } it "exists and is optional" do From 2c87bd08bc6a60dd1991d39e2e6ed264afbe5697 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 28 Feb 2017 12:19:32 +0100 Subject: [PATCH 24/52] kdbkey: ruby provider is always the default --- lib/puppet/provider/kdbkey/ruby.rb | 7 +++++-- lib/puppet/provider/kdbmount/ruby.rb | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index fc069db..ab7736e 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -23,8 +23,11 @@ module Puppet @@have_kdb = false end - # make this provider default for Linux systems - defaultfor :kernel => :Linux + # make this provider always to be default (aslong it is useable + #defaultfor :kernel => :Linux + def self.default? + @@have_kdb + end # if we can load the 'kdb' extension confine :true => @@have_kdb diff --git a/lib/puppet/provider/kdbmount/ruby.rb b/lib/puppet/provider/kdbmount/ruby.rb index aa6b109..91fc275 100644 --- a/lib/puppet/provider/kdbmount/ruby.rb +++ b/lib/puppet/provider/kdbmount/ruby.rb @@ -16,7 +16,7 @@ module Puppet begin require 'kdbtools' - rescue + rescue LoadError @@have_kdb = false end From 9bd6de725928da80ed860073cb978163ada0bfe6 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 28 Feb 2017 16:27:58 +0100 Subject: [PATCH 25/52] kdbkey: autorequire kdbmounts autorequire all possible kdbmounts this key can be affected by --- examples/kdbkey.pp | 29 ++++++++++++++++++++ lib/puppet/type/kdbkey.rb | 50 +++++++++++++++++++++++++++++++++++ spec/unit/type/kdbkey_spec.rb | 43 ++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/examples/kdbkey.pp b/examples/kdbkey.pp index 05c4648..4b4b5d6 100644 --- a/examples/kdbkey.pp +++ b/examples/kdbkey.pp @@ -182,3 +182,32 @@ check => { type => short }, value => 5 } + + + +# +# autorequire +# +$mount_ar = 'user/test/puppet-ar' + +kdbkey { 'x1': + ensure => present, + prefix => $mount_ar, +} + +kdbkey { 'x2': + prefix => $mount_ar, + value => "hello" +} + +kdbkey { "$mount_ar/x3": value => 'xx3' ; + "$mount_ar/x4": value => 'xx4' ; + "$mount_ar/x5": value => 'xx5' ; + "$mount_ar/x6": value => 'xx6' ; + "$mount_ar/x7": value => 'xx7' ; +} + +kdbmount { $mount_ar: + file => 'puppet-arxx.ini', + plugins => ['ini', 'type'] +} diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index 88aefac..472bc82 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -318,4 +318,54 @@ def change_to_s(current_value, new_value) end + autorequire(:kdbmount) do + if self[:name].is_a? String + + # split name into path elements, so token separated by '/' not including + # escaped '/' occurrences + # Thus, when we detect a '\\' at the end of a token, we do not want + # to split this up + names = self[:name].split '/' + remember = nil + names.collect! do |token| + next unless token.is_a? String + next if token.empty? + if token[-1] == '\\' + remember ||= "" + remember << "/" unless remember.empty? + remember << token + next + elsif !remember.nil? + ret = remember << "/" + token + remember = nil + ret + else + token + end + end + # the previous escaped / token joining returns nils, so remove them + names.compact! + + # generate an array where each element is joined (by a '/') with its + # previous elements + req_resources = [names.shift] + names.each do |n| + req_resources << req_resources.last + "/" + n + end + + # if we have a cascading key, we could access any possible Elektra + # namespace, thus we autorequire all of them + if self[:name][0] == '/' + ns_res = [] + ["system", "user", "spec", "dir"].each do |ns| + req_resources.each do |name| + ns_res << ns + '/' + name + end + end + req_resources = ns_res + end + req_resources + end + end + end diff --git a/spec/unit/type/kdbkey_spec.rb b/spec/unit/type/kdbkey_spec.rb index 2e306d6..3d04c5a 100644 --- a/spec/unit/type/kdbkey_spec.rb +++ b/spec/unit/type/kdbkey_spec.rb @@ -229,4 +229,47 @@ end end + context "auto requires kdbmounts" do + + RSpec.shared_examples "autorequire kdbmounts" do |keyname, expected| + it "each possible mountpoint for '#{keyname}'" do + t = described_class.new({:name => keyname}) + list = [] + types = [] + t.class.eachautorequire do |type,block| + types << type + list << t.instance_eval(&block) + end + list.flatten! + + expect(types).to eq [:kdbmount] + expect(list).to eq expected + end + end + + include_examples "autorequire kdbmounts", "user/test", ["user", "user/test"] + include_examples "autorequire kdbmounts", + "system/test", + ["system", "system/test"] + + include_examples "autorequire kdbmounts", + "system/test/hello/world", + ["system", "system/test", "system/test/hello", "system/test/hello/world"] + + include_examples "autorequire kdbmounts", + 'system/test/hello\/escaped\/world/a/b', + ["system", "system/test", 'system/test/hello\/escaped\/world', + 'system/test/hello\/escaped\/world/a', + 'system/test/hello\/escaped\/world/a/b'] + + include_examples "autorequire kdbmounts", "/test/puppet", [ + "system/test", "system/test/puppet", + "user/test", "user/test/puppet", + "spec/test", "spec/test/puppet", + "dir/test", "dir/test/puppet" + ] + + end + + end From fc30dac8bd1a3ba193db723793e182eafb9efc91 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 28 Feb 2017 19:57:52 +0100 Subject: [PATCH 26/52] kdbkey provider ruby: kdb.open/get for each managed key --- lib/puppet/provider/kdbkey/ruby.rb | 135 ++++++++++++++++++------- spec/unit/provider/kdbkey_ruby_spec.rb | 2 +- 2 files changed, 98 insertions(+), 39 deletions(-) diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index ab7736e..384746f 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -31,55 +31,86 @@ def self.default? # if we can load the 'kdb' extension confine :true => @@have_kdb - if @@have_kdb - Puppet.debug "kdbkey/ruby: open kdb db" - @@db = Kdb.open - @@ks = Kdb::KeySet.new - @@db.get @@ks, "/" - end + # remember all opened kdb handles + # since there is not suitable way to a propper provider instance + # cleanup. + # The flush method is only called, if the underlaying resource was + # modified. + # All opened handles will be closed on 'self.post_resource_eval' + # which is done once per provider class. + @@open_handles = [] # just used during testing to inject a mock - def self.use_fake_ks(ks) - @@ks = ks - @@is_fake_ks = true + def use_fake_ks(ks) + @ks = ks + @is_fake_ks = true end # allow access to internal key, used during testing attr_reader :resource_key def create - #puts "ruby create #{@resource[:name]}" @resource_key = Kdb::Key.new @resource[:name], value: @resource[:value] self.check= @resource[:check] unless @resource[:check].nil? self.metadata= @resource[:metadata] unless @resource[:metadata].nil? self.comments= @resource[:comments] unless @resource[:comments].nil? - @@ks << @resource_key + @ks << @resource_key end def destroy - #puts "ruby destroy #{@resource[:name]}" - @@ks.delete @resource[:name] unless @resource_key.nil? + @ks.delete @resource[:name] unless @resource_key.nil? end # is called first for each managed resource # stores the queried key for later modifications def exists? Puppet.debug "kdbkey/ruby exists? #{@resource[:name]}" - #puts "kdbkey/ruby @should: #{value(:metadata)}" - @resource_key = @@ks.lookup @resource[:name] + + # this is the first method call for a managed resource + # so, here we have to do a kdb.open + # all opend kdb objects are used by later methods so keep them + # + # note: for the moment we do a kdb.open/get/set for EACH managed + # kdbkey resource separately. This results in a opened kdb handle + # and keySet for each manged key. This strategy is required, since + # we might have modified the underlying Elektra key space + # (a changed/added mountpoint after the actual kdb.open). + # + # It would be better if we could share our handles and keysets, but: + # - one shared handle and keyset is definitely too less, for the following reasons + # - actually there is no way to guarantee that all kdbmount modifications happen + # BEFORE the first kdb.open call + # - since we not really know here, which resource keys we have to manage, we end + # up with fetching the whole Elektra key space, which would be way too much + # - a better strategy would be to use one handle and keyset per mountpoint. But for + # the moment this is too complicated (e.g. how to proceed with cascading keys?) + # + begin + @kdb_handle = Kdb.open + @@open_handles << @kdb_handle + @ks = Kdb::KeySet.new + @cascading_key = @resource[:name].gsub(/^\w+\//, '/') + puts "do kdb.get ks, #{@cascading_key}" if @verbose + @kdb_handle.get @ks, @cascading_key + @ks.pretty_print if @verbose + end unless @is_fake_ks + @resource_key = @ks.lookup @resource[:name] + puts "resource key nil? #{@resource_key.nil?}" if @verbose return !@resource_key.nil? end def value - #puts "getting value #{@resource[:name]}" return @resource_key.value unless @resource_key.nil? end def value=(value) - #puts "setting value of #{@resource[:name]} to #{value}" @resource_key.value= value unless @resource_key.nil? end + # get metadata values as Hash + # note: in order to not trigger an refresh cycle, we have to be careful which + # keys should be returned. It 'purge_meta_keys?' is not set, we have to remove + # the not-specified metakeys from the result set. def metadata #key.meta.to_h unless key.nil? ruby 1.9 does not have Enumerable.to_h :( res = Hash.new @@ -99,6 +130,9 @@ def metadata return res end + # set metadata values + # if 'purge_meta_keys?' == true, also remove all not specified keys but not + # too much (keeping internal ones) def metadata=(value) # update metadata value.each { |k, v| @@ -115,13 +149,22 @@ def metadata=(value) end end + # currently Elektra plugins implement a not consistent way of specifying + # comments. So store the used metakey name to use the same one when writing the + # comments. see https://github.com/ElektraInitiative/libelektra/issues/1375 + @comments_key_name = "comments" + + # get key comments as one string + # merge the Elektra 'comments?/#' array def comments comments = "" first = true # used for splitting lines # search for all meta keys which names starts with 'comments/#' # and concat its values line by line @resource_key.meta.each do |e| - if e.name.start_with? "comments/#" + if /^(comments?)\/#/ =~ e.name + puts "update comments key name to #{$1}" if @verbose + @comments_key_name = $1 comments << "\n" unless first comments << e.value.sub(/^# ?/, '') first = false @@ -130,36 +173,45 @@ def comments return comments end + # update comments + # def comments=(value) + # why do we have to init this inst var again??? + @comments_key_name ||= "comments" # split specified comment into lines comment_lines = value.split "\n" - # if we do not have any comments, remove them - if comment_lines.size == 0 - @resource_key.del_meta "comments" - else - @resource_key.set_meta "comments", "##{comment_lines.size - 1}" - end - # update all comment lines comment_lines.each_with_index do |line, index| - @resource_key.set_meta "comments/##{index}", "##{line}" + puts "comments keyname: #{@comments_key_name}" if @verbose + @resource_key.set_meta "#{@comments_key_name}/##{index}", "##{line}" end # iterate over all meta keys and remove all comment keys which # represent a comment line, which does not exist any more @resource_key.meta.each do |e| - if e.name.match(/^comments\/#(\d+)$/) - index = $~[1].to_i + if e.name.match(/^#{@comments_key_name}\/#(\d+)$/) + index = $1.to_i if comment_lines[index].nil? @resource_key.del_meta e.name end end end + + # the (old) ini plugin comments strategy uses a 'comments' metakey + # to store the last comments array index. This has to be updated. + if comment_lines.size > 0 and @comments_key_name == "comments" + @resource_key.set_meta "comments", "##{comment_lines.size - 1}" + else + @resource_key.del_meta "comments" + end + @resource_key.pretty_print if @verbose end + # get all 'check/*' meta keys of the corresponding 'spec/' key + # def check spec_hash = {} - spec_key = @@ks.lookup get_spec_key_name + spec_key = @ks.lookup get_spec_key_name unless spec_key.nil? spec_key.meta.each do |m| if /^check\/(.*)$/ =~ m.name @@ -178,18 +230,19 @@ def check if spec_hash.size == 1 and spec_hash.values[0] == "" spec_hash = spec_hash.keys[0] end - Puppet.debug "having spec: #{spec_hash}" return spec_hash end + # update 'check/*' meta data on the corresponding 'spec/' key + # def check=(value) Puppet.debug "setting spec: #{value}" spec_key = Kdb::Key.new get_spec_key_name - if @@ks.lookup(spec_key).nil? - @@ks << spec_key + if @ks.lookup(spec_key).nil? + @ks << spec_key else - spec_key = @@ks.lookup spec_key + spec_key = @ks.lookup spec_key end spec_to_set = specified_checks_to_meta value @@ -211,18 +264,24 @@ def check=(value) # flush is call if a resource was modified # thus this method is perfectly suitable for our db.set method which will # finally bring the changes to disk + # also do a kdbclose for this handle def flush - unless @@is_fake_ks + unless @is_fake_ks Puppet.debug "kdbkey/ruby: flush #{@resource[:name]}" - @@db.set @@ks, "/" + @kdb_handle.set @ks, @cascading_key + @@open_handles.delete @kdb_handle + @kdb_handle.close end end - # this is the provider de-init hook - # so lets close our kdb db now + # provider de-init hook + # this is our last chance to close remaining kdb handles def self.post_resource_eval Puppet.debug "kdbkey/ruby: closing kdb db" - @@db.close if @@have_kdb + @@open_handles.delete_if do |handle| + handle.close + true + end end end diff --git a/spec/unit/provider/kdbkey_ruby_spec.rb b/spec/unit/provider/kdbkey_ruby_spec.rb index 235d990..b098b40 100644 --- a/spec/unit/provider/kdbkey_ruby_spec.rb +++ b/spec/unit/provider/kdbkey_ruby_spec.rb @@ -21,7 +21,7 @@ let(:provider) { described_class.new } before :example do - described_class.use_fake_ks h.ks + provider.use_fake_ks h.ks provider.resource = create_resource :name => keyname end From cf3e6f0882482935802c3879333acb7a5315d4c1 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 28 Feb 2017 20:02:04 +0100 Subject: [PATCH 27/52] kdbkey kdb provider: fix missing spec creation on resource create --- lib/puppet/provider/kdbkey/kdb.rb | 3 ++- spec/unit/provider/key_helper.rb | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index bdead02..25a0f4e 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -24,7 +24,8 @@ def run_kdb(args, params = {:combine => true, :failonfail => true}) end def create - self.value=(@resource[:value]) + self.value= @resource[:value] + self.check= @resource[:check] unless @resource[:check].nil? self.metadata= @resource[:metadata] unless @resource[:metadata].nil? self.comments= @resource[:comments] unless @resource[:comments].nil? end diff --git a/spec/unit/provider/key_helper.rb b/spec/unit/provider/key_helper.rb index 3457886..fd31ecd 100644 --- a/spec/unit/provider/key_helper.rb +++ b/spec/unit/provider/key_helper.rb @@ -44,6 +44,7 @@ def create_resource(params) context "should create key" do before :example do h.ensure_key_is_missing keyname + h.ensure_key_is_missing provider.get_spec_key_name end it "with defined name" do @@ -101,6 +102,33 @@ def create_resource(params) expect(h.key_get_comment keyname).to eq comments end + it "with defined name, value, meta, comments and spec" do + value = "5" + meta = {"nvmcs" => "some value"} + comments = "other comments" + checks = {'type' => 'short'} + + provider.resource = create_resource :name => keyname, + :value => value, + :metadata => meta, + :comments => comments, + :check => checks + + provider.create + provider.flush + + expect(h.check_key_exists keyname).to eq true + expect(h.key_get_value keyname).to eq value + meta.each do |k, v| + expect(h.key_get_meta keyname, k).to eq(v) + end + expect(h.key_get_comment keyname).to eq comments + expect(h.check_key_exists provider.get_spec_key_name).to eq true + expect( + h.key_get_meta provider.get_spec_key_name, 'check/type' + ).to eq 'short' + end + end From debdc13da69367b597b8fc6fa94ece598fe47229 Mon Sep 17 00:00:00 2001 From: bernhard Date: Mon, 13 Mar 2017 12:17:14 +0100 Subject: [PATCH 28/52] kdbkey provider: check with array fix enum requires to have the last array index set for its root key e.g. check/enum/#0 = one check/enum/#1 = two check/enum = #1 <==== --- lib/puppet/provider/kdbkey/common.rb | 3 +++ spec/unit/provider/key_helper.rb | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/puppet/provider/kdbkey/common.rb b/lib/puppet/provider/kdbkey/common.rb index a48aa33..fdaaa46 100644 --- a/lib/puppet/provider/kdbkey/common.rb +++ b/lib/puppet/provider/kdbkey/common.rb @@ -51,6 +51,9 @@ def specified_checks_to_meta(value) check_value.each_with_index do |v, index| spec_to_set["check/#{check_key}/##{index}"] = v end + # at least the 'enum' plugin requires to have the last array index + # set at its root key => check/enum = #x + spec_to_set["check/#{check_key}"] = "##{check_value.size - 1}" else spec_to_set["check/#{check_key}"] = check_value end diff --git a/spec/unit/provider/key_helper.rb b/spec/unit/provider/key_helper.rb index fd31ecd..192162a 100644 --- a/spec/unit/provider/key_helper.rb +++ b/spec/unit/provider/key_helper.rb @@ -540,10 +540,12 @@ def has_expected_but_not_specified(got_meta) h.key_get_meta spec_key, "check/#{c}" ).to eq v end + expect(h.key_get_meta spec_key, "check/enum").to eq "#2" end it "update spec for a single check with multiple values (array)" do - { "enum/#0" => "x0", + { "enum" => "#3", + "enum/#0" => "x0", "enum/#1" => "x1", "enum/#2" => "x2", "enum/#3" => "x3"}.each do |k,v| @@ -558,6 +560,7 @@ def has_expected_but_not_specified(got_meta) h.key_get_meta spec_key, "check/#{c}" ).to eq v end + expect(h.key_get_meta spec_key, "check/enum").to eq "#2" expect( h.check_meta_exists spec_key, "check/enum/#3" ).to eq false From 8a43527bc49064d4e81f4cca7f4148bab3c4371b Mon Sep 17 00:00:00 2001 From: bernhard Date: Sun, 19 Mar 2017 18:55:51 +0100 Subject: [PATCH 29/52] kdbkey provider: add missing require 'common' --- lib/puppet/provider/kdbkey/kdb.rb | 8 +++++--- lib/puppet/provider/kdbkey/ruby.rb | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 25a0f4e..700b36b 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -7,6 +7,8 @@ # @copyright BSD License (see LICENSE or http://www.libelektra.org) # # + +require 'puppet/provider/kdbkey/common' require 'tempfile' module Puppet @@ -41,7 +43,7 @@ def exists? output.exitstatus == 0 end - def value + def value run_kdb ["sget", "--color=never", @resource[:name], "''"] end @@ -87,7 +89,7 @@ def metadata # do not keep this key_name delete = ( # if it is an internal key (unless specified) - skip_this_metakey?(k, true) or + skip_this_metakey?(k, true) or # or unless purge_meta_keys == true or k is specified not( @resource[:metadata].nil? or @resource.purge_meta_keys? or @@ -126,7 +128,7 @@ def comments comments[$1] = value.sub(/^#/, '') end end - # we get a hash, with + # we get a hash, with # #num => line # so sort by #num, take the lines and join with newline comments = comments.sort_by{|k,v| k}.map{|e|e[1]}.join "\n" diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 384746f..e9eede7 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -8,6 +8,8 @@ # # +require 'puppet/provider/kdbkey/common' + module Puppet Type.type(:kdbkey).provide :ruby, :parent => Puppet::Provider::KdbKeyCommon do desc "kdb through libelektra Ruby API" @@ -149,7 +151,7 @@ def metadata=(value) end end - # currently Elektra plugins implement a not consistent way of specifying + # currently Elektra plugins implement a not consistent way of specifying # comments. So store the used metakey name to use the same one when writing the # comments. see https://github.com/ElektraInitiative/libelektra/issues/1375 @comments_key_name = "comments" @@ -183,8 +185,11 @@ def comments=(value) # update all comment lines comment_lines.each_with_index do |line, index| puts "comments keyname: #{@comments_key_name}" if @verbose + # currently hosts plugin treats #0 comment as inline comment + #@resource_key.set_meta "#{@comments_key_name}/##{index + 1}", "##{line}" @resource_key.set_meta "#{@comments_key_name}/##{index}", "##{line}" end + #@resource_key.set_meta "#{@comments_key_name}/#0", '' # iterate over all meta keys and remove all comment keys which # represent a comment line, which does not exist any more From a9019b710332f17d6493b77a4ec0446cc655deac Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 21 Mar 2017 13:20:48 +0100 Subject: [PATCH 30/52] kdbkey: manage elektra array values Allow to manage Elektra array values. Elektra uses the following format for storing array values: .../key = .../key/#0 = first .../key/#1 = second ... When passing an array to the kdbkey value property, update the associated key to reflect this array value (and vice versa). --- lib/puppet/provider/kdbkey/kdb.rb | 57 ++++++++++++++++++- lib/puppet/provider/kdbkey/ruby.rb | 47 +++++++++++++++- lib/puppet/type/kdbkey.rb | 2 +- spec/unit/provider/key_helper.rb | 88 +++++++++++++++++++++++++++++- spec/unit/type/kdbkey_spec.rb | 8 ++- 5 files changed, 192 insertions(+), 10 deletions(-) diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 700b36b..026af19 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -44,11 +44,58 @@ def exists? end def value - run_kdb ["sget", "--color=never", @resource[:name], "''"] + output = run_kdb ["ls", "--color=never", @resource[:name]] + elems = output.split + + unless elems.include? "#{@resource[:name]}/#0" + # single value key + return [get_key_value(@resource[:name])] + else + # Array key + value = [] + elems.select do |x| + x =~ /^#{@resource[:name]}\/#\d+$/ + end.each do |x| + value << get_key_value(x) + end + return value + end + + end + + def get_key_value(key) + return run_kdb ["sget", "--color=never", key, "''"] end def value=(value) - run_kdb ["set", @resource[:name], value] + remove_from_this_index = 0 + if not value.is_a? Array + set_key_value @resource[:name], value + + elsif value.size == 1 + set_key_value @resource[:name], value[0] + + else + set_key_value @resource[:name], '' + value.each_with_index do |elem_value, index| + set_key_value "#{@resource[:name]}/##{index}", elem_value + end + remove_from_this_index = value.size + end + # remove possible "old" array keys + output = run_kdb ["ls", "--color=never", @resource[:name]] + output.split.each do |x| + if x =~ /^#{@resource[:name]}\/#(\d+)$/ + index = $1.to_i + if index >= remove_from_this_index + run_kdb ['rm', x] + end + end + end + end + + def set_key_value(key, value) + run_kdb ["set", key, value] end def read_metadata_values @@ -206,7 +253,11 @@ def flush return unless @metadata_values.is_a? Hash Tempfile.open("key") do |file| file.puts - file.puts " = #{@resource[:value]}" + if not @resource[:value].is_a? Array + file.puts " = #{@resource[:value]}" + else + file.puts " = #{@resource[:value][0]}" + end file.puts file.puts "[]" @metadata_values.each do |k,v| diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index e9eede7..37a8a76 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -52,7 +52,8 @@ def use_fake_ks(ks) attr_reader :resource_key def create - @resource_key = Kdb::Key.new @resource[:name], value: @resource[:value] + @resource_key = Kdb::Key.new @resource[:name] + self.value= @resource[:value] unless @resource[:value].nil? self.check= @resource[:check] unless @resource[:check].nil? self.metadata= @resource[:metadata] unless @resource[:metadata].nil? self.comments= @resource[:comments] unless @resource[:comments].nil? @@ -102,11 +103,51 @@ def exists? end def value - return @resource_key.value unless @resource_key.nil? + return nil if @resource_key.nil? + return [@resource_key.value] if @ks.lookup("#{@resource_key.name}/#0").nil? + + # array value + value = [] + @ks.select do |x| + x.name.start_with? "#{@resource_key.name}/#" + end.each do + |x| value << x.value + end + value end def value=(value) - @resource_key.value= value unless @resource_key.nil? + if @resource_key.nil? + return + end + + remove_from_this_index = 0 + if not value.is_a? Array + @resource_key.value= value.to_s + + elsif value.size == 1 + @resource_key.value= value[0].to_s + + else + @resource_key.value= '' + value.each_with_index do |elem_value, index| + elem_key_name = "#{@resource_key.name}/##{index}" + elem_key = @ks.lookup elem_key_name + if elem_key.nil? + elem_key = Kdb::Key.new elem_key_name + @ks << elem_key + end + elem_key.value= elem_value.to_s + end + remove_from_this_index = value.size + end + + # remove possible "old" array keys + i = remove_from_this_index + while not (key = @ks.lookup("#{@resource_key.name}/##{i}")).nil? + i += 1 + @ks.delete key + end end # get metadata values as Hash diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index 472bc82..e8b8e08 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -103,7 +103,7 @@ def self.title_patterns end - newproperty(:value) do + newproperty(:value, :array_matching => :all) do desc <<-EOT Desired value of the key. EOT diff --git a/spec/unit/provider/key_helper.rb b/spec/unit/provider/key_helper.rb index 192162a..8a81f92 100644 --- a/spec/unit/provider/key_helper.rb +++ b/spec/unit/provider/key_helper.rb @@ -148,20 +148,104 @@ def create_resource(params) provider.exists? end + context "should read the key's value" do + it "for a single string value" do + expect(provider.value).to eq ["test"] + end + + it "for an Array of strings" do + h.ensure_key_exists keyname, '' + h.ensure_key_exists "#{keyname}/#0", 'one' + h.ensure_key_exists "#{keyname}/#1", 'two' + h.ensure_key_exists "#{keyname}/#2", 'three' + + expect(provider.value).to eq ['one', 'two', 'three'] + end + end + context "should update the key value" do it "to an arbitrary string" do expect(h.key_get_value keyname).to eq "test" - provider.value= "some string value" + provider.value= ["some string value"] provider.flush expect(h.key_get_value keyname).to eq "some string value" end it "to an empty string" do expect(h.key_get_value keyname).to eq "test" - provider.value= "" + provider.value= [""] provider.flush expect(h.key_get_value keyname).to eq "" end + + it "to an truth value" do + provider.value= [true] + provider.flush + expect(h.key_get_value keyname).to eq "true" + end + + it "to a numerical value" do + provider.value= [5] + provider.flush + expect(h.key_get_value keyname).to eq "5" + end + + it "to an array of strings" do + expect(h.key_get_value keyname).to eq "test" + provider.value= ['one', 'two'] + provider.flush + expect(h.key_get_value keyname).to eq '' + expect(h.key_get_value "#{keyname}/#0").to eq 'one' + expect(h.key_get_value "#{keyname}/#1").to eq 'two' + end + + it "to an array of different types" do + provider.value= ["string", 3, true, 5.5] + provider.flush + expect(h.key_get_value keyname).to eq '' + expect(h.key_get_value "#{keyname}/#0").to eq 'string' + expect(h.key_get_value "#{keyname}/#1").to eq '3' + expect(h.key_get_value "#{keyname}/#2").to eq 'true' + expect(h.key_get_value "#{keyname}/#3").to eq '5.5' + end + + it "to an array while removing old array values" do + h.ensure_key_exists keyname, '' + h.ensure_key_exists "#{keyname}/#0", '1' + h.ensure_key_exists "#{keyname}/#1", '2' + h.ensure_key_exists "#{keyname}/#2", '3' + h.ensure_key_exists "#{keyname}/#3", '4' + h.ensure_key_exists "#{keyname}/#4", '5' + + provider.value= ['one', 'two'] + provider.flush + + expect(h.key_get_value keyname).to eq '' + expect(h.key_get_value "#{keyname}/#0").to eq 'one' + expect(h.key_get_value "#{keyname}/#1").to eq 'two' + expect(h.check_key_exists "#{keyname}/#2").to eq false + expect(h.check_key_exists "#{keyname}/#3").to eq false + expect(h.check_key_exists "#{keyname}/#4").to eq false + end + + it "to an string value while removing old array values" do + h.ensure_key_exists keyname, '' + h.ensure_key_exists "#{keyname}/#0", '1' + h.ensure_key_exists "#{keyname}/#1", '2' + h.ensure_key_exists "#{keyname}/#2", '3' + h.ensure_key_exists "#{keyname}/#3", '4' + h.ensure_key_exists "#{keyname}/#4", '5' + + provider.value= "my new string value" + provider.flush + + expect(h.key_get_value keyname).to eq "my new string value" + expect(h.check_key_exists "#{keyname}/#0").to eq false + expect(h.check_key_exists "#{keyname}/#1").to eq false + expect(h.check_key_exists "#{keyname}/#2").to eq false + expect(h.check_key_exists "#{keyname}/#3").to eq false + expect(h.check_key_exists "#{keyname}/#4").to eq false + end end context "and existing metadata" do diff --git a/spec/unit/type/kdbkey_spec.rb b/spec/unit/type/kdbkey_spec.rb index 3d04c5a..f89d38f 100644 --- a/spec/unit/type/kdbkey_spec.rb +++ b/spec/unit/type/kdbkey_spec.rb @@ -122,8 +122,14 @@ context "property 'value'" do let(:params) { {:name => "user/test/puppet/x1", :value => "some value"} } + let(:params_wo_value) { {:name => "user/test/puppet/x1"} } + let(:params_w_array) { {:name => "user/test/puppet/x1", :value => ['one', 'two']} } it "exists and is optional" do - expect(described_class.new(params)[:value]).to eq(params[:value]) + expect(described_class.new(params_wo_value)[:value]).to be_nil + end + it "returns an array of values" do + expect(described_class.new(params)[:value]).to eq([params[:value]]) + expect(described_class.new(params_w_array)[:value]).to eq(params_w_array[:value]) end end From baab4a605417fad983b15c57f2a8e0a7d9dc173c Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 21 Mar 2017 16:30:46 +0100 Subject: [PATCH 31/52] kdbkey: fix removal of arrays --- lib/puppet/provider/kdbkey/kdb.rb | 14 +++++++++++-- lib/puppet/provider/kdbkey/ruby.rb | 14 +++++++++---- spec/unit/provider/key_helper.rb | 32 ++++++++++++++++++++++-------- 3 files changed, 46 insertions(+), 14 deletions(-) diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 026af19..67a9a0f 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -34,6 +34,12 @@ def create def destroy run_kdb ["rm", @resource[:name]] + # remove possible array elements + list_keys.each do |x| + if x =~ /#{@resource[:name]}\/#\d+/ + run_kdb ["rm", x] + end + end end def exists? @@ -43,9 +49,13 @@ def exists? output.exitstatus == 0 end - def value + def list_keys output = run_kdb ["ls", "--color=never", @resource[:name]] - elems = output.split + return output.split + end + + def value + elems = list_keys unless elems.include? "#{@resource[:name]}/#0" # single value key diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 37a8a76..cccade3 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -62,6 +62,12 @@ def create def destroy @ks.delete @resource[:name] unless @resource_key.nil? + # check if there are array keys left + @ks.each do |x| + if x.name =~ /^#{@resource[:name]}\/#\d+$/ + @ks.delete x + end + end end # is called first for each managed resource @@ -108,10 +114,10 @@ def value # array value value = [] - @ks.select do |x| - x.name.start_with? "#{@resource_key.name}/#" - end.each do - |x| value << x.value + @ks.each do |x| + if x.name =~ /^#{@resource_key.name}\/#\d+$/ + value << x.value + end end value end diff --git a/spec/unit/provider/key_helper.rb b/spec/unit/provider/key_helper.rb index 8a81f92..5c62f65 100644 --- a/spec/unit/provider/key_helper.rb +++ b/spec/unit/provider/key_helper.rb @@ -132,14 +132,30 @@ def create_resource(params) end - it "should remove key on destroy" do - h.ensure_key_exists keyname - # we have to call exists? first - provider.exists? - provider.destroy - provider.flush - - expect(h.check_key_exists keyname).to eq false + context "should remove key on destroy" do + it "for single value keys" do + h.ensure_key_exists keyname + # we have to call exists? first + provider.exists? + provider.destroy + provider.flush + + expect(h.check_key_exists keyname).to eq false + end + + it "for array value keys" do + h.ensure_key_exists keyname, '' + h.ensure_key_exists "#{keyname}/#0" 'one' + h.ensure_key_exists "#{keyname}/#1" 'one' + + provider.exists? + provider.destroy + provider.flush + + expect(h.check_key_exists keyname).to eq false + expect(h.check_key_exists "#{keyname}/#0").to eq false + expect(h.check_key_exists "#{keyname}/#1").to eq false + end end context "with existing key" do From 5dd2c7274795ac27343bb5aee681655d70e90078 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 21 Mar 2017 19:44:43 +0100 Subject: [PATCH 32/52] kdbkey: only use error message for exception If an exception occurs only set the error message, do not print any warnings, since the warnings could be misleading because usually warnings are not associated with the manipulated key. --- lib/puppet/provider/kdbkey/ruby.rb | 36 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index cccade3..0f1698c 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -98,8 +98,8 @@ def exists? @kdb_handle = Kdb.open @@open_handles << @kdb_handle @ks = Kdb::KeySet.new - @cascading_key = @resource[:name].gsub(/^\w+\//, '/') - puts "do kdb.get ks, #{@cascading_key}" if @verbose + @cascading_key = Kdb::Key.new @resource[:name].gsub(/^\w+\//, '/') + puts "do kdb.get ks, #{@cascading_key.name}" if @verbose @kdb_handle.get @ks, @cascading_key @ks.pretty_print if @verbose end unless @is_fake_ks @@ -312,6 +312,22 @@ def check=(value) end end + # generate an error string from a Kdb::Key + def key_get_error_msg(key) + return nil unless key.is_a? Kdb::Key + + msg = "" + if key.has_meta? 'error' + msg += key['error/description'] + "\n" + msg += "Reason: #{key['error/reason']}\n" + msg += "Error number: ##{key['error/number']}\n" + msg += "Module: #{key['error/module']}\n" + msg += "Configfile: #{key['error/configfile']}\n" + msg += "Mountpoint: #{key['error/mountpoint']}\n" + end + return msg + end + # flush is call if a resource was modified # thus this method is perfectly suitable for our db.set method which will @@ -319,10 +335,18 @@ def check=(value) # also do a kdbclose for this handle def flush unless @is_fake_ks - Puppet.debug "kdbkey/ruby: flush #{@resource[:name]}" - @kdb_handle.set @ks, @cascading_key - @@open_handles.delete @kdb_handle - @kdb_handle.close + begin + Puppet.debug "kdbkey/ruby: flush #{@resource[:name]}" + @kdb_handle.set @ks, @cascading_key + rescue + # we only care about the error message here, warnings could be + # misleading, especially if they do not concern the key we are + # manipulating + raise Puppet::Error.new key_get_error_msg(@cascading_key) + ensure + @@open_handles.delete @kdb_handle + @kdb_handle.close + end end end From 309414af18110aed8f8821cce0820abe530eefd9 Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 21 Mar 2017 20:15:42 +0100 Subject: [PATCH 33/52] kdbkey: provide custom 'change' message convert single array to string --- lib/puppet/type/kdbkey.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index e8b8e08..8849f84 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -107,6 +107,19 @@ def self.title_patterns desc <<-EOT Desired value of the key. EOT + + def change_to_s(current_value, new_value) + def single_elem_as_string(v) + return "" if v.nil? + if v.is_a? Array and v.size == 1 + return v[0].to_s + else + return v.to_s + end + end + + "value changed '#{single_elem_as_string current_value}' to '#{single_elem_as_string new_value}'" + end end newproperty(:metadata) do From 692be1a646971d63d7b7daa59602f640973ae17d Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 21 Mar 2017 20:24:07 +0100 Subject: [PATCH 34/52] kdbkey: add docu for array handling of value property --- lib/puppet/type/kdbkey.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index 8849f84..9f587fc 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -105,7 +105,12 @@ def self.title_patterns newproperty(:value, :array_matching => :all) do desc <<-EOT - Desired value of the key. + Desired value of the key. This can be any type, however elektra currently + just manages to store String values only. Therefore all types are + implicitly converted to Strings. + + If value is an array, the key is managed as an Elektra array. Therefore + a subkey named `*name*/#` will be created for each array element. EOT def change_to_s(current_value, new_value) From dc22b9054e3ba52ce59f0f802519053a594359bf Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 21 Mar 2017 20:29:38 +0100 Subject: [PATCH 35/52] more useless examples --- examples/hosts.pp | 33 +++++++++++++++++++++++++++++++++ examples/kdbkey.pp | 28 ++++++++++++++-------------- examples/kdbmount.pp | 6 ------ examples/multifile.pp | 19 +++++++++++++++++++ 4 files changed, 66 insertions(+), 20 deletions(-) create mode 100644 examples/hosts.pp create mode 100644 examples/multifile.pp diff --git a/examples/hosts.pp b/examples/hosts.pp new file mode 100644 index 0000000..3842aeb --- /dev/null +++ b/examples/hosts.pp @@ -0,0 +1,33 @@ + +kdbmount { 'system/network/hosts': + ensure => present, + file => 'myhosts', + plugins => 'hosts' +} + +kdbkey { 'dragon': + prefix => 'system/network/hosts/ipv4', + value => '192.168.1.140', + check => 'network' +} + +kdbkey { 'dragon/office': + ensure => present, + # value => '192.168.1.140', + prefix => 'system/network/hosts/ipv4', +} + +kdbkey { 'dragon/dell': + ensure => present, + prefix => 'system/network/hosts/ipv4', +} + +kdbkey { 'blacksheep': + prefix => 'system/network/hosts/ipv4', + value => '192.168.1.118', + check => 'network', + comments => 'headless virtualization host + other + + comment' +} diff --git a/examples/kdbkey.pp b/examples/kdbkey.pp index 4b4b5d6..5e0283a 100644 --- a/examples/kdbkey.pp +++ b/examples/kdbkey.pp @@ -107,19 +107,18 @@ # on the mountpoint corresponding to our settings kdbmount { $ns_validation: file => 'puppet-val.ini', - plugins => ['ini', 'type', 'enum', 'path', 'validation'], + plugins => ['ini', 'type', 'enum', 'validation', 'range'], } # ensure our setting is of type 'short' # (see '$> kdb info type' for other types) kdbkey { 'spec/x1': - prefix => $ns_validation, - value => 5, - check => { - 'type' => 'short' + prefix => $ns_validation, + value => 7, + check => { + 'range' => '0-10' }, - # TODO: autorequire?? - require => Kdbmount[$ns_validation] + provider => 'ruby' } kdbkey { 'spec/x2': @@ -190,21 +189,22 @@ # $mount_ar = 'user/test/puppet-ar' -kdbkey { 'x1': +kdbkey { 's1/x1': ensure => present, prefix => $mount_ar, } -kdbkey { 'x2': +kdbkey { 's3/x2': prefix => $mount_ar, value => "hello" } -kdbkey { "$mount_ar/x3": value => 'xx3' ; - "$mount_ar/x4": value => 'xx4' ; - "$mount_ar/x5": value => 'xx5' ; - "$mount_ar/x6": value => 'xx6' ; - "$mount_ar/x7": value => 'xx7' ; +kdbkey { + "$mount_ar/s2/x3": value => 'xx3', metadata => {'internal/ini/key/number' => '1'}; + "$mount_ar/s2/x4": value => 'xx4', metadata => {'internal/ini/key/number' => '2'}; + "$mount_ar/s2/x5": value => 'xx5', metadata => {'internal/ini/key/number' => '3'}; + "$mount_ar/s2/x6": value => 'xx6', metadata => {'internal/ini/key/number' => '4'}; + "$mount_ar/s2/x7": value => 'xx7', metadata => {'internal/ini/key/number' => '5'}; } kdbmount { $mount_ar: diff --git a/examples/kdbmount.pp b/examples/kdbmount.pp index 744b069..d0391d5 100644 --- a/examples/kdbmount.pp +++ b/examples/kdbmount.pp @@ -13,12 +13,6 @@ #plugins => ['sync', 'ini'] } -kdbmount { 'system/network/hosts': - ensure => present, - file => '/etc/hosts', - plugins => 'hosts' -} - kdbmount { '/test/cascading': ensure => present, file => 'test.ini', diff --git a/examples/multifile.pp b/examples/multifile.pp new file mode 100644 index 0000000..7aff7e8 --- /dev/null +++ b/examples/multifile.pp @@ -0,0 +1,19 @@ + + + +kdbmount { 'user/test/mm': + file => 'mmtest.json', + #file => 'mmtest.ini', + plugins => ['json', 'type', 'enum'] + #plugins => ['ini', 'type', 'enum'] +} + + +kdbkey { + 'user/test/mm/s1': value => "ss1"; + 'user/test/mm/s2': value => 'ss2'; + 'user/test/mm/section abc/x1': value => 'sec_x1'; + 'user/test/mm/section abc/x2': value => 'sec_x2'; + 'user/test/mm/section xyz/y1': value => 'sec_y1'; + 'user/test/mm/section xyz/y2': value => 'sec_y2'; +} From bc4235d56dd8c7fa9cd36d3f40d1cb8063798747 Mon Sep 17 00:00:00 2001 From: bernhard Date: Fri, 24 Mar 2017 11:48:06 +0100 Subject: [PATCH 36/52] kdbkey ruby: add check metadata also to the resource key This ensures, that the check can be performed even if the spec_key is just created (in the same kdb.set operation). Otherwise we could end up creating keys with invalid values --- examples/kdbkey.pp | 2 +- lib/puppet/provider/kdbkey/ruby.rb | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/kdbkey.pp b/examples/kdbkey.pp index 5e0283a..68ae6bc 100644 --- a/examples/kdbkey.pp +++ b/examples/kdbkey.pp @@ -114,7 +114,7 @@ # (see '$> kdb info type' for other types) kdbkey { 'spec/x1': prefix => $ns_validation, - value => 7, + value => 11, check => { 'range' => '0-10' }, diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 0f1698c..3daea2e 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -302,12 +302,21 @@ def check=(value) # set meta data on spec_key spec_to_set.each do |spec_name, spec_value| spec_key[spec_name] = spec_value + # also add the check meta data to resource_key directly, they will get + # removed by the 'spec' plugin (it the bug is fixed ;) + # This is required, since the check is only evaluated if the key has the + # appropriate metadata attached. If the spec_key is created with the same + # keyset, the resources value will be set before the check can be performed + # so we end up with an invalid value for the setting. + @resource_key[spec_name] = spec_value end # remove all not specified meta keys from spec_key starting with 'check' spec_key.meta.each do |e| if e.name.start_with? "check" and !spec_to_set.include? e.name spec_key.del_meta e.name + # perform same operation on resource_key + @resource_key.del_meta e.name end end end From 90ac4e9beaa2b1da05b4716681c66f9425fb082c Mon Sep 17 00:00:00 2001 From: bernhard Date: Mon, 27 Mar 2017 17:44:25 +0200 Subject: [PATCH 37/52] kdbkey: autorequire all parent key names This gives us automatically an order relation ship between parent and child key names (section before key) --- lib/puppet/type/kdbkey.rb | 13 ++++++++++++- spec/unit/type/kdbkey_spec.rb | 17 ++++++++++------- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index 9f587fc..3708f6b 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -336,7 +336,15 @@ def change_to_s(current_value, new_value) end - autorequire(:kdbmount) do + autorequire(:kdbmount) do + get_autorequire_path_names true + end + + autorequire(:kdbkey) do + get_autorequire_path_names false + end + + def get_autorequire_path_names(include_self) if self[:name].is_a? String # split name into path elements, so token separated by '/' not including @@ -371,6 +379,9 @@ def change_to_s(current_value, new_value) req_resources << req_resources.last + "/" + n end + # if include_self == false remove the last entry (equals :name) + req_resources.delete self[:name] unless include_self + # if we have a cascading key, we could access any possible Elektra # namespace, thus we autorequire all of them if self[:name][0] == '/' diff --git a/spec/unit/type/kdbkey_spec.rb b/spec/unit/type/kdbkey_spec.rb index f89d38f..224bdeb 100644 --- a/spec/unit/type/kdbkey_spec.rb +++ b/spec/unit/type/kdbkey_spec.rb @@ -240,16 +240,19 @@ RSpec.shared_examples "autorequire kdbmounts" do |keyname, expected| it "each possible mountpoint for '#{keyname}'" do t = described_class.new({:name => keyname}) - list = [] - types = [] + autoreq = {} t.class.eachautorequire do |type,block| - types << type - list << t.instance_eval(&block) + autoreq[type] = t.instance_eval(&block) end - list.flatten! - expect(types).to eq [:kdbmount] - expect(list).to eq expected + expect(autoreq.include? :kdbmount).to be true + expect(autoreq.include? :kdbkey).to be true + + expect(autoreq[:kdbmount]).to eq expected + + # kdbkey should not require itself + expected.delete keyname + expect(autoreq[:kdbkey]).to eq expected end end From 508ecaec6ce869c3ec628ae3dee59f32a2ac759b Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 28 Mar 2017 09:57:57 +0200 Subject: [PATCH 38/52] kdbkey ruby provider: fix test cases for set spec --- spec/unit/provider/key_helper.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/unit/provider/key_helper.rb b/spec/unit/provider/key_helper.rb index 5c62f65..9d0156b 100644 --- a/spec/unit/provider/key_helper.rb +++ b/spec/unit/provider/key_helper.rb @@ -521,6 +521,8 @@ def has_expected_but_not_specified(got_meta) context "handle key specifications ('check')" do before :example do h.ensure_key_is_missing provider.get_spec_key_name + h.ensure_key_exists keyname + provider.exists? end it "get spec for a single String check" do From 63239591eba774fc2ce611c88eb44dc34f85d2ca Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 28 Mar 2017 11:46:45 +0200 Subject: [PATCH 39/52] kdbkey ruby provider: implement 'user' param --- lib/puppet/provider/kdbkey/ruby.rb | 67 ++++++++++++++++++++++++++++-- lib/puppet/type/kdbkey.rb | 8 ---- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 3daea2e..99994f6 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -7,7 +7,7 @@ # @copyright BSD License (see LICENSE or http://www.libelektra.org) # # - +require 'etc' require 'puppet/provider/kdbkey/common' module Puppet @@ -51,6 +51,55 @@ def use_fake_ks(ks) # allow access to internal key, used during testing attr_reader :resource_key + + def do_asuser(proc_obj) + unless @resource[:user].nil? + Puppet::Util::SUIDManager.asuser(@resource[:user]) do + + old_user = ENV['USER'] + old_home = ENV['HOME'] + old_xdg = ENV['XDG_CONFIG_HOME'] + + if @resource[:user] =~ /^\d+/ + # we got a numeric user argument try to convert to user name + begin + user = Etc.getpwuid(@resource[:user].to_i).name + rescue + user = @resource[:user] + end + else + user = @resource[:user] + end + + ENV['USER'] = user + begin + # if passwd entry for user does not exist, this will trigger an + # ArgumentError + ENV['HOME'] = Etc.getpwnam(user).dir + rescue + ENV['HOME'] = '' + end + + ENV['XDG_CONFIG_HOME'] = '' + + begin + Puppet.debug("do_asuser: euid: #{Process.euid} " + + "user: #{@resource[:user]} " + + "HOME: #{ENV['HOME']} " + + "USER: #{ENV['USER']} ") + proc_obj.call + rescue + ENV['USER'] = old_user + ENV['HOME'] = old_home + ENV['XDG_CONFIG_HOME'] = old_xdg + end + end + else + proc_obj.call + end + + end + def create @resource_key = Kdb::Key.new @resource[:name] self.value= @resource[:value] unless @resource[:value].nil? @@ -94,15 +143,21 @@ def exists? # - a better strategy would be to use one handle and keyset per mountpoint. But for # the moment this is too complicated (e.g. how to proceed with cascading keys?) # - begin + open_proc = Proc.new do @kdb_handle = Kdb.open @@open_handles << @kdb_handle @ks = Kdb::KeySet.new @cascading_key = Kdb::Key.new @resource[:name].gsub(/^\w+\//, '/') puts "do kdb.get ks, #{@cascading_key.name}" if @verbose @kdb_handle.get @ks, @cascading_key + Puppet.debug "reading from config file '#{@cascading_key.value}'" @ks.pretty_print if @verbose - end unless @is_fake_ks + end + + unless @is_fake_ks + do_asuser open_proc + end + @resource_key = @ks.lookup @resource[:name] puts "resource key nil? #{@resource_key.nil?}" if @verbose return !@resource_key.nil? @@ -343,7 +398,7 @@ def key_get_error_msg(key) # finally bring the changes to disk # also do a kdbclose for this handle def flush - unless @is_fake_ks + close_proc = Proc.new do begin Puppet.debug "kdbkey/ruby: flush #{@resource[:name]}" @kdb_handle.set @ks, @cascading_key @@ -357,6 +412,10 @@ def flush @kdb_handle.close end end + + unless @is_fake_ks + do_asuser close_proc + end end # provider de-init hook diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index 3708f6b..b8755fd 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -326,14 +326,6 @@ def change_to_s(current_value, new_value) namespace (starting with 'user/'). EOT - # misuse the validate method, to change the provider, if required - validate do |value| - if provider.class.name != :kdb - Puppet.debug "Puppet::Type::Kdbkey: change provider to 'kdb' (required by param 'user')" - @resource.provider= :kdb - end - end - end autorequire(:kdbmount) do From a6a0dfdd66488423b9a8be1205dbf63215122a7e Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 28 Mar 2017 12:02:01 +0200 Subject: [PATCH 40/52] kdbkey ruby provider: add missing has_feature :user --- lib/puppet/provider/kdbkey/ruby.rb | 2 ++ lib/puppet/type/kdbkey.rb | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 99994f6..cbc0e6c 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -18,6 +18,8 @@ module Puppet @@have_kdb = true @@is_fake_ks = false + has_feature :user + begin # load libelektra Ruby binding extension require 'kdb' diff --git a/lib/puppet/type/kdbkey.rb b/lib/puppet/type/kdbkey.rb index b8755fd..22561ea 100644 --- a/lib/puppet/type/kdbkey.rb +++ b/lib/puppet/type/kdbkey.rb @@ -66,7 +66,13 @@ desc <<-EOT The fully qualified name of the key - TODO: describe if it is safe or not to use cascading keys? + Elektra manages its keys within several namespaces ('system', 'user', + 'dir'... see https://www.libelektra.org/manpages/elektra-namespaces + for more details.) + + Cascading key names (keys starting with a '/') are probably not optimal + here, as they are implicitly converted to a key name with the 'dir', + 'user' or 'system' namespace. EOT # add the prefix, if given @@ -175,8 +181,8 @@ def single_elem_as_string(v) and add them as metadata keys to the corresponding keys. This attribute allows to manage those comment lines. - TODO finish this docu - + Multi-line comments (those including a newline character) are implicitly + converted to a multi-line comment. EOT def change_to_s(current_value, new_value) From 1da8e4cf6d7bc6a0b02a7539ffab5ab8e0fa47ff Mon Sep 17 00:00:00 2001 From: bernhard Date: Tue, 28 Mar 2017 16:08:58 +0200 Subject: [PATCH 41/52] update readme --- README.md | 549 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 504 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index c413092..a68b30f 100644 --- a/README.md +++ b/README.md @@ -3,83 +3,542 @@ #### Table of Contents 1. [Description](#description) + * [Elektra](#elektra) 1. [Setup - The basics of getting started with libelektra](#setup) - * [What libelektra affects](#what-libelektra-affects) + * [Elektra installation](#elektra-installation) * [Setup requirements](#setup-requirements) * [Beginning with libelektra](#beginning-with-libelektra) -1. [Usage - Configuration options and additional functionality](#usage) -1. [Reference - An under-the-hood peek at what the module is doing and how](#reference) -1. [Limitations - OS compatibility, etc.](#limitations) +1. [Usage - Basica and Examples](#usage) +1. [Reference - An under-the-hood peek at what the module is doing](#reference) + * [Kdbkey - manage Elektra keys](#kdbkey) + * [Kdbmount - manage Elektra mountpoints](#kdbmount) +1. [Limitations](#limitations) 1. [Development - Guide for contributing to the module](#development) +1. [Release Notes](#release-notes) ## Description -TODO +Puppet module for *libelektra* (https://www.libelektra.org). This allows +key-value based configuration manipulation. -Stub: Start with a one- or two-sentence summary of what the module does and/or what -problem it solves. This is your 30-second elevator pitch for your module. -Consider including OS/Puppet version it works with. +### Elektra -You can give more descriptive information in a second paragraph. This paragraph -should answer the questions: "What does this module *do*?" and "Why would I use -it?" If your module has a range of functionality (installation, configuration, -management, etc.), this is the time to mention it. +Elektra is a general purpose key value based configuration framework. It +manages its keys in a global, modular and hierarchically organized key space: + + * **global**: all processes on the same machine access the same key space + * **hierarchical**: the key space is structured as a tree, similar to the UNIX + file system. Each key has a unique name, similar to the absolute path of a + file. + * **modular**: the key space can be split up in several parts, whereas each + part corresponds to a different configuration file. This split-up is called + **mounting**. Similar to a UNIX file system (the whole file system can be + split in different disks, locations...), the Elektra key space can be built + up by a set of different configuration files. A set of Elektra plugins define + how a configuration file is integrated into the Elektra key space (defining + which storage format is used, conversion parameter...). + +This *mounting* process makes Elektra very suitable for Puppet. Mounting allows +us to integrate different configuration files, of different formats, into the +Elektra key space. Once in the key space, configuration settings can be +manipulated on a key value basis. For example, to change the 'workgroup' in +Samba's configuration file everything needed is: +```puppet +kdbmount { 'system/sw/samba': + file => '/etc/samba/smb.conf', + plugins => ['ini'] +} + +kdbkey { 'system/sw/samba/global/workgroup': + value => 'MY_WORKGROUP' +} +``` + +Elektra also provides CLI tools to operate on the Elektra key space: +```sh +$> kdb get system/sw/samba/global/workgroup +MY_WORKGROUP +$> kdb set system/sw/samba/global/workgroup OTHER +Set string to OTHER +``` +Another important feature of Elektra is called *configuration specification*. +This allows us to define value (and even structure) restrictions. This means, we +can instruct Elektra to perform validation checks before writing a configuration +file. For example, if an application only accepts numeric values in the range of +1-10 for a certain setting, we can add this check with: +```puppet +kdbmount { 'system/sw/myapp': + file => '/etc/myapp/config.ini', + plugins => ['ini', 'type', 'range'] # add checking plugins +} + +kdbkey { 'system/sw/myapp/instances': + value => $instances, + check => { + 'type' => 'short', + 'range' => '1-10' + } +} +``` +This *configuration specification* stored within Elektra, thus it is now active +for other Elektra aware tools: +``` +$> kdb set system/sw/myapp/instances 11 +The command kdb set failed while accessing the key database with the info: +... +Description: value not within specified range. +Reason: value 11 not within range 1-10 +... +``` + +For further details on Elektra see https://www.libelektra.org/ ## Setup -### What libelektra affects **OPTIONAL** +The 'libelektra' Puppet module currently requires a recent Elektra version +(0.8.19) to work correctly. + +Elektra has to be installed on each managed node. In theory, Elektra is not +required on the master node, if the master node is unmanaged. -stub: If it's obvious what your module touches, you can skip this section. For -example, folks can probably figure out that your mysql_instance module affects -their MySQL instances. +### Elektra installation -If there's more that they should know about, though, this is the place to mention: +For Elektra installation instructions see +[Elektra installation](https://www.libelektra.org/docgettingstarted/installation). -* A list of files, packages, services, or operations that the module will alter, - impact, or execute. -* Dependencies that your module automatically installs. -* Warnings or other important notices. -### Setup Requirements **OPTIONAL** +The 'libelektra' Puppet module integrates with Elektra by the Elektra Ruby bindings +or the Elektra CLI tool `kdb`. Although, `kdb` is the minimum requirement for +this module to work, it is **highly** recommended to install Elektra's Ruby +bindings. -stub: If your module requires anything extra before setting up (pluginsync enabled, -etc.), mention it here. +### Setup Requirements -If your most recent release breaks compatibility or requires particular steps -for upgrading, you might want to include an additional "Upgrading" section -here. +We currently only support Linux operating systems. (Tested on Ubuntu Xenial and +Debian Jessie) ### Beginning with libelektra -Stub: The very basic steps needed for a user to get the module up and running. This -can include setup steps, if necessary, or it can be an example of the most -basic use of the module. +After a successful Elektra installation, install the 'libelektra' Puppet module. +Currently this can only be done by cloning the Github repo. + +Once the module is released, it will be available in Puppet forge. ## Usage -Stub: This section is where you describe how to customize, configure, and do the -fancy stuff with your module here. It's especially helpful if you include usage -examples and code samples for doing things with your module. +The 'libelektra' Puppet module provides two resource types for managing +configuration files with Elektra: + + * **kdbmount**: mount configuration file into the Elektra key space + * **kdbkey**: manipulate configuration settings through Elektra + +To start configuring your systems with 'libelektra', you first have to integrate +(**mount**) your configuration files into the Elektra key space: +```puppet +kdbmount { 'system/sw/samba': # Elektra mount path + file => '/etc/samba/smb.conf', # path to configuration file + plugins => ['ini'] # list of Elektra plugins used for mounting +} +``` +Now all configuration settings defined in `/etc/samba/smb.conf` are available +under the Elektra path `system/sw/samba`. So `system/sw/samba/global/workgroup` +refers to the setting `workgroup` in section `global` in config file +`/etc/samba/smb.conf`. + +Now we can manipulate smb.conf settings: +```puppet +# add a new logging parameter +kdbkey { 'system/sw/samba/global/logging': + value => 'syslog@1 file' +} + +# remove the 'debuglevel' setting +kdbkey { 'system/sw/samba/global/debuglevel': + ensure => absent +} +``` + +**Autorequires**: A order relation ship ('requires', 'before'...) between the +`kdbmount` and `kdbkey` definitions is not required. This is added implicitly. + +We often manipulate settings under a certain Elektra path. To avoid using the +the full Elektra path over and over again, we can use the `prefix` parameter` +together with resource defaults here: +```puppet +class samba::config { + $mountpoint = 'system/sw/samba' + + kdbmount { $mountpoint: + file => '/etc/samba/smb.conf', + plugins => ['ini', 'enum'] # use the enum check plugin + } + + Kdbkey { + prefix => $mountpoint + } + + # the Elektra path is concatenated by `prefix` and `name` parameters + kdbkey { 'global/workgroup': + value => 'MY_WORKGROUP' + } + + # it can be even be more readable (Note: ';' at the end) + kdbkey { + 'global/logging': value => 'syslog@1 file'; + 'global/log level': value => '3 auth:10'; + } + + # sections are created automatically + kdbkey { + 'my_share/path': + value => '/var/data/my_share', + # goes in smb.conf as comment line + comment => 'This is my share definition'; + + 'my_share/comment': + value => 'This is my share'; + + 'my_share/guest ok': + value => 'yes', + # only allow 'yes' or 'no' + check => { 'enum' => ['yes', 'no'] }; + } +``` ## Reference -Stub: Here, include a complete list of your module's classes, types, providers, -facts, along with the parameters for each. Users refer to this section (thus -the name "Reference") to find specific details; most users don't read it per -se. +Obtained by `puppet doc` + +### kdbkey + +Manage libelekra keys. + +This resource type allows to define and manipulate keys of libelektra's +key database. + +#### Parameters + +* `name`: The fully qualified name of the key +* `ensure`: The basic property that the resource should be in. +* `value`: Desired value of the key. +* `prefix`: Prefix for the key name (optional) +* `check`: Add value validation. +* `comments`: comments for this key +* `user`: define/modify key in the context of given user. +* `metadata`: Metadata for this key supplied as Hash of key-value pairs. +* `purge_meta_keys`: manage complete set of metadata keys +* `provider`: The specific backend to use for this `kdbkey` resource. + +##### Parameter Details + +* `check`: Add value validation. + + This property allows to define certain restrictions to be applied on the + key value, which are automatically checked on each key database write. These + validation checks are performed by Elektra itself, so modifications done + by other applications will be also restricted to the defined value + specifications. + + The value for this property can be either a single String or a Hash + of settings. The following plugins are currently available. + + * `path`: check for an absolute path name + + The 'path' plugin does not require any additional settings + so it is enough to just pass 'path' as 'check' value. + ```puppet + kdbkey { 'system/sw/myapp/setting1': + check => 'path', + value => '/some/absolute/path/will/pass' + } + ``` + Note: this does not check if the path really exists (instead it just + issues a warning). The check will fail, if the given value is not an + absolute path. + + * `network`: check for a valid IP address + + The network plugin checks if the supplied value is valid IP address. + ```puppet + kdbkey { 'system/sw/myapp/myip': + check => 'ipaddr', + value => ${given_myip} + } + ``` + to check for valid IPv4 addresses use + ```puppet + kdbkey { 'system/sw/myapp/myip': + check => { 'ipaddr' => 'ipv4' }, # works with 'ipv6' too + value => $given_myip + } + ``` + + * `type`: type checks + + The `type` plugin checks if the supplied key value conforms to a defined + data type (e.g. numeric value). Additionally, it is able to check if + the supplied key value is within an allowed range. + ```puppet + kdbkey { 'system/sw/myapp/port': + check => { 'type' => 'unsigned_long' }, + value => $given_port + } + + kdbkey { 'system/sw/myapp/num_instances': + check => { + 'type' => 'short', + 'type/min' => 1, + 'type/max' => 20 + }, + value => $given_num_instance + } + ``` + + * `range`: checks if value is within one ore more ranges + + ```puppet + kdbkey { 'system/sw/myapp/value': + check => { 'range' => '1-10,12-20' }, # <1, 11 and >20 is not allowed + value => $value + } + ``` + + * `enum`: define a list of valid values + + The enum plugin check it the supplied value is within a predefined set + of values. Two different formats are possible: + ```puppet + kdbkey { 'system/sw/myapp/scheduler': + # as string, values seperated with ', ' and encloseed by ' + check => { 'enum' => "'ondemand', 'performance', 'energy saving'" }, + value => $given_scheduler + } + + kdbkey { 'system/sw/myapp/notification': + # as array of strings + check => { 'enum' => ['off', 'email', 'slack', 'irc'] }, + value => $given_notification + } + ``` + + * `validation`: perform regular expression checks + + The validation plugin checks if the supplied value matches a predefined + regular expression: + ```puppet + kdbkey { 'system/sw/myapp/email': + check => { + 'validation' => '^[a-z0-9._]+@mycompany.com$' + 'validation/message' => 'we require an internal email address here', + 'validation/ignorecase' => '', # existence of flag is enough + } + ... + } + ``` + + For further check plugins see the Elektra + [plugin documentation](https://www.libelektra.org/plugins/readme). + + Note: for each 'check/xxx' metadata, required by the Elektra plugins, just + remove the 'check/' part and add it to the 'check' property here. + (e.g. validation plugin: 'check/validation' => 'validation' ...) + +* `comments`: comments for this key + + Comments form a critical part of documentation. May configuration file + formats support adding comment lines. Libelektra plugins parse comments + and add them as metadata keys to the corresponding keys. This attribute + allows to manage those comment lines. + + Multi-line comments (those including a newline character) are implicitly + converted to a multi-line comment. + +* `ensure`: The basic property that the resource should be in. + + Valid values are `present`, `absent`. + +* `metadata`: Metadata for this key supplied as Hash of key-value pairs. + + The concrete behaviour is defined by the parameter `purge_meta_keys`. + The default case (`purge_meta_keys` => false) is to manage the specified + metadata keys only. Already present but not specified metadata keys will not + be removed. If `purge_meta_keys` is set to true, already present but not + specified metadata keys will be removed. + + Examples: + ```puppet + kdbkey { 'system/sw/app/s1': + metadata => { + 'owner' => 'me', + 'other meta' => 'you' + } + } + ``` + +* `name`: The fully qualified name of the key + + (**Namevar:** If omitted, this parameter's value defaults to the resource's title.) + + Elektra manages its keys within several namespaces ('system', 'user', + 'dir'... see + [Elektra-namespaces](https://www.libelektra.org/manpages/elektra-namespaces) + for more details.) + + Cascading key names (keys starting with a '/') are probably not optimal + here, as they are implicitly converted to a key name with the 'dir', + 'user' or 'system' namespace. + +* `prefix`: Prefix for the key name (optional) + + If given, this value will prefix the given libelektra key name. + e.g.: + ```puppet + kdbkey { 'puppet/x1': + prefix => 'system/test', + value => 'hello' + } + ``` + This will manage the key 'system/test/puppet/x1'. + + Prefix and name are joined with a '/', if prefix does not end with '/' + or name does not start with '/'. + + Both, name and prefix parameter are used to uniquely identify a + libelektra key. + +* `provider`: The specific backend to use for this `kdbkey` resource. + + You will seldom need to specify this --- Puppet will usually + discover the appropriate provider for your platform. + + Default: `ruby` if Ruby bindings are installed + + Available providers are: + + * kdb: manage keys through `kdb` command + + * Required binaries: `kdb`. + * Supported features: `user`. + + * ruby: manage keys through libelektra Ruby API + + * Supported features: `user`. + +* `purge_meta_keys`: manage complete set of metadata keys + + If set to true, kdbkey will remove all unspecifed metadata keys, ensuring + only the specified set of metadata keys will exist. Otherwise, + unspecified metadata keys will not be touched. + + Valid values are `true`, `false`, `yes`, `no`. + +* `user`: define/modify key in the context of given user. + + This is only relevant, if key name referes to a user context, thus is + either cascading (starting with a '/') or is within the 'user' + namespace (starting with 'user/'). + +* `value`: Desired value of the key. + + This can be any type, however elektra currently + just manages to store String values only. Therefore all types are + implicitly converted to Strings. + + If value is an array, the key is managed as an Elektra array. Therefore + a subkey named `/#` will be created for each array element. + + + +### kdbmount + +Manage libelekra global key-space. + +This resource type allows to define and manipulate libelektra's global key +database. Libelektra allows to 'mount' external configuration files into +its key database. A specific libelektra backend plugin is for reading and +writing the configuration file. + +#### Parameters + +* `name`: The fully qualified mount path within the libelektra key database. +* `ensure`: The basic property that the resource should be in. +* `file`: The configuration file to mount into the Elektra key database. +* `plugins`: A list of libelektra plugins with optional configuration settings +* `provider`: The specific backend to use for this `kdbmount` resource. +* `resolver`: The resolver plugin to use for mounting. +* `add_recommended_plugins`: If set to true, Elektra will add recommended + +##### Parameter Details + +* `add_recommended_plugins`: If set to true, Elektra will add recommended + plugins to the mounted backend configuration. + Recommended plugins are: sync + Default: false + + Valid values are `true`, `false`, `yes`, `no`. + +* `ensure`: The basic property that the resource should be in. + + Valid values are `present`, `absent`. + +* `file`: The configuration file to mount into the Elektra key database. + +* `name`: The fully qualified mount path within the libelektra key database. + +* `plugins`: A list of libelektra plugins with optional configuration settings + use for mounting. + + The following value formats are acceped: + - a string value describing a single plugin name + - an array of string values each defining a single plugin + - a hash of plugin names with corresponding configuration settings + e.g. + ```puppet + [ 'ini' => { + 'delimiter' => " " + 'array' => '' + }, + 'type' + ] + ``` + +* `provider`: The specific backend to use for this `kdbmount` resource. + You will seldom need to specify this --- Puppet will usually discover the + appropriate provider for your platform. + + Available providers are: + + * `kdb`: kdbmount through kdb command + + * Required binaries: `kdb`. + + * `ruby`: kdbmount through libelektra Ruby API + + * Default for `kernel` == `Linux`. + +* `resolver`: The resolver plugin to use for mounting. + + Default: 'resolver' + + + + ## Limitations -Stub: This is where you list OS compatibility, version compatibility, etc. If there -are Known Issues, you might want to include them under their own heading here. +The 'libelektra' Puppet module was only tested with Puppet 3.x (3.8). Since +Puppet 4.x uses its own Ruby runtime, the system installed Elektra Ruby bindings +can't be used by 'libelektra'. Thus the fallback provider `kdb` for both +resource types (kdbkey and kdbmount) are usable only. ## Development -Stub: Since your module is awesome, other users will want to play with it. Let them -know what the ground rules for contributing are. +This module is hosted under +https://github.com/ElektraInitiative/puppet-libelektra + +Contribution welcome ;) -## Release Notes/Contributors/Etc. **Optional** +## Release Notes -Stub: If you aren't using changelog, put your release notes here (though you should -consider using changelog). You can also add any additional sections you feel -are necessary or important to include here. Please use the `## ` header. +Currently the 'libelektra' Puppet module is under heavy development. No releases +till now. From 31ea52968ded60bfaa9265b9c96a0053bc940b88 Mon Sep 17 00:00:00 2001 From: markus2330 Date: Fri, 7 Apr 2017 18:09:53 +0200 Subject: [PATCH 42/52] small fixes --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a68b30f..4356baf 100644 --- a/README.md +++ b/README.md @@ -218,15 +218,15 @@ key database. #### Parameters -* `name`: The fully qualified name of the key +* `name`: The fully qualified name of the key. * `ensure`: The basic property that the resource should be in. * `value`: Desired value of the key. -* `prefix`: Prefix for the key name (optional) +* `prefix`: Prefix for the key name (optional). * `check`: Add value validation. -* `comments`: comments for this key -* `user`: define/modify key in the context of given user. +* `comments`: Comments for this key. +* `user`: Define or modify key in the context of given user. * `metadata`: Metadata for this key supplied as Hash of key-value pairs. -* `purge_meta_keys`: manage complete set of metadata keys +* `purge_meta_keys`: Manage complete set of metadata keys. * `provider`: The specific backend to use for this `kdbkey` resource. ##### Parameter Details @@ -240,7 +240,7 @@ key database. specifications. The value for this property can be either a single String or a Hash - of settings. The following plugins are currently available. + of settings. The following plugins were tested with puppet-elektra: * `path`: check for an absolute path name @@ -336,7 +336,7 @@ key database. } ``` - For further check plugins see the Elektra + For further plugins see the Elektra [plugin documentation](https://www.libelektra.org/plugins/readme). Note: for each 'check/xxx' metadata, required by the Elektra plugins, just @@ -382,7 +382,7 @@ key database. Elektra manages its keys within several namespaces ('system', 'user', 'dir'... see [Elektra-namespaces](https://www.libelektra.org/manpages/elektra-namespaces) - for more details.) + for details.) Cascading key names (keys starting with a '/') are probably not optimal here, as they are implicitly converted to a key name with the 'dir', @@ -536,7 +536,7 @@ resource types (kdbkey and kdbmount) are usable only. This module is hosted under https://github.com/ElektraInitiative/puppet-libelektra -Contribution welcome ;) +Contributions welcome ;) ## Release Notes From 3869e79eae149a5892a1ee944f4f47e8b7c7fd42 Mon Sep 17 00:00:00 2001 From: bernhard Date: Fri, 21 Apr 2017 14:31:50 +0200 Subject: [PATCH 43/52] kdbmount: default 'true' for add_recommended_plugins --- lib/puppet/type/kdbmount.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/puppet/type/kdbmount.rb b/lib/puppet/type/kdbmount.rb index 7fe7aad..fd3c290 100644 --- a/lib/puppet/type/kdbmount.rb +++ b/lib/puppet/type/kdbmount.rb @@ -78,9 +78,9 @@ If set to true, Elektra will add recommended plugins to the mounted backend configuration. Recommended plugins are: #{RECOMMENDED_PLUGINS.join ', '} - Default: false + Default: true EOT - defaultto :false + defaultto :true end From d7fcfa7c76bccf0397739913c860184ca9066575 Mon Sep 17 00:00:00 2001 From: bernhard Date: Sun, 23 Apr 2017 12:18:12 +0200 Subject: [PATCH 44/52] kdbmount kdb: remove old 'puts' debugging code --- lib/puppet/provider/kdbmount/kdb.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/puppet/provider/kdbmount/kdb.rb b/lib/puppet/provider/kdbmount/kdb.rb index 1891d1e..05523dd 100644 --- a/lib/puppet/provider/kdbmount/kdb.rb +++ b/lib/puppet/provider/kdbmount/kdb.rb @@ -66,7 +66,6 @@ def create end def destroy - puts "kdb destroy" kdb ["umount", @resource[:name]] end From a64b5c041fd771ed2706e453c56d62e3064211ad Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 27 Apr 2017 08:32:26 +0200 Subject: [PATCH 45/52] kdbmount: fix exception handling --- lib/puppet/provider/kdbkey/ruby.rb | 16 ++++++++-------- lib/puppet/provider/kdbmount/ruby.rb | 16 ++++++++++------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index cbc0e6c..413145e 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -36,9 +36,9 @@ def self.default? confine :true => @@have_kdb # remember all opened kdb handles - # since there is not suitable way to a propper provider instance + # since there is not suitable way to a proper provider instance # cleanup. - # The flush method is only called, if the underlaying resource was + # The flush method is only called, if the underlying resource was # modified. # All opened handles will be closed on 'self.post_resource_eval' # which is done once per provider class. @@ -128,10 +128,10 @@ def exists? # this is the first method call for a managed resource # so, here we have to do a kdb.open - # all opend kdb objects are used by later methods so keep them + # all opened kdb objects are used by later methods so keep them # # note: for the moment we do a kdb.open/get/set for EACH managed - # kdbkey resource separately. This results in a opened kdb handle + # kdbkey resource separately. This results in an opened kdb handle # and keySet for each manged key. This strategy is required, since # we might have modified the underlying Elektra key space # (a changed/added mountpoint after the actual kdb.open). @@ -214,8 +214,8 @@ def value=(value) end # get metadata values as Hash - # note: in order to not trigger an refresh cycle, we have to be careful which - # keys should be returned. It 'purge_meta_keys?' is not set, we have to remove + # note: in order not to trigger an refresh cycle, we have to be careful which + # keys should be returned. If 'purge_meta_keys?' is not set, we have to remove # the not-specified metakeys from the result set. def metadata #key.meta.to_h unless key.nil? ruby 1.9 does not have Enumerable.to_h :( @@ -360,11 +360,11 @@ def check=(value) spec_to_set.each do |spec_name, spec_value| spec_key[spec_name] = spec_value # also add the check meta data to resource_key directly, they will get - # removed by the 'spec' plugin (it the bug is fixed ;) + # removed by the 'spec' plugin (if the plugin placement bug is fixed ;) # This is required, since the check is only evaluated if the key has the # appropriate metadata attached. If the spec_key is created with the same # keyset, the resources value will be set before the check can be performed - # so we end up with an invalid value for the setting. + # so we might end up with an invalid value for the setting. @resource_key[spec_name] = spec_value end diff --git a/lib/puppet/provider/kdbmount/ruby.rb b/lib/puppet/provider/kdbmount/ruby.rb index 91fc275..97762e1 100644 --- a/lib/puppet/provider/kdbmount/ruby.rb +++ b/lib/puppet/provider/kdbmount/ruby.rb @@ -253,7 +253,7 @@ def set_mount_backend_config(mountconf) # mountpoint mpk = Kdb::Key.new @resource[:name] unless mpk.is_valid? - raise Puppet::Error "invalid mountpoint: #{@resource[:name]}" + raise Puppet::Error, "invalid mountpoint: #{@resource[:name]}" end # add new mount point, checks for mountpoint validity and @@ -267,9 +267,9 @@ def set_mount_backend_config(mountconf) backend.need_plugin "storage" - @resource.class.const_get(:RECOMMENDED_PLUGINS).each do |p| - backend.recommend_plugin p - end + #@resource.class.const_get(:RECOMMENDED_PLUGINS).each do |p| + # backend.recommend_plugin p + #end plugin_config = convert_plugin_settings(@resource[:plugins]) # add user requested plugins @@ -284,8 +284,12 @@ def set_mount_backend_config(mountconf) # resolv all required plugins (without recommended (false)) backend.resolve_needs @resource[:add_recommended_plugins] - # add new backend to mount config - backend.serialize mountconf + begin + # add new backend to mount config + backend.serialize mountconf + rescue + raise Puppet::Error, "unable to create mountpoint; #{$!}" + end end From 5bfcfbf19ca3054a50186994ae2e03ffd6fecb41 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 27 Apr 2017 08:32:58 +0200 Subject: [PATCH 46/52] kdbmount: make :file and :plugins mandatory --- README.md | 6 ++++-- lib/puppet/type/kdbmount.rb | 12 ++++++++++++ spec/unit/type/kdbmount_spec.rb | 19 +++++++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4356baf..9454563 100644 --- a/README.md +++ b/README.md @@ -481,11 +481,13 @@ writing the configuration file. Valid values are `present`, `absent`. -* `file`: The configuration file to mount into the Elektra key database. +* `file`: (**mandatory**) The configuration file to mount into the Elektra + key database. * `name`: The fully qualified mount path within the libelektra key database. -* `plugins`: A list of libelektra plugins with optional configuration settings +* `plugins`: (**mandatory**) A list of libelektra plugins with optional + configuration settings use for mounting. The following value formats are acceped: diff --git a/lib/puppet/type/kdbmount.rb b/lib/puppet/type/kdbmount.rb index fd3c290..381c0c5 100644 --- a/lib/puppet/type/kdbmount.rb +++ b/lib/puppet/type/kdbmount.rb @@ -137,4 +137,16 @@ def self.plugin_name_is_valid?(name) /^\w+$/ =~ name end + validate do + # make :file and :plugins properties mandatory if one of them are used + if @parameters.include?(:plugins) + self.fail("file property missing") unless @parameters.include?(:file) + end + + if @parameters.include?(:file) + self.fail("plugins property missing") unless @parameters.include?(:plugins) + end + end + + end diff --git a/spec/unit/type/kdbmount_spec.rb b/spec/unit/type/kdbmount_spec.rb index d198ba0..9643c6c 100644 --- a/spec/unit/type/kdbmount_spec.rb +++ b/spec/unit/type/kdbmount_spec.rb @@ -75,25 +75,40 @@ :name => "user/test/puppet" } } - it "exists and is optionsl" do + it "exists and is optional if file is not used" do expect(described_class.new(params)[:plugins]).to be_nil end + it "exists and is mandatory if file is set" do + params[:file] = 'somefile.txt' + expect { described_class.new(params) }.to raise_error(Puppet::Error) + end + it "accepts a string" do + params[:file] = 'somefile.ini' params[:plugins] = "ini" expect(described_class.new(params)[:plugins]).to eq ["ini"] end it "accepts an array of strings" do + params[:file] = 'somefile.ini' params[:plugins] = ["ini", "type"] expect(described_class.new(params)[:plugins]).to eq ["ini", "type"] end - it "accepts a plugin name with corresponding configuration settings" do + it "accepts an array with plugin name with corresponding configuration settings" do + params[:file] = 'somefile.ini' params[:plugins] = ["ini", {"seperator" => " ", "array" => ""}] expect(described_class.new(params)[:plugins]).to eq params[:plugins] end + it "accepts a Hash with plugin name with corresponding configuration settings" do + params[:file] = 'somefile.ini' + params[:plugins] = {"ini" => {"seperator" => " ", "array" => ""}} + # we always get an Array back + expect(described_class.new(params)[:plugins]).to eq [params[:plugins]] + end + RSpec.shared_examples "invalid plugin names" do |plugins| it "rejects invalid plugin names '#{plugins}'" do expect { From b80dd265da85df131efc033127681c8631f54339 Mon Sep 17 00:00:00 2001 From: bernhard Date: Thu, 27 Apr 2017 13:37:02 +0200 Subject: [PATCH 47/52] kdbmount: fix updating of plugins (ruby) Now we carfully check which plugins are currenlty used for mounting the config file and which plugins should really be used. --- README.md | 7 +- lib/puppet/provider/kdbmount/ruby.rb | 135 +++++++++++++++++++-------- lib/puppet/type/kdbmount.rb | 67 +++++++++++-- 3 files changed, 159 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 9454563..abf1725 100644 --- a/README.md +++ b/README.md @@ -472,8 +472,11 @@ writing the configuration file. * `add_recommended_plugins`: If set to true, Elektra will add recommended plugins to the mounted backend configuration. - Recommended plugins are: sync - Default: false + Recommended plugins are defined by metadata of specified plugins. E.g. the + `hosts` plugins recommends `glob`, `error` and `network`. So, if mounting a + file with the `hosts` plugin and this parameter set to `true` all four + plugins will be used for mounting. + Default: true Valid values are `true`, `false`, `yes`, `no`. diff --git a/lib/puppet/provider/kdbmount/ruby.rb b/lib/puppet/provider/kdbmount/ruby.rb index 97762e1..489ad93 100644 --- a/lib/puppet/provider/kdbmount/ruby.rb +++ b/lib/puppet/provider/kdbmount/ruby.rb @@ -91,7 +91,7 @@ def file=(value) perform_kdb_action backend_root do |mountconf| if path_key.nil? - raise Puppet::Error.new "path key not found in backend config" + raise Puppet::Error, "path key not found in backend config" end path_key.value = @resource[:file] @@ -122,6 +122,61 @@ def resolver=(value) # puts "plugins: #{@resource[:plugins]}" #end + + def resolve_plugins(plugins) + result = {} + plugins.each do |plugin| + backend = Kdbtools::MountBackendBuilder.new + backend.add_plugin Kdbtools::PluginSpec.new(plugin) + backend.resolve_needs @resource[:add_recommended_plugins] + backend.to_add.each do |ps| + result[plugin] ||= [] + result[plugin] << ps.name + result[plugin] << ps.refname if ps.name != ps.refname + end + end + return result + end + + + # convert the Puppet given :plugins value to a more suitable + # hash: + # pluginname => plugin config settings + # + # Puppet will give us an array of values, combining plugin names and + # config settings. e.g. + # ["ini", {"delimiter" => " ", "setting2" => "aa"}, "type"] + # + # e.g: + # ini => { + # delimiter => " " + # array => "" + # }, + # type => { } + # + def convert_plugin_settings(plugins) + config = {} + cur_plugin = nil + if plugins.is_a? Array and plugins.size == 1 and plugins[0].is_a? Hash + # if we have a single array element and this is a Hash, user has passed + # a Hash object to plugins property + config = plugins[0] + elsif plugins.is_a? Array + plugins.each do |e| + if e.is_a? String + cur_plugin = e + config[e] = {} + elsif e.is_a? Hash + config[cur_plugin] = e + else + raise Puppet::Error, "invalid plugins configuration given" + end + end + end + return config + end + + private # get all active mountpoint @@ -192,40 +247,6 @@ def self.get_mountoint_plugin_config(backend) end - # convert the Puppet given :plugins value to a more suitable - # hash: - # pluginname => plugin config settings - # - # Puppet will give us an array of values, combining plugin names and - # config settings. e.g. - # ["ini", {"delimiter" => " ", "setting2" => "aa"}, "type"] - # - # e.g: - # ini => { - # delimiter => " " - # array => "" - # }, - # type => { } - # - def convert_plugin_settings(plugins) - config = {} - cur_plugin = nil - if plugins.respond_to? :each - plugins.each do |e| - if e.is_a? String - cur_plugin = e - config[e] = {} - elsif e.is_a? Hash - config[cur_plugin] = e - else - Puppet::Error "invalid plugins configuration given" - end - end - end - return config - end - - # helper function to modify Elektra key database # helps to avoid multiple Kdb.open/close sequences # @@ -242,7 +263,7 @@ def perform_kdb_action path, &block end - # create a new mount point and add it the the existing + # create a new mount point and add it to the existing # mount config (fetched from system/elektra/mountpoints # use with perform_kdb_action # @@ -267,13 +288,45 @@ def set_mount_backend_config(mountconf) backend.need_plugin "storage" - #@resource.class.const_get(:RECOMMENDED_PLUGINS).each do |p| - # backend.recommend_plugin p - #end + plugins = convert_plugin_settings(@resource[:plugins]) + plugins_to_mount = {} + # for each plugin get all dependent (and if req. recommended) plugins + resolved_plugins = resolve_plugins plugins.keys + + plugins.each do |name, config| + # if user has specified a plugin configuration, we have to use this + # plugin for mounting + unless config.empty? + plugins_to_mount[name] = config + end + + # check if this plugin would be added by any other plugin through + # dependency or recommends lists. + # If so do not explicetly for mounting, since this might lead to + # ordering or placement errors + use_plugin = true + resolved_plugins.each do |other, depends| + next if other == name + if depends.include? name + use_plugin = false + end + end + + if use_plugin + plugins_to_mount[name] = config + end + end + + all_used_plugins = resolved_plugins.values.flatten.uniq.sort + if plugins.keys.sort != all_used_plugins + Puppet.notice "#{@resource}: using additional plugins: #{(all_used_plugins - plugins.keys.sort)}" + end + + #puts "should plugins: #{plugins}" + #puts "actually used: #{plugins_to_mount}" - plugin_config = convert_plugin_settings(@resource[:plugins]) # add user requested plugins - plugin_config.each do |p_name, p_config| + plugins_to_mount.each do |p_name, p_config| ps = Kdbtools::PluginSpec.new p_name p_config.each do |k, v| ps.append_config Kdb::KeySet.new Kdb::Key.new("user/#{k}", value: v) diff --git a/lib/puppet/type/kdbmount.rb b/lib/puppet/type/kdbmount.rb index 381c0c5..97e3179 100644 --- a/lib/puppet/type/kdbmount.rb +++ b/lib/puppet/type/kdbmount.rb @@ -109,20 +109,73 @@ validate do |value| if value.is_a? String unless @resource.class.plugin_name_is_valid? value - raise ArgumentError, "'%s' is not a vlid plugin name" % value + raise ArgumentError, "'%s' is not a valid plugin name" % value end end end # this can't be done here, since we get each value at once for # munge, thus one munge call for each array entry. - #munge do |plugins| - # # convert plugins array to a hash + #munge do |plugin| #end - # TODO implement this to allow better plugins handling - #def insync?(value) - # puts "insync? #{value}" - # false + # customized insync? method to handle more complex cases. + # a plugin can have dependencies and can recommend other plugins, therefore + # during mounting a plugin, Elektra might add additional plugins. So the + # is and should in two subsequent runs might differ. + # This method checks, + # - if we have to add a newly specified plugin (not found in the current + # mounted plugin list) + # - if we really have to remove a plugin + # - if plugin config settings have changed + def insync?(is) + #puts "insync? is: #{is}, should #{should}" + return false unless provider.respond_to? :resolve_plugins + + # convert to plugins-config Hash + my_is = provider.convert_plugin_settings is + my_should = provider.convert_plugin_settings should + + + # fist, check if all :should plugins are in :is plugins array + # so, is there a plugin missing? + return false if my_should.keys.any? { |p| not my_is.include? p } + + # pass the :should plugins list to libelektra to get a list plugins that + # will be used when mounting is done with these + # (honores :add_recommended_plugins parameter) + resolved = provider.resolve_plugins my_should.keys + will_use_plugins = resolved.values.flatten.uniq + + #puts "resolved: #{resolved}" + #puts "will_use_plugins: #{will_use_plugins}" + + # now, check if plugins should be removed + # if we have mounted a plugin, which is not in the list of plugins which + # will be used when mounting with the :should plugins, we have to remove + # it + my_is.keys.each do |is_plugin| + return false unless will_use_plugins.include? is_plugin + end + + # now do the reverse order, check if all will use are actually used + # (this is possible if someone switches :add_recommended_plugins + # from false to true) + will_use_plugins.each do |p| + return false unless my_is.include? p + end + + # finally, check if some plugin configuration has changed + my_should.each do |plugin, config| + return false unless my_is.include? plugin + return false unless my_is[plugin] == config + end + + true + end + + # TODO: add nice formating messages when changing plugins + #def change_to_s(cur_value, new_value) + # return "changed: will_use_plugins: #{@will_use_plugins}" #end end From f3d2c046946107c6a0eff8a91213ef5027d1de50 Mon Sep 17 00:00:00 2001 From: bernhard Date: Sat, 6 May 2017 09:15:08 +0200 Subject: [PATCH 48/52] kdbkey: fix array index names greater >= 10 --- lib/puppet/provider/kdbkey/common.rb | 6 +++++ lib/puppet/provider/kdbkey/kdb.rb | 8 +++--- lib/puppet/provider/kdbkey/ruby.rb | 18 ++++++------- spec/unit/provider/kdbkey_common_spec.rb | 32 +++++++++++++++++++++--- 4 files changed, 48 insertions(+), 16 deletions(-) diff --git a/lib/puppet/provider/kdbkey/common.rb b/lib/puppet/provider/kdbkey/common.rb index fdaaa46..d4335ae 100644 --- a/lib/puppet/provider/kdbkey/common.rb +++ b/lib/puppet/provider/kdbkey/common.rb @@ -36,6 +36,12 @@ def is_special_meta_key?(metakey) return false end + def array_key_name(name, index) + index_str = index.to_s + (1..(index.to_s.size - 1)).each { index_str = "_#{index_str}" } + "#{name}/##{index_str}" + end + def get_spec_key_name(keyname = @resource[:name]) return keyname.gsub(/^\w*\//, "spec/") end diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 67a9a0f..10c21b3 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -36,7 +36,7 @@ def destroy run_kdb ["rm", @resource[:name]] # remove possible array elements list_keys.each do |x| - if x =~ /#{@resource[:name]}\/#\d+/ + if x =~ /#{@resource[:name]}\/#_*\d+/ run_kdb ["rm", x] end end @@ -64,7 +64,7 @@ def value # Array key value = [] elems.select do |x| - x =~ /^#{@resource[:name]}\/#\d+$/ + x =~ /^#{@resource[:name]}\/#_*\d+$/ end.each do |x| value << get_key_value(x) end @@ -88,7 +88,7 @@ def value=(value) else set_key_value @resource[:name], '' value.each_with_index do |elem_value, index| - set_key_value "#{@resource[:name]}/##{index}", elem_value + set_key_value array_key_name(@resource[:name], index), elem_value end remove_from_this_index = value.size end @@ -201,7 +201,7 @@ def comments=(value) @metadata_values["comments"] = "##{comment_lines.size}" comment_lines.each_with_index do |line, index| - @metadata_values["comments/##{index}"] = line + @metadata_values[array_key_name "comments", index] = line end end diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 413145e..ff7032e 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -115,7 +115,7 @@ def destroy @ks.delete @resource[:name] unless @resource_key.nil? # check if there are array keys left @ks.each do |x| - if x.name =~ /^#{@resource[:name]}\/#\d+$/ + if x.name =~ /^#{@resource[:name]}\/#_*\d+$/ @ks.delete x end end @@ -172,7 +172,7 @@ def value # array value value = [] @ks.each do |x| - if x.name =~ /^#{@resource_key.name}\/#\d+$/ + if x.name =~ /^#{@resource_key.name}\/#_*\d+$/ value << x.value end end @@ -194,7 +194,7 @@ def value=(value) else @resource_key.value= '' value.each_with_index do |elem_value, index| - elem_key_name = "#{@resource_key.name}/##{index}" + elem_key_name = array_key_name @resource_key.name, index elem_key = @ks.lookup elem_key_name if elem_key.nil? elem_key = Kdb::Key.new elem_key_name @@ -207,7 +207,7 @@ def value=(value) # remove possible "old" array keys i = remove_from_this_index - while not (key = @ks.lookup("#{@resource_key.name}/##{i}")).nil? + while not (key = @ks.lookup(array_key_name @resource_key.name, i)).nil? i += 1 @ks.delete key end @@ -268,7 +268,7 @@ def comments # search for all meta keys which names starts with 'comments/#' # and concat its values line by line @resource_key.meta.each do |e| - if /^(comments?)\/#/ =~ e.name + if /^(comments?)\/#_*\d+$/ =~ e.name puts "update comments key name to #{$1}" if @verbose @comments_key_name = $1 comments << "\n" unless first @@ -290,15 +290,15 @@ def comments=(value) comment_lines.each_with_index do |line, index| puts "comments keyname: #{@comments_key_name}" if @verbose # currently hosts plugin treats #0 comment as inline comment - #@resource_key.set_meta "#{@comments_key_name}/##{index + 1}", "##{line}" - @resource_key.set_meta "#{@comments_key_name}/##{index}", "##{line}" + #@resource_key.set_meta array_key_name(@comments_key_name, index + 1), "##{line}" + @resource_key.set_meta array_key_name(@comments_key_name, index), "##{line}" end #@resource_key.set_meta "#{@comments_key_name}/#0", '' # iterate over all meta keys and remove all comment keys which # represent a comment line, which does not exist any more @resource_key.meta.each do |e| - if e.name.match(/^#{@comments_key_name}\/#(\d+)$/) + if e.name.match(/^#{@comments_key_name}\/#_*(\d+)$/) index = $1.to_i if comment_lines[index].nil? @resource_key.del_meta e.name @@ -325,7 +325,7 @@ def check spec_key.meta.each do |m| if /^check\/(.*)$/ =~ m.name check_name = $1 - if /^(\w+)\/#\d+$/ =~ check_name + if /^(\w+)\/#_*\d+$/ =~ check_name spec_hash[$1] = [] unless spec_hash[$1].is_a? Array spec_hash[$1] << m.value else diff --git a/spec/unit/provider/kdbkey_common_spec.rb b/spec/unit/provider/kdbkey_common_spec.rb index 667b9ac..83ce8d2 100644 --- a/spec/unit/provider/kdbkey_common_spec.rb +++ b/spec/unit/provider/kdbkey_common_spec.rb @@ -13,13 +13,13 @@ require 'puppet/provider/kdbkey/common.rb' describe Puppet::Provider::KdbKeyCommon do - let(:provider) { described_class.new } + subject { described_class.new } RSpec.shared_examples "key => spec-key" do |keyname, expected| it "for key '#{keyname}'" do - provider.resource = create_resource :name => keyname + subject.resource = create_resource :name => keyname expect( - provider.get_spec_key_name + subject.get_spec_key_name ).to eq expected end end @@ -35,4 +35,30 @@ end + + RSpec.shared_examples "array element index" do |index, expected| + it "for index element #{index}" do + expect(subject.array_key_name keyname, index).to eq "#{keyname}/#{expected}" + end + end + + context "should get correct elektra array key names" do + let(:keyname) { "system/sw/test" } + + include_examples "array element index", 0, "#0" + include_examples "array element index", 1, "#1" + include_examples "array element index", 9, "#9" + include_examples "array element index", 10, "#_10" + include_examples "array element index", 11, "#_11" + include_examples "array element index", 19, "#_19" + include_examples "array element index", 20, "#_20" + include_examples "array element index", 90, "#_90" + include_examples "array element index", 100, "#__100" + include_examples "array element index", 286, "#__286" + include_examples "array element index", 1000, "#___1000" + include_examples "array element index", 10000, "#____10000" + include_examples "array element index", 100000, "#_____100000" + end + + end From e56f1557d1057fe365ce4ce555b31a088998408b Mon Sep 17 00:00:00 2001 From: bernhard Date: Fri, 28 Apr 2017 13:19:19 +0200 Subject: [PATCH 49/52] kdbkey: handle comments in a uniform way handle comments both for 'hosts' and 'ini' style comment metadata. --- lib/puppet/provider/kdbkey/kdb.rb | 2 ++ lib/puppet/provider/kdbkey/ruby.rb | 13 ++++++++++--- spec/unit/provider/key_ruby_helper.rb | 3 ++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 10c21b3..5515b0d 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -128,6 +128,8 @@ def read_metadata_values_from_key(key_to_read_from) next end next if metadata_reached == false + # end of metadata reached + break if line.empty? key_name, key_value = line.split(" = ") key_name.strip! diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index ff7032e..2cd25ff 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -282,6 +282,7 @@ def comments # update comments # def comments=(value) + default_comment_start = '#' # why do we have to init this inst var again??? @comments_key_name ||= "comments" # split specified comment into lines @@ -290,10 +291,16 @@ def comments=(value) comment_lines.each_with_index do |line, index| puts "comments keyname: #{@comments_key_name}" if @verbose # currently hosts plugin treats #0 comment as inline comment - #@resource_key.set_meta array_key_name(@comments_key_name, index + 1), "##{line}" - @resource_key.set_meta array_key_name(@comments_key_name, index), "##{line}" + if @resource_key.has_meta? "#{array_key_name @comments_key_name, index}/start" + comment_start = "" + else + comment_start = default_comment_start + end + @resource_key.set_meta array_key_name(@comments_key_name, index), "#{comment_start}#{line}" + if index == 0 + @resource_key.set_meta "#{array_key_name @comments_key_name, index}/space", "1" + end end - #@resource_key.set_meta "#{@comments_key_name}/#0", '' # iterate over all meta keys and remove all comment keys which # represent a comment line, which does not exist any more diff --git a/spec/unit/provider/key_ruby_helper.rb b/spec/unit/provider/key_ruby_helper.rb index a128b40..0fdf961 100644 --- a/spec/unit/provider/key_ruby_helper.rb +++ b/spec/unit/provider/key_ruby_helper.rb @@ -114,7 +114,8 @@ def key_get_comment(keyname) key = @ks.lookup keyname unless key.nil? key.meta.find_all do |e| - e.name.start_with? COMMENT+"/#" + #e.name.start_with? COMMENT+"/#" + e.name =~ /#{COMMENT}+\/#\d+$/ end.each do |c| comment = [] if comment.nil? if c.value.start_with? "#" From 4095a7b86c2da539d1d460e10f87423713ffee00 Mon Sep 17 00:00:00 2001 From: bernhard Date: Sat, 6 May 2017 09:31:59 +0200 Subject: [PATCH 50/52] set version to 0.8.1 --- metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata.json b/metadata.json index bbce79f..4c7c9e7 100644 --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,6 @@ { "name": "libelektra-libelektra", - "version": "0.1.0", + "version": "0.8.1", "author": "Bernhard Denner", "summary": "manage your configuration through libelektra", "license": "BSD License", From 8f3d1cb676dbb31b0bbe5e40a7a6ffc844e1444d Mon Sep 17 00:00:00 2001 From: Bernhard Denner Date: Thu, 15 Jun 2017 21:27:30 +0000 Subject: [PATCH 51/52] kdbmount: fix plugins parameter handling for kdb provider --- lib/puppet/provider/kdbmount/kdb.rb | 31 +++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/lib/puppet/provider/kdbmount/kdb.rb b/lib/puppet/provider/kdbmount/kdb.rb index 05523dd..27b5ed9 100644 --- a/lib/puppet/provider/kdbmount/kdb.rb +++ b/lib/puppet/provider/kdbmount/kdb.rb @@ -45,23 +45,38 @@ def create cmd_args << "-W" if @resource[:add_recommended_plugins] cmd_args << @resource[:file] cmd_args << @resource[:name] # mountpoint + puts "plugins: #{@resource[:plugins]}" if @resource[:plugins].is_a? Array - @resource[:plugins].each do |e| - # build plugin config cmdline argument - if e.is_a? Hash + if @resource[:plugins][0].is_a? Hash + @resource[:plugins][0].each do |plugin, params| + puts "here, params: #{params.inspect}" + cmd_args << plugin config_line = '' - e.each do |k,v| + params.each do |k, v| config_line << "," unless config_line.empty? config_line << "#{k}=#{v}" end - cmd_args << config_line - else - # plain plugin name - cmd_args << e + cmd_args << config_line unless config_line.empty? + end + else + @resource[:plugins].each do |e| + # build plugin config cmdline argument + if e.is_a? Hash + config_line = '' + e.each do |k,v| + config_line << "," unless config_line.empty? + config_line << "#{k}=#{v}" + end + cmd_args << config_line + else + # plain plugin name + cmd_args << e + end end end end cmd_args.flatten! + puts "cmd_args: #{cmd_args.inspect}" kdb(cmd_args) end From 24cd9d4be808a2919f8517d074abff5939c55307 Mon Sep 17 00:00:00 2001 From: Bernhard Denner Date: Mon, 6 Nov 2017 21:50:31 +0000 Subject: [PATCH 52/52] use require_relative to source the common provider --- lib/puppet/provider/kdbkey/kdb.rb | 2 +- lib/puppet/provider/kdbkey/ruby.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/puppet/provider/kdbkey/kdb.rb b/lib/puppet/provider/kdbkey/kdb.rb index 5515b0d..34edd04 100644 --- a/lib/puppet/provider/kdbkey/kdb.rb +++ b/lib/puppet/provider/kdbkey/kdb.rb @@ -8,7 +8,7 @@ # # -require 'puppet/provider/kdbkey/common' +require_relative 'common' require 'tempfile' module Puppet diff --git a/lib/puppet/provider/kdbkey/ruby.rb b/lib/puppet/provider/kdbkey/ruby.rb index 2cd25ff..26caa65 100644 --- a/lib/puppet/provider/kdbkey/ruby.rb +++ b/lib/puppet/provider/kdbkey/ruby.rb @@ -8,7 +8,7 @@ # # require 'etc' -require 'puppet/provider/kdbkey/common' +require_relative 'common' module Puppet Type.type(:kdbkey).provide :ruby, :parent => Puppet::Provider::KdbKeyCommon do