diff options
author | Luke Kanies <luke@madstop.com> | 2008-12-02 16:26:54 -0600 |
---|---|---|
committer | Luke Kanies <luke@madstop.com> | 2008-12-02 16:26:54 -0600 |
commit | 99a9b5a045af6f1c68619792a45603cbe450652d (patch) | |
tree | 579963d464c0529d4e9250f937d495e415f1e867 /lib/puppet | |
parent | f73e13e7464edab73443857d628602b89361c220 (diff) | |
parent | 278bfe83015312292360f727d6532a143610db0d (diff) | |
download | puppet-99a9b5a045af6f1c68619792a45603cbe450652d.tar.gz puppet-99a9b5a045af6f1c68619792a45603cbe450652d.tar.xz puppet-99a9b5a045af6f1c68619792a45603cbe450652d.zip |
Merge branch '0.24.x'
Conflicts:
bin/puppetca
lib/puppet/type/group.rb
lib/puppet/type/tidy.rb
lib/puppet/util/settings.rb
Also edited the following files so tests will pass:
lib/puppet/type/component.rb
spec/unit/ssl/certificate_request.rb
spec/unit/type/computer.rb
spec/unit/type/mcx.rb
spec/unit/type/resources.rb
spec/unit/util/settings.rb
spec/unit/util/storage.rb
test/ral/type/zone.rb
Diffstat (limited to 'lib/puppet')
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 = " ::" * 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 + (<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 + (<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 + (<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 + (<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% %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: +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: +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: +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: +END:facts + </div> +ENDIF:facts + +<!-- if plugins --> +IF:plugins + <div id="class-list"> + <h3 class="section-bar">Plugins</h3> +START:plugins +HREF:aref:name: +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"> </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>-></td> + <td class="context-item-value">%new_name%</td> + </tr> +IF:desc + <tr class="top-aligned-row context-row"> + <td> </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"> [%rw%] </td> +ENDIF:rw +IFNOT:rw + <td class="context-item-value"> </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 + <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% & %defines_title% + + --> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head> + <title>%classes_title% & %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 |