summaryrefslogtreecommitdiffstats
path: root/lib/puppet
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet')
-rw-r--r--lib/puppet/defaults.rb2
-rw-r--r--lib/puppet/feature/base.rb3
-rw-r--r--lib/puppet/module.rb2
-rw-r--r--lib/puppet/network/client/master.rb2
-rw-r--r--lib/puppet/network/xmlrpc/client.rb4
-rw-r--r--lib/puppet/parser/ast.rb15
-rw-r--r--lib/puppet/parser/ast/casestatement.rb2
-rw-r--r--lib/puppet/parser/ast/collection.rb2
-rw-r--r--lib/puppet/parser/ast/comparison_operator.rb4
-rw-r--r--lib/puppet/parser/ast/definition.rb2
-rw-r--r--lib/puppet/parser/ast/else.rb3
-rw-r--r--lib/puppet/parser/ast/function.rb31
-rw-r--r--lib/puppet/parser/ast/hostclass.rb3
-rw-r--r--lib/puppet/parser/ast/ifstatement.rb3
-rw-r--r--lib/puppet/parser/ast/node.rb3
-rw-r--r--lib/puppet/parser/ast/resource.rb3
-rw-r--r--lib/puppet/parser/ast/resource_defaults.rb2
-rw-r--r--lib/puppet/parser/ast/resource_override.rb3
-rw-r--r--lib/puppet/parser/ast/vardef.rb3
-rw-r--r--lib/puppet/parser/functions.rb14
-rw-r--r--lib/puppet/parser/functions/inline_template.rb21
-rw-r--r--lib/puppet/parser/functions/template.rb5
-rw-r--r--lib/puppet/parser/grammar.ra22
-rw-r--r--lib/puppet/parser/lexer.rb62
-rw-r--r--lib/puppet/parser/parser.rb212
-rw-r--r--lib/puppet/parser/parser_support.rb22
-rw-r--r--lib/puppet/parser/resource/reference.rb7
-rw-r--r--lib/puppet/parser/templatewrapper.rb52
-rw-r--r--lib/puppet/property.rb2
-rw-r--r--lib/puppet/property/list.rb9
-rw-r--r--lib/puppet/property/ordered_list.rb22
-rw-r--r--lib/puppet/provider/augeas/augeas.rb8
-rw-r--r--lib/puppet/provider/computer/computer.rb22
-rw-r--r--lib/puppet/provider/confine.rb5
-rw-r--r--lib/puppet/provider/confine/variable.rb15
-rw-r--r--lib/puppet/provider/confine_collection.rb5
-rw-r--r--lib/puppet/provider/confiner.rb2
-rw-r--r--lib/puppet/provider/group/directoryservice.rb5
-rw-r--r--lib/puppet/provider/mcx/mcxcontent.rb199
-rw-r--r--lib/puppet/provider/nameservice/directoryservice.rb317
-rw-r--r--lib/puppet/provider/package/appdmg.rb3
-rwxr-xr-xlib/puppet/provider/package/apt.rb6
-rwxr-xr-xlib/puppet/provider/package/rpm.rb2
-rwxr-xr-xlib/puppet/provider/package/yum.rb6
-rw-r--r--lib/puppet/provider/package/yumhelper.py9
-rw-r--r--lib/puppet/provider/service/daemontools.rb42
-rw-r--r--lib/puppet/provider/service/gentoo.rb2
-rwxr-xr-xlib/puppet/provider/service/init.rb40
-rw-r--r--lib/puppet/provider/service/launchd.rb194
-rw-r--r--lib/puppet/provider/service/runit.rb40
-rw-r--r--lib/puppet/provider/ssh_authorized_key/parsed.rb22
-rw-r--r--lib/puppet/provider/user/user_role_add.rb49
-rw-r--r--lib/puppet/provider/zfs/solaris.rb56
-rw-r--r--lib/puppet/provider/zone/solaris.rb12
-rw-r--r--lib/puppet/provider/zpool/solaris.rb112
-rw-r--r--lib/puppet/rails/database/003_add_environment_to_host.rb9
-rw-r--r--lib/puppet/rails/database/schema.rb1
-rw-r--r--lib/puppet/rails/host.rb4
-rw-r--r--lib/puppet/reference/configuration.rb2
-rw-r--r--lib/puppet/reports/tagmail.rb20
-rw-r--r--lib/puppet/transaction/change.rb8
-rw-r--r--lib/puppet/transportable.rb1
-rw-r--r--lib/puppet/type/augeas.rb8
-rw-r--r--lib/puppet/type/component.rb2
-rw-r--r--lib/puppet/type/computer.rb61
-rw-r--r--lib/puppet/type/file.rb2
-rwxr-xr-xlib/puppet/type/file/ensure.rb9
-rwxr-xr-xlib/puppet/type/file/mode.rb14
-rwxr-xr-xlib/puppet/type/file/owner.rb96
-rw-r--r--lib/puppet/type/file/selcontext.rb11
-rwxr-xr-xlib/puppet/type/group.rb59
-rw-r--r--lib/puppet/type/mcx.rb114
-rw-r--r--lib/puppet/type/ssh_authorized_key.rb16
-rwxr-xr-xlib/puppet/type/user.rb26
-rwxr-xr-xlib/puppet/type/zfs.rb45
-rw-r--r--lib/puppet/type/zone.rb8
-rwxr-xr-xlib/puppet/type/zpool.rb60
-rw-r--r--lib/puppet/util.rb9
-rwxr-xr-xlib/puppet/util/filetype.rb6
-rw-r--r--lib/puppet/util/metric.rb5
-rw-r--r--lib/puppet/util/rdoc.rb85
-rw-r--r--lib/puppet/util/rdoc/code_objects.rb219
-rw-r--r--lib/puppet/util/rdoc/generators/puppet_generator.rb829
-rw-r--r--lib/puppet/util/rdoc/generators/template/puppet/puppet.rb1051
-rw-r--r--lib/puppet/util/rdoc/parser.rb437
-rw-r--r--lib/puppet/util/selinux.rb198
-rw-r--r--lib/puppet/util/settings.rb154
87 files changed, 4655 insertions, 629 deletions
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index d0c2eff84..1a47b05b0 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -320,7 +320,7 @@ module Puppet
a proxy in front of the process or processes, since Mongrel cannot
speak SSL.",
:call_on_define => true, # Call our hook with the default value, so we always get the correct bind address set.
- :hook => proc { |value| value == "webrick" ? parent[:bindaddress] = "0.0.0.0" : parent[:bindaddress] = "127.0.0.1" if parent[:bindaddress] == "" }
+ :hook => proc { |value| value == "webrick" ? Puppet.settings[:bindaddress] = "0.0.0.0" : Puppet.settings[:bindaddress] = "127.0.0.1" if Puppet.settings[:bindaddress] == "" }
}
)
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/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
diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb
index fe257db2b..d24fd7df7 100644
--- a/lib/puppet/network/client/master.rb
+++ b/lib/puppet/network/client/master.rb
@@ -487,7 +487,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
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."
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/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)
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..fc3797f15 100644
--- a/lib/puppet/parser/ast/function.rb
+++ b/lib/puppet/parser/ast/function.rb
@@ -3,25 +3,17 @@ 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
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
@@ -42,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
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/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)
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
+ </trac/puppet/wiki/PuppetTemplating>`_ 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
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/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..a7b87e6d1 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
@@ -132,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
)
@@ -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)
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
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
diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb
index 122fc00c4..fd3492970 100644
--- a/lib/puppet/property.rb
+++ b/lib/puppet/property.rb
@@ -445,7 +445,7 @@ class Puppet::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/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/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/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/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..9bef69412 100644
--- a/lib/puppet/provider/confine/variable.rb
+++ b/lib/puppet/provider/confine/variable.rb
@@ -24,8 +24,13 @@ 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'" % [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.
@@ -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?
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
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/mcx/mcxcontent.rb b/lib/puppet/provider/mcx/mcxcontent.rb
new file mode 100644
index 000000000..27c583ed1
--- /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 <mccune.jeff@gmail.com>
+
+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",
+ :computerlist => "ComputerLists",
+ }
+
+ 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
+ raise MCXContentProviderException unless TypeMap.keys.include? ds_type.to_sym
+
+ 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/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb
index fcc44f9e3..4e21d4169 100644
--- a/lib/puppet/provider/nameservice/directoryservice.rb
+++ b/lib/puppet/provider/nameservice/directoryservice.rb
@@ -14,6 +14,8 @@
require 'puppet'
require 'puppet/provider/nameservice'
+require 'facter/util/plist'
+
class Puppet::Provider::NameService
class DirectoryService < Puppet::Provider::NameService
@@ -26,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.
@@ -36,9 +39,9 @@ class DirectoryService < Puppet::Provider::NameService
initvars()
commands :dscl => "/usr/bin/dscl"
+ commands :dseditgroup => "/usr/sbin/dseditgroup"
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 +58,10 @@ class DirectoryService < Puppet::Provider::NameService
'UniqueID' => :uid,
'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 = {
@@ -65,16 +72,19 @@ class DirectoryService < Puppet::Provider::NameService
:uid => 'UniqueID',
:comment => 'RealName',
:password => 'Password',
+ :guid => 'GeneratedUID',
+ :en_address => 'ENetAddress',
+ :ip_address => 'IPAddress',
+ :members => 'GroupMembership',
}
+ @@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 +129,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 +142,37 @@ 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
+ ds_value = dscl_plist[key]
+ 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
-
- # 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]
+ attribute_hash[@@ds_to_ns_attribute_map[ds_attribute]] = ds_value
+ end
+
+ # 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
+ if @resource_type.validproperties.include?(:password)
+ attribute_hash[:password] = self.get_password(attribute_hash[:guid])
end
- return attribute_hash
+ return attribute_hash
end
def self.get_exec_preamble(ds_action, resource_name = nil)
@@ -181,7 +184,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 +199,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
@@ -206,7 +255,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
@@ -223,79 +271,130 @@ 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
+ 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
- # 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
+ # 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 get(param)
- hash = getinfo(false)
- if hash
- return hash[param]
+ 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
- return :absent
+ 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
- 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
+ # 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])
- # 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
+ 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(@resource.class.name).validproperties.each do |property|
+ next if property == :ensure
+ if value = @resource.should(property) and value != ""
+ 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 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])
+ 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(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
+
+ 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
def deletecmd
@@ -341,9 +440,13 @@ 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)
+ [: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
end
-end
+end \ No newline at end of file
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_<name>
-# 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/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
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]
diff --git a/lib/puppet/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb
index 56fad1af9..581a446ea 100755
--- a/lib/puppet/provider/package/yum.rb
+++ b/lib/puppet/provider/package/yum.rb
@@ -100,5 +100,9 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do
# Install in yum can be used for update, too
self.install
end
-end
+
+ def purge
+ yum "-y", :erase, @resource[:name]
+ end
+ end
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():
diff --git a/lib/puppet/provider/service/daemontools.rb b/lib/puppet/provider/service/daemontools.rb
index 52d8c6b6c..46729e1b1 100644
--- a/lib/puppet/provider/service/daemontools.rb
+++ b/lib/puppet/provider/service/daemontools.rb
@@ -2,27 +2,37 @@
#
# author Brice Figureau <brice-puppet@daysofwonder.com>
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/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
diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb
index e95fbd0f9..46fa2216e 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.debug "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.
diff --git a/lib/puppet/provider/service/launchd.rb b/lib/puppet/provider/service/launchd.rb
new file mode 100644
index 000000000..11d7bd2b4
--- /dev/null
+++ b/lib/puppet/provider/service/launchd.rb
@@ -0,0 +1,194 @@
+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 plist_from_label(label)
+ job = self.class.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 <jobname> exits zero if the job is loaded
+ # and non-zero if it isn't. Simple way to check...
+ begin
+ launchctl :list, resource[:name]
+ 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_path, job_plist = plist_from_label(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_path, job_plist = plist_from_label(resource[:name])
+ did_disable_job = false
+ cmds = []
+ 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])
+ 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
+
+
+ # 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 = 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 = plist_from_label(resource[:name])
+ if self.enabled? == :false
+ job_plist.delete("Disabled")
+ Plist::Emit.save_plist(job_plist, job_path)
+ end
+ end
+
+
+ def disable
+ job_path, job_plist = plist_from_label(resource[:name])
+ job_plist["Disabled"] = true
+ Plist::Emit.save_plist(job_plist, job_path)
+ end
+
+
+end
diff --git a/lib/puppet/provider/service/runit.rb b/lib/puppet/provider/service/runit.rb
index 230fa75d9..e8a0da18f 100644
--- a/lib/puppet/provider/service/runit.rb
+++ b/lib/puppet/provider/service/runit.rb
@@ -2,26 +2,36 @@
#
# author Brice Figureau <brice-puppet@daysofwonder.com>
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"
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
diff --git a/lib/puppet/provider/user/user_role_add.rb b/lib/puppet/provider/user/user_role_add.rb
index 00fc24b3a..278893724 100644
--- a/lib/puppet/provider/user/user_role_add.rb
+++ b/lib/puppet/provider/user/user_role_add.rb
@@ -22,17 +22,15 @@ 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
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 [: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 != ""
@@ -87,6 +85,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
@@ -152,5 +154,40 @@ 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)
+ 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
+ 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
+ end
end
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/zone/solaris.rb b/lib/puppet/provider/zone/solaris.rb
index be2dd97f9..a5a18198c 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
}
@@ -35,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.
@@ -65,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/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/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
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
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
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
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/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
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
#
diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb
index 17ab5b130..498f1c503 100644
--- a/lib/puppet/type/component.rb
+++ b/lib/puppet/type/component.rb
@@ -87,7 +87,7 @@ Puppet::Type.newtype(:component) do
@reference = Puppet::ResourceReference.new(:component, @title)
- if catalog and ! catalog.resource[@reference.to_s]
+ if catalog and ! catalog.resource(@reference.to_s)
catalog.alias(self, @reference.to_s)
end
end
diff --git a/lib/puppet/type/computer.rb b/lib/puppet/type/computer.rb
new file mode 100644
index 000000000..ccbcadf72
--- /dev/null
+++ b/lib/puppet/type/computer.rb
@@ -0,0 +1,61 @@
+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
+ 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
+ provider.delete
+ end
+ end
+
+ newparam(:name) do
+ desc "The authoritative 'short' name of the computer record."
+ isnamevar
+ end
+
+ newparam(:realname) do
+ desc "The 'long' name of the computer record."
+ 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/file.rb b/lib/puppet/type/file.rb
index 6eb7f62f5..463ea7d9e 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -854,7 +854,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)
diff --git a/lib/puppet/type/file/ensure.rb b/lib/puppet/type/file/ensure.rb
index ea89a7d83..a3cf2d4a6 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::
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/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
diff --git a/lib/puppet/type/file/selcontext.rb b/lib/puppet/type/file/selcontext.rb
index d5111caf8..22e3080b1 100644
--- a/lib/puppet/type/file/selcontext.rb
+++ b/lib/puppet/type/file/selcontext.rb
@@ -38,18 +38,13 @@ 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
def sync
- unless @resource.stat(false)
- stat = @resource.stat(true)
- unless stat
- return nil
- end
- 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 250b1353b..e3507ad5c 100755
--- a/lib/puppet/type/group.rb
+++ b/lib/puppet/type/group.rb
@@ -1,28 +1,22 @@
-# 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'
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.
- 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."
+ 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."
- 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,20 +29,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
end
newproperty(:gid) do
@@ -87,13 +67,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
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 <mccune.jeff@gmail.com>
+
+Puppet::Type.newtype(:mcx) do
+
+ @doc = "MCX object management using DirectoryService on OS X.
+
+Original Author: Jeff McCune <mccune.jeff@gmail.com>
+
+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/<ds_type>/<ds_name>
+ 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
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
diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb
index 740141742..47d162547 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
@@ -98,6 +99,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|
@@ -109,6 +120,8 @@ module Puppet
end
fail "Could not find group(s) %s" % @should.join(",") unless found
+
+ # Use the default event.
end
end
@@ -263,6 +276,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
@@ -301,7 +325,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."
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/zone.rb b/lib/puppet/type/zone.rb
index 1dee65b78..6e5d784b3 100644
--- a/lib/puppet/type/zone.rb
+++ b/lib/puppet/type/zone.rb
@@ -358,6 +358,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
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
+
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]
diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb
index b10b50fb7..a58b9a23b 100755
--- a/lib/puppet/util/filetype.rb
+++ b/lib/puppet/util/filetype.rb
@@ -218,10 +218,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
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
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 = "&nbsp;&nbsp;::" * level;
+
+ context.nodes.sort.each do |node|
+ if node.document_self
+ res <<
+ prefix <<
+ "Node " <<
+ href(url(node.viewer.path), "link", node.full_name) <<
+ "<br />\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 <ged@FaerieMUD.org>
+#
+# 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 = %{<?xml version="1.0" encoding="%charset%"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+}
+
+HEADER = XHTML_PREAMBLE + %{
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <meta http-equiv="Content-Script-Type" content="text/javascript" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
+ <script type="text/javascript">
+ // <![CDATA[
+
+ function popupCode( url ) {
+ window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
+ }
+
+ function toggleCode( id ) {
+ if ( document.getElementById )
+ elem = document.getElementById( id );
+ else if ( document.all )
+ elem = eval( "document.all." + id );
+ else
+ return false;
+
+ elemStyle = elem.style;
+
+ if ( elemStyle.display != "block" ) {
+ elemStyle.display = "block"
+ } else {
+ elemStyle.display = "none"
+ }
+
+ return true;
+ }
+
+ // Make codeblocks hidden by default
+ document.writeln( "<style type=\\"text/css\\">div.method-source-code { display: none }</style>" )
+
+ // ]]>
+ </script>
+
+</head>
+<body>
+}
+
+
+#####################################################################
+### 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 = %{
+<div id="validator-badges">
+ <p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
+</div>
+
+</body>
+</html>
+}
+
+
+#####################################################################
+### F I L E P A G E H E A D E R T E M P L A T E
+#####################################################################
+
+FILE_PAGE = %{
+ <div id="fileHeader">
+ <h1>%short_name%</h1>
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>Path:</strong></td>
+ <td>%full_path%
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
+ENDIF:cvsurl
+ </td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>Last Update:</strong></td>
+ <td>%dtm_modified%</td>
+ </tr>
+ </table>
+ </div>
+}
+
+
+#####################################################################
+### 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 = %{
+ <div id="classHeader">
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>%classmod%</strong></td>
+ <td class="class-name-in-header">%full_name%</td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>In:</strong></td>
+ <td>
+START:infiles
+IF:full_path_url
+ <a href="%full_path_url%">
+ENDIF:full_path_url
+ %full_path%
+IF:full_path_url
+ </a>
+ENDIF:full_path_url
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
+ENDIF:cvsurl
+ <br />
+END:infiles
+ </td>
+ </tr>
+
+IF:parent
+ <tr class="top-aligned-row">
+ <td><strong>Parent:</strong></td>
+ <td>
+IF:par_url
+ <a href="%par_url%">
+ENDIF:par_url
+ %parent%
+IF:par_url
+ </a>
+ENDIF:par_url
+ </td>
+ </tr>
+ENDIF:parent
+ </table>
+ </div>
+}
+
+NODE_PAGE = %{
+ <div id="nodeHeader">
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>%classmod%</strong></td>
+ <td class="class-name-in-header">%full_name%</td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>In:</strong></td>
+ <td>
+START:infiles
+IF:full_path_url
+ <a href="%full_path_url%">
+ENDIF:full_path_url
+ %full_path%
+IF:full_path_url
+ </a>
+ENDIF:full_path_url
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
+ENDIF:cvsurl
+ <br />
+END:infiles
+ </td>
+ </tr>
+
+IF:parent
+ <tr class="top-aligned-row">
+ <td><strong>Parent:</strong></td>
+ <td>
+IF:par_url
+ <a href="%par_url%">
+ENDIF:par_url
+ %parent%
+IF:par_url
+ </a>
+ENDIF:par_url
+ </td>
+ </tr>
+ENDIF:parent
+ </table>
+ </div>
+}
+
+PLUGIN_PAGE = %{
+ <div id="classHeader">
+ <table class="header-table">
+ <tr class="top-aligned-row">
+ <td><strong>%classmod%</strong></td>
+ <td class="class-name-in-header">%full_name%</td>
+ </tr>
+ <tr class="top-aligned-row">
+ <td><strong>In:</strong></td>
+ <td>
+START:infiles
+IF:full_path_url
+ <a href="%full_path_url%">
+ENDIF:full_path_url
+ %full_path%
+IF:full_path_url
+ </a>
+ENDIF:full_path_url
+IF:cvsurl
+ &nbsp;(<a href="%cvsurl%"><acronym title="Concurrent Versioning System">CVS</acronym></a>)
+ENDIF:cvsurl
+ <br />
+END:infiles
+ </td>
+ </tr>
+ </table>
+ </div>
+}
+
+
+#####################################################################
+### M E T H O D L I S T T E M P L A T E
+#####################################################################
+
+PLUGIN_LIST = %{
+
+ <div id="contextContent">
+IF:description
+ <div id="description">
+ %description%
+ </div>
+ENDIF:description
+
+
+IF:toc
+ <div id="contents-list">
+ <h3 class="section-bar">Contents</h3>
+ <ul>
+START:toc
+ <li><a href="#%href%">%secname%</a></li>
+END:toc
+ </ul>
+ENDIF:toc
+ </div>
+
+ </div>
+
+<!-- Confine -->
+IF:confine
+START:confine
+ <div id="attribute-list">
+ <h3 class="section-bar">Confine</h3>
+ %type%&nbsp;%value%
+ <div class="name-list">
+ </div>
+ </div>
+END:confine
+ENDIF:confine
+
+<!-- Type -->
+IF:type
+ <div id="attribute-list">
+ <h3 class="section-bar">Type</h3>
+ %type%
+ <div class="name-list">
+ </div>
+ </div>
+ENDIF:type
+
+START:sections
+ <div id="section">
+IF:sectitle
+ <h2 class="section-title"><a name="%secsequence%">%sectitle%</a></h2>
+IF:seccomment
+ <div class="section-comment">
+ %seccomment%
+ </div>
+ENDIF:seccomment
+ENDIF:sectitle
+END:sections
+}
+
+
+METHOD_LIST = %{
+
+ <div id="contextContent">
+IF:diagram
+ <div id="diagram">
+ %diagram%
+ </div>
+ENDIF:diagram
+
+IF:description
+ <div id="description">
+ %description%
+ </div>
+ENDIF:description
+
+IF:requires
+ <div id="requires-list">
+ <h3 class="section-bar">Required files</h3>
+
+ <div class="name-list">
+START:requires
+ HREF:aref:name:&nbsp;&nbsp;
+END:requires
+ </div>
+ </div>
+ENDIF:requires
+
+IF:toc
+ <div id="contents-list">
+ <h3 class="section-bar">Contents</h3>
+ <ul>
+START:toc
+ <li><a href="#%href%">%secname%</a></li>
+END:toc
+ </ul>
+ENDIF:toc
+ </div>
+
+IF:methods
+ <div id="method-list">
+ <h3 class="section-bar">Defines</h3>
+
+ <div class="name-list">
+START:methods
+ HREF:aref:name:&nbsp;&nbsp;
+END:methods
+ </div>
+ </div>
+ENDIF:methods
+
+IF:resources
+ <div id="method-list">
+ <h3 class="section-bar">Resources</h3>
+
+ <div class="name-list">
+START:resources
+ HREF:aref:name:&nbsp;&nbsp;
+END:resources
+ </div>
+ </div>
+ENDIF:resources
+
+ </div>
+
+
+ <!-- if includes -->
+IF:includes
+ <div id="includes">
+ <h3 class="section-bar">Included Classes</h3>
+
+ <div id="includes-list">
+START:includes
+ <span class="include-name">HREF:aref:name:</span>
+END:includes
+ </div>
+ </div>
+ENDIF:includes
+
+START:sections
+ <div id="section">
+IF:sectitle
+ <h2 class="section-title"><a name="%secsequence%">%sectitle%</a></h2>
+IF:seccomment
+ <div class="section-comment">
+ %seccomment%
+ </div>
+ENDIF:seccomment
+ENDIF:sectitle
+
+
+<!-- if facts -->
+IF:facts
+ <div id="class-list">
+ <h3 class="section-bar">Custom Facts</h3>
+START:facts
+ HREF:aref:name:&nbsp;&nbsp;
+END:facts
+ </div>
+ENDIF:facts
+
+<!-- if plugins -->
+IF:plugins
+ <div id="class-list">
+ <h3 class="section-bar">Plugins</h3>
+START:plugins
+HREF:aref:name:&nbsp;&nbsp;
+END:plugins
+ </div>
+ENDIF:plugins
+
+<!-- if nodes -->
+IF:nodelist
+ <div id="class-list">
+ <h3 class="section-bar">Nodes</h3>
+
+ %nodelist%
+ </div>
+ENDIF:nodelist
+
+<!-- if class -->
+IF:classlist
+ <div id="class-list">
+ <h3 class="section-bar">Classes and Modules</h3>
+
+ %classlist%
+ </div>
+ENDIF:classlist
+
+IF:constants
+ <div id="constants-list">
+ <h3 class="section-bar">Global Variables</h3>
+
+ <div class="name-list">
+ <table summary="Variables">
+START:constants
+ <tr class="top-aligned-row context-row">
+ <td class="context-item-name">%name%</td>
+ <td>=</td>
+ <td class="context-item-value">%value%</td>
+IF:desc
+ <td width="3em">&nbsp;</td>
+ <td class="context-item-desc">%desc%</td>
+ENDIF:desc
+ </tr>
+END:constants
+ </table>
+ </div>
+ </div>
+ENDIF:constants
+
+IF:aliases
+ <div id="aliases-list">
+ <h3 class="section-bar">External Aliases</h3>
+
+ <div class="name-list">
+ <table summary="aliases">
+START:aliases
+ <tr class="top-aligned-row context-row">
+ <td class="context-item-name">%old_name%</td>
+ <td>-&gt;</td>
+ <td class="context-item-value">%new_name%</td>
+ </tr>
+IF:desc
+ <tr class="top-aligned-row context-row">
+ <td>&nbsp;</td>
+ <td colspan="2" class="context-item-desc">%desc%</td>
+ </tr>
+ENDIF:desc
+END:aliases
+ </table>
+ </div>
+ </div>
+ENDIF:aliases
+
+
+IF:attributes
+ <div id="attribute-list">
+ <h3 class="section-bar">Attributes</h3>
+
+ <div class="name-list">
+ <table>
+START:attributes
+ <tr class="top-aligned-row context-row">
+ <td class="context-item-name">%name%</td>
+IF:rw
+ <td class="context-item-value">&nbsp;[%rw%]&nbsp;</td>
+ENDIF:rw
+IFNOT:rw
+ <td class="context-item-value">&nbsp;&nbsp;</td>
+ENDIF:rw
+ <td class="context-item-desc">%a_desc%</td>
+ </tr>
+END:attributes
+ </table>
+ </div>
+ </div>
+ENDIF:attributes
+
+
+
+ <!-- if method_list -->
+IF:method_list
+ <div id="methods">
+START:method_list
+IF:methods
+ <h3 class="section-bar">Defines</h3>
+
+START:methods
+ <div id="method-%aref%" class="method-detail">
+ <a name="%aref%"></a>
+
+ <div class="method-heading">
+IF:codeurl
+ <a href="%codeurl%" target="Code" class="method-signature"
+ onclick="popupCode('%codeurl%');return false;">
+ENDIF:codeurl
+IF:sourcecode
+ <a href="#%aref%" class="method-signature">
+ENDIF:sourcecode
+IF:callseq
+ <span class="method-name">%callseq%</span>
+ENDIF:callseq
+IFNOT:callseq
+ <span class="method-name">%name%</span><span class="method-args">%params%</span>
+ENDIF:callseq
+IF:codeurl
+ </a>
+ENDIF:codeurl
+IF:sourcecode
+ </a>
+ENDIF:sourcecode
+ </div>
+
+ <div class="method-description">
+IF:m_desc
+ %m_desc%
+ENDIF:m_desc
+IF:sourcecode
+ <p><a class="source-toggle" href="#"
+ onclick="toggleCode('%aref%-source');return false;">[Source]</a></p>
+ <div class="method-source-code" id="%aref%-source">
+<pre>
+%sourcecode%
+</pre>
+ </div>
+ENDIF:sourcecode
+ </div>
+ </div>
+
+END:methods
+ENDIF:methods
+END:method_list
+
+ </div>
+ENDIF:method_list
+
+
+ <!-- if resource_list -->
+IF:resource_list
+ <div id="resources">
+ <h3 class="section-bar">Resources</h3>
+START:resource_list
+
+ <div id="method-%aref%" class="method-detail">
+ <a name="%aref%"></a>
+
+ <div class="method-heading">
+ <span class="method-name">%name%</span><br />
+IF:params
+START:params
+ &nbsp;&nbsp;&nbsp;<span class="method-args">%name% => %value%</span><br />
+END:params
+ENDIF:params
+ </div>
+
+ <div class="method-description">
+IF:m_desc
+ %m_desc%
+ENDIF:m_desc
+ </div>
+ </div>
+END:resource_list
+
+ </div>
+ENDIF:resource_list
+
+END:sections
+}
+
+
+#####################################################################
+### B O D Y T E M P L A T E
+#####################################################################
+
+BODY = HEADER + %{
+
+!INCLUDE! <!-- banner header -->
+
+ <div id="bodyContent">
+
+} + METHOD_LIST + %{
+
+ </div>
+
+} + FOOTER
+
+BODYINC = HEADER + %{
+
+!INCLUDE! <!-- banner header -->
+
+ <div id="bodyContent">
+
+!INCLUDE!
+
+ </div>
+
+} + FOOTER
+
+
+
+#####################################################################
+### S O U R C E C O D E T E M P L A T E
+#####################################################################
+
+SRC_PAGE = XHTML_PREAMBLE + %{
+<html>
+<head>
+ <title>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" media="screen" />
+</head>
+<body class="standalone-code">
+ <pre>%code%</pre>
+</body>
+</html>
+}
+
+
+#####################################################################
+### 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%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%list_title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" />
+ <base target="docwin" />
+</head>
+<body>
+<div id="index">
+ <h1 class="section-bar">%list_title%</h1>
+ <div id="index-entries">
+START:entries
+ <a href="%href%">%name%</a><br />
+END:entries
+ </div>
+</div>
+</body>
+</html>
+}
+
+TOP_INDEX = XHTML_PREAMBLE + %{
+<!--
+
+ %list_title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%list_title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="%style_url%" type="text/css" />
+ <base target="classes" />
+ <SCRIPT LANGUAGE="JavaScript">
+ <!--
+ function load(classlist,module) {
+ parent.classes.location.href = classlist;
+ parent.docwin.location.href = module;
+ }
+ //--></SCRIPT>
+</head>
+<body>
+<div id="index">
+ <h1 class="section-bar">%list_title%</h1>
+ <div id="index-entries">
+START:entries
+ <a href="%classlist%" onclick="load('%classlist%','%module%'); return true;">%name%</a><br />
+END:entries
+ </div>
+</div>
+</body>
+</html>
+}
+
+
+CLASS_INDEX = FILE_INDEX
+METHOD_INDEX = FILE_INDEX
+
+COMBO_INDEX = XHTML_PREAMBLE + %{
+<!--
+
+ %classes_title% &amp; %defines_title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%classes_title% &amp; %defines_title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+ <link rel="stylesheet" href="../%style_url%" type="text/css" />
+ <base target="docwin" />
+ <SCRIPT LANGUAGE="JavaScript">
+ <!--
+ function load(url) {
+ parent.docwin.location.href = url;
+ }
+ //--></SCRIPT>
+
+</head>
+<body>
+<div id="index">
+
+ <a href="../fr_class_index.html" target="classes">All Classes</a><br />
+
+
+<h1 class="section-bar">Module</h1>
+ <div id="index-entries">
+START:module
+ <a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:module
+ </div>
+ </div>
+<div id="index">
+
+IF:nodes
+ <h1 class="section-bar">%nodes_title%</h1>
+ <div id="index-entries">
+START:nodes
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:nodes
+ </div>
+ENDIF:nodes
+
+IF:classes
+ <h1 class="section-bar">%classes_title%</h1>
+ <div id="index-entries">
+START:classes
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:classes
+ </div>
+ENDIF:classes
+
+IF:defines
+ <h1 class="section-bar">%defines_title%</h1>
+ <div id="index-entries">
+START:defines
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:defines
+ </div>
+ENDIF:defines
+
+IF:facts
+ <h1 class="section-bar">%facts_title%</h1>
+ <div id="index-entries">
+START:facts
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:facts
+ </div>
+ENDIF:facts
+
+
+IF:plugins
+ <h1 class="section-bar">%plugins_title%</h1>
+ <div id="index-entries">
+START:plugins
+<a href="%href%" onclick="load('%href%'); return true;">%name%</a><br />
+END:plugins
+ </div>
+ENDIF:plugins
+
+</div>
+</body>
+</html>
+}
+
+INDEX = %{<?xml version="1.0" encoding="%charset%"?>
+<!DOCTYPE html
+ PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
+
+<!--
+
+ %title%
+
+ -->
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title>%title%</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=%charset%" />
+</head>
+<frameset cols="20%, 80%">
+ <frameset rows="30%,70%">
+ <frame src="fr_modules_index.html" title="All Modules" />
+ <frame src="fr_class_index.html" name="classes" title="Classes & Defines" />
+ </frameset>
+ <frame src="%initial_page%" name="docwin" />
+</frameset>
+</html>
+}
+
+
+
+ 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, "<site>" 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 <site>
+ 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 "<site>"
+ 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 == "<site>"
+
+ @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
diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb
index 148748950..70f244507 100644
--- a/lib/puppet/util/selinux.rb
+++ b/lib/puppet/util/selinux.rb
@@ -1,74 +1,67 @@
# 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")
+ # 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
- context = ""
+ # 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
- execpipe("/usr/sbin/matchpathcon #{file}") do |out|
- out.each do |line|
- context << line
- end
- end
- rescue Puppet::ExecutionFailure
+ filestat = File.lstat(file)
+ mode = filestat.mode
+ rescue Errno::ENOENT
+ mode = 0
+ end
+ retval = Selinux.matchpathcon(file, mode)
+ if retval == -1
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 +84,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
@@ -136,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
diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb
index d0c16ec92..976c80a82 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
@@ -60,7 +58,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
@@ -87,25 +84,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.
@@ -178,10 +171,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
@@ -199,14 +194,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] = {} }
@@ -310,7 +308,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
@@ -333,14 +334,24 @@ 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
+ 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.
if @config[:environment]
env = self.value(:environment).to_sym
@@ -377,12 +388,11 @@ class Puppet::Util::Settings
# 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.
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+\//, /^\//:
@@ -392,7 +402,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
@@ -415,7 +425,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()
@@ -424,7 +434,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)
@@ -508,7 +518,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
@@ -621,7 +630,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?
@@ -680,16 +689,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?
@@ -975,14 +987,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
@@ -1002,10 +1009,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
@@ -1060,7 +1066,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]
@@ -1075,7 +1081,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
@@ -1086,7 +1092,7 @@ Generated on #{Time.now}.
def group
if defined? @group
- return @parent.convert(@group)
+ return @settings.convert(@group)
else
return nil
end
@@ -1094,7 +1100,7 @@ Generated on #{Time.now}.
def owner
if defined? @owner
- return @parent.convert(@owner)
+ return @settings.convert(@owner)
else
return nil
end
@@ -1117,7 +1123,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 =~ /\/$/
@@ -1187,7 +1193,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