From 7b70e85d717fbd1ae52e94658df8b407b043f8b8 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 1 Nov 2008 10:44:21 +1100 Subject: Fixed #1721 - puppet.conf documentation incorrectly lists signals that affect the daemons --- lib/puppet/reference/configuration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/reference/configuration.rb b/lib/puppet/reference/configuration.rb index 8e82c8adf..6c40c86ba 100644 --- a/lib/puppet/reference/configuration.rb +++ b/lib/puppet/reference/configuration.rb @@ -133,7 +133,7 @@ Signals ------- The ``puppetd`` and ``puppetmasterd`` executables catch some signals for special handling. Both daemons catch (``SIGHUP``), which forces the server to restart -tself. Predictably, interrupt and terminate (``SIGINT`` and ``SIGHUP``) will shut +tself. Predictably, interrupt and terminate (``SIGINT`` and ``SIGTERM``) will shut down the server, whether it be an instance of ``puppetd`` or ``puppetmasterd``. Sending the ``SIGUSR1`` signal to an instance of ``puppetd`` will cause it to -- cgit From 0a3d34dbe7b5175f4822b6f1bfaa6f0ba05e9bd2 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 1 Nov 2008 10:56:52 +1100 Subject: Fixes #1714 - yumhelper handling with yum 2.2.x is broken --- lib/puppet/provider/package/yumhelper.py | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib') diff --git a/lib/puppet/provider/package/yumhelper.py b/lib/puppet/provider/package/yumhelper.py index 8eab0d081..6263d3473 100644 --- a/lib/puppet/provider/package/yumhelper.py +++ b/lib/puppet/provider/package/yumhelper.py @@ -39,6 +39,15 @@ def pkg_lists(my): my.doTsSetup() my.doRpmDBSetup() + + # Yum 2.2/2.3 python libraries require a couple of extra function calls to setup package sacks. + # They also don't have a __version__ attribute + try: + yumver = yum.__version__ + except AttributeError: + my.doRepoSetup() + my.doSackSetup() + return my.doPackageLists('updates') def shell_out(): -- cgit From 6d7b5ef81d671ad27b4bbcfb5e0065f76209ee7d Mon Sep 17 00:00:00 2001 From: Martin Englund Date: Wed, 5 Nov 2008 11:35:09 +0100 Subject: Fixes #1672 - unsafe crontab handling in Solaris Signed-off-by: Martin Englund --- lib/puppet/util/filetype.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb index 7e799b613..60cbc77e7 100755 --- a/lib/puppet/util/filetype.rb +++ b/lib/puppet/util/filetype.rb @@ -213,10 +213,10 @@ class Puppet::Util::FileType begin output = Puppet::Util.execute(%w{crontab -l}, :uid => @path) return "" if output.include?("can't open your crontab") + raise Puppet::Error, "User %s not authorized to use cron" % @path if output.include?("you are not authorized to use cron") return output - rescue - # If there's a failure, treat it like an empty file. - return "" + rescue => detail + raise Puppet::Error, "Could not read crontab for %s: %s" % [@path, detail] end end -- cgit From dfc055405dd84f6b81ec92e4570c9a13dd9549e6 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 5 Nov 2008 22:09:20 +1100 Subject: Fixed #1730 - Edited file/ensure.rb docs for clarity --- lib/puppet/type/file/ensure.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/puppet/type/file/ensure.rb b/lib/puppet/type/file/ensure.rb index a9ddc2dba..3a1a824bd 100755 --- a/lib/puppet/type/file/ensure.rb +++ b/lib/puppet/type/file/ensure.rb @@ -2,11 +2,10 @@ module Puppet Puppet.type(:file).ensurable do require 'etc' desc "Whether to create files that don't currently exist. - Possible values are *absent*, *present* (will match any form of - file existence, and if the file is missing will create an empty - file), *file*, and *directory*. Specifying ``absent`` will delete - the file, although currently this will not recursively delete - directories. + Possible values are *absent*, *present*, *file*, and *directory*. + Specifying ``present`` will match any form of file existence, and + if the file is missing will create an empty file. Specifying + ``absent`` will delete the file (and directory if recurse => true). Anything other than those values will be considered to be a symlink. For instance, the following text creates a link:: -- cgit From e33d0871028ab3483f555103811f1c8c2685f511 Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Tue, 11 Nov 2008 00:05:53 +0100 Subject: Fix #1740 - Daemontools and Runit is not ReST compliant --- lib/puppet/provider/service/daemontools.rb | 41 ++++++++++++++++++------------ lib/puppet/provider/service/runit.rb | 38 ++++++++++++++++----------- 2 files changed, 48 insertions(+), 31 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/service/daemontools.rb b/lib/puppet/provider/service/daemontools.rb index 52d8c6b6c..db4a7cdf8 100644 --- a/lib/puppet/provider/service/daemontools.rb +++ b/lib/puppet/provider/service/daemontools.rb @@ -2,27 +2,36 @@ # # author Brice Figureau Puppet::Type.type(:service).provide :daemontools, :parent => :base do - desc "Daemontools service management. - This provider manages daemons running supervised by D.J.Bernstein daemontools. - It tries to detect the service directory, with by order of preference: - * /service - * /etc/service - * /var/lib/svscan - The daemon directory should be placed in a directory that can be - by default in: - * /var/lib/service - * /etc - or this can be overriden in the service resource parameters: + desc """ +Daemontools service management. +This provider manages daemons running supervised by D.J.Bernstein daemontools. +It tries to detect the service directory, with by order of preference: + + * /service + * /etc/service + * /var/lib/svscan + +The daemon directory should be placed in a directory that can be +by default in: + + * /var/lib/service + * /etc + +or this can be overriden in the service resource parameters:: + service { \"myservice\": provider => \"daemontools\", path => \"/path/to/daemons\"; } - This provider supports out of the box: - * start/stop (mapped to enable/disable) - * enable/disable - * restart - * status" +This provider supports out of the box: + + * start/stop (mapped to enable/disable) + * enable/disable + * restart + * status + +""" commands :svc => "/usr/bin/svc" commands :svstat => "/usr/bin/svstat" diff --git a/lib/puppet/provider/service/runit.rb b/lib/puppet/provider/service/runit.rb index 230fa75d9..5c2b9c733 100644 --- a/lib/puppet/provider/service/runit.rb +++ b/lib/puppet/provider/service/runit.rb @@ -2,26 +2,34 @@ # # author Brice Figureau Puppet::Type.type(:service).provide :runit, :parent => :daemontools do - desc "Runit service management. - This provider manages daemons running supervised by Runit. - It tries to detect the service directory, with by order of preference: - * /service - * /var/service - * /etc/service - The daemon directory should be placed in a directory that can be - by default in: - * /etc/sv - or this can be overriden in the service resource parameters: + desc """ +Runit service management. +This provider manages daemons running supervised by Runit. +It tries to detect the service directory, with by order of preference: + + * /service + * /var/service + * /etc/service + +The daemon directory should be placed in a directory that can be +by default in: + + * /etc/sv + +or this can be overriden in the service resource parameters:: + service { \"myservice\": provider => \"runit\", path => \"/path/to/daemons\"; } - This provider supports out of the box: - * start/stop - * enable/disable - * restart - * status" +This provider supports out of the box: + + * start/stop + * enable/disable + * restart + * status +""" commands :sv => "/usr/bin/sv" -- cgit From e032034fc4f5f45d883f24104c436e16ce555c0e Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Mon, 10 Nov 2008 15:24:55 +0100 Subject: Fix #1737 - ssh_authorized_keys should be able to parse options containing commas Signed-off-by: Brice Figureau --- lib/puppet/provider/ssh_authorized_key/parsed.rb | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/provider/ssh_authorized_key/parsed.rb b/lib/puppet/provider/ssh_authorized_key/parsed.rb index 5411a1fb8..77af58ef5 100644 --- a/lib/puppet/provider/ssh_authorized_key/parsed.rb +++ b/lib/puppet/provider/ssh_authorized_key/parsed.rb @@ -19,7 +19,7 @@ Puppet::Type.type(:ssh_authorized_key).provide(:parsed, if record[:options].nil? record[:options] = [:absent] else - record[:options] = record[:options].split(',') + record[:options] = Puppet::Type::Ssh_authorized_key::ProviderParsed.parse_options(record[:options]) end }, :pre_gen => proc { |record| @@ -71,5 +71,25 @@ Puppet::Type.type(:ssh_authorized_key).provide(:parsed, File.chown(Puppet::Util.uid(user), nil, @property_hash[:target]) end end + + # parse sshv2 option strings, wich is a comma separated list of + # either key="values" elements or bare-word elements + def self.parse_options(options) + result = [] + scanner = StringScanner.new(options) + while !scanner.eos? + scanner.skip(/[ \t]*/) + # scan a long option + if out = scanner.scan(/[-a-z0-9A-Z_]+=\".*?\"/) or out = scanner.scan(/[-a-z0-9A-Z_]+/) + result << out + else + # found an unscannable token, let's abort + break + end + # eat a comma + scanner.skip(/[ \t]*,[ \t]*/) + end + result + end end -- cgit From bf5be00f87a8df66964931939531399384142aff Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Tue, 11 Nov 2008 13:43:29 +0100 Subject: Fix #1737 - part2 - Fix display of "options" --- lib/puppet/type/ssh_authorized_key.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'lib') diff --git a/lib/puppet/type/ssh_authorized_key.rb b/lib/puppet/type/ssh_authorized_key.rb index 4afca1cca..66cf3e733 100644 --- a/lib/puppet/type/ssh_authorized_key.rb +++ b/lib/puppet/type/ssh_authorized_key.rb @@ -38,6 +38,22 @@ module Puppet should be specified as an array." defaultto do :absent end + + def is_to_s(value) + if value == :absent or value.include?(:absent) + super + else + value.join(",") + end + end + + def should_to_s(value) + if value == :absent or value.include?(:absent) + super + else + value.join(",") + end + end end autorequire(:user) do -- cgit From d5e19f18eddd0d6ddb9818dabc873b3203335f95 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 13 Nov 2008 10:17:32 +1100 Subject: Fixed #1739 - Added uninstall functionality to yum provider --- lib/puppet/provider/package/yum.rb | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'lib') diff --git a/lib/puppet/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb index 56fad1af9..5e49b324a 100755 --- a/lib/puppet/provider/package/yum.rb +++ b/lib/puppet/provider/package/yum.rb @@ -100,5 +100,10 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do # Install in yum can be used for update, too self.install end + + def purge + yum "-y", :erase, @resource[:name] + end + end end -- cgit From 3a5dcab28682a1bbf1b71b2d1de39008468b1ca6 Mon Sep 17 00:00:00 2001 From: "Sean E. Millichamp" Date: Sun, 2 Nov 2008 20:05:57 -0500 Subject: Refactoring of SELinux functions to use native Ruby SELinux interface --- lib/puppet/util/selinux.rb | 133 +++++++++++++++++++++++---------------------- 1 file changed, 67 insertions(+), 66 deletions(-) (limited to 'lib') diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb index 148748950..b181b3556 100644 --- a/lib/puppet/util/selinux.rb +++ b/lib/puppet/util/selinux.rb @@ -1,74 +1,55 @@ # Provides utility functions to help interfaces Puppet to SELinux. # -# Currently this is implemented via the command line tools. At some -# point support should be added to use the new SELinux ruby bindings -# as that will be faster and more reliable then shelling out when they -# are available. At this time (2008-09-26) these bindings aren't bundled on -# any SELinux-using distribution I know of. +# This requires the very new SELinux Ruby bindings. These bindings closely +# mirror the SELinux C library interface. +# +# Support for the command line tools is not provided because the performance +# was abysmal. At this time (2008-11-02) the only distribution providing +# these Ruby SELinux bindings which I am aware of is Fedora (in libselinux-ruby). -require 'puppet/util' +begin + require 'selinux' +rescue LoadError + # Nothing +end module Puppet::Util::SELinux - include Puppet::Util - def selinux_support? - FileTest.exists?("/selinux/enforce") + unless defined? Selinux + return false + end + if Selinux.is_selinux_enabled == 1 + return true + end + return false end # Retrieve and return the full context of the file. If we don't have - # SELinux support or if the stat call fails then return nil. + # SELinux support or if the SELinux call fails then return nil. def get_selinux_current_context(file) unless selinux_support? return nil end - context = "" - begin - execpipe("/usr/bin/stat -c %C #{file}") do |out| - out.each do |line| - context << line - end - end - rescue Puppet::ExecutionFailure - return nil - end - context.chomp! - # Handle the case that the system seems to have SELinux support but - # stat finds unlabled files. - if context == "(null)" + retval = Selinux.lgetfilecon(file) + if retval == -1 return nil end - return context + return retval[1] end - # Use the matchpathcon command, if present, to return the SELinux context - # which the SELinux policy on the system expects the file to have. We can - # use this to obtain a good default context. If the command does not - # exist or the call fails return nil. - # - # Note: For this command to work a full, non-relative, filesystem path - # should be given. + # Retrieve and return the default context of the file. If we don't have + # SELinux support or if the SELinux call fails to file a default then return nil. def get_selinux_default_context(file) unless selinux_support? return nil end - unless FileTest.executable?("/usr/sbin/matchpathcon") + filestat = File.lstat(file) + retval = Selinux.matchpathcon(file, filestat.mode) + if retval == -1 return nil end - context = "" - begin - execpipe("/usr/sbin/matchpathcon #{file}") do |out| - out.each do |line| - context << line - end - end - rescue Puppet::ExecutionFailure - return nil - end - # For a successful match, matchpathcon returns two fields separated by - # a variable amount of whitespace. The second field is the full context. - context = context.split(/\s/)[1] - return context + return retval[1] end # Take the full SELinux context returned from the tools and parse it @@ -91,32 +72,52 @@ module Puppet::Util::SELinux end # This updates the actual SELinux label on the file. You can update - # only a single component or update the entire context. It is just a - # wrapper around the chcon command. + # only a single component or update the entire context. + # The caveat is that since setting a partial context makes no sense the + # file has to already exist. Puppet (via the File resource) will always + # just try to set components, even if all values are specified by the manifest. + # I believe that the OS should always provide at least a fall-through context + # though on any well-running system. def set_selinux_context(file, value, component = false) unless selinux_support? return nil end - case component - when :seluser - flag = "-u" - when :selrole - flag = "-r" - when :seltype - flag = "-t" - when :selrange - flag = "-l" - else - flag = nil - end - if flag.nil? - cmd = ["/usr/bin/chcon","-h",value,file] + if component + # Must first get existing context to replace a single component + context = Selinux.lgetfilecon(file)[1] + if context == -1 + # We can't set partial context components when no context exists + # unless/until we can find a way to make Puppet call this method + # once for all selinux file label attributes. + Puppet.warning "Can't set SELinux context on file unless the file already has some kind of context" + return nil + end + context = context.split(':') + case component + when :seluser + context[0] = value + when :selrole + context[1] = value + when :seltype + context[2] = value + when :selrange + context[3] = value + else + raise ArguementError, "set_selinux_context component must be one of :seluser, :selrole, :seltype, or :selrange" + end + context = context.join(':') + else + context = value + end + + retval = Selinux.lsetfilecon(file, context) + if retval == 0 + return true else - cmd = ["/usr/bin/chcon","-h",flag,value,file] + Puppet.warning "Failed to set SELinux context %s on %s" % [context, file] + return false end - execute(cmd) - return true end # Since this call relies on get_selinux_default_context it also needs a -- cgit From 71a9e60396b1a4964c21308ce3a8bdddc97a721e Mon Sep 17 00:00:00 2001 From: "Sean E. Millichamp" Date: Thu, 6 Nov 2008 10:43:19 -0500 Subject: Fixes relating to transition to native SELinux bindings --- lib/puppet/type/file/selcontext.rb | 7 +++++++ lib/puppet/util/selinux.rb | 11 +++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/puppet/type/file/selcontext.rb b/lib/puppet/type/file/selcontext.rb index d5111caf8..982910c04 100644 --- a/lib/puppet/type/file/selcontext.rb +++ b/lib/puppet/type/file/selcontext.rb @@ -50,6 +50,13 @@ module Puppet end end + selcontext = self.should + + if selcontext == :absent + # This is only valid for create states... + return nil + end + self.set_selinux_context(@resource[:path], @should, name) return :file_changed end diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb index b181b3556..0df137370 100644 --- a/lib/puppet/util/selinux.rb +++ b/lib/puppet/util/selinux.rb @@ -44,8 +44,15 @@ module Puppet::Util::SELinux unless selinux_support? return nil end - filestat = File.lstat(file) - retval = Selinux.matchpathcon(file, filestat.mode) + # If the file exists we should pass the mode to matchpathcon for the most specific + # matching. If not, we can pass a mode of 0. + begin + filestat = File.lstat(file) + mode = filestat.mode + rescue Errno::ENOENT + mode = 0 + end + retval = Selinux.matchpathcon(file, mode) if retval == -1 return nil end -- cgit From 60455e708cbf3069a64ed4a0af6ce457c65c684e Mon Sep 17 00:00:00 2001 From: "Sean E. Millichamp" Date: Thu, 6 Nov 2008 11:13:34 -0500 Subject: Quiet debug when no default SELinux context found for one of the components --- lib/puppet/type/file/selcontext.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/type/file/selcontext.rb b/lib/puppet/type/file/selcontext.rb index 982910c04..084cd3d2c 100644 --- a/lib/puppet/type/file/selcontext.rb +++ b/lib/puppet/type/file/selcontext.rb @@ -38,7 +38,9 @@ module Puppet return nil end property_default = self.parse_selinux_context(property, context) - self.debug "Found #{property} default '#{property_default}' for #{@resource[:path]}" + if not property_default.nil? + self.debug "Found #{property} default '#{property_default}' for #{@resource[:path]}" + end return property_default end -- cgit From cebadd9296ad9193f583f698ec77f0fc99ae6fc9 Mon Sep 17 00:00:00 2001 From: "Sean E. Millichamp" Date: Thu, 13 Nov 2008 16:54:50 -0500 Subject: Fix bug #1681: Add filesystem type check to test for per-file SELinux context support --- lib/puppet/util/selinux.rb | 64 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) (limited to 'lib') diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb index 0df137370..70f244507 100644 --- a/lib/puppet/util/selinux.rb +++ b/lib/puppet/util/selinux.rb @@ -44,6 +44,11 @@ module Puppet::Util::SELinux unless selinux_support? return nil end + # If the filesystem has no support for SELinux labels, return a default of nil + # instead of what matchpathcon would return + unless selinux_label_support?(file) + return nil + end # If the file exists we should pass the mode to matchpathcon for the most specific # matching. If not, we can pass a mode of 0. begin @@ -144,4 +149,63 @@ module Puppet::Util::SELinux end return nil end + + # Internal helper function to read and parse /proc/mounts + def read_mounts + begin + mounts = File.read("/proc/mounts") + rescue + return nil + end + + mntpoint = {} + + # Read all entries in /proc/mounts. The second column is the + # mountpoint and the third column is the filesystem type. + # We skip rootfs because it is always mounted at / + mounts.collect do |line| + params = line.split(' ') + next if params[2] == 'rootfs' + mntpoint[params[1]] = params[2] + end + return mntpoint + end + + # Internal helper function to return which type of filesystem a + # given file path resides on + def find_fs(file) + unless mnts = read_mounts() + return nil + end + + # For a given file: + # Check if the filename is in the data structure; + # return the fstype if it is. + # Just in case: return something if you're down to "/" or "" + # Remove the last slash and everything after it, + # and repeat with that as the file for the next loop through. + ary = file.split('/') + while not ary.empty? do + path = ary.join('/') + if mnts.has_key?(path) + return mnts[path] + end + ary.pop + end + return mnts['/'] + end + + # Check filesystem a path resides on for SELinux support against + # whitelist of known-good filesystems. + # Returns true if the filesystem can support SELinux labels and + # false if not. + def selinux_label_support?(file) + fstype = find_fs(file) + if fstype.nil? + return false + end + filesystems = ['ext2', 'ext3', 'ext4', 'gfs', 'gfs2', 'xfs', 'jfs'] + return filesystems.include?(fstype) + end + end -- cgit From 1e81739f80f59aa1d10b46ab0ed22c95eeeca36c Mon Sep 17 00:00:00 2001 From: "Sean E. Millichamp" Date: Thu, 13 Nov 2008 17:41:50 -0500 Subject: Fix bug #1746: Sync SELinux file attributes after file contents created/modified --- lib/puppet/type/file.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index 371571ff3..b6396b0bf 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -1127,7 +1127,7 @@ module Puppet # file creation/modification, so we have to do some extra checking. def property_fix properties.each do |thing| - next unless [:mode, :owner, :group].include?(thing.name) + next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name) # Make sure we get a new stat objct self.stat(true) -- cgit From 6426a29cd7df62967aa54dc60989cc248a831e77 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 14 Nov 2008 10:23:22 +1100 Subject: Removed extra 'end' from yum.rb --- lib/puppet/provider/package/yum.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb index 5e49b324a..581a446ea 100755 --- a/lib/puppet/provider/package/yum.rb +++ b/lib/puppet/provider/package/yum.rb @@ -105,5 +105,4 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do yum "-y", :erase, @resource[:name] end end -end -- cgit From c98f7a5fe2917c9486ec5ab2fca5403446d43932 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 13 Nov 2008 17:40:35 -0800 Subject: Fixing the provider's confine subsystem so the logs are more useful. I apparently lost some context in these logs when I switched to this separate subsystem. Note that this also fixes some of the informational issues in Signed-off-by: Luke Kanies --- lib/puppet/provider/confine.rb | 5 ++++- lib/puppet/provider/confine/variable.rb | 2 +- lib/puppet/provider/confine_collection.rb | 5 ++++- lib/puppet/provider/confiner.rb | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/confine.rb b/lib/puppet/provider/confine.rb index 70148fc33..ff97831ee 100644 --- a/lib/puppet/provider/confine.rb +++ b/lib/puppet/provider/confine.rb @@ -42,6 +42,9 @@ class Puppet::Provider::Confine for_binary end + # Used for logging. + attr_accessor :label + def initialize(values) values = [values] unless values.is_a?(Array) @values = values @@ -61,7 +64,7 @@ class Puppet::Provider::Confine def valid? values.each do |value| unless pass?(value) - Puppet.debug message(value) + Puppet.debug(label + ": " + message(value)) return false end end diff --git a/lib/puppet/provider/confine/variable.rb b/lib/puppet/provider/confine/variable.rb index 0ef90d6d8..c868a4e9e 100644 --- a/lib/puppet/provider/confine/variable.rb +++ b/lib/puppet/provider/confine/variable.rb @@ -25,7 +25,7 @@ class Puppet::Provider::Confine::Variable < Puppet::Provider::Confine end def message(value) - "facter value '%s' for '%s' not in required list '%s'" % [value, self.name, values.join(",")] + "facter value '%s' for '%s' not in required list '%s'" % [test_value, self.name, values.join(",")] end # Compare the passed-in value to the retrieved value. diff --git a/lib/puppet/provider/confine_collection.rb b/lib/puppet/provider/confine_collection.rb index 35f461acb..0dbdc7790 100644 --- a/lib/puppet/provider/confine_collection.rb +++ b/lib/puppet/provider/confine_collection.rb @@ -19,10 +19,13 @@ class Puppet::Provider::ConfineCollection confine.name = test @confines << confine end + @confines[-1].label = self.label end end - def initialize + attr_reader :label + def initialize(label) + @label = label @confines = [] end diff --git a/lib/puppet/provider/confiner.rb b/lib/puppet/provider/confiner.rb index 4605523e8..65243efce 100644 --- a/lib/puppet/provider/confiner.rb +++ b/lib/puppet/provider/confiner.rb @@ -7,7 +7,7 @@ module Puppet::Provider::Confiner def confine_collection unless defined?(@confine_collection) - @confine_collection = Puppet::Provider::ConfineCollection.new + @confine_collection = Puppet::Provider::ConfineCollection.new(self.to_s) end @confine_collection end -- cgit From 0ca502593b199d577e44352d8bde476fb2cf2267 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 16 Nov 2008 10:11:19 +1100 Subject: Fixed #1718 - Added preseed to apt uninstall and purge --- lib/puppet/provider/package/apt.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib') diff --git a/lib/puppet/provider/package/apt.rb b/lib/puppet/provider/package/apt.rb index 80465129d..a99ee4c57 100755 --- a/lib/puppet/provider/package/apt.rb +++ b/lib/puppet/provider/package/apt.rb @@ -99,10 +99,16 @@ Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do end def uninstall + if @resource[:responsefile] + self.run_preseed + end aptget "-y", "-q", :remove, @resource[:name] end def purge + if @resource[:responsefile] + self.run_preseed + end aptget '-y', '-q', :remove, '--purge', @resource[:name] # workaround a "bug" in apt, that already removed packages are not purged super -- cgit From b8ed6670cd0d77f63b153fb5d5afff41f2c03704 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sun, 16 Nov 2008 10:20:22 +1100 Subject: Fixed #1735 and #1747 - Fixes to confine system --- lib/puppet/provider/confine/variable.rb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/provider/confine/variable.rb b/lib/puppet/provider/confine/variable.rb index c868a4e9e..9bef69412 100644 --- a/lib/puppet/provider/confine/variable.rb +++ b/lib/puppet/provider/confine/variable.rb @@ -24,6 +24,11 @@ class Puppet::Provider::Confine::Variable < Puppet::Provider::Confine @facter_value end + def initialize(values) + super + @values = @values.collect { |v| v.to_s.downcase } + end + def message(value) "facter value '%s' for '%s' not in required list '%s'" % [test_value, self.name, values.join(",")] end @@ -35,10 +40,16 @@ class Puppet::Provider::Confine::Variable < Puppet::Provider::Confine def reset # Reset the cache. We want to cache it during a given - # run, but across runs. + # run, but not across runs. @facter_value = nil end + def valid? + @values.include?(test_value.to_s.downcase) + ensure + reset + end + private def setting? -- cgit From 064fb006a350e9555abe766c5cb4aeb803fd623a Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Sat, 15 Nov 2008 13:10:55 +0100 Subject: Add a doc attribute to AST nodes and fill it with the last seen comments The lexer maintains a stack of last seen comments. On blank lines the lexer flush the comments. On each opening brace the lexer enters a new stack level. On each block AST nodes, the stack is popped. Each AST nodes has a doc property that is filled with the last seen comments on node creation (in fact only on important node creation representing statements). Signed-off-by: Brice Figureau --- lib/puppet/parser/ast.rb | 15 ++ lib/puppet/parser/ast/casestatement.rb | 2 + lib/puppet/parser/ast/collection.rb | 2 + lib/puppet/parser/ast/definition.rb | 2 + lib/puppet/parser/ast/else.rb | 3 + lib/puppet/parser/ast/function.rb | 3 + lib/puppet/parser/ast/hostclass.rb | 3 + lib/puppet/parser/ast/ifstatement.rb | 3 + lib/puppet/parser/ast/node.rb | 3 + lib/puppet/parser/ast/resource.rb | 3 + lib/puppet/parser/ast/resource_defaults.rb | 2 + lib/puppet/parser/ast/resource_override.rb | 3 + lib/puppet/parser/ast/vardef.rb | 3 + lib/puppet/parser/grammar.ra | 22 ++- lib/puppet/parser/lexer.rb | 60 ++++++-- lib/puppet/parser/parser.rb | 212 ++++++++++++++++------------- lib/puppet/parser/parser_support.rb | 22 ++- 17 files changed, 252 insertions(+), 111 deletions(-) (limited to 'lib') diff --git a/lib/puppet/parser/ast.rb b/lib/puppet/parser/ast.rb index ddf88521c..303d75bac 100644 --- a/lib/puppet/parser/ast.rb +++ b/lib/puppet/parser/ast.rb @@ -12,8 +12,23 @@ class Puppet::Parser::AST include Puppet::Util::Errors include Puppet::Util::MethodHelper + include Puppet::Util::Docs + attr_accessor :line, :file, :parent, :scope + # don't fetch lexer comment by default + def use_docs + self.class.use_docs + end + + # allow our subclass to specify they want documentation + class << self + attr_accessor :use_docs + def associates_doc + self.use_docs = true + end + end + # Does this ast object set something? If so, it gets evaluated first. def self.settor? if defined? @settor diff --git a/lib/puppet/parser/ast/casestatement.rb b/lib/puppet/parser/ast/casestatement.rb index aa03090de..73fbdcf1e 100644 --- a/lib/puppet/parser/ast/casestatement.rb +++ b/lib/puppet/parser/ast/casestatement.rb @@ -6,6 +6,8 @@ class Puppet::Parser::AST class CaseStatement < AST::Branch attr_accessor :test, :options, :default + associates_doc + # Short-curcuit evaluation. Return the value of the statements for # the first option that matches. def evaluate(scope) diff --git a/lib/puppet/parser/ast/collection.rb b/lib/puppet/parser/ast/collection.rb index 9e795a33c..a51b9b4d2 100644 --- a/lib/puppet/parser/ast/collection.rb +++ b/lib/puppet/parser/ast/collection.rb @@ -8,6 +8,8 @@ class Puppet::Parser::AST class Collection < AST::Branch attr_accessor :type, :query, :form + associates_doc + # We return an object that does a late-binding evaluation. def evaluate(scope) if self.query diff --git a/lib/puppet/parser/ast/definition.rb b/lib/puppet/parser/ast/definition.rb index 0c65c702c..3cd8e79c7 100644 --- a/lib/puppet/parser/ast/definition.rb +++ b/lib/puppet/parser/ast/definition.rb @@ -10,6 +10,8 @@ class Puppet::Parser::AST::Definition < Puppet::Parser::AST::Branch attr_accessor :name end + associates_doc + # The class name @name = :definition diff --git a/lib/puppet/parser/ast/else.rb b/lib/puppet/parser/ast/else.rb index affac625d..70e80b4ee 100644 --- a/lib/puppet/parser/ast/else.rb +++ b/lib/puppet/parser/ast/else.rb @@ -4,6 +4,9 @@ class Puppet::Parser::AST # A separate ElseIf statement; can function as an 'else' if there's no # test. class Else < AST::Branch + + associates_doc + attr_accessor :statements def each diff --git a/lib/puppet/parser/ast/function.rb b/lib/puppet/parser/ast/function.rb index eb36fa906..192940a7a 100644 --- a/lib/puppet/parser/ast/function.rb +++ b/lib/puppet/parser/ast/function.rb @@ -3,6 +3,9 @@ require 'puppet/parser/ast/branch' class Puppet::Parser::AST # An AST object to call a function. class Function < AST::Branch + + associates_doc + attr_accessor :name, :arguments @settor = true diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 4f5c4797c..23d9a00e5 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -4,6 +4,9 @@ require 'puppet/parser/ast/definition' # in that each class is a singleton -- only one will exist for a given # node. class Puppet::Parser::AST::HostClass < Puppet::Parser::AST::Definition + + associates_doc + @name = :class # Are we a child of the passed class? Do a recursive search up our diff --git a/lib/puppet/parser/ast/ifstatement.rb b/lib/puppet/parser/ast/ifstatement.rb index afa2cd572..d216b7c65 100644 --- a/lib/puppet/parser/ast/ifstatement.rb +++ b/lib/puppet/parser/ast/ifstatement.rb @@ -3,6 +3,9 @@ require 'puppet/parser/ast/branch' class Puppet::Parser::AST # A basic 'if/elsif/else' statement. class IfStatement < AST::Branch + + associates_doc + attr_accessor :test, :else, :statements def each diff --git a/lib/puppet/parser/ast/node.rb b/lib/puppet/parser/ast/node.rb index 2bf6c1882..442518f44 100644 --- a/lib/puppet/parser/ast/node.rb +++ b/lib/puppet/parser/ast/node.rb @@ -3,6 +3,9 @@ require 'puppet/parser/ast/hostclass' # The specific code associated with a host. Nodes are annoyingly unlike # other objects. That's just the way it is, at least for now. class Puppet::Parser::AST::Node < Puppet::Parser::AST::HostClass + + associates_doc + @name = :node def initialize(options) diff --git a/lib/puppet/parser/ast/resource.rb b/lib/puppet/parser/ast/resource.rb index 8a60522a3..1a07fc585 100644 --- a/lib/puppet/parser/ast/resource.rb +++ b/lib/puppet/parser/ast/resource.rb @@ -4,6 +4,9 @@ require 'puppet/parser/ast/resource_reference' # builtin type. class Puppet::Parser::AST class Resource < AST::ResourceReference + + associates_doc + attr_accessor :title, :type, :exported, :virtual attr_reader :params diff --git a/lib/puppet/parser/ast/resource_defaults.rb b/lib/puppet/parser/ast/resource_defaults.rb index 4856f0594..4919817fb 100644 --- a/lib/puppet/parser/ast/resource_defaults.rb +++ b/lib/puppet/parser/ast/resource_defaults.rb @@ -6,6 +6,8 @@ class Puppet::Parser::AST class ResourceDefaults < AST::Branch attr_accessor :type, :params + associates_doc + # As opposed to ResourceDef, this stores each default for the given # object type. def evaluate(scope) diff --git a/lib/puppet/parser/ast/resource_override.rb b/lib/puppet/parser/ast/resource_override.rb index 8380dcd00..5c4a2410f 100644 --- a/lib/puppet/parser/ast/resource_override.rb +++ b/lib/puppet/parser/ast/resource_override.rb @@ -4,6 +4,9 @@ class Puppet::Parser::AST # Set a parameter on a resource specification created somewhere else in the # configuration. The object is responsible for verifying that this is allowed. class ResourceOverride < Resource + + associates_doc + attr_accessor :object attr_reader :params diff --git a/lib/puppet/parser/ast/vardef.rb b/lib/puppet/parser/ast/vardef.rb index a3094ac6e..2d5f623f7 100644 --- a/lib/puppet/parser/ast/vardef.rb +++ b/lib/puppet/parser/ast/vardef.rb @@ -3,6 +3,9 @@ require 'puppet/parser/ast/branch' class Puppet::Parser::AST # Define a variable. Stores the value in the current scope. class VarDef < AST::Branch + + associates_doc + attr_accessor :name, :value, :append @settor = true diff --git a/lib/puppet/parser/grammar.ra b/lib/puppet/parser/grammar.ra index 23c2934b9..67303ab46 100644 --- a/lib/puppet/parser/grammar.ra +++ b/lib/puppet/parser/grammar.ra @@ -130,6 +130,7 @@ namestring: name } resource: classname LBRACE resourceinstances endsemi RBRACE { + @lexer.commentpop array = val[2] if array.instance_of?(AST::ResourceInstance) array = [array] @@ -158,6 +159,7 @@ resource: classname LBRACE resourceinstances endsemi RBRACE { # Override a value set elsewhere in the configuration. resourceoverride: resourceref LBRACE anyparams endcomma RBRACE { + @lexer.commentpop result = ast AST::ResourceOverride, :object => val[0], :params => val[2] } @@ -198,7 +200,7 @@ collection: classref collectrhand { Puppet.warning addcontext("Collection names must now be capitalized") end type = val[0].downcase - args = {:type => type} + args = {:type => type } if val[1].is_a?(AST::CollExpr) args[:query] = val[1] @@ -410,6 +412,7 @@ resourceref: NAME LBRACK rvalues RBRACK { } ifstatement: IF expression LBRACE statements RBRACE else { + @lexer.commentpop args = { :test => val[1], :statements => val[3] @@ -422,6 +425,7 @@ ifstatement: IF expression LBRACE statements RBRACE else { result = ast AST::IfStatement, args } | IF expression LBRACE RBRACE else { + @lexer.commentpop args = { :test => val[1], :statements => ast(AST::Nop) @@ -436,9 +440,11 @@ ifstatement: IF expression LBRACE statements RBRACE else { else: # nothing | ELSE LBRACE statements RBRACE { + @lexer.commentpop result = ast AST::Else, :statements => val[2] } | ELSE LBRACE RBRACE { + @lexer.commentpop result = ast AST::Else, :statements => ast(AST::Nop) } @@ -508,6 +514,7 @@ expression: rvalue } casestatement: CASE rvalue LBRACE caseopts RBRACE { + @lexer.commentpop options = val[3] unless options.instance_of?(AST::ASTArray) options = ast AST::ASTArray, :children => [val[3]] @@ -526,8 +533,10 @@ caseopts: caseopt } caseopt: casevalues COLON LBRACE statements RBRACE { + @lexer.commentpop result = ast AST::CaseOpt, :value => val[0], :statements => val[3] } | casevalues COLON LBRACE RBRACE { + @lexer.commentpop result = ast(AST::CaseOpt, :value => val[0], :statements => ast(AST::ASTArray) @@ -549,7 +558,10 @@ selector: selectlhand QMARK svalues { } svalues: selectval - | LBRACE sintvalues endcomma RBRACE { result = val[1] } + | LBRACE sintvalues endcomma RBRACE { + @lexer.commentpop + result = val[1] +} sintvalues: selectval | sintvalues comma selectval { @@ -593,12 +605,14 @@ import: IMPORT qtexts { # Disable definition inheritance for now. 8/27/06, luke #definition: DEFINE NAME argumentlist parent LBRACE statements RBRACE { definition: DEFINE classname argumentlist LBRACE statements RBRACE { + @lexer.commentpop newdefine classname(val[1]), :arguments => val[2], :code => val[4] @lexer.indefine = false result = nil #} | DEFINE NAME argumentlist parent LBRACE RBRACE { } | DEFINE classname argumentlist LBRACE RBRACE { + @lexer.commentpop newdefine classname(val[1]), :arguments => val[2] @lexer.indefine = false result = nil @@ -606,11 +620,13 @@ definition: DEFINE classname argumentlist LBRACE statements RBRACE { #hostclass: CLASS NAME argumentlist parent LBRACE statements RBRACE { hostclass: CLASS classname classparent LBRACE statements RBRACE { + @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop newclass classname(val[1]), :code => val[4], :parent => val[2] result = nil } | CLASS classname classparent LBRACE RBRACE { + @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop newclass classname(val[1]), :parent => val[2] @@ -618,9 +634,11 @@ hostclass: CLASS classname classparent LBRACE statements RBRACE { } nodedef: NODE hostnames nodeparent LBRACE statements RBRACE { + @lexer.commentpop newnode val[1], :parent => val[2], :code => val[4] result = nil } | NODE hostnames nodeparent LBRACE RBRACE { + @lexer.commentpop newnode val[1], :parent => val[2] result = nil } diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb index dd6c29d9f..69a46d0c1 100644 --- a/lib/puppet/parser/lexer.rb +++ b/lib/puppet/parser/lexer.rb @@ -17,7 +17,7 @@ class Puppet::Parser::Lexer # Our base token class. class Token - attr_accessor :regex, :name, :string, :skip, :incr_line, :skip_text + attr_accessor :regex, :name, :string, :skip, :incr_line, :skip_text, :accumulate def initialize(regex, name) if regex.is_a?(String) @@ -28,8 +28,10 @@ class Puppet::Parser::Lexer end end - def skip? - self.skip + %w{skip accumulate}.each do |method| + define_method(method+"?") do + self.send(method) + end end def to_s @@ -155,11 +157,16 @@ class Puppet::Parser::Lexer [string_token, value] end - TOKENS.add_token :COMMENT, %r{#.*}, :skip => true + TOKENS.add_token :COMMENT, %r{#.*}, :accumulate => true, :skip => true do |lexer,value| + value.sub!(/# ?/,'') + [self, value] + end - TOKENS.add_token :MLCOMMENT, %r{/\*(.*?)\*/}m do |lexer, value| + TOKENS.add_token :MLCOMMENT, %r{/\*(.*?)\*/}m, :accumulate => true, :skip => true do |lexer, value| lexer.line += value.count("\n") - [nil,nil] + value.sub!(/^\/\* ?/,'') + value.sub!(/ ?\*\/$/,'') + [self,value] end TOKENS.add_token :RETURN, "\n", :skip => true, :incr_line => true, :skip_text => true @@ -325,6 +332,7 @@ class Puppet::Parser::Lexer @namestack = [] @indefine = false @expected = [] + @commentstack = [''] end # Make any necessary changes to the token and/or value. @@ -333,12 +341,18 @@ class Puppet::Parser::Lexer skip() if token.skip_text - return if token.skip + return if token.skip and not token.accumulate? token, value = token.convert(self, value) if token.respond_to?(:convert) return unless token + if token.accumulate? + @commentstack.last << value + "\n" + end + + return if token.skip + return token, value end @@ -389,6 +403,18 @@ class Puppet::Parser::Lexer raise "Could not match '%s'" % nword end + if matched_token.name == :RETURN + # this matches a blank line + if @last_return + # eat the previously accumulated comments + getcomment + end + # since :RETURN skips, we won't survive to munge_token + @last_return = true + else + @last_return = false + end + final_token, value = munge_token(matched_token, value) next unless final_token @@ -399,6 +425,10 @@ class Puppet::Parser::Lexer @expected.pop end + if final_token.name == :LBRACE + commentpush + end + yield [final_token.name, value] if @previous_token @@ -414,7 +444,6 @@ class Puppet::Parser::Lexer @indefine = value end end - @previous_token = final_token skip() end @@ -453,4 +482,19 @@ class Puppet::Parser::Lexer def string=(string) @scanner = StringScanner.new(string) end + + # returns the content of the currently accumulated content cache + def commentpop + return @commentstack.pop + end + + def getcomment + comment = @commentstack.pop + @commentstack.push('') + return comment + end + + def commentpush + @commentstack.push('') + end end diff --git a/lib/puppet/parser/parser.rb b/lib/puppet/parser/parser.rb index 713f93eb0..60a849cb6 100644 --- a/lib/puppet/parser/parser.rb +++ b/lib/puppet/parser/parser.rb @@ -29,7 +29,7 @@ module Puppet class Parser < Racc::Parser -module_eval <<'..end grammar.ra modeval..id5cb4445525', 'grammar.ra', 741 +module_eval <<'..end grammar.ra modeval..id987bcfd032', 'grammar.ra', 759 # It got too annoying having code in a file that needs to be compiled. require 'puppet/parser/parser_support' @@ -41,7 +41,7 @@ require 'puppet/parser/parser_support' # $Id$ -..end grammar.ra modeval..id5cb4445525 +..end grammar.ra modeval..id987bcfd032 ##### racc 1.4.5 generates ### @@ -1103,8 +1103,9 @@ module_eval <<'.,.,', 'grammar.ra', 130 end .,., -module_eval <<'.,.,', 'grammar.ra', 151 +module_eval <<'.,.,', 'grammar.ra', 152 def _reduce_34( val, _values, result ) + @lexer.commentpop array = val[2] if array.instance_of?(AST::ResourceInstance) array = [array] @@ -1127,7 +1128,7 @@ module_eval <<'.,.,', 'grammar.ra', 151 end .,., -module_eval <<'.,.,', 'grammar.ra', 154 +module_eval <<'.,.,', 'grammar.ra', 155 def _reduce_35( val, _values, result ) # This is a deprecated syntax. error "All resource specifications require names" @@ -1135,7 +1136,7 @@ module_eval <<'.,.,', 'grammar.ra', 154 end .,., -module_eval <<'.,.,', 'grammar.ra', 157 +module_eval <<'.,.,', 'grammar.ra', 158 def _reduce_36( val, _values, result ) # a defaults setting for a type result = ast(AST::ResourceDefaults, :type => val[0], :params => val[2]) @@ -1143,14 +1144,15 @@ module_eval <<'.,.,', 'grammar.ra', 157 end .,., -module_eval <<'.,.,', 'grammar.ra', 162 +module_eval <<'.,.,', 'grammar.ra', 164 def _reduce_37( val, _values, result ) + @lexer.commentpop result = ast AST::ResourceOverride, :object => val[0], :params => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 189 +module_eval <<'.,.,', 'grammar.ra', 191 def _reduce_38( val, _values, result ) type = val[0] @@ -1178,27 +1180,27 @@ module_eval <<'.,.,', 'grammar.ra', 189 end .,., -module_eval <<'.,.,', 'grammar.ra', 190 +module_eval <<'.,.,', 'grammar.ra', 192 def _reduce_39( val, _values, result ) result = :virtual result end .,., -module_eval <<'.,.,', 'grammar.ra', 191 +module_eval <<'.,.,', 'grammar.ra', 193 def _reduce_40( val, _values, result ) result = :exported result end .,., -module_eval <<'.,.,', 'grammar.ra', 214 +module_eval <<'.,.,', 'grammar.ra', 216 def _reduce_41( val, _values, result ) if val[0] =~ /^[a-z]/ Puppet.warning addcontext("Collection names must now be capitalized") end type = val[0].downcase - args = {:type => type} + args = {:type => type } if val[1].is_a?(AST::CollExpr) args[:query] = val[1] @@ -1215,7 +1217,7 @@ module_eval <<'.,.,', 'grammar.ra', 214 end .,., -module_eval <<'.,.,', 'grammar.ra', 224 +module_eval <<'.,.,', 'grammar.ra', 226 def _reduce_42( val, _values, result ) if val[1] result = val[1] @@ -1227,7 +1229,7 @@ module_eval <<'.,.,', 'grammar.ra', 224 end .,., -module_eval <<'.,.,', 'grammar.ra', 232 +module_eval <<'.,.,', 'grammar.ra', 234 def _reduce_43( val, _values, result ) if val[1] result = val[1] @@ -1243,7 +1245,7 @@ module_eval <<'.,.,', 'grammar.ra', 232 # reduce 45 omitted -module_eval <<'.,.,', 'grammar.ra', 240 +module_eval <<'.,.,', 'grammar.ra', 242 def _reduce_46( val, _values, result ) result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] result @@ -1252,7 +1254,7 @@ module_eval <<'.,.,', 'grammar.ra', 240 # reduce 47 omitted -module_eval <<'.,.,', 'grammar.ra', 246 +module_eval <<'.,.,', 'grammar.ra', 248 def _reduce_48( val, _values, result ) result = val[1] result.parens = true @@ -1264,7 +1266,7 @@ module_eval <<'.,.,', 'grammar.ra', 246 # reduce 50 omitted -module_eval <<'.,.,', 'grammar.ra', 254 +module_eval <<'.,.,', 'grammar.ra', 256 def _reduce_51( val, _values, result ) result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] #result = ast AST::CollExpr @@ -1273,7 +1275,7 @@ module_eval <<'.,.,', 'grammar.ra', 254 end .,., -module_eval <<'.,.,', 'grammar.ra', 259 +module_eval <<'.,.,', 'grammar.ra', 261 def _reduce_52( val, _values, result ) result = ast AST::CollExpr, :test1 => val[0], :oper => val[1], :test2 => val[2] #result = ast AST::CollExpr @@ -1286,7 +1288,7 @@ module_eval <<'.,.,', 'grammar.ra', 259 # reduce 54 omitted -module_eval <<'.,.,', 'grammar.ra', 266 +module_eval <<'.,.,', 'grammar.ra', 268 def _reduce_55( val, _values, result ) result = ast AST::ResourceInstance, :children => [val[0],val[2]] result @@ -1295,7 +1297,7 @@ module_eval <<'.,.,', 'grammar.ra', 266 # reduce 56 omitted -module_eval <<'.,.,', 'grammar.ra', 276 +module_eval <<'.,.,', 'grammar.ra', 278 def _reduce_57( val, _values, result ) if val[0].instance_of?(AST::ResourceInstance) result = ast AST::ASTArray, :children => [val[0],val[2]] @@ -1311,21 +1313,21 @@ module_eval <<'.,.,', 'grammar.ra', 276 # reduce 59 omitted -module_eval <<'.,.,', 'grammar.ra', 283 +module_eval <<'.,.,', 'grammar.ra', 285 def _reduce_60( val, _values, result ) result = ast AST::Undef, :value => :undef result end .,., -module_eval <<'.,.,', 'grammar.ra', 287 +module_eval <<'.,.,', 'grammar.ra', 289 def _reduce_61( val, _values, result ) result = ast AST::Name, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 291 +module_eval <<'.,.,', 'grammar.ra', 293 def _reduce_62( val, _values, result ) result = ast AST::Type, :value => val[0] result @@ -1344,7 +1346,7 @@ module_eval <<'.,.,', 'grammar.ra', 291 # reduce 68 omitted -module_eval <<'.,.,', 'grammar.ra', 307 +module_eval <<'.,.,', 'grammar.ra', 309 def _reduce_69( val, _values, result ) if val[0] =~ /::/ raise Puppet::ParseError, "Cannot assign to variables in other namespaces" @@ -1356,7 +1358,7 @@ module_eval <<'.,.,', 'grammar.ra', 307 end .,., -module_eval <<'.,.,', 'grammar.ra', 312 +module_eval <<'.,.,', 'grammar.ra', 314 def _reduce_70( val, _values, result ) variable = ast AST::Name, :value => val[0] result = ast AST::VarDef, :name => variable, :value => val[2], :append => true @@ -1364,21 +1366,21 @@ module_eval <<'.,.,', 'grammar.ra', 312 end .,., -module_eval <<'.,.,', 'grammar.ra', 317 +module_eval <<'.,.,', 'grammar.ra', 319 def _reduce_71( val, _values, result ) result = ast AST::ASTArray result end .,., -module_eval <<'.,.,', 'grammar.ra', 317 +module_eval <<'.,.,', 'grammar.ra', 319 def _reduce_72( val, _values, result ) result = val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 326 +module_eval <<'.,.,', 'grammar.ra', 328 def _reduce_73( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) @@ -1390,14 +1392,14 @@ module_eval <<'.,.,', 'grammar.ra', 326 end .,., -module_eval <<'.,.,', 'grammar.ra', 330 +module_eval <<'.,.,', 'grammar.ra', 332 def _reduce_74( val, _values, result ) result = ast AST::ResourceParam, :param => val[0], :value => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 335 +module_eval <<'.,.,', 'grammar.ra', 337 def _reduce_75( val, _values, result ) result = ast AST::ResourceParam, :param => val[0], :value => val[2], :add => true @@ -1409,21 +1411,21 @@ module_eval <<'.,.,', 'grammar.ra', 335 # reduce 77 omitted -module_eval <<'.,.,', 'grammar.ra', 343 +module_eval <<'.,.,', 'grammar.ra', 345 def _reduce_78( val, _values, result ) result = ast AST::ASTArray result end .,., -module_eval <<'.,.,', 'grammar.ra', 343 +module_eval <<'.,.,', 'grammar.ra', 345 def _reduce_79( val, _values, result ) result = val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 352 +module_eval <<'.,.,', 'grammar.ra', 354 def _reduce_80( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) @@ -1437,7 +1439,7 @@ module_eval <<'.,.,', 'grammar.ra', 352 # reduce 81 omitted -module_eval <<'.,.,', 'grammar.ra', 361 +module_eval <<'.,.,', 'grammar.ra', 363 def _reduce_82( val, _values, result ) if val[0].instance_of?(AST::ASTArray) result = val[0].push(val[2]) @@ -1480,7 +1482,7 @@ module_eval <<'.,.,', 'grammar.ra', 361 # reduce 98 omitted -module_eval <<'.,.,', 'grammar.ra', 388 +module_eval <<'.,.,', 'grammar.ra', 390 def _reduce_99( val, _values, result ) args = aryfy(val[2]) result = ast AST::Function, @@ -1491,7 +1493,7 @@ module_eval <<'.,.,', 'grammar.ra', 388 end .,., -module_eval <<'.,.,', 'grammar.ra', 393 +module_eval <<'.,.,', 'grammar.ra', 395 def _reduce_100( val, _values, result ) result = ast AST::Function, :name => val[0], @@ -1501,28 +1503,28 @@ module_eval <<'.,.,', 'grammar.ra', 393 end .,., -module_eval <<'.,.,', 'grammar.ra', 397 +module_eval <<'.,.,', 'grammar.ra', 399 def _reduce_101( val, _values, result ) result = ast AST::String, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 399 +module_eval <<'.,.,', 'grammar.ra', 401 def _reduce_102( val, _values, result ) result = ast AST::FlatString, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 403 +module_eval <<'.,.,', 'grammar.ra', 405 def _reduce_103( val, _values, result ) result = ast AST::Boolean, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 408 +module_eval <<'.,.,', 'grammar.ra', 410 def _reduce_104( val, _values, result ) Puppet.warning addcontext("Deprecation notice: Resource references should now be capitalized") result = ast AST::ResourceReference, :type => val[0], :title => val[2] @@ -1530,15 +1532,16 @@ module_eval <<'.,.,', 'grammar.ra', 408 end .,., -module_eval <<'.,.,', 'grammar.ra', 410 +module_eval <<'.,.,', 'grammar.ra', 412 def _reduce_105( val, _values, result ) result = ast AST::ResourceReference, :type => val[0], :title => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 423 +module_eval <<'.,.,', 'grammar.ra', 426 def _reduce_106( val, _values, result ) + @lexer.commentpop args = { :test => val[1], :statements => val[3] @@ -1553,8 +1556,9 @@ module_eval <<'.,.,', 'grammar.ra', 423 end .,., -module_eval <<'.,.,', 'grammar.ra', 435 +module_eval <<'.,.,', 'grammar.ra', 439 def _reduce_107( val, _values, result ) + @lexer.commentpop args = { :test => val[1], :statements => ast(AST::Nop) @@ -1571,15 +1575,17 @@ module_eval <<'.,.,', 'grammar.ra', 435 # reduce 108 omitted -module_eval <<'.,.,', 'grammar.ra', 440 +module_eval <<'.,.,', 'grammar.ra', 445 def _reduce_109( val, _values, result ) + @lexer.commentpop result = ast AST::Else, :statements => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 443 +module_eval <<'.,.,', 'grammar.ra', 449 def _reduce_110( val, _values, result ) + @lexer.commentpop result = ast AST::Else, :statements => ast(AST::Nop) result end @@ -1587,127 +1593,128 @@ module_eval <<'.,.,', 'grammar.ra', 443 # reduce 111 omitted -module_eval <<'.,.,', 'grammar.ra', 460 +module_eval <<'.,.,', 'grammar.ra', 466 def _reduce_112( val, _values, result ) result = ast AST::ArithmeticOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 463 +module_eval <<'.,.,', 'grammar.ra', 469 def _reduce_113( val, _values, result ) result = ast AST::ArithmeticOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 466 +module_eval <<'.,.,', 'grammar.ra', 472 def _reduce_114( val, _values, result ) result = ast AST::ArithmeticOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 469 +module_eval <<'.,.,', 'grammar.ra', 475 def _reduce_115( val, _values, result ) result = ast AST::ArithmeticOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 472 +module_eval <<'.,.,', 'grammar.ra', 478 def _reduce_116( val, _values, result ) result = ast AST::ArithmeticOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 475 +module_eval <<'.,.,', 'grammar.ra', 481 def _reduce_117( val, _values, result ) result = ast AST::ArithmeticOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 478 +module_eval <<'.,.,', 'grammar.ra', 484 def _reduce_118( val, _values, result ) result = ast AST::Minus, :value => val[1] result end .,., -module_eval <<'.,.,', 'grammar.ra', 481 +module_eval <<'.,.,', 'grammar.ra', 487 def _reduce_119( val, _values, result ) result = ast AST::ComparisonOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 484 +module_eval <<'.,.,', 'grammar.ra', 490 def _reduce_120( val, _values, result ) result = ast AST::ComparisonOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 487 +module_eval <<'.,.,', 'grammar.ra', 493 def _reduce_121( val, _values, result ) result = ast AST::ComparisonOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 490 +module_eval <<'.,.,', 'grammar.ra', 496 def _reduce_122( val, _values, result ) result = ast AST::ComparisonOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 493 +module_eval <<'.,.,', 'grammar.ra', 499 def _reduce_123( val, _values, result ) result = ast AST::ComparisonOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 496 +module_eval <<'.,.,', 'grammar.ra', 502 def _reduce_124( val, _values, result ) result = ast AST::ComparisonOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 499 +module_eval <<'.,.,', 'grammar.ra', 505 def _reduce_125( val, _values, result ) result = ast AST::Not, :value => val[1] result end .,., -module_eval <<'.,.,', 'grammar.ra', 502 +module_eval <<'.,.,', 'grammar.ra', 508 def _reduce_126( val, _values, result ) result = ast AST::BooleanOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 505 +module_eval <<'.,.,', 'grammar.ra', 511 def _reduce_127( val, _values, result ) result = ast AST::BooleanOperator, :operator => val[1], :lval => val[0], :rval => val[2] result end .,., -module_eval <<'.,.,', 'grammar.ra', 508 +module_eval <<'.,.,', 'grammar.ra', 514 def _reduce_128( val, _values, result ) result = val[1] result end .,., -module_eval <<'.,.,', 'grammar.ra', 516 +module_eval <<'.,.,', 'grammar.ra', 523 def _reduce_129( val, _values, result ) + @lexer.commentpop options = val[3] unless options.instance_of?(AST::ASTArray) options = ast AST::ASTArray, :children => [val[3]] @@ -1719,7 +1726,7 @@ module_eval <<'.,.,', 'grammar.ra', 516 # reduce 130 omitted -module_eval <<'.,.,', 'grammar.ra', 526 +module_eval <<'.,.,', 'grammar.ra', 533 def _reduce_131( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push val[1] @@ -1731,15 +1738,17 @@ module_eval <<'.,.,', 'grammar.ra', 526 end .,., -module_eval <<'.,.,', 'grammar.ra', 530 +module_eval <<'.,.,', 'grammar.ra', 538 def _reduce_132( val, _values, result ) + @lexer.commentpop result = ast AST::CaseOpt, :value => val[0], :statements => val[3] result end .,., -module_eval <<'.,.,', 'grammar.ra', 535 +module_eval <<'.,.,', 'grammar.ra', 544 def _reduce_133( val, _values, result ) + @lexer.commentpop result = ast(AST::CaseOpt, :value => val[0], :statements => ast(AST::ASTArray) @@ -1750,7 +1759,7 @@ module_eval <<'.,.,', 'grammar.ra', 535 # reduce 134 omitted -module_eval <<'.,.,', 'grammar.ra', 545 +module_eval <<'.,.,', 'grammar.ra', 554 def _reduce_135( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) @@ -1762,7 +1771,7 @@ module_eval <<'.,.,', 'grammar.ra', 545 end .,., -module_eval <<'.,.,', 'grammar.ra', 549 +module_eval <<'.,.,', 'grammar.ra', 558 def _reduce_136( val, _values, result ) result = ast AST::Selector, :param => val[0], :values => val[2] result @@ -1771,16 +1780,17 @@ module_eval <<'.,.,', 'grammar.ra', 549 # reduce 137 omitted -module_eval <<'.,.,', 'grammar.ra', 551 +module_eval <<'.,.,', 'grammar.ra', 564 def _reduce_138( val, _values, result ) - result = val[1] + @lexer.commentpop + result = val[1] result end .,., # reduce 139 omitted -module_eval <<'.,.,', 'grammar.ra', 562 +module_eval <<'.,.,', 'grammar.ra', 574 def _reduce_140( val, _values, result ) if val[0].instance_of?(AST::ASTArray) val[0].push(val[2]) @@ -1792,7 +1802,7 @@ module_eval <<'.,.,', 'grammar.ra', 562 end .,., -module_eval <<'.,.,', 'grammar.ra', 566 +module_eval <<'.,.,', 'grammar.ra', 578 def _reduce_141( val, _values, result ) result = ast AST::ResourceParam, :param => val[0], :value => val[2] result @@ -1813,28 +1823,28 @@ module_eval <<'.,.,', 'grammar.ra', 566 # reduce 148 omitted -module_eval <<'.,.,', 'grammar.ra', 577 +module_eval <<'.,.,', 'grammar.ra', 589 def _reduce_149( val, _values, result ) result = ast AST::Default, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 579 +module_eval <<'.,.,', 'grammar.ra', 591 def _reduce_150( val, _values, result ) result = [val[0].value] result end .,., -module_eval <<'.,.,', 'grammar.ra', 583 +module_eval <<'.,.,', 'grammar.ra', 595 def _reduce_151( val, _values, result ) results = val[0] << val[2].value result end .,., -module_eval <<'.,.,', 'grammar.ra', 591 +module_eval <<'.,.,', 'grammar.ra', 603 def _reduce_152( val, _values, result ) val[1].each do |file| import(file) @@ -1845,8 +1855,9 @@ module_eval <<'.,.,', 'grammar.ra', 591 end .,., -module_eval <<'.,.,', 'grammar.ra', 601 +module_eval <<'.,.,', 'grammar.ra', 614 def _reduce_153( val, _values, result ) + @lexer.commentpop newdefine classname(val[1]), :arguments => val[2], :code => val[4] @lexer.indefine = false result = nil @@ -1856,8 +1867,9 @@ module_eval <<'.,.,', 'grammar.ra', 601 end .,., -module_eval <<'.,.,', 'grammar.ra', 605 +module_eval <<'.,.,', 'grammar.ra', 619 def _reduce_154( val, _values, result ) + @lexer.commentpop newdefine classname(val[1]), :arguments => val[2] @lexer.indefine = false result = nil @@ -1865,8 +1877,9 @@ module_eval <<'.,.,', 'grammar.ra', 605 end .,., -module_eval <<'.,.,', 'grammar.ra', 613 +module_eval <<'.,.,', 'grammar.ra', 628 def _reduce_155( val, _values, result ) + @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop newclass classname(val[1]), :code => val[4], :parent => val[2] @@ -1875,8 +1888,9 @@ module_eval <<'.,.,', 'grammar.ra', 613 end .,., -module_eval <<'.,.,', 'grammar.ra', 618 +module_eval <<'.,.,', 'grammar.ra', 634 def _reduce_156( val, _values, result ) + @lexer.commentpop # Our class gets defined in the parent namespace, not our own. @lexer.namepop newclass classname(val[1]), :parent => val[2] @@ -1885,16 +1899,18 @@ module_eval <<'.,.,', 'grammar.ra', 618 end .,., -module_eval <<'.,.,', 'grammar.ra', 623 +module_eval <<'.,.,', 'grammar.ra', 640 def _reduce_157( val, _values, result ) + @lexer.commentpop newnode val[1], :parent => val[2], :code => val[4] result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 626 +module_eval <<'.,.,', 'grammar.ra', 644 def _reduce_158( val, _values, result ) + @lexer.commentpop newnode val[1], :parent => val[2] result = nil result @@ -1909,7 +1925,7 @@ module_eval <<'.,.,', 'grammar.ra', 626 # reduce 162 omitted -module_eval <<'.,.,', 'grammar.ra', 640 +module_eval <<'.,.,', 'grammar.ra', 658 def _reduce_163( val, _values, result ) result = val[0] result = [result] unless result.is_a?(Array) @@ -1926,14 +1942,14 @@ module_eval <<'.,.,', 'grammar.ra', 640 # reduce 167 omitted -module_eval <<'.,.,', 'grammar.ra', 649 +module_eval <<'.,.,', 'grammar.ra', 667 def _reduce_168( val, _values, result ) result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 653 +module_eval <<'.,.,', 'grammar.ra', 671 def _reduce_169( val, _values, result ) result = ast AST::ASTArray, :children => [] result @@ -1942,14 +1958,14 @@ module_eval <<'.,.,', 'grammar.ra', 653 # reduce 170 omitted -module_eval <<'.,.,', 'grammar.ra', 658 +module_eval <<'.,.,', 'grammar.ra', 676 def _reduce_171( val, _values, result ) result = nil result end .,., -module_eval <<'.,.,', 'grammar.ra', 662 +module_eval <<'.,.,', 'grammar.ra', 680 def _reduce_172( val, _values, result ) result = val[1] result = [result] unless result[0].is_a?(Array) @@ -1959,7 +1975,7 @@ module_eval <<'.,.,', 'grammar.ra', 662 # reduce 173 omitted -module_eval <<'.,.,', 'grammar.ra', 669 +module_eval <<'.,.,', 'grammar.ra', 687 def _reduce_174( val, _values, result ) result = val[0] result = [result] unless result[0].is_a?(Array) @@ -1968,7 +1984,7 @@ module_eval <<'.,.,', 'grammar.ra', 669 end .,., -module_eval <<'.,.,', 'grammar.ra', 674 +module_eval <<'.,.,', 'grammar.ra', 692 def _reduce_175( val, _values, result ) Puppet.warning addcontext("Deprecation notice: must now include '$' in prototype") result = [val[0], val[2]] @@ -1976,7 +1992,7 @@ module_eval <<'.,.,', 'grammar.ra', 674 end .,., -module_eval <<'.,.,', 'grammar.ra', 678 +module_eval <<'.,.,', 'grammar.ra', 696 def _reduce_176( val, _values, result ) Puppet.warning addcontext("Deprecation notice: must now include '$' in prototype") result = [val[0]] @@ -1984,14 +2000,14 @@ module_eval <<'.,.,', 'grammar.ra', 678 end .,., -module_eval <<'.,.,', 'grammar.ra', 680 +module_eval <<'.,.,', 'grammar.ra', 698 def _reduce_177( val, _values, result ) result = [val[0], val[2]] result end .,., -module_eval <<'.,.,', 'grammar.ra', 682 +module_eval <<'.,.,', 'grammar.ra', 700 def _reduce_178( val, _values, result ) result = [val[0]] result @@ -2000,7 +2016,7 @@ module_eval <<'.,.,', 'grammar.ra', 682 # reduce 179 omitted -module_eval <<'.,.,', 'grammar.ra', 687 +module_eval <<'.,.,', 'grammar.ra', 705 def _reduce_180( val, _values, result ) result = val[1] result @@ -2009,7 +2025,7 @@ module_eval <<'.,.,', 'grammar.ra', 687 # reduce 181 omitted -module_eval <<'.,.,', 'grammar.ra', 692 +module_eval <<'.,.,', 'grammar.ra', 710 def _reduce_182( val, _values, result ) result = val[1] result @@ -2020,14 +2036,14 @@ module_eval <<'.,.,', 'grammar.ra', 692 # reduce 184 omitted -module_eval <<'.,.,', 'grammar.ra', 698 +module_eval <<'.,.,', 'grammar.ra', 716 def _reduce_185( val, _values, result ) result = ast AST::Variable, :value => val[0] result end .,., -module_eval <<'.,.,', 'grammar.ra', 706 +module_eval <<'.,.,', 'grammar.ra', 724 def _reduce_186( val, _values, result ) if val[1].instance_of?(AST::ASTArray) result = val[1] @@ -2038,7 +2054,7 @@ module_eval <<'.,.,', 'grammar.ra', 706 end .,., -module_eval <<'.,.,', 'grammar.ra', 713 +module_eval <<'.,.,', 'grammar.ra', 731 def _reduce_187( val, _values, result ) if val[1].instance_of?(AST::ASTArray) result = val[1] @@ -2049,7 +2065,7 @@ module_eval <<'.,.,', 'grammar.ra', 713 end .,., -module_eval <<'.,.,', 'grammar.ra', 715 +module_eval <<'.,.,', 'grammar.ra', 733 def _reduce_188( val, _values, result ) result = ast AST::ASTArray result @@ -2062,7 +2078,7 @@ module_eval <<'.,.,', 'grammar.ra', 715 # reduce 191 omitted -module_eval <<'.,.,', 'grammar.ra', 720 +module_eval <<'.,.,', 'grammar.ra', 738 def _reduce_192( val, _values, result ) result = nil result diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb index 1583973a7..d59093799 100644 --- a/lib/puppet/parser/parser_support.rb +++ b/lib/puppet/parser/parser_support.rb @@ -18,6 +18,7 @@ class Puppet::Parser::Parser attr_reader :version, :environment attr_accessor :files + attr_accessor :lexer # Add context to a message; useful for error messages and such. def addcontext(message, obj = nil) @@ -56,7 +57,9 @@ class Puppet::Parser::Parser end end - return klass.new(hash) + k = klass.new(hash) + k.doc = lexer.getcomment if !k.nil? and k.use_docs and k.doc.empty? + return k end # The fully qualifed name, with the full namespace. @@ -272,6 +275,7 @@ class Puppet::Parser::Parser end code = options[:code] parent = options[:parent] + doc = options[:doc] # If the class is already defined, then add code to it. if other = @astset.classes[name] @@ -304,6 +308,12 @@ class Puppet::Parser::Parser other.code ||= code end end + + if other.doc and doc + other.doc += doc + else + other.doc ||= doc + end else # Define it anew. # Note we're doing something somewhat weird here -- we're setting @@ -312,6 +322,8 @@ class Puppet::Parser::Parser args = {:namespace => name, :classname => name, :parser => self} args[:code] = code if code args[:parentclass] = parent if parent + args[:doc] = doc + @astset.classes[name] = ast AST::HostClass, args end @@ -336,7 +348,8 @@ class Puppet::Parser::Parser :arguments => options[:arguments], :code => options[:code], :parser => self, - :classname => name + :classname => name, + :doc => options[:doc] } [:code, :arguments].each do |param| @@ -350,6 +363,7 @@ class Puppet::Parser::Parser # table, not according to namespaces. def newnode(names, options = {}) names = [names] unless names.instance_of?(Array) + doc = lexer.getcomment names.collect do |name| name = name.to_s.downcase if other = @astset.nodes[name] @@ -358,7 +372,8 @@ class Puppet::Parser::Parser name = name.to_s if name.is_a?(Symbol) args = { :name => name, - :parser => self + :parser => self, + :doc => doc } if options[:code] args[:code] = options[:code] @@ -399,6 +414,7 @@ class Puppet::Parser::Parser self.string = string end begin + @yydebug = false main = yyparse(@lexer,:scan) rescue Racc::ParseError => except error = Puppet::ParseError.new(except) -- cgit From 2c05a0abcb55347c179e66bb0c9d23698e729046 Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Sat, 15 Nov 2008 13:22:26 +0100 Subject: Move function existance test to parser evaluation The aim is to let --parseonly succeeds even if the function is not (yet) present. This is usefull in commit-hooks and for the inline documentation generation system. Signed-off-by: Brice Figureau --- lib/puppet/parser/ast/function.rb | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) (limited to 'lib') diff --git a/lib/puppet/parser/ast/function.rb b/lib/puppet/parser/ast/function.rb index 192940a7a..fc3797f15 100644 --- a/lib/puppet/parser/ast/function.rb +++ b/lib/puppet/parser/ast/function.rb @@ -11,20 +11,9 @@ class Puppet::Parser::AST @settor = true def evaluate(scope) - # We don't need to evaluate the name, because it's plaintext - args = @arguments.safeevaluate(scope) - - return scope.send("function_" + @name, args) - end - - def initialize(hash) - @ftype = hash[:ftype] || :rvalue - hash.delete(:ftype) if hash.include? :ftype - - super(hash) # Make sure it's a defined function - unless @fname = Puppet::Parser::Functions.function(@name) + unless @fname raise Puppet::ParseError, "Unknown function %s" % @name end @@ -45,6 +34,21 @@ class Puppet::Parser::AST raise Puppet::DevError, "Invalid function type %s" % @ftype.inspect end + + + # We don't need to evaluate the name, because it's plaintext + args = @arguments.safeevaluate(scope) + + return scope.send("function_" + @name, args) + end + + def initialize(hash) + @ftype = hash[:ftype] || :rvalue + hash.delete(:ftype) if hash.include? :ftype + + super(hash) + + @fname = Puppet::Parser::Functions.function(@name) # Lastly, check the parity end end -- cgit From dc192b00dc2c44b6174cb4a84663e8ad4e561d3c Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Sat, 15 Nov 2008 13:21:00 +0100 Subject: Manifest documentation generation There is currently two type of documentation generation for manifests (module or modulepath): * RDoc HTML generation for modules and global manifests * console output for sole manifest Both version handles classes, defines, nodes, global variable assignements, and resources when --all is used. The usage is the following: For the rdoc variant: $ puppetdoc --mode rdoc --outputdir doc It uses the puppet.conf configuration file to get the modulepath and manifestdir settings. Those are overridable on the command line with --modulepath and --manifestdir. For the console output version: $ puppetdoc /path/to/manifests Signed-off-by: Brice Figureau --- lib/puppet/util/rdoc.rb | 85 ++ lib/puppet/util/rdoc/code_objects.rb | 219 ++++ .../util/rdoc/generators/puppet_generator.rb | 829 +++++++++++++++ .../util/rdoc/generators/template/puppet/puppet.rb | 1051 ++++++++++++++++++++ lib/puppet/util/rdoc/parser.rb | 437 ++++++++ 5 files changed, 2621 insertions(+) create mode 100644 lib/puppet/util/rdoc.rb create mode 100644 lib/puppet/util/rdoc/code_objects.rb create mode 100644 lib/puppet/util/rdoc/generators/puppet_generator.rb create mode 100644 lib/puppet/util/rdoc/generators/template/puppet/puppet.rb create mode 100644 lib/puppet/util/rdoc/parser.rb (limited to 'lib') diff --git a/lib/puppet/util/rdoc.rb b/lib/puppet/util/rdoc.rb new file mode 100644 index 000000000..b33e67c71 --- /dev/null +++ b/lib/puppet/util/rdoc.rb @@ -0,0 +1,85 @@ + +module Puppet::Util::RDoc + + module_function + + # launch a rdoc documenation process + # with the files/dir passed in +files+ + def rdoc(outputdir, files) + begin + Puppet[:ignoreimport] = true + + # then rdoc + require 'rdoc/rdoc' + + # load our parser + require 'puppet/util/rdoc/parser' + + r = RDoc::RDoc.new + RDoc::RDoc::GENERATORS["puppet"] = RDoc::RDoc::Generator.new("puppet/util/rdoc/generators/puppet_generator.rb", + "PuppetGenerator".intern, + "puppet") + # specify our own format & where to output + options = [ "--fmt", "puppet", + "--quiet", + "--op", outputdir ] + + options += files + + # launch the documentation process + r.document(options) + rescue RDoc::RDocError => e + raise Puppet::ParseError.new("RDoc error %s" % e) + end + end + + # launch a output to console manifest doc + def manifestdoc(files) + Puppet[:ignoreimport] = true + files.select { |f| FileTest.file?(f) }.each do |f| + parser = Puppet::Parser::Parser.new(:environment => Puppet[:environment]) + parser.file = f + ast = parser.parse + output(f, ast) + end + end + + # Ouputs to the console the documentation + # of a manifest + def output(file, ast) + astobj = [] + ast[:nodes].each do |name, k| + astobj << k if k.file == file + end + ast[:classes].each do |name, k| + astobj << k if k.file == file + end + ast[:definitions].each do |name, k| + astobj << k if k.file == file + end + astobj.sort! {|a,b| a.line <=> b.line }.each do |k| + output_astnode_doc(k) + end + end + + def output_astnode_doc(ast) + puts ast.doc if !ast.doc.nil? and !ast.doc.empty? + if Puppet.settings[:document_all] + # scan each underlying resources to produce documentation + code = ast.code.children if ast.code.is_a?(Puppet::Parser::AST::ASTArray) + code ||= ast.code + output_resource_doc(code) unless code.nil? + end + end + + def output_resource_doc(code) + code.sort { |a,b| a.line <=> b.line }.each do |stmt| + output_resource_doc(stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) + + if stmt.is_a?(Puppet::Parser::AST::Resource) + puts stmt.doc if !stmt.doc.nil? and !stmt.doc.empty? + end + end + end + +end \ No newline at end of file diff --git a/lib/puppet/util/rdoc/code_objects.rb b/lib/puppet/util/rdoc/code_objects.rb new file mode 100644 index 000000000..52df2193e --- /dev/null +++ b/lib/puppet/util/rdoc/code_objects.rb @@ -0,0 +1,219 @@ +require 'rdoc/code_objects' + +module RDoc + + # This modules contains various class that are used to hold information + # about the various Puppet language structures we found while parsing. + # + # Those will be mapped to their html counterparts which are defined in + # PuppetGenerator. + + # PuppetTopLevel is a top level (usually a .pp/.rb file) + class PuppetTopLevel < TopLevel + attr_accessor :module_name, :global + + # will contain all plugins + @@all_plugins = {} + + # contains all cutoms facts + @@all_facts = {} + + def initialize(toplevel) + super(toplevel.file_relative_name) + end + + def self.all_plugins + @@all_plugins.values + end + + def self.all_facts + @@all_facts.values + end + end + + # PuppetModule holds a Puppet Module + # This is mapped to an HTMLPuppetModule + # it leverage the RDoc (ruby) module infrastructure + class PuppetModule < NormalModule + attr_accessor :facts, :plugins + + def initialize(name,superclass=nil) + @facts = [] + @plugins = [] + super(name,superclass) + end + + def initialize_classes_and_modules + super + @nodes = {} + end + + def add_plugin(plugin) + add_to(@plugins, plugin) + end + + def add_fact(fact) + add_to(@facts, fact) + end + + def add_node(name,superclass) + cls = @nodes[name] + unless cls + cls = PuppetNode.new(name, superclass) + @nodes[name] = cls if !@done_documenting + cls.parent = self + cls.section = @current_section + end + cls + end + + def each_fact + @facts.each {|c| yield c} + end + + def each_plugin + @plugins.each {|c| yield c} + end + + def each_node + @nodes.each {|c| yield c} + end + + def nodes + @nodes.values + end + end + + # PuppetClass holds a puppet class + # It is mapped to a HTMLPuppetClass for display + # It leverages RDoc (ruby) Class + class PuppetClass < ClassModule + attr_accessor :resource_list + + def initialize(name, superclass) + super(name,superclass) + @resource_list = [] + end + + def add_resource(resource) + add_to(@resource_list, resource) + end + + def is_module? + false + end + end + + # PuppetNode holds a puppet node + # It is mapped to a HTMLPuppetNode for display + # A node is just a variation of a class + class PuppetNode < PuppetClass + def initialize(name, superclass) + super(name,superclass) + end + + def is_module? + false + end + end + + # Plugin holds a native puppet plugin (function,type...) + # It is mapped to a HTMLPuppetPlugin for display + class Plugin < Context + attr_accessor :name, :type + + def initialize(name, type) + super() + @name = name + @type = type + @comment = "" + end + + def <=>(other) + @name <=> other.name + end + + def full_name + @name + end + + def http_url(prefix) + path = full_name.split("::") + File.join(prefix, *path) + ".html" + end + + def is_fact? + false + end + + def to_s + res = self.class.name + ": " + @name + " (" + @type + ")\n" + res << @comment.to_s + res + end + end + + # Fact holds a custom fact + # It is mapped to a HTMLPuppetPlugin for display + class Fact < Context + attr_accessor :name, :confine + + def initialize(name, confine) + super() + @name = name + @confine = confine + @comment = "" + end + + def <=>(other) + @name <=> other.name + end + + def is_fact? + true + end + + def full_name + @name + end + + def to_s + res = self.class.name + ": " + @name + "\n" + res << @comment.to_s + res + end + end + + # PuppetResource holds a puppet resource + # It is mapped to a HTMLPuppetResource for display + # A resource is defined by its "normal" form Type[title] + class PuppetResource < CodeObject + attr_accessor :type, :title, :params + + def initialize(type, title, comment, params) + super() + @type = type + @title = title + @comment = comment + @params = params + end + + def <=>(other) + full_name <=> other.full_name + end + + def full_name + @type + "[" + @title + "]" + end + + def name + full_name + end + + def to_s + res = @type + "[" + @title + "]\n" + res << @comment.to_s + res + end + end +end diff --git a/lib/puppet/util/rdoc/generators/puppet_generator.rb b/lib/puppet/util/rdoc/generators/puppet_generator.rb new file mode 100644 index 000000000..22f001164 --- /dev/null +++ b/lib/puppet/util/rdoc/generators/puppet_generator.rb @@ -0,0 +1,829 @@ +require 'rdoc/generators/html_generator' +require 'puppet/util/rdoc/code_objects' +module Generators + + # This module holds all the classes needed to generate the HTML documentation + # of a bunch of puppet manifests. + # + # It works by traversing all the code objects defined by the Puppet RDoc::Parser + # and produces HTML counterparts objects that in turns are used by RDoc template engine + # to produce the final HTML. + # + # It is also responsible of creating the whole directory hierarchy, and various index + # files. + # + # It is to be noted that the whole system is built on top of ruby RDoc. As such there + # is an implicit mapping of puppet entities to ruby entitites: + # + # Puppet => Ruby + # ------------------------ + # Module Module + # Class Class + # Definition Method + # Resource + # Node + # Plugin + # Fact + + MODULE_DIR = "modules" + NODE_DIR = "nodes" + PLUGIN_DIR = "plugins" + + # This is a specialized HTMLGenerator tailored to Puppet manifests + class PuppetGenerator < HTMLGenerator + + def PuppetGenerator.for(options) + AllReferences::reset + HtmlMethod::reset + + if options.all_one_file + PuppetGeneratorInOne.new(options) + else + PuppetGenerator.new(options) + end + end + + def initialize(options) #:not-new: + @options = options + load_html_template + end + + # loads our own html template file + def load_html_template + begin + require 'puppet/util/rdoc/generators/template/puppet/puppet' + extend RDoc::Page + rescue LoadError + $stderr.puts "Could not find Puppet template '#{template}'" + exit 99 + end + end + + def gen_method_index + # we don't generate an all define index + # as the presentation is per module/per class + end + + # This is the central method, it generates the whole structures + # along with all the indices. + def generate_html + super + gen_into(@nodes) + gen_into(@plugins) + end + + ## + # Generate: + # the list of modules + # the list of classes and definitions of a specific module + # the list of all classes + # the list of nodes + # the list of resources + def build_indices + @allfiles = [] + @nodes = [] + @plugins = [] + + # contains all the seen modules + @modules = {} + @allclasses = {} + + # build the modules, classes and per modules classes and define list + @toplevels.each do |toplevel| + next unless toplevel.document_self + file = HtmlFile.new(toplevel, @options, FILE_DIR) + classes = [] + methods = [] + modules = [] + nodes = [] + + # find all classes of this toplevel + # store modules if we find one + toplevel.each_classmodule do |k| + generate_class_list(classes, modules, k, toplevel, CLASS_DIR) + end + + # find all defines belonging to this toplevel + HtmlMethod.all_methods.each do |m| + # find parent module, check this method is not already + # defined. + if m.context.parent.toplevel === toplevel + methods << m + end + end + + classes.each do |k| + @allclasses[k.index_name] = k if !@allclasses.has_key?(k.index_name) + end + + # generate nodes and plugins found + classes.each do |k| + if k.context.is_module? + k.context.each_node do |name,node| + nodes << HTMLPuppetNode.new(node, toplevel, NODE_DIR, @options) + @nodes << nodes.last + end + k.context.each_plugin do |plugin| + @plugins << HTMLPuppetPlugin.new(plugin, toplevel, PLUGIN_DIR, @options) + end + k.context.each_fact do |fact| + @plugins << HTMLPuppetPlugin.new(fact, toplevel, PLUGIN_DIR, @options) + end + end + end + + @files << file + @allfiles << { "file" => file, "modules" => modules, "classes" => classes, "methods" => methods, "nodes" => nodes } + end + + @classes = @allclasses.values + end + + # produce a class/module list of HTMLPuppetModule/HTMLPuppetClass + # based on the code object traversal. + def generate_class_list(classes, modules, from, html_file, class_dir) + if from.is_module? and !@modules.has_key?(from.name) + k = HTMLPuppetModule.new(from, html_file, class_dir, @options) + classes << k + @modules[from.name] = k + modules << @modules[from.name] + elsif from.is_module? + modules << @modules[from.name] + elsif !from.is_module? + k = HTMLPuppetClass.new(from, html_file, class_dir, @options) + classes << k + end + from.each_classmodule do |mod| + generate_class_list(classes, modules, mod, html_file, class_dir) + end + end + + # generate all the subdirectories, modules, classes and files + def gen_sub_directories + begin + super + File.makedirs(MODULE_DIR) + File.makedirs(NODE_DIR) + File.makedirs(PLUGIN_DIR) + rescue + $stderr.puts $!.message + exit 1 + end + end + + # generate the index of modules + def gen_file_index + gen_top_index(@modules.values, 'All Modules', RDoc::Page::TOP_INDEX, "fr_modules_index.html") + end + + # generate a top index + def gen_top_index(collection, title, template, filename) + template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template) + res = [] + collection.sort.each do |f| + if f.document_self + res << { "classlist" => CGI.escapeHTML("#{MODULE_DIR}/fr_#{f.index_name}.html"), "module" => CGI.escapeHTML("#{CLASS_DIR}/#{f.index_name}.html"),"name" => CGI.escapeHTML(f.index_name) } + end + end + + values = { + "entries" => res, + 'list_title' => CGI.escapeHTML(title), + 'index_url' => main_url, + 'charset' => @options.charset, + 'style_url' => style_url('', @options.css), + } + + File.open(filename, "w") do |f| + template.write_html_on(f, values) + end + end + + # generate the all classes index file and the combo index + def gen_class_index + gen_an_index(@classes, 'All Classes', RDoc::Page::CLASS_INDEX, "fr_class_index.html") + @allfiles.each do |file| + unless file['file'].context.file_relative_name =~ /\.rb$/ + gen_composite_index(file, + RDoc::Page::COMBO_INDEX, + "#{MODULE_DIR}/fr_#{file["file"].context.module_name}.html") + end + end + end + + def gen_composite_index(collection, template, filename)\ + return if FileTest.exists?(filename) + + template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template) + res1 = [] + collection['classes'].sort.each do |f| + if f.document_self + unless f.context.is_module? + res1 << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.index_name) } + end + end + end + + res2 = [] + collection['methods'].sort.each do |f| + if f.document_self + res2 << { "href" => "../"+f.path, "name" => f.index_name.sub(/\(.*\)$/,'') } + end + end + + module_name = [] + res3 = [] + res4 = [] + collection['modules'].sort.each do |f| + module_name << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.index_name) } + unless f.facts.nil? + f.facts.each do |fact| + res3 << {"href" => "../"+CGI.escapeHTML(AllReferences["PLUGIN(#{fact.name})"].path), "name" => CGI.escapeHTML(fact.name)} + end + end + unless f.plugins.nil? + f.plugins.each do |plugin| + res4 << {"href" => "../"+CGI.escapeHTML(AllReferences["PLUGIN(#{plugin.name})"].path), "name" => CGI.escapeHTML(plugin.name)} + end + end + end + + res5 = [] + collection['nodes'].sort.each do |f| + if f.document_self + res5 << { "href" => "../"+CGI.escapeHTML(f.path), "name" => CGI.escapeHTML(f.name) } + end + end + + values = { + "module" => module_name, + "classes" => res1, + 'classes_title' => CGI.escapeHTML("Classes"), + 'defines_title' => CGI.escapeHTML("Defines"), + 'facts_title' => CGI.escapeHTML("Custom Facts"), + 'plugins_title' => CGI.escapeHTML("Plugins"), + 'nodes_title' => CGI.escapeHTML("Nodes"), + 'index_url' => main_url, + 'charset' => @options.charset, + 'style_url' => style_url('', @options.css), + } + + values["defines"] = res2 if res2.size>0 + values["facts"] = res3 if res3.size>0 + values["plugins"] = res4 if res4.size>0 + values["nodes"] = res5 if res5.size>0 + + File.open(filename, "w") do |f| + template.write_html_on(f, values) + end + end + + # returns the initial_page url + def main_url + main_page = @options.main_page + ref = nil + if main_page + ref = AllReferences[main_page] + if ref + ref = ref.path + else + $stderr.puts "Could not find main page #{main_page}" + end + end + + unless ref + for file in @files + if file.document_self and file.context.global + ref = CGI.escapeHTML("#{CLASS_DIR}/#{file.context.module_name}.html") + break + end + end + end + + unless ref + for file in @files + if file.document_self and !file.context.global + ref = CGI.escapeHTML("#{CLASS_DIR}/#{file.context.module_name}.html") + break + end + end + end + + unless ref + $stderr.puts "Couldn't find anything to document" + $stderr.puts "Perhaps you've used :stopdoc: in all classes" + exit(1) + end + + ref + end + + end + + # This module is used to hold/generate a list of puppet resources + # this is used in HTMLPuppetClass and HTMLPuppetNode + module ResourceContainer + def collect_resources + list = @context.resource_list + @resources = list.collect {|m| HTMLPuppetResource.new(m, self, @options) } + end + + def build_resource_summary_list(path_prefix='') + collect_resources unless @resources + resources = @resources.sort + res = [] + resources.each do |r| + res << { + "name" => CGI.escapeHTML(r.name), + "aref" => "#{path_prefix}\##{r.aref}" + } + end + res + end + + def build_resource_detail_list(section) + outer = [] + resources = @resources.sort + resources.each do |r| + row = {} + if r.section == section and r.document_self + row["name"] = CGI.escapeHTML(r.name) + desc = r.description.strip + row["m_desc"] = desc unless desc.empty? + row["aref"] = r.aref + row["params"] = r.params + outer << row + end + end + outer + end + end + + class HTMLPuppetClass < HtmlClass + include ResourceContainer + + def value_hash + super + rl = build_resource_summary_list + @values["resources"] = rl unless rl.empty? + + @context.sections.each do |section| + secdata = @values["sections"].select { |secdata| secdata["secsequence"] == section.sequence } + if secdata.size == 1 + secdata = secdata[0] + + rdl = build_resource_detail_list(section) + secdata["resource_list"] = rdl unless rdl.empty? + end + end + @values + end + end + + class HTMLPuppetNode < ContextUser + include ResourceContainer + + attr_reader :path + + def initialize(context, html_file, prefix, options) + super(context, options) + + @html_file = html_file + @is_module = context.is_module? + @values = {} + + context.viewer = self + + if options.all_one_file + @path = context.full_name + else + @path = http_url(context.full_name, prefix) + end + + AllReferences.add("NODE(#{@context.full_name})", self) + end + + def name + @context.name + end + + # return the relative file name to store this class in, + # which is also its url + def http_url(full_name, prefix) + path = full_name.dup + if path['<<'] + path.gsub!(/<<\s*(\w*)/) { "from-#$1" } + end + File.join(prefix, path.split("::")) + ".html" + end + + def parent_name + @context.parent.full_name + end + + def index_name + name + end + + def write_on(f) + value_hash + template = TemplatePage.new(RDoc::Page::BODYINC, + RDoc::Page::NODE_PAGE, + RDoc::Page::METHOD_LIST) + template.write_html_on(f, @values) + end + + def value_hash + class_attribute_values + add_table_of_sections + + @values["charset"] = @options.charset + @values["style_url"] = style_url(path, @options.css) + + d = markup(@context.comment) + @values["description"] = d unless d.empty? + + ml = build_method_summary_list + @values["methods"] = ml unless ml.empty? + + rl = build_resource_summary_list + @values["resources"] = rl unless rl.empty? + + il = build_include_list(@context) + @values["includes"] = il unless il.empty? + + @values["sections"] = @context.sections.map do |section| + + secdata = { + "sectitle" => section.title, + "secsequence" => section.sequence, + "seccomment" => markup(section.comment) + } + + al = build_alias_summary_list(section) + secdata["aliases"] = al unless al.empty? + + co = build_constants_summary_list(section) + secdata["constants"] = co unless co.empty? + + al = build_attribute_list(section) + secdata["attributes"] = al unless al.empty? + + cl = build_class_list(0, @context, section) + secdata["classlist"] = cl unless cl.empty? + + mdl = build_method_detail_list(section) + secdata["method_list"] = mdl unless mdl.empty? + + rdl = build_resource_detail_list(section) + secdata["resource_list"] = rdl unless rdl.empty? + + secdata + end + + @values + end + + def build_attribute_list(section) + atts = @context.attributes.sort + res = [] + atts.each do |att| + next unless att.section == section + if att.visibility == :public || att.visibility == :protected || @options.show_all + entry = { + "name" => CGI.escapeHTML(att.name), + "rw" => att.rw, + "a_desc" => markup(att.comment, true) + } + unless att.visibility == :public || att.visibility == :protected + entry["rw"] << "-" + end + res << entry + end + end + res + end + + def class_attribute_values + h_name = CGI.escapeHTML(name) + + @values["classmod"] = "Node" + @values["title"] = "#{@values['classmod']}: #{h_name}" + + c = @context + c = c.parent while c and !c.diagram + + if c && c.diagram + @values["diagram"] = diagram_reference(c.diagram) + end + + @values["full_name"] = h_name + + parent_class = @context.superclass + + if parent_class + @values["parent"] = CGI.escapeHTML(parent_class) + + if parent_name + lookup = parent_name + "::" + parent_class + else + lookup = parent_class + end + lookup = "NODE(#{lookup})" + parent_url = AllReferences[lookup] || AllReferences[parent_class] + if parent_url and parent_url.document_self + @values["par_url"] = aref_to(parent_url.path) + end + end + + files = [] + @context.in_files.each do |f| + res = {} + full_path = CGI.escapeHTML(f.file_absolute_name) + + res["full_path"] = full_path + res["full_path_url"] = aref_to(f.viewer.path) if f.document_self + + if @options.webcvs + res["cvsurl"] = cvs_url( @options.webcvs, full_path ) + end + + files << res + end + + @values['infiles'] = files + end + + def <=>(other) + self.name <=> other.name + end + end + + class HTMLPuppetModule < HtmlClass + + def initialize(context, html_file, prefix, options) + super(context, html_file, prefix, options) + end + + def value_hash + @values = super + + fl = build_facts_summary_list + @values["facts"] = fl unless fl.empty? + + pl = build_plugins_summary_list + @values["plugins"] = pl unless pl.empty? + + nl = build_nodes_list(0, @context) + @values["nodelist"] = nl unless nl.empty? + + @values + end + + def build_nodes_list(level, context) + res = "" + prefix = "  ::" * level; + + context.nodes.sort.each do |node| + if node.document_self + res << + prefix << + "Node " << + href(url(node.viewer.path), "link", node.full_name) << + "
\n" + end + end + res + end + + def build_facts_summary_list + potentially_referenced_list(context.facts) {|fn| ["PLUGIN(#{fn})"] } + end + + def build_plugins_summary_list + potentially_referenced_list(context.plugins) {|fn| ["PLUGIN(#{fn})"] } + end + + def facts + @context.facts + end + + def plugins + @context.plugins + end + + end + + class HTMLPuppetPlugin < ContextUser + attr_reader :path + + def initialize(context, html_file, prefix, options) + super(context, options) + + @html_file = html_file + @is_module = false + @values = {} + + context.viewer = self + + if options.all_one_file + @path = context.full_name + else + @path = http_url(context.full_name, prefix) + end + + AllReferences.add("PLUGIN(#{@context.full_name})", self) + end + + def name + @context.name + end + + # return the relative file name to store this class in, + # which is also its url + def http_url(full_name, prefix) + path = full_name.dup + if path['<<'] + path.gsub!(/<<\s*(\w*)/) { "from-#$1" } + end + File.join(prefix, path.split("::")) + ".html" + end + + def parent_name + @context.parent.full_name + end + + def index_name + name + end + + def write_on(f) + value_hash + template = TemplatePage.new(RDoc::Page::BODYINC, + RDoc::Page::PLUGIN_PAGE, + RDoc::Page::PLUGIN_LIST) + template.write_html_on(f, @values) + end + + def value_hash + attribute_values + add_table_of_sections + + @values["charset"] = @options.charset + @values["style_url"] = style_url(path, @options.css) + + d = markup(@context.comment) + @values["description"] = d unless d.empty? + + if context.is_fact? + unless context.confine.empty? + res = {} + res["type"] = context.confine[:type] + res["value"] = context.confine[:value] + @values["confine"] = [res] + end + else + @values["type"] = context.type + end + + @values["sections"] = @context.sections.map do |section| + secdata = { + "sectitle" => section.title, + "secsequence" => section.sequence, + "seccomment" => markup(section.comment) + } + secdata + end + + @values + end + + def attribute_values + h_name = CGI.escapeHTML(name) + + if @context.is_fact? + @values["classmod"] = "Fact" + else + @values["classmod"] = "Plugin" + end + @values["title"] = "#{@values['classmod']}: #{h_name}" + + c = @context + @values["full_name"] = h_name + + files = [] + @context.in_files.each do |f| + res = {} + full_path = CGI.escapeHTML(f.file_absolute_name) + + res["full_path"] = full_path + res["full_path_url"] = aref_to(f.viewer.path) if f.document_self + + if @options.webcvs + res["cvsurl"] = cvs_url( @options.webcvs, full_path ) + end + + files << res + end + + @values['infiles'] = files + end + + def <=>(other) + self.name <=> other.name + end + + end + + class HTMLPuppetResource + include MarkUp + + attr_reader :context + + @@seq = "R000000" + + def initialize(context, html_class, options) + @context = context + @html_class = html_class + @options = options + @@seq = @@seq.succ + @seq = @@seq + + context.viewer = self + + AllReferences.add(name, self) + end + + def as_href(from_path) + if @options.all_one_file + "#" + path + else + HTMLGenerator.gen_url(from_path, path) + end + end + + def name + @context.name + end + + def section + @context.section + end + + def index_name + "#{@context.name}" + end + + def params + @context.params + end + + def parent_name + if @context.parent.parent + @context.parent.parent.full_name + else + nil + end + end + + def aref + @seq + end + + def path + if @options.all_one_file + aref + else + @html_class.path + "#" + aref + end + end + + def description + markup(@context.comment) + end + + def <=>(other) + @context <=> other.context + end + + def document_self + @context.document_self + end + + def find_symbol(symbol, method=nil) + res = @context.parent.find_symbol(symbol, method) + if res + res = res.viewer + end + res + end + + end + + class PuppetGeneratorInOne < HTMLGeneratorInOne + def gen_method_index + gen_an_index(HtmlMethod.all_methods, 'Defines') + end + end + +end diff --git a/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb b/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb new file mode 100644 index 000000000..c71f81915 --- /dev/null +++ b/lib/puppet/util/rdoc/generators/template/puppet/puppet.rb @@ -0,0 +1,1051 @@ +# +# = CSS2 RDoc HTML template +# +# This is a template for RDoc that uses XHTML 1.0 Transitional and dictates a +# bit more of the appearance of the output to cascading stylesheets than the +# default. It was designed for clean inline code display, and uses DHTMl to +# toggle the visbility of each method's source with each click on the '[source]' +# link. +# +# == Authors +# +# * Michael Granger +# +# Copyright (c) 2002, 2003 The FaerieMUD Consortium. Some rights reserved. +# +# This work is licensed under the Creative Commons Attribution License. To view +# a copy of this license, visit http://creativecommons.org/licenses/by/1.0/ or +# send a letter to Creative Commons, 559 Nathan Abbott Way, Stanford, California +# 94305, USA. +# + +module RDoc + module Page + + FONTS = "Verdana,Arial,Helvetica,sans-serif" + +STYLE = %{ +body { + font-family: Verdana,Arial,Helvetica,sans-serif; + font-size: 90%; + margin: 0; + margin-left: 40px; + padding: 0; + background: white; +} + +h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } +h1 { font-size: 150%; } +h2,h3,h4 { margin-top: 1em; } + +a { background: #eef; color: #039; text-decoration: none; } +a:hover { background: #039; color: #eef; } + +/* Override the base stylesheet's Anchor inside a table cell */ +td > a { + background: transparent; + color: #039; + text-decoration: none; +} + +/* and inside a section title */ +.section-title > a { + background: transparent; + color: #eee; + text-decoration: none; +} + +/* === Structural elements =================================== */ + +div#index { + margin: 0; + margin-left: -40px; + padding: 0; + font-size: 90%; +} + + +div#index a { + margin-left: 0.7em; +} + +div#index .section-bar { + margin-left: 0px; + padding-left: 0.7em; + background: #ccc; + font-size: small; +} + + +div#classHeader, div#fileHeader { + width: auto; + color: white; + padding: 0.5em 1.5em 0.5em 1.5em; + margin: 0; + margin-left: -40px; + border-bottom: 3px solid #006; +} + +div#classHeader a, div#fileHeader a { + background: inherit; + color: white; +} + +div#classHeader td, div#fileHeader td { + background: inherit; + color: white; +} + + +div#fileHeader { + background: #057; +} + +div#classHeader { + background: #048; +} + +div#nodeHeader { + background: #7f7f7f; +} + +.class-name-in-header { + font-size: 180%; + font-weight: bold; +} + + +div#bodyContent { + padding: 0 1.5em 0 1.5em; +} + +div#description { + padding: 0.5em 1.5em; + background: #efefef; + border: 1px dotted #999; +} + +div#description h1,h2,h3,h4,h5,h6 { + color: #125;; + background: transparent; +} + +div#validator-badges { + text-align: center; +} +div#validator-badges img { border: 0; } + +div#copyright { + color: #333; + background: #efefef; + font: 0.75em sans-serif; + margin-top: 5em; + margin-bottom: 0; + padding: 0.5em 2em; +} + + +/* === Classes =================================== */ + +table.header-table { + color: white; + font-size: small; +} + +.type-note { + font-size: small; + color: #DEDEDE; +} + +.xxsection-bar { + background: #eee; + color: #333; + padding: 3px; +} + +.section-bar { + color: #333; + border-bottom: 1px solid #999; + margin-left: -20px; +} + + +.section-title { + background: #79a; + color: #eee; + padding: 3px; + margin-top: 2em; + margin-left: -30px; + border: 1px solid #999; +} + +.top-aligned-row { vertical-align: top } +.bottom-aligned-row { vertical-align: bottom } + +/* --- Context section classes ----------------------- */ + +.context-row { } +.context-item-name { font-family: monospace; font-weight: bold; color: black; } +.context-item-value { font-size: small; color: #448; } +.context-item-desc { color: #333; padding-left: 2em; } + +/* --- Method classes -------------------------- */ +.method-detail { + background: #efefef; + padding: 0; + margin-top: 0.5em; + margin-bottom: 1em; + border: 1px dotted #ccc; +} +.method-heading { + color: black; + background: #ccc; + border-bottom: 1px solid #666; + padding: 0.2em 0.5em 0 0.5em; +} +.method-signature { color: black; background: inherit; } +.method-name { font-weight: bold; } +.method-args { font-style: italic; } +.method-description { padding: 0 0.5em 0 0.5em; } + +/* --- Source code sections -------------------- */ + +a.source-toggle { font-size: 90%; } +div.method-source-code { + background: #262626; + color: #ffdead; + margin: 1em; + padding: 0.5em; + border: 1px dashed #999; + overflow: hidden; +} + +div.method-source-code pre { color: #ffdead; overflow: hidden; } + +/* --- Ruby keyword styles --------------------- */ + +.standalone-code { background: #221111; color: #ffdead; overflow: hidden; } + +.ruby-constant { color: #7fffd4; background: transparent; } +.ruby-keyword { color: #00ffff; background: transparent; } +.ruby-ivar { color: #eedd82; background: transparent; } +.ruby-operator { color: #00ffee; background: transparent; } +.ruby-identifier { color: #ffdead; background: transparent; } +.ruby-node { color: #ffa07a; background: transparent; } +.ruby-comment { color: #b22222; font-weight: bold; background: transparent; } +.ruby-regexp { color: #ffa07a; background: transparent; } +.ruby-value { color: #7fffd4; background: transparent; } +} + + +##################################################################### +### H E A D E R T E M P L A T E +##################################################################### + +XHTML_PREAMBLE = %{ + +} + +HEADER = XHTML_PREAMBLE + %{ + + + %title% + + + + + + + +} + + +##################################################################### +### C O N T E X T C O N T E N T T E M P L A T E +##################################################################### + +CONTEXT_CONTENT = %{ +} + + +##################################################################### +### F O O T E R T E M P L A T E +##################################################################### +FOOTER = %{ + + + + +} + + +##################################################################### +### F I L E P A G E H E A D E R T E M P L A T E +##################################################################### + +FILE_PAGE = %{ +
+

%short_name%

+ + + + + + + + + +
Path:%full_path% +IF:cvsurl +  (CVS) +ENDIF:cvsurl +
Last Update:%dtm_modified%
+
+} + + +##################################################################### +### C L A S S P A G E H E A D E R T E M P L A T E +##################################################################### + +CLASS_PAGE = %{ +
+ + + + + + + + + + +IF:parent + + + + +ENDIF:parent +
%classmod%%full_name%
In: +START:infiles +IF:full_path_url + +ENDIF:full_path_url + %full_path% +IF:full_path_url + +ENDIF:full_path_url +IF:cvsurl +  (CVS) +ENDIF:cvsurl +
+END:infiles +
Parent: +IF:par_url + +ENDIF:par_url + %parent% +IF:par_url + +ENDIF:par_url +
+
+} + +NODE_PAGE = %{ +
+ + + + + + + + + + +IF:parent + + + + +ENDIF:parent +
%classmod%%full_name%
In: +START:infiles +IF:full_path_url + +ENDIF:full_path_url + %full_path% +IF:full_path_url + +ENDIF:full_path_url +IF:cvsurl +  (CVS) +ENDIF:cvsurl +
+END:infiles +
Parent: +IF:par_url + +ENDIF:par_url + %parent% +IF:par_url + +ENDIF:par_url +
+
+} + +PLUGIN_PAGE = %{ +
+ + + + + + + + + +
%classmod%%full_name%
In: +START:infiles +IF:full_path_url + +ENDIF:full_path_url + %full_path% +IF:full_path_url + +ENDIF:full_path_url +IF:cvsurl +  (CVS) +ENDIF:cvsurl +
+END:infiles +
+
+} + + +##################################################################### +### M E T H O D L I S T T E M P L A T E +##################################################################### + +PLUGIN_LIST = %{ + +
+IF:description +
+ %description% +
+ENDIF:description + + +IF:toc +
+

