diff options
author | Luke Kanies <luke@madstop.com> | 2008-05-20 10:19:10 -0500 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-05-20 10:19:10 -0500 |
commit | fe157f239a301abb52f81c62719355c8e50c970c (patch) | |
tree | 2ff683d14dbf5c95a99410fe1d12300368cf497e | |
parent | 3cb0d60d3d0870f1d9ac83e5dbeaa06d2888231f (diff) | |
parent | 84a787a2a764a5035f7cbb8d30f94fc601bed154 (diff) | |
download | puppet-fe157f239a301abb52f81c62719355c8e50c970c.tar.gz puppet-fe157f239a301abb52f81c62719355c8e50c970c.tar.xz puppet-fe157f239a301abb52f81c62719355c8e50c970c.zip |
Merge branch '0.24.x'
Conflicts:
CHANGELOG
spec/integration/defaults.rb
spec/integration/node/catalog.rb
spec/unit/rails.rb
spec/unit/type/mount.rb
97 files changed, 4071 insertions, 386 deletions
@@ -3,6 +3,24 @@ whether we should even use a CRL. This way we aren't trying to set file paths to 'false' to disable the CRL. + Moving all confine code out of the Provider class, and fixing #1197. + Created a Confiner module for the Provider class methods, enhanced + the interface between it and the Confine class to make sure binary + paths are searched for fresh each time. + + Modified the 'factpath' setting to automatically configure + Facter to load facts there if a new enough version of + Facter is used. + + Crontab provider: fix a parse error when a line begins with a space + character (fixes #1216) + + Instead of deleting the init scripts (with --del) we should simply + disable it with chkconfig service off, and respectfully do the same + for enable => true; + + Added ldap providers for users and groups. + Added support for the --all option to puppetca --clean. If puppetca --clean --all is issued then all client certificates are removed. diff --git a/bin/puppetd b/bin/puppetd index b92773c76..d408af7d3 100755 --- a/bin/puppetd +++ b/bin/puppetd @@ -10,7 +10,7 @@ # # puppetd [-D|--daemonize|--no-daemonize] [-d|--debug] [--disable] [--enable] # [-h|--help] [--fqdn <host name>] [-l|--logdest syslog|<file>|console] -# [-o|--onetime] [--serve <handler>] [-t|--test] +# [-o|--onetime] [--serve <handler>] [-t|--test] [--noop] # [-V|--version] [-v|--verbose] [-w|--waitforcert <seconds>] # # = Description @@ -57,7 +57,7 @@ # parameter, so you can specify '--server <servername>' as an argument. # # See the configuration file documentation at -# http://reductivelabs.com/projects/puppet/reference/configref.html for +# http://reductivelabs.com/trac/puppet/wiki/ConfigurationReference for # the full list of acceptable parameters. A commented list of all # configuration options can also be generated by running puppetd with # '--genconfig'. @@ -124,6 +124,10 @@ # Enable the most common options used for testing. These are +onetime+, # +verbose+, +ignorecache, and +no-usecacheonfailure+. # +# noop:: +# Use +noop+ mode where the daemon runs in a no-op or dry-run mode. This is useful +# for seeing what changes Puppet will make without actually executing the changes. +# # verbose:: # Turn on verbose reporting. # @@ -65,7 +65,7 @@ # # This example uses ``ralsh`` to return Puppet configuration for the user ``luke``:: # -# $ ralsh user luke +# $ ralsh user luke # user { 'luke': # home => '/home/luke', # uid => '100', diff --git a/ext/autotest/Rakefile b/ext/autotest/Rakefile new file mode 100644 index 000000000..86327c04b --- /dev/null +++ b/ext/autotest/Rakefile @@ -0,0 +1,8 @@ +dest = File.expand_path("~/.autotest") +file dest => ["config", "Rakefile"] do + sh "cp config #{dest}" +end + +task :install => dest + +task :default => :install diff --git a/ext/autotest/config b/ext/autotest/config new file mode 100644 index 000000000..d37c1b2c6 --- /dev/null +++ b/ext/autotest/config @@ -0,0 +1,43 @@ +# vim: syntax=ruby +# From http://pastie.caboo.se/115692, linked from rickbradley + +require 'autotest/redgreen' +require 'autotest/timestamp' + +Autotest.send(:alias_method, :real_find_files, :find_files) +Autotest.send(:define_method, :find_files) do |*args| + real_find_files.reject do |k, v| + if (ENV['AUTOTEST'] and !ENV['AUTOTEST'].empty?) + !Regexp.new(ENV['AUTOTEST']).match(k) + end + end +end + +module Autotest::Growl + + def self.growl title, msg, img, pri=0, sticky="" + system "growlnotify -n autotest --image #{img} -p #{pri} -m #{msg.inspect} #{title} #{sticky}" + end + + Autotest.add_hook :ran_command do |at| + image_root = "~/.autotest_images" + results = [at.results].flatten.join("\n") + output = results.slice(/(\d+)\stests,\s(\d+)\sassertions,\s(\d+)\sfailures,\s(\d+)\serrors/) + if output + if $~[3].to_i > 0 || $~[4].to_i > 0 + growl "FAIL", "#{output}", "#{image_root}/fail.png", 2 + else + growl "Pass", "#{output}", "#{image_root}/pass.png" + end + end + + output = results.slice(/(\d+)\sexamples,\s(\d+)\sfailures?(,\s+\d+\s+pending)?/) + if output + if $~[2].to_i > 0 || $~[4].to_i > 0 + growl "FAIL", "#{output}", "#{image_root}/fail.png", 2 + else + growl "Pass", "#{output}", "#{image_root}/pass.png" + end + end + end +end diff --git a/ext/autotest/readme.rst b/ext/autotest/readme.rst new file mode 100644 index 000000000..93d9ed2fc --- /dev/null +++ b/ext/autotest/readme.rst @@ -0,0 +1,16 @@ +Autotest is a simple tool that automatically links tests with the files being +tested, and runs tests automatically when either the test or code has changed. + +If you are running on a Mac and have growlnotify_ installed, install the +ZenTest_ gem, then copy the ``config`` file to ``~/.autotest`` (or just +run ``rake`` in this directory). + +Once you have ``autotest`` installed, change to the root of your Puppet +git repository and run ``autotest`` with no arguments. To refresh the list +of files to scan, hit ``^c`` (that is, control-c). + +It's recommended you leave this running in another terminal during all +development, preferably on another monitor. + +.. _zentest: http://www.zenspider.com/ZSS/Products/ZenTest/ +.. _growlnotify: http://growl.info/extras.php diff --git a/ext/emacs/puppet-mode.el b/ext/emacs/puppet-mode.el index 0a7ee1a5f..f3f4589aa 100644 --- a/ext/emacs/puppet-mode.el +++ b/ext/emacs/puppet-mode.el @@ -2,10 +2,11 @@ ;;; puppet-mode.el ;;; ;;; Author: lutter -;;; Description: A simple mode for editing puppet manifests +;;; Author: Russ Allbery <rra@stanford.edu> ;;; +;;; Description: A simple mode for editing puppet manifests -(defconst puppet-mode-version "0.1") +(defconst puppet-mode-version "0.2") (defvar puppet-mode-abbrev-table nil "Abbrev table in use in puppet-mode buffers.") @@ -56,11 +57,43 @@ "*Indentation column of comments." :type 'integer :group 'puppet) +(defun puppet-count-matches (re start end) + "The same as Emacs 22 count-matches, for portability to other versions +of Emacs." + (save-excursion + (let ((n 0)) + (goto-char start) + (while (re-search-forward re end t) (setq n (1+ n))) + n))) + (defun puppet-comment-line-p () "Return non-nil iff this line is a comment." (save-excursion - (beginning-of-line) - (looking-at (format "\\s-*%s" comment-start)))) + (save-match-data + (beginning-of-line) + (looking-at (format "\\s-*%s" comment-start))))) + +(defun puppet-block-indent () + "If point is in a block, return the indentation of the first line of that +block (the line containing the opening brace). Used to set the indentation +of the closing brace of a block." + (save-excursion + (save-match-data + (let ((opoint (point)) + (apoint (search-backward "{" nil t))) + (when apoint + ;; This is a bit of a hack and doesn't allow for strings. We really + ;; want to parse by sexps at some point. + (let ((close-braces (puppet-count-matches "}" apoint opoint)) + (open-braces 0)) + (while (and apoint (> close-braces open-braces)) + (setq apoint (search-backward "{" nil t)) + (when apoint + (setq close-braces (puppet-count-matches "}" apoint opoint)) + (setq open-braces (1+ open-braces))))) + (if apoint + (current-indentation) + nil)))))) (defun puppet-in-array () "If point is in an array, return the position of the opening '[' of @@ -77,7 +110,7 @@ that array, else return nil." ;; ### steps, baby steps. A more robust strategy might be ;; ### to walk backwards by sexps, until hit a wall, then ;; ### inspect the nature of that wall. - (if (= (count-matches "\\]" apoint opoint) 0) + (if (= (puppet-count-matches "\\]" apoint opoint) 0) apoint)))))) (defun puppet-in-include () @@ -90,15 +123,14 @@ of the initial include plus puppet-include-indent." (while not-found (forward-line -1) (cond - ((puppet-comment-line-p) - (if (bobp) - (setq not-found nil))) - ((looking-at "^\\s-*include\\s-+.*,\\s-*$") - (setq include-column - (+ (current-indentation) puppet-include-indent)) - (setq not-found nil)) - ((not (looking-at ".*,\\s-*$")) - (setq not-found nil)))) + ((bobp) + (setq not-found nil)) + ((looking-at "^\\s-*include\\s-+.*,\\s-*$") + (setq include-column + (+ (current-indentation) puppet-include-indent)) + (setq not-found nil)) + ((not (looking-at ".*,\\s-*$")) + (setq not-found nil)))) include-column)))) (defun puppet-indent-line () @@ -110,6 +142,7 @@ of the initial include plus puppet-include-indent." (let ((not-indented t) (array-start (puppet-in-array)) (include-start (puppet-in-include)) + (block-indent (puppet-block-indent)) cur-indent) (cond (array-start @@ -146,18 +179,11 @@ of the initial include plus puppet-include-indent." (setq cur-indent (current-column)))) (include-start (setq cur-indent include-start)) - ((looking-at "^[^{\n]*}") - ;; This line contains the end of a block, but the block does - ;; not also begin on this line, so decrease the indentation. - (save-excursion - (forward-line -1) - (if (looking-at "^.*}") - (progn - (setq cur-indent (- (current-indentation) puppet-indent-level)) - (setq not-indented nil)) - (setq cur-indent (- (current-indentation) puppet-indent-level)))) - (if (< cur-indent 0) ; We can't indent past the left margin - (setq cur-indent 0))) + ((and (looking-at "^\\s-*}\\s-*$") block-indent) + ;; This line contains only a closing brace and we're at the inner + ;; block, so we should indent it matching the indentation of the + ;; opening brace of the block. + (setq cur-indent block-indent)) (t ;; Otherwise, we did not start on a block-ending-only line. (save-excursion @@ -165,30 +191,136 @@ of the initial include plus puppet-include-indent." (while not-indented (forward-line -1) (cond + ;; Comment lines are ignored unless we're at the start of the + ;; buffer. ((puppet-comment-line-p) (if (bobp) - (setq not-indented nil) - ;; else ignore the line and continue iterating backwards - )) - ((looking-at "^.*}") ; indent at the level of the END_ token + (setq not-indented nil))) + + ;; Brace or paren on a line by itself will already be indented to + ;; the right level, so we can cheat and stop there. + ((looking-at "^\\s-*[\)}]\\s-*") (setq cur-indent (current-indentation)) (setq not-indented nil)) - ((looking-at "^.*{") ; indent an extra level + + ;; Brace or paren not on a line by itself will be indented one + ;; level too much, but don't catch cases where the block is + ;; started and closed on the same line. + ((looking-at "^[^\({]*[\)}]\\s-*$") + (setq cur-indent (- (current-indentation) puppet-indent-level)) + (setq not-indented nil)) + + ;; Indent by one level more than the start of our block. We lose + ;; if there is more than one block opened and closed on the same + ;; line but it's still unbalanced; hopefully people don't do that. + ((looking-at "^.*{[^}]*$") + (setq cur-indent (+ (current-indentation) puppet-indent-level)) + (setq not-indented nil)) + + ;; Indent by one level if the line ends with an open paren. + ((looking-at "^.*\(\\s-*$") (setq cur-indent (+ (current-indentation) puppet-indent-level)) (setq not-indented nil)) - ((looking-at "^.*;\\s-*$") ; Semicolon ends a nested resource + + ;; Semicolon ends a block for a resource when multiple resources + ;; are defined in the same block, but try not to get the case of + ;; a complete resource on a single line wrong. + ((looking-at "^\\([^'\":\n]\\|\"[^\"]*\"\\|'[^']'\\)**;\\s-*$") (setq cur-indent (- (current-indentation) puppet-indent-level)) (setq not-indented nil)) - ((looking-at "^.*:\\s-*$") ; indent an extra level after : + + ;; Indent an extra level after : since it introduces a resource. + ((looking-at "^.*:\\s-*$") (setq cur-indent (+ (current-indentation) puppet-indent-level)) (setq not-indented nil)) + + ;; Start of buffer. ((bobp) - (setq not-indented nil)) - ))))) - (if cur-indent + (setq not-indented nil))))) + + ;; If this line contains only a closing paren, we should lose one + ;; level of indentation. + (if (looking-at "^\\s-*\)\\s-*$") + (setq cur-indent (- cur-indent puppet-indent-level))))) + + ;; We've figured out the indentation, so do it. + (if (and cur-indent (> cur-indent 0)) (indent-line-to cur-indent) (indent-line-to 0))))) +(defvar puppet-font-lock-syntax-table + (let* ((tbl (copy-syntax-table puppet-mode-syntax-table))) + (modify-syntax-entry ?_ "w" tbl) + tbl)) + +(defvar puppet-font-lock-keywords + (list + ;; defines, classes, and nodes + '("^\\s *\\(class\\|define\\|node\\)\\s +\\([^( \t\n]+\\)" + 2 font-lock-function-name-face) + ;; inheritence + '("\\s +inherits\\s +\\([^( \t\n]+\\)" + 1 font-lock-function-name-face) + ;; include + '("\\(^\\|\\s +\\)include\\s +\\(\\([a-zA-Z0-9:_-]+\\(,[ \t\n]*\\)?\\)+\\)" + 2 font-lock-reference-face) + ;; keywords + (cons (concat + "\\b\\(\\(" + (mapconcat + 'identity + '("alert" + "case" + "class" + "crit" + "debug" + "default" + "define" + "defined" + "else" + "emerg" + "err" + "fail" + "false" + "file" + "filebucket" + "generate" + "if" + "import" + "include" + "info" + "inherits" + "node" + "notice" + "realize" + "search" + "tag" + "tagged" + "template" + "true" + "warning" + ) + "\\|") + "\\)\\>\\)") + 1) + ;; variables + '("\\(^\\|[^_:.@$]\\)\\b\\(true\\|false\\)\\>" + 2 font-lock-variable-name-face) + ;; variables + '("\\(\\$\\([^a-zA-Z0-9 \n]\\|[0-9]\\)\\)\\W" + 1 font-lock-variable-name-face) + '("\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\|:\\)+" + 0 font-lock-variable-name-face) + ;; usage of types + '("^\\s *\\([a-zA-Z_-]+\\)\\s +{" + 1 font-lock-type-face) + ;; overrides + '("^\\s +\\([a-zA-Z_-]+\\)\\[" + 1 font-lock-type-face) + ;; general delimited string + '("\\(^\\|[[ \t\n<+(,=]\\)\\(%[xrqQwW]?\\([^<[{(a-zA-Z0-9 \n]\\)[^\n\\\\]*\\(\\\\.[^\n\\\\]*\\)*\\(\\3\\)\\)" + (2 font-lock-string-face))) + "*Additional expressions to highlight in puppet mode.") ;;;###autoload (defun puppet-mode () @@ -213,97 +345,14 @@ The variable puppet-indent-level controls the amount of indentation. (set (make-local-variable 'paragraph-ignore-fill-prefix) t) (set (make-local-variable 'paragraph-start) "\f\\|[ ]*$") (set (make-local-variable 'paragraph-separate) "[ \f]*$") - (run-hooks 'puppet-mode-hook)) - -(cond - ((featurep 'font-lock) (or (boundp 'font-lock-variable-name-face) (setq font-lock-variable-name-face font-lock-type-face)) - - (setq puppet-font-lock-syntactic-keywords - '( - ("\\(^\\|[=(,~?:;]\\|\\(^\\|\\s \\)\\(if\\|elsif\\|unless\\|while\\|until\\|when\\|and\\|or\\|&&\\|||\\)\\|g?sub!?\\|scan\\|split!?\\)\\s *\\(/\\)[^/\n\\\\]*\\(\\\\.[^/\n\\\\]*\\)*\\(/\\)" - (4 (7 . ?/)) - (6 (7 . ?/))) - ("^\\(=\\)begin\\(\\s \\|$\\)" 1 (7 . nil)) - ("^\\(=\\)end\\(\\s \\|$\\)" 1 (7 . nil)))) - - (cond ((featurep 'xemacs) - (put 'puppet-mode 'font-lock-defaults - '((puppet-font-lock-keywords) - nil nil nil - beginning-of-line - (font-lock-syntactic-keywords - . puppet-font-lock-syntactic-keywords)))) - (t - (add-hook 'puppet-mode-hook - '(lambda () - (make-local-variable 'font-lock-defaults) - (make-local-variable 'font-lock-keywords) - (make-local-variable 'font-lock-syntax-table) - (make-local-variable 'font-lock-syntactic-keywords) - (setq font-lock-defaults '((puppet-font-lock-keywords) nil nil)) - (setq font-lock-keywords puppet-font-lock-keywords) - (setq font-lock-syntax-table puppet-font-lock-syntax-table) - (setq font-lock-syntactic-keywords puppet-font-lock-syntactic-keywords))))) - - (defvar puppet-font-lock-syntax-table - (let* ((tbl (copy-syntax-table puppet-mode-syntax-table))) - (modify-syntax-entry ?_ "w" tbl) - tbl)) - - (defvar puppet-font-lock-keywords - (list - ;; defines - '("^\\s *\\(define\\|node\\|class\\)\\s +\\([^( \t\n]+\\)" - 2 font-lock-function-name-face) - '("\\s +inherits\\s +\\([^( \t\n]+\\)" - 1 font-lock-function-name-face) - ;; include - '("^\\s *include\\s +\\([^( \t\n,]+\\)" - 1 font-lock-reference-face) - ;; hack to catch continued includes - '("^\\s *\\([a-zA-Z0-9:_-]+\\),?\\s *$" - 1 font-lock-reference-face) - ;; keywords - (cons (concat - "\\b\\(\\(" - (mapconcat - 'identity - '("case" - "class" - "default" - "define" - "false" - "import" - "include" - "inherits" - "node" - "realize" - "true" - ) - "\\|") - "\\)\\>\\)") - 1) - ;; variables - '("\\(^\\|[^_:.@$]\\|\\.\\.\\)\\b\\(nil\\|self\\|true\\|false\\)\\>" - 2 font-lock-variable-name-face) - ;; variables - '("\\(\\$\\([^a-zA-Z0-9 \n]\\|[0-9]\\)\\)\\W" - 1 font-lock-variable-name-face) - '("\\(\\$\\|@\\|@@\\)\\(\\w\\|_\\)+" - 0 font-lock-variable-name-face) - ;; usage of types - '("^\\s +\\([a-zA-Z_-]+\\)\\s +{" - 1 font-lock-type-face) - ;; overrides - '("^\\s +\\([a-zA-Z_-]+\\)\\[" - 1 font-lock-type-face) - ;; general delimited string - '("\\(^\\|[[ \t\n<+(,=]\\)\\(%[xrqQwW]?\\([^<[{(a-zA-Z0-9 \n]\\)[^\n\\\\]*\\(\\\\.[^\n\\\\]*\\)*\\(\\3\\)\\)" - (2 font-lock-string-face)) - ) - "*Additional expressions to highlight in puppet mode.")) - ) + (set (make-local-variable 'font-lock-keywords) puppet-font-lock-keywords) + (set (make-local-variable 'font-lock-multiline) t) + (set (make-local-variable 'font-lock-defaults) + '((puppet-font-lock-keywords) nil nil)) + (set (make-local-variable 'font-lock-syntax-table) + puppet-font-lock-syntax-table) + (run-hooks 'puppet-mode-hook)) (provide 'puppet-mode) diff --git a/ext/ldap/puppet.schema b/ext/ldap/puppet.schema index d8dc4260d..a7a5f46ff 100644 --- a/ext/ldap/puppet.schema +++ b/ext/ldap/puppet.schema @@ -17,6 +17,11 @@ attributetype ( 1.1.3.11 NAME 'environment' EQUALITY caseIgnoreIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) +attributetype ( 1.1.3.12 NAME 'puppetvar' + DESC 'A variable setting for puppet' + EQUALITY caseIgnoreIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 ) + objectclass ( 1.1.1.2 NAME 'puppetClient' SUP top AUXILIARY DESC 'Puppet Client objectclass' - MAY ( puppetclass $ parentnode $ environment )) + MAY ( puppetclass $ parentnode $ environment $ puppetvar )) diff --git a/install.rb b/install.rb index d744f2d2d..c32c24245 100755 --- a/install.rb +++ b/install.rb @@ -106,7 +106,7 @@ def do_man(man, strip = 'man/') File.install(mf, omf, 0644, true) gzip = %x{which gzip} gzip.chomp! - %x{#{gzip} #{omf}} + %x{#{gzip} -f #{omf}} end end @@ -210,6 +210,15 @@ def prepare_installation end end + # Mac OS X 10.5 declares bindir and sbindir as + # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin + # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/sbin + # which is not generally where people expect executables to be installed + if RUBY_PLATFORM == "universal-darwin9.0" + Config::CONFIG['bindir'] = "/usr/bin" + Config::CONFIG['sbindir'] = "/usr/sbin" + end + if (destdir = ENV['DESTDIR']) bindir = "#{destdir}#{Config::CONFIG['bindir']}" sbindir = "#{destdir}#{Config::CONFIG['sbindir']}" diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 78cf7c47d..d77ec0486 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -516,9 +516,11 @@ module Puppet # Central fact information. self.setdefaults(:main, - :factpath => ["$vardir/facts", - "Where Puppet should look for facts. Multiple directories should - be colon-separated, like normal PATH variables."], + :factpath => {:default => "$vardir/facts", + :desc => "Where Puppet should look for facts. Multiple directories should + be colon-separated, like normal PATH variables.", + :call_on_define => true, # Call our hook with the default value, so we always get the value added to facter. + :hook => proc { |value| Facter.search(value) if Facter.respond_to?(:search) }}, :factdest => ["$vardir/facts", "Where Puppet should store facts that it pulls down from the central server."], @@ -633,6 +635,10 @@ module Puppet :ldapclassattrs => ["puppetclass", "The LDAP attributes to use to define Puppet classes. Values should be comma-separated."], + :ldapstackedattrs => ["puppetvar", + "The LDAP attributes that should be stacked to arrays by adding + the values in all hierarchy elements of the tree. Values + should be comma-separated."], :ldapattrs => ["all", "The LDAP attributes to include when querying LDAP for nodes. All returned attributes are set as variables in the top-level scope. diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb index 6c41c18d4..bc58908fd 100644 --- a/lib/puppet/indirector/node/ldap.rb +++ b/lib/puppet/indirector/node/ldap.rb @@ -19,6 +19,8 @@ class Puppet::Node::Ldap < Puppet::Indirector::Ldap node = Puppet::Node.new(name) + information[:stacked_parameters] = {} + parent_info = nil parent = information[:parent] parents = [name] @@ -34,6 +36,10 @@ class Puppet::Node::Ldap < Puppet::Indirector::Ldap raise Puppet::Error.new("Could not find parent node '%s'" % parent) end information[:classes] += parent_info[:classes] + parent_info[:stacked].each do |value| + param = value.split('=', 2) + information[:stacked_parameters][param[0]] = param[1] + end parent_info[:parameters].each do |param, value| # Specifically test for whether it's set, so false values are handled # correctly. @@ -45,6 +51,15 @@ class Puppet::Node::Ldap < Puppet::Indirector::Ldap parent = parent_info[:parent] end + information[:stacked].each do |value| + param = value.split('=', 2) + information[:stacked_parameters][param[0]] = param[1] + end + + information[:stacked_parameters].each do |param, value| + information[:parameters][param] = value unless information[:parameters].include?(param) + end + node.classes = information[:classes].uniq unless information[:classes].empty? node.parameters = information[:parameters] unless information[:parameters].empty? node.environment = information[:environment] if information[:environment] @@ -62,6 +77,12 @@ class Puppet::Node::Ldap < Puppet::Indirector::Ldap end end + # The attributes that Puppet will stack as array over the full + # hierarchy. + def stacked_attributes + Puppet[:ldapstackedattrs].split(/\s*,\s*/) + end + # Process the found entry. We assume that we don't just want the # ldap object. def process(name, entry) @@ -85,6 +106,14 @@ class Puppet::Node::Ldap < Puppet::Indirector::Ldap end } + result[:stacked] = [] + stacked_attributes.each { |attr| + if values = entry.vals(attr) + result[:stacked] = result[:stacked] + values + end + } + + result[:parameters] = entry.to_hash.inject({}) do |hash, ary| if ary[1].length == 1 hash[ary[0]] = ary[1].shift diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index 8fba41121..d67b3d275 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -332,7 +332,7 @@ class Puppet::Parser::Compiler unless remaining.empty? fail Puppet::ParseError, - "Could not find object(s) %s" % remaining.collect { |o| + "Could not find resource(s) %s for overriding" % remaining.collect { |o| o.ref }.join(", ") end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index e0b60e161..93991275c 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -165,7 +165,7 @@ module Functions type is defined, either as a native type or a defined type, or whether a class is defined. This is useful for checking whether a class is defined and only including it if it is. This function can also test whether a resource has been defined, using resource references - (e.g., ``if defined(File['/tmp/myfile'] { ... }``). This function is unfortunately + (e.g., ``if defined(File['/tmp/myfile']) { ... }``). This function is unfortunately dependent on the parse order of the configuration when testing whether a resource is defined.") do |vals| result = false vals.each do |val| diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index f27c1c5c8..04ca41494 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -62,7 +62,7 @@ class Puppet::Parser::Interpreter # exception elsewhere and reuse the parser. If one doesn't # exist, then reraise. if @parsers[environment] - Puppet.err detail + Puppet.err(detail.to_s + "; using previously parsed manifests") else raise detail end diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index 7a8f74156..4790cea30 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -20,6 +20,15 @@ class Puppet::Parser::TemplateWrapper end end + # Should return true if a variable is defined, false if it is not + def has_variable?(name) + if @scope.lookupvar(name.to_s, false) != :undefined + true + else + false + end + end + # Ruby treats variables like methods, so we can cheat here and # trap missing vars like they were missing methods. def method_missing(name, *args) diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb index e73bb0cb6..c02e15029 100644 --- a/lib/puppet/provider.rb +++ b/lib/puppet/provider.rb @@ -5,6 +5,10 @@ class Puppet::Provider include Puppet::Util::Warnings extend Puppet::Util::Warnings + require 'puppet/provider/confiner' + + extend Puppet::Provider::Confiner + Puppet::Util.logmethods(self, true) class << self @@ -40,27 +44,13 @@ class Puppet::Provider [name, self.name] end - if command == :missing - return nil - end - - command + return binary(command) end # Define commands that are not optional. def self.commands(hash) optional_commands(hash) do |name, path| - confine :exists => path - end - end - - def self.confine(hash) - hash.each do |p,v| - if v.is_a? Array - @confines[p] += v - else - @confines[p] << v - end + confine :exists => path, :for_binary => true end end @@ -108,10 +98,6 @@ class Puppet::Provider def self.initvars @defaults = {} @commands = {} - @origcommands = {} - @confines = Hash.new do |hash, key| - hash[key] = [] - end end # The method for returning a list of provider instances. Note that it returns providers, preferably with values already @@ -180,16 +166,7 @@ class Puppet::Provider def self.optional_commands(hash) hash.each do |name, path| name = symbolize(name) - @origcommands[name] = path - - # Try to find the full path (or verify already-full paths); otherwise - # store that the command is missing so we know it's defined but absent. - if tmp = binary(path) - path = tmp - @commands[name] = path - else - @commands[name] = :missing - end + @commands[name] = path if block_given? yield(name, path) @@ -208,69 +185,6 @@ class Puppet::Provider @source end - # Check whether this implementation is suitable for our platform. - def self.suitable?(short = true) - # A single false result is sufficient to turn the whole thing down. - # We don't return 'true' until the very end, though, so that every - # confine is tested. - missing = {} - @confines.each do |check, values| - case check - when :exists: - values.each do |value| - unless value and FileTest.exists? value - debug "Not suitable: missing %s" % value - return false if short - missing[:exists] ||= [] - missing[:exists] << value - end - end - when :true: - values.each do |v| - debug "Not suitable: false value" - unless v - return false if short - missing[:true] ||= 0 - missing[:true] += 1 - end - end - when :false: - values.each do |v| - debug "Not suitable: true value" - if v and short - return false if short - missing[:false] ||= 0 - missing[:false] += 1 - end - end - else # Just delegate everything else to facter - if result = Facter.value(check) - result = result.to_s.downcase.intern - - found = values.find do |v| - result == v.to_s.downcase.intern - end - unless found - debug "Not suitable: %s not in %s" % [check, values] - return false if short - missing[:facter] ||= {} - missing[:facter][check] = values - end - else - return false if short - missing[:facter] ||= {} - missing[:facter][check] = values - end - end - end - - if short - return true - else - return missing - end - end - # Does this provider support the specified parameter? def self.supports_parameter?(param) if param.is_a?(Class) @@ -309,8 +223,8 @@ class Puppet::Provider end dochook(:commands) do - if @origcommands.length > 0 - return " Required binaries: " + @origcommands.collect do |n, c| + if @commands.length > 0 + return " Required binaries: " + @commands.collect do |n, c| "``#{c}``" end.join(", ") + "." end diff --git a/lib/puppet/provider/confine.rb b/lib/puppet/provider/confine.rb new file mode 100644 index 000000000..35b80fdcf --- /dev/null +++ b/lib/puppet/provider/confine.rb @@ -0,0 +1,77 @@ +# The class that handles testing whether our providers +# actually work or not. +require 'puppet/util' + +class Puppet::Provider::Confine + include Puppet::Util + + @tests = {} + + class << self + attr_accessor :name + end + + def self.inherited(klass) + name = klass.to_s.split("::").pop.downcase.to_sym + raise "Test %s is already defined" % name if @tests.include?(name) + + klass.name = name + + @tests[name] = klass + end + + def self.test(name) + unless @tests[name] + begin + require "puppet/provider/confine/%s" % name + rescue LoadError => detail + unless detail.to_s.include?("no such file") + warn "Could not load confine test '%s': %s" % [name, detail] + end + # Could not find file + end + end + return @tests[name] + end + + attr_reader :values + + # Mark that this confine is used for testing binary existence. + attr_accessor :for_binary + def for_binary? + for_binary + end + + def initialize(values) + values = [values] unless values.is_a?(Array) + @values = values + end + + # Provide a hook for the message when there's a failure. + def message(value) + "" + end + + # Collect the results of all of them. + def result + values.collect { |value| pass?(value) } + end + + # Test whether our confine matches. + def valid? + values.each do |value| + unless pass?(value) + Puppet.debug message(value) + return false + end + end + + return true + ensure + reset + end + + # Provide a hook for subclasses. + def reset + end +end diff --git a/lib/puppet/provider/confine/exists.rb b/lib/puppet/provider/confine/exists.rb new file mode 100644 index 000000000..1d1ed8c84 --- /dev/null +++ b/lib/puppet/provider/confine/exists.rb @@ -0,0 +1,22 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::Exists < Puppet::Provider::Confine + def self.summarize(confines) + confines.inject([]) { |total, confine| total + confine.summary } + end + + def pass?(value) + if for_binary? + return false unless value = binary(value) + end + value and FileTest.exist?(value) + end + + def message(value) + "file %s does not exist" % value + end + + def summary + result.zip(values).inject([]) { |array, args| val, f = args; array << f unless val; array } + end +end diff --git a/lib/puppet/provider/confine/facter.rb b/lib/puppet/provider/confine/facter.rb new file mode 100644 index 000000000..9bb66c058 --- /dev/null +++ b/lib/puppet/provider/confine/facter.rb @@ -0,0 +1,37 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::Facter < Puppet::Provider::Confine + def self.summarize(confines) + result = Hash.new { |hash, key| hash[key] = [] } + confines.inject(result) { |total, confine| total[confine.fact] += confine.values unless confine.valid?; total } + end + + attr_accessor :fact + + # Are we a facter comparison? + def facter? + defined?(@facter) + end + + # Retrieve the value from facter + def facter_value + unless defined?(@facter_value) and @facter_value + @facter_value = ::Facter.value(@fact).to_s.downcase + end + @facter_value + end + + def message(value) + "facter value '%s' for '%s' not in required list '%s'" % [value, self.fact, values.join(",")] + end + + def pass?(value) + facter_value == value.to_s.downcase + end + + def reset + # Reset the cache. We want to cache it during a given + # run, but across runs. + @facter_value = nil + end +end diff --git a/lib/puppet/provider/confine/false.rb b/lib/puppet/provider/confine/false.rb new file mode 100644 index 000000000..b5b2b51c8 --- /dev/null +++ b/lib/puppet/provider/confine/false.rb @@ -0,0 +1,19 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::False < Puppet::Provider::Confine + def self.summarize(confines) + confines.inject(0) { |count, confine| count + confine.summary } + end + + def pass?(value) + ! value + end + + def message(value) + "true value when expecting false" + end + + def summary + result.find_all { |v| v == false }.length + end +end diff --git a/lib/puppet/provider/confine/feature.rb b/lib/puppet/provider/confine/feature.rb new file mode 100644 index 000000000..1d92b001a --- /dev/null +++ b/lib/puppet/provider/confine/feature.rb @@ -0,0 +1,17 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::Feature < Puppet::Provider::Confine + def self.summarize(confines) + confines.collect { |c| c.values }.flatten.uniq.find_all { |value| ! confines[0].pass?(value) } + end + + # Is the named feature available? + def pass?(value) + Puppet.features.send(value.to_s + "?") + end + + def message(value) + "feature %s is missing" % value + end +end + diff --git a/lib/puppet/provider/confine/true.rb b/lib/puppet/provider/confine/true.rb new file mode 100644 index 000000000..86b3b144f --- /dev/null +++ b/lib/puppet/provider/confine/true.rb @@ -0,0 +1,20 @@ +require 'puppet/provider/confine' + +class Puppet::Provider::Confine::True < Puppet::Provider::Confine + def self.summarize(confines) + confines.inject(0) { |count, confine| count + confine.summary } + end + + def pass?(value) + # Double negate, so we only get true or false. + ! ! value + end + + def message(value) + "false value when expecting true" + end + + def summary + result.find_all { |v| v == true }.length + end +end diff --git a/lib/puppet/provider/confine_collection.rb b/lib/puppet/provider/confine_collection.rb new file mode 100644 index 000000000..0c80086c9 --- /dev/null +++ b/lib/puppet/provider/confine_collection.rb @@ -0,0 +1,47 @@ +# Manage a collection of confines, returning a boolean or +# helpful information. +require 'puppet/provider/confine' + +class Puppet::Provider::ConfineCollection + def confine(hash) + if hash.include?(:for_binary) + for_binary = true + hash.delete(:for_binary) + else + for_binary = false + end + hash.each do |test, values| + if klass = Puppet::Provider::Confine.test(test) + @confines << klass.new(values) + @confines[-1].for_binary = true if for_binary + else + confine = Puppet::Provider::Confine.test(:facter).new(values) + confine.fact = test + @confines << confine + end + end + end + + def initialize + @confines = [] + end + + # Return a hash of the whole confine set, used for the Provider + # reference. + def summary + confines = Hash.new { |hash, key| hash[key] = [] } + @confines.each { |confine| confines[confine.class] << confine } + result = {} + confines.each do |klass, list| + value = klass.summarize(list) + next if (value.respond_to?(:length) and value.length == 0) or (value == 0) + result[klass.name] = value + + end + result + end + + def valid? + ! @confines.detect { |c| ! c.valid? } + end +end diff --git a/lib/puppet/provider/confiner.rb b/lib/puppet/provider/confiner.rb new file mode 100644 index 000000000..4605523e8 --- /dev/null +++ b/lib/puppet/provider/confiner.rb @@ -0,0 +1,20 @@ +require 'puppet/provider/confine_collection' + +module Puppet::Provider::Confiner + def confine(hash) + confine_collection.confine(hash) + end + + def confine_collection + unless defined?(@confine_collection) + @confine_collection = Puppet::Provider::ConfineCollection.new + end + @confine_collection + end + + # Check whether this implementation is suitable for our platform. + def suitable?(short = true) + return confine_collection.valid? if short + return confine_collection.summary + end +end diff --git a/lib/puppet/provider/cron/crontab.rb b/lib/puppet/provider/cron/crontab.rb index 7ddcc0505..e2228b15e 100755 --- a/lib/puppet/provider/cron/crontab.rb +++ b/lib/puppet/provider/cron/crontab.rb @@ -30,7 +30,7 @@ Puppet::Type.type(:cron).provide(:crontab, } crontab = record_line :crontab, :fields => %w{minute hour monthday month weekday command}, - :match => %r{^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$}, + :match => %r{^\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$}, :optional => %w{minute hour weekday month monthday}, :absent => "*" class << crontab diff --git a/lib/puppet/provider/group/ldap.rb b/lib/puppet/provider/group/ldap.rb new file mode 100644 index 000000000..632358ff1 --- /dev/null +++ b/lib/puppet/provider/group/ldap.rb @@ -0,0 +1,37 @@ +require 'puppet/provider/ldap' + +Puppet::Type.type(:group).provide :ldap, :parent => Puppet::Provider::Ldap do + desc "Group management via ``ldap``. This provider requires that you + have valid values for all of the ldap-related settings, + including ``ldapbase``. You will also almost definitely need settings + for ``ldapuser`` and ``ldappassword``, so that your clients can write + to ldap. + + Note that this provider will automatically generate a GID for you if + you do not specify one, but it is a potentially expensive operation, + as it iterates across all existing groups to pick the appropriate next + one." + + confine :true => Puppet.features.ldap? + + # We're mapping 'members' here because we want to make it + # easy for the ldap user provider to manage groups. This + # way it can just use the 'update' method in the group manager, + # whereas otherwise it would need to replicate that code. + manages(:posixGroup).at("ou=Groups").and.maps :name => :cn, :gid => :gidNumber, :members => :memberUid + + # Find the next gid after the current largest gid. + provider = self + manager.generates(:gidNumber).with do + largest = 0 + provider.manager.search.each do |hash| + next unless value = hash[:gid] + num = value[0].to_i + if num > largest + largest = num + end + end + largest + 1 + end + +end diff --git a/lib/puppet/provider/ldap.rb b/lib/puppet/provider/ldap.rb new file mode 100644 index 000000000..76834f94d --- /dev/null +++ b/lib/puppet/provider/ldap.rb @@ -0,0 +1,137 @@ +require 'puppet/provider' + +# The base class for LDAP providers. +class Puppet::Provider::Ldap < Puppet::Provider + require 'puppet/util/ldap/manager' + + class << self + attr_reader :manager + end + + # Look up all instances at our location. Yay. + def self.instances + return [] unless list = manager.search + + list.collect { |entry| new(entry) } + end + + # Specify the ldap manager for this provider, which is + # used to figure out how we actually interact with ldap. + def self.manages(*args) + @manager = Puppet::Util::Ldap::Manager.new + @manager.manages(*args) + + # Set up our getter/setter methods. + mk_resource_methods + return @manager + end + + # Query all of our resources from ldap. + def self.prefetch(resources) + resources.each do |name, resource| + if result = manager.find(name) + result[:ensure] = :present + resource.provider = new(result) + else + resource.provider = new(:ensure => :absent) + end + end + end + + attr_reader :ldap_properties + + def manager + self.class.manager + end + + def create + @property_hash[:ensure] = :present + self.class.resource_type.validproperties.each do |property| + if val = resource.should(property) + @property_hash[property] = val + end + end + end + + def delete + @property_hash[:ensure] = :absent + end + + def exists? + @property_hash[:ensure] != :absent + end + + # Apply our changes to ldap, yo. + def flush + # Just call the manager's update() method. + @property_hash.delete(:groups) + @ldap_properties.delete(:groups) + manager.update(name, ldap_properties, properties) + @property_hash.clear + @ldap_properties.clear + end + + def initialize(*args) + raise(Puppet::DevError, "No LDAP Configuration defined for %s" % self.class) unless self.class.manager + raise(Puppet::DevError, "Invalid LDAP Configuration defined for %s" % self.class) unless self.class.manager.valid? + super + + @property_hash = @property_hash.inject({}) do |result, ary| + param, values = ary + + # Skip any attributes we don't manage. + next result unless self.class.resource_type.validattr?(param) + + paramclass = self.class.resource_type.attrclass(param) + + unless values.is_a?(Array) + result[param] = values + next result + end + + # Only use the first value if the attribute class doesn't manage + # arrays of values. + if paramclass.superclass == Puppet::Parameter or paramclass.array_matching == :first + result[param] = values[0] + else + result[param] = values + end + result + end + + # Make a duplicate, so that we have a copy for comparison + # at the end. + @ldap_properties = @property_hash.dup + end + + # Return the current state of ldap. + def ldap_properties + @ldap_properties.dup + end + + # Return (and look up if necessary) the desired state. + def properties + if @property_hash.empty? + @property_hash = query || {:ensure => :absent} + if @property_hash.empty? + @property_hash[:ensure] = :absent + end + end + @property_hash.dup + end + + # Collect the current attributes from ldap. Returns + # the results, but also stores the attributes locally, + # so we have something to compare against when we update. + # LAK:NOTE This is normally not used, because we rely on prefetching. + def query + # Use the module function. + unless attributes = manager.find(name) + @ldap_properties = {} + return nil + end + + @ldap_properties = attributes + return @ldap_properties.dup + end +end diff --git a/lib/puppet/provider/nameservice/objectadd.rb b/lib/puppet/provider/nameservice/objectadd.rb index 8ebf8924b..4682b5169 100644 --- a/lib/puppet/provider/nameservice/objectadd.rb +++ b/lib/puppet/provider/nameservice/objectadd.rb @@ -25,7 +25,7 @@ class ObjectAdd < Puppet::Provider::NameService cmd = [command(:modify), flag(param), value] - if @resource[:allowdupe] == :true + if @resource[:allowdupe] == :true && param == :uid cmd << "-o" end cmd << @resource[:name] diff --git a/lib/puppet/provider/package/rpm.rb b/lib/puppet/provider/package/rpm.rb index 98ca1efa6..35684e11d 100755 --- a/lib/puppet/provider/package/rpm.rb +++ b/lib/puppet/provider/package/rpm.rb @@ -67,7 +67,7 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr end cmd = [command(:rpm), "-q", "--qf", "#{NEVRAFORMAT}\n", "-p", "#{@resource[:source]}"] - h = nevra_to_hash(execfail(cmd, Puppet::Error)) + h = self.class.nevra_to_hash(execfail(cmd, Puppet::Error)) return h[:ensure] end diff --git a/lib/puppet/provider/package/urpmi.rb b/lib/puppet/provider/package/urpmi.rb index 8adc62ab4..a95835284 100644 --- a/lib/puppet/provider/package/urpmi.rb +++ b/lib/puppet/provider/package/urpmi.rb @@ -1,6 +1,6 @@ Puppet::Type.type(:package).provide :urpmi, :parent => :rpm, :source => :rpm do desc "Support via ``urpmi``." - commands :urpmi => "urpmi", :rpm => "rpm" + commands :urpmi => "urpmi", :urpmq => "urpmq", :rpm => "rpm" if command('rpm') confine :true => begin @@ -41,9 +41,9 @@ Puppet::Type.type(:package).provide :urpmi, :parent => :rpm, :source => :rpm do # What's the latest package version available? def latest - output = urpmi "-S", :available, @resource[:name] + output = urpmq "-S", @resource[:name] - if output =~ /^#{@resource[:name]}\S+\s+(\S+)\s/ + if output =~ /^#{@resource[:name]}\s+:\s+.*\(\s+(\S+)\s+\)/ return $1 else # urpmi didn't find updates, pretend the current diff --git a/lib/puppet/provider/service/base.rb b/lib/puppet/provider/service/base.rb index 254b4fe4c..8964322b6 100755 --- a/lib/puppet/provider/service/base.rb +++ b/lib/puppet/provider/service/base.rb @@ -1,10 +1,10 @@ Puppet::Type.type(:service).provide :base do desc "The simplest form of service support. You have to specify enough about your service for this to work; the minimum you can specify - is a binary for starting the process, and this same binary will be searched - for in the process table to stop the service. It is preferable to - specify start, stop, and status commands, akin to how you would do - so using ``init``." + is a binary for starting the process, and this same binary will be + searched for in the process table to stop the service. It is + preferable to specify start, stop, and status commands, akin to how you + would do so using ``init``." commands :kill => "kill" diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb index 274c334a3..3081d0eb8 100755 --- a/lib/puppet/provider/service/init.rb +++ b/lib/puppet/provider/service/init.rb @@ -2,9 +2,9 @@ # customizations of this module. Puppet::Type.type(:service).provide :init, :parent => :base do desc "Standard init service management. This provider assumes that the - init script has not ``status`` command, because so few scripts do, - so you need to either provide a status command or specify via ``hasstatus`` - that one already exists in the init script." + init script has no ``status`` command, because so few scripts do, + so you need to either provide a status command or specify via + ``hasstatus`` that one already exists in the init script." class << self attr_accessor :defpath diff --git a/lib/puppet/provider/service/redhat.rb b/lib/puppet/provider/service/redhat.rb index b013c34dc..e2d6ac947 100755 --- a/lib/puppet/provider/service/redhat.rb +++ b/lib/puppet/provider/service/redhat.rb @@ -1,11 +1,11 @@ -# Manage debian services. Start/stop is the same as InitSvc, but enable/disable -# is special. +# Manage Red Hat services. Start/stop uses /sbin/service and enable/disable uses chkconfig + Puppet::Type.type(:service).provide :redhat, :parent => :init do desc "Red Hat's (and probably many others) form of ``init``-style service management; uses ``chkconfig`` for service enabling and disabling." - commands :chkconfig => "/sbin/chkconfig" - + commands :chkconfig => "/sbin/chkconfig", :service => "/sbin/service" + defaultfor :operatingsystem => [:redhat, :fedora, :suse, :centos] def self.defpath @@ -16,7 +16,6 @@ Puppet::Type.type(:service).provide :redhat, :parent => :init do def disable begin output = chkconfig(@resource[:name], :off) - output += chkconfig("--del", @resource[:name]) rescue Puppet::ExecutionFailure raise Puppet::Error, "Could not disable %s: %s" % [self.name, output] @@ -43,12 +42,28 @@ Puppet::Type.type(:service).provide :redhat, :parent => :init do # in the init scripts. def enable begin - output = chkconfig("--add", @resource[:name]) - output += chkconfig(@resource[:name], :on) + output = chkconfig(@resource[:name], :on) rescue Puppet::ExecutionFailure => detail raise Puppet::Error, "Could not enable %s: %s" % [self.name, detail] end end + + def restart + if @resource[:hasrestart] == true + service(@resource[:name], "restart") + else + return false + end + end + + def start + service(@resource[:name], "start") + end + + def stop + service(@resource[:name], "stop") + end + end diff --git a/lib/puppet/provider/ssh_authorized_key/parsed.rb b/lib/puppet/provider/ssh_authorized_key/parsed.rb new file mode 100644 index 000000000..7cb6626de --- /dev/null +++ b/lib/puppet/provider/ssh_authorized_key/parsed.rb @@ -0,0 +1,49 @@ +require 'puppet/provider/parsedfile' + +Puppet::Type.type(:ssh_authorized_key).provide(:parsed, + :parent => Puppet::Provider::ParsedFile, + :filetype => :flat, + :default_target => '' +) do + desc "Parse and generate authorized_keys files for SSH." + + text_line :comment, :match => /^#/ + text_line :blank, :match => /^\s+/ + + record_line :parsed, + :fields => %w{options type key name}, + :optional => %w{options}, + :rts => /^\s+/, + :match => /^(?:([^ ]+) )?(ssh-dss|ssh-rsa) ([^ ]+)(?: (.+))?$/, + :post_parse => proc { |record| + if record[:options].nil? + record[:options] = [:absent] + else + record[:options] = record[:options].split(',') + end + }, + :pre_gen => proc { |record| + if record[:options].include?(:absent) + record[:options] = "" + else + record[:options] = record[:options].join(',') + end + } + + def prefetch + if not @resource.should(:target) + # + # Set default target when user is given + if val = @resource.should(:user) + target = File.expand_path("~%s/.ssh/authorized_keys" % val) + Puppet::debug("Setting target to %s" % target) + @resource[:target] = target + else + raise Puppet::Error, "Missing attribute 'user' or 'target'" + end + end + + super + end +end + diff --git a/lib/puppet/provider/user/ldap.rb b/lib/puppet/provider/user/ldap.rb new file mode 100644 index 000000000..ba91a871e --- /dev/null +++ b/lib/puppet/provider/user/ldap.rb @@ -0,0 +1,115 @@ +require 'puppet/provider/ldap' + +Puppet::Type.type(:user).provide :ldap, :parent => Puppet::Provider::Ldap do + desc "User management via ``ldap``. This provider requires that you + have valid values for all of the ldap-related settings, + including ``ldapbase``. You will also almost definitely need settings + for ``ldapuser`` and ``ldappassword``, so that your clients can write + to ldap. + + Note that this provider will automatically generate a UID for you if + you do not specify one, but it is a potentially expensive operation, + as it iterates across all existing users to pick the appropriate next + one." + + confine :true => Puppet.features.ldap? + + manages(:posixAccount, :person).at("ou=People").named_by(:uid).and.maps :name => :uid, + :password => :userPassword, + :comment => :cn, + :uid => :uidNumber, + :gid => :gidNumber, + :home => :homeDirectory, + :shell => :loginShell + + # Use the last field of a space-separated array as + # the sn. LDAP requires a surname, for some stupid reason. + manager.generates(:sn).from(:cn).with do |cn| + x = 1 + cn[0].split(/\s+/)[-1] + end + + # Find the next uid after the current largest uid. + provider = self + manager.generates(:uidNumber).with do + largest = 0 + provider.manager.search.each do |hash| + next unless value = hash[:uid] + num = value[0].to_i + if num > largest + largest = num + end + end + largest + 1 + end + + # Find all groups this user is a member of in ldap. + def groups + # We want to cache the current result, so we know if we + # have to remove old values. + unless @property_hash[:groups] + unless result = group_manager.search("memberUid=%s" % name) + return @property_hash[:groups] = :absent + end + + return @property_hash[:groups] = result.collect { |r| r[:name] }.join(",") + end + return @property_hash[:groups] + end + + # Manage the list of groups this user is a member of. + def groups=(values) + should = values.split(",") + + if groups() == :absent + is = [] + else + is = groups().split(",") + end + + modes = {} + [is, should].flatten.uniq.each do |group| + # Skip it when they're in both + next if is.include?(group) and should.include?(group) + + # We're adding a group. + modes[group] = :add and next unless is.include?(group) + + # We're removing a group. + modes[group] = :remove and next unless should.include?(group) + end + + modes.each do |group, form| + self.fail "Could not find ldap group %s" % group unless ldap_group = group_manager.find(group) + + current = ldap_group[:members] + + if form == :add + if current.is_a?(Array) and ! current.empty? + new = current + [name] + else + new = [name] + end + else + new = current - [name] + new = :absent if new.empty? + end + + group_manager.update(group, {:ensure => :present, :members => current}, {:ensure => :present, :members => new}) + end + end + + private + + def group_manager + Puppet::Type.type(:group).provider(:ldap).manager + end + + def group_properties(values) + if values.empty? or values == :absent + {:ensure => :present} + else + {:ensure => :present, :members => values} + end + end +end diff --git a/lib/puppet/reference/providers.rb b/lib/puppet/reference/providers.rb index da815ddf1..610c7550d 100644 --- a/lib/puppet/reference/providers.rb +++ b/lib/puppet/reference/providers.rb @@ -71,6 +71,8 @@ providers = Puppet::Util::Reference.newreference :providers, :title => "Provider details += " - Got %s true tests that should have been false\n" % values when :false: details += " - Got %s false tests that should have been true\n" % values + when :feature: + details += " - Missing features %s\n" % values.collect { |f| f.to_s }.join(",") end end notes << details diff --git a/lib/puppet/type/mount.rb b/lib/puppet/type/mount.rb index e18630cc8..1679b73a3 100755 --- a/lib/puppet/type/mount.rb +++ b/lib/puppet/type/mount.rb @@ -141,7 +141,9 @@ module Puppet newproperty(:dump) do desc "Whether to dump the mount. Not all platforms - support this." + support this. Valid values are ``1`` or ``0``. Default is ``0``." + + newvalue(%r{(0|1)}) { } defaultto { if @resource.managed? diff --git a/lib/puppet/type/ssh_authorized_key.rb b/lib/puppet/type/ssh_authorized_key.rb new file mode 100644 index 000000000..e28fb7cda --- /dev/null +++ b/lib/puppet/type/ssh_authorized_key.rb @@ -0,0 +1,44 @@ +module Puppet + newtype(:ssh_authorized_key) do + @doc = "Manages ssh authorized keys." + + ensurable + + newparam(:name) do + desc "The ssh key comment." + + isnamevar + end + + newproperty(:type) do + desc "The encryption type used. Probably ssh-dss or ssh-rsa for + ssh version 2. Not used for ssh version 1." + + newvalue("ssh-dss") + newvalue("ssh-rsa") + + aliasvalue(:dsa, "ssh-dss") + aliasvalue(:rsa, "ssh-rsa") + end + + newproperty(:key) do + desc "The key itself; generally a long string of hex digits." + end + + newproperty(:user) do + desc "The user account in which the ssh key should be installed." + end + + newproperty(:target) do + desc "The file in which to store the ssh key." + end + + newproperty(:options, :array_matching => :all) do + desc "Key options, see sshd(8) for possible values. Multiple values + should be specified as an array." + + defaultto do :absent end + end + end +end + diff --git a/lib/puppet/util/ldap.rb b/lib/puppet/util/ldap.rb new file mode 100644 index 000000000..33f01f789 --- /dev/null +++ b/lib/puppet/util/ldap.rb @@ -0,0 +1,5 @@ +# +# Created by Luke Kanies on 2008-3-23. +# Copyright (c) 2008. All rights reserved. +module Puppet::Util::Ldap +end diff --git a/lib/puppet/util/ldap/connection.rb b/lib/puppet/util/ldap/connection.rb new file mode 100644 index 000000000..abcc07ecb --- /dev/null +++ b/lib/puppet/util/ldap/connection.rb @@ -0,0 +1,57 @@ +# +# Created by Luke Kanies on 2008-3-23. +# Copyright (c) 2008. All rights reserved. +require 'puppet/util/ldap' + +class Puppet::Util::Ldap::Connection + attr_accessor :host, :port, :user, :password, :reset, :ssl + + attr_reader :connection + + def close + connection.unbind if connection.bound? + end + + def initialize(host, port, options = {}) + raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" unless Puppet.features.ldap? + + @host, @port = host, port + + options.each do |param, value| + begin + send(param.to_s + "=", value) + rescue + raise ArgumentError, "LDAP connections do not support %s parameters" % param + end + end + end + + # Create a per-connection unique name. + def name + [host, port, user, password, ssl].collect { |p| p.to_s }.join("/") + end + + # Should we reset the connection? + def reset? + reset + end + + # Start our ldap connection. + def start + begin + case ssl + when :tls: + @connection = LDAP::SSLConn.new(host, port, true) + when true: + @connection = LDAP::SSLConn.new(host, port) + else + @connection = LDAP::Conn.new(host, port) + end + @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @connection.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @connection.simple_bind(user, password) + rescue => detail + raise Puppet::Error, "Could not connect to LDAP: %s" % detail + end + end +end diff --git a/lib/puppet/util/ldap/generator.rb b/lib/puppet/util/ldap/generator.rb new file mode 100644 index 000000000..2a868b0d9 --- /dev/null +++ b/lib/puppet/util/ldap/generator.rb @@ -0,0 +1,45 @@ +# +# Created by Luke Kanies on 2008-3-28. +# Copyright (c) 2008. All rights reserved. +require 'puppet/util/ldap' + +class Puppet::Util::Ldap::Generator + # Declare the attribute we'll use to generate the value. + def from(source) + @source = source + return self + end + + # Actually do the generation. + def generate(value = nil) + if value.nil? + @generator.call + else + @generator.call(value) + end + end + + # Initialize our generator with the name of the parameter + # being generated. + def initialize(name) + @name = name + end + + def name + @name.to_s + end + + def source + if defined?(@source) and @source + @source.to_s + else + nil + end + end + + # Provide the code that does the generation. + def with(&block) + @generator = block + return self + end +end diff --git a/lib/puppet/util/ldap/manager.rb b/lib/puppet/util/ldap/manager.rb new file mode 100644 index 000000000..9761fc753 --- /dev/null +++ b/lib/puppet/util/ldap/manager.rb @@ -0,0 +1,281 @@ +require 'puppet/util/ldap' +require 'puppet/util/ldap/connection' +require 'puppet/util/ldap/generator' + +# The configuration class for LDAP providers, plus +# connection handling for actually interacting with ldap. +class Puppet::Util::Ldap::Manager + attr_reader :objectclasses, :puppet2ldap, :location, :rdn + + # A null-op that just returns the config. + def and + return self + end + + # Set the offset from the search base and return the config. + def at(location) + @location = location + return self + end + + # The basic search base. + def base + [location, Puppet[:ldapbase]].join(",") + end + + # Convert the name to a dn, then pass the args along to + # our connection. + def create(name, attributes) + attributes = attributes.dup + + # Add the objectclasses + attributes["objectClass"] = objectclasses.collect { |o| o.to_s } + attributes["objectClass"] << "top" unless attributes["objectClass"].include?("top") + + attributes[rdn.to_s] = [name] + + # Generate any new values we might need. + generate(attributes) + + # And create our resource. + connect { |conn| conn.add dn(name), attributes } + end + + # Open, yield, and close the connection. Cannot be left + # open, at this point. + def connect + raise ArgumentError, "You must pass a block to #connect" unless block_given? + + unless defined?(@connection) and @connection + if Puppet[:ldaptls] + ssl = :tls + elsif Puppet[:ldapssl] + ssl = true + else + ssl = false + end + options = {:ssl => ssl} + if user = Puppet[:ldapuser] and user != "" + options[:user] = user + end + if password = Puppet[:ldappassword] and password != "" + options[:password] = password + end + @connection = Puppet::Util::Ldap::Connection.new(Puppet[:ldapserver], Puppet[:ldapport], options) + end + @connection.start + begin + yield @connection.connection + ensure + @connection.close + end + return nil + end + + # Convert the name to a dn, then pass the args along to + # our connection. + def delete(name) + connect { |connection| connection.delete dn(name) } + end + + # Calculate the dn for a given resource. + def dn(name) + ["#{rdn.to_s}=%s" % name, base].join(",") + end + + # Convert an ldap-style entry hash to a provider-style hash. + def entry2provider(entry) + raise ArgumentError, "Could not get dn from ldap entry" unless entry["dn"] + + # DN is always a single-entry array. Strip off the bits before the + # first comma, then the bits after the remaining equal sign. This is the + # name. + name = entry["dn"].dup.pop.split(",").shift.split("=").pop + + result = {:name => name} + + @ldap2puppet.each do |ldap, puppet| + result[puppet] = entry[ldap.to_s] || :absent + end + + result + end + + # Create our normal search filter. + def filter + return "objectclass=%s" % objectclasses[0] if objectclasses.length == 1 + return "(&(objectclass=" + objectclasses.join(")(objectclass=") + "))" + end + + # Find the associated entry for a resource. Returns a hash, minus + # 'dn', or nil if the entry cannot be found. + def find(name) + result = nil + connect do |conn| + begin + conn.search2(dn(name), 0, "objectclass=*") do |result| + # Convert to puppet-appropriate attributes + return entry2provider(result) + end + rescue => detail + return nil + end + end + end + + # Declare a new attribute generator. + def generates(parameter) + @generators << Puppet::Util::Ldap::Generator.new(parameter) + @generators[-1] + end + + # Generate any extra values we need to make the ldap entry work. + def generate(values) + return unless @generators.length > 0 + + @generators.each do |generator| + # Don't override any values that might exist. + next if values[generator.name] + + if generator.source + unless value = values[generator.source] + raise ArgumentError, "%s must be defined to generate %s" % [generator.source, generator.name] + end + result = generator.generate(value) + else + result = generator.generate + end + + result = [result] unless result.is_a?(Array) + result = result.collect { |r| r.to_s } + + values[generator.name] = result + end + end + + def initialize + @rdn = :cn + @generators = [] + end + + # Specify what classes this provider models. + def manages(*classes) + @objectclasses = classes + return self + end + + # Specify the attribute map. Assumes the keys are the puppet + # attributes, and the values are the ldap attributes, and creates a map + # for each direction. + def maps(attributes) + # The map with the puppet attributes as the keys + @puppet2ldap = attributes + + # and the ldap attributes as the keys. + @ldap2puppet = attributes.inject({}) { |map, ary| map[ary[1]] = ary[0]; map } + + return self + end + + # Return the ldap name for a puppet attribute. + def ldap_name(attribute) + @puppet2ldap[attribute].to_s + end + + # Convert the name to a dn, then pass the args along to + # our connection. + def modify(name, mods) + connect { |connection| connection.modify dn(name), mods } + end + + # Specify the rdn that we use to build up our dn. + def named_by(attribute) + @rdn = attribute + self + end + + # Return the puppet name for an ldap attribute. + def puppet_name(attribute) + @ldap2puppet[attribute] + end + + # Search for all entries at our base. A potentially expensive search. + def search(sfilter = nil) + sfilter ||= filter() + + result = [] + connect do |conn| + conn.search2(base, 1, sfilter) do |entry| + result << entry2provider(entry) + end + end + return nil if result.empty? + return result + end + + # Update the ldap entry with the desired state. + def update(name, is, should) + if should[:ensure] == :absent + Puppet.info "Removing %s from ldap" % dn(name) + delete(name) + return + end + + # We're creating a new entry + if is.empty? or is[:ensure] == :absent + Puppet.info "Creating %s in ldap" % dn(name) + # Remove any :absent params and :ensure, then convert the names to ldap names. + attrs = ldap_convert(should) + create(name, attrs) + return + end + + # We're modifying an existing entry. Yuck. + + mods = [] + # For each attribute we're deleting that is present, create a + # modify instance for deletion. + [is.keys, should.keys].flatten.uniq.each do |property| + # They're equal, so do nothing. + next if is[property] == should[property] + + attributes = ldap_convert(should) + + prop_name = ldap_name(property).to_s + + # We're creating it. + if is[property] == :absent or is[property].nil? + mods << LDAP::Mod.new(LDAP::LDAP_MOD_ADD, prop_name, attributes[prop_name]) + next + end + + # We're deleting it + if should[property] == :absent or should[property].nil? + mods << LDAP::Mod.new(LDAP::LDAP_MOD_DELETE, prop_name, []) + next + end + + # We're replacing an existing value + mods << LDAP::Mod.new(LDAP::LDAP_MOD_REPLACE, prop_name, attributes[prop_name]) + end + + modify(name, mods) + end + + # Is this a complete ldap configuration? + def valid? + location and objectclasses and ! objectclasses.empty? and puppet2ldap + end + + private + + # Convert a hash of attributes to ldap-like forms. This mostly means + # getting rid of :ensure and making sure everything's an array of strings. + def ldap_convert(attributes) + attributes.reject { |param, value| value == :absent or param == :ensure }.inject({}) do |result, ary| + value = (ary[1].is_a?(Array) ? ary[1] : [ary[1]]).collect { |v| v.to_s } + result[ldap_name(ary[0])] = value + result + end + end +end diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb index 1b953c95e..eec86625d 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -123,7 +123,7 @@ class Puppet::Util::Settings if pval = self.value(varname) pval else - raise Puppet::DevError, "Could not find value for %s" % parent + raise Puppet::DevError, "Could not find value for %s" % value end end diff --git a/lib/puppet/util/storage.rb b/lib/puppet/util/storage.rb index 9358a28e9..dc4e9cd71 100644 --- a/lib/puppet/util/storage.rb +++ b/lib/puppet/util/storage.rb @@ -6,6 +6,10 @@ class Puppet::Util::Storage include Singleton include Puppet::Util + def self.state + return @@state + end + def initialize self.class.load end diff --git a/lib/puppet/util/variables.rb b/lib/puppet/util/variables.rb deleted file mode 100644 index 1a78ef5c1..000000000 --- a/lib/puppet/util/variables.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Puppet::Util::Variables - def inithooks - @instance_init_hooks.dup - end - - def initvars - return unless defined? @class_init_hooks - self.inithooks.each do |var, value| - if value.is_a?(Class) - instance_variable_set("@" + var.to_s, value.new) - else - instance_variable_set("@" + var.to_s, value) - end - end - end - - def instancevar(hash) - @instance_init_hooks ||= {} - - unless method_defined?(:initvars) - define_method(:initvars) do - self.class.inithooks.each do |var, value| - if value.is_a?(Class) - instance_variable_set("@" + var.to_s, value.new) - else - instance_variable_set("@" + var.to_s, value) - end - end - end - end - hash.each do |var, value| - raise("Already initializing %s" % var) if @instance_init_hooks[var] - - @instance_init_hooks[var] = value - end - end -end - diff --git a/man/man8/filebucket.8 b/man/man8/filebucket.8 index fdaca9f31..0ef4c3842 100644 --- a/man/man8/filebucket.8 +++ b/man/man8/filebucket.8 @@ -112,5 +112,5 @@ Copyright (c) 2005 Reductive Labs, LLC Licensed under the GNU Public License -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/man/man8/pi.8 b/man/man8/pi.8 index da13d8e52..ba685797a 100644 --- a/man/man8/pi.8 +++ b/man/man8/pi.8 @@ -30,5 +30,5 @@ Only list parameters without detail Include metaparams -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/man/man8/puppet.8 b/man/man8/puppet.8 index 0281d330e..8fc24aab2 100644 --- a/man/man8/puppet.8 +++ b/man/man8/puppet.8 @@ -73,5 +73,5 @@ Copyright (c) 2005 Reductive Labs, LLC Licensed under the GNU Public License -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/man/man8/puppet.conf.8 b/man/man8/puppet.conf.8 index e8040fb8e..1bc1eb65a 100644 --- a/man/man8/puppet.conf.8 +++ b/man/man8/puppet.conf.8 @@ -4,7 +4,7 @@ Configuration Reference \- .\" Man page generated from reStructeredText. This page is autogenerated; any changes will get overwritten -.I (last generated on Sat Mar 22 17:46:15 +1100 2008) +.I (last generated on Mon May 05 09:33:01 +1000 2008) @@ -691,7 +691,7 @@ puppetmasterd .TP 2 \(bu -Default: development +Default: production .SS environments @@ -1739,9 +1739,9 @@ Default: $vardir/yaml .ce 0 .sp -.I This page autogenerated on Sat Mar 22 17:46:15 +1100 2008 +.I This page autogenerated on Mon May 05 09:33:01 +1000 2008 -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/man/man8/puppetca.8 b/man/man8/puppetca.8 index 42d6c1f1f..f9f89a95f 100644 --- a/man/man8/puppetca.8 +++ b/man/man8/puppetca.8 @@ -42,8 +42,8 @@ configuration options can also be generated by running puppetca with .TP -.B all: Operate on all outstanding requests. Only makes sense with -\'\-\-sign\', or \'\-\-list\'. +.B all: Operate on all items. Currently only makes sense with +\'\-\-sign\', \'\-\-clean\', or \'\-\-list\'. .TP @@ -51,7 +51,9 @@ configuration options can also be generated by running puppetca with This is useful when rebuilding hosts, since new certificate signing requests will only be honored if puppetca does not have a copy of a signed certificate for that host. The -certificate of the host remains valid. +certificate of the host remains valid. If \'\-\-all\' is specified +then all host certificates, both signed and unsigned, will be +removed. debug: Enable full debugging. @@ -112,5 +114,5 @@ Copyright (c) 2005 Reductive Labs, LLC Licensed under the GNU Public License -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/man/man8/puppetd.8 b/man/man8/puppetd.8 index 83b172809..9cadcd4e3 100644 --- a/man/man8/puppetd.8 +++ b/man/man8/puppetd.8 @@ -180,5 +180,5 @@ Copyright (c) 2005, 2006 Reductive Labs, LLC Licensed under the GNU Public License -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/man/man8/puppetdoc.8 b/man/man8/puppetdoc.8 index d7aacee75..8303b7ae0 100644 --- a/man/man8/puppetdoc.8 +++ b/man/man8/puppetdoc.8 @@ -58,5 +58,5 @@ Copyright (c) 2005\-2007 Reductive Labs, LLC Licensed under the GNU Public License -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/man/man8/puppetmasterd.8 b/man/man8/puppetmasterd.8 index 5a8f02751..c14da72d3 100644 --- a/man/man8/puppetmasterd.8 +++ b/man/man8/puppetmasterd.8 @@ -83,5 +83,5 @@ Copyright (c) 2005 Reductive Labs, LLC Licensed under the GNU Public License -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/man/man8/puppetrun.8 b/man/man8/puppetrun.8 index 753be1ca0..6b4048170 100644 --- a/man/man8/puppetrun.8 +++ b/man/man8/puppetrun.8 @@ -147,5 +147,5 @@ Copyright (c) 2005 Reductive Labs, LLC Licensed under the GNU Public License -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/man/man8/ralsh.8 b/man/man8/ralsh.8 index fc84fe0df..89c99c982 100644 --- a/man/man8/ralsh.8 +++ b/man/man8/ralsh.8 @@ -111,29 +111,16 @@ luke .nf $ ralsh user luke -.fi - -.\" visit_block_quote - -.TP -.B user { \'luke\': -home => \'/home/luke\', -uid => \'100\', -ensure => \'present\', -comment => \'Luke Kanies,,,\', -gid => \'1000\', -shell => \'/bin/bash\', -groups => [\'sysadmin\',\'audio\',\'video\',\'puppet\'] - -\.SH system-message -System Message: WARNING/2 (./ralsh.rst:, line 87) -Definition list ends without a blank line; unexpected unindent. - - +user { \'luke\': + home => \'/home/luke\', + uid => \'100\', + ensure => \'present\', + comment => \'Luke Kanies,,,\', + gid => \'1000\', + shell => \'/bin/bash\', + groups => [\'sysadmin\',\'audio\',\'video\',\'puppet\'] } - - -.\" depart_block_quote +.fi .SH AUTHOR Luke Kanies @@ -144,5 +131,5 @@ Copyright (c) 2005\-2007 Reductive Labs, LLC Licensed under the GNU Public License -.\" Generated by docutils manpage writer on 2008-03-22 17:46. +.\" Generated by docutils manpage writer on 2008-05-05 09:33. .\" diff --git a/spec/integration/defaults.rb b/spec/integration/defaults.rb index 185aa0026..b6f6bf109 100755 --- a/spec/integration/defaults.rb +++ b/spec/integration/defaults.rb @@ -5,8 +5,16 @@ require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/defaults' describe "Puppet defaults" do + after { Puppet.settings.clear } describe "when configuring the :crl" do - after { Puppet.settings.clear } + it "should add the :factpath to Facter's search paths" do + Facter.expects(:search).with("/my/fact/path") + + Puppet.settings[:factpath] = "/my/fact/path" + end + end + + describe "when setting the :factpath" do it "should warn if :cacrl is set to false" do Puppet.expects(:warning) diff --git a/spec/integration/node/catalog.rb b/spec/integration/node/catalog.rb index b0e651511..285b85869 100755 --- a/spec/integration/node/catalog.rb +++ b/spec/integration/node/catalog.rb @@ -8,6 +8,12 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Node::Catalog do describe "when using the indirector" do after { Puppet::Util::Cacher.invalidate } + before do + # This is so the tests work w/out networking. + Facter.stubs(:to_hash).returns({"hostname" => "foo.domain.com"}) + Facter.stubs(:value).returns("eh") + end + it "should be able to delegate to the :yaml terminus" do Puppet::Node::Catalog.indirection.stubs(:terminus_class).returns :yaml diff --git a/spec/integration/ral/types/package.rb b/spec/integration/type/package.rb index 20567629d..c244fa1cd 100755 --- a/spec/integration/ral/types/package.rb +++ b/spec/integration/type/package.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/type/package' diff --git a/spec/unit/indirector/node/ldap.rb b/spec/unit/indirector/node/ldap.rb index a40698662..878039c7c 100755 --- a/spec/unit/indirector/node/ldap.rb +++ b/spec/unit/indirector/node/ldap.rb @@ -17,6 +17,7 @@ describe Puppet::Node::Ldap do @searcher.stubs(:connection).returns(@connection) @searcher.stubs(:class_attributes).returns([]) @searcher.stubs(:parent_attribute).returns(nil) + @searcher.stubs(:stacked_attributes).returns([]) @searcher.stubs(:search_base).returns(:yay) @searcher.stubs(:search_filter).returns(:filter) @@ -195,6 +196,96 @@ describe Puppet::Node::Ldap do proc { @searcher.find(@request) }.should raise_error(ArgumentError) end end + + describe "and a puppet variable is specified" do + before do + @searcher.stubs(:stacked_attributes).returns(['puppetvar']) + end + + it "should add the variable to the node parameters" do + @entry.stubs(:vals).with("puppetvar").returns(%w{one=two}) + @entry.stubs(:to_hash).returns({}) + @node.expects(:parameters=).with("one" => "two") + @searcher.find(@request) + end + + it "should not overwrite node parameters specified as ldap object attribute" do + @entry.stubs(:vals).with("puppetvar").returns(%w{one=two}) + @entry.stubs(:to_hash).returns("one" => "three") + @node.expects(:parameters=).with("one" => "three") + @searcher.find(@request) + end + + it "should set entries without an equal sign to nil" do + @entry.stubs(:vals).with("puppetvar").returns(%w{one}) + @entry.stubs(:to_hash).returns({}) + @node.expects(:parameters=).with("one" => nil) + @searcher.find(@request) + end + + it "should ignore empty entries" do + @entry.stubs(:vals).with("puppetvar").returns(%w{}) + @entry.stubs(:to_hash).returns({}) + @searcher.find(@request) + end + end + describe "and a puppet variable as well as a parent node are specified" do + before do + @parent = mock 'parent' + + @searcher.meta_def(:search_filter) do |name| + return name + end + @connection.stubs(:search).with { |*args| args[2] == @name }.yields(@entry) + @connection.stubs(:search).with { |*args| args[2] == 'parent' }.yields(@parent) + + @searcher.stubs(:stacked_attributes).returns(['puppetvar']) + @searcher.stubs(:parent_attribute).returns(:parent) + end + + it "should add parent node variables to the child node parameters" do + @parent.stubs(:to_hash).returns({}) + @parent.stubs(:vals).with("puppetvar").returns(%w{one=two}) + @parent.stubs(:vals).with(:parent).returns(nil) + + @entry.stubs(:to_hash).returns({}) + @entry.stubs(:vals).with("puppetvar").returns(%w{}) + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @node.expects(:parameters=).with("one" => "two") + + @searcher.find(@request) + end + + it "should overwrite parent node variables with child node parameters" do + @parent.stubs(:to_hash).returns({}) + @parent.stubs(:vals).with("puppetvar").returns(%w{one=two}) + @parent.stubs(:vals).with(:parent).returns(nil) + + @entry.stubs(:to_hash).returns({}) + @entry.stubs(:vals).with("puppetvar").returns(%w{one=three}) + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @node.expects(:parameters=).with("one" => "three") + + @searcher.find(@request) + end + + it "should not overwrite parent node parameters specified as ldap object attribute" do + @parent.stubs(:to_hash).returns("one" => "three") + @parent.stubs(:vals).with("puppetvar").returns(%w{}) + @parent.stubs(:vals).with(:parent).returns(nil) + + @entry.stubs(:vals).with("puppetvar").returns(%w{one=two}) + @entry.stubs(:to_hash).returns({}) + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @node.expects(:parameters=).with("one" => "three") + + @searcher.find(@request) + end + + end end end diff --git a/spec/unit/parser/templatewrapper.rb b/spec/unit/parser/templatewrapper.rb new file mode 100755 index 000000000..40465f955 --- /dev/null +++ b/spec/unit/parser/templatewrapper.rb @@ -0,0 +1,57 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Parser::TemplateWrapper do + before(:each) do + compiler = stub('compiler', :environment => "foo") + parser = stub('parser', :watch_file => true) + @scope = stub('scope', :compiler => compiler, :parser => parser) + @file = "fake_template" + Puppet::Module.stubs(:find_template).returns("/tmp/fake_template") + FileTest.stubs(:exists?).returns("true") + @tw = Puppet::Parser::TemplateWrapper.new(@scope, @file) + end + + it "should create a new object TemplateWrapper from a scope and a file" do + Puppet::Module.expects(:find_template).with("fake_template", "foo").returns("/tmp/fake_template") + FileTest.expects(:exists?).with("/tmp/fake_template").returns(true) + tw = Puppet::Parser::TemplateWrapper.new(@scope, @file) + tw.should be_a_kind_of(Puppet::Parser::TemplateWrapper) + end + + it "should turn into a string like template[name]" do + @tw.to_s.should eql("template[/tmp/fake_template]") + end + + it "should return the processed template contents with a call to result" do + template_mock = mock("template", :result => "woot!") + File.expects(:read).with("/tmp/fake_template").returns("template contents") + ERB.expects(:new).with("template contents", 0, "-").returns(template_mock) + @tw.result.should eql("woot!") + end + + it "should return the contents of a variable if called via method_missing" do + @scope.expects(:lookupvar).with("chicken", false).returns("is good") + tw = Puppet::Parser::TemplateWrapper.new(@scope, @file) + tw.chicken.should eql("is good") + end + + it "should throw an exception if a variable is called via method_missing and it does not exist" do + @scope.expects(:lookupvar).with("chicken", false).returns(:undefined) + tw = Puppet::Parser::TemplateWrapper.new(@scope, @file) + lambda { tw.chicken }.should raise_error(Puppet::ParseError) + end + + it "should allow you to check whether a variable is defined with has_variable?" do + @scope.expects(:lookupvar).with("chicken", false).returns("is good") + tw = Puppet::Parser::TemplateWrapper.new(@scope, @file) + tw.has_variable?("chicken").should eql(true) + end + + it "should allow you to check whether a variable is not defined with has_variable?" do + @scope.expects(:lookupvar).with("chicken", false).returns(:undefined) + tw = Puppet::Parser::TemplateWrapper.new(@scope, @file) + tw.has_variable?("chicken").should eql(false) + end +end diff --git a/spec/unit/provider/confine.rb b/spec/unit/provider/confine.rb new file mode 100755 index 000000000..6a9214e26 --- /dev/null +++ b/spec/unit/provider/confine.rb @@ -0,0 +1,67 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/provider/confine' + +describe Puppet::Provider::Confine do + it "should require a value" do + lambda { Puppet::Provider::Confine.new() }.should raise_error(ArgumentError) + end + + it "should always convert values to an array" do + Puppet::Provider::Confine.new("/some/file").values.should be_instance_of(Array) + end + + it "should have a 'true' test" do + Puppet::Provider::Confine.test(:true).should be_instance_of(Class) + end + + it "should have a 'false' test" do + Puppet::Provider::Confine.test(:false).should be_instance_of(Class) + end + + it "should have a 'feature' test" do + Puppet::Provider::Confine.test(:feature).should be_instance_of(Class) + end + + it "should have an 'exists' test" do + Puppet::Provider::Confine.test(:exists).should be_instance_of(Class) + end + + it "should have a 'facter' test" do + Puppet::Provider::Confine.test(:facter).should be_instance_of(Class) + end + + describe "when testing all values" do + before { @confine = Puppet::Provider::Confine.new(%w{a b c}) } + + it "should be invalid if any values fail" do + @confine.stubs(:pass?).returns true + @confine.expects(:pass?).with("b").returns false + @confine.should_not be_valid + end + + it "should be valid if all values pass" do + @confine.stubs(:pass?).returns true + @confine.should be_valid + end + + it "should short-cut at the first failing value" do + @confine.expects(:pass?).once.returns false + @confine.valid? + end + end + + describe "when testing the result of the values" do + before { @confine = Puppet::Provider::Confine.new(%w{a b c d}) } + + it "should return an array with the result of the test for each value" do + @confine.stubs(:pass?).returns true + @confine.expects(:pass?).with("b").returns false + @confine.expects(:pass?).with("d").returns false + + @confine.result.should == [true, false, true, false] + end + end +end diff --git a/spec/unit/provider/confine/exists.rb b/spec/unit/provider/confine/exists.rb new file mode 100755 index 000000000..1ab1d39f7 --- /dev/null +++ b/spec/unit/provider/confine/exists.rb @@ -0,0 +1,80 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/exists' + +describe Puppet::Provider::Confine::Exists do + before do + @confine = Puppet::Provider::Confine::Exists.new("/my/file") + end + + it "should be named :exists" do + Puppet::Provider::Confine::Exists.name.should == :exists + end + + it "should use the 'pass?' method to test validity" do + @confine.expects(:pass?).with("/my/file") + @confine.valid? + end + + it "should return false if the value is false" do + @confine.pass?(false).should be_false + end + + it "should return false if the value does not point to a file" do + FileTest.expects(:exist?).with("/my/file").returns false + @confine.pass?("/my/file").should be_false + end + + it "should return true if the value points to a file" do + FileTest.expects(:exist?).with("/my/file").returns true + @confine.pass?("/my/file").should be_true + end + + it "should produce a message saying that a file is missing" do + @confine.message("/my/file").should be_include("does not exist") + end + + describe "and the confine is for binaries" do + before { @confine.stubs(:for_binary).returns true } + it "should use its 'binary' method to look up the full path of the file" do + @confine.expects(:binary).returns nil + @confine.pass?("/my/file") + end + + it "should return false if no binary can be found" do + @confine.expects(:binary).with("/my/file").returns nil + @confine.pass?("/my/file").should be_false + end + + it "should return true if the binary can be found and the file exists" do + @confine.expects(:binary).with("/my/file").returns "/my/file" + FileTest.expects(:exist?).with("/my/file").returns true + @confine.pass?("/my/file").should be_true + end + + it "should return false if the binary can be found but the file does not exist" do + @confine.expects(:binary).with("/my/file").returns "/my/file" + FileTest.expects(:exist?).with("/my/file").returns true + @confine.pass?("/my/file").should be_true + end + end + + it "should produce a summary containing all missing files" do + FileTest.stubs(:exist?).returns true + FileTest.expects(:exist?).with("/two").returns false + FileTest.expects(:exist?).with("/four").returns false + + confine = Puppet::Provider::Confine::Exists.new %w{/one /two /three /four} + confine.summary.should == %w{/two /four} + end + + it "should summarize multiple instances by returning a flattened array of their summaries" do + c1 = mock '1', :summary => %w{one} + c2 = mock '2', :summary => %w{two} + c3 = mock '3', :summary => %w{three} + + Puppet::Provider::Confine::Exists.summarize([c1, c2, c3]).should == %w{one two three} + end +end diff --git a/spec/unit/provider/confine/facter.rb b/spec/unit/provider/confine/facter.rb new file mode 100755 index 000000000..560263257 --- /dev/null +++ b/spec/unit/provider/confine/facter.rb @@ -0,0 +1,86 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/facter' + +describe Puppet::Provider::Confine::Facter::Facter do + it "should be named :facter" do + Puppet::Provider::Confine::Facter.name.should == :facter + end + + it "should require a value" do + lambda { Puppet::Provider::Confine::Facter.new() }.should raise_error(ArgumentError) + end + + it "should always convert values to an array" do + Puppet::Provider::Confine::Facter.new("/some/file").values.should be_instance_of(Array) + end + + it "should have an accessor for its fact" do + Puppet::Provider::Confine::Facter.new(:bar).should respond_to(:fact) + end + + describe "when testing values" do + before { @confine = Puppet::Provider::Confine::Facter.new("foo") } + it "should use the 'pass?' method to test validity" do + @confine.expects(:pass?).with("foo") + @confine.valid? + end + + it "should return true if the value matches the facter value" do + Facter.expects(:value).returns("foo") + + @confine.pass?("foo").should be_true + end + + it "should return false if the value does not match the facter value" do + Facter.expects(:value).returns("boo") + + @confine.pass?("foo").should be_false + end + + it "should be case insensitive" do + Facter.expects(:value).returns("FOO") + + @confine.pass?("foo").should be_true + end + + it "should not care whether the value is a string or symbol" do + Facter.expects(:value).returns("FOO") + + @confine.pass?(:foo).should be_true + end + + it "should cache the fact during testing" do + Facter.expects(:value).once.returns("FOO") + + @confine.pass?(:foo) + @confine.pass?(:foo) + end + + it "should produce a message that the fact value is not correct" do + @confine = Puppet::Provider::Confine::Facter.new(%w{bar bee}) + message = @confine.message("value") + message.should be_include("facter") + message.should be_include("bar,bee") + end + end + + describe "when summarizing multiple instances" do + it "should return a hash of failing variables and their values" do + c1 = stub '1', :valid? => false, :values => %w{one}, :fact => "uno" + c2 = stub '2', :valid? => true, :values => %w{two}, :fact => "dos" + c3 = stub '3', :valid? => false, :values => %w{three}, :fact => "tres" + + Puppet::Provider::Confine::Facter.summarize([c1, c2, c3]).should == {"uno" => %w{one}, "tres" => %w{three}} + end + + it "should combine the values of multiple confines with the same fact" do + c1 = stub '1', :valid? => false, :values => %w{one}, :fact => "uno" + c2 = stub '2', :valid? => false, :values => %w{two}, :fact => "uno" + + Puppet::Provider::Confine::Facter.summarize([c1, c2]).should == {"uno" => %w{one two}} + end + end +end diff --git a/spec/unit/provider/confine/false.rb b/spec/unit/provider/confine/false.rb new file mode 100755 index 000000000..c6c43e391 --- /dev/null +++ b/spec/unit/provider/confine/false.rb @@ -0,0 +1,52 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/false' + +describe Puppet::Provider::Confine::False do + it "should be named :false" do + Puppet::Provider::Confine::False.name.should == :false + end + + it "should require a value" do + lambda { Puppet::Provider::Confine.new() }.should raise_error(ArgumentError) + end + + describe "when testing values" do + before { @confine = Puppet::Provider::Confine::False.new("foo") } + + it "should use the 'pass?' method to test validity" do + @confine = Puppet::Provider::Confine::False.new("foo") + @confine.expects(:pass?).with("foo") + @confine.valid? + end + + it "should return true if the value is false" do + @confine.pass?(false).should be_true + end + + it "should return false if the value is not false" do + @confine.pass?("else").should be_false + end + + it "should produce a message that a value is true" do + @confine = Puppet::Provider::Confine::False.new("foo") + @confine.message("eh").should be_include("true") + end + end + + it "should be able to produce a summary with the number of incorrectly true values" do + confine = Puppet::Provider::Confine::False.new %w{one two three four} + confine.expects(:pass?).times(4).returns(true).returns(false).returns(true).returns(false) + confine.summary.should == 2 + end + + it "should summarize multiple instances by summing their summaries" do + c1 = mock '1', :summary => 1 + c2 = mock '2', :summary => 2 + c3 = mock '3', :summary => 3 + + Puppet::Provider::Confine::False.summarize([c1, c2, c3]).should == 6 + end +end diff --git a/spec/unit/provider/confine/feature.rb b/spec/unit/provider/confine/feature.rb new file mode 100755 index 000000000..1845c9a47 --- /dev/null +++ b/spec/unit/provider/confine/feature.rb @@ -0,0 +1,59 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/feature' + +describe Puppet::Provider::Confine::Feature do + it "should be named :feature" do + Puppet::Provider::Confine::Feature.name.should == :feature + end + + it "should require a value" do + lambda { Puppet::Provider::Confine::Feature.new() }.should raise_error(ArgumentError) + end + + it "should always convert values to an array" do + Puppet::Provider::Confine::Feature.new("/some/file").values.should be_instance_of(Array) + end + + describe "when testing values" do + before do + @features = mock 'features' + Puppet.stubs(:features).returns @features + @confine = Puppet::Provider::Confine::Feature.new("myfeature") + end + + it "should use the Puppet features instance to test validity" do + @features.expects(:myfeature?) + @confine.valid? + end + + it "should return true if the feature is present" do + @features.expects(:myfeature?).returns true + @confine.pass?("myfeature").should be_true + end + + it "should return false if the value is false" do + @features.expects(:myfeature?).returns false + @confine.pass?("myfeature").should be_false + end + + it "should log that a feature is missing" do + @confine.message("myfeat").should be_include("missing") + end + end + + it "should summarize multiple instances by returning a flattened array of all missing features" do + confines = [] + confines << Puppet::Provider::Confine::Feature.new(%w{one two}) + confines << Puppet::Provider::Confine::Feature.new(%w{two}) + confines << Puppet::Provider::Confine::Feature.new(%w{three four}) + + features = mock 'feature' + features.stub_everything + Puppet.stubs(:features).returns features + + Puppet::Provider::Confine::Feature.summarize(confines).sort.should == %w{one two three four}.sort + end +end diff --git a/spec/unit/provider/confine/true.rb b/spec/unit/provider/confine/true.rb new file mode 100755 index 000000000..c9cc83c9e --- /dev/null +++ b/spec/unit/provider/confine/true.rb @@ -0,0 +1,50 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/provider/confine/true' + +describe Puppet::Provider::Confine::True do + it "should be named :true" do + Puppet::Provider::Confine::True.name.should == :true + end + + it "should require a value" do + lambda { Puppet::Provider::Confine::True.new() }.should raise_error(ArgumentError) + end + + describe "when testing values" do + before { @confine = Puppet::Provider::Confine::True.new("foo") } + + it "should use the 'pass?' method to test validity" do + @confine.expects(:pass?).with("foo") + @confine.valid? + end + + it "should return true if the value is not false" do + @confine.pass?("else").should be_true + end + + it "should return false if the value is false" do + @confine.pass?(nil).should be_false + end + + it "should produce the message that a value is false" do + @confine.message("eh").should be_include("false") + end + end + + it "should produce the number of false values when asked for a summary" do + @confine = Puppet::Provider::Confine::True.new %w{one two three four} + @confine.expects(:pass?).times(4).returns(true).returns(false).returns(true).returns(false) + @confine.summary.should == 2 + end + + it "should summarize multiple instances by summing their summaries" do + c1 = mock '1', :summary => 1 + c2 = mock '2', :summary => 2 + c3 = mock '3', :summary => 3 + + Puppet::Provider::Confine::True.summarize([c1, c2, c3]).should == 6 + end +end diff --git a/spec/unit/provider/confine_collection.rb b/spec/unit/provider/confine_collection.rb new file mode 100755 index 000000000..da4b3fe72 --- /dev/null +++ b/spec/unit/provider/confine_collection.rb @@ -0,0 +1,122 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/provider/confine_collection' + +describe Puppet::Provider::ConfineCollection do + it "should be able to add confines" do + Puppet::Provider::ConfineCollection.new.should respond_to(:confine) + end + + describe "when creating confine instances" do + it "should create an instance of the named test with the provided values" do + test_class = mock 'test_class' + test_class.expects(:new).with(%w{my values}) + Puppet::Provider::Confine.expects(:test).with(:foo).returns test_class + + Puppet::Provider::ConfineCollection.new.confine :foo => %w{my values} + end + + describe "and the test cannot be found" do + before do + @facter = mock 'facter_test' + + Puppet::Provider::Confine.expects(:test).with(:foo).returns nil + Puppet::Provider::Confine.expects(:test).with(:facter).returns @facter + end + + it "should create a Facter test with the provided values and set the fact to the test name" do + confine = mock 'confine' + confine.expects(:fact=).with(:foo) + @facter.expects(:new).with(%w{my values}).returns confine + Puppet::Provider::ConfineCollection.new.confine :foo => %w{my values} + end + end + + describe "and the 'for_binary' option was provided" do + it "should mark the test as a binary confine" do + confine = mock 'confine' + confine.expects(:for_binary=).with true + Puppet::Provider::Confine.test(:exists).expects(:new).with(:bar).returns confine + Puppet::Provider::ConfineCollection.new.confine :exists => :bar, :for_binary => true + end + end + end + + it "should be valid if no confines are present" do + Puppet::Provider::ConfineCollection.new.should be_valid + end + + it "should be valid if all confines pass" do + c1 = mock 'c1', :valid? => true + c2 = mock 'c2', :valid? => true + + Puppet::Provider::Confine.test(:true).expects(:new).returns(c1) + Puppet::Provider::Confine.test(:false).expects(:new).returns(c2) + + confiner = Puppet::Provider::ConfineCollection.new + confiner.confine :true => :bar, :false => :bee + + confiner.should be_valid + end + + it "should not be valid if any confines fail" do + c1 = stub 'c1', :valid? => true + c2 = stub 'c2', :valid? => false + + Puppet::Provider::Confine.test(:true).expects(:new).returns(c1) + Puppet::Provider::Confine.test(:false).expects(:new).returns(c2) + + confiner = Puppet::Provider::ConfineCollection.new + confiner.confine :true => :bar, :false => :bee + + confiner.should_not be_valid + end + + describe "when providing a summary" do + before do + @confiner = Puppet::Provider::ConfineCollection.new + end + + it "should return a hash" do + @confiner.summary.should be_instance_of(Hash) + end + + it "should return an empty hash if the confiner is valid" do + @confiner.summary.should == {} + end + + it "should add each test type's summary to the hash" do + @confiner.confine :true => :bar, :false => :bee + Puppet::Provider::Confine.test(:true).expects(:summarize).returns :tsumm + Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm + + @confiner.summary.should == {:true => :tsumm, :false => :fsumm} + end + + it "should not include tests that return 0" do + @confiner.confine :true => :bar, :false => :bee + Puppet::Provider::Confine.test(:true).expects(:summarize).returns 0 + Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm + + @confiner.summary.should == {:false => :fsumm} + end + + it "should not include tests that return empty arrays" do + @confiner.confine :true => :bar, :false => :bee + Puppet::Provider::Confine.test(:true).expects(:summarize).returns [] + Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm + + @confiner.summary.should == {:false => :fsumm} + end + + it "should not include tests that return empty hashes" do + @confiner.confine :true => :bar, :false => :bee + Puppet::Provider::Confine.test(:true).expects(:summarize).returns({}) + Puppet::Provider::Confine.test(:false).expects(:summarize).returns :fsumm + + @confiner.summary.should == {:false => :fsumm} + end + end +end diff --git a/spec/unit/provider/confiner.rb b/spec/unit/provider/confiner.rb new file mode 100755 index 000000000..078fc4420 --- /dev/null +++ b/spec/unit/provider/confiner.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/provider/confiner' + +describe Puppet::Provider::Confiner do + before do + @object = Object.new + @object.extend(Puppet::Provider::Confiner) + end + + it "should have a method for defining confines" do + @object.should respond_to(:confine) + end + + it "should have a method for returning its confine collection" do + @object.should respond_to(:confine_collection) + end + + it "should have a method for testing suitability" do + @object.should respond_to(:suitable?) + end + + it "should delegate its confine method to its confine collection" do + coll = mock 'collection' + @object.stubs(:confine_collection).returns coll + coll.expects(:confine).with(:foo => :bar, :bee => :baz) + @object.confine(:foo => :bar, :bee => :baz) + end + + it "should create a new confine collection if one does not exist" do + Puppet::Provider::ConfineCollection.expects(:new).returns "mycoll" + @object.confine_collection.should == "mycoll" + end + + it "should reuse the confine collection" do + @object.confine_collection.should equal(@object.confine_collection) + end + + describe "when testing suitability" do + before do + @coll = mock 'collection' + @object.stubs(:confine_collection).returns @coll + end + + it "should return true if the confine collection is valid" do + @coll.expects(:valid?).returns true + @object.should be_suitable + end + + it "should return false if the confine collection is invalid" do + @coll.expects(:valid?).returns false + @object.should_not be_suitable + end + + it "should return the summary of the confine collection if a long result is asked for" do + @coll.expects(:summary).returns "myresult" + @object.suitable?(false).should == "myresult" + end + end +end diff --git a/spec/unit/provider/group/ldap.rb b/spec/unit/provider/group/ldap.rb new file mode 100755 index 000000000..3f12d74e3 --- /dev/null +++ b/spec/unit/provider/group/ldap.rb @@ -0,0 +1,66 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-10. +# Copyright (c) 2006. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +provider_class = Puppet::Type.type(:group).provider(:ldap) + +describe provider_class do + it "should have the Ldap provider class as its baseclass" do + provider_class.superclass.should equal(Puppet::Provider::Ldap) + end + + it "should manage :posixGroup objectclass" do + provider_class.manager.objectclasses.should == [:posixGroup] + end + + it "should use 'ou=Groups' as its relative base" do + provider_class.manager.location.should == "ou=Groups" + end + + it "should use :cn as its rdn" do + provider_class.manager.rdn.should == :cn + end + + it "should map :name to 'cn'" do + provider_class.manager.ldap_name(:name).should == 'cn' + end + + it "should map :gid to 'gidNumber'" do + provider_class.manager.ldap_name(:gid).should == 'gidNumber' + end + + it "should map :members to 'memberUid', to be used by the user ldap provider" do + provider_class.manager.ldap_name(:members).should == 'memberUid' + end + + describe "when being created" do + before do + # So we don't try to actually talk to ldap + @connection = mock 'connection' + provider_class.manager.stubs(:connect).yields @connection + end + + describe "with no gid specified" do + it "should pick the first available GID after the largest existing GID" do + low = {:name=>["luke"], :gid=>["100"]} + high = {:name=>["testing"], :gid=>["140"]} + provider_class.manager.expects(:search).returns([low, high]) + + resource = stub 'resource', :should => %w{whatever} + resource.stubs(:should).with(:gid).returns nil + resource.stubs(:should).with(:ensure).returns :present + instance = provider_class.new(:name => "luke", :ensure => :absent) + instance.stubs(:resource).returns resource + + @connection.expects(:add).with { |dn, attrs| attrs["gidNumber"] == ["141"] } + + instance.create + instance.flush + end + end + end + +end diff --git a/spec/unit/ral/provider/interface/redhat.rb b/spec/unit/provider/interface/redhat.rb index 9bf1b9722..99ac50f01 100755 --- a/spec/unit/ral/provider/interface/redhat.rb +++ b/spec/unit/provider/interface/redhat.rb @@ -3,18 +3,18 @@ # Created by Luke Kanies on 2007-11-20. # Copyright (c) 2006. All rights reserved. -require File.dirname(__FILE__) + '/../../../../spec_helper' +require File.dirname(__FILE__) + '/../../../spec_helper' provider_class = Puppet::Type.type(:interface).provider(:redhat) describe provider_class do it "should not be functional on systems without a network-scripts directory" do - FileTest.expects(:exists?).with("/etc/sysconfig/network-scripts").returns(false) + FileTest.expects(:exist?).with("/etc/sysconfig/network-scripts").returns(false) provider_class.should_not be_suitable end it "should be functional on systems with a network-scripts directory" do - FileTest.expects(:exists?).with("/etc/sysconfig/network-scripts").returns(true) + FileTest.expects(:exist?).with("/etc/sysconfig/network-scripts").returns(true) provider_class.should be_suitable end end diff --git a/spec/unit/ral/provider/interface/sunos.rb b/spec/unit/provider/interface/sunos.rb index 7b9f462e6..6a7bd19c1 100755 --- a/spec/unit/ral/provider/interface/sunos.rb +++ b/spec/unit/provider/interface/sunos.rb @@ -3,7 +3,7 @@ # Created by Luke Kanies on 2007-11-25. # Copyright (c) 2006. All rights reserved. -require File.dirname(__FILE__) + '/../../../../spec_helper' +require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/provider/interface/sunos' diff --git a/spec/unit/provider/ldap.rb b/spec/unit/provider/ldap.rb new file mode 100755 index 000000000..fd5d1bdc3 --- /dev/null +++ b/spec/unit/provider/ldap.rb @@ -0,0 +1,248 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-21. +# Copyright (c) 2006. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/provider/ldap' + +describe Puppet::Provider::Ldap do + before do + @class = Class.new(Puppet::Provider::Ldap) + end + + it "should be able to define its manager" do + manager = mock 'manager' + Puppet::Util::Ldap::Manager.expects(:new).returns manager + @class.stubs :mk_resource_methods + manager.expects(:manages).with(:one) + @class.manages(:one).should equal(manager) + @class.manager.should equal(manager) + end + + it "should be able to prefetch instances from ldap" do + @class.should respond_to(:prefetch) + end + + it "should create its resource getter/setter methods when the manager is defined" do + manager = mock 'manager' + Puppet::Util::Ldap::Manager.expects(:new).returns manager + @class.expects :mk_resource_methods + manager.stubs(:manages) + @class.manages(:one).should equal(manager) + end + + it "should have an instances method" do + @class.should respond_to(:instances) + end + + describe "when providing a list of instances" do + it "should convert all results returned from the manager's :search method into provider instances" do + manager = mock 'manager' + @class.stubs(:manager).returns manager + + manager.expects(:search).returns %w{one two three} + + @class.expects(:new).with("one").returns(1) + @class.expects(:new).with("two").returns(2) + @class.expects(:new).with("three").returns(3) + + @class.instances.should == [1,2,3] + end + end + + it "should have a prefetch method" do + @class.should respond_to(:prefetch) + end + + describe "when prefetching" do + before do + @manager = mock 'manager' + @class.stubs(:manager).returns @manager + + @resource = mock 'resource' + + @resources = {"one" => @resource} + end + + it "should find an entry for each passed resource" do + @manager.expects(:find).with("one").returns nil + + @class.stubs(:new) + @resource.stubs(:provider=) + @class.prefetch(@resources) + end + + describe "resources that do not exist" do + it "should create a provider with :ensure => :absent" do + result = mock 'result' + @manager.expects(:find).with("one").returns nil + + @class.expects(:new).with(:ensure => :absent).returns "myprovider" + + @resource.expects(:provider=).with("myprovider") + + @class.prefetch(@resources) + end + end + + describe "resources that exist" do + it "should create a provider with the results of the find" do + @manager.expects(:find).with("one").returns("one" => "two") + + @class.expects(:new).with("one" => "two", :ensure => :present).returns "myprovider" + + @resource.expects(:provider=).with("myprovider") + + @class.prefetch(@resources) + end + + it "should set :ensure to :present in the returned values" do + @manager.expects(:find).with("one").returns("one" => "two") + + @class.expects(:new).with("one" => "two", :ensure => :present).returns "myprovider" + + @resource.expects(:provider=).with("myprovider") + + @class.prefetch(@resources) + end + end + end + + describe "when being initialized" do + it "should fail if no manager has been defined" do + lambda { @class.new }.should raise_error(Puppet::DevError) + end + + it "should fail if the manager is invalid" do + manager = stub "manager", :valid? => false + @class.stubs(:manager).returns manager + lambda { @class.new }.should raise_error(Puppet::DevError) + end + + describe "with a hash" do + before do + @manager = stub "manager", :valid? => true + @class.stubs(:manager).returns @manager + + @resource_class = mock 'resource_class' + @class.stubs(:resource_type).returns @resource_class + + @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property + @resource_class.stubs(:attrclass).with(:one).returns(@property_class) + @resource_class.stubs(:validattr?).returns true + end + + it "should store a copy of the hash as its ldap_properties" do + instance = @class.new(:one => :two) + instance.ldap_properties.should == {:one => :two} + end + + it "should only store the first value of each value array for those attributes that do not match all values" do + @property_class.expects(:array_matching).returns :first + instance = @class.new(:one => %w{two three}) + instance.properties.should == {:one => "two"} + end + + it "should store the whole value array for those attributes that match all values" do + @property_class.expects(:array_matching).returns :all + instance = @class.new(:one => %w{two three}) + instance.properties.should == {:one => %w{two three}} + end + + it "should only use the first value for attributes that are not properties" do + # Yay. hackish, but easier than mocking everything. + @resource_class.expects(:attrclass).with(:a).returns Puppet::Type.type(:user).attrclass(:name) + @property_class.stubs(:array_matching).returns :all + + instance = @class.new(:one => %w{two three}, :a => %w{b c}) + instance.properties.should == {:one => %w{two three}, :a => "b"} + end + + it "should discard any properties not valid in the resource class" do + @resource_class.expects(:validattr?).with(:a).returns false + @property_class.stubs(:array_matching).returns :all + + instance = @class.new(:one => %w{two three}, :a => %w{b}) + instance.properties.should == {:one => %w{two three}} + end + end + end + + describe "when an instance" do + before do + @manager = stub "manager", :valid? => true + @class.stubs(:manager).returns @manager + @instance = @class.new + + @property_class = stub 'property_class', :array_matching => :all, :superclass => Puppet::Property + @resource_class = stub 'resource_class', :attrclass => @property_class, :validattr? => true, :validproperties => [:one, :two] + @class.stubs(:resource_type).returns @resource_class + end + + it "should have a method for creating the ldap entry" do + @instance.should respond_to(:create) + end + + it "should have a method for removing the ldap entry" do + @instance.should respond_to(:delete) + end + + it "should have a method for returning the class's manager" do + @instance.manager.should equal(@manager) + end + + it "should indicate when the ldap entry already exists" do + @instance = @class.new(:ensure => :present) + @instance.exists?.should be_true + end + + it "should indicate when the ldap entry does not exist" do + @instance = @class.new(:ensure => :absent) + @instance.exists?.should be_false + end + + describe "is being flushed" do + it "should call the manager's :update method with its name, current attributes, and desired attributes" do + @instance.stubs(:name).returns "myname" + @instance.stubs(:ldap_properties).returns(:one => :two) + @instance.stubs(:properties).returns(:three => :four) + @manager.expects(:update).with(@instance.name, {:one => :two}, {:three => :four}) + @instance.flush + end + end + + describe "is being created" do + before do + @rclass = mock 'resource_class' + @rclass.stubs(:validproperties).returns([:one, :two]) + @resource = mock 'resource' + @resource.stubs(:class).returns @rclass + @resource.stubs(:should).returns nil + @instance.stubs(:resource).returns @resource + end + + it "should set its :ensure value to :present" do + @instance.create + @instance.properties[:ensure].should == :present + end + + it "should set all of the other attributes from the resource" do + @resource.expects(:should).with(:one).returns "oneval" + @resource.expects(:should).with(:two).returns "twoval" + + @instance.create + @instance.properties[:one].should == "oneval" + @instance.properties[:two].should == "twoval" + end + end + + describe "is being deleted" do + it "should set its :ensure value to :absent" do + @instance.delete + @instance.properties[:ensure].should == :absent + end + end + end +end diff --git a/spec/unit/ral/provider/mount.rb b/spec/unit/provider/mount.rb index 0b90d53c9..41abcd424 100755 --- a/spec/unit/ral/provider/mount.rb +++ b/spec/unit/provider/mount.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/provider/mount' diff --git a/spec/unit/ral/provider/mount/parsed.rb b/spec/unit/provider/mount/parsed.rb index b7f08a464..8d043f97f 100755 --- a/spec/unit/ral/provider/mount/parsed.rb +++ b/spec/unit/provider/mount/parsed.rb @@ -3,7 +3,7 @@ # Created by Luke Kanies on 2007-9-12. # Copyright (c) 2006. All rights reserved. -require File.dirname(__FILE__) + '/../../../../spec_helper' +require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppettest/support/utils' require 'puppettest/fileparsing' diff --git a/spec/unit/provider/ssh_authorized_key/parsed.rb b/spec/unit/provider/ssh_authorized_key/parsed.rb new file mode 100755 index 000000000..c35ddc513 --- /dev/null +++ b/spec/unit/provider/ssh_authorized_key/parsed.rb @@ -0,0 +1,74 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppettest' +require 'puppettest/support/utils' +require 'puppettest/fileparsing' + +provider_class = Puppet::Type.type(:ssh_authorized_key).provider(:parsed) + +describe provider_class do + include PuppetTest + include PuppetTest::FileParsing + + before :each do + @sshauthkey_class = Puppet.type(:ssh_authorized_key) + @provider = @sshauthkey_class.provider(:parsed) + end + + after :each do + @provider.initvars + end + + def mkkey(args) + fakeresource = fakeresource(:ssh_authorized_key, args[:name]) + + key = @provider.new(fakeresource) + args.each do |p,v| + key.send(p.to_s + "=", v) + end + + return key + end + + def genkey(key) + @provider.filetype = :ram + file = @provider.default_target + + key.flush + text = @provider.target_object(file).read + return text + end + + it "should be able to parse each example" do + fakedata("data/providers/ssh_authorized_key/parsed").each { |file| + puts "Parsing %s" % file + fakedataparse(file) + } + end + + it "should be able to generate a basic authorized_keys file" do + key = mkkey({ + :name => "Just Testing", + :key => "AAAAfsfddsjldjgksdflgkjsfdlgkj", + :type => "ssh-dss", + :ensure => :present, + :options => [:absent] + }) + + genkey(key).should == "ssh-dss AAAAfsfddsjldjgksdflgkjsfdlgkj Just Testing\n" + end + + it "should be able to generate a authorized_keys file with options" do + key = mkkey({ + :name => "root@localhost", + :key => "AAAAfsfddsjldjgksdflgkjsfdlgkj", + :type => "ssh-rsa", + :ensure => :present, + :options => ['from="192.168.1.1"', "no-pty", "no-X11-forwarding"] + }) + + genkey(key).should == "from=\"192.168.1.1\",no-pty,no-X11-forwarding ssh-rsa AAAAfsfddsjldjgksdflgkjsfdlgkj root@localhost\n" + end +end diff --git a/spec/unit/provider/user/ldap.rb b/spec/unit/provider/user/ldap.rb new file mode 100755 index 000000000..c4731cbbb --- /dev/null +++ b/spec/unit/provider/user/ldap.rb @@ -0,0 +1,252 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-10. +# Copyright (c) 2006. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +provider_class = Puppet::Type.type(:user).provider(:ldap) + +describe provider_class do + it "should have the Ldap provider class as its baseclass" do + provider_class.superclass.should equal(Puppet::Provider::Ldap) + end + + it "should manage :posixAccount and :person objectclasses" do + provider_class.manager.objectclasses.should == [:posixAccount, :person] + end + + it "should use 'ou=People' as its relative base" do + provider_class.manager.location.should == "ou=People" + end + + it "should use :uid as its rdn" do + provider_class.manager.rdn.should == :uid + end + + {:name => "uid", + :password => "userPassword", + :comment => "cn", + :uid => "uidNumber", + :gid => "gidNumber", + :home => "homeDirectory", + :shell => "loginShell" + }.each do |puppet, ldap| + it "should map :#{puppet.to_s} to '#{ldap}'" do + provider_class.manager.ldap_name(puppet).should == ldap + end + end + + describe "when being created" do + before do + # So we don't try to actually talk to ldap + @connection = mock 'connection' + provider_class.manager.stubs(:connect).yields @connection + end + + it "should generate the sn as the last field of the cn" do + resource = stub 'resource', :should => %w{whatever} + resource.stubs(:should).with(:comment).returns ["Luke Kanies"] + resource.stubs(:should).with(:ensure).returns :present + instance = provider_class.new(:name => "luke", :ensure => :absent) + instance.stubs(:resource).returns resource + + @connection.expects(:add).with { |dn, attrs| attrs["sn"] == ["Kanies"] } + + instance.create + instance.flush + end + + describe "with no uid specified" do + it "should pick the first available UID after the largest existing UID" do + low = {:name=>["luke"], :shell=>:absent, :uid=>["100"], :home=>["/h"], :gid=>["1000"], :password=>["blah"], :comment=>["l k"]} + high = {:name=>["testing"], :shell=>:absent, :uid=>["140"], :home=>["/h"], :gid=>["1000"], :password=>["blah"], :comment=>["t u"]} + provider_class.manager.expects(:search).returns([low, high]) + + resource = stub 'resource', :should => %w{whatever} + resource.stubs(:should).with(:uid).returns nil + resource.stubs(:should).with(:ensure).returns :present + instance = provider_class.new(:name => "luke", :ensure => :absent) + instance.stubs(:resource).returns resource + + @connection.expects(:add).with { |dn, attrs| attrs["uidNumber"] == ["141"] } + + instance.create + instance.flush + end + end + end + + describe "when flushing" do + before do + provider_class.stubs(:suitable?).returns true + + @instance = provider_class.new(:name => "myname", :groups => %w{whatever}, :uid => "400") + end + + it "should remove the :groups value before updating" do + @instance.class.manager.expects(:update).with { |name, ldap, puppet| puppet[:groups].nil? } + + @instance.flush + end + + it "should empty the property hash" do + @instance.class.manager.stubs(:update) + + @instance.flush + + @instance.uid.should == :absent + end + + it "should empty the ldap property hash" do + @instance.class.manager.stubs(:update) + + @instance.flush + + @instance.ldap_properties[:uid].should be_nil + end + end + + describe "when checking group membership" do + before do + @groups = Puppet::Type.type(:group).provider(:ldap) + @group_manager = @groups.manager + provider_class.stubs(:suitable?).returns true + + @instance = provider_class.new(:name => "myname") + end + + it "should show its group membership as the list of all groups returned by an ldap query of group memberships" do + one = {:name => "one"} + two = {:name => "two"} + @group_manager.expects(:search).with("memberUid=myname").returns([one, two]) + + @instance.groups.should == "one,two" + end + + it "should show its group membership as :absent if no matching groups are found in ldap" do + @group_manager.expects(:search).with("memberUid=myname").returns(nil) + + @instance.groups.should == :absent + end + + it "should cache the group value" do + @group_manager.expects(:search).with("memberUid=myname").once.returns nil + + @instance.groups + @instance.groups.should == :absent + end + end + + describe "when modifying group membership" do + before do + @groups = Puppet::Type.type(:group).provider(:ldap) + @group_manager = @groups.manager + provider_class.stubs(:suitable?).returns true + + @one = {:name => "one", :gid => "500"} + @group_manager.stubs(:find).with("one").returns(@one) + + @two = {:name => "one", :gid => "600"} + @group_manager.stubs(:find).with("two").returns(@two) + + @instance = provider_class.new(:name => "myname") + + @instance.stubs(:groups).returns :absent + end + + it "should fail if the group does not exist" do + @group_manager.expects(:find).with("mygroup").returns nil + + lambda { @instance.groups = "mygroup" }.should raise_error(Puppet::Error) + end + + it "should only pass the attributes it cares about to the group manager" do + @group_manager.expects(:update).with { |name, attrs| attrs[:gid].nil? } + + @instance.groups = "one" + end + + it "should always include :ensure => :present in the current values" do + @group_manager.expects(:update).with { |name, is, should| is[:ensure] == :present } + + @instance.groups = "one" + end + + it "should always include :ensure => :present in the desired values" do + @group_manager.expects(:update).with { |name, is, should| should[:ensure] == :present } + + @instance.groups = "one" + end + + it "should always pass the group's original member list" do + @one[:members] = %w{yay ness} + @group_manager.expects(:update).with { |name, is, should| is[:members] == %w{yay ness} } + + @instance.groups = "one" + end + + it "should find the group again when resetting its member list, so it has the full member list" do + @group_manager.expects(:find).with("one").returns(@one) + + @group_manager.stubs(:update) + + @instance.groups = "one" + end + + describe "for groups that have no members" do + it "should create a new members attribute with its value being the user's name" do + @group_manager.expects(:update).with { |name, is, should| should[:members] == %w{myname} } + + @instance.groups = "one" + end + end + + describe "for groups it is being removed from" do + it "should replace the group's member list with one missing the user's name" do + @one[:members] = %w{myname a} + @two[:members] = %w{myname b} + + @group_manager.expects(:update).with { |name, is, should| name == "two" and should[:members] == %w{b} } + + @instance.stubs(:groups).returns "one,two" + @instance.groups = "one" + end + + it "should mark the member list as empty if there are no remaining members" do + @one[:members] = %w{myname} + @two[:members] = %w{myname b} + + @group_manager.expects(:update).with { |name, is, should| name == "one" and should[:members] == :absent } + + @instance.stubs(:groups).returns "one,two" + @instance.groups = "two" + end + end + + describe "for groups that already have members" do + it "should replace each group's member list with a new list including the user's name" do + @one[:members] = %w{a b} + @group_manager.expects(:update).with { |name, is, should| should[:members] == %w{a b myname} } + @two[:members] = %w{b c} + @group_manager.expects(:update).with { |name, is, should| should[:members] == %w{b c myname} } + + @instance.groups = "one,two" + end + end + + describe "for groups of which it is a member" do + it "should do nothing" do + @one[:members] = %w{a b} + @group_manager.expects(:update).with { |name, is, should| should[:members] == %w{a b myname} } + + @two[:members] = %w{c myname} + @group_manager.expects(:update).with { |name, *other| name == "two" }.never + + @instance.stubs(:groups).returns "two" + + @instance.groups = "one,two" + end + end + end +end diff --git a/spec/unit/rails.rb b/spec/unit/rails.rb index ae7ea9f95..4a4667543 100755 --- a/spec/unit/rails.rb +++ b/spec/unit/rails.rb @@ -9,8 +9,9 @@ describe Puppet::Rails, "when initializing any connection" do before do @logger = stub 'logger', :level= => nil @logger.stub_everything + Logger.stubs(:new).returns(@logger) - Logger.stubs(:new).returns @logger + ActiveRecord::Base.stubs(:logger).returns(@logger) end it "should use settings" do diff --git a/spec/unit/ral/type.rb b/spec/unit/type.rb index 5980167d6..9815ed32d 100755 --- a/spec/unit/ral/type.rb +++ b/spec/unit/type.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../spec_helper' +require File.dirname(__FILE__) + '/../spec_helper' describe Puppet::Type, " when in a configuration" do before do diff --git a/spec/unit/ral/type/exec.rb b/spec/unit/type/exec.rb index 260804227..cf0e02929 100755 --- a/spec/unit/ral/type/exec.rb +++ b/spec/unit/type/exec.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/type/exec' diff --git a/spec/unit/ral/type/file.rb b/spec/unit/type/file.rb index 83546bef0..d6add8609 100755 --- a/spec/unit/ral/type/file.rb +++ b/spec/unit/type/file.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Type.type(:file) do before do diff --git a/spec/unit/ral/type/interface.rb b/spec/unit/type/interface.rb index e51465a0c..4e27e35ea 100755 --- a/spec/unit/ral/type/interface.rb +++ b/spec/unit/type/interface.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' interface = Puppet::Type.type(:interface) diff --git a/spec/unit/ral/type/mount.rb b/spec/unit/type/mount.rb index b1a01749d..9d09225cc 100755 --- a/spec/unit/ral/type/mount.rb +++ b/spec/unit/type/mount.rb @@ -1,69 +1,67 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' -require 'puppet/type/mount' - -describe Puppet::Type::Mount do +describe Puppet::Type.type(:mount) do it "should have a :refreshable feature that requires the :remount method" do - Puppet::Type::Mount.provider_feature(:refreshable).methods.should == [:remount] + Puppet::Type.type(:mount).provider_feature(:refreshable).methods.should == [:remount] end it "should have no default value for :ensure" do - mount = Puppet::Type::Mount.create(:name => "yay") + mount = Puppet::Type.type(:mount).create(:name => "yay") mount.should(:ensure).should be_nil end end -describe Puppet::Type::Mount, "when validating attributes" do +describe Puppet::Type.type(:mount), "when validating attributes" do [:name, :remounts].each do |param| it "should have a #{param} parameter" do - Puppet::Type::Mount.attrtype(param).should == :param + Puppet::Type.type(:mount).attrtype(param).should == :param end end [:ensure, :device, :blockdevice, :fstype, :options, :pass, :dump, :atboot, :target].each do |param| it "should have a #{param} property" do - Puppet::Type::Mount.attrtype(param).should == :property + Puppet::Type.type(:mount).attrtype(param).should == :property end end end -describe Puppet::Type::Mount::Ensure, "when validating values" do +describe Puppet::Type.type(:mount)::Ensure, "when validating values" do before do - @provider = stub 'provider', :class => Puppet::Type::Mount.defaultprovider, :clear => nil - Puppet::Type::Mount.defaultprovider.expects(:new).returns(@provider) + @provider = stub 'provider', :class => Puppet::Type.type(:mount).defaultprovider, :clear => nil + Puppet::Type.type(:mount).defaultprovider.expects(:new).returns(@provider) end it "should support :present as a value to :ensure" do - Puppet::Type::Mount.create(:name => "yay", :ensure => :present) + Puppet::Type.type(:mount).create(:name => "yay", :ensure => :present) end it "should alias :unmounted to :present as a value to :ensure" do - mount = Puppet::Type::Mount.create(:name => "yay", :ensure => :unmounted) + mount = Puppet::Type.type(:mount).create(:name => "yay", :ensure => :unmounted) mount.should(:ensure).should == :present end it "should support :absent as a value to :ensure" do - Puppet::Type::Mount.create(:name => "yay", :ensure => :absent) + Puppet::Type.type(:mount).create(:name => "yay", :ensure => :absent) end it "should support :mounted as a value to :ensure" do - Puppet::Type::Mount.create(:name => "yay", :ensure => :mounted) + Puppet::Type.type(:mount).create(:name => "yay", :ensure => :mounted) end end -describe Puppet::Type::Mount::Ensure do +describe Puppet::Type.type(:mount)::Ensure do before :each do - @provider = stub 'provider', :class => Puppet::Type::Mount.defaultprovider, :clear => nil, :satisfies? => true, :name => :mock - Puppet::Type::Mount.defaultprovider.stubs(:new).returns(@provider) - @mount = Puppet::Type::Mount.create(:name => "yay", :check => :ensure) + @provider = stub 'provider', :class => Puppet::Type.type(:mount).defaultprovider, :clear => nil, :satisfies? => true, :name => :mock + Puppet::Type.type(:mount).defaultprovider.stubs(:new).returns(@provider) + @mount = Puppet::Type.type(:mount).create(:name => "yay", :check => :ensure) @ensure = @mount.property(:ensure) end def mount_stub(params) - Puppet::Type::Mount.validproperties.each do |prop| + Puppet::Type.type(:mount).validproperties.each do |prop| unless params[prop] params[prop] = :absent @mount[prop] = :absent @@ -75,7 +73,7 @@ describe Puppet::Type::Mount::Ensure do end end - describe Puppet::Type::Mount::Ensure, "when retrieving its current state" do + describe Puppet::Type.type(:mount)::Ensure, "when retrieving its current state" do it "should return the provider's value if it is :absent" do @provider.expects(:ensure).returns(:absent) @@ -95,7 +93,7 @@ describe Puppet::Type::Mount::Ensure do end end - describe Puppet::Type::Mount::Ensure, "when changing the host" do + describe Puppet::Type.type(:mount)::Ensure, "when changing the host" do it "should destroy itself if it should be absent" do @provider.stubs(:mounted?).returns(false) @@ -158,7 +156,7 @@ describe Puppet::Type::Mount::Ensure do end end - describe Puppet::Type::Mount, "when responding to events" do + describe Puppet::Type.type(:mount), "when responding to events" do it "should remount if it is currently mounted" do @provider.expects(:mounted?).returns(true) diff --git a/spec/unit/ral/type/nagios.rb b/spec/unit/type/nagios.rb index 35f00b0e5..563c82c2f 100755 --- a/spec/unit/ral/type/nagios.rb +++ b/spec/unit/type/nagios.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/external/nagios' diff --git a/spec/unit/ral/type/noop_metaparam.rb b/spec/unit/type/noop_metaparam.rb index 236599ed5..540603ef9 100755 --- a/spec/unit/ral/type/noop_metaparam.rb +++ b/spec/unit/type/noop_metaparam.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/metatype/metaparams' diff --git a/spec/unit/ral/type/package.rb b/spec/unit/type/package.rb index 8cc11cc2c..d2fc85ed1 100755 --- a/spec/unit/ral/type/package.rb +++ b/spec/unit/type/package.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/type/package' diff --git a/spec/unit/ral/type/schedule.rb b/spec/unit/type/schedule.rb index e53138c5e..b533d17e4 100755 --- a/spec/unit/ral/type/schedule.rb +++ b/spec/unit/type/schedule.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/type/schedule' diff --git a/spec/unit/ral/type/service.rb b/spec/unit/type/service.rb index 17cb2105d..1a57bdd41 100755 --- a/spec/unit/ral/type/service.rb +++ b/spec/unit/type/service.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' +require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/type/service' diff --git a/spec/unit/type/ssh_authorized_key.rb b/spec/unit/type/ssh_authorized_key.rb new file mode 100755 index 000000000..0e869747d --- /dev/null +++ b/spec/unit/type/ssh_authorized_key.rb @@ -0,0 +1,80 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +ssh_authorized_key = Puppet::Type.type(:ssh_authorized_key) + +describe ssh_authorized_key do + before do + @class = Puppet::Type.type(:ssh_authorized_key) + + @provider_class = stub 'provider_class', :name => "fake", :suitable? => true, :supports_parameter? => true + @class.stubs(:defaultprovider).returns(@provider_class) + @class.stubs(:provider).returns(@provider_class) + + @provider = stub 'provider', :class => @provider_class, :file_path => "/tmp/whatever", :clear => nil + @provider_class.stubs(:new).returns(@provider) + end + + it "should have a name parameter" do + @class.attrtype(:name).should == :param + end + + it "should have :name be its namevar" do + @class.namevar.should == :name + end + + it "should have a :provider parameter" do + @class.attrtype(:provider).should == :param + end + + it "should have an ensure property" do + @class.attrtype(:ensure).should == :property + end + + it "should support :present as a value for :ensure" do + proc { @class.create(:name => "whev", :ensure => :present) }.should_not raise_error + end + + it "should support :absent as a value for :ensure" do + proc { @class.create(:name => "whev", :ensure => :absent) }.should_not raise_error + end + + it "should have an type property" do + @class.attrtype(:type).should == :property + end + it "should support ssh-dss as an type value" do + proc { @class.create(:name => "whev", :type => "ssh-dss") }.should_not raise_error + end + it "should support ssh-rsa as an type value" do + proc { @class.create(:name => "whev", :type => "ssh-rsa") }.should_not raise_error + end + it "should support :dsa as an type value" do + proc { @class.create(:name => "whev", :type => :dsa) }.should_not raise_error + end + it "should support :rsa as an type value" do + proc { @class.create(:name => "whev", :type => :rsa) }.should_not raise_error + end + + it "should not support values other than ssh-dss, ssh-rsa, dsa, rsa in the ssh_authorized_key_type" do + proc { @class.create(:name => "whev", :type => :something) }.should raise_error(Puppet::Error) + end + + it "should have an key property" do + @class.attrtype(:key).should == :property + end + + it "should have an user property" do + @class.attrtype(:user).should == :property + end + + it "should have an options property" do + @class.attrtype(:options).should == :property + end + + it "should have a target property" do + @class.attrtype(:target).should == :property + end + + after { @class.clear } +end diff --git a/spec/unit/ral/type/user.rb b/spec/unit/type/user.rb index 4e43a8ceb..d16d752f9 100755 --- a/spec/unit/ral/type/user.rb +++ b/spec/unit/type/user.rb @@ -1,14 +1,12 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../../../spec_helper' - -require 'puppet/type/user' +require File.dirname(__FILE__) + '/../../spec_helper' module UserTestFunctions def mkuser(name) user = nil; lambda { - user = Puppet::Type::User.create( + user = Puppet::Type.type(:user).create( :name => name, :comment => "Puppet Testing User", :gid => Puppet::Util::SUIDManager.gid, @@ -30,12 +28,12 @@ module UserTestFunctions end end -describe Puppet::Type::User do +describe Puppet::Type.type(:user) do include UserTestFunctions it "should have a default provider inheriting from Puppet::Provider" do - test_provider_class Puppet::Type::User.defaultprovider + test_provider_class Puppet::Type.type(:user).defaultprovider end it "should be able to create a instance" do @@ -43,7 +41,7 @@ describe Puppet::Type::User do end end -describe Puppet::Type::User, "instances" do +describe Puppet::Type.type(:user), "instances" do include UserTestFunctions diff --git a/spec/unit/util/ldap/connection.rb b/spec/unit/util/ldap/connection.rb new file mode 100755 index 000000000..212f3ca54 --- /dev/null +++ b/spec/unit/util/ldap/connection.rb @@ -0,0 +1,114 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-19. +# Copyright (c) 2006. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/util/ldap/connection' + +# So our mocks and such all work, even when ldap isn't available. +unless defined?(LDAP::Conn) + class LDAP + class Conn + def initialize(*args) + end + end + class SSLConn < Conn; end + + LDAP_OPT_PROTOCOL_VERSION = 1 + LDAP_OPT_REFERRALS = 2 + LDAP_OPT_ON = 3 + end +end + +describe Puppet::Util::Ldap::Connection do + before do + Puppet.features.stubs(:ldap?).returns true + + @ldapconn = mock 'ldap' + LDAP::Conn.stubs(:new).returns(@ldapconn) + LDAP::SSLConn.stubs(:new).returns(@ldapconn) + + @ldapconn.stub_everything + + @connection = Puppet::Util::Ldap::Connection.new("host", "port") + end + + + describe "when creating connections" do + it "should require the host and port" do + lambda { Puppet::Util::Ldap::Connection.new("myhost") }.should raise_error(ArgumentError) + end + + it "should allow specification of a user and password" do + lambda { Puppet::Util::Ldap::Connection.new("myhost", "myport", :user => "blah", :password => "boo") }.should_not raise_error + end + + it "should allow specification of ssl" do + lambda { Puppet::Util::Ldap::Connection.new("myhost", "myport", :ssl => :tsl) }.should_not raise_error + end + + it "should support requiring a new connection" do + lambda { Puppet::Util::Ldap::Connection.new("myhost", "myport", :reset => true) }.should_not raise_error + end + + it "should fail if ldap is unavailable" do + Puppet.features.expects(:ldap?).returns(false) + + lambda { Puppet::Util::Ldap::Connection.new("host", "port") }.should raise_error(Puppet::Error) + end + + it "should use neither ssl nor tls by default" do + LDAP::Conn.expects(:new).with("host", "port").returns(@ldapconn) + + @connection.start + end + + it "should use LDAP::SSLConn if ssl is requested" do + LDAP::SSLConn.expects(:new).with("host", "port").returns(@ldapconn) + + @connection.ssl = true + + @connection.start + end + + it "should use LDAP::SSLConn and tls if tls is requested" do + LDAP::SSLConn.expects(:new).with("host", "port", true).returns(@ldapconn) + + @connection.ssl = :tls + + @connection.start + end + + it "should set the protocol version to 3 and enable referrals" do + @ldapconn.expects(:set_option).with(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @ldapconn.expects(:set_option).with(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @connection.start + end + + it "should bind with the provided user and password" do + @connection.user = "myuser" + @connection.password = "mypassword" + @ldapconn.expects(:simple_bind).with("myuser", "mypassword") + + @connection.start + end + + it "should bind with no user and password if none has been provided" do + @ldapconn.expects(:simple_bind).with(nil, nil) + @connection.start + end + end + + describe "when closing connections" do + it "should not close connections that are not open" do + @connection.stubs(:connection).returns(@ldapconn) + + @ldapconn.expects(:bound?).returns false + @ldapconn.expects(:unbind).never + + @connection.close + end + end +end diff --git a/spec/unit/util/ldap/generator.rb b/spec/unit/util/ldap/generator.rb new file mode 100755 index 000000000..a6c69de83 --- /dev/null +++ b/spec/unit/util/ldap/generator.rb @@ -0,0 +1,54 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-28. +# Copyright (c) 2008. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/util/ldap/generator' + +describe Puppet::Util::Ldap::Generator do + before do + @generator = Puppet::Util::Ldap::Generator.new(:uno) + end + + it "should require a parameter name at initialization" do + lambda { Puppet::Util::Ldap::Generator.new }.should raise_error + end + + it "should always return its name as a string" do + g = Puppet::Util::Ldap::Generator.new(:myname) + g.name.should == "myname" + end + + it "should provide a method for declaring the source parameter" do + @generator.from(:dos) + end + + it "should always return a set source as a string" do + @generator.from(:dos) + @generator.source.should == "dos" + end + + it "should return the source as nil if there is no source" do + @generator.source.should be_nil + end + + it "should return itself when declaring the source" do + @generator.from(:dos).should equal(@generator) + end + + it "should run the provided block when asked to generate the value" do + @generator.with { "yayness" } + @generator.generate().should == "yayness" + end + + it "should pass in any provided value to the block" do + @generator.with { |value| value.upcase } + @generator.generate("myval").should == "MYVAL" + end + + it "should return itself when declaring the code used for generating" do + @generator.with { |value| value.upcase }.should equal(@generator) + end +end diff --git a/spec/unit/util/ldap/manager.rb b/spec/unit/util/ldap/manager.rb new file mode 100755 index 000000000..b18b1b933 --- /dev/null +++ b/spec/unit/util/ldap/manager.rb @@ -0,0 +1,654 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2008-3-19. +# Copyright (c) 2006. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/util/ldap/manager' + +# If the ldap classes aren't available, go ahead and +# create some, so our tests will pass. +unless defined?(LDAP::Mod) + class LDAP + LDAP_MOD_ADD = :adding + LDAP_MOD_REPLACE = :replacing + LDAP_MOD_DELETE = :deleting + class ResultError < RuntimeError; end + class Mod + def initialize(*args) + end + end + end +end + +describe Puppet::Util::Ldap::Manager do + before do + @manager = Puppet::Util::Ldap::Manager.new + end + + it "should return self when specifying objectclasses" do + @manager.manages(:one, :two).should equal(@manager) + end + + it "should allow specification of what objectclasses are managed" do + @manager.manages(:one, :two).objectclasses.should == [:one, :two] + end + + it "should return self when specifying the relative base" do + @manager.at("yay").should equal(@manager) + end + + it "should allow specification of the relative base" do + @manager.at("yay").location.should == "yay" + end + + it "should return self when specifying the attribute map" do + @manager.maps(:one => :two).should equal(@manager) + end + + it "should allow specification of the rdn attribute" do + @manager.named_by(:uid).rdn.should == :uid + end + + it "should allow specification of the attribute map" do + @manager.maps(:one => :two).puppet2ldap.should == {:one => :two} + end + + it "should have a no-op 'and' method that just returns self" do + @manager.and.should equal(@manager) + end + + it "should allow specification of generated attributes" do + @manager.generates(:thing).should be_instance_of(Puppet::Util::Ldap::Generator) + end + + describe "when generating attributes" do + before do + @generator = stub 'generator', :source => "one", :name => "myparam" + + Puppet::Util::Ldap::Generator.stubs(:new).with(:myparam).returns @generator + end + + it "should create a generator to do the parameter generation" do + Puppet::Util::Ldap::Generator.expects(:new).with(:myparam).returns @generator + @manager.generates(:myparam) + end + + it "should return the generator from the :generates method" do + @manager.generates(:myparam).should equal(@generator) + end + + it "should not replace already present values" do + @manager.generates(:myparam) + + attrs = {"myparam" => "testing"} + @generator.expects(:generate).never + + @manager.generate attrs + + attrs["myparam"].should == "testing" + end + + it "should look for the parameter as a string, not a symbol" do + @manager.generates(:myparam) + @generator.expects(:generate).with("yay").returns %w{double yay} + attrs = {"one" => "yay"} + @manager.generate attrs + + attrs["myparam"].should == %w{double yay} + end + + it "should fail if a source is specified and no source value is not defined" do + @manager.generates(:myparam) + lambda { @manager.generate "two" => "yay" }.should raise_error(ArgumentError) + end + + it "should use the source value to generate the new value if a source attribute is specified" do + @manager.generates(:myparam) + @generator.expects(:generate).with("yay").returns %w{double yay} + @manager.generate "one" => "yay" + end + + it "should not pass in any value if no source attribute is specified" do + @generator.stubs(:source).returns nil + @manager.generates(:myparam) + @generator.expects(:generate).with().returns %w{double yay} + @manager.generate "one" => "yay" + end + + it "should convert any results to arrays of strings if necessary" do + @generator.expects(:generate).returns :test + @manager.generates(:myparam) + + attrs = {"one" => "two"} + @manager.generate(attrs) + attrs["myparam"].should == ["test"] + end + + it "should add the result to the passed-in attribute hash" do + @generator.expects(:generate).returns %w{test} + @manager.generates(:myparam) + + attrs = {"one" => "two"} + @manager.generate(attrs) + attrs["myparam"].should == %w{test} + end + end + + it "should be considered invalid if it is missing a location" do + @manager.manages :me + @manager.maps :me => :you + @manager.should_not be_valid + end + + it "should be considered invalid if it is missing an objectclass list" do + @manager.maps :me => :you + @manager.at "ou=yayness" + @manager.should_not be_valid + end + + it "should be considered invalid if it is missing an attribute map" do + @manager.manages :me + @manager.at "ou=yayness" + @manager.should_not be_valid + end + + it "should be considered valid if it has an attribute map, location, and objectclass list" do + @manager.maps :me => :you + @manager.manages :me + @manager.at "ou=yayness" + @manager.should be_valid + end + + it "should calculate an instance's dn using the :ldapbase setting and the relative base" do + Puppet.settings.expects(:value).with(:ldapbase).returns "dc=testing" + @manager.at "ou=mybase" + @manager.dn("me").should == "cn=me,ou=mybase,dc=testing" + end + + it "should use the specified rdn when calculating an instance's dn" do + Puppet.settings.expects(:value).with(:ldapbase).returns "dc=testing" + @manager.named_by :uid + @manager.at "ou=mybase" + @manager.dn("me").should =~ /^uid=me/ + end + + it "should calculate its base using the :ldapbase setting and the relative base" do + Puppet.settings.expects(:value).with(:ldapbase).returns "dc=testing" + @manager.at "ou=mybase" + @manager.base.should == "ou=mybase,dc=testing" + end + + describe "when generating its search filter" do + it "should using a single 'objectclass=<name>' filter if a single objectclass is specified" do + @manager.manages("testing") + @manager.filter.should == "objectclass=testing" + end + + it "should create an LDAP AND filter if multiple objectclasses are specified" do + @manager.manages "testing", "okay", "done" + @manager.filter.should == "(&(objectclass=testing)(objectclass=okay)(objectclass=done))" + end + end + + it "should have a method for converting a Puppet attribute name to an LDAP attribute name as a string" do + @manager.maps :puppet_attr => :ldap_attr + @manager.ldap_name(:puppet_attr).should == "ldap_attr" + end + + it "should have a method for converting an LDAP attribute name to a Puppet attribute name" do + @manager.maps :puppet_attr => :ldap_attr + @manager.puppet_name(:ldap_attr).should == :puppet_attr + end + + it "should have a :create method for creating ldap entries" do + @manager.should respond_to(:create) + end + + it "should have a :delete method for deleting ldap entries" do + @manager.should respond_to(:delete) + end + + it "should have a :modify method for modifying ldap entries" do + @manager.should respond_to(:modify) + end + + it "should have a method for finding an entry by name in ldap" do + @manager.should respond_to(:find) + end + + describe "when converting ldap entries to hashes for providers" do + before do + @manager.maps :uno => :one, :dos => :two + + @result = @manager.entry2provider("dn" => ["cn=one,ou=people,dc=madstop"], "one" => ["two"], "three" => %w{four}, "objectclass" => %w{yay ness}) + end + + it "should set the name to the short portion of the dn" do + @result[:name].should == "one" + end + + it "should remove the objectclasses" do + @result["objectclass"].should be_nil + end + + it "should remove any attributes that are not mentioned in the map" do + @result["three"].should be_nil + end + + it "should rename convert to symbols all attributes to their puppet names" do + @result[:uno].should == %w{two} + end + + it "should set the value of all unset puppet attributes as :absent" do + @result[:dos].should == :absent + end + end + + describe "when using an ldap connection" do + before do + @ldapconn = mock 'ldapconn' + @conn = stub 'connection', :connection => @ldapconn, :start => nil, :close => nil + Puppet::Util::Ldap::Connection.stubs(:new).returns(@conn) + end + + it "should fail unless a block is given" do + lambda { @manager.connect }.should raise_error(ArgumentError) + end + + it "should open the connection with its server set to :ldapserver" do + Puppet.settings.stubs(:value).returns(false) + Puppet.settings.expects(:value).with(:ldapserver).returns("myserver") + Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[0] == "myserver" }.returns @conn + + @manager.connect { |c| } + end + + it "should open the connection with its port set to the :ldapport" do + Puppet.settings.stubs(:value).returns(false) + Puppet.settings.expects(:value).with(:ldapport).returns("28") + Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[1] == "28" }.returns @conn + + @manager.connect { |c| } + end + + it "should open the connection with no user if :ldapuser is not set" do + Puppet.settings.stubs(:value).returns(false) + Puppet.settings.expects(:value).with(:ldapuser).returns("") + Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:user].nil? }.returns @conn + + @manager.connect { |c| } + end + + it "should open the connection with its user set to the :ldapuser if it is set" do + Puppet.settings.stubs(:value).returns(false) + Puppet.settings.expects(:value).with(:ldapuser).returns("mypass") + Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:user] == "mypass" }.returns @conn + + @manager.connect { |c| } + end + + it "should open the connection with no password if :ldappassword is not set" do + Puppet.settings.stubs(:value).returns(false) + Puppet.settings.expects(:value).with(:ldappassword).returns("") + Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:password].nil? }.returns @conn + + @manager.connect { |c| } + end + + it "should open the connection with its password set to the :ldappassword if it is set" do + Puppet.settings.stubs(:value).returns(false) + Puppet.settings.expects(:value).with(:ldappassword).returns("mypass") + Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:password] == "mypass" }.returns @conn + + @manager.connect { |c| } + end + + it "should set ssl to :tls if ldaptls is enabled" do + Puppet.settings.stubs(:value).returns(false) + Puppet.settings.expects(:value).with(:ldaptls).returns(true) + Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:ssl] == :tls }.returns @conn + + @manager.connect { |c| } + end + + it "should set ssl to true if ldapssl is enabled" do + Puppet.settings.stubs(:value).returns(false) + Puppet.settings.expects(:value).with(:ldapssl).returns(true) + Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:ssl] == true }.returns @conn + + @manager.connect { |c| } + end + + it "should set ssl to false if neither ldaptls nor ldapssl is enabled" do + Puppet.settings.stubs(:value).returns(false) + Puppet.settings.expects(:value).with(:ldapssl).returns(false) + Puppet::Util::Ldap::Connection.expects(:new).with { |*args| args[2][:ssl] == false }.returns @conn + + @manager.connect { |c| } + end + + it "should open, yield, and then close the connection" do + @conn.expects(:start) + @conn.expects(:close) + Puppet::Util::Ldap::Connection.expects(:new).returns(@conn) + @ldapconn.expects(:test) + @manager.connect { |c| c.test } + end + + it "should close the connection even if there's an exception in the passed block" do + @conn.expects(:close) + lambda { @manager.connect { |c| raise ArgumentError } }.should raise_error(ArgumentError) + end + end + + describe "when using ldap" do + before do + @conn = mock 'connection' + @manager.stubs(:connect).yields @conn + @manager.stubs(:objectclasses).returns [:oc1, :oc2] + @manager.maps :one => :uno, :two => :dos, :three => :tres, :four => :quatro + end + + describe "to create entries" do + it "should convert the first argument to its :create method to a full dn and pass the resulting argument list to its connection" do + @manager.expects(:dn).with("myname").returns "mydn" + @conn.expects(:add).with { |name, attrs| name == "mydn" } + + @manager.create("myname", {"attr" => "myattrs"}) + end + + it "should add the objectclasses to the attributes" do + @manager.expects(:dn).with("myname").returns "mydn" + @conn.expects(:add).with { |name, attrs| attrs["objectClass"].include?("oc1") and attrs["objectClass"].include?("oc2") } + + @manager.create("myname", {:one => :testing}) + end + + it "should add the rdn to the attributes" do + @manager.expects(:dn).with("myname").returns "mydn" + @conn.expects(:add).with { |name, attrs| attrs["cn"] == %w{myname} } + + @manager.create("myname", {:one => :testing}) + end + + it "should add 'top' to the objectclasses if it is not listed" do + @manager.expects(:dn).with("myname").returns "mydn" + @conn.expects(:add).with { |name, attrs| attrs["objectClass"].include?("top") } + + @manager.create("myname", {:one => :testing}) + end + + it "should add any generated values that are defined" do + generator = stub 'generator', :source => :one, :name => "myparam" + + Puppet::Util::Ldap::Generator.expects(:new).with(:myparam).returns generator + + @manager.generates(:myparam) + + @manager.stubs(:dn).with("myname").returns "mydn" + + generator.expects(:generate).with(:testing).returns ["generated value"] + @conn.expects(:add).with { |name, attrs| attrs["myparam"] == ["generated value"] } + + @manager.create("myname", {:one => :testing}) + end + + it "should convert any generated values to arrays of strings if necessary" do + generator = stub 'generator', :source => :one, :name => "myparam" + + Puppet::Util::Ldap::Generator.expects(:new).with(:myparam).returns generator + + @manager.generates(:myparam) + + @manager.stubs(:dn).returns "mydn" + + generator.expects(:generate).returns :generated + @conn.expects(:add).with { |name, attrs| attrs["myparam"] == ["generated"] } + + @manager.create("myname", {:one => :testing}) + end + end + + describe "do delete entries" do + it "should convert the first argument to its :delete method to a full dn and pass the resulting argument list to its connection" do + @manager.expects(:dn).with("myname").returns "mydn" + @conn.expects(:delete).with("mydn") + + @manager.delete("myname") + end + end + + describe "to modify entries" do + it "should convert the first argument to its :modify method to a full dn and pass the resulting argument list to its connection" do + @manager.expects(:dn).with("myname").returns "mydn" + @conn.expects(:modify).with("mydn", :mymods) + + @manager.modify("myname", :mymods) + end + end + + describe "to find a single entry" do + it "should use the dn of the provided name as the search base, a scope of 0, and 'objectclass=*' as the filter for a search2 call" do + @manager.expects(:dn).with("myname").returns "mydn" + @conn.expects(:search2).with("mydn", 0, "objectclass=*") + + @manager.find("myname") + end + + it "should return nil if an exception is thrown because no result is found" do + @manager.expects(:dn).with("myname").returns "mydn" + @conn.expects(:search2).raises LDAP::ResultError + + @manager.find("myname").should be_nil + end + + it "should return a converted provider hash if the result is found" do + @manager.expects(:dn).with("myname").returns "mydn" + result = {"one" => "two"} + @conn.expects(:search2).yields result + + @manager.expects(:entry2provider).with(result).returns "myprovider" + + @manager.find("myname").should == "myprovider" + end + end + + describe "to search for multiple entries" do + before do + @manager.stubs(:filter).returns "myfilter" + end + + it "should use the manager's search base as the dn of the provided name as the search base" do + @manager.expects(:base).returns "mybase" + @conn.expects(:search2).with { |base, scope, filter| base == "mybase" } + + @manager.search + end + + it "should use a scope of 1" do + @conn.expects(:search2).with { |base, scope, filter| scope == 1 } + + @manager.search + end + + it "should use any specified search filter" do + @manager.expects(:filter).never + @conn.expects(:search2).with { |base, scope, filter| filter == "boo" } + + @manager.search("boo") + end + + it "should turn its objectclass list into its search filter if one is not specified" do + @manager.expects(:filter).returns "yay" + @conn.expects(:search2).with { |base, scope, filter| filter == "yay" } + + @manager.search + end + + it "should return nil if no result is found" do + @conn.expects(:search2) + + @manager.search.should be_nil + end + + it "should return an array of the found results converted to provider hashes" do + # LAK: AFAICT, it's impossible to yield multiple times in an expectation. + one = {"dn" => "cn=one,dc=madstop,dc=com", "one" => "two"} + @conn.expects(:search2).yields(one) + + @manager.expects(:entry2provider).with(one).returns "myprov" + + @manager.search.should == ["myprov"] + end + end + end + + describe "when an instance" do + before do + @name = "myname" + @manager.maps :one => :uno, :two => :dos, :three => :tres, :four => :quatro + end + + describe "is being updated" do + it "should get created if the current attribute list is empty and the desired attribute list has :ensure == :present" do + @manager.expects(:create) + @manager.update(@name, {}, {:ensure => :present}) + end + + it "should get created if the current attribute list has :ensure == :absent and the desired attribute list has :ensure == :present" do + @manager.expects(:create) + @manager.update(@name, {:ensure => :absent}, {:ensure => :present}) + end + + it "should get deleted if the current attribute list has :ensure == :present and the desired attribute list has :ensure == :absent" do + @manager.expects(:delete) + @manager.update(@name, {:ensure => :present}, {:ensure => :absent}) + end + + it "should get modified if both attribute lists have :ensure == :present" do + @manager.expects(:modify) + @manager.update(@name, {:ensure => :present, :one => :two}, {:ensure => :present, :one => :three}) + end + end + + describe "is being deleted" do + it "should call the :delete method with its name and manager" do + @manager.expects(:delete).with(@name) + + @manager.update(@name, {}, {:ensure => :absent}) + end + end + + describe "is being created" do + before do + @is = {} + @should = {:ensure => :present, :one => :yay, :two => :absent} + end + + it "should call the :create method with its name" do + @manager.expects(:create).with { |name, attrs| name == @name } + @manager.update(@name, @is, @should) + end + + it "should call the :create method with its property hash converted to ldap attribute names" do + @manager.expects(:create).with { |name, attrs| attrs["uno"] == ["yay"] } + @manager.update(@name, @is, @should) + end + + it "should convert the property names to strings" do + @manager.expects(:create).with { |name, attrs| attrs["uno"] == ["yay"] } + @manager.update(@name, @is, @should) + end + + it "should convert the property values to arrays if necessary" do + @manager.expects(:create).with { |name, attrs| attrs["uno"] == ["yay"] } + @manager.update(@name, @is, @should) + end + + it "should convert the property values to strings if necessary" do + @manager.expects(:create).with { |name, attrs| attrs["uno"] == ["yay"] } + @manager.update(@name, @is, @should) + end + + it "should not include :ensure in the properties sent" do + @manager.expects(:create).with { |*args| args[1][:ensure].nil? } + @manager.update(@name, @is, @should) + end + + it "should not include attributes set to :absent in the properties sent" do + @manager.expects(:create).with { |*args| args[1][:dos].nil? } + @manager.update(@name, @is, @should) + end + end + + describe "is being modified" do + it "should call the :modify method with its name and an array of LDAP::Mod instances" do + LDAP::Mod.stubs(:new).returns "whatever" + + @is = {:one => :yay} + @should = {:one => :yay, :two => :foo} + + @manager.expects(:modify).with { |name, mods| name == @name } + @manager.update(@name, @is, @should) + end + + it "should create the LDAP::Mod with the property name converted to the ldap name as a string" do + @is = {:one => :yay} + @should = {:one => :yay, :two => :foo} + mod = mock 'module' + LDAP::Mod.expects(:new).with { |form, name, value| name == "dos" }.returns mod + + @manager.stubs(:modify) + + @manager.update(@name, @is, @should) + end + + it "should create an LDAP::Mod instance of type LDAP_MOD_ADD for each attribute being added, with the attribute value converted to a string of arrays" do + @is = {:one => :yay} + @should = {:one => :yay, :two => :foo} + mod = mock 'module' + LDAP::Mod.expects(:new).with(LDAP::LDAP_MOD_ADD, "dos", ["foo"]).returns mod + + @manager.stubs(:modify) + + @manager.update(@name, @is, @should) + end + + it "should create an LDAP::Mod instance of type LDAP_MOD_DELETE for each attribute being deleted" do + @is = {:one => :yay, :two => :foo} + @should = {:one => :yay, :two => :absent} + mod = mock 'module' + LDAP::Mod.expects(:new).with(LDAP::LDAP_MOD_DELETE, "dos", []).returns mod + + @manager.stubs(:modify) + + @manager.update(@name, @is, @should) + end + + it "should create an LDAP::Mod instance of type LDAP_MOD_REPLACE for each attribute being modified, with the attribute converted to a string of arrays" do + @is = {:one => :yay, :two => :four} + @should = {:one => :yay, :two => :five} + mod = mock 'module' + LDAP::Mod.expects(:new).with(LDAP::LDAP_MOD_REPLACE, "dos", ["five"]).returns mod + + @manager.stubs(:modify) + + @manager.update(@name, @is, @should) + end + + it "should pass all created Mod instances to the modify method" do + @is = {:one => :yay, :two => :foo, :three => :absent} + @should = {:one => :yay, :two => :foe, :three => :fee, :four => :fie} + LDAP::Mod.expects(:new).times(3).returns("mod1").then.returns("mod2").then.returns("mod3") + + @manager.expects(:modify).with do |name, mods| + mods.sort == %w{mod1 mod2 mod3}.sort + end + + @manager.update(@name, @is, @should) + end + end + end +end diff --git a/spec/unit/util/storage.rb b/spec/unit/util/storage.rb new file mode 100755 index 000000000..55d1d1f61 --- /dev/null +++ b/spec/unit/util/storage.rb @@ -0,0 +1,237 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'yaml' +require 'tempfile' + +require 'puppet/util/storage' + +describe Puppet::Util::Storage do + before(:all) do + Puppet[:statedir] = Dir.tmpdir() + end + + before(:each) do + Puppet::Util::Storage.clear() + end + + describe "when caching a symbol" do + it "should return an empty hash" do + Puppet::Util::Storage.cache(:yayness).should == {} + Puppet::Util::Storage.cache(:more_yayness).should == {} + end + + it "should add the symbol to its internal state" do + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + end + + it "should not clobber existing state when caching additional objects" do + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + Puppet::Util::Storage.cache(:bubblyness) + Puppet::Util::Storage.state().should == {:yayness=>{},:bubblyness=>{}} + end + end + + describe "when caching a Puppet::Type" do + before(:all) do + @file_test = Puppet.type(:file).create(:name => "/yayness", :check => %w{checksum type}) + @exec_test = Puppet.type(:exec).create(:name => "/bin/ls /yayness") + end + + it "should return an empty hash" do + Puppet::Util::Storage.cache(@file_test).should == {} + Puppet::Util::Storage.cache(@exec_test).should == {} + end + + it "should add the resource ref to its internal state" do + Puppet::Util::Storage.state().should == {} + Puppet::Util::Storage.cache(@file_test) + Puppet::Util::Storage.state().should == {"File[/yayness]"=>{}} + Puppet::Util::Storage.cache(@exec_test) + Puppet::Util::Storage.state().should == {"File[/yayness]"=>{}, "Exec[/bin/ls /yayness]"=>{}} + end + end + + describe "when caching invalid objects" do + before(:all) do + @bogus_objects = [ {}, [], "foo", 42, nil, Tempfile.new('storage_test') ] + end + + it "should raise an ArgumentError" do + @bogus_objects.each do |object| + proc { Puppet::Util::Storage.cache(object) }.should raise_error() + end + end + + it "should not add anything to its internal state" do + @bogus_objects.each do |object| + begin + Puppet::Util::Storage.cache(object) + rescue + Puppet::Util::Storage.state().should == {} + end + end + end + end + + it "should clear its internal state when clear() is called" do + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + Puppet::Util::Storage.clear() + Puppet::Util::Storage.state().should == {} + end + + describe "when loading from the state file" do + describe "when the state file/directory does not exist" do + before(:each) do + transient = Tempfile.new('storage_test') + @path = transient.path() + transient.close!() + end + + it "should not fail to load()" do + FileTest.exists?(@path).should be_false() + Puppet[:statedir] = @path + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet[:statefile] = @path + proc { Puppet::Util::Storage.load() }.should_not raise_error() + end + + it "should not lose its internal state when load() is called" do + FileTest.exists?(@path).should be_false() + + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + + Puppet[:statefile] = @path + proc { Puppet::Util::Storage.load() }.should_not raise_error() + + Puppet::Util::Storage.state().should == {:yayness=>{}} + end + end + + describe "when the state file/directory exists" do + before(:each) do + @state_file = Tempfile.new('storage_test') + @saved_statefile = Puppet[:statefile] + Puppet[:statefile] = @state_file.path() + end + + it "should overwrite its internal state if load() is called" do + # Should the state be overwritten even if Puppet[:statefile] is not valid YAML? + Puppet::Util::Storage.cache(:yayness) + Puppet::Util::Storage.state().should == {:yayness=>{}} + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == {} + end + + it "should restore its internal state if the state file contains valid YAML" do + test_yaml = {'File["/yayness"]'=>{"name"=>{:a=>:b,:c=>:d}}} + YAML.expects(:load).returns(test_yaml) + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == test_yaml + end + + it "should initialize with a clear internal state if the state file does not contain valid YAML" do + @state_file.write(:booness) + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == {} + end + + it "should raise an error if the state file does not contain valid YAML and cannot be renamed" do + @state_file.write(:booness) + File.chmod(0000, @state_file.path()) + + proc { Puppet::Util::Storage.load() }.should raise_error() + end + + it "should attempt to rename the state file if the file is corrupted" do + # We fake corruption by causing YAML.load to raise an exception + YAML.expects(:load).raises(Puppet::Error) + File.expects(:rename).at_least_once + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + end + + it "should fail gracefully on load() if the state file is not a regular file" do + @state_file.close!() + Dir.mkdir(Puppet[:statefile]) + File.expects(:rename).returns(0) + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + + Dir.rmdir(Puppet[:statefile]) + end + + it "should fail gracefully on load() if it cannot get a read lock on the state file" do + Puppet::Util.expects(:readlock).yields(false) + test_yaml = {'File["/yayness"]'=>{"name"=>{:a=>:b,:c=>:d}}} + YAML.expects(:load).returns(test_yaml) + + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == test_yaml + end + + after(:each) do + @state_file.close!() + Puppet[:statefile] = @saved_statefile + end + end + end + + describe "when storing to the state file" do + before(:each) do + @state_file = Tempfile.new('storage_test') + @saved_statefile = Puppet[:statefile] + Puppet[:statefile] = @state_file.path() + end + + it "should create the state file if it does not exist" do + @state_file.close!() + FileTest.exists?(Puppet[:statefile]).should be_false() + Puppet::Util::Storage.cache(:yayness) + + proc { Puppet::Util::Storage.store() }.should_not raise_error() + FileTest.exists?(Puppet[:statefile]).should be_true() + end + + it "should raise an exception if the state file is not a regular file" do + @state_file.close!() + Dir.mkdir(Puppet[:statefile]) + Puppet::Util::Storage.cache(:yayness) + + proc { Puppet::Util::Storage.store() }.should raise_error() + + Dir.rmdir(Puppet[:statefile]) + end + + it "should raise an exception if it cannot get a write lock on the state file" do + Puppet::Util.expects(:writelock).yields(false) + Puppet::Util::Storage.cache(:yayness) + + proc { Puppet::Util::Storage.store() }.should raise_error() + end + + it "should load() the same information that it store()s" do + Puppet::Util::Storage.cache(:yayness) + + Puppet::Util::Storage.state().should == {:yayness=>{}} + proc { Puppet::Util::Storage.store() }.should_not raise_error() + Puppet::Util::Storage.clear() + Puppet::Util::Storage.state().should == {} + proc { Puppet::Util::Storage.load() }.should_not raise_error() + Puppet::Util::Storage.state().should == {:yayness=>{}} + end + + after(:each) do + @state_file.close!() + Puppet[:statefile] = @saved_statefile + end + end +end diff --git a/test/data/providers/ssh_authorized_key/parsed/authorized_keys b/test/data/providers/ssh_authorized_key/parsed/authorized_keys new file mode 100644 index 000000000..033f98b87 --- /dev/null +++ b/test/data/providers/ssh_authorized_key/parsed/authorized_keys @@ -0,0 +1,5 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVe+esFfd3qWBseb83PoFX63geZJAg6bjV4/Rdn1zEoa9EO2QyUdYUen4+rpsh3vVKZ6HFNsn3+W5+kPYgE1F/N4INqkbjY3sqCkP/W1BL9+sbVVbuJFAAAAFQCfjWDk5XhvGUkPjNWWVqltBYzHtwAAAIEAg/XL7ky7x9Ad5banzPFAfmM+DGFe0A/JEbLDjKmr5KBM5x4RFohtEvZ8ECuVGUOqBWdgAjyYwsG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4knEl+mNfOLq+FH0011UhecOiqTcESMzQDtgQ1vJh2VchElBLjl3x/ZugAAACAAh5jGQC338t5ObP8trSlOefkx0sXmmEzUbo3Mt8mGUuGJPx8m+X0L8Xd+l5rQxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaLZq7m/RmsWYvoLT3jebBlpvvQE8YlI= francois.deppierraz@nimag.net +ssh-dss AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVe+esFfd3qWBseb83PoFX63geZJAg6bjV4/Rdn1zEoa9EO2QyUdYUen4+rpsh3vVKZ6HFNsn3+W5+kPYgE1F/N4INqkbjY3sqCkP/W1BL9+sbVVbuJFAAAAFQCfjWDk5XhvGUkPjNWWVqltBYzHtwAAAIEAg/XL7ky7x9Ad5banzPFAfmM+DGFe0A/JEbLDjKmr5KBM5x4RFohtEvZ8ECuVGUOqBWdgAjyYwsG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4knEl+mNfOLq+FH0011UhecOiqTcESMzQDtgQ1vJh2VchElBLjl3x/ZugAAACAAh5jGQC338t5ObP8trSlOefkx0sXmmEzUbo3Mt8mGUuGJPx8m+X0L8Xd+l5rQxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaLZq7m/RmsWYvoLT3jebBlpvvQE8YlI= Francois Deppierraz <francois@ctrlaltdel.ch> +from="192.168.1.1",command="/bin/false",no-pty,no-port-forwarding ssh-dss AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVe+esFfd3qWBseb83PoFX63geZJAg6bjV4/Rdn1zEoa9EO2QyUdYUen4+rpsh3vVKZ6HFNsn3+W5+kPYgE1F/N4INqkbjY3sqCkP/W1BL9+sbVVbuJFAAAAFQCfjWDk5XhvGUkPjNWWVqltBYzHtwAAAIEAg/XL7ky7x9Ad5banzPFAfmM+DGFe0A/JEbLDjKmr5KBM5x4RFohtEvZ8ECuVGUOqBWdgAjyYwsG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4knEl+mNfOLq+FH0011UhecOiqTcESMzQDtgQ1vJh2VchElBLjl3x/ZugAAACAAh5jGQC338t5ObP8trSlOefkx0sXmmEzUbo3Mt8mGUuGJPx8m+X0L8Xd+l5rQxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaLZq7m/RmsWYvoLT3jebBlpvvQE8YlI= Francois Deppierraz +ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA2Vi+TdC3iOGYcIo5vGTvC9P9rjHl9RxCuZmSfn+YDFQ35RXf0waijtjp9I7GYh6R4hBjA5z0u/Pzi95LET5NfRM0Gdc0DJyvBI7K+ALBxIT383Iz6Yz4iKxe1TEJgHGM2he4+7BHkjc3kdIZqIpZjucCk4VsXSxujO4MKKvtaKK2l+kahlLQHHw/vZkDpIgL52iGVsjW9l8RLJaKHZ4mDHJN/Q/Rzn2W4EvcdHUzwhvGMwZlm8clDwITBrSsawYtnivJrQSYcmTRqJuS8wprNDrLIhTGjrwFg5WpruUuMt6fLuCqwe6TeEL+nh3DQ4g554c5aRp3oU6LGBKTvNZGWQ== francois@korn +ssh-dss AAAAB3NzaC1kc3MAAACBAMPpCYnjywOemd8LqbbmC+bePNR3/H1rXsiFwjSLhYE3bbOpvclvOzN1DruFc34m0FopVnMkP+aubjdIYF8pijl+5hg9ggB7Kno2dl0Dd1rGN/swvmhA8OpLAQv7Qt7UnXKVho3as08zYZsrHxYFu0wlnkdbsv4cy4aXyQKd4MPVAAAAFQDSyQFWg8Qt3wU05buhZ10psoR7tQAAAIEAmAhguXwUnI3P2FF5NAW/mpJUmUERdL4pyZARUyAgpf7ezwrh9TJqrvGTQNBF97Xqaivyncm5JWQdMIsTBxEFaXZGkmBta02KnWcn447qvIh7iv8XpNL6M9flCkBEZOJ4t9El0ytTSHHaiCz8A20Et+E8evWyi1kXkFDt8ML2dGgAAACBAK0X4ympbdEjgV/ZyOc+BU22u7vOnfSOUJmyar4Ax1MIDNnoyNWKnGvxRutydQcQOKQHZEU0fE8MhPFn6nLF6CoVfEl/oz0EYz3hjV4WPFpHrF5DY/rhvNj8iuneKJ5P0dy/rG6m5qey25PnHyGFVoIRlkHJvBCJT40dHs40YEjI francois@korn diff --git a/test/data/types/ssh_authorized_key/1 b/test/data/types/ssh_authorized_key/1 new file mode 100644 index 000000000..69d1af15e --- /dev/null +++ b/test/data/types/ssh_authorized_key/1 @@ -0,0 +1,2 @@ +ssh-dss AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVe+esFfd3qWBseb83PoFX63geZJAg6bjV4/Rdn1zEoa9EO2QyUdYUen4+rpsh3vVKZ6HFNsn3+W5+kPYgE1F/N4INqkbjY3sqCkP/W1BL9+sbVVbuJFAAAAFQCfjWDk5XhvGUkPjNWWVqltBYzHtwAAAIEAg/XL7ky7x9Ad5banzPFAfmM+DGFe0A/JEbLDjKmr5KBM5x4RFohtEvZ8ECuVGUOqBWdgAjyYwsG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4knEl+mNfOLq+FH0011UhecOiqTcESMzQDtgQ1vJh2VchElBLjl3x/ZugAAACAAh5jGQC338t5ObP8trSlOefkx0sXmmEzUbo3Mt8mGUuGJPx8m+X0L8Xd+l5rQxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaLZq7m/RmsWYvoLT3jebBlpvvQE8YlI= francois.deppierraz@camptocamp.com +from="192.168.1.2",command="/usr/local/bin/backup.sh",no-agent-forwarding,no-port-forwarding,no-X11-forwarding ssh-rsa AAAAB3NzaC1kc3MAAACBAJkupmdsJSDXfUy5EU5NTRBDr9Woo3w0YnB8KmnJW9ghU8C7SkWPB1fIHVesG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4ksG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4ksG4oRVjLnKrf/rgmbNRzSFgEWkcAye3BVwk7Dt6hh4kxytqE3SmV/RD+6REqBuPqHM8RQuqAzfjdOeg/Ajdggx1CRMTVhltZsgQoxO30cz9Qo0SdPoL+Jp1fLuaL Backup system diff --git a/test/ral/providers/cron/crontab.rb b/test/ral/providers/cron/crontab.rb index 53bd76c50..900a0478e 100755 --- a/test/ral/providers/cron/crontab.rb +++ b/test/ral/providers/cron/crontab.rb @@ -599,5 +599,33 @@ class TestCronParsedProvider < Test::Unit::TestCase result = target.read assert_equal("# Puppet Name: test\n* 4 * * * /bin/echo yay\n", result, "Did not write out environment setting") end + + # Testing #1216 + def test_strange_lines + @provider.stubs(:filetype).returns(Puppet::Util::FileType.filetype(:ram)) + text = " 5 \t\t 1,2 * 1 0 /bin/echo funtest" + + records = nil + assert_nothing_raised { + records = @provider.parse(text) + } + + should = { + :minute => %w{5}, + :hour => %w{1 2}, + :monthday => :absent, + :month => %w{1}, + :weekday => %w{0}, + :command => "/bin/echo funtest" + } + + is = records.shift + assert(is, "Did not get record") + + should.each do |p, v| + assert_equal(v, is[p], "did not parse %s correctly" % p) + end + end + end diff --git a/test/ral/providers/provider.rb b/test/ral/providers/provider.rb index 2196fafce..70f606a37 100755 --- a/test/ral/providers/provider.rb +++ b/test/ral/providers/provider.rb @@ -37,12 +37,13 @@ class TestProvider < Test::Unit::TestCase cleanup { Puppet::Type.rmtype(:provider_test) } end - def test_confine - provider = newprovider + def test_confine_defaults_to_suitable - assert(provider.suitable?, - "Marked unsuitable with no confines") + provider = newprovider + assert(provider.suitable?, "Marked unsuitable with no confines") + end + def test_confine_results { {:true => true} => true, {:true => false} => false, @@ -54,6 +55,8 @@ class TestProvider < Test::Unit::TestCase {:exists => echo} => true, {:exists => "/this/file/does/not/exist"} => false, }.each do |hash, result| + provider = newprovider + # First test :true hash.each do |test, val| assert_nothing_raised do @@ -61,19 +64,25 @@ class TestProvider < Test::Unit::TestCase end end - assert_equal(result, provider.suitable?, - "Failed for %s" % [hash.inspect]) + assert_equal(result, provider.suitable?, "Failed for %s" % [hash.inspect]) provider.initvars end + end + + def test_multiple_confines_do_not_override + provider = newprovider # Make sure multiple confines don't overwrite each other provider.confine :true => false assert(! provider.suitable?) provider.confine :true => true assert(! provider.suitable?) + end - provider.initvars + def test_one_failed_confine_is_sufficient + + provider = newprovider # Make sure we test multiple of them, and that a single false wins provider.confine :true => true, :false => false @@ -82,6 +91,18 @@ class TestProvider < Test::Unit::TestCase assert(! provider.suitable?) end + # #1197 - the binary should not be + def test_command_checks_for_binaries_each_time + provider = newprovider + + provider.commands :testing => "/no/such/path" + + provider.stubs(:binary).returns "/no/such/path" + + provider.command(:testing) + assert_equal("/no/such/path", provider.command(:testing), "Did not return correct binary path") + end + def test_command {:echo => "echo", :echo_with_path => echo, :missing => "nosuchcommand", :missing_qualified => "/path/to/nosuchcommand"}.each do |name, command| provider = newprovider |