Contents

+ +ENDIF:toc +
+ +
+ + +IF:confine +START:confine +
+

Confine

+ %type% %value% +
+
+
+END:confine +ENDIF:confine + + +IF:type +
+

Type

+ %type% +
+
+
+ENDIF:type + +START:sections +
+IF:sectitle +

%sectitle%

+IF:seccomment +
+ %seccomment% +
+ENDIF:seccomment +ENDIF:sectitle +END:sections +} + + +METHOD_LIST = %{ + +
+IF:diagram +
+ %diagram% +
+ENDIF:diagram + +IF:description +
+ %description% +
+ENDIF:description + +IF:requires +
+

Required files

+ +
+START:requires + HREF:aref:name:   +END:requires +
+
+ENDIF:requires + +IF:toc +
+

Contents

+ +ENDIF:toc +
+ +IF:methods +
+

Defines

+ +
+START:methods + HREF:aref:name:   +END:methods +
+
+ENDIF:methods + +IF:resources +
+

Resources

+ +
+START:resources + HREF:aref:name:   +END:resources +
+
+ENDIF:resources + +
+ + + +IF:includes +
+

Included Classes

+ +
+START:includes + HREF:aref:name: +END:includes +
+
+ENDIF:includes + +START:sections +
+IF:sectitle +

%sectitle%

+IF:seccomment +
+ %seccomment% +
+ENDIF:seccomment +ENDIF:sectitle + + + +IF:facts +
+

Custom Facts

+START:facts + HREF:aref:name:   +END:facts +
+ENDIF:facts + + +IF:plugins +
+

Plugins

+START:plugins +HREF:aref:name:   +END:plugins +
+ENDIF:plugins + + +IF:nodelist +
+

Nodes

+ + %nodelist% +
+ENDIF:nodelist + + +IF:classlist +
+

Classes and Modules

+ + %classlist% +
+ENDIF:classlist + +IF:constants +
+

Global Variables

+ +
+ +START:constants + + + + +IF:desc + + +ENDIF:desc + +END:constants +
%name%=%value% %desc%
+
+
+ENDIF:constants + +IF:aliases +
+

External Aliases

+ +
+ +START:aliases + + + + + +IF:desc + + + + +ENDIF:desc +END:aliases +
%old_name%->%new_name%
 %desc%
+
+
+ENDIF:aliases + + +IF:attributes +
+

Attributes

+ +
+ +START:attributes + + +IF:rw + +ENDIF:rw +IFNOT:rw + +ENDIF:rw + + +END:attributes +
%name% [%rw%]   %a_desc%
+
+
+ENDIF:attributes + + + + +IF:method_list +
+START:method_list +IF:methods +

Defines

+ +START:methods +
+ + + + +
+IF:m_desc + %m_desc% +ENDIF:m_desc +IF:sourcecode +

[Source]

+
+
+%sourcecode%
+
+
+ENDIF:sourcecode +
+
+ +END:methods +ENDIF:methods +END:method_list + +
+ENDIF:method_list + + + +IF:resource_list +
+

Resources

+START:resource_list + +
+ + +
+ %name%
+IF:params +START:params +    %name% => %value%
+END:params +ENDIF:params +
+ +
+IF:m_desc + %m_desc% +ENDIF:m_desc +
+
+END:resource_list + +
+ENDIF:resource_list + +END:sections +} + + +##################################################################### +### B O D Y T E M P L A T E +##################################################################### + +BODY = HEADER + %{ + +!INCLUDE! + +
+ +} + METHOD_LIST + %{ + +
+ +} + FOOTER + +BODYINC = HEADER + %{ + +!INCLUDE! + +
+ +!INCLUDE! + +
+ +} + FOOTER + + + +##################################################################### +### S O U R C E C O D E T E M P L A T E +##################################################################### + +SRC_PAGE = XHTML_PREAMBLE + %{ + + + %title% + + + + +
%code%
+ + +} + + +##################################################################### +### I N D E X F I L E T E M P L A T E S +##################################################################### + +FR_INDEX_BODY = %{ +!INCLUDE! +} + +FILE_INDEX = XHTML_PREAMBLE + %{ + + + + %list_title% + + + + + +
+

%list_title%

+
+START:entries + %name%
+END:entries +
+
+ + +} + +TOP_INDEX = XHTML_PREAMBLE + %{ + + + + %list_title% + + + + + + +
+

%list_title%

+
+START:entries + %name%
+END:entries +
+
+ + +} + + +CLASS_INDEX = FILE_INDEX +METHOD_INDEX = FILE_INDEX + +COMBO_INDEX = XHTML_PREAMBLE + %{ + + + + %classes_title% & %defines_title% + + + + + + + +
+ + All Classes
+ + +

Module

+
+START:module + %name%
+END:module +
+
+
+ +IF:nodes +

%nodes_title%

+
+START:nodes +%name%
+END:nodes +
+ENDIF:nodes + +IF:classes +

%classes_title%

+
+START:classes +%name%
+END:classes +
+ENDIF:classes + +IF:defines +

%defines_title%

+
+START:defines +%name%
+END:defines +
+ENDIF:defines + +IF:facts +

%facts_title%

+
+START:facts +%name%
+END:facts +
+ENDIF:facts + + +IF:plugins +

%plugins_title%

+
+START:plugins +%name%
+END:plugins +
+ENDIF:plugins + +
+ + +} + +INDEX = %{ + + + + + + %title% + + + + + + + + + + +} + + + + end # module Page +end # class RDoc + +require 'rdoc/generators/template/html/one_page_html' diff --git a/lib/puppet/util/rdoc/parser.rb b/lib/puppet/util/rdoc/parser.rb new file mode 100644 index 000000000..33dd6564f --- /dev/null +++ b/lib/puppet/util/rdoc/parser.rb @@ -0,0 +1,437 @@ +# Puppet "parser" for the rdoc system +# The parser uses puppet parser and traverse the AST to instruct RDoc about +# our current structures. It also parses ruby files that could contain +# either custom facts or puppet plugins (functions, types...) + +# rdoc mandatory includes +require "rdoc/code_objects" +require "puppet/util/rdoc/code_objects" +require "rdoc/tokenstream" +require "rdoc/markup/simple_markup/preprocess" +require "rdoc/parsers/parserfactory" + +module RDoc + +class Parser + extend ParserFactory + + # parser registration into RDoc + parse_files_matching(/\.(rb|pp)$/) + + # called with the top level file + def initialize(top_level, file_name, content, options, stats) + @options = options + @stats = stats + @input_file_name = file_name + @top_level = PuppetTopLevel.new(top_level) + @progress = $stderr unless options.quiet + end + + # main entry point + def scan + Puppet.info "rdoc: scanning %s" % @input_file_name + if @input_file_name =~ /\.pp$/ + @parser = Puppet::Parser::Parser.new(:environment => Puppet[:environment]) + @parser.file = @input_file_name + @ast = @parser.parse + end + scan_top_level(@top_level) + @top_level + end + + private + + # walk down the namespace and lookup/create container as needed + def get_class_or_module(container, name) + + # class ::A -> A is in the top level + if name =~ /^::/ + container = @top_level + end + + names = name.split('::') + + final_name = names.pop + names.each do |name| + prev_container = container + container = container.find_module_named(name) + unless container + container = prev_container.add_module(PuppetClass, name) + end + end + return [container, final_name] + end + + # split_module tries to find if +path+ belongs to the module path + # if it does, it returns the module name, otherwise if we are sure + # it is part of the global manifest path, "" is returned. + # And finally if this path couldn't be mapped anywhere, nil is returned. + def split_module(path) + # find a module + fullpath = File.expand_path(path) + Puppet.debug "rdoc: testing %s" % fullpath + if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins)\/.+\.(pp|rb)$/ + modpath = $1 + name = $2 + Puppet.debug "rdoc: module %s into %s ?" % [name, modpath] + Puppet::Module.modulepath().each do |mp| + if File.identical?(modpath,mp) + Puppet.debug "rdoc: found module %s" % name + return name + end + end + end + if fullpath =~ /\.(pp|rb)$/ + # there can be paths we don't want to scan under modules + # imagine a ruby or manifest that would be distributed as part as a module + # but we don't want those to be hosted under + Puppet::Module.modulepath().each do |mp| + # check that fullpath is a descendant of mp + dirname = fullpath + while (dirname = File.dirname(dirname)) != '/' + return nil if File.identical?(dirname,mp) + end + end + end + # we are under a global manifests + Puppet.debug "rdoc: global manifests" + return "" + end + + # create documentation for the top level +container+ + def scan_top_level(container) + # use the module README as documentation for the module + comment = "" + readme = File.join(File.dirname(File.dirname(@input_file_name)), "README") + comment = File.open(readme,"r") { |f| f.read } if FileTest.readable?(readme) + look_for_directives_in(container, comment) unless comment.empty? + + # infer module name from directory + name = split_module(@input_file_name) + if name.nil? + # skip .pp files that are not in manifests directories as we can't guarantee they're part + # of a module or the global configuration. + container.document_self = false + return + end + + Puppet.debug "rdoc: scanning for %s" % name + + container.module_name = name + container.global=true if name == "" + + @stats.num_modules += 1 + container, name = get_class_or_module(container,name) + mod = container.add_module(PuppetModule, name) + mod.record_location(@top_level) + mod.comment = comment + + if @input_file_name =~ /\.pp$/ + parse_elements(mod) + elsif @input_file_name =~ /\.rb$/ + parse_plugins(mod) + end + end + + # create documentation for include statements we can find in +code+ + # and associate it with +container+ + def scan_for_include(container, code) + code.each do |stmt| + scan_for_include(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) + + if stmt.is_a?(Puppet::Parser::AST::Function) and stmt.name == "include" + stmt.arguments.each do |included| + Puppet.debug "found include: %s" % included.value + container.add_include(Include.new(included.value, stmt.doc)) + end + end + end + end + + # create documentation for global variables assignements we can find in +code+ + # and associate it with +container+ + def scan_for_vardef(container, code) + code.each do |stmt| + scan_for_vardef(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) + + if stmt.is_a?(Puppet::Parser::AST::VarDef) + Puppet.debug "rdoc: found constant: %s = %s" % [stmt.name.to_s, value_to_s(stmt.value)] + container.add_constant(Constant.new(stmt.name.to_s, value_to_s(stmt.value), stmt.doc)) + end + end + end + + # create documentation for resources we can find in +code+ + # and associate it with +container+ + def scan_for_resource(container, code) + code.each do |stmt| + scan_for_resource(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) + + if stmt.is_a?(Puppet::Parser::AST::Resource) + type = stmt.type.split("::").collect { |s| s.capitalize }.join("::") + title = value_to_s(stmt.title) + Puppet.debug "rdoc: found resource: %s[%s]" % [type,title] + + param = [] + stmt.params.children.each do |p| + res = {} + res["name"] = p.param + if !p.value.nil? + if !p.value.is_a?(Puppet::Parser::AST::ASTArray) + res["value"] = "'#{p.value}'" + else + res["value"] = "[%s]" % p.value.children.collect { |v| "'#{v}'" }.join(", ") + end + end + param << res + end + + container.add_resource(PuppetResource.new(type, title, stmt.doc, param)) + end + end + end + + # create documentation for a class named +name+ + def document_class(name, klass, container) + Puppet.debug "rdoc: found new class %s" % name + container, name = get_class_or_module(container, name) + + superclass = klass.parentclass + superclass = "" if superclass.nil? or superclass.empty? + + @stats.num_classes += 1 + comment = klass.doc + look_for_directives_in(container, comment) unless comment.empty? + cls = container.add_class(PuppetClass, name, superclass) + cls.record_location(@top_level) + + # scan class code for include + code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::ASTArray) + code ||= klass.code + unless code.nil? + scan_for_include(cls, code) + scan_for_resource(cls, code) if Puppet.settings[:document_all] + end + + cls.comment = comment + end + + # create documentation for a node + def document_node(name, node, container) + Puppet.debug "rdoc: found new node %s" % name + superclass = node.parentclass + superclass = "" if superclass.nil? or superclass.empty? + + comment = node.doc + look_for_directives_in(container, comment) unless comment.empty? + n = container.add_node(name, superclass) + n.record_location(@top_level) + + code = node.code.children if node.code.is_a?(Puppet::Parser::AST::ASTArray) + code ||= node.code + unless code.nil? + scan_for_include(n, code) + scan_for_vardef(n, code) + scan_for_resource(n, code) if Puppet.settings[:document_all] + end + + n.comment = comment + end + + # create documentation for a define + def document_define(name, define, container) + Puppet.debug "rdoc: found new definition %s" % name + # find superclas if any + @stats.num_methods += 1 + + # find the parentclass + # split define name by :: to find the complete module hierarchy + container, name = get_class_or_module(container,name) + + return if container.find_local_symbol(name) + + # build up declaration + declaration = "" + define.arguments.each do |arg,value| + declaration << "\$#{arg}" + if !value.nil? + declaration << " => " + if !value.is_a?(Puppet::Parser::AST::ASTArray) + declaration << "'#{value.value}'" + else + declaration << "[%s]" % value.children.collect { |v| "'#{v}'" }.join(", ") + end + end + declaration << ", " + end + declaration.chop!.chop! if declaration.size > 1 + + # register method into the container + meth = AnyMethod.new(declaration, name) + container.add_method(meth) + meth.comment = define.doc + look_for_directives_in(container, meth.comment) unless meth.comment.empty? + meth.params = "( " + declaration + " )" + meth.visibility = :public + meth.document_self = true + meth.singleton = false + end + + # Traverse the AST tree and produce code-objects node + # that contains the documentation + def parse_elements(container) + Puppet.debug "rdoc: scanning manifest" + @ast[:classes].each do |name, klass| + if klass.file == @input_file_name + unless name.empty? + document_class(name,klass,container) + else # on main class document vardefs + code = klass.code.children unless klass.code.is_a?(Puppet::Parser::AST::ASTArray) + code ||= klass.code + scan_for_vardef(container, code) unless code.nil? + end + end + end + + @ast[:definitions].each do |name, define| + if define.file == @input_file_name + document_define(name,define,container) + end + end + + @ast[:nodes].each do |name, node| + if node.file == @input_file_name + document_node(name,node,container) + end + end + end + + # create documentation for plugins + def parse_plugins(container) + Puppet.debug "rdoc: scanning plugin or fact" + if @input_file_name =~ /\/facter\/[^\/]+\.rb$/ + parse_fact(container) + else + parse_puppet_plugin(container) + end + end + + # this is a poor man custom fact parser :-) + def parse_fact(container) + comments = "" + current_fact = nil + File.open(@input_file_name) do |of| + of.each do |line| + # fetch comments + if line =~ /^[ \t]*# ?(.*)$/ + comments += $1 + "\n" + elsif line =~ /^[ \t]*Facter.add\(['"](.*?)['"]\)/ + current_fact = Fact.new($1,{}) + container.add_fact(current_fact) + look_for_directives_in(container, comments) unless comments.empty? + current_fact.comment = comments + current_fact.record_location(@top_level) + comments = "" + Puppet.debug "rdoc: found custom fact %s" % current_fact.name + elsif line =~ /^[ \t]*confine[ \t]*:(.*?)[ \t]*=>[ \t]*(.*)$/ + current_fact.confine = { :type => $1, :value => $2 } unless current_fact.nil? + else # unknown line type + comments ="" + end + end + end + end + + # this is a poor man puppet plugin parser :-) + # it doesn't extract doc nor desc :-( + def parse_puppet_plugin(container) + comments = "" + current_plugin = nil + + File.open(@input_file_name) do |of| + of.each do |line| + # fetch comments + if line =~ /^[ \t]*# ?(.*)$/ + comments += $1 + "\n" + elsif line =~ /^[ \t]*newfunction[ \t]*\([ \t]*:(.*?)[ \t]*,[ \t]*:type[ \t]*=>[ \t]*(:rvalue|:lvalue)\)/ + current_plugin = Plugin.new($1, "function") + container.add_plugin(current_plugin) + look_for_directives_in(container, comments) unless comments.empty? + current_plugin.comment = comments + current_plugin.record_location(@top_level) + comments = "" + Puppet.debug "rdoc: found new function plugins %s" % current_plugin.name + elsif line =~ /^[ \t]*Puppet::Type.newtype[ \t]*\([ \t]*:(.*?)\)/ + current_plugin = Plugin.new($1, "type") + container.add_plugin(current_plugin) + look_for_directives_in(container, comments) unless comments.empty? + current_plugin.comment = comments + current_plugin.record_location(@top_level) + comments = "" + Puppet.debug "rdoc: found new type plugins %s" % current_plugin.name + elsif line =~ /module Puppet::Parser::Functions/ + # skip + else # unknown line type + comments ="" + end + end + end + end + + # look_for_directives_in scans the current +comment+ for RDoc directives + def look_for_directives_in(context, comment) + preprocess = SM::PreProcess.new(@input_file_name, @options.rdoc_include) + + preprocess.handle(comment) do |directive, param| + case directive + when "stopdoc" + context.stop_doc + "" + when "startdoc" + context.start_doc + context.force_documentation = true + "" + when "enddoc" + #context.done_documenting = true + #"" + throw :enddoc + when "main" + options = Options.instance + options.main_page = param + "" + when "title" + options = Options.instance + options.title = param + "" + when "section" + context.set_current_section(param, comment) + comment.replace("") # 1.8 doesn't support #clear + break + else + warn "Unrecognized directive '#{directive}'" + break + end + end + remove_private_comments(comment) + end + + def remove_private_comments(comment) + comment.gsub!(/^#--.*?^#\+\+/m, '') + comment.sub!(/^#--.*/m, '') + end + + # convert an AST value to a string + def value_to_s(value) + value = value.children if value.is_a?(Puppet::Parser::AST::ASTArray) + if value.is_a?(Array) + "['#{value.join(", ")}']" + elsif [:true, true, "true"].include?(value) + "true" + elsif [:false, false, "false"].include?(value) + "false" + else + value.to_s + end + end +end +end \ No newline at end of file -- cgit From 030c7913643fa9b37b54815b09ee03427c4849ef Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 17 Nov 2008 21:07:08 +1100 Subject: Moved RRD feature from util/metric.rb to feature/base.rb --- lib/puppet/feature/base.rb | 3 +++ lib/puppet/util/metric.rb | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb index 0b12d730a..c3fb9a2f3 100644 --- a/lib/puppet/feature/base.rb +++ b/lib/puppet/feature/base.rb @@ -25,3 +25,6 @@ Puppet.features.add :diff, :libs => %w{diff/lcs diff/lcs/hunk} # We have augeas Puppet.features.add(:augeas, :libs => ["augeas"]) + +# We have RRD available +Puppet.features.add(:rrd, :libs => ["RRDtool"]) diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb index e6d7678aa..f352462cd 100644 --- a/lib/puppet/util/metric.rb +++ b/lib/puppet/util/metric.rb @@ -4,11 +4,6 @@ require 'puppet' # A class for handling metrics. This is currently ridiculously hackish. class Puppet::Util::Metric - # Load the library as a feature, so we can test its presence. - # It's only used by this class, so there's no reason to move it - # to the main feature list. - Puppet.features.add :rrd, :libs => 'RRDtool' - attr_accessor :type, :name, :value, :label attr_writer :values -- cgit From 3c870d8f33eb757ddc16c33e89618a58685606fd Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Tue, 18 Nov 2008 22:54:22 +1100 Subject: Added versionable feature to the RPM provider --- lib/puppet/provider/package/rpm.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'lib') diff --git a/lib/puppet/provider/package/rpm.rb b/lib/puppet/provider/package/rpm.rb index b5a5c5dbc..09f78bf72 100755 --- a/lib/puppet/provider/package/rpm.rb +++ b/lib/puppet/provider/package/rpm.rb @@ -4,6 +4,8 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr desc "RPM packaging support; should work anywhere with a working ``rpm`` binary." + has_feature :versionable + # The query format by which we identify installed packages NEVRAFORMAT = "%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH}" NEVRA_FIELDS = [:name, :epoch, :version, :release, :arch] -- cgit From 43967408a166ca4155b1febae987300b2b327f97 Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Tue, 18 Nov 2008 14:40:00 -0800 Subject: Fix the init service type to cope with an array for defpath and if defpath does not exist --- lib/puppet/provider/service/init.rb | 40 +++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 17 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb index e95fbd0f9..3dc12caa2 100755 --- a/lib/puppet/provider/service/init.rb +++ b/lib/puppet/provider/service/init.rb @@ -24,24 +24,30 @@ Puppet::Type.type(:service).provide :init, :parent => :base do # List all services of this type. def self.instances - path = self.defpath - unless FileTest.directory?(path) - Puppet.notice "Service path %s does not exist" % path - next - end - - check = [:ensure] - - if public_method_defined? :enabled? - check << :enable - end - - Dir.entries(path).reject { |e| - fullpath = File.join(path, e) - e =~ /^\./ or ! FileTest.executable?(fullpath) - }.collect do |name| - new(:name => name, :path => path) + self.defpath = [self.defpath] unless self.defpath.is_a? Array + + instances = [] + + self.defpath.each do |path| + unless FileTest.directory?(path) + Puppet.notice "Service path %s does not exist" % path + next + end + + check = [:ensure] + + if public_method_defined? :enabled? + check << :enable + end + + Dir.entries(path).each do |name| + fullpath = File.join(path, name) + next if name =~ /^\./ + next if not FileTest.executable?(fullpath) + instances << new(:name => name, :path => path) + end end + instances end # Mark that our init script supports 'status' commands. -- cgit From d32d7f30f672e59c1427d9dfe32e7b7be35a48ab Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 19 Nov 2008 18:44:41 +1100 Subject: Fixed #1752 - Add an optional argument to Puppet::Util.execute to determine whether stderr and stdout are combined in the output --- lib/puppet/util.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index 59f732dae..09c94c3c9 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -268,7 +268,7 @@ module Util # Execute the desired command, and return the status and output. # def execute(command, failonfail = true, uid = nil, gid = nil) - def execute(command, arguments = {:failonfail => true}) + def execute(command, arguments = {:failonfail => true, :combine => true}) if command.is_a?(Array) command = command.flatten.collect { |i| i.to_s } str = command.join(" ") @@ -301,9 +301,13 @@ module Util # The idea here is to avoid IO#read whenever possible. output_file="/dev/null" + error_file="/dev/null" if ! arguments[:squelch] require "tempfile" output_file = Tempfile.new("puppet") + if arguments[:combine] + error_file=output_file + end end oldverb = $VERBOSE @@ -319,7 +323,8 @@ module Util begin $stdin.reopen("/dev/null") $stdout.reopen(output_file) - $stderr.reopen(output_file) + $stderr.reopen(error_file) + 3.upto(256){|fd| IO::new(fd).close rescue nil} if arguments[:gid] Process.egid = arguments[:gid] -- cgit From 8523a483155eccc543dd7d17ea8c4f942dcc249f Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Wed, 19 Nov 2008 18:49:50 +1100 Subject: Fixed #1751 - Mac OS X DirectoryService nameservice provider support for plist output and password hash fil --- .../provider/nameservice/directoryservice.rb | 227 ++++++++++++--------- 1 file changed, 127 insertions(+), 100 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb index fcc44f9e3..a20a8a96e 100644 --- a/lib/puppet/provider/nameservice/directoryservice.rb +++ b/lib/puppet/provider/nameservice/directoryservice.rb @@ -14,6 +14,7 @@ require 'puppet' require 'puppet/provider/nameservice' +require 'facter/util/plist' class Puppet::Provider::NameService class DirectoryService < Puppet::Provider::NameService @@ -37,8 +38,7 @@ class DirectoryService < Puppet::Provider::NameService commands :dscl => "/usr/bin/dscl" confine :operatingsystem => :darwin - # JJM FIXME: This will need to be the default around October 2007. - # defaultfor :operatingsystem => :darwin + defaultfor :operatingsystem => :darwin # JJM 2007-07-25: This map is used to map NameService attributes to their @@ -55,6 +55,7 @@ class DirectoryService < Puppet::Provider::NameService 'UniqueID' => :uid, 'RealName' => :comment, 'Password' => :password, + 'GeneratedUID' => :guid, } # JJM The same table as above, inverted. @@ns_to_ds_attribute_map = { @@ -65,16 +66,16 @@ class DirectoryService < Puppet::Provider::NameService :uid => 'UniqueID', :comment => 'RealName', :password => 'Password', + :guid => 'GeneratedUID', } + @@password_hash_dir = "/var/db/shadow/hash" + def self.instances # JJM Class method that provides an array of instance objects of this # type. - # JJM: Properties are dependent on the Puppet::Type we're managine. type_property_array = [:name] + @resource_type.validproperties - # JJM: No sense reporting the password. It's hashed. - type_property_array.delete(:password) if type_property_array.include? :password # Create a new instance of this Puppet::Type for each object present # on the system. @@ -119,7 +120,7 @@ class DirectoryService < Puppet::Provider::NameService all_present_str_array = list_all_present() - # JJM: Return nil if the named object isn't present. + # NBK: shortcut the process if the resource is missing return nil unless all_present_str_array.include? resource_name dscl_vector = get_exec_preamble("-read", resource_name) @@ -132,44 +133,20 @@ class DirectoryService < Puppet::Provider::NameService # JJM: We need a new hash to return back to our caller. attribute_hash = Hash.new - # JJM: First, the output string goes into an array. - # Then, the each array element is split - # If you want to figure out what this is doing, I suggest - # ruby-debug, and stepping through it. - dscl_output.split("\n").each do |line| - # JJM: Split the attribute name and the list of values. - ds_attribute, ds_values_string = line.split(':') - - # Split sets the values to nil if there's nothing after the : - ds_values_string ||= "" - - # JJM: skip this attribute line if the Puppet::Type doesn't care about it. + dscl_plist = Plist.parse_xml(dscl_output) + dscl_plist.keys().each do |key| + ds_attribute = key.sub("dsAttrTypeStandard:", "") next unless (@@ds_to_ns_attribute_map.keys.include?(ds_attribute) and type_properties.include? @@ds_to_ns_attribute_map[ds_attribute]) - - # JJM: We asked dscl to output url encoded values so we're able - # to machine parse on whitespace. We need to urldecode: - # " Jeff%20McCune John%20Doe " => ["Jeff McCune", "John Doe"] - ds_value_array = ds_values_string.scan(/[^\s]+/).collect do |v| - url_decoded_value = CGI::unescape v - if url_decoded_value =~ /^[-0-9]+$/ - url_decoded_value.to_i - else - url_decoded_value - end - end - - # JJM: Finally, we're able to build up our attribute hash. - # Remember, the hash is keyed by NameService attribute names, - # not DirectoryService attribute names. - # NOTE: We're also sort of cheating here... DirectoryService - # is robust enough to allow multiple values for almost every - # attribute in the system. Traditional NameService things - # really don't handle this case, so we'll always pull thet first - # value returned from DirectoryService. - # THERE MAY BE AN ORDERING ISSUE HERE, but I think it's ok... - attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value_array[0] + ds_value = dscl_plist[key][0] # only care about the first entry... + attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value end - return attribute_hash + + # NBK: need to read the existing password here as it's not actually + # stored in the user record. It is stored at a path that involves the + # UUID of the user record for non-Mobile local acccounts. + # Mobile Accounts are out of scope for this provider for now + attribute_hash[:password] = self.get_password(attribute_hash[:guid]) + return attribute_hash end def self.get_exec_preamble(ds_action, resource_name = nil) @@ -181,7 +158,7 @@ class DirectoryService < Puppet::Provider::NameService # We EXPECT name to be @resource[:name] when called from an instance object. # There are two ways to specify paths in 10.5. See man dscl. - command_vector = [ command(:dscl), "-url", "." ] + command_vector = [ command(:dscl), "-plist", "." ] # JJM: The actual action to perform. See "man dscl" # Common actiosn: -create, -delete, -merge, -append, -passwd command_vector << ds_action @@ -196,6 +173,52 @@ class DirectoryService < Puppet::Provider::NameService # e.g. 'dscl / -create /Users/mccune' return command_vector end + + def self.set_password(resource_name, guid, password_hash) + password_hash_file = "#{@@password_hash_dir}/#{guid}" + begin + File.open(password_hash_file, 'w') { |f| f.write(password_hash)} + rescue Errno::EACCES => detail + raise Puppet::Error, "Could not write to password hash file: #{detail}" + end + + # NBK: For shadow hashes, the user AuthenticationAuthority must contain a value of + # ";ShadowHash;". The LKDC in 10.5 makes this more interesting though as it + # will dynamically generate ;Kerberosv5;;username@LKDC:SHA1 attributes if + # missing. Thus we make sure we only set ;ShadowHash; if it is missing, and + # we can do this with the merge command. This allows people to continue to + # use other custom AuthenticationAuthority attributes without stomping on them. + # + # There is a potential problem here in that we're only doing this when setting + # the password, and the attribute could get modified at other times while the + # hash doesn't change and so this doesn't get called at all... but + # without switching all the other attributes to merge instead of create I can't + # see a simple enough solution for this that doesn't modify the user record + # every single time. This should be a rather rare edge case. (famous last words) + + dscl_vector = self.get_exec_preamble("-merge", resource_name) + dscl_vector << "AuthenticationAuthority" << ";ShadowHash;" + begin + dscl_output = execute(dscl_vector) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set AuthenticationAuthority." + end + end + + def self.get_password(guid) + password_hash = nil + password_hash_file = "#{@@password_hash_dir}/#{guid}" + # TODO: sort out error conditions? + if File.exists?(password_hash_file) + if not File.readable?(password_hash_file) + raise Puppet::Error("Could not read password hash file at #{password_hash_file} for #{@resource[:name]}") + end + f = File.new(password_hash_file) + password_hash = f.read + f.close + end + password_hash + end def ensure=(ensure_value) super @@ -223,54 +246,19 @@ class DirectoryService < Puppet::Provider::NameService end def password=(passphrase) - # JJM: Setting the password is a special case. We don't just - # set the attribute because we need to update the password - # databases. - # FIRST, make sure the AuthenticationAuthority is ;ShadowHash; If - # we don't do this, we don't get a shadow hash account. ("Obviously...") - dscl_vector = self.class.get_exec_preamble("-create", @resource[:name]) - dscl_vector << "AuthenticationAuthority" << ";ShadowHash;" - begin - dscl_output = execute(dscl_vector) - rescue Puppet::ExecutionFailure => detail - raise Puppet::Error, "Could not set AuthenticationAuthority." - end - - # JJM: Second, we need to actually set the password. dscl does - # some magic, creating the proper hash for us based on the - # AuthenticationAuthority attribute, set above. - dscl_vector = self.class.get_exec_preamble("-passwd", @resource[:name]) - dscl_vector << passphrase - # JJM: Should we not log the password string? This may be a security - # risk... - begin - dscl_output = execute(dscl_vector) - rescue Puppet::ExecutionFailure => detail - raise Puppet::Error, "Could not set password using command vector: %{dscl_vector.inspect}" - end - end - - # JJM: nameservice.rb defines methods for each attribute of the type. - # We implement these methods here, by implementing get() and set() - # See the resource_type= method defined in nameservice.rb - # I'm not sure what the implications are of doing things this way. - # It was a bit difficult to sort out what was happening in my head, - # but ruby-debug makes this process much more transparent. - # - def set(property, value) - # JJM: As it turns out, the set method defined in our parent class - # is fine. It just calls the modifycmd() method, which - # I'll implement here. - super - end - - def get(param) - hash = getinfo(false) - if hash - return hash[param] - else - return :absent - end + exec_arg_vector = self.class.get_exec_preamble("-read", @resource.name) + exec_arg_vector << @@ns_to_ds_attribute_map[:guid] + begin + guid_output = execute(exec_arg_vector) + guid_plist = Plist.parse_xml(guid_output) + # Although GeneratedUID like all DirectoryService values can be multi-valued + # according to the schema, in practice user accounts cannot have multiple UUIDs + # otherwise Bad Things Happen, so we just deal with the first value. + guid = guid_plist["dsAttrTypeStandard:#{@@ns_to_ds_attribute_map[:guid]}"][0] + self.class.set_password(@resource.name, guid, passphrase) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail] + end end def modifycmd(property, value) @@ -287,15 +275,53 @@ class DirectoryService < Puppet::Provider::NameService return exec_arg_vector end - def addcmd - # JJM 2007-07-24: - # - addcmd returns an array to be executed to create a new object. - # - This method is probably being called from the - # ensure= method in nameservice.rb, or here... - # - This should only be called if the object doesn't exist. - # JJM: Blame nameservice.rb for the terse method name. =) - # - self.class.get_exec_preamble("-create", @resource[:name]) + # NBK: we override @parent.create as we need to execute a series of commands + # to create objects with dscl, rather than the single command nameservice.rb + # expects to be returned by addcmd. Thus we don't bother defining addcmd. + def create + if exists? + info "already exists" + # The object already exists + return nil + end + + # NBK: First we create the object with a known guid so we can set the contents + # of the password hash if required + # Shelling out sucks, but for a single use case it doesn't seem worth + # requiring people install a UUID library that doesn't come with the system. + # This should be revisited if Puppet starts managing UUIDs for other platform + # user records. + guid = %x{/usr/bin/uuidgen}.chomp + + exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name]) + exec_arg_vector << @@ns_to_ds_attribute_map[:guid] << guid + begin + execute(exec_arg_vector) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set GeneratedUID for %s %s: %s" % + [@resource.class.name, @resource.name, detail] + end + + if value = @resource.should(:password) and value != "" + self.class.set_password(@resource[:name], guid, value) + end + + # Now we create all the standard properties + Puppet::Type.type(:user).validproperties.each do |property| + next if property == :ensure + if value = @resource.should(property) and value != "" + exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name]) + exec_arg_vector << @@ns_to_ds_attribute_map[symbolize(property)] + next if property == :password # skip setting the password here + exec_arg_vector << value.to_s + begin + execute(exec_arg_vector) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not create %s %s: %s" % + [@resource.class.name, @resource.name, detail] + end + end + end end def deletecmd @@ -341,6 +367,7 @@ class DirectoryService < Puppet::Provider::NameService # list, then report on the remaining list. Pretty whacky, ehh? type_properties = [:name] + self.class.resource_type.validproperties type_properties.delete(:ensure) if type_properties.include? :ensure + type_properties << :guid # append GeneratedUID so we just get the report here @property_value_cache_hash = self.class.single_report(@resource[:name], *type_properties) end return @property_value_cache_hash -- cgit From 1ad33cc1499bc9c5fee89d921c219b06986c34b5 Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Wed, 19 Nov 2008 10:10:54 +0100 Subject: Fix #1759 - Comparison operator was using string comparison for numbers Signed-off-by: Brice Figureau --- lib/puppet/parser/ast/comparison_operator.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/puppet/parser/ast/comparison_operator.rb b/lib/puppet/parser/ast/comparison_operator.rb index 63aa36c7f..3af86efea 100644 --- a/lib/puppet/parser/ast/comparison_operator.rb +++ b/lib/puppet/parser/ast/comparison_operator.rb @@ -18,6 +18,10 @@ class Puppet::Parser::AST lval = @lval.safeevaluate(scope) rval = @rval.safeevaluate(scope) + # convert to number if operands are number + lval = Puppet::Parser::Scope.number?(lval) || lval + rval = Puppet::Parser::Scope.number?(rval) || rval + # return result unless @operator == '!=' lval.send(@operator,rval) -- cgit From ed4c4050a8ac46c509b20031814646a49ba86e54 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 20 Nov 2008 14:37:16 -0600 Subject: Fixing #1755 - File modes (and other strange properties) will now display correctly in ralsh and generated manifests. Signed-off-by: Luke Kanies --- lib/puppet/type.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 7e8654921..7a1c8b40b 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -2487,8 +2487,8 @@ class Type trans = TransObject.new(self.title, self.class.name) values = retrieve() - values.each do |name, value| - trans[name.name] = value + values.each do |param, value| + trans[param.name] = param.is_to_s(value) end @parameters.each do |name, param| -- cgit From e9f858a1f9108c523efb3856e3ce46e3f9615646 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 20 Nov 2008 12:51:42 -0600 Subject: Refactoring the file/owner property to be simpler and cleaner. It now looks almost exactly like the group property, and has a much more current data flow (e.g., sync is never no-op, and the sync method doesn't check whether the file is present). Signed-off-by: Luke Kanies --- lib/puppet/type/file/owner.rb | 96 +++++++++++++++---------------------------- 1 file changed, 32 insertions(+), 64 deletions(-) (limited to 'lib') diff --git a/lib/puppet/type/file/owner.rb b/lib/puppet/type/file/owner.rb index 6f9bbd6a2..6bc40ecbd 100755 --- a/lib/puppet/type/file/owner.rb +++ b/lib/puppet/type/file/owner.rb @@ -1,5 +1,8 @@ module Puppet Puppet.type(:file).newproperty(:owner) do + include Puppet::Util::POSIX + include Puppet::Util::Warnings + require 'etc' desc "To whom the file should belong. Argument can be user name or user ID." @@ -24,40 +27,32 @@ module Puppet end end - def name2id(value) - if value.is_a?(Symbol) - return value.to_s + def insync?(current) + unless Puppet::Util::SUIDManager.uid == 0 + warning "Cannot manage ownership unless running as root" + return true end - begin - user = Etc.getpwnam(value) - if user.uid == "" - return nil + + @should.each do |value| + if value =~ /^\d+$/ + uid = Integer(value) + elsif value.is_a?(String) + fail "Could not find user %s" % value unless uid = uid(value) + else + uid = value end - return user.uid - rescue ArgumentError => detail - return nil + + return true if uid == current end + return false end # Determine if the user is valid, and if so, return the UID def validuser?(value) - if value =~ /^\d+$/ - value = value.to_i - end - - if value.is_a?(Integer) - # verify the user is a valid user - if tmp = id2name(value) - return value - else - return false - end + if number = uid(value) + return number else - if tmp = name2id(value) - return tmp - else - return false - end + return false end end @@ -99,13 +94,6 @@ module Puppet return :absent end - # Set our method appropriately, depending on links. - if stat.ftype == "link" and @resource[:links] != :follow - @method = :lchown - else - @method = :chown - end - currentvalue = stat.uid # On OS X, files that are owned by -2 get returned as really @@ -120,44 +108,24 @@ module Puppet end def sync - unless Puppet::Util::SUIDManager.uid == 0 - unless defined? @@notifieduid - self.notice "Cannot manage ownership unless running as root" - #@resource.delete(self.name) - @@notifieduid = true - end - return nil + # Set our method appropriately, depending on links. + if resource[:links] == :manage + method = :lchown + else + method = :chown end - user = nil - unless user = self.validuser?(self.should) - tmp = self.should - unless defined? @@usermissing - @@usermissing = {} - end - - if @@usermissing.include?(tmp) - @@usermissing[tmp] += 1 - else - self.notice "user %s does not exist" % tmp - @@usermissing[tmp] = 1 - end - return nil + uid = nil + @should.each do |user| + break if uid = validuser?(user) end - unless @resource.stat(false) - unless @resource.stat(true) - self.debug "File does not exist; cannot set owner" - return nil - end - #self.debug "%s: after refresh, is '%s'" % [self.class.name,@is] - end + raise Puppet::Error, "Could not find user(s) %s" % @should.join(",") unless uid begin - File.send(@method, user, nil, @resource[:path]) + File.send(method, uid, nil, @resource[:path]) rescue => detail - raise Puppet::Error, "Failed to set owner to '%s': %s" % - [user, detail] + raise Puppet::Error, "Failed to set owner to '%s': %s" % [uid, detail] end return :file_changed -- cgit From eb0d32a1548314713dc3a1360a4e9d44621afcfc Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 20 Nov 2008 13:55:14 -0600 Subject: Fixing #1764 - a property's 'sync' method is never considered a no-op. *This is a behaviour change.* If the property does not return an event name, then one is generated based on the property name. Previously, the 'sync' method could return nil and it would be considered a noop, but if you need a noop, then you need to modify your 'insync?' method to return 'true' in the noop cases. Also modifying all of the builtin types that didn't handle this explicitly or returned nil in 'sync'. There should be no behaviour change in any of them. Signed-off-by: Luke Kanies --- lib/puppet/property.rb | 2 +- lib/puppet/transaction/change.rb | 8 +++----- lib/puppet/type/file/mode.rb | 14 -------------- lib/puppet/type/file/selcontext.rb | 14 -------------- lib/puppet/type/group.rb | 31 ++----------------------------- lib/puppet/type/tidy.rb | 28 +++++++++++++++------------- lib/puppet/type/user.rb | 2 ++ 7 files changed, 23 insertions(+), 76 deletions(-) (limited to 'lib') diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb index 50a1b71de..e6d0704e6 100644 --- a/lib/puppet/property.rb +++ b/lib/puppet/property.rb @@ -485,7 +485,7 @@ class Property < Puppet::Parameter end # This doc will probably get overridden - @doc ||= "The basic property that the object should be in." + @doc ||= "The basic property that the resource should be in." end def self.inherited(sub) diff --git a/lib/puppet/transaction/change.rb b/lib/puppet/transaction/change.rb index e05c2592c..42c3a174f 100644 --- a/lib/puppet/transaction/change.rb +++ b/lib/puppet/transaction/change.rb @@ -53,12 +53,10 @@ class Puppet::Transaction::Change # The transaction catches any exceptions here. events = @property.sync if events.nil? - return nil - end - - if events.is_a?(Array) + events = [(@property.name.to_s + "_changed").to_sym] + elsif events.is_a?(Array) if events.empty? - return nil + events = [(@property.name.to_s + "_changed").to_sym] end else events = [events] diff --git a/lib/puppet/type/file/mode.rb b/lib/puppet/type/file/mode.rb index 8674e0a88..ada1b5b47 100755 --- a/lib/puppet/type/file/mode.rb +++ b/lib/puppet/type/file/mode.rb @@ -103,22 +103,8 @@ module Puppet end def sync - unless @resource.stat(false) - stat = @resource.stat(true) - - unless stat - self.debug "File does not exist; cannot set mode" - return nil - end - end - mode = self.should - if mode == :absent - # This is really only valid for create states... - return nil - end - begin File.chmod(mode, @resource[:path]) rescue => detail diff --git a/lib/puppet/type/file/selcontext.rb b/lib/puppet/type/file/selcontext.rb index 084cd3d2c..22e3080b1 100644 --- a/lib/puppet/type/file/selcontext.rb +++ b/lib/puppet/type/file/selcontext.rb @@ -45,20 +45,6 @@ module Puppet end def sync - unless @resource.stat(false) - stat = @resource.stat(true) - unless stat - return nil - end - end - - selcontext = self.should - - if selcontext == :absent - # This is only valid for create states... - return nil - end - self.set_selinux_context(@resource[:path], @should, name) return :file_changed end diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index cb11a60a4..29486d3f0 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -21,8 +21,8 @@ module Puppet for Mac OS X, NetInfo is used. This is currently unconfigurable, but if you desperately need it to be so, please contact us." - newproperty(:ensure) do - desc "The basic state that the object should be in." + ensurable do + desc "Create or remove the group." newvalue(:present) do provider.create @@ -35,33 +35,6 @@ module Puppet :group_removed end - - # If they're talking about the thing at all, they generally want to - # say it should exist. - defaultto do - if @resource.managed? - :present - else - nil - end - end - - def retrieve - return provider.exists? ? :present : :absent - end - - # The default 'sync' method only selects among a list of registered - # values. - def sync - unless self.class.values - self.devfail "No values defined for %s" % - self.class.name - end - - # Set ourselves to whatever our should value is. - self.set(self.should) - end - end newproperty(:gid) do diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb index c352ec85b..a3d994128 100755 --- a/lib/puppet/type/tidy.rb +++ b/lib/puppet/type/tidy.rb @@ -44,6 +44,11 @@ module Puppet end def insync?(is) + if File.lstat(file).ftype == "directory" and ! @resource[:rmdirs] + self.debug "Not tidying directories" + return true + end + if is.is_a?(Symbol) if [:absent, :notidy].include?(is) return true @@ -100,20 +105,17 @@ module Puppet file = @resource[:path] case File.lstat(file).ftype when "directory": - if @resource[:rmdirs] - subs = Dir.entries(@resource[:path]).reject { |d| - d == "." or d == ".." - }.length - if subs > 0 - self.info "%s has %s children; not tidying" % - [@resource[:path], subs] - self.info Dir.entries(@resource[:path]).inspect - else - Dir.rmdir(@resource[:path]) - end + # If 'rmdirs' is disabled, then we would have never + # gotten to this method. + subs = Dir.entries(@resource[:path]).reject { |d| + d == "." or d == ".." + }.length + if subs > 0 + self.info "%s has %s children; not tidying" % + [@resource[:path], subs] + self.info Dir.entries(@resource[:path]).inspect else - self.debug "Not tidying directories" - return nil + Dir.rmdir(@resource[:path]) end when "file": @resource.handlebackup(file) diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 929e45d14..637fe9b46 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -109,6 +109,8 @@ module Puppet end fail "Could not find group(s) %s" % @should.join(",") unless found + + # Use the default event. end end -- cgit From 27a750d4884f2f48c05f5ab6a5c70c7cecf5ff04 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 22 Nov 2008 10:00:55 +1100 Subject: Revert "Fixing #1755 - File modes (and other strange properties) will now display correctly" This reverts commit ed4c4050a8ac46c509b20031814646a49ba86e54. --- lib/puppet/type.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 7a1c8b40b..7e8654921 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -2487,8 +2487,8 @@ class Type trans = TransObject.new(self.title, self.class.name) values = retrieve() - values.each do |param, value| - trans[param.name] = param.is_to_s(value) + values.each do |name, value| + trans[name.name] = value end @parameters.each do |name, param| -- cgit From b415848841edac9b08ff604416ad2e24dd350b4d Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 24 Nov 2008 12:17:52 -0600 Subject: Fixing #1708 - user groups specified as names are now detected correctly. There was a bug when groups were specified by name -- the group was always compared to the current gid, rather than being converted to an integer and comparing that way. This is now fixed. Signed-off-by: Luke Kanies --- lib/puppet/type/user.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'lib') diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 637fe9b46..0fe7928e6 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -98,6 +98,16 @@ module Puppet end end + def insync?(is) + # We know the 'is' is a number, so we need to convert the 'should' to a number, + # too. + @should.each do |value| + return true if number = Puppet::Util.gid(value) and is == number + end + + return false + end + def sync found = false @should.each do |value| -- cgit From bbad9831577a123cc5f6bd7f04836f483c9e8f6f Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 23 Nov 2008 00:56:38 -0600 Subject: Removing the included testing gems; you must now install them yourself. Everything passes, but autotest doesn't include color. Signed-off-by: Luke Kanies --- lib/puppet/network/xmlrpc/client.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/network/xmlrpc/client.rb b/lib/puppet/network/xmlrpc/client.rb index c79f91d57..37ace2101 100644 --- a/lib/puppet/network/xmlrpc/client.rb +++ b/lib/puppet/network/xmlrpc/client.rb @@ -73,7 +73,9 @@ module Puppet::Network rescue Timeout::Error => detail Puppet.err "Connection timeout calling %s.%s: %s" % [namespace, method, detail.to_s] - raise XMLRPCClientError.new("Connection Timeout").set_backtrace(detail.backtrace) + error = XMLRPCClientError.new("Connection Timeout") + error.set_backtrace(detail.backtrace) + raise error rescue => detail if detail.message =~ /^Wrong size\. Was \d+, should be \d+$/ Puppet.warning "XMLRPC returned wrong size. Retrying." -- cgit From 5bd27c8b81c3250dd04307460868ff113b64190c Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 25 Nov 2008 13:21:32 -0600 Subject: Partially fixing #1772 - broken 'resources' tests. The main problem was that the directory_services user provider was returning a string for its uid instead of an integer. I also began a 'resources' spec file. Signed-off-by: Luke Kanies --- lib/puppet/provider/nameservice/directoryservice.rb | 3 +++ lib/puppet/type/resources.rb | 1 + 2 files changed, 4 insertions(+) (limited to 'lib') diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb index a20a8a96e..ecd5fa6f4 100644 --- a/lib/puppet/provider/nameservice/directoryservice.rb +++ b/lib/puppet/provider/nameservice/directoryservice.rb @@ -369,6 +369,9 @@ class DirectoryService < Puppet::Provider::NameService type_properties.delete(:ensure) if type_properties.include? :ensure type_properties << :guid # append GeneratedUID so we just get the report here @property_value_cache_hash = self.class.single_report(@resource[:name], *type_properties) + [:uid, :gid].each do |param| + @property_value_cache_hash[param] = @property_value_cache_hash[param].to_i if @property_value_cache_hash and @property_value_cache_hash.include?(param) + end end return @property_value_cache_hash end diff --git a/lib/puppet/type/resources.rb b/lib/puppet/type/resources.rb index c0d892bb8..358ad603b 100644 --- a/lib/puppet/type/resources.rb +++ b/lib/puppet/type/resources.rb @@ -137,6 +137,7 @@ Puppet::Type.newtype(:resources) do return false end + p current_values[resource.property(:uid)] if current_values[resource.property(:uid)] <= self[:unless_system_user] return false else -- cgit From a839fe277bcf0e5d71da9987e2608e73f6cca59b Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 25 Nov 2008 13:21:54 -0600 Subject: Partially fixing #1772 - fixing tidy code I broke. Signed-off-by: Luke Kanies --- lib/puppet/type/tidy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb index a3d994128..e37da5ef8 100755 --- a/lib/puppet/type/tidy.rb +++ b/lib/puppet/type/tidy.rb @@ -44,7 +44,7 @@ module Puppet end def insync?(is) - if File.lstat(file).ftype == "directory" and ! @resource[:rmdirs] + if File.lstat(resource[:path]).ftype == "directory" and ! @resource[:rmdirs] self.debug "Not tidying directories" return true end -- cgit From 78bced1de85c268a89d3c2f44e84ea50d31c775c Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 25 Nov 2008 18:14:40 -0600 Subject: Fixing #1683 - accessing and changing settings is now thread-safe. Applying patch by Matt Palmer. Signed-off-by: Luke Kanies --- lib/puppet/util/settings.rb | 284 ++++++++++++++++++++++---------------------- 1 file changed, 144 insertions(+), 140 deletions(-) (limited to 'lib') diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb index 1e49a3ada..a76776be0 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -9,8 +9,6 @@ class Puppet::Util::Settings include Enumerable include Puppet::Util - @@sync = Sync.new - attr_accessor :file attr_reader :timer @@ -21,22 +19,22 @@ class Puppet::Util::Settings # Set a config value. This doesn't set the defaults, it sets the value itself. def []=(param, value) - @@sync.synchronize do # yay, thread-safe - param = symbolize(param) - unless element = @config[param] - raise ArgumentError, - "Attempt to assign a value to unknown configuration parameter %s" % param.inspect - end - if element.respond_to?(:munge) - value = element.munge(value) - end - if element.respond_to?(:handle) - element.handle(value) - end - # Reset the name, so it's looked up again. - if param == :name - @name = nil - end + param = symbolize(param) + unless element = @config[param] + raise ArgumentError, + "Attempt to assign a value to unknown configuration parameter %s" % param.inspect + end + if element.respond_to?(:munge) + value = element.munge(value) + end + if element.respond_to?(:handle) + element.handle(value) + end + # Reset the name, so it's looked up again. + if param == :name + @name = nil + end + @sync.synchronize do # yay, thread-safe @values[:memory][param] = value @cache.clear end @@ -58,7 +56,6 @@ class Puppet::Util::Settings return options end - # Turn the config into a Puppet configuration and apply it def apply trans = self.to_transportable begin @@ -86,25 +83,21 @@ class Puppet::Util::Settings # Remove all set values, potentially skipping cli values. def clear(exceptcli = false) - @config.each { |name, obj| - unless exceptcli and obj.setbycli - obj.clear + @sync.synchronize do + @values.each do |name, values| + @values.delete(name) unless exceptcli and name == :cli end - } - @values.each do |name, values| - next if name == :cli and exceptcli - @values.delete(name) - end - # Don't clear the 'used' in this case, since it's a config file reparse, - # and we want to retain this info. - unless exceptcli - @used = [] - end + # Don't clear the 'used' in this case, since it's a config file reparse, + # and we want to retain this info. + unless exceptcli + @used = [] + end - @cache.clear + @cache.clear - @name = nil + @name = nil + end end # This is mostly just used for testing. @@ -177,10 +170,12 @@ class Puppet::Util::Settings end str = str.intern if self.valid?(str) - if self.boolean?(str) - @values[:cli][str] = bool - else - @values[:cli][str] = value + @sync.synchronize do + if self.boolean?(str) + @values[:cli][str] = bool + else + @values[:cli][str] = value + end end else raise ArgumentError, "Invalid argument %s" % opt @@ -198,14 +193,17 @@ class Puppet::Util::Settings @shortnames.include?(short) end - # Create a new config object + # Create a new collection of config settings. def initialize @config = {} @shortnames = {} - + @created = [] @searchpath = nil + # Mutex-like thing to protect @values + @sync = Sync.new + # Keep track of set values. @values = Hash.new { |hash, key| hash[key] = {} } @@ -309,7 +307,10 @@ class Puppet::Util::Settings end searchpath.each do |source| next if source == :name - break if @name = @values[source][:name] + @sync.synchronize do + @name = @values[source][:name] + end + break if @name end unless @name @name = convert(@config[:name].default).intern @@ -336,10 +337,12 @@ class Puppet::Util::Settings def parse(file) clear(true) - parse_file(file).each do |area, values| - @values[area] = values + @sync.synchronize do + parse_file(file).each do |area, values| + @values[area] = values + end end - + # Determine our environment, if we have one. if @config[:environment] env = self.value(:environment).to_sym @@ -348,16 +351,18 @@ class Puppet::Util::Settings end # Call any hooks we should be calling. - settings_with_hooks.each do |setting| - each_source(env) do |source| - if value = @values[source][setting.name] - # We still have to use value() to retrieve the value, since - # we want the fully interpolated value, not $vardir/lib or whatever. - # This results in extra work, but so few of the settings - # will have associated hooks that it ends up being less work this - # way overall. - setting.handle(self.value(setting.name, env)) - break + @sync.synchronize do + settings_with_hooks.each do |setting| + each_source(env) do |source| + if value = @values[source][setting.name] + # We still have to use value() to retrieve the value, since + # we want the fully interpolated value, not $vardir/lib or whatever. + # This results in extra work, but so few of the settings + # will have associated hooks that it ends up being less work this + # way overall. + setting.handle(self.value(setting.name, env)) + break + end end end end @@ -365,9 +370,11 @@ class Puppet::Util::Settings # We have to do it in the reverse of the search path, # because multiple sections could set the same value # and I'm too lazy to only set the metadata once. - searchpath.reverse.each do |source| - if meta = @values[source][:_meta] - set_metadata(meta) + @sync.synchronize do + searchpath.reverse.each do |source| + if meta = @values[source][:_meta] + set_metadata(meta) + end end end end @@ -394,9 +401,11 @@ class Puppet::Util::Settings raise Puppet::Error, "Permission denied to file %s" % file end - @values = Hash.new { |names, name| - names[name] = {} - } + @sync.synchronize do + @values = Hash.new { |names, name| + names[name] = {} + } + end # Get rid of the values set by the file, keeping cli values. self.clear(true) @@ -404,72 +413,71 @@ class Puppet::Util::Settings section = "puppet" metas = %w{owner group mode} values = Hash.new { |hash, key| hash[key] = {} } - text.split(/\n/).each { |line| - case line - when /^\[(\w+)\]$/: section = $1 # Section names - when /^\s*#/: next # Skip comments - when /^\s*$/: next # Skip blanks - when /^\s*(\w+)\s*=\s*(.+)$/: # settings - var = $1.intern - if var == :mode - value = $2 - else - value = munge_value($2) - end + @sync.synchronize do + text.split(/\n/).each { |line| + case line + when /^\[(\w+)\]$/: section = $1 # Section names + when /^\s*#/: next # Skip comments + when /^\s*$/: next # Skip blanks + when /^\s*(\w+)\s*=\s*(.+)$/: # settings + var = $1.intern + if var == :mode + value = $2 + else + value = munge_value($2) + end - # Only warn if we don't know what this config var is. This - # prevents exceptions later on. - unless @config.include?(var) or metas.include?(var.to_s) - Puppet.warning "Discarded unknown configuration parameter %s" % var.inspect - next # Skip this line. - end + # Only warn if we don't know what this config var is. This + # prevents exceptions later on. + unless @config.include?(var) or metas.include?(var.to_s) + Puppet.warning "Discarded unknown configuration parameter %s" % var.inspect + next # Skip this line. + end + + # Mmm, "special" attributes + if metas.include?(var.to_s) + unless values.include?(section) + values[section] = {} + end + values[section][var.to_s] = value - # Mmm, "special" attributes - if metas.include?(var.to_s) - unless values.include?(section) - values[section] = {} + # If the parameter is valid, then set it. + if section == Puppet[:name] and @config.include?(var) + #@config[var].value = value + @values[:main][var] = value + end + next end - values[section][var.to_s] = value - # If the parameter is valid, then set it. - if section == Puppet[:name] and @config.include?(var) - #@config[var].value = value + # Don't override set parameters, since the file is parsed + # after cli arguments are handled. + unless @config.include?(var) and @config[var].setbycli + Puppet.debug "%s: Setting %s to '%s'" % [section, var, value] @values[:main][var] = value end - next - end - - # Don't override set parameters, since the file is parsed - # after cli arguments are handled. - unless @config.include?(var) and @config[var].setbycli - Puppet.debug "%s: Setting %s to '%s'" % [section, var, value] - @values[:main][var] = value - end - @config[var].section = symbolize(section) + @config[var].section = symbolize(section) - metas.each { |meta| - if values[section][meta] - if @config[var].respond_to?(meta + "=") - @config[var].send(meta + "=", values[section][meta]) + metas.each { |meta| + if values[section][meta] + if @config[var].respond_to?(meta + "=") + @config[var].send(meta + "=", values[section][meta]) + end end - end - } - else - raise Puppet::Error, "Could not match line %s" % line - end - } + } + else + raise Puppet::Error, "Could not match line %s" % line + end + } + end end - # Create a new element. The value is passed in because it's used to determine - # what kind of element we're creating, but the value itself might be either - # a default or a value, so we can't actually assign it. + # Create a new config option. def newelement(hash) - value = hash[:value] || hash[:default] klass = nil if hash[:section] hash[:section] = symbolize(hash[:section]) end - case value + case hash[:default] when true, false, "true", "false": klass = CBoolean when /^\$\w+\//, /^\//: @@ -479,7 +487,7 @@ class Puppet::Util::Settings else raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, hash[:name]] end - hash[:parent] = self + hash[:settings] = self element = klass.new(hash) return element @@ -502,7 +510,7 @@ class Puppet::Util::Settings def reparse if defined? @file and @file.changed? Puppet.notice "Reparsing %s" % @file.file - @@sync.synchronize do + @sync.synchronize do parse(@file) end reuse() @@ -511,7 +519,7 @@ class Puppet::Util::Settings def reuse return unless defined? @used - @@sync.synchronize do # yay, thread-safe + @sync.synchronize do # yay, thread-safe @used.each do |section| @used.delete(section) self.use(section) @@ -595,7 +603,6 @@ class Puppet::Util::Settings name = symbolize(name) hash[:name] = name hash[:section] = section - name = hash[:name] if @config.include?(name) raise ArgumentError, "Parameter %s is already defined" % name end @@ -708,7 +715,7 @@ Generated on #{Time.now}. # Create the necessary objects to use a section. This is idempotent; # you can 'use' a section as many times as you want. def use(*sections) - @@sync.synchronize do # yay, thread-safe + @sync.synchronize do # yay, thread-safe sections = sections.reject { |s| @used.include?(s.to_sym) } return if sections.empty? @@ -769,16 +776,19 @@ Generated on #{Time.now}. end # See if we can find it within our searchable list of values - val = nil - each_source(environment) do |source| - # Look for the value. We have to test the hash for whether - # it exists, because the value might be false. - if @values[source].include?(param) - val = @values[source][param] - break + val = catch :foundval do + each_source(environment) do |source| + # Look for the value. We have to test the hash for whether + # it exists, because the value might be false. + @sync.synchronize do + if @values[source].include?(param) + throw :foundval, @values[source][param] + end + end end + throw :foundval, nil end - + # If we didn't get a value, use the default val = @config[param].default if val.nil? @@ -1058,14 +1068,9 @@ Generated on #{Time.now}. # The base element type. class CElement - attr_accessor :name, :section, :default, :parent, :setbycli, :call_on_define + attr_accessor :name, :section, :default, :setbycli, :call_on_define attr_reader :desc, :short - # Unset any set value. - def clear - @value = nil - end - def desc=(value) @desc = value.gsub(/^\s*/, '') end @@ -1085,10 +1090,9 @@ Generated on #{Time.now}. # Create the new element. Pretty much just sets the name. def initialize(args = {}) - if args.include?(:parent) - self.parent = args[:parent] - args.delete(:parent) - end + @settings = args.delete(:settings) + raise ArgumentError.new("You must refer to a settings object") if @settings.nil? or !@settings.is_a?(Puppet::Util::Settings) + args.each do |param, value| method = param.to_s + "=" unless self.respond_to? method @@ -1143,7 +1147,7 @@ Generated on #{Time.now}. # If the value has not been overridden, then print it out commented # and unconverted, so it's clear that that's the default and how it # works. - value = @parent.value(self.name) + value = @settings.value(self.name) if value != @default line = "%s = %s" % [@name, value] @@ -1158,7 +1162,7 @@ Generated on #{Time.now}. # Retrieves the value, or if it's not set, retrieves the default. def value - @parent.value(self.name) + @settings.value(self.name) end end @@ -1169,7 +1173,7 @@ Generated on #{Time.now}. def group if defined? @group - return @parent.convert(@group) + return @settings.convert(@group) else return nil end @@ -1177,7 +1181,7 @@ Generated on #{Time.now}. def owner if defined? @owner - return @parent.convert(@owner) + return @settings.convert(@owner) else return nil end @@ -1200,7 +1204,7 @@ Generated on #{Time.now}. # Return the appropriate type. def type - value = @parent.value(self.name) + value = @settings.value(self.name) if @name.to_s =~ /dir/ return :directory elsif value.to_s =~ /\/$/ @@ -1271,7 +1275,7 @@ Generated on #{Time.now}. return true unless value.is_a? String value.scan(/\$(\w+)/) { |name| name = $1 - unless @parent.include?(name) + unless @settings.include?(name) raise ArgumentError, "Settings parameter '%s' is undefined" % name -- cgit From 97a817706f7993044b69f148fe2ba74bbcb5d4a3 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 25 Nov 2008 18:21:52 -0600 Subject: Refactoring the thread-safety in Puppet::Util a bit. Signed-off-by: Luke Kanies --- lib/puppet/util/settings.rb | 48 +++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 21 deletions(-) (limited to 'lib') diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb index a76776be0..ac25e0815 100644 --- a/lib/puppet/util/settings.rb +++ b/lib/puppet/util/settings.rb @@ -333,14 +333,22 @@ class Puppet::Util::Settings end end - # Parse the configuration file. + # Parse the configuration file. Just provides + # thread safety. def parse(file) + # We have to clear outside of the sync, because it's + # also using synchronize(). clear(true) @sync.synchronize do - parse_file(file).each do |area, values| - @values[area] = values - end + unsafe_parse(file) + end + end + + # Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly. + def unsafe_parse(file) + parse_file(file).each do |area, values| + @values[area] = values end # Determine our environment, if we have one. @@ -351,18 +359,16 @@ class Puppet::Util::Settings end # Call any hooks we should be calling. - @sync.synchronize do - settings_with_hooks.each do |setting| - each_source(env) do |source| - if value = @values[source][setting.name] - # We still have to use value() to retrieve the value, since - # we want the fully interpolated value, not $vardir/lib or whatever. - # This results in extra work, but so few of the settings - # will have associated hooks that it ends up being less work this - # way overall. - setting.handle(self.value(setting.name, env)) - break - end + settings_with_hooks.each do |setting| + each_source(env) do |source| + if value = @values[source][setting.name] + # We still have to use value() to retrieve the value, since + # we want the fully interpolated value, not $vardir/lib or whatever. + # This results in extra work, but so few of the settings + # will have associated hooks that it ends up being less work this + # way overall. + setting.handle(self.value(setting.name, env)) + break end end end @@ -370,15 +376,15 @@ class Puppet::Util::Settings # We have to do it in the reverse of the search path, # because multiple sections could set the same value # and I'm too lazy to only set the metadata once. - @sync.synchronize do - searchpath.reverse.each do |source| - if meta = @values[source][:_meta] - set_metadata(meta) - end + searchpath.reverse.each do |source| + if meta = @values[source][:_meta] + set_metadata(meta) end end end + private :unsafe_parse + # Parse the configuration file. As of May 2007, this is a backward-compatibility method and # will be deprecated soon. def old_parse(file) -- cgit From 86ce934270992d192a1163d211761a505d5b2812 Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Tue, 25 Nov 2008 07:37:23 -0800 Subject: launchd service provider --- lib/puppet/provider/service/init.rb | 2 +- lib/puppet/provider/service/launchd.rb | 188 +++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 lib/puppet/provider/service/launchd.rb (limited to 'lib') diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb index 3dc12caa2..46fa2216e 100755 --- a/lib/puppet/provider/service/init.rb +++ b/lib/puppet/provider/service/init.rb @@ -30,7 +30,7 @@ Puppet::Type.type(:service).provide :init, :parent => :base do self.defpath.each do |path| unless FileTest.directory?(path) - Puppet.notice "Service path %s does not exist" % path + Puppet.debug "Service path %s does not exist" % path next end diff --git a/lib/puppet/provider/service/launchd.rb b/lib/puppet/provider/service/launchd.rb new file mode 100644 index 000000000..e51451cac --- /dev/null +++ b/lib/puppet/provider/service/launchd.rb @@ -0,0 +1,188 @@ +require 'facter/util/plist' + +Puppet::Type.type(:service).provide :launchd, :parent => :base do + desc "launchd service management framework. + + This provider manages launchd jobs, the default service framework for + Mac OS X, that has also been open sourced by Apple for possible use on + other platforms. + + See: + * http://developer.apple.com/macosx/launchd.html + * http://launchd.macosforge.org/ + + This provider reads plists out of the following directories: + * /System/Library/LaunchDaemons + * /System/Library/LaunchAgents + * /Library/LaunchDaemons + * /Library/LaunchAgents + + and builds up a list of services based upon each plists \"Label\" entry. + + This provider supports: + * ensure => running/stopped, + * enable => true/false + * status + * restart + + Here is how the Puppet states correspond to launchd states: + * stopped => job unloaded + * started => job loaded + * enabled => 'Disable' removed from job plist file + * disabled => 'Disable' added to job plist file + + Note that this allows you to do something launchctl can't do, which is to + be in a state of \"stopped/enabled\ or \"running/disabled\". + " + + commands :launchctl => "/bin/launchctl" + + defaultfor :operatingsystem => :darwin + confine :operatingsystem => :darwin + + has_feature :enableable + + Launchd_Paths = ["/Library/LaunchAgents", + "/Library/LaunchDaemons", + "/System/Library/LaunchAgents", + "/System/Library/LaunchDaemons",] + + + # returns a label => path map for either all jobs, or just a single + # job if the label is specified + def self.jobsearch(label=nil) + label_to_path_map = {} + Launchd_Paths.each do |path| + if FileTest.exists?(path) + Dir.entries(path).each do |f| + next if f =~ /^\..*$/ + next if FileTest.directory?(f) + fullpath = File.join(path, f) + job = Plist::parse_xml(fullpath) + if job and job.has_key?("Label") + if job["Label"] == label + return { label => fullpath } + else + label_to_path_map[job["Label"]] = fullpath + end + end + end + end + end + + # if we didn't find the job above and we should have, error. + if label + raise Puppet::Error.new("Unable to find launchd plist for job: #{label}") + end + # if returning all jobs + label_to_path_map + end + + + def self.instances + jobs = self.jobsearch + jobs.keys.collect do |job| + new(:name => job, :provider => :launchd, :path => jobs[job]) + end + end + + + # finds the path for a given label and returns the path and parsed plist + # as an array of [path, plist]. Note plist is really a Hash here. + def self.plist_from_label(label) + job = self.jobsearch(label) + job_path = job[label] + job_plist = Plist::parse_xml(job_path) + if not job_plist + raise Puppet::Error.new("Unable to parse launchd plist at path: #{job_path}") + end + [job_path, job_plist] + end + + + def status + # launchctl list exits zero if the job is loaded + # and non-zero if it isn't. Simple way to check... + cmds = [] + cmds << :launchctl << "list" << @resource[:name] + begin + execute(cmds) + return :running + rescue Puppet::ExecutionFailure + return :stopped + end + end + + + # start the service. To get to a state of running/enabled, we need to + # conditionally enable at load, then disable by modifying the plist file + # directly. + def start + job = self.class.jobsearch(@resource[:name]) + job_path = job[@resource[:name]] + did_enable_job = false + cmds = [] + cmds << :launchctl << "load" + if self.enabled? == :false # launchctl won't load disabled jobs + cmds << "-w" + did_enable_job = true + end + cmds << job_path + begin + execute(cmds) + rescue Puppet::ExecutionFailure + raise Puppet::Error.new("Unable to start service: %s at path: %s" % [@resource[:name], job_path]) + end + # As load -w clears the Disabled flag, we need to add it in after + if did_enable_job and @resource[:enable] == :false + self.disable + end + end + + + def stop + job = self.class.jobsearch(@resource[:name]) + job_path = job[@resource[:name]] + cmds = [] + cmds << :launchctl << "unload" << job_path + begin + execute(cmds) + rescue Puppet::ExecutionFailure + raise Puppet::Error.new("Unable to stop service: %s at path: %s" % [@resource[:name], job_path]) + end + end + + + # launchd jobs are enabled by default. They are only disabled if the key + # "Disabled" is set to true, but it can also be set to false to enable it. + def enabled? + job_path, job_plist = self.class.plist_from_label(@resource[:name]) + if job_plist.has_key?("Disabled") + if job_plist["Disabled"] # inverse of disabled is enabled + return :false + end + end + return :true + end + + + # enable and disable are a bit hacky. We write out the plist with the appropriate value + # rather than dealing with launchctl as it is unable to change the Disabled flag + # without actually loading/unloading the job. + def enable + job_path, job_plist = self.class.plist_from_label(@resource[:name]) + if not self.enabled? + job_plist.delete("Disabled") + Plist::Emit.save_plist(job_plist, job_path) + end + end + + + def disable + job_path, job_plist = self.class.plist_from_label(@resource[:name]) + job_plist["Disabled"] = true + Plist::Emit.save_plist(job_plist, job_path) + end + + +end -- cgit From d978668c03f42ce314245c23b06179f6a62f3d67 Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Tue, 25 Nov 2008 08:32:46 -0800 Subject: Lots of DirectoryService work. New Computer Type. Users now use password hashes. Groups now support setting members as attributes of the group for OS X. --- lib/puppet/provider/computer/computer.rb | 22 +++++ lib/puppet/provider/group/directoryservice.rb | 5 +- .../provider/nameservice/directoryservice.rb | 109 ++++++++++++++++----- lib/puppet/type/computer.rb | 59 +++++++++++ lib/puppet/type/group.rb | 38 +++++-- 5 files changed, 198 insertions(+), 35 deletions(-) create mode 100644 lib/puppet/provider/computer/computer.rb create mode 100644 lib/puppet/type/computer.rb (limited to 'lib') diff --git a/lib/puppet/provider/computer/computer.rb b/lib/puppet/provider/computer/computer.rb new file mode 100644 index 000000000..76d0f1883 --- /dev/null +++ b/lib/puppet/provider/computer/computer.rb @@ -0,0 +1,22 @@ +require 'puppet/provider/nameservice/directoryservice' + +Puppet::Type.type(:computer).provide :directoryservice, :parent => Puppet::Provider::NameService::DirectoryService do + desc "Computer object management using DirectoryService on OS X. + + Note that these are distinctly different kinds of objects to 'hosts', + as they require a MAC address and can have all sorts of policy attached to + them. + + This provider only manages Computer objects in the local directory service + domain, not in remote directories. + + If you wish to manage /etc/hosts on Mac OS X, then simply use the host + type as per other platforms. + " + + confine :operatingsystem => :darwin + defaultfor :operatingsystem => :darwin + + # hurray for abstraction. The nameservice directoryservice provider can + # handle everything we need. super. +end \ No newline at end of file diff --git a/lib/puppet/provider/group/directoryservice.rb b/lib/puppet/provider/group/directoryservice.rb index 406622224..2f393052b 100644 --- a/lib/puppet/provider/group/directoryservice.rb +++ b/lib/puppet/provider/group/directoryservice.rb @@ -16,8 +16,9 @@ require 'puppet/provider/nameservice/directoryservice' Puppet::Type.type(:group).provide :directoryservice, :parent => Puppet::Provider::NameService::DirectoryService do desc "Group management using DirectoryService on OS X." - + commands :dscl => "/usr/bin/dscl" confine :operatingsystem => :darwin - #defaultfor :operatingsystem => :darwin + defaultfor :operatingsystem => :darwin + has_feature :manages_members end diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb index ecd5fa6f4..0e8002c31 100644 --- a/lib/puppet/provider/nameservice/directoryservice.rb +++ b/lib/puppet/provider/nameservice/directoryservice.rb @@ -16,6 +16,7 @@ require 'puppet' require 'puppet/provider/nameservice' require 'facter/util/plist' + class Puppet::Provider::NameService class DirectoryService < Puppet::Provider::NameService # JJM: Dive into the eigenclass @@ -27,6 +28,7 @@ class DirectoryService < Puppet::Provider::NameService attr_writer :ds_path end + # JJM 2007-07-24: Not yet sure what initvars() does. I saw it in netinfo.rb # I do know, however, that it makes methods "work" =) # e.g. addcmd isn't available if this method call isn't present. @@ -37,6 +39,7 @@ class DirectoryService < Puppet::Provider::NameService initvars() commands :dscl => "/usr/bin/dscl" + commands :dseditgroup => "/usr/sbin/dseditgroup" confine :operatingsystem => :darwin defaultfor :operatingsystem => :darwin @@ -56,6 +59,9 @@ class DirectoryService < Puppet::Provider::NameService 'RealName' => :comment, 'Password' => :password, 'GeneratedUID' => :guid, + 'IPAddress' => :ip_address, + 'ENetAddress' => :en_address, + 'GroupMembership' => :members, } # JJM The same table as above, inverted. @@ns_to_ds_attribute_map = { @@ -67,6 +73,9 @@ class DirectoryService < Puppet::Provider::NameService :comment => 'RealName', :password => 'Password', :guid => 'GeneratedUID', + :en_address => 'ENetAddress', + :ip_address => 'IPAddress', + :members => 'GroupMembership', } @@password_hash_dir = "/var/db/shadow/hash" @@ -137,7 +146,10 @@ class DirectoryService < Puppet::Provider::NameService dscl_plist.keys().each do |key| ds_attribute = key.sub("dsAttrTypeStandard:", "") next unless (@@ds_to_ns_attribute_map.keys.include?(ds_attribute) and type_properties.include? @@ds_to_ns_attribute_map[ds_attribute]) - ds_value = dscl_plist[key][0] # only care about the first entry... + ds_value = dscl_plist[key] + if not @@ds_to_ns_attribute_map[ds_attribute] == :members # only members uses arrays so far + ds_value = ds_value[0] + end attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value end @@ -229,7 +241,6 @@ class DirectoryService < Puppet::Provider::NameService if ensure_value == :present @resource.class.validproperties.each do |name| next if name == :ensure - # LAK: We use property.sync here rather than directly calling # the settor method because the properties might do some kind # of conversion. In particular, the user gid property might @@ -261,18 +272,36 @@ class DirectoryService < Puppet::Provider::NameService end end - def modifycmd(property, value) - # JJM: This method will assemble a exec vector which modifies - # a single property and it's value using dscl. - # JJM: With /usr/bin/dscl, the -create option will destroy an - # existing property record if it exists - exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name]) - # JJM: The following line just maps the NS name to the DS name - # e.g. { :uid => 'UniqueID' } - exec_arg_vector << @@ns_to_ds_attribute_map[symbolize(property)] - # JJM: The following line sends the actual value to set the property to - exec_arg_vector << value.to_s - return exec_arg_vector + # NBK: we override @parent.set as we need to execute a series of commands + # to deal with array values, rather than the single command nameservice.rb + # expects to be returned by modifycmd. Thus we don't bother defining modifycmd. + + def set(param, value) + self.class.validate(param, value) + current_members = @property_value_cache_hash[:members] + if param == :members + # If we are meant to be authoritative for the group membership + # then remove all existing members who haven't been specified + # in the manifest. + if @resource[:auth_membership] and not current_members.nil? + remove_unwanted_members(current_members, value) + end + + # if they're not a member, make them one. + add_members(current_members, value) + else + exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name]) + # JJM: The following line just maps the NS name to the DS name + # e.g. { :uid => 'UniqueID' } + exec_arg_vector << @@ns_to_ds_attribute_map[symbolize(param)] + # JJM: The following line sends the actual value to set the property to + exec_arg_vector << value.to_s + begin + execute(exec_arg_vector) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail] + end + end end # NBK: we override @parent.create as we need to execute a series of commands @@ -307,20 +336,50 @@ class DirectoryService < Puppet::Provider::NameService end # Now we create all the standard properties - Puppet::Type.type(:user).validproperties.each do |property| + Puppet::Type.type(@resource.class.name).validproperties.each do |property| next if property == :ensure if value = @resource.should(property) and value != "" - exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name]) - exec_arg_vector << @@ns_to_ds_attribute_map[symbolize(property)] - next if property == :password # skip setting the password here - exec_arg_vector << value.to_s + if property == :members + add_members(nil, value) + else + exec_arg_vector = self.class.get_exec_preamble("-create", @resource[:name]) + exec_arg_vector << @@ns_to_ds_attribute_map[symbolize(property)] + next if property == :password # skip setting the password here + exec_arg_vector << value.to_s + begin + execute(exec_arg_vector) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not create %s %s: %s" % + [@resource.class.name, @resource.name, detail] + end + end + end + end + end + + def remove_unwanted_members(current_members, new_members) + current_members.each do |member| + if not value.include?(member) + cmd = [:dseditgroup, "-o", "edit", "-n", ".", "-d", member, @resource[:name]] begin - execute(exec_arg_vector) + execute(cmd) rescue Puppet::ExecutionFailure => detail - raise Puppet::Error, "Could not create %s %s: %s" % - [@resource.class.name, @resource.name, detail] - end - end + raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail] + end + end + end + end + + def add_members(current_members, new_members) + new_members.each do |user| + if current_members.nil? or not current_members.include?(user) + cmd = [:dseditgroup, "-o", "edit", "-n", ".", "-a", user, @resource[:name]] + begin + execute(cmd) + rescue Puppet::ExecutionFailure => detail + raise Puppet::Error, "Could not set %s on %s[%s]: %s" % [param, @resource.class.name, @resource.name, detail] + end + end end end @@ -376,4 +435,4 @@ class DirectoryService < Puppet::Provider::NameService return @property_value_cache_hash end end -end +end \ No newline at end of file diff --git a/lib/puppet/type/computer.rb b/lib/puppet/type/computer.rb new file mode 100644 index 000000000..0c0a70900 --- /dev/null +++ b/lib/puppet/type/computer.rb @@ -0,0 +1,59 @@ +Puppet::Type.newtype(:computer) do + + @doc = "Computer object management using DirectoryService on OS X. + + Note that these are distinctly different kinds of objects to 'hosts', + as they require a MAC address and can have all sorts of policy attached to + them. + + This provider only manages Computer objects in the local directory service + domain, not in remote directories. + + If you wish to manage /etc/hosts on Mac OS X, then simply use the host + type as per other platforms. + + This type primarily exists to create localhost Computer objects that MCX + policy can then be attached to." + + # ensurable + + # We autorequire the computer object in case it is being managed at the + # file level by Puppet. + + autorequire(:file) do + if self[:name] + "/var/db/dslocal/nodes/Default/computers/#{self[:name]}.plist" + else + nil + end + end + + newproperty(:ensure, :parent => Puppet::Property::Ensure) do + newvalue(:present) do + provider.create + end + + newvalue(:absent) do + Puppet.notice "prop ensure = absent" + provider.delete + end + end + + newparam(:name) do + desc "The " + isnamevar + end + + newparam(:realname) do + desc "realname" + end + + newproperty(:en_address) do + desc "The MAC address of the primary network interface. Must match en0." + end + + newproperty(:ip_address) do + desc "The IP Address of the Computer object." + end + +end \ No newline at end of file diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index 29486d3f0..1167962fe 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -11,15 +11,22 @@ require 'facter' module Puppet newtype(:group) do - @doc = "Manage groups. This type can only create groups. Group - membership must be managed on individual users. This resource type - uses the prescribed native tools for creating groups and generally - uses POSIX APIs for retrieving information about them. It does - not directly modify ``/etc/group`` or anything. + @doc = "Manage groups. On most platforms this can only create groups. + Group membership must be managed on individual users. + + On OS X, group membership is managed as an attribute of the group. + This resource type uses the prescribed native tools for creating + groups and generally uses POSIX APIs for retrieving information + about them. It does not directly modify ``/etc/group`` or anything. For most platforms, the tools used are ``groupadd`` and its ilk; - for Mac OS X, NetInfo is used. This is currently unconfigurable, - but if you desperately need it to be so, please contact us." + for Mac OS X, dscl/dseditgroup are used. + + This is currently unconfigurable, but if you desperately need it + to be so, please contact us." + + feature :manages_members, + "For directories where membership is an attribute of groups not users." ensurable do desc "Create or remove the group." @@ -73,13 +80,28 @@ module Puppet return gid end end + + newproperty(:members, :array_matching => :all, :required_features => :manages_members) do + desc "The members of the group. For directory services where group + membership is stored in the group objects, not the users." + + def change_to_s(currentvalue, newvalue) + currentvalue = currentvalue.join(",") if currentvalue != :absent + newvalue = newvalue.join(",") + super(currentvalue, newvalue) + end + end + + newparam(:auth_membership) do + desc "whether the provider is authoritative for group membership." + defaultto true + end newparam(:name) do desc "The group name. While naming limitations vary by system, it is advisable to keep the name to the degenerate limitations, which is a maximum of 8 characters beginning with a letter." - isnamevar end -- cgit From 3421954444f4c06d6e7b80430f89fccf56343fe2 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 25 Nov 2008 22:20:39 -0600 Subject: Fixing #1755 - handling fully qualified classes correctly. This involves lexing '::class' tokens along with correctly looking them up from the Resource::Reference class. Signed-off-by: Luke Kanies --- lib/puppet/parser/lexer.rb | 2 +- lib/puppet/parser/resource/reference.rb | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb index 69a46d0c1..a7b87e6d1 100644 --- a/lib/puppet/parser/lexer.rb +++ b/lib/puppet/parser/lexer.rb @@ -134,7 +134,7 @@ class Puppet::Parser::Lexer '*' => :TIMES, '<<' => :LSHIFT, '>>' => :RSHIFT, - %r{([a-z][-\w]*::)+[a-z][-\w]*} => :CLASSNAME, + %r{([a-z][-\w]*)?(::[a-z][-\w]*)+} => :CLASSNAME, # Require '::' in the class name, else we'd compete with NAME %r{((::){0,1}[A-Z][-\w]*)+} => :CLASSREF ) diff --git a/lib/puppet/parser/resource/reference.rb b/lib/puppet/parser/resource/reference.rb index c59748049..cb505d606 100644 --- a/lib/puppet/parser/resource/reference.rb +++ b/lib/puppet/parser/resource/reference.rb @@ -37,7 +37,7 @@ class Puppet::Parser::Resource::Reference < Puppet::ResourceReference if self.title == :main tmp = @scope.findclass("") else - unless tmp = @scope.findclass(self.title) + unless tmp = @scope.parser.classes[self.title] fail Puppet::ParseError, "Could not find class '%s'" % self.title end end @@ -46,8 +46,9 @@ class Puppet::Parser::Resource::Reference < Puppet::ResourceReference fail Puppet::ParseError, "Could not find node '%s'" % self.title end else # normal definitions - # We have to swap these variables around so the errors are right. - tmp = @scope.finddefine(self.type) + # The resource type is capitalized, so we have to downcase. Really, + # we should have a better interface for finding these, but eh. + tmp = @scope.parser.definitions[self.type.downcase] end if tmp -- cgit From 5742966d79206cb5f26cc61c20b065df876f540e Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 26 Nov 2008 00:44:47 -0600 Subject: Fixing #1743 - defined types get catalogs too. I wasn't adding the catalog to the defined types when creating the transportable objects. Signed-off-by: Luke Kanies --- lib/puppet/transportable.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index d203b5928..41c51fde6 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -53,6 +53,7 @@ module Puppet Puppet.debug "Defining %s on %s" % [param, ref] trans[param] = value } + trans.catalog = self.catalog Puppet::Type::Component.create(trans) end -- cgit From 965c08d8c053996779a6f94e1ea5c3c1863907cd Mon Sep 17 00:00:00 2001 From: Thom May Date: Tue, 2 Sep 2008 17:59:59 -0400 Subject: Slight denormalisation to store a host's environment as a first class object in the database Fixes: #1392 --- lib/puppet/rails/database/002_add_environment_to_host.rb | 9 +++++++++ lib/puppet/rails/database/schema.rb | 1 + lib/puppet/rails/host.rb | 4 ++++ 3 files changed, 14 insertions(+) create mode 100644 lib/puppet/rails/database/002_add_environment_to_host.rb (limited to 'lib') diff --git a/lib/puppet/rails/database/002_add_environment_to_host.rb b/lib/puppet/rails/database/002_add_environment_to_host.rb new file mode 100644 index 000000000..4593a06f7 --- /dev/null +++ b/lib/puppet/rails/database/002_add_environment_to_host.rb @@ -0,0 +1,9 @@ +class AddEnvironmentToHost < ActiveRecord::Migration + def self.up + add_column :hosts, :environment, :string + end + + def self.down + remove_column :hosts, :environment + end +end diff --git a/lib/puppet/rails/database/schema.rb b/lib/puppet/rails/database/schema.rb index f3ad2c11e..246bec482 100644 --- a/lib/puppet/rails/database/schema.rb +++ b/lib/puppet/rails/database/schema.rb @@ -54,6 +54,7 @@ class Puppet::Rails::Schema create_table :hosts do |t| t.column :name, :string, :null => false t.column :ip, :string + t.column :environment, :string t.column :last_compile, :datetime t.column :last_freshcheck, :datetime t.column :last_report, :datetime diff --git a/lib/puppet/rails/host.rb b/lib/puppet/rails/host.rb index 626edaa88..187dc657a 100644 --- a/lib/puppet/rails/host.rb +++ b/lib/puppet/rails/host.rb @@ -43,6 +43,10 @@ class Puppet::Rails::Host < ActiveRecord::Base host.ip = ip end + if env = node.environment + host.environment = env + end + # Store the facts into the database. host.setfacts node.parameters -- cgit From 2764ab4a991cca94d8ae41b42db2e3b5398951ed Mon Sep 17 00:00:00 2001 From: Thom May Date: Wed, 26 Nov 2008 15:17:02 -0500 Subject: Rename migration so it's still applied --- lib/puppet/rails/database/002_add_environment_to_host.rb | 9 --------- lib/puppet/rails/database/003_add_environment_to_host.rb | 9 +++++++++ 2 files changed, 9 insertions(+), 9 deletions(-) delete mode 100644 lib/puppet/rails/database/002_add_environment_to_host.rb create mode 100644 lib/puppet/rails/database/003_add_environment_to_host.rb (limited to 'lib') diff --git a/lib/puppet/rails/database/002_add_environment_to_host.rb b/lib/puppet/rails/database/002_add_environment_to_host.rb deleted file mode 100644 index 4593a06f7..000000000 --- a/lib/puppet/rails/database/002_add_environment_to_host.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddEnvironmentToHost < ActiveRecord::Migration - def self.up - add_column :hosts, :environment, :string - end - - def self.down - remove_column :hosts, :environment - end -end diff --git a/lib/puppet/rails/database/003_add_environment_to_host.rb b/lib/puppet/rails/database/003_add_environment_to_host.rb new file mode 100644 index 000000000..4593a06f7 --- /dev/null +++ b/lib/puppet/rails/database/003_add_environment_to_host.rb @@ -0,0 +1,9 @@ +class AddEnvironmentToHost < ActiveRecord::Migration + def self.up + add_column :hosts, :environment, :string + end + + def self.down + remove_column :hosts, :environment + end +end -- cgit From 4f67a7c26a5e740fd672b3c4f17e0fd366154840 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 28 Nov 2008 08:51:21 +1100 Subject: Fixed #1776 - Trivial fix for gentoo service provider --- lib/puppet/provider/service/gentoo.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/provider/service/gentoo.rb b/lib/puppet/provider/service/gentoo.rb index c5ba7b5f1..d84aaf6a8 100644 --- a/lib/puppet/provider/service/gentoo.rb +++ b/lib/puppet/provider/service/gentoo.rb @@ -35,7 +35,7 @@ Puppet::Type.type(:service).provide :gentoo, :parent => :init do return :false unless line # If it's enabled then it will print output showing service | runlevel - if output =~ /#{@resource[:name]}\s*|\s*default/ + if output =~ /#{@resource[:name]}\s*\|\s*default/ return :true else return :false -- cgit From 81a91a7321f1a0c664e04cc2b7045819a970c18f Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 28 Nov 2008 08:57:18 +1100 Subject: Documentation fix for daemontools provider --- lib/puppet/provider/service/daemontools.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/service/daemontools.rb b/lib/puppet/provider/service/daemontools.rb index db4a7cdf8..46729e1b1 100644 --- a/lib/puppet/provider/service/daemontools.rb +++ b/lib/puppet/provider/service/daemontools.rb @@ -5,14 +5,14 @@ Puppet::Type.type(:service).provide :daemontools, :parent => :base do desc """ Daemontools service management. This provider manages daemons running supervised by D.J.Bernstein daemontools. -It tries to detect the service directory, with by order of preference: +It tries to detect the service directory, with by order of preference:: * /service * /etc/service * /var/lib/svscan The daemon directory should be placed in a directory that can be -by default in: +by default in:: * /var/lib/service * /etc @@ -24,13 +24,14 @@ or this can be overriden in the service resource parameters:: provider => \"daemontools\", path => \"/path/to/daemons\"; } -This provider supports out of the box: +This provider supports out of the box:: * start/stop (mapped to enable/disable) * enable/disable * restart * status + """ commands :svc => "/usr/bin/svc" -- cgit From 1f52795c43e085e73290fbfccffafcc8cca1bc70 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 28 Nov 2008 08:58:34 +1100 Subject: Documentation fix for runit provider --- lib/puppet/provider/service/runit.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/service/runit.rb b/lib/puppet/provider/service/runit.rb index 5c2b9c733..e8a0da18f 100644 --- a/lib/puppet/provider/service/runit.rb +++ b/lib/puppet/provider/service/runit.rb @@ -5,14 +5,14 @@ Puppet::Type.type(:service).provide :runit, :parent => :daemontools do desc """ Runit service management. This provider manages daemons running supervised by Runit. -It tries to detect the service directory, with by order of preference: +It tries to detect the service directory, with by order of preference:: * /service * /var/service * /etc/service The daemon directory should be placed in a directory that can be -by default in: +by default in:: * /etc/sv @@ -23,12 +23,14 @@ or this can be overriden in the service resource parameters:: provider => \"runit\", path => \"/path/to/daemons\"; } -This provider supports out of the box: +This provider supports out of the box:: * start/stop * enable/disable * restart * status + + """ commands :sv => "/usr/bin/sv" -- cgit From a45c6b1b9f9675aa76e96fa053742af621887591 Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Wed, 26 Nov 2008 07:54:32 -0800 Subject: fix bug with numeric uid/gid in directoryservice provider. doc string cleanups --- lib/puppet/provider/nameservice/directoryservice.rb | 16 ++++++++++++++-- lib/puppet/type/computer.rb | 8 +++++--- lib/puppet/type/group.rb | 21 ++++----------------- 3 files changed, 23 insertions(+), 22 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb index 0e8002c31..308f5192e 100644 --- a/lib/puppet/provider/nameservice/directoryservice.rb +++ b/lib/puppet/provider/nameservice/directoryservice.rb @@ -147,8 +147,20 @@ class DirectoryService < Puppet::Provider::NameService ds_attribute = key.sub("dsAttrTypeStandard:", "") next unless (@@ds_to_ns_attribute_map.keys.include?(ds_attribute) and type_properties.include? @@ds_to_ns_attribute_map[ds_attribute]) ds_value = dscl_plist[key] - if not @@ds_to_ns_attribute_map[ds_attribute] == :members # only members uses arrays so far - ds_value = ds_value[0] + case @@ds_to_ns_attribute_map[ds_attribute] + when :members: + ds_value = ds_value # only members uses arrays so far + when :gid, :uid: + # OS X stores objects like uid/gid as strings. + # Try casting to an integer for these cases to be + # consistent with the other providers and the group type + # validation + begin + ds_value = Integer(ds_value[0]) + rescue ArgumentError + ds_value = ds_value[0] + end + else ds_value = ds_value[0] end attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value end diff --git a/lib/puppet/type/computer.rb b/lib/puppet/type/computer.rb index 0c0a70900..ccbcadf72 100644 --- a/lib/puppet/type/computer.rb +++ b/lib/puppet/type/computer.rb @@ -29,23 +29,25 @@ Puppet::Type.newtype(:computer) do end newproperty(:ensure, :parent => Puppet::Property::Ensure) do + desc "Control the existences of this computer record. Set this attribute to + ``present`` to ensure the computer record exists. Set it to ``absent`` + to delete any computer records with this name" newvalue(:present) do provider.create end newvalue(:absent) do - Puppet.notice "prop ensure = absent" provider.delete end end newparam(:name) do - desc "The " + desc "The authoritative 'short' name of the computer record." isnamevar end newparam(:realname) do - desc "realname" + desc "The 'long' name of the computer record." end newproperty(:en_address) do diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index 1167962fe..e3507ad5c 100755 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -1,10 +1,3 @@ -# Manage Unix groups. This class is annoyingly complicated; There -# is some variety in whether systems use 'groupadd' or 'addgroup', but OS X -# significantly complicates the picture by using NetInfo. Eventually we -# will also need to deal with systems that have their groups hosted elsewhere -# (e.g., in LDAP). That will likely only be a problem for OS X, since it -# currently does not use the POSIX interfaces, since lookupd's cache screws -# things up. require 'etc' require 'facter' @@ -14,16 +7,10 @@ module Puppet @doc = "Manage groups. On most platforms this can only create groups. Group membership must be managed on individual users. - On OS X, group membership is managed as an attribute of the group. - This resource type uses the prescribed native tools for creating - groups and generally uses POSIX APIs for retrieving information - about them. It does not directly modify ``/etc/group`` or anything. - - For most platforms, the tools used are ``groupadd`` and its ilk; - for Mac OS X, dscl/dseditgroup are used. - - This is currently unconfigurable, but if you desperately need it - to be so, please contact us." + On some platforms such as OS X, group membership is managed as an + attribute of the group, not the user record. Providers must have + the feature 'manages_members' to manage the 'members' property of + a group record." feature :manages_members, "For directories where membership is an attribute of groups not users." -- cgit From 3a395095d7b913661484bc5caeeb90cb34cd5b3f Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Wed, 26 Nov 2008 13:59:55 -0800 Subject: make sure only types that have passwords search for the password --- lib/puppet/provider/nameservice/directoryservice.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb index 308f5192e..4e21d4169 100644 --- a/lib/puppet/provider/nameservice/directoryservice.rb +++ b/lib/puppet/provider/nameservice/directoryservice.rb @@ -169,7 +169,9 @@ class DirectoryService < Puppet::Provider::NameService # stored in the user record. It is stored at a path that involves the # UUID of the user record for non-Mobile local acccounts. # Mobile Accounts are out of scope for this provider for now - attribute_hash[:password] = self.get_password(attribute_hash[:guid]) + if @resource_type.validproperties.include?(:password) + attribute_hash[:password] = self.get_password(attribute_hash[:guid]) + end return attribute_hash end -- cgit From 3c4efa7ec2043043d72d325e67fe5bd6098e0413 Mon Sep 17 00:00:00 2001 From: Thom May Date: Wed, 26 Nov 2008 14:58:27 -0500 Subject: Fixes #1773 - no longer check for absolute paths --- lib/puppet/module.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 9385812b1..7bf35ac18 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -24,7 +24,7 @@ class Puppet::Module def self.templatepath(environment = nil) dirs = Puppet.settings.value(:templatedir, environment).split(":") dirs.select do |p| - p =~ /^#{File::SEPARATOR}/ && File::directory?(p) + File::directory?(p) end end -- cgit From d8c741f9d3b07b11f11af0765d740d9e78889794 Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Wed, 26 Nov 2008 23:18:45 +0100 Subject: Fix #1741 - Puppet::Parser::Functions rmfunctions and unit test --- lib/puppet/parser/functions.rb | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'lib') diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 5fb0439da..b1cd0d083 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -54,6 +54,20 @@ module Functions end end + # Remove a function added by newfunction + def self.rmfunction(name) + name = symbolize(name) + + unless @functions.include? name + raise Puppet::DevError, "Function %s is not defined" % name + end + + @functions.delete(name) + + fname = "function_" + name.to_s + Puppet::Parser::Scope.send(:remove_method, fname) + end + # Determine if a given name is a function def self.function(name) name = symbolize(name) -- cgit From cc45c435b7f62f83f0d0cd4b952a5c05ccfaaac9 Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Sun, 23 Nov 2008 00:01:04 +0100 Subject: Fix #1741 - refactor TemplateWrapper, test for template function --- lib/puppet/parser/functions/template.rb | 5 ++-- lib/puppet/parser/templatewrapper.rb | 52 +++++++++++++++++++-------------- 2 files changed, 33 insertions(+), 24 deletions(-) (limited to 'lib') diff --git a/lib/puppet/parser/functions/template.rb b/lib/puppet/parser/functions/template.rb index e62c3b326..2eaace1d7 100644 --- a/lib/puppet/parser/functions/template.rb +++ b/lib/puppet/parser/functions/template.rb @@ -9,10 +9,11 @@ Puppet::Parser::Functions::newfunction(:template, :type => :rvalue, :doc => # Use a wrapper, so the template can't get access to the full # Scope object. debug "Retrieving template %s" % file - wrapper = Puppet::Parser::TemplateWrapper.new(self, file) + wrapper = Puppet::Parser::TemplateWrapper.new(self) + wrapper.file = file begin - wrapper.result() + wrapper.result rescue => detail raise Puppet::ParseError, "Failed to parse template %s: %s" % diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index 036f6604e..55c7745ba 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -1,33 +1,18 @@ # A simple wrapper for templates, so they don't have full access to # the scope objects. class Puppet::Parser::TemplateWrapper - attr_accessor :scope, :file + attr_accessor :scope, :file, :string include Puppet::Util Puppet::Util.logmethods(self) - def initialize(scope, filename) + def initialize(scope) @__scope__ = scope - @__file__ = Puppet::Module::find_template(filename, scope.compiler.environment) - - unless FileTest.exists?(file) - raise Puppet::ParseError, - "Could not find template %s" % file - end - - # We'll only ever not have a parser in testing, but, eh. - if scope.parser - scope.parser.watch_file(file) - end end def scope @__scope__ end - def file - @__file__ - 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 @@ -77,11 +62,34 @@ class Puppet::Parser::TemplateWrapper end end - def result + def file=(filename) + @file = Puppet::Module::find_template(filename, scope.compiler.environment) + + unless FileTest.exists?(file) + raise Puppet::ParseError, + "Could not find template %s" % file + end + + # We'll only ever not have a parser in testing, but, eh. + if scope.parser + scope.parser.watch_file(file) + end + + @string = File.read(file) + end + + def result(string = nil) + if string + self.string = string + template_source = "inline template" + else + template_source = file + end + # Expose all the variables in our scope as instance variables of the # current object, making it possible to access them without conflict # to the regular methods. - benchmark(:debug, "Bound template variables for #{file}") do + benchmark(:debug, "Bound template variables for #{template_source}") do scope.to_hash.each { |name, value| if name.kind_of?(String) realname = name.gsub(/[^\w]/, "_") @@ -93,8 +101,8 @@ class Puppet::Parser::TemplateWrapper end result = nil - benchmark(:debug, "Interpolated template #{file}") do - template = ERB.new(File.read(file), 0, "-") + benchmark(:debug, "Interpolated template #{template_source}") do + template = ERB.new(self.string, 0, "-") result = template.result(binding) end @@ -102,7 +110,7 @@ class Puppet::Parser::TemplateWrapper end def to_s - "template[%s]" % file + "template[%s]" % (file ? file : "inline") end end -- cgit From 607958cb7fe178596930b1506b69807dc78d111b Mon Sep 17 00:00:00 2001 From: Brice Figureau Date: Sun, 23 Nov 2008 00:01:23 +0100 Subject: Fix #1741 - Add inline_template function --- lib/puppet/parser/functions/inline_template.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 lib/puppet/parser/functions/inline_template.rb (limited to 'lib') diff --git a/lib/puppet/parser/functions/inline_template.rb b/lib/puppet/parser/functions/inline_template.rb new file mode 100644 index 000000000..289740873 --- /dev/null +++ b/lib/puppet/parser/functions/inline_template.rb @@ -0,0 +1,21 @@ +Puppet::Parser::Functions::newfunction(:inline_template, :type => :rvalue, :doc => + "Evaluate a template string and return its value. See `the templating docs + `_ for more information. Note that + if multiple template strings are specified, their output is all concatenated + and returned as the output of the function.") do |vals| + require 'erb' + + vals.collect do |string| + # Use a wrapper, so the template can't get access to the full + # Scope object. + + wrapper = Puppet::Parser::TemplateWrapper.new(self) + begin + wrapper.result(string) + rescue => detail + raise Puppet::ParseError, + "Failed to parse inline template: %s" % + [detail] + end + end.join("") +end -- cgit From 0171e25ec457e9e9d078aabbea491bbcd03448bf Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 28 Nov 2008 23:58:04 -0600 Subject: Fixing #1749 - Splay now hopefully behaves "better" for small values. Signed-off-by: Luke Kanies --- lib/puppet/network/client/master.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb index 5e9422b7e..073d2e229 100644 --- a/lib/puppet/network/client/master.rb +++ b/lib/puppet/network/client/master.rb @@ -488,7 +488,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client return unless Puppet[:splay] return if splayed? - time = rand(Integer(Puppet[:splaylimit])) + time = rand(Integer(Puppet[:splaylimit] + 1)) Puppet.info "Sleeping for %s seconds (splay is enabled)" % time sleep(time) @splayed = true -- cgit From 65a60748526d66fb9c7f6fb7df6f011ad5c9a3a1 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Mon, 1 Dec 2008 12:58:04 +1100 Subject: Fixed #1695 - Solaris 10 zone provider doesn't properly handle unknown zone attributes in newer releases --- lib/puppet/provider/zone/solaris.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'lib') diff --git a/lib/puppet/provider/zone/solaris.rb b/lib/puppet/provider/zone/solaris.rb index be2dd97f9..24bbb99ec 100644 --- a/lib/puppet/provider/zone/solaris.rb +++ b/lib/puppet/provider/zone/solaris.rb @@ -12,6 +12,7 @@ Puppet::Type.type(:zone).provide(:solaris) do properties = {} line.split(":").each_with_index { |value, index| + next unless fields[index] properties[fields[index]] = value } -- cgit From 83b3a1ec2a98edeab12a5eb9c4b107a6fe08613c Mon Sep 17 00:00:00 2001 From: Nigel Kersten Date: Wed, 26 Nov 2008 13:16:52 -0800 Subject: Simplify launchd service provider and add tests --- lib/puppet/provider/package/appdmg.rb | 3 --- lib/puppet/provider/service/launchd.rb | 42 +++++++++++++++++++--------------- 2 files changed, 24 insertions(+), 21 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/package/appdmg.rb b/lib/puppet/provider/package/appdmg.rb index 2ee82a95d..ee8726cbc 100644 --- a/lib/puppet/provider/package/appdmg.rb +++ b/lib/puppet/provider/package/appdmg.rb @@ -12,9 +12,6 @@ # As a result, we store installed .app.dmg file names # in /var/db/.puppet_appdmg_installed_ -# require 'ruby-debug' -# Debugger.start - require 'puppet/provider/package' Puppet::Type.type(:package).provide(:appdmg, :parent => Puppet::Provider::Package) do desc "Package management which copies application bundles to a target." diff --git a/lib/puppet/provider/service/launchd.rb b/lib/puppet/provider/service/launchd.rb index e51451cac..11d7bd2b4 100644 --- a/lib/puppet/provider/service/launchd.rb +++ b/lib/puppet/provider/service/launchd.rb @@ -89,8 +89,8 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do # finds the path for a given label and returns the path and parsed plist # as an array of [path, plist]. Note plist is really a Hash here. - def self.plist_from_label(label) - job = self.jobsearch(label) + def plist_from_label(label) + job = self.class.jobsearch(label) job_path = job[label] job_plist = Plist::parse_xml(job_path) if not job_plist @@ -103,10 +103,8 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do def status # launchctl list exits zero if the job is loaded # and non-zero if it isn't. Simple way to check... - cmds = [] - cmds << :launchctl << "list" << @resource[:name] begin - execute(cmds) + launchctl :list, resource[:name] return :running rescue Puppet::ExecutionFailure return :stopped @@ -118,11 +116,10 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do # conditionally enable at load, then disable by modifying the plist file # directly. def start - job = self.class.jobsearch(@resource[:name]) - job_path = job[@resource[:name]] + job_path, job_plist = plist_from_label(resource[:name]) did_enable_job = false cmds = [] - cmds << :launchctl << "load" + cmds << :launchctl << :load if self.enabled? == :false # launchctl won't load disabled jobs cmds << "-w" did_enable_job = true @@ -131,24 +128,33 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do begin execute(cmds) rescue Puppet::ExecutionFailure - raise Puppet::Error.new("Unable to start service: %s at path: %s" % [@resource[:name], job_path]) + raise Puppet::Error.new("Unable to start service: %s at path: %s" % [resource[:name], job_path]) end # As load -w clears the Disabled flag, we need to add it in after - if did_enable_job and @resource[:enable] == :false + if did_enable_job and resource[:enable] == :false self.disable end end def stop - job = self.class.jobsearch(@resource[:name]) - job_path = job[@resource[:name]] + job_path, job_plist = plist_from_label(resource[:name]) + did_disable_job = false cmds = [] - cmds << :launchctl << "unload" << job_path + cmds << :launchctl << :unload + if self.enabled? == :true # keepalive jobs can't be stopped without disabling + cmds << "-w" + did_disable_job = true + end + cmds << job_path begin execute(cmds) rescue Puppet::ExecutionFailure - raise Puppet::Error.new("Unable to stop service: %s at path: %s" % [@resource[:name], job_path]) + raise Puppet::Error.new("Unable to stop service: %s at path: %s" % [resource[:name], job_path]) + end + # As unload -w sets the Disabled flag, we need to add it in after + if did_disable_job and resource[:enable] == :true + self.enable end end @@ -156,7 +162,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do # launchd jobs are enabled by default. They are only disabled if the key # "Disabled" is set to true, but it can also be set to false to enable it. def enabled? - job_path, job_plist = self.class.plist_from_label(@resource[:name]) + job_path, job_plist = plist_from_label(resource[:name]) if job_plist.has_key?("Disabled") if job_plist["Disabled"] # inverse of disabled is enabled return :false @@ -170,8 +176,8 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do # rather than dealing with launchctl as it is unable to change the Disabled flag # without actually loading/unloading the job. def enable - job_path, job_plist = self.class.plist_from_label(@resource[:name]) - if not self.enabled? + job_path, job_plist = plist_from_label(resource[:name]) + if self.enabled? == :false job_plist.delete("Disabled") Plist::Emit.save_plist(job_plist, job_path) end @@ -179,7 +185,7 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do def disable - job_path, job_plist = self.class.plist_from_label(@resource[:name]) + job_path, job_plist = plist_from_label(resource[:name]) job_plist["Disabled"] = true Plist::Emit.save_plist(job_plist, job_path) end -- cgit From 9329c95d6fbb2df5e8b754620427645f6eae69b9 Mon Sep 17 00:00:00 2001 From: Jeffrey McCune Date: Fri, 28 Nov 2008 22:39:22 -0500 Subject: type/mcx.rb Feature #1026 - MCX Type Added new MCX type and base test. This type manages MCX settings on DirectoryService nodes. These settings take the form of plist XML documents attached to Users, Groups, and Computers in DirectoryService. --- lib/puppet/provider/mcx/mcxcontent.rb | 199 ++++++++++++++++++++++++++++++++++ lib/puppet/type/mcx.rb | 114 +++++++++++++++++++ 2 files changed, 313 insertions(+) create mode 100644 lib/puppet/provider/mcx/mcxcontent.rb create mode 100644 lib/puppet/type/mcx.rb (limited to 'lib') diff --git a/lib/puppet/provider/mcx/mcxcontent.rb b/lib/puppet/provider/mcx/mcxcontent.rb new file mode 100644 index 000000000..fdcc8cc5d --- /dev/null +++ b/lib/puppet/provider/mcx/mcxcontent.rb @@ -0,0 +1,199 @@ +#-- +# Copyright (C) 2008 Jeffrey J McCune. + +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# Author: Jeff McCune + +require 'tempfile' + +Puppet::Type.type(:mcx).provide :mcxcontent, :parent => Puppet::Provider do + + desc "MCX Settings management using DirectoryService on OS X. + +This provider manages the entire MCXSettings attribute available +to some directory services nodes. This management is 'all or nothing' +in that discrete application domain key value pairs are not managed +by this provider. + +It is recommended to use WorkGroup Manager to configure Users, Groups, +Computers, or ComputerLists, then use 'ralsh mcx' to generate a puppet +manifest from the resulting configuration. + +Original Author: Jeff McCune (mccune.jeff@gmail.com)" + + # This provides a mapping of puppet types to DirectoryService + # type strings. + TypeMap = { + :user => "Users", + :group => "Groups", + :computer => "Computers", + :computergroup => "ComputerGroups", + } + + class MCXContentProviderException < Exception + + end + + commands :dscl => "/usr/bin/dscl" + confine :operatingsystem => :darwin + defaultfor :operatingsystem => :darwin + + # self.instances is all important. + # This is the only class method, it returns + # an array of instances of this class. + def self.instances + mcx_list = [] + for ds_type in TypeMap.keys + ds_path = "/Local/Default/#{TypeMap[ds_type]}" + output = dscl 'localhost', '-list', ds_path + member_list = output.split + for ds_name in member_list + content = mcxexport(ds_type, ds_name) + if content.empty? + Puppet.debug "/#{TypeMap[ds_type]}/#{ds_name} has no MCX data." + else + # This node has MCX data. + rsrc = self.new(:name => "/#{TypeMap[ds_type]}/#{ds_name}", + :ds_type => ds_type, + :ds_name => ds_name, + :content => content) + mcx_list << rsrc + end + end + end + return mcx_list + end + + private + + # mcxexport is used by instances, and therefore + # a class method. + def self.mcxexport(ds_type, ds_name) + ds_t = TypeMap[ds_type] + ds_n = ds_name.to_s + ds_path = "/Local/Default/#{ds_t}/#{ds_n}" + + dscl 'localhost', '-mcxexport', ds_path + end + + def mcximport(ds_type, ds_name, val) + ds_t = TypeMap[ds_type] + ds_n = ds_name.to_s + ds_path = "/Local/Default/#{ds_t}/#{ds_name}" + + tmp = Tempfile.new('puppet_mcx') + begin + tmp << val + tmp.flush + dscl 'localhost', '-mcximport', ds_path, tmp.path + ensure + tmp.close + tmp.unlink + end + end + + # Given the resource name string, parse ds_type out. + def parse_type(name) + tmp = name.split('/')[1] + if ! tmp.is_a? String + raise MCXContentProviderException, + "Coult not parse ds_type from resource name '#{name}'. Specify with ds_type parameter." + end + # De-pluralize and downcase. + tmp = tmp.chop.downcase.to_sym + if not TypeMap.keys.member? tmp + raise MCXContentProviderException, + "Coult not parse ds_type from resource name '#{name}'. Specify with ds_type parameter." + end + return tmp + end + + # Given the resource name string, parse ds_name out. + def parse_name(name) + ds_name = name.split('/')[2] + if ! ds_name.is_a? String + raise MCXContentProviderException, + "Could not parse ds_name from resource name '#{name}'. Specify with ds_name parameter." + end + return ds_name + end + + # Gather ds_type and ds_name from resource or + # parse it out of the name. + # This is a private instance method, not a class method. + def get_dsparams + ds_type = resource[:ds_type] + if ds_type.nil? + ds_type = parse_type(resource[:name]) + end + + ds_name = resource[:ds_name] + if ds_name.nil? + ds_name = parse_name(resource[:name]) + end + + rval = { + :ds_type => ds_type.to_sym, + :ds_name => ds_name, + } + + return rval + + end + + public + + def create + self.content=(resource[:content]) + end + + def destroy + ds_parms = get_dsparams + ds_t = TypeMap[ds_parms[:ds_type]] + ds_n = ds_parms[:ds_name].to_s + ds_path = "/Local/Default/#{ds_t}/#{ds_n}" + + dscl 'localhost', '-mcxdelete', ds_path + end + + def exists? + # JJM Just re-use the content method and see if it's empty. + begin + mcx = content + rescue Puppet::ExecutionFailure => e + return false + end + has_mcx = ! mcx.empty? + return has_mcx + end + + def content + ds_parms = get_dsparams + mcx = self.class.mcxexport(ds_parms[:ds_type], + ds_parms[:ds_name]) + return mcx + end + + def content=(value) + # dscl localhost -mcximport + ds_parms = get_dsparams + mcx = mcximport(ds_parms[:ds_type], + ds_parms[:ds_name], + resource[:content]) + return mcx + end + +end diff --git a/lib/puppet/type/mcx.rb b/lib/puppet/type/mcx.rb new file mode 100644 index 000000000..ec33afd13 --- /dev/null +++ b/lib/puppet/type/mcx.rb @@ -0,0 +1,114 @@ +#-- +# Copyright (C) 2008 Jeffrey J McCune. + +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +# Author: Jeff McCune + +Puppet::Type.newtype(:mcx) do + + @doc = "MCX object management using DirectoryService on OS X. + +Original Author: Jeff McCune + +The default provider of this type merely manages the XML plist as +reported by the dscl -mcxexport command. This is similar to the +content property of the file type in Puppet. + +The recommended method of using this type is to use Work Group Manager +to manage users and groups on the local computer, record the resulting +puppet manifest using the command 'ralsh mcx' then deploying this +to other machines. +" + feature :manages_content, \ + "The provider can manage MCXSettings as a string.", + :methods => [:content, :content=] + + ensurable do + desc "Create or remove the MCX setting." + + newvalue(:present) do + provider.create + end + + newvalue(:absent) do + provider.destroy + end + + end + + newparam(:name) do + desc "The name of the resource being managed. + The default naming convention follows Directory Service paths: + '/Computers/localhost' + '/Groups/admin' + '/Users/localadmin' + + The ds_type and ds_name type parameters are not necessary if the + default naming convention is followed." + isnamevar + end + + newparam(:ds_type) do + + desc "The DirectoryService type this MCX setting attaches to." + + newvalues(:user, :group, :computer, :computerlist) + + end + + newparam(:ds_name) do + desc "The name to attach the MCX Setting to. + e.g. 'localhost' when ds_type => computer. This setting is not + required, as it may be parsed so long as the resource name is + parseable. e.g. /Groups/admin where 'group' is the dstype." + end + + newproperty(:content, :required_features => :manages_content) do + desc "The XML Plist. The value of MCXSettings in DirectoryService. + This is the standard output from the system command: + dscl localhost -mcxexport /Local/Default// + Note that ds_type is capitalized and plural in the dscl command." + end + + # JJM Yes, this is not DRY at all. Because of the code blocks + # autorequire must be done this way. I think. + + def setup_autorequire(type) + # value returns a Symbol + name = value(:name) + ds_type = value(:ds_type) + ds_name = value(:ds_name) + if ds_type == type + rval = [ ds_name.to_s ] + else + rval = [ ] + end + rval + end + + autorequire(:user) do + setup_autorequire(:user) + end + + autorequire(:group) do + setup_autorequire(:group) + end + + autorequire(:computer) do + setup_autorequire(:computer) + end + +end -- cgit From a219c88866d8f91672b1830cc519da68a0d9b2c7 Mon Sep 17 00:00:00 2001 From: Andrew Shafer Date: Thu, 27 Nov 2008 01:22:36 -0700 Subject: Solaris doesn't have a native tool to set hashed passwords Added support for passwords by directly editing /etc/shadow (I tried to make it work with libshadow, but considering it is not packaged for Solaris and adds little benefit, I decided against it) password and password= are now defined on the default Solaris provider --- lib/puppet/provider/user/user_role_add.rb | 35 ++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/user/user_role_add.rb b/lib/puppet/provider/user/user_role_add.rb index 00fc24b3a..1be3fa6f1 100644 --- a/lib/puppet/provider/user/user_role_add.rb +++ b/lib/puppet/provider/user/user_role_add.rb @@ -22,11 +22,7 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd do value !~ /\s/ end - has_features :manages_homedir, :allows_duplicates, :manages_solaris_rbac - - if Puppet.features.libshadow? - has_feature :manages_passwords - end + has_features :manages_homedir, :allows_duplicates, :manages_solaris_rbac, :manages_passwords #must override this to hand the keyvalue pairs def add_properties @@ -152,5 +148,34 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd do def keys=(keys_hash) run([command(:modify)] + build_keys_cmd(keys_hash) << @resource[:name], "modify attribute key pairs") end + + #Read in /etc/shadow, find the line for this user (skipping comments, because who knows) and return the hashed pw (the second entry) + #No abstraction, all esoteric knowledge of file formats, yay + def password + #got perl? + if ary = File.readlines("/etc/shadow").reject { |r| r =~ /^[^\w]/}.collect { |l| l.split(':')[0..1] }.find { |user, passwd| user == @resource[:name] } + pass = ary[1] + end + pass + end + + #Read in /etc/shadow, find the line for our used and rewrite it with the new pw + #Smooth like 80 grit + def password=(cryptopw) + File.open("/etc/shadow", "r") do |shadow| + File.open("/etc/shadow_tmp", "w", 0600) do |shadow_tmp| + while line = shadow.gets do + line_arr = line.split(':') + if line_arr[0] = @resource[:name] + line_arr[1] = cryptopw + line = line_arr.join(':') + end + shadow_tmp.print line + end + end + end + + File.rename("/etc/shadow_tmp", "/etc/shadow") + end end -- cgit From 88edf66cf8f80b2b35a99854ffa40a29aeafccbb Mon Sep 17 00:00:00 2001 From: Andrew Shafer Date: Thu, 27 Nov 2008 02:31:27 -0700 Subject: == is not = --- lib/puppet/provider/user/user_role_add.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/provider/user/user_role_add.rb b/lib/puppet/provider/user/user_role_add.rb index 1be3fa6f1..f7ae066bd 100644 --- a/lib/puppet/provider/user/user_role_add.rb +++ b/lib/puppet/provider/user/user_role_add.rb @@ -166,7 +166,7 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd do File.open("/etc/shadow_tmp", "w", 0600) do |shadow_tmp| while line = shadow.gets do line_arr = line.split(':') - if line_arr[0] = @resource[:name] + if line_arr[0] == @resource[:name] line_arr[1] = cryptopw line = line_arr.join(':') end -- cgit From 047e5d073c2362da95553c1778b9eb5176b0c05b Mon Sep 17 00:00:00 2001 From: Andrew Shafer Date: Thu, 27 Nov 2008 03:09:03 -0700 Subject: Handle password when user is created strip out the -p and call password= after the thing is done --- lib/puppet/provider/user/user_role_add.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/provider/user/user_role_add.rb b/lib/puppet/provider/user/user_role_add.rb index f7ae066bd..65bd63973 100644 --- a/lib/puppet/provider/user/user_role_add.rb +++ b/lib/puppet/provider/user/user_role_add.rb @@ -28,7 +28,8 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd do def add_properties cmd = [] Puppet::Type.type(:user).validproperties.each do |property| - next if property == :ensure + #skip the password because we can't create it with the solaris useradd + next if property == :ensure || property == :password # the value needs to be quoted, mostly because -c might # have spaces in it if value = @resource.should(property) and value != "" @@ -83,6 +84,10 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd do else run(addcmd, "create") end + # added to handle case when password is specified + if @resource[:password] + self.password = @resource[:password] + end end def destroy -- cgit From 0a40668b348d210cb6cb9e9c25320ccc981ae422 Mon Sep 17 00:00:00 2001 From: Andrew Shafer Date: Sun, 30 Nov 2008 23:54:57 -0700 Subject: Feature #1783 - Add ZFS support Types and providers to manage zfs and zpool --- lib/puppet/provider/zfs/solaris.rb | 56 ++++++++++++++++++ lib/puppet/provider/zpool/solaris.rb | 112 +++++++++++++++++++++++++++++++++++ lib/puppet/type/zfs.rb | 45 ++++++++++++++ lib/puppet/type/zpool.rb | 60 +++++++++++++++++++ 4 files changed, 273 insertions(+) create mode 100644 lib/puppet/provider/zfs/solaris.rb create mode 100644 lib/puppet/provider/zpool/solaris.rb create mode 100755 lib/puppet/type/zfs.rb create mode 100755 lib/puppet/type/zpool.rb (limited to 'lib') diff --git a/lib/puppet/provider/zfs/solaris.rb b/lib/puppet/provider/zfs/solaris.rb new file mode 100644 index 000000000..4d382cfad --- /dev/null +++ b/lib/puppet/provider/zfs/solaris.rb @@ -0,0 +1,56 @@ +Puppet::Type.type(:zfs).provide(:solaris) do + desc "Provider for Solaris zfs." + + commands :zfs => "/usr/sbin/zfs" + defaultfor :operatingsystem => :solaris + + def add_properties + properties = [] + Puppet::Type.type(:zfs).validproperties.each do |property| + next if property == :ensure + if value = @resource[property] and value != "" + properties << "-o" << "#{property}=#{value}" + end + end + properties + end + + def arrayify_second_line_on_whitespace(text) + if second_line = text.split("\n")[1] + second_line.split("\s") + else + [] + end + end + + def create + zfs *([:create] + add_properties + [@resource[:name]]) + end + + def delete + zfs(:destroy, @resource[:name]) + end + + def exists? + if zfs(:list).split("\n").detect { |line| line.split("\s")[0] == @resource[:name] } + true + else + false + end + end + + [:mountpoint, :compression, :copies, :quota, :reservation, :sharenfs, :snapdir].each do |field| + define_method(field) do + #special knowledge of format + #the command returns values in this format with the header + #NAME PROPERTY VALUE SOURCE + arrayify_second_line_on_whitespace(zfs(:get, field, @resource[:name]))[2] + end + + define_method(field.to_s + "=") do |should| + zfs(:set, "#{field}=#{should}", @resource[:name]) + end + end + +end + diff --git a/lib/puppet/provider/zpool/solaris.rb b/lib/puppet/provider/zpool/solaris.rb new file mode 100644 index 000000000..d680a5f63 --- /dev/null +++ b/lib/puppet/provider/zpool/solaris.rb @@ -0,0 +1,112 @@ +Puppet::Type.type(:zpool).provide(:solaris) do + desc "Provider for Solaris zpool." + + commands :zpool => "/usr/sbin/zpool" + defaultfor :operatingsystem => :solaris + + def process_zpool_data(pool_array) + if pool_array == [] + return Hash.new(:absent) + end + #get the name and get rid of it + pool = Hash.new([]) + pool[:pool] = pool_array[0] + pool_array.shift + + #order matters here :( + tmp = [] + + pool_array.reverse.each_with_index do |value, i| + case value + when "spares": pool[:spare] = tmp.reverse and tmp.clear + when "logs": pool[:log] = tmp.reverse and tmp.clear + when "mirror", "raidz1", "raidz2": + sym = value == "mirror" ? :mirror : :raidz + pool[sym].unshift(tmp.reverse.join(' ')) + pool[:raid_parity] = "raidz2" if value == "raidz2" + tmp.clear + else + tmp << value + pool[:disk] = tmp.reverse if i == 0 + end + end + + pool + end + + def get_pool_data + #this is all voodoo dependent on the output from zpool + zpool_data = %x{ zpool status #{@resource[:pool]}}.split("\n").select { |line| line.index("\t") == 0 }.collect { |l| l.strip.split("\s")[0] } + zpool_data.shift + zpool_data + end + + def current_pool + unless (defined?(@current_pool) and @current_pool) + @current_pool = process_zpool_data(get_pool_data) + end + @current_pool + end + + def flush + @current_pool= nil + end + + #Adds log and spare + def build_named(name) + if prop = @resource[name.intern] + [name] + prop.collect { |p| p.split(' ') }.flatten + else + [] + end + end + + #query for parity and set the right string + def raidzarity + @resource[:raid_parity] ? @resource[:raid_parity] : "raidz1" + end + + #handle mirror or raid + def handle_multi_arrays(prefix, array) + array.collect{ |a| [prefix] + a.split(' ') }.flatten + end + + #builds up the vdevs for create command + def build_vdevs + if disk = @resource[:disk] + disk.collect { |d| d.split(' ') }.flatten + elsif mirror = @resource[:mirror] + handle_multi_arrays("mirror", mirror) + elsif raidz = @resource[:raidz] + handle_multi_arrays(raidzarity, raidz) + end + end + + def create + zpool(*([:create, @resource[:pool]] + build_vdevs + build_named("spare") + build_named("log"))) + end + + def delete + zpool :destroy, @resource[:pool] + end + + def exists? + if current_pool[:pool] == :absent + false + else + true + end + end + + [:disk, :mirror, :raidz, :log, :spare].each do |field| + define_method(field) do + current_pool[field] + end + + define_method(field.to_s + "=") do |should| + Puppet.warning "NO CHANGES BEING MADE: zpool %s does not match, should be '%s' currently is '%s'" % [field, should, current_pool[field]] + end + end + +end + diff --git a/lib/puppet/type/zfs.rb b/lib/puppet/type/zfs.rb new file mode 100755 index 000000000..d3af3a461 --- /dev/null +++ b/lib/puppet/type/zfs.rb @@ -0,0 +1,45 @@ +module Puppet + newtype(:zfs) do + @doc = "Manage zfs. Create destroy and set properties on zfs instances." + + ensurable + + newparam(:name) do + desc "The full name for this filesystem. (including the zpool)" + end + + newproperty(:mountpoint) do + desc "The mountpoint property." + end + + newproperty(:compression) do + desc "The compression property." + end + + newproperty(:copies) do + desc "The copies property." + end + + newproperty(:quota) do + desc "The quota property." + end + + newproperty(:reservation) do + desc "The reservation property." + end + + newproperty(:sharenfs) do + desc "The sharenfs property." + end + + newproperty(:snapdir) do + desc "The sharenfs property." + end + + autorequire(:zpool) do + #strip the zpool off the zfs name and autorequire it + [@parameters[:name].value.split('/')[0]] + end + end +end + diff --git a/lib/puppet/type/zpool.rb b/lib/puppet/type/zpool.rb new file mode 100755 index 000000000..6d589a0fe --- /dev/null +++ b/lib/puppet/type/zpool.rb @@ -0,0 +1,60 @@ +module Puppet + newtype(:zpool) do + @doc = "Manage zpools. Create and delete zpools. The provider WILL NOT SYNC, only report differences. + + Supports vdevs with mirrors, raidz, logs and spares." + + ensurable + + newproperty(:disk, :array_matching => :all) do + desc "The disk(s) for this pool. Can be an array or space separated string" + end + + newproperty(:mirror, :array_matching => :all) do + desc "List of all the devices to mirror for this pool. Each mirror should be a space separated string. + mirror => [\"disk1 disk2\", \"disk3 disk4\"]" + + validate do |value| + if value.include?(",") + raise ArgumentError, "mirror names must be provided as string separated, not a comma-separated list" + end + end + end + + newproperty(:raidz, :array_matching => :all) do + desc "List of all the devices to raid for this pool. Should be an array of space separated strings. + raidz => [\"disk1 disk2\", \"disk3 disk4\"]" + + validate do |value| + if value.include?(",") + raise ArgumentError, "raid names must be provided as string separated, not a comma-separated list" + end + end + end + + newproperty(:spare, :array_matching => :all) do + desc "Spare disk(s) for this pool." + end + + newproperty(:log, :array_matching => :all) do + desc "Log disks for this pool. (doesn't support mirroring yet)" + end + + newparam(:pool) do + desc "The name for this pool." + isnamevar + end + + newparam(:raid_parity) do + desc "Determines parity when using raidz property." + end + + validate do + has_should = [:disk, :mirror, :raidz].select { |prop| self.should(prop) } + if has_should.length > 1 + self.fail "You cannot specify %s on this type (only one)" % has_should.join(" and ") + end + end + end +end + -- cgit From f6fa4f7b8c85303dd801fa6e4c5f47845af53c54 Mon Sep 17 00:00:00 2001 From: Andrew Shafer Date: Mon, 1 Dec 2008 00:21:20 -0700 Subject: Bug # 1680 Now you can set the hashed passwords on solaris Straight /etc/shadow hackery --- lib/puppet/provider/user/user_role_add.rb | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/user/user_role_add.rb b/lib/puppet/provider/user/user_role_add.rb index 65bd63973..278893724 100644 --- a/lib/puppet/provider/user/user_role_add.rb +++ b/lib/puppet/provider/user/user_role_add.rb @@ -29,7 +29,8 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd do cmd = [] Puppet::Type.type(:user).validproperties.each do |property| #skip the password because we can't create it with the solaris useradd - next if property == :ensure || property == :password + next if [:ensure, :password].include?(property) + # 1680 Now you can set the hashed passwords on solaris:lib/puppet/provider/user/user_role_add.rb # the value needs to be quoted, mostly because -c might # have spaces in it if value = @resource.should(property) and value != "" @@ -167,20 +168,26 @@ Puppet::Type.type(:user).provide :user_role_add, :parent => :useradd do #Read in /etc/shadow, find the line for our used and rewrite it with the new pw #Smooth like 80 grit def password=(cryptopw) - File.open("/etc/shadow", "r") do |shadow| - File.open("/etc/shadow_tmp", "w", 0600) do |shadow_tmp| - while line = shadow.gets do - line_arr = line.split(':') - if line_arr[0] == @resource[:name] - line_arr[1] = cryptopw - line = line_arr.join(':') + begin + File.open("/etc/shadow", "r") do |shadow| + File.open("/etc/shadow_tmp", "w", 0600) do |shadow_tmp| + while line = shadow.gets do + line_arr = line.split(':') + if line_arr[0] == @resource[:name] + line_arr[1] = cryptopw + line = line_arr.join(':') + end + shadow_tmp.print line end - shadow_tmp.print line end end + File.rename("/etc/shadow_tmp", "/etc/shadow") + rescue => detail + fail "Could not write temporary shadow file: %s" % detail + ensure + # Make sure this *always* gets deleted + File.unlink("/etc/shadow_tmp") if File.exist?("/etc/shadow_tmp") end - - File.rename("/etc/shadow_tmp", "/etc/shadow") end end -- cgit From fa9820baaebe29675defb14bc9d64f6cb9b75211 Mon Sep 17 00:00:00 2001 From: Andrew Shafer Date: Mon, 1 Dec 2008 00:07:04 -0700 Subject: Bug #1778 - Solaris RBAC profiles should maintain order Created OrderedList property Added to profile property small refactor in List to make inheriting easier --- lib/puppet/property/list.rb | 9 +++++++-- lib/puppet/property/ordered_list.rb | 22 ++++++++++++++++++++++ lib/puppet/type/user.rb | 3 ++- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 lib/puppet/property/ordered_list.rb (limited to 'lib') diff --git a/lib/puppet/property/list.rb b/lib/puppet/property/list.rb index 4e7f6ec90..0c933f164 100644 --- a/lib/puppet/property/list.rb +++ b/lib/puppet/property/list.rb @@ -28,6 +28,11 @@ module Puppet @resource[membership] == :inclusive end + #dearrayify was motivated because to simplify the implementation of the OrderedList property + def dearrayify(array) + array.sort.join(delimiter) + end + def should unless defined? @should and @should return nil @@ -39,7 +44,7 @@ module Puppet members = add_should_with_current(members, retrieve) end - members.sort.join(delimiter) + dearrayify(members) end def delimiter @@ -57,7 +62,7 @@ module Puppet def prepare_is_for_comparison(is) if is.is_a? Array - is = is.sort.join(delimiter) + is = dearrayify(is) end is end diff --git a/lib/puppet/property/ordered_list.rb b/lib/puppet/property/ordered_list.rb new file mode 100644 index 000000000..816b16c48 --- /dev/null +++ b/lib/puppet/property/ordered_list.rb @@ -0,0 +1,22 @@ +require 'puppet/property/list' + +module Puppet + class Property + class OrderedList < List + + def add_should_with_current(should, current) + if current.is_a?(Array) + #tricky trick + #Preserve all the current items in the list + #but move them to the back of the line + should = should + (current - should) + end + should + end + + def dearrayify(array) + array.join(delimiter) + end + end + end +end diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 0fe7928e6..c6f1eccf6 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -1,6 +1,7 @@ require 'etc' require 'facter' require 'puppet/property/list' +require 'puppet/property/ordered_list' require 'puppet/property/keyvalue' module Puppet @@ -316,7 +317,7 @@ module Puppet defaultto :minimum end - newproperty(:profiles, :parent => Puppet::Property::List, :required_features => :manages_solaris_rbac) do + newproperty(:profiles, :parent => Puppet::Property::OrderedList, :required_features => :manages_solaris_rbac) do desc "The profiles the user has. Multiple profiles should be specified as an array." -- cgit From 3eff2254e69cf66b6e9f94631900fba26172c850 Mon Sep 17 00:00:00 2001 From: Andrew Shafer Date: Mon, 1 Dec 2008 02:58:09 -0700 Subject: Feature 1696 Add support for branded zones Applied the patch from the ticket and wrote tests with the changes --- lib/puppet/provider/zone/solaris.rb | 11 +++++++---- lib/puppet/type/zone.rb | 8 ++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/zone/solaris.rb b/lib/puppet/provider/zone/solaris.rb index 24bbb99ec..a5a18198c 100644 --- a/lib/puppet/provider/zone/solaris.rb +++ b/lib/puppet/provider/zone/solaris.rb @@ -36,9 +36,8 @@ Puppet::Type.type(:zone).provide(:solaris) do # Perform all of our configuration steps. def configure # If the thing is entirely absent, then we need to create the config. - str = %{create -b -set zonepath=%s -} % @resource[:path] + # Is there someway to get this on one line? + str = "create -b #{@resource[:create_args]}\nset zonepath=%s\n" % @resource[:path] # Then perform all of our configuration steps. It's annoying # that we need this much internal info on the resource. @@ -66,7 +65,11 @@ set zonepath=%s end def install - zoneadm :install + if @resource[:install_args] + zoneadm :install, @resource[:install_args].split(" ") + else + zoneadm :install + end end # Look up the current status. diff --git a/lib/puppet/type/zone.rb b/lib/puppet/type/zone.rb index 7601ec47b..8c4261241 100644 --- a/lib/puppet/type/zone.rb +++ b/lib/puppet/type/zone.rb @@ -357,6 +357,14 @@ end end end + newparam(:create_args) do + desc "Arguments to the zonecfg create command. This can be used to create branded zones." + end + + newparam(:install_args) do + desc "Arguments to the zoneadm install command. This can be used to create branded zones." + end + newparam(:realhostname) do desc "The actual hostname of the zone." end -- cgit From c005dcf2748326ea0f177d7da806a0cd4241c31d Mon Sep 17 00:00:00 2001 From: Andrew Shafer Date: Mon, 1 Dec 2008 03:08:38 -0700 Subject: Ticket 1780 - Solaris RBAC roles should be autorequired Autorequire the roles that belong to the user so they will be created first --- lib/puppet/type/user.rb | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'lib') diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index c6f1eccf6..9e32c89ae 100755 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -279,6 +279,17 @@ module Puppet end end + #autorequire the roles that the user has + autorequire(:user) do + reqs = [] + + if roles_property = @parameters[:roles] and roles = roles_property.should + reqs += roles.split(',') + end + + reqs + end + newparam(:role_membership) do desc "Whether specified roles should be treated as the only roles of which the user is a member or whether they should merely -- cgit From 9ab3afb5423b406c661805946aac174f7f3e5674 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 28 Nov 2008 11:23:17 -0600 Subject: Hopefully fixing #1703 - using a mutex around the sending of the tagmails It's not really possible to test that this fixes the problem, and I can't even reproduce it, but hopefully this will work. Signed-off-by: Luke Kanies --- lib/puppet/reports/tagmail.rb | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'lib') diff --git a/lib/puppet/reports/tagmail.rb b/lib/puppet/reports/tagmail.rb index 102647c66..fa4e536e1 100644 --- a/lib/puppet/reports/tagmail.rb +++ b/lib/puppet/reports/tagmail.rb @@ -151,12 +151,14 @@ Puppet::Reports.register_report(:tagmail) do reports.each do |emails, messages| Puppet.info "Sending report to %s" % emails.join(", ") # We need to open a separate process for every set of email addresses - IO.popen(Puppet[:sendmail] + " " + emails.join(" "), "w") do |p| - p.puts "From: #{Puppet[:reportfrom]}" - p.puts "Subject: Puppet Report for %s" % self.host - p.puts "To: " + emails.join(", ") - - p.puts messages + sync.synchronize do + IO.popen(Puppet[:sendmail] + " " + emails.join(" "), "w") do |p| + p.puts "From: #{Puppet[:reportfrom]}" + p.puts "Subject: Puppet Report for %s" % self.host + p.puts "To: " + emails.join(", ") + + p.puts messages + end end end rescue => detail @@ -174,5 +176,11 @@ Puppet::Reports.register_report(:tagmail) do # Don't bother waiting for the pid to return. Process.detach(pid) end + + def sync + unless defined?(@sync) + @sync = Sync.new + end + end end -- cgit From 968f5cc9014c235a95f7c432da5c4033ddc2c9f7 Mon Sep 17 00:00:00 2001 From: Bryan Kearney Date: Mon, 1 Dec 2008 15:22:56 -0500 Subject: Relicense under GPLv2+ --- lib/puppet/provider/augeas/augeas.rb | 8 ++++---- lib/puppet/type/augeas.rb | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index cadbd667b..0ffddebb0 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -2,16 +2,16 @@ # Copyright (C) 2008 Red Hat Inc. # # This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public +# modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 2 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. +# General Public License for more details. # -# You should have received a copy of the GNU Lesser General Public +# You should have received a copy of the GNU General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # diff --git a/lib/puppet/type/augeas.rb b/lib/puppet/type/augeas.rb index 61339407b..058ea2fd9 100644 --- a/lib/puppet/type/augeas.rb +++ b/lib/puppet/type/augeas.rb @@ -2,16 +2,16 @@ # Copyright (C) 2008 Red Hat Inc. # # This library is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public +# modify it under the terms of the GNU General Public # License as published by the Free Software Foundation; either -# version 2.1 of the License, or (at your option) any later version. +# version 2 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. +# General Public License for more details. # -# You should have received a copy of the GNU Lesser General Publicretu +# You should have received a copy of the GNU General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -- cgit From 6a4c0d52bf4a1314bb7f2fcf9ecb409fdeab8226 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 1 Dec 2008 17:58:36 -0600 Subject: Removing debugging from the "resources" type Signed-off-by: Luke Kanies --- lib/puppet/type/resources.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/type/resources.rb b/lib/puppet/type/resources.rb index 358ad603b..c0d892bb8 100644 --- a/lib/puppet/type/resources.rb +++ b/lib/puppet/type/resources.rb @@ -137,7 +137,6 @@ Puppet::Type.newtype(:resources) do return false end - p current_values[resource.property(:uid)] if current_values[resource.property(:uid)] <= self[:unless_system_user] return false else -- cgit From cdcbc5bceb98a1edd6c9a2c5b7f21583cd3bd76f Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 1 Dec 2008 17:58:58 -0600 Subject: Fixing splaytime tests Signed-off-by: Luke Kanies --- lib/puppet/network/client/master.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb index 073d2e229..4b0cfdae3 100644 --- a/lib/puppet/network/client/master.rb +++ b/lib/puppet/network/client/master.rb @@ -488,7 +488,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client return unless Puppet[:splay] return if splayed? - time = rand(Integer(Puppet[:splaylimit] + 1)) + time = rand(Integer(Puppet[:splaylimit]) + 1) Puppet.info "Sleeping for %s seconds (splay is enabled)" % time sleep(time) @splayed = true -- cgit From 65d6b49950160e45ee4f12b525a1d9878666157f Mon Sep 17 00:00:00 2001 From: Jeffrey McCune Date: Mon, 1 Dec 2008 20:16:11 -0500 Subject: Updated mcx type and provider with comprehensive spec tests. Signed-off-by: Jeffrey McCune Fixed default provider error with mcx type spec. Signed-off-by: Jeffrey McCune --- lib/puppet/provider/mcx/mcxcontent.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib') diff --git a/lib/puppet/provider/mcx/mcxcontent.rb b/lib/puppet/provider/mcx/mcxcontent.rb index fdcc8cc5d..27c583ed1 100644 --- a/lib/puppet/provider/mcx/mcxcontent.rb +++ b/lib/puppet/provider/mcx/mcxcontent.rb @@ -40,7 +40,7 @@ Original Author: Jeff McCune (mccune.jeff@gmail.com)" :user => "Users", :group => "Groups", :computer => "Computers", - :computergroup => "ComputerGroups", + :computerlist => "ComputerLists", } class MCXContentProviderException < Exception @@ -85,7 +85,6 @@ Original Author: Jeff McCune (mccune.jeff@gmail.com)" ds_t = TypeMap[ds_type] ds_n = ds_name.to_s ds_path = "/Local/Default/#{ds_t}/#{ds_n}" - dscl 'localhost', '-mcxexport', ds_path end @@ -139,6 +138,7 @@ Original Author: Jeff McCune (mccune.jeff@gmail.com)" if ds_type.nil? ds_type = parse_type(resource[:name]) end + raise MCXContentProviderException unless TypeMap.keys.include? ds_type.to_sym ds_name = resource[:ds_name] if ds_name.nil? -- cgit