diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index f25bb65a9..4fc314a6a 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -1,173 +1,176 @@ require 'puppet/util/cacher' require 'monitor' # Just define it, so this class has fewer load dependencies. class Puppet::Node end # Model the environment that a node can operate in. This class just # provides a simple wrapper for the functionality around environments. class Puppet::Node::Environment module Helper def environment Puppet::Node::Environment.new(@environment) end def environment=(env) if env.is_a?(String) or env.is_a?(Symbol) @environment = env else @environment = env.name end end end include Puppet::Util::Cacher @seen = {} # Return an existing environment instance, or create a new one. def self.new(name = nil) return name if name.is_a?(self) name ||= Puppet.settings.value(:environment) raise ArgumentError, "Environment name must be specified" unless name symbol = name.to_sym return @seen[symbol] if @seen[symbol] obj = self.allocate obj.send :initialize, symbol @seen[symbol] = obj end def self.current Thread.current[:environment] || root end def self.current=(env) Thread.current[:environment] = new(env) end def self.root @root end # This is only used for testing. def self.clear @seen.clear end attr_reader :name # Return an environment-specific setting. def [](param) Puppet.settings.value(param, self.name) end def initialize(name) @name = name extend MonitorMixin end def known_resource_types # This makes use of short circuit evaluation to get the right thread-safe # per environment semantics with an efficient most common cases; we almost # always just return our thread's known-resource types. Only at the start # of a compilation (after our thread var has been set to nil) or when the # environment has changed do we delve deeper. Thread.current[:known_resource_types] = nil if (krt = Thread.current[:known_resource_types]) && krt.environment != self Thread.current[:known_resource_types] ||= synchronize { if @known_resource_types.nil? or @known_resource_types.require_reparse? @known_resource_types = Puppet::Resource::TypeCollection.new(self) @known_resource_types.import_ast(perform_initial_import, '') end @known_resource_types } end def module(name) mod = Puppet::Module.new(name, self) return nil unless mod.exist? mod end # Cache the modulepath, so that we aren't searching through # all known directories all the time. cached_attr(:modulepath, Puppet[:filetimeout]) do dirs = self[:modulepath].split(File::PATH_SEPARATOR) dirs = ENV["PUPPETLIB"].split(File::PATH_SEPARATOR) + dirs if ENV["PUPPETLIB"] validate_dirs(dirs) end # Return all modules from this environment. # Cache the list, because it can be expensive to create. cached_attr(:modules, Puppet[:filetimeout]) do module_names = modulepath.collect { |path| Dir.entries(path) }.flatten.uniq module_names.collect do |path| begin Puppet::Module.new(path, self) rescue Puppet::Module::Error => e nil end end.compact end def to_s name.to_s end def to_sym to_s.to_sym end # The only thing we care about when serializing an environment is its # identity; everything else is ephemeral and should not be stored or # transmitted. def to_zaml(z) self.to_s.to_zaml(z) end def validate_dirs(dirs) dir_regex = Puppet.features.microsoft_windows? ? /^[A-Za-z]:#{File::SEPARATOR}/ : /^#{File::SEPARATOR}/ + # REMIND: Dir.getwd on windows returns a path containing backslashes, which when joined with + # dir containing forward slashes, breaks our regex matching. In general, path validation needs + # to be refactored which will be handled in a future commit. dirs.collect do |dir| if dir !~ dir_regex - File.join(Dir.getwd, dir) + File.expand_path(File.join(Dir.getwd, dir)) else dir end end.find_all do |p| p =~ dir_regex && FileTest.directory?(p) end end private def perform_initial_import return empty_parse_result if Puppet.settings[:ignoreimport] parser = Puppet::Parser::Parser.new(self) if code = Puppet.settings.uninterpolated_value(:code, name.to_s) and code != "" parser.string = code else file = Puppet.settings.value(:manifest, name.to_s) parser.file = file end parser.parse rescue => detail known_resource_types.parse_failed = true msg = "Could not parse for environment #{self}: #{detail}" error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end def empty_parse_result # Return an empty toplevel hostclass to use as the result of # perform_initial_import when no file was actually loaded. return Puppet::Parser::AST::Hostclass.new('') end @root = new(:'*root*') end diff --git a/lib/puppet/util/rdoc/parser.rb b/lib/puppet/util/rdoc/parser.rb index 762ce25f0..a8996ee9a 100644 --- a/lib/puppet/util/rdoc/parser.rb +++ b/lib/puppet/util/rdoc/parser.rb @@ -1,493 +1,495 @@ # 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" if ::RUBY_VERSION =~ /1.9/ require "rdoc/markup/preprocess" require "rdoc/parser" else require "rdoc/markup/simple_markup/preprocess" require "rdoc/parsers/parserfactory" end module RDoc class Parser extend ParserFactory unless ::RUBY_VERSION =~ /1.9/ SITE = "__site__" attr_accessor :input_file_name, :top_level # 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 environment = Puppet::Node::Environment.new @known_resource_types = environment.known_resource_types unless environment.known_resource_types.watching_file?(@input_file_name) Puppet.info "rdoc: scanning #{@input_file_name}" if @input_file_name =~ /\.pp$/ @parser = Puppet::Parser::Parser.new(environment) @parser.file = @input_file_name @parser.parse.instantiate('').each do |type| @known_resource_types.add type end end end scan_top_level(@top_level) @top_level end # Due to a bug in RDoc, we need to roll our own find_module_named # The issue is that RDoc tries harder by asking the parent for a class/module # of the name. But by doing so, it can mistakenly use a module of same name # but from which we are not descendant. def find_object_named(container, name) return container if container.name == name container.each_classmodule do |m| return m if m.name == name end nil end # 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 = find_object_named(container, name) container ||= prev_container.add_class(PuppetClass, name, nil) end [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 #{fullpath}" if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins|lib)\/.+\.(pp|rb)$/ modpath = $1 name = $2 Puppet.debug "rdoc: module #{name} into #{modpath} ?" Puppet::Module.modulepath.each do |mp| if File.identical?(modpath,mp) Puppet.debug "rdoc: found module #{name}" return name end end end if fullpath =~ /\.(pp|rb)$/ # there can be paths we don't want to scan under modules # imagine a ruby or manifest that would be distributed as part as a module # but we don't want those to be hosted under Puppet::Module.modulepath.each do |mp| # check that fullpath is a descendant of mp dirname = fullpath - while (dirname = File.dirname(dirname)) != '/' + previous = dirname + while (dirname = File.dirname(previous)) != previous + previous = dirname return nil if File.identical?(dirname,mp) end end end # we are under a global manifests Puppet.debug "rdoc: global manifests" 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 #{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_or_require(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_include_or_require(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) if stmt.is_a?(Puppet::Parser::AST::Function) and ['include','require'].include?(stmt.name) stmt.arguments.each do |included| Puppet.debug "found #{stmt.name}: #{included}" container.send("add_#{stmt.name}",Include.new(included.to_s, stmt.doc)) end end end end # create documentation for realize statements we can find in +code+ # and associate it with +container+ def scan_for_realize(container, code) code = [code] unless code.is_a?(Array) code.each do |stmt| scan_for_realize(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) if stmt.is_a?(Puppet::Parser::AST::Function) and stmt.name == 'realize' stmt.arguments.each do |realized| Puppet.debug "found #{stmt.name}: #{realized}" container.add_realize(Include.new(realized.to_s, 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 = [code] unless code.is_a?(Array) 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: #{stmt.name} = #{stmt.value}" container.add_constant(Constant.new(stmt.name.to_s, stmt.value.to_s, 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 = [code] unless code.is_a?(Array) 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) and !stmt.type.nil? begin type = stmt.type.split("::").collect { |s| s.capitalize }.join("::") stmt.instances.each do |inst| title = inst.title.is_a?(Puppet::Parser::AST::ASTArray) ? inst.title.to_s.gsub(/\[(.*)\]/,'\1') : inst.title.to_s Puppet.debug "rdoc: found resource: #{type}[#{title}]" param = [] inst.parameters.children.each do |p| res = {} res["name"] = p.param res["value"] = "#{p.value.to_s}" unless p.value.nil? param << res end container.add_resource(PuppetResource.new(type, title, stmt.doc, param)) end rescue => detail raise Puppet::ParseError, "impossible to parse resource in #{stmt.file} at line #{stmt.line}: #{detail}" end end end end def resource_stmt_to_ref(stmt) type = stmt.type.split("::").collect { |s| s.capitalize }.join("::") title = stmt.title.is_a?(Puppet::Parser::AST::ASTArray) ? stmt.title.to_s.gsub(/\[(.*)\]/,'\1') : stmt.title.to_s param = stmt.params.children.collect do |p| {"name" => p.param, "value" => p.value.to_s} end PuppetResource.new(type, title, stmt.doc, param) end # create documentation for a class named +name+ def document_class(name, klass, container) Puppet.debug "rdoc: found new class #{name}" container, name = get_class_or_module(container, name) superclass = klass.parent 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) # it is possible we already encountered this class, while parsing some namespaces # from other classes of other files. But at that time we couldn't know this class superclass # so, now we know it and force it. cls.superclass = 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_or_require(cls, code) scan_for_realize(cls, code) scan_for_resource(cls, code) if Puppet.settings[:document_all] end cls.comment = comment rescue => detail raise Puppet::ParseError, "impossible to parse class '#{name}' in #{klass.file} at line #{klass.line}: #{detail}" end # create documentation for a node def document_node(name, node, container) Puppet.debug "rdoc: found new node #{name}" superclass = node.parent 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_or_require(n, code) scan_for_realize(n, code) scan_for_vardef(n, code) scan_for_resource(n, code) if Puppet.settings[:document_all] end n.comment = comment rescue => detail raise Puppet::ParseError, "impossible to parse node '#{name}' in #{node.file} at line #{node.line}: #{detail}" end # create documentation for a define def document_define(name, define, container) Puppet.debug "rdoc: found new definition #{name}" # find superclas if any @stats.num_methods += 1 # find the parent # split define name by :: to find the complete module hierarchy container, name = get_class_or_module(container,name) # build up declaration declaration = "" define.arguments.each do |arg,value| declaration << "\$#{arg}" unless value.nil? declaration << " => " case value when Puppet::Parser::AST::Leaf declaration << "'#{value.value}'" when Puppet::Parser::AST::ASTArray declaration << "[#{value.children.collect { |v| "'#{v}'" }.join(", ")}]" else declaration << "#{value.to_s}" end end declaration << ", " end declaration.chop!.chop! if declaration.size > 1 # register method into the container meth = AnyMethod.new(declaration, name) meth.comment = define.doc container.add_method(meth) look_for_directives_in(container, meth.comment) unless meth.comment.empty? meth.params = "( #{declaration} )" meth.visibility = :public meth.document_self = true meth.singleton = false rescue => detail raise Puppet::ParseError, "impossible to parse definition '#{name}' in #{define.file} at line #{define.line}: #{detail}" end # Traverse the AST tree and produce code-objects node # that contains the documentation def parse_elements(container) Puppet.debug "rdoc: scanning manifest" @known_resource_types.hostclasses.values.sort { |a,b| a.name <=> b.name }.each do |klass| name = klass.name if klass.file == @input_file_name unless name.empty? document_class(name,klass,container) else # on main class document vardefs code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::ASTArray) code ||= klass.code scan_for_vardef(container, code) unless code.nil? end end end @known_resource_types.definitions.each do |name, define| if define.file == @input_file_name document_define(name,define,container) end end @known_resource_types.nodes.each do |name, node| if node.file == @input_file_name document_node(name.to_s,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,{}) look_for_directives_in(container, comments) unless comments.empty? current_fact.comment = comments container.add_fact(current_fact) current_fact.record_location(@top_level) comments = "" Puppet.debug "rdoc: found custom fact #{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 #{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 #{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 end end diff --git a/spec/integration/application/doc_spec.rb b/spec/integration/application/doc_spec.rb index 47fd93a03..2cf5fd1e9 100755 --- a/spec/integration/application/doc_spec.rb +++ b/spec/integration/application/doc_spec.rb @@ -1,54 +1,54 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet_spec/files' describe Puppet::Application::Doc do include PuppetSpec::Files - it "should not generate an error when module dir overlaps parent of site.pp (#4798)", :'fails_on_ruby_1.9.2' => true, :fails_on_windows => true do + it "should not generate an error when module dir overlaps parent of site.pp (#4798)", :'fails_on_ruby_1.9.2' => true, :unless => Puppet.features.microsoft_windows? do begin # Note: the directory structure below is more complex than it # needs to be, but it's representative of the directory structure # used in bug #4798. old_dir = Dir.getwd # Note: can't use chdir with a block because it will generate bogus warnings tmpdir = tmpfile('doc_spec') Dir.mkdir(tmpdir) Dir.chdir(tmpdir) site_file = 'site.pp' File.open(site_file, 'w') do |f| f.puts '# A comment' end modules_dir = 'modules' Dir.mkdir(modules_dir) rt_dir = File.join(modules_dir, 'rt') Dir.mkdir(rt_dir) manifests_dir = File.join(rt_dir, 'manifests') Dir.mkdir(manifests_dir) rt_file = File.join(manifests_dir, 'rt.pp') File.open(rt_file, 'w') do |f| f.puts '# A class' f.puts 'class foo { }' f.puts '# A definition' f.puts 'define bar { }' end puppet = Puppet::Application[:doc] Puppet[:modulepath] = modules_dir Puppet[:manifest] = site_file puppet.options[:mode] = :rdoc expect { puppet.run_command }.to exit_with 0 File.should be_exist('doc') ensure Dir.chdir(old_dir) end end it "should respect the -o option" do puppetdoc = Puppet::Application[:doc] puppetdoc.command_line.stubs(:args).returns(['foo', '-o', 'bar']) puppetdoc.parse_options puppetdoc.options[:outputdir].should == 'bar' end end diff --git a/spec/integration/network/server/webrick_spec.rb b/spec/integration/network/server/webrick_spec.rb index 2390fcab1..7365462d3 100755 --- a/spec/integration/network/server/webrick_spec.rb +++ b/spec/integration/network/server/webrick_spec.rb @@ -1,85 +1,83 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/server' require 'puppet/ssl/certificate_authority' require 'socket' -describe Puppet::Network::Server do +describe Puppet::Network::Server, :unless => Puppet.features.microsoft_windows? do + include PuppetSpec::Files + describe "when using webrick" do before :each do Puppet[:servertype] = 'webrick' Puppet[:server] = '127.0.0.1' @params = { :port => 34343, :handlers => [ :node ], :xmlrpc_handlers => [ :status ] } # Get a safe temporary file - @tmpfile = Tempfile.new("webrick_integration_testing") - @dir = @tmpfile.path + "_dir" + dir = tmpdir("webrick_integration_testing") - Puppet.settings[:confdir] = @dir - Puppet.settings[:vardir] = @dir + Puppet.settings[:confdir] = dir + Puppet.settings[:vardir] = dir Puppet.settings[:group] = Process.gid Puppet::SSL::Host.ca_location = :local ca = Puppet::SSL::CertificateAuthority.new ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.indirection.find(Puppet[:certname]) end after do - @tmpfile.delete Puppet.settings.clear - system("rm -rf #{@dir}") - Puppet::SSL::Host.ca_location = :none end describe "before listening" do it "should not be reachable at the specified address and port" do lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error end end describe "when listening" do it "should be reachable on the specified address and port" do @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.listen lambda { TCPSocket.new('127.0.0.1', 34343) }.should_not raise_error end it "should default to '0.0.0.0' as its bind address" do Puppet.settings.clear Puppet[:servertype] = 'webrick' Puppet[:bindaddress].should == '0.0.0.0' end it "should use any specified bind address" do Puppet[:bindaddress] = "127.0.0.1" @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.stubs(:unlisten) # we're breaking listening internally, so we have to keep it from unlistening @server.send(:http_server).expects(:listen).with { |args| args[:address] == "127.0.0.1" } @server.listen end it "should not allow multiple servers to listen on the same address and port" do @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.listen @server2 = Puppet::Network::Server.new(@params.merge(:port => 34343)) lambda { @server2.listen }.should raise_error end after :each do @server.unlisten if @server && @server.listening? end end describe "after unlistening" do it "should not be reachable on the port and address assigned" do @server = Puppet::Network::Server.new(@params.merge(:port => 34343)) @server.listen @server.unlisten lambda { TCPSocket.new('127.0.0.1', 34343) }.should raise_error(Errno::ECONNREFUSED) end end end end diff --git a/spec/integration/ssl/certificate_authority_spec.rb b/spec/integration/ssl/certificate_authority_spec.rb index 68b2401fb..dc8af6a7b 100755 --- a/spec/integration/ssl/certificate_authority_spec.rb +++ b/spec/integration/ssl/certificate_authority_spec.rb @@ -1,132 +1,130 @@ #!/usr/bin/env rspec # # Created by Luke Kanies on 2008-4-17. # Copyright (c) 2008. All rights reserved. require 'spec_helper' require 'puppet/ssl/certificate_authority' -require 'tempfile' -describe Puppet::SSL::CertificateAuthority, :fails_on_windows => true do +describe Puppet::SSL::CertificateAuthority, :unless => Puppet.features.microsoft_windows? do + include PuppetSpec::Files + before do # Get a safe temporary file - file = Tempfile.new("ca_integration_testing") - @dir = file.path - file.delete + dir = tmpdir("ca_integration_testing") - Puppet.settings[:confdir] = @dir - Puppet.settings[:vardir] = @dir + Puppet.settings[:confdir] = dir + Puppet.settings[:vardir] = dir Puppet.settings[:group] = Process.gid Puppet::SSL::Host.ca_location = :local @ca = Puppet::SSL::CertificateAuthority.new end after { Puppet::SSL::Host.ca_location = :none - system("rm -rf #{@dir}") Puppet.settings.clear Puppet::SSL::CertificateAuthority.instance_variable_set("@instance", nil) } it "should create a CA host" do @ca.host.should be_ca end it "should be able to generate a certificate" do @ca.generate_ca_certificate @ca.host.certificate.should be_instance_of(Puppet::SSL::Certificate) end it "should be able to generate a new host certificate" do @ca.generate("newhost") Puppet::SSL::Certificate.indirection.find("newhost").should be_instance_of(Puppet::SSL::Certificate) end it "should be able to revoke a host certificate" do @ca.generate("newhost") @ca.revoke("newhost") lambda { @ca.verify("newhost") }.should raise_error end it "should have a CRL" do @ca.generate_ca_certificate @ca.crl.should_not be_nil end it "should be able to read in a previously created CRL" do @ca.generate_ca_certificate # Create it to start with. @ca.crl Puppet::SSL::CertificateAuthority.new.crl.should_not be_nil end describe "when signing certificates" do before do @host = Puppet::SSL::Host.new("luke.madstop.com") # We have to provide the key, since when we're in :ca_only mode, we can only interact # with the CA key. key = Puppet::SSL::Key.new(@host.name) key.generate @host.key = key @host.generate_certificate_request path = File.join(Puppet[:requestdir], "luke.madstop.com.pem") end it "should be able to sign certificates" do @ca.sign("luke.madstop.com") end it "should save the signed certificate" do @ca.sign("luke.madstop.com") Puppet::SSL::Certificate.indirection.find("luke.madstop.com").should be_instance_of(Puppet::SSL::Certificate) end it "should be able to sign multiple certificates" do @other = Puppet::SSL::Host.new("other.madstop.com") okey = Puppet::SSL::Key.new(@other.name) okey.generate @other.key = okey @other.generate_certificate_request @ca.sign("luke.madstop.com") @ca.sign("other.madstop.com") Puppet::SSL::Certificate.indirection.find("other.madstop.com").should be_instance_of(Puppet::SSL::Certificate) Puppet::SSL::Certificate.indirection.find("luke.madstop.com").should be_instance_of(Puppet::SSL::Certificate) end it "should save the signed certificate to the :signeddir" do @ca.sign("luke.madstop.com") client_cert = File.join(Puppet[:signeddir], "luke.madstop.com.pem") File.read(client_cert).should == Puppet::SSL::Certificate.indirection.find("luke.madstop.com").content.to_s end it "should save valid certificates" do @ca.sign("luke.madstop.com") unless ssl = Puppet::Util::which('openssl') pending "No ssl available" else ca_cert = Puppet[:cacert] client_cert = File.join(Puppet[:signeddir], "luke.madstop.com.pem") output = %x{openssl verify -CAfile #{ca_cert} #{client_cert}} $CHILD_STATUS.should == 0 end end end end diff --git a/spec/integration/ssl/certificate_request_spec.rb b/spec/integration/ssl/certificate_request_spec.rb index 07a4d9269..6c1c8b964 100755 --- a/spec/integration/ssl/certificate_request_spec.rb +++ b/spec/integration/ssl/certificate_request_spec.rb @@ -1,62 +1,59 @@ #!/usr/bin/env rspec # # Created by Luke Kanies on 2008-4-17. # Copyright (c) 2008. All rights reserved. require 'spec_helper' require 'puppet/ssl/certificate_request' -require 'tempfile' +# REMIND: Fails on windows because there is no user provider yet describe Puppet::SSL::CertificateRequest, :fails_on_windows => true do + include PuppetSpec::Files + before do # Get a safe temporary file - file = Tempfile.new("csr_integration_testing") - @dir = file.path - file.delete - - Dir.mkdir(@dir) + dir = tmpdir("csr_integration_testing") Puppet.settings.clear - Puppet.settings[:confdir] = @dir - Puppet.settings[:vardir] = @dir + Puppet.settings[:confdir] = dir + Puppet.settings[:vardir] = dir Puppet.settings[:group] = Process.gid Puppet::SSL::Host.ca_location = :none @csr = Puppet::SSL::CertificateRequest.new("luke.madstop.com") @key = OpenSSL::PKey::RSA.new(512) # This is necessary so the terminus instances don't lie around. Puppet::SSL::CertificateRequest.indirection.termini.clear end after do - system("rm -rf #{@dir}") Puppet.settings.clear end it "should be able to generate CSRs" do @csr.generate(@key) end it "should be able to save CSRs" do Puppet::SSL::CertificateRequest.indirection.save(@csr) end it "should be able to find saved certificate requests via the Indirector" do @csr.generate(@key) Puppet::SSL::CertificateRequest.indirection.save(@csr) Puppet::SSL::CertificateRequest.indirection.find("luke.madstop.com").should be_instance_of(Puppet::SSL::CertificateRequest) end it "should save the completely CSR when saving" do @csr.generate(@key) Puppet::SSL::CertificateRequest.indirection.save(@csr) Puppet::SSL::CertificateRequest.indirection.find("luke.madstop.com").content.to_s.should == @csr.content.to_s end end diff --git a/spec/integration/ssl/certificate_revocation_list_spec.rb b/spec/integration/ssl/certificate_revocation_list_spec.rb index 5d2b102f5..d140fd950 100755 --- a/spec/integration/ssl/certificate_revocation_list_spec.rb +++ b/spec/integration/ssl/certificate_revocation_list_spec.rb @@ -1,43 +1,42 @@ #!/usr/bin/env rspec # # Created by Luke Kanies on 2008-5-5. # Copyright (c) 2008. All rights reserved. require 'spec_helper' require 'puppet/ssl/certificate_revocation_list' -require 'tempfile' +# REMIND: Fails on windows because there is no user provider yet describe Puppet::SSL::CertificateRevocationList, :fails_on_windows => true do + include PuppetSpec::Files + before do # Get a safe temporary file - file = Tempfile.new("ca_integration_testing") - @dir = file.path - file.delete + dir = tmpdir("ca_integration_testing") - Puppet.settings[:confdir] = @dir - Puppet.settings[:vardir] = @dir + Puppet.settings[:confdir] = dir + Puppet.settings[:vardir] = dir Puppet.settings[:group] = Process.gid Puppet::SSL::Host.ca_location = :local end after { Puppet::SSL::Host.ca_location = :none - system("rm -rf #{@dir}") Puppet.settings.clear # This is necessary so the terminus instances don't lie around. Puppet::SSL::Host.indirection.termini.clear } it "should be able to read in written out CRLs with no revoked certificates" do ca = Puppet::SSL::CertificateAuthority.new raise "CRL not created" unless FileTest.exist?(Puppet[:hostcrl]) crl = Puppet::SSL::CertificateRevocationList.new("crl_int_testing") crl.read(Puppet[:hostcrl]) end end diff --git a/spec/integration/ssl/host_spec.rb b/spec/integration/ssl/host_spec.rb index 53ff88ea4..94e245554 100755 --- a/spec/integration/ssl/host_spec.rb +++ b/spec/integration/ssl/host_spec.rb @@ -1,90 +1,89 @@ #!/usr/bin/env rspec # # Created by Luke Kanies on 2008-4-17. # Copyright (c) 2008. All rights reserved. require 'spec_helper' require 'puppet/ssl/host' -require 'tempfile' +# REMIND: Fails on windows because there is no user provider yet describe Puppet::SSL::Host, :fails_on_windows => true do + include PuppetSpec::Files + before do # Get a safe temporary file - file = Tempfile.new("host_integration_testing") - @dir = file.path - file.delete + dir = tmpdir("host_integration_testing") - Puppet.settings[:confdir] = @dir - Puppet.settings[:vardir] = @dir + Puppet.settings[:confdir] = dir + Puppet.settings[:vardir] = dir Puppet.settings[:group] = Process.gid Puppet::SSL::Host.ca_location = :local @host = Puppet::SSL::Host.new("luke.madstop.com") @ca = Puppet::SSL::CertificateAuthority.new end after { Puppet::SSL::Host.ca_location = :none - system("rm -rf #{@dir}") Puppet.settings.clear } it "should be considered a CA host if its name is equal to 'ca'" do Puppet::SSL::Host.new(Puppet::SSL::CA_NAME).should be_ca end describe "when managing its key" do it "should be able to generate and save a key" do @host.generate_key end it "should save the key such that the Indirector can find it" do @host.generate_key Puppet::SSL::Key.indirection.find(@host.name).content.to_s.should == @host.key.to_s end it "should save the private key into the :privatekeydir" do @host.generate_key File.read(File.join(Puppet.settings[:privatekeydir], "luke.madstop.com.pem")).should == @host.key.to_s end end describe "when managing its certificate request" do it "should be able to generate and save a certificate request" do @host.generate_certificate_request end it "should save the certificate request such that the Indirector can find it" do @host.generate_certificate_request Puppet::SSL::CertificateRequest.indirection.find(@host.name).content.to_s.should == @host.certificate_request.to_s end it "should save the private certificate request into the :privatekeydir" do @host.generate_certificate_request File.read(File.join(Puppet.settings[:requestdir], "luke.madstop.com.pem")).should == @host.certificate_request.to_s end end describe "when the CA host" do it "should never store its key in the :privatekeydir" do Puppet.settings.use(:main, :ssl, :ca) @ca = Puppet::SSL::Host.new(Puppet::SSL::Host.ca_name) @ca.generate_key FileTest.should_not be_exist(File.join(Puppet[:privatekeydir], "ca.pem")) end end - it "should pass the verification of its own SSL store" do + it "should pass the verification of its own SSL store", :unless => Puppet.features.microsoft_windows? do @host.generate @ca = Puppet::SSL::CertificateAuthority.new @ca.sign(@host.name) @host.ssl_store.verify(@host.certificate.content).should be_true end end diff --git a/spec/integration/type/tidy_spec.rb b/spec/integration/type/tidy_spec.rb index 08a24099c..d1bb62d6e 100755 --- a/spec/integration/type/tidy_spec.rb +++ b/spec/integration/type/tidy_spec.rb @@ -1,31 +1,31 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet_spec/files' require 'puppet/file_bucket/dipper' describe Puppet::Type.type(:tidy) do include PuppetSpec::Files before do Puppet::Util::Storage.stubs(:store) end # Testing #355. - it "should be able to remove dead links", :fails_on_windows => true do + it "should be able to remove dead links", :unless => Puppet.features.microsoft_windows? do dir = tmpfile("tidy_link_testing") link = File.join(dir, "link") target = tmpfile("no_such_file_tidy_link_testing") Dir.mkdir(dir) File.symlink(target, link) tidy = Puppet::Type.type(:tidy).new :path => dir, :recurse => true catalog = Puppet::Resource::Catalog.new catalog.add_resource(tidy) catalog.apply FileTest.should_not be_symlink(link) end end diff --git a/spec/integration/util/autoload_spec.rb b/spec/integration/util/autoload_spec.rb index 771e6a718..92fc6554c 100755 --- a/spec/integration/util/autoload_spec.rb +++ b/spec/integration/util/autoload_spec.rb @@ -1,113 +1,113 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/util/autoload' require 'fileutils' class AutoloadIntegrator @things = [] def self.newthing(name) @things << name end def self.thing?(name) @things.include? name end def self.clear @things.clear end end require 'puppet_spec/files' describe Puppet::Util::Autoload do include PuppetSpec::Files def with_file(name, *path) path = File.join(*path) # Now create a file to load File.open(path, "w") { |f| f.puts "\nAutoloadIntegrator.newthing(:#{name.to_s})\n" } yield File.delete(path) end def with_loader(name, path) dir = tmpfile(name + path) $LOAD_PATH << dir Dir.mkdir(dir) rbdir = File.join(dir, path.to_s) Dir.mkdir(rbdir) loader = Puppet::Util::Autoload.new(name, path) yield rbdir, loader Dir.rmdir(rbdir) Dir.rmdir(dir) $LOAD_PATH.pop AutoloadIntegrator.clear end it "should make instances available by the loading class" do loader = Puppet::Util::Autoload.new("foo", "bar") Puppet::Util::Autoload["foo"].should == loader end it "should not fail when asked to load a missing file" do Puppet::Util::Autoload.new("foo", "bar").load(:eh).should be_false end it "should load and return true when it successfully loads a file" do with_loader("foo", "bar") { |dir,loader| with_file(:mything, dir, "mything.rb") { loader.load(:mything).should be_true loader.should be_loaded(:mything) AutoloadIntegrator.should be_thing(:mything) } } end it "should consider a file loaded when asked for the name without an extension" do with_loader("foo", "bar") { |dir,loader| with_file(:noext, dir, "noext.rb") { loader.load(:noext) loader.should be_loaded(:noext) } } end it "should consider a file loaded when asked for the name with an extension" do with_loader("foo", "bar") { |dir,loader| with_file(:noext, dir, "withext.rb") { loader.load(:withext) loader.should be_loaded("withext.rb") } } end it "should register the fact that the instance is loaded with the Autoload base class" do with_loader("foo", "bar") { |dir,loader| with_file(:baseload, dir, "baseload.rb") { loader.load(:baseload) Puppet::Util::Autoload.should be_loaded("bar/withext.rb") } } end - it "should be able to load files directly from modules", :fails_on_windows => true do + it "should be able to load files directly from modules" do modulepath = tmpfile("autoload_module_testing") libdir = File.join(modulepath, "mymod", "lib", "foo") FileUtils.mkdir_p(libdir) file = File.join(libdir, "plugin.rb") Puppet[:modulepath] = modulepath with_loader("foo", "foo") do |dir, loader| with_file(:plugin, file.split("/")) do loader.load(:plugin) loader.should be_loaded("plugin.rb") end end end end diff --git a/spec/unit/application/inspect_spec.rb b/spec/unit/application/inspect_spec.rb index be5887f01..750f25ab8 100755 --- a/spec/unit/application/inspect_spec.rb +++ b/spec/unit/application/inspect_spec.rb @@ -1,283 +1,283 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/application/inspect' require 'puppet/resource/catalog' require 'puppet/indirector/catalog/yaml' require 'puppet/indirector/report/rest' require 'puppet/indirector/file_bucket_file/rest' describe Puppet::Application::Inspect do include PuppetSpec::Files before :each do @inspect = Puppet::Application[:inspect] @inspect.preinit end it "should operate in agent run_mode" do @inspect.class.run_mode.name.should == :agent end describe "during setup" do it "should print its configuration if asked" do Puppet[:configprint] = "all" Puppet.settings.expects(:print_configs).returns(true) expect { @inspect.setup }.to exit_with 0 end it "should fail if reporting is turned off" do Puppet[:report] = false lambda { @inspect.setup }.should raise_error(/report=true/) end end describe "when executing" do before :each do Puppet[:report] = true @inspect.options[:logset] = true Puppet::Transaction::Report::Rest.any_instance.stubs(:save) @inspect.setup end it "should retrieve the local catalog" do Puppet::Resource::Catalog::Yaml.any_instance.expects(:find).with {|request| request.key == Puppet[:certname] }.returns(Puppet::Resource::Catalog.new) @inspect.run_command end it "should save the report to REST" do Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(Puppet::Resource::Catalog.new) Puppet::Transaction::Report::Rest.any_instance.expects(:save).with {|request| request.instance.host == Puppet[:certname] } @inspect.run_command end it "should audit the specified properties" do catalog = Puppet::Resource::Catalog.new file = Tempfile.new("foo") file.puts("file contents") file.close resource = Puppet::Resource.new(:file, file.path, :parameters => {:audit => "all"}) catalog.add_resource(resource) Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(catalog) events = nil Puppet::Transaction::Report::Rest.any_instance.expects(:save).with do |request| events = request.instance.resource_statuses.values.first.events end @inspect.run_command properties = events.inject({}) do |property_values, event| property_values.merge(event.property => event.previous_value) end properties["ensure"].should == :file properties["content"].should == "{md5}#{Digest::MD5.hexdigest("file contents\n")}" properties.has_key?("target").should == false end it "should set audited to true for all events" do catalog = Puppet::Resource::Catalog.new file = Tempfile.new("foo") resource = Puppet::Resource.new(:file, file.path, :parameters => {:audit => "all"}) catalog.add_resource(resource) Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(catalog) events = nil Puppet::Transaction::Report::Rest.any_instance.expects(:save).with do |request| events = request.instance.resource_statuses.values.first.events end @inspect.run_command events.each do |event| event.audited.should == true end end it "should not report irrelevent attributes if the resource is absent" do catalog = Puppet::Resource::Catalog.new file = Tempfile.new("foo") resource = Puppet::Resource.new(:file, file.path, :parameters => {:audit => "all"}) file.close file.delete catalog.add_resource(resource) Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(catalog) events = nil Puppet::Transaction::Report::Rest.any_instance.expects(:save).with do |request| events = request.instance.resource_statuses.values.first.events end @inspect.run_command properties = events.inject({}) do |property_values, event| property_values.merge(event.property => event.previous_value) end properties.should == {"ensure" => :absent} end describe "when archiving to a bucket" do before :each do Puppet[:archive_files] = true Puppet[:archive_file_server] = "filebucketserver" @catalog = Puppet::Resource::Catalog.new Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(@catalog) end describe "when auditing files" do before :each do @file = tmpfile("foo") @resource = Puppet::Resource.new(:file, @file, :parameters => {:audit => "content"}) @catalog.add_resource(@resource) end it "should send an existing file to the file bucket" do File.open(@file, 'w') { |f| f.write('stuff') } Puppet::FileBucketFile::Rest.any_instance.expects(:head).with do |request| request.server == Puppet[:archive_file_server] end.returns(false) Puppet::FileBucketFile::Rest.any_instance.expects(:save).with do |request| request.server == Puppet[:archive_file_server] and request.instance.contents == 'stuff' end @inspect.run_command end - it "should not send unreadable files", :fails_on_windows => true do + it "should not send unreadable files", :unless => Puppet.features.microsoft_windows? do File.open(@file, 'w') { |f| f.write('stuff') } File.chmod(0, @file) Puppet::FileBucketFile::Rest.any_instance.expects(:head).never Puppet::FileBucketFile::Rest.any_instance.expects(:save).never @inspect.run_command end it "should not try to send non-existent files" do Puppet::FileBucketFile::Rest.any_instance.expects(:head).never Puppet::FileBucketFile::Rest.any_instance.expects(:save).never @inspect.run_command end it "should not try to send files whose content we are not auditing" do @resource[:audit] = "group" Puppet::FileBucketFile::Rest.any_instance.expects(:head).never Puppet::FileBucketFile::Rest.any_instance.expects(:save).never @inspect.run_command end it "should continue if bucketing a file fails" do File.open(@file, 'w') { |f| f.write('stuff') } Puppet::FileBucketFile::Rest.any_instance.stubs(:head).returns false Puppet::FileBucketFile::Rest.any_instance.stubs(:save).raises "failure" Puppet::Transaction::Report::Rest.any_instance.expects(:save).with do |request| @report = request.instance end @inspect.run_command @report.logs.first.should_not == nil @report.logs.first.message.should =~ /Could not back up/ end end describe "when auditing non-files" do before :each do Puppet::Type.newtype(:stub_type) do newparam(:name) do desc "The name var" isnamevar end newproperty(:content) do desc "content" def retrieve :whatever end end end @resource = Puppet::Resource.new(:stub_type, 'foo', :parameters => {:audit => "all"}) @catalog.add_resource(@resource) end after :each do Puppet::Type.rmtype(:stub_type) end it "should not try to send non-files" do Puppet::FileBucketFile::Rest.any_instance.expects(:head).never Puppet::FileBucketFile::Rest.any_instance.expects(:save).never @inspect.run_command end end end describe "when there are failures" do before :each do Puppet::Type.newtype(:stub_type) do newparam(:name) do desc "The name var" isnamevar end newproperty(:content) do desc "content" def retrieve raise "failed" end end end @catalog = Puppet::Resource::Catalog.new Puppet::Resource::Catalog::Yaml.any_instance.stubs(:find).returns(@catalog) Puppet::Transaction::Report::Rest.any_instance.expects(:save).with do |request| @report = request.instance end end after :each do Puppet::Type.rmtype(:stub_type) end it "should mark the report failed and create failed events for each property" do @resource = Puppet::Resource.new(:stub_type, 'foo', :parameters => {:audit => "all"}) @catalog.add_resource(@resource) @inspect.run_command @report.status.should == "failed" @report.logs.select{|log| log.message =~ /Could not inspect/}.size.should == 1 @report.resource_statuses.size.should == 1 @report.resource_statuses['Stub_type[foo]'].events.size.should == 1 event = @report.resource_statuses['Stub_type[foo]'].events.first event.property.should == "content" event.status.should == "failure" event.audited.should == true event.instance_variables.should_not include("@previous_value") end it "should continue to the next resource" do @resource = Puppet::Resource.new(:stub_type, 'foo', :parameters => {:audit => "all"}) @other_resource = Puppet::Resource.new(:stub_type, 'bar', :parameters => {:audit => "all"}) @catalog.add_resource(@resource) @catalog.add_resource(@other_resource) @inspect.run_command @report.resource_statuses.size.should == 2 @report.resource_statuses.keys.should =~ ['Stub_type[foo]', 'Stub_type[bar]'] end end end after :all do Puppet::Resource::Catalog.indirection.reset_terminus_class Puppet::Transaction::Report.indirection.terminus_class = :processor end end diff --git a/spec/unit/face/ca_spec.rb b/spec/unit/face/ca_spec.rb index b8c82ce99..1df4d7c53 100755 --- a/spec/unit/face/ca_spec.rb +++ b/spec/unit/face/ca_spec.rb @@ -1,355 +1,355 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/face' -describe Puppet::Face[:ca, '0.1.0'] do +describe Puppet::Face[:ca, '0.1.0'], :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :each do Puppet.run_mode.stubs(:master?).returns(true) Puppet[:ca] = true Puppet[:ssldir] = tmpdir("face-ca-ssldir") Puppet::SSL::Host.ca_location = :only Puppet[:certificate_revocation] = true # This is way more intimate than I want to be with the implementation, but # there doesn't seem any other way to test this. --daniel 2011-07-18 Puppet::SSL::CertificateAuthority.stubs(:instance).returns( # ...and this actually does the directory creation, etc. Puppet::SSL::CertificateAuthority.new ) end def make_certs(csr_names, crt_names) Array(csr_names).map do |name| Puppet::SSL::Host.new(name).generate_certificate_request end Array(crt_names).map do |name| Puppet::SSL::Host.new(name).generate end end context "#verify" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:verify) end it "should not explode if there is no certificate" do expect { subject.verify('random-host').should == { :host => 'random-host', :valid => false, :error => 'Could not find a certificate for random-host' } }.should_not raise_error end it "should not explode if there is only a CSR" do make_certs('random-host', []) expect { subject.verify('random-host').should == { :host => 'random-host', :valid => false, :error => 'Could not find a certificate for random-host' } }.should_not raise_error end it "should verify a signed certificate" do make_certs([], 'random-host') subject.verify('random-host').should == { :host => 'random-host', :valid => true } end it "should not verify a revoked certificate" do make_certs([], 'random-host') subject.revoke('random-host') expect { subject.verify('random-host').should == { :host => 'random-host', :valid => false, :error => 'certificate revoked' } }.should_not raise_error end it "should verify a revoked certificate if CRL use was turned off" do make_certs([], 'random-host') subject.revoke('random-host') Puppet[:certificate_revocation] = false subject.verify('random-host').should == { :host => 'random-host', :valid => true } end end context "#fingerprint" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:fingerprint) end it "should have a 'digest' option" do action.should be_option :digest end it "should not explode if there is no certificate" do expect { subject.fingerprint('random-host').should be_nil }.should_not raise_error end it "should fingerprint a CSR" do make_certs('random-host', []) expect { subject.fingerprint('random-host').should =~ /^[0-9A-F:]+$/ }.should_not raise_error end it "should fingerprint a certificate" do make_certs([], 'random-host') subject.fingerprint('random-host').should =~ /^[0-9A-F:]+$/ end %w{md5 MD5 sha1 ShA1 SHA1 RIPEMD160 sha256 sha512}.each do |digest| it "should fingerprint with #{digest.inspect}" do make_certs([], 'random-host') subject.fingerprint('random-host', :digest => digest).should =~ /^[0-9A-F:]+$/ end it "should fingerprint with #{digest.to_sym} as a symbol" do make_certs([], 'random-host') subject.fingerprint('random-host', :digest => digest.to_sym). should =~ /^[0-9A-F:]+$/ end end end context "#print" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:print) end it "should not explode if there is no certificate" do expect { subject.print('random-host').should be_nil }.should_not raise_error end it "should return nothing if there is only a CSR" do make_certs('random-host', []) expect { subject.print('random-host').should be_nil }.should_not raise_error end it "should return the certificate content if there is a cert" do make_certs([], 'random-host') text = subject.print('random-host') text.should be_an_instance_of String text.should =~ /^Certificate:/ text.should =~ /Issuer: CN=Puppet CA: / text.should =~ /Subject: CN=random-host$/ end end context "#sign" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:sign) end it "should not explode if there is no CSR" do expect { subject.sign('random-host'). should == 'Could not find certificate request for random-host' }.should_not raise_error end it "should not explode if there is a signed cert" do make_certs([], 'random-host') expect { subject.sign('random-host'). should == 'Could not find certificate request for random-host' }.should_not raise_error end it "should sign a CSR if one exists" do make_certs('random-host', []) subject.sign('random-host').should be_an_instance_of Puppet::SSL::Certificate list = subject.list(:signed => true) list.length.should == 1 list.first.name.should == 'random-host' end end context "#generate" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:generate) end it "should generate a certificate if requested" do subject.list(:all => true).should == [] subject.generate('random-host') list = subject.list(:signed => true) list.length.should == 1 list.first.name.should == 'random-host' end it "should not explode if a CSR with that name already exists" do make_certs('random-host', []) expect { subject.generate('random-host').should =~ /already has a certificate request/ }.should_not raise_error end it "should not explode if the certificate with that name already exists" do make_certs([], 'random-host') expect { subject.generate('random-host').should =~ /already has a certificate/ }.should_not raise_error end end context "#revoke" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:revoke) end it "should not explode when asked to revoke something that doesn't exist" do expect { subject.revoke('nonesuch') }.should_not raise_error end it "should let the user know what went wrong" do subject.revoke('nonesuch').should == 'Nothing was revoked' end it "should revoke a certificate" do make_certs([], 'random-host') found = subject.list(:all => true, :subject => 'random-host') subject.get_action(:list).when_rendering(:console).call(found). should =~ /^\+ random-host/ subject.revoke('random-host') found = subject.list(:all => true, :subject => 'random-host') subject.get_action(:list).when_rendering(:console).call(found). should =~ /^- random-host \([:0-9A-F]+\) \(certificate revoked\)/ end end context "#destroy" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:destroy) end it "should not explode when asked to delete something that doesn't exist" do expect { subject.destroy('nonesuch') }.should_not raise_error end it "should let the user know if nothing was deleted" do subject.destroy('nonesuch').should == "Nothing was deleted" end it "should destroy a CSR, if we have one" do make_certs('random-host', []) subject.list(:pending => true, :subject => 'random-host').should_not == [] subject.destroy('random-host') subject.list(:pending => true, :subject => 'random-host').should == [] end it "should destroy a certificate, if we have one" do make_certs([], 'random-host') subject.list(:signed => true, :subject => 'random-host').should_not == [] subject.destroy('random-host') subject.list(:signed => true, :subject => 'random-host').should == [] end it "should tell the user something was deleted" do make_certs([], 'random-host') subject.list(:signed => true, :subject => 'random-host').should_not == [] subject.destroy('random-host'). should == "Deleted for random-host: Puppet::SSL::Certificate, Puppet::SSL::Key" end end context "#list" do let :action do Puppet::Face[:ca, '0.1.0'].get_action(:list) end context "options" do subject { Puppet::Face[:ca, '0.1.0'].get_action(:list) } it { should be_option :pending } it { should be_option :signed } it { should be_option :all } it { should be_option :subject } end context "with no hosts in CA" do [:pending, :signed, :all].each do |type| it "should return nothing for #{type}" do subject.list(type => true).should == [] end it "should not fail when a matcher is passed" do expect { subject.list(type => true, :subject => '.').should == [] }.should_not raise_error end end end context "with some hosts" do csr_names = (1..3).map {|n| "csr-#{n}" } crt_names = (1..3).map {|n| "crt-#{n}" } all_names = csr_names + crt_names { {} => csr_names, { :pending => true } => csr_names, { :signed => true } => crt_names, { :all => true } => all_names, { :pending => true, :signed => true } => all_names, }.each do |input, expect| it "should map #{input.inspect} to #{expect.inspect}" do make_certs(csr_names, crt_names) subject.list(input).map(&:name).should =~ expect end ['', '.', '2', 'none'].each do |pattern| filtered = expect.select {|x| Regexp.new(pattern).match(x) } it "should filter all hosts matching #{pattern.inspect} to #{filtered.inspect}" do make_certs(csr_names, crt_names) subject.list(input.merge :subject => pattern).map(&:name).should =~ filtered end end end context "when_rendering :console" do { [["csr1.local"], []] => '^ csr1.local ', [[], ["crt1.local"]] => '^\+ crt1.local ', [["csr2"], ["crt2"]] => ['^ csr2 ', '^\+ crt2 '] }.each do |input, pattern| it "should render #{input.inspect} to match #{pattern.inspect}" do make_certs(*input) text = action.when_rendering(:console).call(subject.list(:all => true)) Array(pattern).each do |item| text.should =~ Regexp.new(item) end end end end end end actions = %w{destroy list revoke generate sign print verify fingerprint} actions.each do |action| it { should be_action action } it "should fail #{action} when not a CA" do Puppet[:ca] = false expect { case subject.method(action).arity when -1 then subject.send(action) when -2 then subject.send(action, 'dummy') else raise "#{action} has arity #{subject.method(action).arity}" end }.should raise_error(/Not a CA/) end end end diff --git a/spec/unit/indirector/certificate_request/ca_spec.rb b/spec/unit/indirector/certificate_request/ca_spec.rb index fb758b59e..36628df9d 100755 --- a/spec/unit/indirector/certificate_request/ca_spec.rb +++ b/spec/unit/indirector/certificate_request/ca_spec.rb @@ -1,64 +1,64 @@ #!/usr/bin/env rspec # # Created by Luke Kanies on 2008-3-7. # Copyright (c) 2007. All rights reserved. require 'spec_helper' require 'puppet/ssl/host' require 'puppet/sslcertificates' require 'puppet/sslcertificates/ca' require 'puppet/indirector/certificate_request/ca' -describe Puppet::SSL::CertificateRequest::Ca, :fails_on_windows => true do +describe Puppet::SSL::CertificateRequest::Ca, :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before :each do Puppet[:ssldir] = tmpdir('ssl') Puppet::SSL::Host.ca_location = :local Puppet[:localcacert] = Puppet[:cacert] Puppet::SSLCertificates::CA.new.mkrootcert @ca = Puppet::SSL::CertificateAuthority.new end after :all do Puppet::SSL::Host.ca_location = :none end it "should have documentation" do Puppet::SSL::CertificateRequest::Ca.doc.should be_instance_of(String) end it "should use the :csrdir as the collection directory" do Puppet.settings.expects(:value).with(:csrdir).returns "/request/dir" Puppet::SSL::CertificateRequest::Ca.collection_directory.should == "/request/dir" end it "should overwrite the previous certificate request if allow_duplicate_certs is true" do Puppet[:allow_duplicate_certs] = true host = Puppet::SSL::Host.new("foo") host.generate_certificate_request @ca.sign(host.name) Puppet::SSL::Host.indirection.find("foo").generate_certificate_request Puppet::SSL::Certificate.indirection.find("foo").name.should == "foo" Puppet::SSL::CertificateRequest.indirection.find("foo").name.should == "foo" Puppet::SSL::Host.indirection.find("foo").state.should == "requested" end it "should reject a new certificate request if allow_duplicate_certs is false" do Puppet[:allow_duplicate_certs] = false host = Puppet::SSL::Host.new("bar") host.generate_certificate_request @ca.sign(host.name) expect { Puppet::SSL::Host.indirection.find("bar").generate_certificate_request }.should raise_error(/ignoring certificate request/) Puppet::SSL::Certificate.indirection.find("bar").name.should == "bar" Puppet::SSL::CertificateRequest.indirection.find("bar").should be_nil Puppet::SSL::Host.indirection.find("bar").state.should == "signed" end end diff --git a/spec/unit/network/handler/fileserver_spec.rb b/spec/unit/network/handler/fileserver_spec.rb index 08852634d..93f124882 100755 --- a/spec/unit/network/handler/fileserver_spec.rb +++ b/spec/unit/network/handler/fileserver_spec.rb @@ -1,169 +1,169 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/network/handler/fileserver' describe Puppet::Network::Handler::FileServer do include PuppetSpec::Files def create_file(filename) File.open(filename, "w") { |f| f.puts filename} end def create_nested_file dirname = File.join(@basedir, "nested_dir") Dir.mkdir(dirname) file = File.join(dirname, "nested_dir_file") create_file(file) end before do @basedir = tmpdir("test_network_handler") @file = File.join(@basedir, "aFile") @link = File.join(@basedir, "aLink") create_file(@file) @mount = Puppet::Network::Handler::FileServer::Mount.new("some_path", @basedir) end it "should list a single directory" do @mount.list("/", false, false).should == [["/", "directory"]] end it "should list a file within a directory when given the file path" do @mount.list("/aFile", false, "false").should == [["/", "file"]] end it "should list a file within a directory when given the file path with recursion" do @mount.list("/aFile", true, "false").should == [["/", "file"]] end it "should return nil for a non-existent path" do @mount.list("/no_such_file", false, false).should be(nil) end - it "should list a symbolic link as a file when given the link path" do + it "should list a symbolic link as a file when given the link path", :unless => Puppet.features.microsoft_windows? do File.symlink(@file, @link) @mount.list("/aLink", false, false).should == [["/", "file"]] end - it "should return nil for a dangling symbolic link when given the link path" do + it "should return nil for a dangling symbolic link when given the link path", :unless => Puppet.features.microsoft_windows? do File.symlink("/some/where", @link) @mount.list("/aLink", false, false).should be(nil) end it "should list directory contents of a flat directory structure when asked to recurse" do list = @mount.list("/", true, false) list.should include(["/aFile", "file"]) list.should include(["/", "directory"]) list.should have(2).items end it "should list the contents of a nested directory" do create_nested_file list = @mount.list("/", true, false) list.sort.should == [ ["/aFile", "file"], ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort end it "should list the contents of a directory ignoring files that match" do create_nested_file list = @mount.list("/", true, "*File") list.sort.should == [ ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort end it "should list the contents of a directory ignoring directories that match" do create_nested_file list = @mount.list("/", true, "*nested_dir") list.sort.should == [ ["/aFile", "file"], ["/", "directory"] ].sort end it "should list the contents of a directory ignoring all ignore patterns that match" do create_nested_file list = @mount.list("/", true, ["*File" , "*nested_dir"]) list.should == [ ["/", "directory"] ] end it "should list the directory when recursing to a depth of zero" do create_nested_file list = @mount.list("/", 0, false) list.should == [["/", "directory"]] end it "should list the base directory and files and nested directory to a depth of one" do create_nested_file list = @mount.list("/", 1, false) list.sort.should == [ ["/aFile", "file"], ["/nested_dir", "directory"], ["/", "directory"] ].sort end it "should list the base directory and files and nested directory to a depth of two" do create_nested_file list = @mount.list("/", 2, false) list.sort.should == [ ["/aFile", "file"], ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort end it "should list the base directory and files and nested directory to a depth greater than the directory structure" do create_nested_file list = @mount.list("/", 42, false) list.sort.should == [ ["/aFile", "file"], ["/", "directory"] , ["/nested_dir", "directory"], ["/nested_dir/nested_dir_file", "file"]].sort end - it "should list a valid symbolic link as a file when recursing base dir" do + it "should list a valid symbolic link as a file when recursing base dir", :unless => Puppet.features.microsoft_windows? do File.symlink(@file, @link) list = @mount.list("/", true, false) list.sort.should == [ ["/", "directory"], ["/aFile", "file"], ["/aLink", "file"] ].sort end - it "should not error when a dangling symlink is present" do + it "should not error when a dangling symlink is present", :unless => Puppet.features.microsoft_windows? do File.symlink("/some/where", @link) lambda { @mount.list("/", true, false) }.should_not raise_error end - it "should return the directory contents of valid entries when a dangling symlink is present" do + it "should return the directory contents of valid entries when a dangling symlink is present", :unless => Puppet.features.microsoft_windows? do File.symlink("/some/where", @link) list = @mount.list("/", true, false) list.sort.should == [ ["/aFile", "file"], ["/", "directory"] ].sort end describe Puppet::Network::Handler::FileServer::PluginMount, :'fails_on_ruby_1.9.2' => true do PLUGINS = Puppet::Network::Handler::FileServer::PLUGINS # create a module plugin hierarchy def create_plugin(mod, plugin) dirname = File.join(@basedir, mod) Dir.mkdir(dirname) plugins = File.join(dirname, PLUGINS) Dir.mkdir(plugins) facter = File.join(plugins, plugin) Dir.mkdir(facter) create_file(File.join(facter,"fact.rb")) end before :each do @modules = ["one","two"] @modules.each { |m| create_plugin(m, "facter") } Puppet::Node::Environment.new.stubs(:modulepath).returns @basedir @mount = Puppet::Network::Handler::FileServer::PluginMount.new(PLUGINS) @mount.allow("*") end it "should list a file within a directory when given the file path with recursion" do @mount.list("facter/fact.rb", true, "false").should == [["/", "file"], ["/", "file"]] end it "should return a merged view of all plugins for all modules" do list = @mount.list("facter",true,false) list.should == [["/", "directory"], ["/fact.rb", "file"], ["/", "directory"], ["/fact.rb", "file"]] end it "should not fail for inexistant plugins type" do @mount.list("puppet/parser",true,false) end end after do FileUtils.rm_rf(@basedir) end end diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb index be74a1052..8504b02e3 100755 --- a/spec/unit/network/http/webrick_spec.rb +++ b/spec/unit/network/http/webrick_spec.rb @@ -1,340 +1,340 @@ #!/usr/bin/env rspec # # Created by Rick Bradley on 2007-10-15. # Copyright (c) 2007. All rights reserved. require 'spec_helper' require 'puppet/network/handler' require 'puppet/network/http' require 'puppet/network/http/webrick' -describe Puppet::Network::HTTP::WEBrick, "after initializing" do +describe Puppet::Network::HTTP::WEBrick, "after initializing", :unless => Puppet.features.microsoft_windows? do it "should not be listening" do Puppet::Network::HTTP::WEBrick.new.should_not be_listening end end -describe Puppet::Network::HTTP::WEBrick, "when turning on listening" do +describe Puppet::Network::HTTP::WEBrick, "when turning on listening", :unless => Puppet.features.microsoft_windows? do before do @mock_webrick = stub('webrick', :[] => {}, :listeners => [], :status => :Running) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging @listen_params = { :address => "127.0.0.1", :port => 31337, :xmlrpc_handlers => [], :protocols => [ :rest ] } end it "should fail if already listening" do @server.listen(@listen_params) Proc.new { @server.listen(@listen_params) }.should raise_error(RuntimeError) end it "should require at least one protocol" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :protocols == k}) }.should raise_error(ArgumentError) end it "should require a listening address to be specified" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :address == k})}.should raise_error(ArgumentError) end it "should require a listening port to be specified" do Proc.new { @server.listen(@listen_params.delete_if {|k,v| :port == k})}.should raise_error(ArgumentError) end it "should order a webrick server to start in a separate thread" do @mock_webrick.expects(:start) # If you remove this you'll sometimes get race condition problems Thread.expects(:new).yields @server.listen(@listen_params) end it "should tell webrick to listen on the specified address and port" do WEBrick::HTTPServer.expects(:new).with {|args| args[:Port] == 31337 and args[:BindAddress] == "127.0.0.1" }.returns(@mock_webrick) @server.listen(@listen_params) end it "should configure a logger for webrick" do @server.expects(:setup_logger).returns(:Logger => :mylogger) WEBrick::HTTPServer.expects(:new).with {|args| args[:Logger] == :mylogger }.returns(@mock_webrick) @server.listen(@listen_params) end it "should configure SSL for webrick" do @server.expects(:setup_ssl).returns(:Ssl => :testing, :Other => :yay) WEBrick::HTTPServer.expects(:new).with {|args| args[:Ssl] == :testing and args[:Other] == :yay }.returns(@mock_webrick) @server.listen(@listen_params) end it "should be listening" do @server.listen(@listen_params) @server.should be_listening end describe "when the REST protocol is requested" do it "should register the REST handler at /" do # We don't care about the options here. @mock_webrick.expects(:mount).with { |path, klass, options| path == "/" and klass == Puppet::Network::HTTP::WEBrickREST } @server.listen(@listen_params.merge(:protocols => [:rest])) end end describe "when the XMLRPC protocol is requested" do before do @servlet = mock 'servlet' Puppet::Network::XMLRPC::WEBrickServlet.stubs(:new).returns @servlet @master_handler = mock('master_handler') @file_handler = mock('file_handler') @master = mock 'master' @file = mock 'file' @master_handler.stubs(:new).returns @master @file_handler.stubs(:new).returns @file Puppet::Network::Handler.stubs(:handler).with(:master).returns @master_handler Puppet::Network::Handler.stubs(:handler).with(:fileserver).returns @file_handler end it "should do nothing if no xmlrpc handlers have been specified" do Puppet::Network::Handler.expects(:handler).never @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [])) end it "should look the handler classes up via their base class" do Puppet::Network::Handler.expects(:handler).with(:master).returns @master_handler Puppet::Network::Handler.expects(:handler).with(:fileserver).returns @file_handler @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end it "should create an instance for each requested xmlrpc handler" do @master_handler.expects(:new).returns @master @file_handler.expects(:new).returns @file @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end it "should create a webrick servlet with the xmlrpc handler instances" do Puppet::Network::XMLRPC::WEBrickServlet.expects(:new).with([@master, @file]).returns @servlet @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end it "should mount the webrick servlet at /RPC2" do @mock_webrick.stubs(:mount) @mock_webrick.expects(:mount).with("/RPC2", @servlet) @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end end end -describe Puppet::Network::HTTP::WEBrick, "when looking up the class to handle a protocol" do +describe Puppet::Network::HTTP::WEBrick, "when looking up the class to handle a protocol", :unless => Puppet.features.microsoft_windows? do it "should require a protocol" do lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol }.should raise_error(ArgumentError) end it "should accept a protocol" do lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("bob") }.should_not raise_error(ArgumentError) end it "should use a WEBrick + REST class when a REST protocol is specified" do Puppet::Network::HTTP::WEBrick.class_for_protocol("rest").should == Puppet::Network::HTTP::WEBrickREST end it "should fail when an unknown protocol is specified" do lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("abcdefg") }.should raise_error end end -describe Puppet::Network::HTTP::WEBrick, "when turning off listening" do +describe Puppet::Network::HTTP::WEBrick, "when turning off listening", :unless => Puppet.features.microsoft_windows? do before do @mock_webrick = stub('webrick', :[] => {}, :listeners => [], :status => :Running) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } end it "should fail unless listening" do Proc.new { @server.unlisten }.should raise_error(RuntimeError) end it "should order webrick server to stop" do @mock_webrick.expects(:shutdown) @server.listen(@listen_params) @server.unlisten end it "should no longer be listening" do @server.listen(@listen_params) @server.unlisten @server.should_not be_listening end end -describe Puppet::Network::HTTP::WEBrick do +describe Puppet::Network::HTTP::WEBrick, :unless => Puppet.features.microsoft_windows? do before do @mock_webrick = stub('webrick', :[] => {}) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new end describe "when configuring an http logger" do before do Puppet.settings.stubs(:value).returns "something" Puppet.settings.stubs(:use) @filehandle = stub 'handle', :fcntl => nil, :sync => nil File.stubs(:open).returns @filehandle end it "should use the settings for :main, :ssl, and the process name" do Puppet.settings.stubs(:value).with(:name).returns "myname" Puppet.settings.expects(:use).with(:main, :ssl, "myname") @server.setup_logger end it "should use the masterlog if the run_mode is master" do Puppet.run_mode.stubs(:master?).returns(true) Puppet.settings.expects(:value).with(:masterhttplog).returns "/master/log" File.expects(:open).with("/master/log", "a+").returns @filehandle @server.setup_logger end it "should use the httplog if the run_mode is not master" do Puppet.run_mode.stubs(:master?).returns(false) Puppet.settings.expects(:value).with(:httplog).returns "/other/log" File.expects(:open).with("/other/log", "a+").returns @filehandle @server.setup_logger end describe "and creating the logging filehandle" do it "should set fcntl to 'Fcntl::F_SETFD, Fcntl::FD_CLOEXEC'" do @filehandle.expects(:fcntl).with(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) @server.setup_logger end it "should sync the filehandle" do @filehandle.expects(:sync) @server.setup_logger end end it "should create a new WEBrick::Log instance with the open filehandle" do WEBrick::Log.expects(:new).with(@filehandle) @server.setup_logger end it "should set debugging if the current loglevel is :debug" do Puppet::Util::Log.expects(:level).returns :debug WEBrick::Log.expects(:new).with { |handle, debug| debug == WEBrick::Log::DEBUG } @server.setup_logger end it "should return the logger as the main log" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger @server.setup_logger[:Logger].should == logger end it "should return the logger as the access log using both the Common and Referer log format" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger @server.setup_logger[:AccessLog].should == [ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT], [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT] ] end end describe "when configuring ssl" do before do @key = stub 'key', :content => "mykey" @cert = stub 'cert', :content => "mycert" @host = stub 'host', :key => @key, :certificate => @cert, :name => "yay", :ssl_store => "mystore" Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns @cert Puppet::SSL::Host.stubs(:localhost).returns @host end it "should use the key from the localhost SSL::Host instance" do Puppet::SSL::Host.expects(:localhost).returns @host @host.expects(:key).returns @key @server.setup_ssl[:SSLPrivateKey].should == "mykey" end it "should configure the certificate" do @server.setup_ssl[:SSLCertificate].should == "mycert" end it "should fail if no CA certificate can be found" do Puppet::SSL::Certificate.indirection.stubs(:find).with('ca').returns nil lambda { @server.setup_ssl }.should raise_error(Puppet::Error) end it "should specify the path to the CA certificate" do Puppet.settings.stubs(:value).returns "whatever" Puppet.settings.stubs(:value).with(:hostcrl).returns 'false' Puppet.settings.stubs(:value).with(:localcacert).returns '/ca/crt' @server.setup_ssl[:SSLCACertificateFile].should == "/ca/crt" end it "should start ssl immediately" do @server.setup_ssl[:SSLStartImmediately].should be_true end it "should enable ssl" do @server.setup_ssl[:SSLEnable].should be_true end it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do @server.setup_ssl[:SSLVerifyClient].should == OpenSSL::SSL::VERIFY_PEER end it "should add an x509 store" do Puppet.settings.stubs(:value).returns "whatever" Puppet.settings.stubs(:value).with(:hostcrl).returns '/my/crl' @host.expects(:ssl_store).returns "mystore" @server.setup_ssl[:SSLCertificateStore].should == "mystore" end it "should set the certificate name to 'nil'" do @server.setup_ssl[:SSLCertName].should be_nil end end end diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index eb20aa7ef..78d383440 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -1,321 +1,321 @@ #!/usr/bin/env rspec require 'spec_helper' require 'tmpdir' require 'puppet/node/environment' require 'puppet/util/execution' describe Puppet::Node::Environment do include PuppetSpec::Files after do Puppet::Node::Environment.clear end it "should use the filetimeout for the ttl for the modulepath" do Puppet::Node::Environment.attr_ttl(:modulepath).should == Integer(Puppet[:filetimeout]) end it "should use the filetimeout for the ttl for the module list" do Puppet::Node::Environment.attr_ttl(:modules).should == Integer(Puppet[:filetimeout]) end it "should use the default environment if no name is provided while initializing an environment" do Puppet.settings.expects(:value).with(:environment).returns("one") Puppet::Node::Environment.new.name.should == :one end it "should treat environment instances as singletons" do Puppet::Node::Environment.new("one").should equal(Puppet::Node::Environment.new("one")) end it "should treat an environment specified as names or strings as equivalent" do Puppet::Node::Environment.new(:one).should equal(Puppet::Node::Environment.new("one")) end it "should return its name when converted to a string" do Puppet::Node::Environment.new(:one).to_s.should == "one" end it "should just return any provided environment if an environment is provided as the name" do one = Puppet::Node::Environment.new(:one) Puppet::Node::Environment.new(one).should equal(one) end describe "when managing known resource types" do before do @env = Puppet::Node::Environment.new("dev") @collection = Puppet::Resource::TypeCollection.new(@env) @env.stubs(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) Thread.current[:known_resource_types] = nil end it "should create a resource type collection if none exists" do Puppet::Resource::TypeCollection.expects(:new).with(@env).returns @collection @env.known_resource_types.should equal(@collection) end it "should reuse any existing resource type collection" do @env.known_resource_types.should equal(@env.known_resource_types) end it "should perform the initial import when creating a new collection" do @env = Puppet::Node::Environment.new("dev") @env.expects(:perform_initial_import).returns(Puppet::Parser::AST::Hostclass.new('')) @env.known_resource_types end it "should return the same collection even if stale if it's the same thread" do Puppet::Resource::TypeCollection.stubs(:new).returns @collection @env.known_resource_types.stubs(:stale?).returns true @env.known_resource_types.should equal(@collection) end it "should return the current thread associated collection if there is one" do Thread.current[:known_resource_types] = @collection @env.known_resource_types.should equal(@collection) end it "should give to all threads using the same environment the same collection if the collection isn't stale" do original_thread_type_collection = Puppet::Resource::TypeCollection.new(@env) Puppet::Resource::TypeCollection.expects(:new).with(@env).returns original_thread_type_collection @env.known_resource_types.should equal(original_thread_type_collection) original_thread_type_collection.expects(:require_reparse?).returns(false) Puppet::Resource::TypeCollection.stubs(:new).with(@env).returns @collection t = Thread.new { @env.known_resource_types.should equal(original_thread_type_collection) } t.join end it "should generate a new TypeCollection if the current one requires reparsing" do old_type_collection = @env.known_resource_types old_type_collection.stubs(:require_reparse?).returns true Thread.current[:known_resource_types] = nil new_type_collection = @env.known_resource_types new_type_collection.should be_a Puppet::Resource::TypeCollection new_type_collection.should_not equal(old_type_collection) end end it "should validate the modulepath directories" do real_file = tmpdir('moduledir') path = %W[/one /two #{real_file}].join(File::PATH_SEPARATOR) Puppet[:modulepath] = path env = Puppet::Node::Environment.new("testing") env.modulepath.should == [real_file] end it "should prefix the value of the 'PUPPETLIB' environment variable to the module path if present" do Puppet::Util::Execution.withenv("PUPPETLIB" => %w{/l1 /l2}.join(File::PATH_SEPARATOR)) do env = Puppet::Node::Environment.new("testing") module_path = %w{/one /two}.join(File::PATH_SEPARATOR) env.expects(:validate_dirs).with(%w{/l1 /l2 /one /two}).returns %w{/l1 /l2 /one /two} env.expects(:[]).with(:modulepath).returns module_path env.modulepath.should == %w{/l1 /l2 /one /two} end end describe "when validating modulepath or manifestdir directories" do before :each do @path_one = make_absolute('/one') @path_two = make_absolute('/two') end it "should not return non-directories" do env = Puppet::Node::Environment.new("testing") FileTest.expects(:directory?).with(@path_one).returns true FileTest.expects(:directory?).with(@path_two).returns false env.validate_dirs([@path_one, @path_two]).should == [@path_one] end it "should use the current working directory to fully-qualify unqualified paths" do FileTest.stubs(:directory?).returns true env = Puppet::Node::Environment.new("testing") - two = File.join(Dir.getwd, "two") + two = File.expand_path(File.join(Dir.getwd, "two")) env.validate_dirs([@path_one, 'two']).should == [@path_one, two] end end describe "when modeling a specific environment" do it "should have a method for returning the environment name" do Puppet::Node::Environment.new("testing").name.should == :testing end it "should provide an array-like accessor method for returning any environment-specific setting" do env = Puppet::Node::Environment.new("testing") env.should respond_to(:[]) end it "should ask the Puppet settings instance for the setting qualified with the environment name" do Puppet.settings.expects(:value).with("myvar", :testing).returns("myval") env = Puppet::Node::Environment.new("testing") env["myvar"].should == "myval" end it "should be able to return an individual module that exists in its module path" do env = Puppet::Node::Environment.new("testing") mod = mock 'module' Puppet::Module.expects(:new).with("one", env).returns mod mod.expects(:exist?).returns true env.module("one").should equal(mod) end it "should return nil if asked for a module that does not exist in its path" do env = Puppet::Node::Environment.new("testing") mod = mock 'module' Puppet::Module.expects(:new).with("one", env).returns mod mod.expects(:exist?).returns false env.module("one").should be_nil end it "should be able to return its modules" do Puppet::Node::Environment.new("testing").should respond_to(:modules) end describe ".modules" do it "should return a module named for every directory in each module path" do env = Puppet::Node::Environment.new("testing") env.expects(:modulepath).at_least_once.returns %w{/a /b} Dir.expects(:entries).with("/a").returns %w{foo bar} Dir.expects(:entries).with("/b").returns %w{bee baz} env.modules.collect{|mod| mod.name}.sort.should == %w{foo bar bee baz}.sort end it "should remove duplicates" do env = Puppet::Node::Environment.new("testing") env.expects(:modulepath).returns( %w{/a /b} ).at_least_once Dir.expects(:entries).with("/a").returns %w{foo} Dir.expects(:entries).with("/b").returns %w{foo} env.modules.collect{|mod| mod.name}.sort.should == %w{foo} end it "should ignore invalid modules" do env = Puppet::Node::Environment.new("testing") env.stubs(:modulepath).returns %w{/a} Dir.expects(:entries).with("/a").returns %w{foo bar} Puppet::Module.expects(:new).with { |name, env| name == "foo" }.returns mock("foomod", :name => "foo") Puppet::Module.expects(:new).with { |name, env| name == "bar" }.raises( Puppet::Module::InvalidName, "name is invalid" ) env.modules.collect{|mod| mod.name}.sort.should == %w{foo} end it "should create modules with the correct environment" do env = Puppet::Node::Environment.new("testing") env.expects(:modulepath).at_least_once.returns %w{/a} Dir.expects(:entries).with("/a").returns %w{foo} env.modules.each {|mod| mod.environment.should == env } end it "should cache the module list" do env = Puppet::Node::Environment.new("testing") env.expects(:modulepath).at_least_once.returns %w{/a} Dir.expects(:entries).once.with("/a").returns %w{foo} env.modules env.modules end end end describe Puppet::Node::Environment::Helper do before do @helper = Object.new @helper.extend(Puppet::Node::Environment::Helper) end it "should be able to set and retrieve the environment" do @helper.environment = :foo @helper.environment.name.should == :foo end it "should accept an environment directly" do env = Puppet::Node::Environment.new :foo @helper.environment = env @helper.environment.name.should == :foo end it "should accept an environment as a string" do env = Puppet::Node::Environment.new "foo" @helper.environment = env @helper.environment.name.should == :foo end end describe "when performing initial import" do before do @parser = Puppet::Parser::Parser.new("test") Puppet::Parser::Parser.stubs(:new).returns @parser @env = Puppet::Node::Environment.new("env") end it "should set the parser's string to the 'code' setting and parse if code is available" do Puppet.settings[:code] = "my code" @parser.expects(:string=).with "my code" @parser.expects(:parse) @env.instance_eval { perform_initial_import } end it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do filename = tmpfile('myfile') File.open(filename, 'w'){|f| } Puppet.settings[:manifest] = filename @parser.expects(:file=).with filename @parser.expects(:parse) @env.instance_eval { perform_initial_import } end it "should pass the manifest file to the parser even if it does not exist on disk" do filename = tmpfile('myfile') Puppet.settings[:code] = "" Puppet.settings[:manifest] = filename @parser.expects(:file=).with(filename).once @parser.expects(:parse).once @env.instance_eval { perform_initial_import } end it "should fail helpfully if there is an error importing" do File.stubs(:exist?).returns true @env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(@env) @parser.expects(:file=).once @parser.expects(:parse).raises ArgumentError lambda { @env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error) end it "should not do anything if the ignore_import settings is set" do Puppet.settings[:ignoreimport] = true @parser.expects(:string=).never @parser.expects(:file=).never @parser.expects(:parse).never @env.instance_eval { perform_initial_import } end it "should mark the type collection as needing a reparse when there is an error parsing" do @parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...") @env.stubs(:known_resource_types).returns Puppet::Resource::TypeCollection.new(@env) lambda { @env.instance_eval { perform_initial_import } }.should raise_error(Puppet::Error, /Syntax error at .../) @env.known_resource_types.require_reparse?.should be_true end end end diff --git a/spec/unit/resource/catalog_spec.rb b/spec/unit/resource/catalog_spec.rb index ce783e393..f5f779fbf 100755 --- a/spec/unit/resource/catalog_spec.rb +++ b/spec/unit/resource/catalog_spec.rb @@ -1,1085 +1,1086 @@ #!/usr/bin/env rspec require 'spec_helper' describe Puppet::Resource::Catalog, "when compiling" do include PuppetSpec::Files before do @basepath = make_absolute("/somepath") # stub this to not try to create state.yaml Puppet::Util::Storage.stubs(:store) end it "should be able to write its list of classes to the class file" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.add_class "foo", "bar" Puppet.settings.expects(:value).with(:classfile).returns "/class/file" fh = mock 'filehandle' File.expects(:open).with("/class/file", "w").yields fh fh.expects(:puts).with "foo\nbar" @catalog.write_class_file end it "should have a client_version attribute" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.client_version = 5 @catalog.client_version.should == 5 end it "should have a server_version attribute" do @catalog = Puppet::Resource::Catalog.new("host") @catalog.server_version = 5 @catalog.server_version.should == 5 end describe "when compiling" do it "should accept tags" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one") config.tags.should == %w{one} end it "should accept multiple tags at once" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one", "two") config.tags.should == %w{one two} end it "should convert all tags to strings" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one", :two) config.tags.should == %w{one two} end it "should tag with both the qualified name and the split name" do config = Puppet::Resource::Catalog.new("mynode") config.tag("one::two") config.tags.include?("one").should be_true config.tags.include?("one::two").should be_true end it "should accept classes" do config = Puppet::Resource::Catalog.new("mynode") config.add_class("one") config.classes.should == %w{one} config.add_class("two", "three") config.classes.should == %w{one two three} end it "should tag itself with passed class names" do config = Puppet::Resource::Catalog.new("mynode") config.add_class("one") config.tags.should == %w{one} end end describe "when extracting transobjects" do def mkscope @node = Puppet::Node.new("mynode") @compiler = Puppet::Parser::Compiler.new(@node) # XXX This is ridiculous. @compiler.send(:evaluate_main) @scope = @compiler.topscope end def mkresource(type, name) Puppet::Parser::Resource.new(type, name, :source => @source, :scope => @scope) end it "should fail if no 'main' stage can be found" do lambda { Puppet::Resource::Catalog.new("mynode").extract }.should raise_error(Puppet::DevError) end it "should warn if any non-main stages are present" do config = Puppet::Resource::Catalog.new("mynode") @scope = mkscope @source = mock 'source' main = mkresource("stage", "main") config.add_resource(main) other = mkresource("stage", "other") config.add_resource(other) Puppet.expects(:warning) config.extract end it "should always create a TransBucket for the 'main' stage" do config = Puppet::Resource::Catalog.new("mynode") @scope = mkscope @source = mock 'source' main = mkresource("stage", "main") config.add_resource(main) result = config.extract result.type.should == "Stage" result.name.should == "main" end # Now try it with a more complicated graph -- a three tier graph, each tier it "should transform arbitrarily deep graphs into isomorphic trees" do config = Puppet::Resource::Catalog.new("mynode") @scope = mkscope @scope.stubs(:tags).returns([]) @source = mock 'source' # Create our scopes. top = mkresource "stage", "main" config.add_resource top topbucket = [] topbucket.expects(:classes=).with([]) top.expects(:to_trans).returns(topbucket) topres = mkresource "file", "/top" topres.expects(:to_trans).returns(:topres) config.add_edge top, topres middle = mkresource "class", "middle" middle.expects(:to_trans).returns([]) config.add_edge top, middle midres = mkresource "file", "/mid" midres.expects(:to_trans).returns(:midres) config.add_edge middle, midres bottom = mkresource "class", "bottom" bottom.expects(:to_trans).returns([]) config.add_edge middle, bottom botres = mkresource "file", "/bot" botres.expects(:to_trans).returns(:botres) config.add_edge bottom, botres toparray = config.extract # This is annoying; it should look like: # [[[:botres], :midres], :topres] # but we can't guarantee sort order. toparray.include?(:topres).should be_true midarray = toparray.find { |t| t.is_a?(Array) } midarray.include?(:midres).should be_true botarray = midarray.find { |t| t.is_a?(Array) } botarray.include?(:botres).should be_true end end describe " when converting to a Puppet::Resource catalog" do before do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @top = Puppet::TransObject.new 'top', "class" @topobject = Puppet::TransObject.new '/topobject', "file" @middle = Puppet::TransObject.new 'middle', "class" @middleobject = Puppet::TransObject.new '/middleobject', "file" @bottom = Puppet::TransObject.new 'bottom', "class" @bottomobject = Puppet::TransObject.new '/bottomobject', "file" @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_resource(*@resources) @original.add_edge(@top, @topobject) @original.add_edge(@top, @middle) @original.add_edge(@middle, @middleobject) @original.add_edge(@middle, @bottom) @original.add_edge(@bottom, @bottomobject) @catalog = @original.to_resource end it "should copy over the version" do @original.version = "foo" @original.to_resource.version.should == "foo" end it "should convert parser resources to plain resources" do resource = Puppet::Parser::Resource.new(:file, "foo", :scope => stub("scope", :environment => nil, :namespaces => nil), :source => stub("source")) catalog = Puppet::Resource::Catalog.new("whev") catalog.add_resource(resource) new = catalog.to_resource new.resource(:file, "foo").class.should == Puppet::Resource end it "should add all resources as Puppet::Resource instances" do @resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Resource) } end it "should copy the tag list to the new catalog" do @catalog.tags.sort.should == @original.tags.sort end it "should copy the class list to the new catalog" do @catalog.classes.should == @original.classes end it "should duplicate the original edges" do @original.edges.each do |edge| @catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true end end it "should set itself as the catalog for each converted resource" do @catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) } end end describe "when converting to a RAL catalog" do before do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @top = Puppet::Resource.new :class, 'top' @topobject = Puppet::Resource.new :file, @basepath+'/topobject' @middle = Puppet::Resource.new :class, 'middle' @middleobject = Puppet::Resource.new :file, @basepath+'/middleobject' @bottom = Puppet::Resource.new :class, 'bottom' @bottomobject = Puppet::Resource.new :file, @basepath+'/bottomobject' @resources = [@top, @topobject, @middle, @middleobject, @bottom, @bottomobject] @original.add_resource(*@resources) @original.add_edge(@top, @topobject) @original.add_edge(@top, @middle) @original.add_edge(@middle, @middleobject) @original.add_edge(@middle, @bottom) @original.add_edge(@bottom, @bottomobject) @catalog = @original.to_ral end it "should add all resources as RAL instances" do @resources.each { |resource| @catalog.resource(resource.ref).should be_instance_of(Puppet::Type) } end it "should copy the tag list to the new catalog" do @catalog.tags.sort.should == @original.tags.sort end it "should copy the class list to the new catalog" do @catalog.classes.should == @original.classes end it "should duplicate the original edges" do @original.edges.each do |edge| @catalog.edge?(@catalog.resource(edge.source.ref), @catalog.resource(edge.target.ref)).should be_true end end it "should set itself as the catalog for each converted resource" do @catalog.vertices.each { |v| v.catalog.object_id.should equal(@catalog.object_id) } end # This tests #931. it "should not lose track of resources whose names vary" do changer = Puppet::TransObject.new 'changer', 'test' config = Puppet::Resource::Catalog.new('test') config.add_resource(changer) config.add_resource(@top) config.add_edge(@top, changer) resource = stub 'resource', :name => "changer2", :title => "changer2", :ref => "Test[changer2]", :catalog= => nil, :remove => nil #changer is going to get duplicated as part of a fix for aliases 1094 changer.expects(:dup).returns(changer) changer.expects(:to_ral).returns(resource) newconfig = nil proc { @catalog = config.to_ral }.should_not raise_error @catalog.resource("Test[changer2]").should equal(resource) end after do # Remove all resource instances. @catalog.clear(true) end end describe "when filtering" do before :each do @original = Puppet::Resource::Catalog.new("mynode") @original.tag(*%w{one two three}) @original.add_class *%w{four five six} @r1 = stub_everything 'r1', :ref => "File[/a]" @r1.stubs(:respond_to?).with(:ref).returns(true) @r1.stubs(:dup).returns(@r1) @r1.stubs(:is_a?).returns(Puppet::Resource).returns(true) @r2 = stub_everything 'r2', :ref => "File[/b]" @r2.stubs(:respond_to?).with(:ref).returns(true) @r2.stubs(:dup).returns(@r2) @r2.stubs(:is_a?).returns(Puppet::Resource).returns(true) @resources = [@r1,@r2] @original.add_resource(@r1,@r2) end it "should transform the catalog to a resource catalog" do @original.expects(:to_catalog).with { |h,b| h == :to_resource } @original.filter end it "should scan each catalog resource in turn and apply filtering block" do @resources.each { |r| r.expects(:test?) } @original.filter do |r| r.test? end end it "should filter out resources which produce true when the filter block is evaluated" do @original.filter do |r| r == @r1 end.resource("File[/a]").should be_nil end it "should not consider edges against resources that were filtered out" do @original.add_edge(@r1,@r2) @original.filter do |r| r == @r1 end.edge?(@r1,@r2).should_not be end end describe "when functioning as a resource container" do before do @catalog = Puppet::Resource::Catalog.new("host") @one = Puppet::Type.type(:notify).new :name => "one" @two = Puppet::Type.type(:notify).new :name => "two" @dupe = Puppet::Type.type(:notify).new :name => "one" end it "should provide a method to add one or more resources" do @catalog.add_resource @one, @two @catalog.resource(@one.ref).should equal(@one) @catalog.resource(@two.ref).should equal(@two) end it "should add resources to the relationship graph if it exists" do relgraph = @catalog.relationship_graph @catalog.add_resource @one relgraph.should be_vertex(@one) end it "should set itself as the resource's catalog if it is not a relationship graph" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should make all vertices available by resource reference" do @catalog.add_resource(@one) @catalog.resource(@one.ref).should equal(@one) @catalog.vertices.find { |r| r.ref == @one.ref }.should equal(@one) end it "should canonize how resources are referred to during retrieval when both type and title are provided" do @catalog.add_resource(@one) @catalog.resource("notify", "one").should equal(@one) end it "should canonize how resources are referred to during retrieval when just the title is provided" do @catalog.add_resource(@one) @catalog.resource("notify[one]", nil).should equal(@one) end it "should not allow two resources with the same resource reference" do @catalog.add_resource(@one) proc { @catalog.add_resource(@dupe) }.should raise_error(Puppet::Resource::Catalog::DuplicateResourceError) end it "should not store objects that do not respond to :ref" do proc { @catalog.add_resource("thing") }.should raise_error(ArgumentError) end it "should remove all resources when asked" do @catalog.add_resource @one @catalog.add_resource @two @one.expects :remove @two.expects :remove @catalog.clear(true) end it "should support a mechanism for finishing resources" do @one.expects :finish @two.expects :finish @catalog.add_resource @one @catalog.add_resource @two @catalog.finalize end it "should make default resources when finalizing" do @catalog.expects(:make_default_resources) @catalog.finalize end it "should add default resources to the catalog upon creation" do @catalog.make_default_resources @catalog.resource(:schedule, "daily").should_not be_nil end it "should optionally support an initialization block and should finalize after such blocks" do @one.expects :finish @two.expects :finish config = Puppet::Resource::Catalog.new("host") do |conf| conf.add_resource @one conf.add_resource @two end end it "should inform the resource that it is the resource's catalog" do @one.expects(:catalog=).with(@catalog) @catalog.add_resource @one end it "should be able to find resources by reference" do @catalog.add_resource @one @catalog.resource(@one.ref).should equal(@one) end it "should be able to find resources by reference or by type/title tuple" do @catalog.add_resource @one @catalog.resource("notify", "one").should equal(@one) end it "should have a mechanism for removing resources" do @catalog.add_resource @one @one.expects :remove @catalog.remove_resource(@one) @catalog.resource(@one.ref).should be_nil @catalog.vertex?(@one).should be_false end it "should have a method for creating aliases for resources" do @catalog.add_resource @one @catalog.alias(@one, "other") @catalog.resource("notify", "other").should equal(@one) end it "should ignore conflicting aliases that point to the aliased resource" do @catalog.alias(@one, "other") lambda { @catalog.alias(@one, "other") }.should_not raise_error end it "should create aliases for isomorphic resources whose names do not match their titles" do resource = Puppet::Type::File.new(:title => "testing", :path => @basepath+"/something") @catalog.add_resource(resource) @catalog.resource(:file, @basepath+"/something").should equal(resource) end it "should not create aliases for non-isomorphic resources whose names do not match their titles", :fails_on_windows => true do resource = Puppet::Type.type(:exec).new(:title => "testing", :command => "echo", :path => %w{/bin /usr/bin /usr/local/bin}) @catalog.add_resource(resource) # Yay, I've already got a 'should' method @catalog.resource(:exec, "echo").object_id.should == nil.object_id end # This test is the same as the previous, but the behaviour should be explicit. it "should alias using the class name from the resource reference, not the resource class name" do @catalog.add_resource @one @catalog.alias(@one, "other") @catalog.resource("notify", "other").should equal(@one) end it "should fail to add an alias if the aliased name already exists" do @catalog.add_resource @one proc { @catalog.alias @two, "one" }.should raise_error(ArgumentError) end it "should not fail when a resource has duplicate aliases created" do @catalog.add_resource @one proc { @catalog.alias @one, "one" }.should_not raise_error end it "should not create aliases that point back to the resource" do @catalog.alias(@one, "one") @catalog.resource(:notify, "one").should be_nil end it "should be able to look resources up by their aliases" do @catalog.add_resource @one @catalog.alias @one, "two" @catalog.resource(:notify, "two").should equal(@one) end it "should remove resource aliases when the target resource is removed" do @catalog.add_resource @one @catalog.alias(@one, "other") @one.expects :remove @catalog.remove_resource(@one) @catalog.resource("notify", "other").should be_nil end it "should add an alias for the namevar when the title and name differ on isomorphic resource types" do resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah" resource.expects(:isomorphic?).returns(true) @catalog.add_resource(resource) @catalog.resource(:file, "other").should equal(resource) @catalog.resource(:file, @basepath+"/something").ref.should == resource.ref end it "should not add an alias for the namevar when the title and name differ on non-isomorphic resource types" do resource = Puppet::Type.type(:file).new :path => @basepath+"/something", :title => "other", :content => "blah" resource.expects(:isomorphic?).returns(false) @catalog.add_resource(resource) @catalog.resource(:file, resource.title).should equal(resource) # We can't use .should here, because the resources respond to that method. raise "Aliased non-isomorphic resource" if @catalog.resource(:file, resource.name) end it "should provide a method to create additional resources that also registers the resource" do args = {:name => "/yay", :ensure => :file} resource = stub 'file', :ref => "File[/yay]", :catalog= => @catalog, :title => "/yay", :[] => "/yay" Puppet::Type.type(:file).expects(:new).with(args).returns(resource) @catalog.create_resource :file, args @catalog.resource("File[/yay]").should equal(resource) end describe "when adding resources with multiple namevars" do before :each do Puppet::Type.newtype(:multiple) do newparam(:color, :namevar => true) newparam(:designation, :namevar => true) def self.title_patterns [ [ /^(\w+) (\w+)$/, [ [:color, lambda{|x| x}], [:designation, lambda{|x| x}] ] ] ] end end end it "should add an alias using the uniqueness key" do @resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5") @catalog.add_resource(@resource) @catalog.resource(:multiple, "some resource").must == @resource @catalog.resource("Multiple[some resource]").must == @resource @catalog.resource("Multiple[red 5]").must == @resource end it "should conflict with a resource with the same uniqueness key" do @resource = Puppet::Type.type(:multiple).new(:title => "some resource", :color => "red", :designation => "5") @other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "5") @catalog.add_resource(@resource) expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "5"\].*resource \["Multiple", "red", "5"\] already defined/) end it "should conflict when its uniqueness key matches another resource's title" do - @resource = Puppet::Type.type(:file).new(:title => "/tmp/foo") - @other = Puppet::Type.type(:file).new(:title => "another file", :path => "/tmp/foo") + path = make_absolute("/tmp/foo") + @resource = Puppet::Type.type(:file).new(:title => path) + @other = Puppet::Type.type(:file).new(:title => "another file", :path => path) @catalog.add_resource(@resource) - expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias File\[another file\] to \["\/tmp\/foo"\].*resource \["File", "\/tmp\/foo"\] already defined/) + expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias File\[another file\] to \["#{Regexp.escape(path)}"\].*resource \["File", "#{Regexp.escape(path)}"\] already defined/) end it "should conflict when its uniqueness key matches the uniqueness key derived from another resource's title" do @resource = Puppet::Type.type(:multiple).new(:title => "red leader") @other = Puppet::Type.type(:multiple).new(:title => "another resource", :color => "red", :designation => "leader") @catalog.add_resource(@resource) expect { @catalog.add_resource(@other) }.to raise_error(ArgumentError, /Cannot alias Multiple\[another resource\] to \["red", "leader"\].*resource \["Multiple", "red", "leader"\] already defined/) end end end describe "when applying" do before :each do @catalog = Puppet::Resource::Catalog.new("host") @transaction = Puppet::Transaction.new(@catalog) Puppet::Transaction.stubs(:new).returns(@transaction) @transaction.stubs(:evaluate) @transaction.stubs(:add_times) @transaction.stubs(:for_network_device=) Puppet.settings.stubs(:use) end it "should create and evaluate a transaction" do @transaction.expects(:evaluate) @catalog.apply end it "should provide the catalog retrieval time to the transaction" do @catalog.retrieval_duration = 5 @transaction.expects(:add_times).with(:config_retrieval => 5) @catalog.apply end it "should use a retrieval time of 0 if none is set in the catalog" do @catalog.retrieval_duration = nil @transaction.expects(:add_times).with(:config_retrieval => 0) @catalog.apply end it "should return the transaction" do @catalog.apply.should equal(@transaction) end it "should yield the transaction if a block is provided" do @catalog.apply do |trans| trans.should equal(@transaction) end end it "should default to being a host catalog" do @catalog.host_config.should be_true end it "should be able to be set to a non-host_config" do @catalog.host_config = false @catalog.host_config.should be_false end it "should pass supplied tags on to the transaction" do @transaction.expects(:tags=).with(%w{one two}) @catalog.apply(:tags => %w{one two}) end it "should set ignoreschedules on the transaction if specified in apply()" do @transaction.expects(:ignoreschedules=).with(true) @catalog.apply(:ignoreschedules => true) end describe "host catalogs" do # super() doesn't work in the setup method for some reason before do @catalog.host_config = true Puppet::Util::Storage.stubs(:store) end it "should initialize the state database before applying a catalog" do Puppet::Util::Storage.expects(:load) # Short-circuit the apply, so we know we're loading before the transaction Puppet::Transaction.expects(:new).raises ArgumentError proc { @catalog.apply }.should raise_error(ArgumentError) end it "should sync the state database after applying" do Puppet::Util::Storage.expects(:store) @transaction.stubs :any_failed? => false @catalog.apply end after { Puppet.settings.clear } end describe "non-host catalogs" do before do @catalog.host_config = false end it "should never send reports" do Puppet[:report] = true Puppet[:summarize] = true @catalog.apply end it "should never modify the state database" do Puppet::Util::Storage.expects(:load).never Puppet::Util::Storage.expects(:store).never @catalog.apply end after { Puppet.settings.clear } end end describe "when creating a relationship graph" do before do Puppet::Type.type(:component) @catalog = Puppet::Resource::Catalog.new("host") @compone = Puppet::Type::Component.new :name => "one" @comptwo = Puppet::Type::Component.new :name => "two", :require => "Class[one]" @file = Puppet::Type.type(:file) @one = @file.new :path => @basepath+"/one" @two = @file.new :path => @basepath+"/two" @sub = @file.new :path => @basepath+"/two/subdir" @catalog.add_edge @compone, @one @catalog.add_edge @comptwo, @two @three = @file.new :path => @basepath+"/three" @four = @file.new :path => @basepath+"/four", :require => "File[#{@basepath}/three]" @five = @file.new :path => @basepath+"/five" @catalog.add_resource @compone, @comptwo, @one, @two, @three, @four, @five, @sub @relationships = @catalog.relationship_graph end it "should be able to create a relationship graph" do @relationships.should be_instance_of(Puppet::SimpleGraph) end it "should not have any components" do @relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil end it "should have all non-component resources from the catalog" do # The failures print out too much info, so i just do a class comparison @relationships.vertex?(@five).should be_true end it "should have all resource relationships set as edges" do @relationships.edge?(@three, @four).should be_true end it "should copy component relationships to all contained resources" do @relationships.path_between(@one, @two).should be end it "should add automatic relationships to the relationship graph" do @relationships.edge?(@two, @sub).should be_true end it "should get removed when the catalog is cleaned up" do @relationships.expects(:clear) @catalog.clear @catalog.instance_variable_get("@relationship_graph").should be_nil end it "should write :relationships and :expanded_relationships graph files if the catalog is a host catalog" do @catalog.clear graph = Puppet::SimpleGraph.new Puppet::SimpleGraph.expects(:new).returns graph graph.expects(:write_graph).with(:relationships) graph.expects(:write_graph).with(:expanded_relationships) @catalog.host_config = true @catalog.relationship_graph end it "should not write graph files if the catalog is not a host catalog" do @catalog.clear graph = Puppet::SimpleGraph.new Puppet::SimpleGraph.expects(:new).returns graph graph.expects(:write_graph).never @catalog.host_config = false @catalog.relationship_graph end it "should create a new relationship graph after clearing the old one" do @relationships.expects(:clear) @catalog.clear @catalog.relationship_graph.should be_instance_of(Puppet::SimpleGraph) end it "should remove removed resources from the relationship graph if it exists" do @catalog.remove_resource(@one) @catalog.relationship_graph.vertex?(@one).should be_false end end describe "when writing dot files" do before do @catalog = Puppet::Resource::Catalog.new("host") @name = :test @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") end it "should only write when it is a host catalog" do File.expects(:open).with(@file).never @catalog.host_config = false Puppet[:graph] = true @catalog.write_graph(@name) end after do Puppet.settings.clear end end describe "when indirecting" do before do @real_indirection = Puppet::Resource::Catalog.indirection @indirection = stub 'indirection', :name => :catalog end it "should use the value of the 'catalog_terminus' setting to determine its terminus class" do # Puppet only checks the terminus setting the first time you ask # so this returns the object to the clean state # at the expense of making this test less pure Puppet::Resource::Catalog.indirection.reset_terminus_class Puppet.settings[:catalog_terminus] = "rest" Puppet::Resource::Catalog.indirection.terminus_class.should == :rest end it "should allow the terminus class to be set manually" do Puppet::Resource::Catalog.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.terminus_class.should == :rest end after do @real_indirection.reset_terminus_class end end describe "when converting to yaml" do before do @catalog = Puppet::Resource::Catalog.new("me") @catalog.add_edge("one", "two") end it "should be able to be dumped to yaml" do YAML.dump(@catalog).should be_instance_of(String) end end describe "when converting from yaml" do before do @catalog = Puppet::Resource::Catalog.new("me") @catalog.add_edge("one", "two") text = YAML.dump(@catalog) @newcatalog = YAML.load(text) end it "should get converted back to a catalog" do @newcatalog.should be_instance_of(Puppet::Resource::Catalog) end it "should have all vertices" do @newcatalog.vertex?("one").should be_true @newcatalog.vertex?("two").should be_true end it "should have all edges" do @newcatalog.edge?("one", "two").should be_true end end end describe Puppet::Resource::Catalog, "when converting to pson", :if => Puppet.features.pson? do before do @catalog = Puppet::Resource::Catalog.new("myhost") end def pson_output_should @catalog.class.expects(:pson_create).with { |hash| yield hash }.returns(:something) end # LAK:NOTE For all of these tests, we convert back to the resource so we can # trap the actual data structure then. it "should set its document_type to 'Catalog'" do pson_output_should { |hash| hash['document_type'] == "Catalog" } PSON.parse @catalog.to_pson end it "should set its data as a hash" do pson_output_should { |hash| hash['data'].is_a?(Hash) } PSON.parse @catalog.to_pson end [:name, :version, :tags, :classes].each do |param| it "should set its #{param} to the #{param} of the resource" do @catalog.send(param.to_s + "=", "testing") unless @catalog.send(param) pson_output_should { |hash| hash['data'][param.to_s] == @catalog.send(param) } PSON.parse @catalog.to_pson end end it "should convert its resources to a PSON-encoded array and store it as the 'resources' data" do one = stub 'one', :to_pson_data_hash => "one_resource", :ref => "Foo[one]" two = stub 'two', :to_pson_data_hash => "two_resource", :ref => "Foo[two]" @catalog.add_resource(one) @catalog.add_resource(two) # TODO this should really guarantee sort order PSON.parse(@catalog.to_pson,:create_additions => false)['data']['resources'].sort.should == ["one_resource", "two_resource"].sort end it "should convert its edges to a PSON-encoded array and store it as the 'edges' data" do one = stub 'one', :to_pson_data_hash => "one_resource", :ref => 'Foo[one]' two = stub 'two', :to_pson_data_hash => "two_resource", :ref => 'Foo[two]' three = stub 'three', :to_pson_data_hash => "three_resource", :ref => 'Foo[three]' @catalog.add_edge(one, two) @catalog.add_edge(two, three) @catalog.edges_between(one, two )[0].expects(:to_pson_data_hash).returns "one_two_pson" @catalog.edges_between(two, three)[0].expects(:to_pson_data_hash).returns "two_three_pson" PSON.parse(@catalog.to_pson,:create_additions => false)['data']['edges'].sort.should == %w{one_two_pson two_three_pson}.sort end end describe Puppet::Resource::Catalog, "when converting from pson", :if => Puppet.features.pson? do def pson_result_should Puppet::Resource::Catalog.expects(:new).with { |hash| yield hash } end before do @data = { 'name' => "myhost" } @pson = { 'document_type' => 'Puppet::Resource::Catalog', 'data' => @data, 'metadata' => {} } @catalog = Puppet::Resource::Catalog.new("myhost") Puppet::Resource::Catalog.stubs(:new).returns @catalog end it "should be extended with the PSON utility module" do Puppet::Resource::Catalog.singleton_class.ancestors.should be_include(Puppet::Util::Pson) end it "should create it with the provided name" do Puppet::Resource::Catalog.expects(:new).with('myhost').returns @catalog PSON.parse @pson.to_pson end it "should set the provided version on the catalog if one is set" do @data['version'] = 50 PSON.parse @pson.to_pson @catalog.version.should == @data['version'] end it "should set any provided tags on the catalog" do @data['tags'] = %w{one two} PSON.parse @pson.to_pson @catalog.tags.should == @data['tags'] end it "should set any provided classes on the catalog" do @data['classes'] = %w{one two} PSON.parse @pson.to_pson @catalog.classes.should == @data['classes'] end it 'should convert the resources list into resources and add each of them' do @data['resources'] = [Puppet::Resource.new(:file, "/foo"), Puppet::Resource.new(:file, "/bar")] @catalog.expects(:add_resource).times(2).with { |res| res.type == "File" } PSON.parse @pson.to_pson end it 'should convert resources even if they do not include "type" information' do @data['resources'] = [Puppet::Resource.new(:file, "/foo")] @data['resources'][0].expects(:to_pson).returns '{"title":"/foo","tags":["file"],"type":"File"}' @catalog.expects(:add_resource).with { |res| res.type == "File" } PSON.parse @pson.to_pson end it 'should convert the edges list into edges and add each of them' do one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh") two = Puppet::Relationship.new("tsource", "ttarget", :event => "two", :callback => "refresh") @data['edges'] = [one, two] @catalog.stubs(:resource).returns("eh") @catalog.expects(:add_edge).with { |edge| edge.event == "one" } @catalog.expects(:add_edge).with { |edge| edge.event == "two" } PSON.parse @pson.to_pson end it "should be able to convert relationships that do not include 'type' information" do one = Puppet::Relationship.new("osource", "otarget", :event => "one", :callback => "refresh") one.expects(:to_pson).returns "{\"event\":\"one\",\"callback\":\"refresh\",\"source\":\"osource\",\"target\":\"otarget\"}" @data['edges'] = [one] @catalog.stubs(:resource).returns("eh") @catalog.expects(:add_edge).with { |edge| edge.event == "one" } PSON.parse @pson.to_pson end it "should set the source and target for each edge to the actual resource" do edge = Puppet::Relationship.new("source", "target") @data['edges'] = [edge] @catalog.expects(:resource).with("source").returns("source_resource") @catalog.expects(:resource).with("target").returns("target_resource") @catalog.expects(:add_edge).with { |edge| edge.source == "source_resource" and edge.target == "target_resource" } PSON.parse @pson.to_pson end it "should fail if the source resource cannot be found" do edge = Puppet::Relationship.new("source", "target") @data['edges'] = [edge] @catalog.expects(:resource).with("source").returns(nil) @catalog.stubs(:resource).with("target").returns("target_resource") lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError) end it "should fail if the target resource cannot be found" do edge = Puppet::Relationship.new("source", "target") @data['edges'] = [edge] @catalog.stubs(:resource).with("source").returns("source_resource") @catalog.expects(:resource).with("target").returns(nil) lambda { PSON.parse @pson.to_pson }.should raise_error(ArgumentError) end describe "#title_key_for_ref" do it "should parse a resource ref string into a pair" do @catalog.title_key_for_ref("Title[name]").should == ["Title", "name"] end it "should parse a resource ref string into a pair, even if there's a newline inside the name" do @catalog.title_key_for_ref("Title[na\nme]").should == ["Title", "na\nme"] end end end diff --git a/spec/unit/ssl/host_spec.rb b/spec/unit/ssl/host_spec.rb index f00451619..226acdecd 100755 --- a/spec/unit/ssl/host_spec.rb +++ b/spec/unit/ssl/host_spec.rb @@ -1,783 +1,792 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/ssl/host' require 'puppet/sslcertificates' require 'puppet/sslcertificates/ca' +# REMIND: Fails on windows because there is no user provider yet describe Puppet::SSL::Host, :fails_on_windows => true do + include PuppetSpec::Files + before do Puppet::SSL::Host.indirection.terminus_class = :file + + # Get a safe temporary file + dir = tmpdir("ssl_host_testing") + Puppet.settings[:confdir] = dir + Puppet.settings[:vardir] = dir + @host = Puppet::SSL::Host.new("myname") end after do # Cleaned out any cached localhost instance. Puppet::SSL::Host.instance_variable_set(:@localhost, nil) Puppet::SSL::Host.ca_location = :none end it "should use any provided name as its name" do @host.name.should == "myname" end it "should retrieve its public key from its private key" do realkey = mock 'realkey' key = stub 'key', :content => realkey Puppet::SSL::Key.indirection.stubs(:find).returns(key) pubkey = mock 'public_key' realkey.expects(:public_key).returns pubkey @host.public_key.should equal(pubkey) end it "should default to being a non-ca host" do @host.ca?.should be_false end it "should be a ca host if its name matches the CA_NAME" do Puppet::SSL::Host.stubs(:ca_name).returns "yayca" Puppet::SSL::Host.new("yayca").should be_ca end it "should have a method for determining the CA location" do Puppet::SSL::Host.should respond_to(:ca_location) end it "should have a method for specifying the CA location" do Puppet::SSL::Host.should respond_to(:ca_location=) end it "should have a method for retrieving the default ssl host" do Puppet::SSL::Host.should respond_to(:ca_location=) end it "should have a method for producing an instance to manage the local host's keys" do Puppet::SSL::Host.should respond_to(:localhost) end it "should generate the certificate for the localhost instance if no certificate is available" do host = stub 'host', :key => nil Puppet::SSL::Host.expects(:new).returns host host.expects(:certificate).returns nil host.expects(:generate) Puppet::SSL::Host.localhost.should equal(host) end it "should always read the key for the localhost instance in from disk" do host = stub 'host', :certificate => "eh" Puppet::SSL::Host.expects(:new).returns host host.expects(:key) Puppet::SSL::Host.localhost end it "should cache the localhost instance" do host = stub 'host', :certificate => "eh", :key => 'foo' Puppet::SSL::Host.expects(:new).once.returns host Puppet::SSL::Host.localhost.should == Puppet::SSL::Host.localhost end it "should be able to verify its certificate matches its key" do Puppet::SSL::Host.new("foo").should respond_to(:certificate_matches_key?) end it "should consider the certificate invalid if it cannot find a key" do host = Puppet::SSL::Host.new("foo") host.expects(:key).returns nil host.should_not be_certificate_matches_key end it "should consider the certificate invalid if it cannot find a certificate" do host = Puppet::SSL::Host.new("foo") host.expects(:key).returns mock("key") host.expects(:certificate).returns nil host.should_not be_certificate_matches_key end it "should consider the certificate invalid if the SSL certificate's key verification fails" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', :content => sslcert host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns false host.should_not be_certificate_matches_key end it "should consider the certificate valid if the SSL certificate's key verification succeeds" do host = Puppet::SSL::Host.new("foo") key = mock 'key', :content => "private_key" sslcert = mock 'sslcert' certificate = mock 'cert', :content => sslcert host.stubs(:key).returns key host.stubs(:certificate).returns certificate sslcert.expects(:check_private_key).with("private_key").returns true host.should be_certificate_matches_key end describe "when specifying the CA location" do it "should support the location ':local'" do lambda { Puppet::SSL::Host.ca_location = :local }.should_not raise_error end it "should support the location ':remote'" do lambda { Puppet::SSL::Host.ca_location = :remote }.should_not raise_error end it "should support the location ':none'" do lambda { Puppet::SSL::Host.ca_location = :none }.should_not raise_error end it "should support the location ':only'" do lambda { Puppet::SSL::Host.ca_location = :only }.should_not raise_error end it "should not support other modes" do lambda { Puppet::SSL::Host.ca_location = :whatever }.should raise_error(ArgumentError) end describe "as 'local'" do before do Puppet::SSL::Host.ca_location = :local end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Certificate.indirection.cache_class.should == :file Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file end it "should set the terminus class for Key and Host as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file Puppet::SSL::Host.indirection.terminus_class.should == :file end it "should set the terminus class for Certificate, CertificateRevocationList, and CertificateRequest as :ca" do Puppet::SSL::Certificate.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca end end describe "as 'remote'" do before do Puppet::SSL::Host.ca_location = :remote end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Certificate.indirection.cache_class.should == :file Puppet::SSL::CertificateRequest.indirection.cache_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.cache_class.should == :file end it "should set the terminus class for Key as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file end it "should set the terminus class for Host, Certificate, CertificateRevocationList, and CertificateRequest as :rest" do Puppet::SSL::Host.indirection.terminus_class.should == :rest Puppet::SSL::Certificate.indirection.terminus_class.should == :rest Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :rest Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :rest end end describe "as 'only'" do before do Puppet::SSL::Host.ca_location = :only end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :ca" do Puppet::SSL::Key.indirection.terminus_class.should == :ca Puppet::SSL::Certificate.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :ca Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :ca end it "should set the cache class for Certificate, CertificateRevocationList, and CertificateRequest to nil" do Puppet::SSL::Certificate.indirection.cache_class.should be_nil Puppet::SSL::CertificateRequest.indirection.cache_class.should be_nil Puppet::SSL::CertificateRevocationList.indirection.cache_class.should be_nil end it "should set the terminus class for Host to :file" do Puppet::SSL::Host.indirection.terminus_class.should == :file end end describe "as 'none'" do before do Puppet::SSL::Host.ca_location = :none end it "should set the terminus class for Key, Certificate, CertificateRevocationList, and CertificateRequest as :file" do Puppet::SSL::Key.indirection.terminus_class.should == :file Puppet::SSL::Certificate.indirection.terminus_class.should == :file Puppet::SSL::CertificateRequest.indirection.terminus_class.should == :file Puppet::SSL::CertificateRevocationList.indirection.terminus_class.should == :file end it "should set the terminus class for Host to 'none'" do lambda { Puppet::SSL::Host.indirection.terminus_class }.should raise_error(Puppet::DevError) end end end it "should have a class method for destroying all files related to a given host" do Puppet::SSL::Host.should respond_to(:destroy) end describe "when destroying a host's SSL files" do before do Puppet::SSL::Key.indirection.stubs(:destroy).returns false Puppet::SSL::Certificate.indirection.stubs(:destroy).returns false Puppet::SSL::CertificateRequest.indirection.stubs(:destroy).returns false end it "should destroy its certificate, certificate request, and key" do Puppet::SSL::Key.indirection.expects(:destroy).with("myhost") Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost") Puppet::SSL::CertificateRequest.indirection.expects(:destroy).with("myhost") Puppet::SSL::Host.destroy("myhost") end it "should return true if any of the classes returned true" do Puppet::SSL::Certificate.indirection.expects(:destroy).with("myhost").returns true Puppet::SSL::Host.destroy("myhost").should be_true end it "should report that nothing was deleted if none of the classes returned true" do Puppet::SSL::Host.destroy("myhost").should == "Nothing was deleted" end end describe "when initializing" do it "should default its name to the :certname setting" do Puppet.settings.expects(:value).with(:certname).returns "myname" Puppet::SSL::Host.new.name.should == "myname" end it "should downcase a passed in name" do Puppet::SSL::Host.new("Host.Domain.Com").name.should == "host.domain.com" end it "should downcase the certname if it's used" do Puppet.settings.expects(:value).with(:certname).returns "Host.Domain.Com" Puppet::SSL::Host.new.name.should == "host.domain.com" end it "should indicate that it is a CA host if its name matches the ca_name constant" do Puppet::SSL::Host.stubs(:ca_name).returns "myca" Puppet::SSL::Host.new("myca").should be_ca end end describe "when managing its private key" do before do @realkey = "mykey" @key = Puppet::SSL::Key.new("mykey") @key.content = @realkey end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(nil) @host.key.should be_nil end it "should find the key in the Key class and return the Puppet instance" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key) @host.key.should equal(@key) end it "should be able to generate and save a new key" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.expects(:generate) Puppet::SSL::Key.indirection.expects(:save) @host.generate_key.should be_true @host.key.should equal(@key) end it "should not retain keys that could not be saved" do Puppet::SSL::Key.expects(:new).with("myname").returns(@key) @key.stubs(:generate) Puppet::SSL::Key.indirection.expects(:save).raises "eh" lambda { @host.generate_key }.should raise_error @host.key.should be_nil end it "should return any previously found key without requerying" do Puppet::SSL::Key.indirection.expects(:find).with("myname").returns(@key).once @host.key.should equal(@key) @host.key.should equal(@key) end end describe "when managing its certificate request" do before do @realrequest = "real request" @request = Puppet::SSL::CertificateRequest.new("myname") @request.content = @realrequest end it "should return nil if the key is not set and cannot be found" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(nil) @host.certificate_request.should be_nil end it "should find the request in the Key class and return it and return the Puppet SSL request" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns @request @host.certificate_request.should equal(@request) end it "should generate a new key when generating the cert request if no key exists" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.expects(:key).times(2).returns(nil).then.returns(key) @host.expects(:generate_key).returns(key) @request.stubs(:generate) Puppet::SSL::CertificateRequest.indirection.stubs(:save) @host.generate_certificate_request end it "should be able to generate and save a new request using the private key" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.expects(:generate).with("mycontent") Puppet::SSL::CertificateRequest.indirection.expects(:save).with(@request) @host.generate_certificate_request.should be_true @host.certificate_request.should equal(@request) end it "should return any previously found request without requerying" do Puppet::SSL::CertificateRequest.indirection.expects(:find).with("myname").returns(@request).once @host.certificate_request.should equal(@request) @host.certificate_request.should equal(@request) end it "should not keep its certificate request in memory if the request cannot be saved" do Puppet::SSL::CertificateRequest.expects(:new).with("myname").returns @request key = stub 'key', :public_key => mock("public_key"), :content => "mycontent" @host.stubs(:key).returns(key) @request.stubs(:generate) @request.stubs(:name).returns("myname") terminus = stub 'terminus' Puppet::SSL::CertificateRequest.indirection.expects(:prepare).returns(terminus) terminus.expects(:save).with { |req| req.instance == @request && req.key == "myname" }.raises "eh" lambda { @host.generate_certificate_request }.should raise_error @host.instance_eval { @certificate_request }.should be_nil end end describe "when managing its certificate" do before do @realcert = mock 'certificate' @cert = stub 'cert', :content => @realcert @host.stubs(:key).returns mock("key") @host.stubs(:certificate_matches_key?).returns true end it "should find the CA certificate if it does not have a certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") Puppet::SSL::Certificate.indirection.stubs(:find).with("myname").returns @cert @host.certificate end it "should not find the CA certificate if it is the CA host" do @host.expects(:ca?).returns true Puppet::SSL::Certificate.indirection.stubs(:find) Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).never @host.certificate end it "should return nil if it cannot find a CA certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns nil Puppet::SSL::Certificate.indirection.expects(:find).with("myname").never @host.certificate.should be_nil end it "should find the key if it does not have one" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns mock("key") @host.certificate end it "should generate the key if one cannot be found" do Puppet::SSL::Certificate.indirection.stubs(:find) @host.expects(:key).returns nil @host.expects(:generate_key) @host.certificate end it "should find the certificate in the Certificate class and return the Puppet certificate instance" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns @cert @host.certificate.should equal(@cert) end it "should fail if the found certificate does not match the private key" do @host.expects(:certificate_matches_key?).returns false Puppet::SSL::Certificate.indirection.stubs(:find).returns @cert lambda { @host.certificate }.should raise_error(Puppet::Error) end it "should return any previously found certificate" do Puppet::SSL::Certificate.indirection.expects(:find).with(Puppet::SSL::CA_NAME).returns mock("cacert") Puppet::SSL::Certificate.indirection.expects(:find).with("myname").returns(@cert).once @host.certificate.should equal(@cert) @host.certificate.should equal(@cert) end end it "should have a method for listing certificate hosts" do Puppet::SSL::Host.should respond_to(:search) end describe "when listing certificate hosts" do it "should default to listing all clients with any file types" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search end it "should be able to list only clients with a key" do Puppet::SSL::Key.indirection.expects(:search).returns [] Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Key end it "should be able to list only clients with a certificate" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).returns [] Puppet::SSL::CertificateRequest.indirection.expects(:search).never Puppet::SSL::Host.search :for => Puppet::SSL::Certificate end it "should be able to list only clients with a certificate request" do Puppet::SSL::Key.indirection.expects(:search).never Puppet::SSL::Certificate.indirection.expects(:search).never Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [] Puppet::SSL::Host.search :for => Puppet::SSL::CertificateRequest end it "should return a Host instance created with the name of each found instance", :'fails_on_ruby_1.9.2' => true do key = stub 'key', :name => "key" cert = stub 'cert', :name => "cert" csr = stub 'csr', :name => "csr" Puppet::SSL::Key.indirection.expects(:search).returns [key] Puppet::SSL::Certificate.indirection.expects(:search).returns [cert] Puppet::SSL::CertificateRequest.indirection.expects(:search).returns [csr] returned = [] %w{key cert csr}.each do |name| result = mock(name) returned << result Puppet::SSL::Host.expects(:new).with(name).returns result end result = Puppet::SSL::Host.search returned.each do |r| result.should be_include(r) end end end it "should have a method for generating all necessary files" do Puppet::SSL::Host.new("me").should respond_to(:generate) end describe "when generating files" do before do @host = Puppet::SSL::Host.new("me") @host.stubs(:generate_key) @host.stubs(:generate_certificate_request) end it "should generate a key if one is not present" do @host.stubs(:key).returns nil @host.expects(:generate_key) @host.generate end it "should generate a certificate request if one is not present" do @host.expects(:certificate_request).returns nil @host.expects(:generate_certificate_request) @host.generate end describe "and it can create a certificate authority" do before do @ca = mock 'ca' Puppet::SSL::CertificateAuthority.stubs(:instance).returns @ca end it "should use the CA to sign its certificate request if it does not have a certificate" do @host.expects(:certificate).returns nil @ca.expects(:sign).with(@host.name) @host.generate end end describe "and it cannot create a certificate authority" do before do Puppet::SSL::CertificateAuthority.stubs(:instance).returns nil end it "should seek its certificate" do @host.expects(:certificate) @host.generate end end end it "should have a method for creating an SSL store" do Puppet::SSL::Host.new("me").should respond_to(:ssl_store) end it "should always return the same store" do host = Puppet::SSL::Host.new("foo") store = mock 'store' store.stub_everything OpenSSL::X509::Store.expects(:new).returns store host.ssl_store.should equal(host.ssl_store) end describe "when creating an SSL store" do before do @host = Puppet::SSL::Host.new("me") @store = mock 'store' @store.stub_everything OpenSSL::X509::Store.stubs(:new).returns @store Puppet.settings.stubs(:value).with(:localcacert).returns "ssl_host_testing" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns(nil) end it "should accept a purpose" do @store.expects(:purpose=).with "my special purpose" @host.ssl_store("my special purpose") end it "should default to OpenSSL::X509::PURPOSE_ANY as the purpose" do @store.expects(:purpose=).with OpenSSL::X509::PURPOSE_ANY @host.ssl_store end it "should add the local CA cert file" do Puppet.settings.stubs(:value).with(:localcacert).returns "/ca/cert/file" @store.expects(:add_file).with "/ca/cert/file" @host.ssl_store end describe "and a CRL is available" do before do @crl = stub 'crl', :content => "real_crl" Puppet::SSL::CertificateRevocationList.indirection.stubs(:find).returns @crl Puppet.settings.stubs(:value).with(:certificate_revocation).returns true end it "should add the CRL" do @store.expects(:add_crl).with "real_crl" @host.ssl_store end it "should set the flags to OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK" do @store.expects(:flags=).with OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK @host.ssl_store end end end describe "when waiting for a cert" do before do @host = Puppet::SSL::Host.new("me") end it "should generate its certificate request and attempt to read the certificate again if no certificate is found" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate) @host.wait_for_cert(1) end it "should catch and log errors during CSR saving" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.stubs(:sleep) @host.wait_for_cert(1) end it "should sleep and retry after failures saving the CSR if waitforcert is enabled" do @host.expects(:certificate).times(2).returns(nil).then.returns "foo" @host.expects(:generate).raises(RuntimeError).then.returns nil @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should exit after failures saving the CSR of waitforcert is disabled" do @host.expects(:certificate).returns(nil) @host.expects(:generate).raises(RuntimeError) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should exit if the wait time is 0 and it can neither find nor retrieve a certificate" do @host.stubs(:certificate).returns nil @host.expects(:generate) @host.expects(:puts) expect { @host.wait_for_cert(0) }.to exit_with 1 end it "should sleep for the specified amount of time if no certificate is found after generating its certificate request" do @host.expects(:certificate).times(3).returns(nil).then.returns(nil).then.returns "foo" @host.expects(:generate) @host.expects(:sleep).with(1) @host.wait_for_cert(1) end it "should catch and log exceptions during certificate retrieval" do @host.expects(:certificate).times(3).returns(nil).then.raises(RuntimeError).then.returns("foo") @host.stubs(:generate) @host.stubs(:sleep) Puppet.expects(:err) @host.wait_for_cert(1) end end - describe "when handling PSON" do + describe "when handling PSON", :unless => Puppet.features.microsoft_windows? do include PuppetSpec::Files before do Puppet[:vardir] = tmpdir("ssl_test_vardir") Puppet[:ssldir] = tmpdir("ssl_test_ssldir") Puppet::SSLCertificates::CA.new.mkrootcert # localcacert is where each client stores the CA certificate # cacert is where the master stores the CA certificate # Since we need to play the role of both for testing we need them to be the same and exist Puppet[:cacert] = Puppet[:localcacert] @ca=Puppet::SSL::CertificateAuthority.new end describe "when converting to PSON" do it "should be able to identify a host with an unsigned certificate request" do host = Puppet::SSL::Host.new("bazinga") host.generate_certificate_request pson_hash = { "fingerprint" => host.certificate_request.fingerprint, "desired_state" => 'requested', "name" => host.name } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end it "should be able to identify a host with a signed certificate" do host = Puppet::SSL::Host.new("bazinga") host.generate_certificate_request @ca.sign(host.name) pson_hash = { "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, "desired_state" => 'signed', "name" => host.name, } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end it "should be able to identify a host with a revoked certificate" do host = Puppet::SSL::Host.new("bazinga") host.generate_certificate_request @ca.sign(host.name) @ca.revoke(host.name) pson_hash = { "fingerprint" => Puppet::SSL::Certificate.indirection.find(host.name).fingerprint, "desired_state" => 'revoked', "name" => host.name, } result = PSON.parse(Puppet::SSL::Host.new(host.name).to_pson) result["fingerprint"].should == pson_hash["fingerprint"] result["name"].should == pson_hash["name"] result["state"].should == pson_hash["desired_state"] end end describe "when converting from PSON" do it "should return a Puppet::SSL::Host object with the specified desired state" do host = Puppet::SSL::Host.new("bazinga") host.desired_state="signed" pson_hash = { "name" => host.name, "desired_state" => host.desired_state, } generated_host = Puppet::SSL::Host.from_pson(pson_hash) generated_host.desired_state.should == host.desired_state generated_host.name.should == host.name end end end end diff --git a/spec/unit/ssl/inventory_spec.rb b/spec/unit/ssl/inventory_spec.rb index 3d141d0cd..000f0a253 100755 --- a/spec/unit/ssl/inventory_spec.rb +++ b/spec/unit/ssl/inventory_spec.rb @@ -1,179 +1,179 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/ssl/inventory' -describe Puppet::SSL::Inventory do +describe Puppet::SSL::Inventory, :unless => Puppet.features.microsoft_windows? do before do @class = Puppet::SSL::Inventory end it "should use the :certinventory setting for the path to the inventory file" do Puppet.settings.expects(:value).with(:cert_inventory).returns "/inven/tory" @class.any_instance.stubs(:rebuild) @class.new.path.should == "/inven/tory" end describe "when initializing" do it "should set its path to the inventory file" do Puppet.settings.stubs(:value).with(:cert_inventory).returns "/inven/tory" @class.new.path.should == "/inven/tory" end end describe "when managing an inventory" do before do Puppet.settings.stubs(:value).with(:cert_inventory).returns "/inven/tory" FileTest.stubs(:exist?).with("/inven/tory").returns true @inventory = @class.new @cert = mock 'cert' end describe "and creating the inventory file" do before do Puppet.settings.stubs(:write) FileTest.stubs(:exist?).with("/inven/tory").returns false Puppet::SSL::Certificate.indirection.stubs(:search).returns [] end it "should log that it is building a new inventory file" do Puppet.expects(:notice) @inventory.rebuild end it "should use the Settings to write to the file" do Puppet.settings.expects(:write).with(:cert_inventory) @inventory.rebuild end it "should add a header to the file" do fh = mock 'filehandle' Puppet.settings.stubs(:write).yields fh fh.expects(:print).with { |str| str =~ /^#/ } @inventory.rebuild end it "should add formatted information on all existing certificates" do cert1 = mock 'cert1' cert2 = mock 'cert2' Puppet::SSL::Certificate.indirection.expects(:search).with("*").returns [cert1, cert2] @class.any_instance.expects(:add).with(cert1) @class.any_instance.expects(:add).with(cert2) @inventory.rebuild end end describe "and adding a certificate" do it "should build the inventory file if one does not exist" do Puppet.settings.stubs(:value).with(:cert_inventory).returns "/inven/tory" Puppet.settings.stubs(:write) FileTest.expects(:exist?).with("/inven/tory").returns false @inventory.expects(:rebuild) @inventory.add(@cert) end it "should use the Settings to write to the file" do Puppet.settings.expects(:write).with(:cert_inventory, "a") @inventory.add(@cert) end it "should use the actual certificate if it was passed a Puppet certificate" do cert = Puppet::SSL::Certificate.new("mycert") cert.content = @cert fh = stub 'filehandle', :print => nil Puppet.settings.stubs(:write).yields fh @inventory.expects(:format).with(@cert) @inventory.add(@cert) end it "should add formatted certificate information to the end of the file" do fh = mock 'filehandle' Puppet.settings.stubs(:write).yields fh @inventory.expects(:format).with(@cert).returns "myformat" fh.expects(:print).with("myformat") @inventory.add(@cert) end end describe "and formatting a certificate", :fails_on_windows => true do before do @cert = stub 'cert', :not_before => Time.now, :not_after => Time.now, :subject => "mycert", :serial => 15 end it "should print the serial number as a 4 digit hex number in the first field" do @inventory.format(@cert).split[0].should == "0x000f" # 15 in hex end it "should print the not_before date in '%Y-%m-%dT%H:%M:%S%Z' format in the second field" do @cert.not_before.expects(:strftime).with('%Y-%m-%dT%H:%M:%S%Z').returns "before_time" @inventory.format(@cert).split[1].should == "before_time" end it "should print the not_after date in '%Y-%m-%dT%H:%M:%S%Z' format in the third field" do @cert.not_after.expects(:strftime).with('%Y-%m-%dT%H:%M:%S%Z').returns "after_time" @inventory.format(@cert).split[2].should == "after_time" end it "should print the subject in the fourth field" do @inventory.format(@cert).split[3].should == "mycert" end it "should add a carriage return" do @inventory.format(@cert).should =~ /\n$/ end it "should produce a line consisting of the serial number, start date, expiration date, and subject" do # Just make sure our serial and subject bracket the lines. @inventory.format(@cert).should =~ /^0x.+mycert$/ end end it "should be able to find a given host's serial number" do @inventory.should respond_to(:serial) end describe "and finding a serial number" do it "should return nil if the inventory file is missing" do FileTest.expects(:exist?).with("/inven/tory").returns false @inventory.serial(:whatever).should be_nil end it "should return the serial number from the line matching the provided name" do File.expects(:readlines).with("/inven/tory").returns ["0x00f blah blah /CN=me\n", "0x001 blah blah /CN=you\n"] @inventory.serial("me").should == 15 end it "should return the number as an integer" do File.expects(:readlines).with("/inven/tory").returns ["0x00f blah blah /CN=me\n", "0x001 blah blah /CN=you\n"] @inventory.serial("me").should == 15 end end end end diff --git a/spec/unit/sslcertificates/ca_spec.rb b/spec/unit/sslcertificates/ca_spec.rb index 2ff4036dd..7a687b825 100755 --- a/spec/unit/sslcertificates/ca_spec.rb +++ b/spec/unit/sslcertificates/ca_spec.rb @@ -1,110 +1,106 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet' require 'puppet/sslcertificates' require 'puppet/sslcertificates/ca' -describe Puppet::SSLCertificates::CA, :fails_on_windows => true do +describe Puppet::SSLCertificates::CA, :unless => Puppet.features.microsoft_windows? do + include PuppetSpec::Files + before :all do @hosts = %w{host.domain.com Other.Testing.Com} end before :each do Puppet::Util::SUIDManager.stubs(:asuser).yields - file = Tempfile.new("ca_testing") - @dir = file.path - file.delete + dir = tmpdir("ca_testing") - Puppet.settings[:confdir] = @dir - Puppet.settings[:vardir] = @dir + Puppet.settings[:confdir] = dir + Puppet.settings[:vardir] = dir @ca = Puppet::SSLCertificates::CA.new end - after :each do - system("rm -rf #{@dir}") - end - describe 'when cleaning' do it 'should remove associated files' do dirs = [:csrdir, :signeddir, :publickeydir, :privatekeydir, :certdir] @hosts.each do |host| files = [] dirs.each do |dir| dir = Puppet[dir] # Case insensitivity is handled through downcasing file = File.join(dir, host.downcase + '.pem') File.open(file, "w") do |f| f.puts "testing" end files << file end lambda { @ca.clean(host) }.should_not raise_error files.reject {|f| ! File.exists?(f)}.should be_empty end end end describe 'when mapping hosts to files' do it 'should correctly return the certfile' do @hosts.each do |host| value = nil lambda { value = @ca.host2certfile host }.should_not raise_error File.join(Puppet[:signeddir], host.downcase + '.pem').should == value end end it 'should correctly return the csrfile' do @hosts.each do |host| value = nil lambda { value = @ca.host2csrfile host }.should_not raise_error File.join(Puppet[:csrdir], host.downcase + '.pem').should == value end end end describe 'when listing' do it 'should find all csr' do list = [] # Make some fake CSRs @hosts.each do |host| file = File.join(Puppet[:csrdir], host.downcase + '.pem') File.open(file, 'w') { |f| f.puts "yay" } list << host.downcase end @ca.list.sort.should == list.sort end end describe 'when creating a root certificate' do before :each do lambda { @ca.mkrootcert }.should_not raise_exception end it 'should store the public key' do File.exists?(Puppet[:capub]).should be_true end it 'should prepend "Puppet CA: " to the fqdn as the ca_name by default' do host_mock_fact = mock() host_mock_fact.expects(:value).returns('myhost') domain_mock_fact = mock() domain_mock_fact.expects(:value).returns('puppetlabs.lan') Facter.stubs(:[]).with('hostname').returns(host_mock_fact) Facter.stubs(:[]).with('domain').returns(domain_mock_fact) @ca.mkrootcert.name.should == 'Puppet CA: myhost.puppetlabs.lan' end end end diff --git a/spec/unit/type/cron_spec.rb b/spec/unit/type/cron_spec.rb index 7bf92eb02..f2c18896b 100755 --- a/spec/unit/type/cron_spec.rb +++ b/spec/unit/type/cron_spec.rb @@ -1,490 +1,490 @@ #!/usr/bin/env rspec require 'spec_helper' -describe Puppet::Type.type(:cron) do +describe Puppet::Type.type(:cron), :unless => Puppet.features.microsoft_windows? do before do @class = Puppet::Type.type(:cron) # Init a fake provider @provider_class = stub 'provider_class', :ancestors => [], :name => 'fake', :suitable? => true, :supports_parameter? => true @class.stubs(:defaultprovider).returns @provider_class @class.stubs(:provider).returns @provider_class @provider = stub 'provider', :class => @provider_class, :clean => nil @provider.stubs(:is_a?).returns false @provider_class.stubs(:new).returns @provider @cron = @class.new( :name => "foo" ) end it "should have :name be its namevar" do @class.key_attributes.should == [:name] end describe "when validating attributes" do [:name, :provider].each do |param| it "should have a #{param} parameter" do @class.attrtype(param).should == :param end end [:command, :special, :minute, :hour, :weekday, :month, :monthday, :environment, :user, :target].each do |property| it "should have a #{property} property" do @class.attrtype(property).should == :property end end [:command, :minute, :hour, :weekday, :month, :monthday].each do |cronparam| it "should have #{cronparam} of type CronParam" do @class.attrclass(cronparam).ancestors.should include CronParam end end end describe "when validating attribute" do describe "ensure" do it "should support present as a value for ensure" do proc { @class.new(:name => 'foo', :ensure => :present) }.should_not raise_error end it "should support absent as a value for ensure" do proc { @class.new(:name => 'foo', :ensure => :present) }.should_not raise_error end end describe "minute" do it "should support absent" do proc { @class.new(:name => 'foo', :minute => 'absent') }.should_not raise_error end it "should support *" do proc { @class.new(:name => 'foo', :minute => '*') }.should_not raise_error end it "should translate absent to :absent" do @class.new(:name => 'foo', :minute => 'absent')[:minute].should == :absent end it "should translate * to :absent" do @class.new(:name => 'foo', :minute => '*')[:minute].should == :absent end it "should support valid single values" do proc { @class.new(:name => 'foo', :minute => '0') }.should_not raise_error proc { @class.new(:name => 'foo', :minute => '1') }.should_not raise_error proc { @class.new(:name => 'foo', :minute => '59') }.should_not raise_error end it "should not support non numeric characters" do proc { @class.new(:name => 'foo', :minute => 'z59') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :minute => '5z9') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :minute => '59z') }.should raise_error(Puppet::Error) end it "should not support single values out of range" do proc { @class.new(:name => 'foo', :minute => '-1') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :minute => '60') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :minute => '61') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :minute => '120') }.should raise_error(Puppet::Error) end it "should support valid multiple values" do proc { @class.new(:name => 'foo', :minute => ['0','1','59'] ) }.should_not raise_error proc { @class.new(:name => 'foo', :minute => ['40','30','20'] ) }.should_not raise_error proc { @class.new(:name => 'foo', :minute => ['10','30','20'] ) }.should_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid proc { @class.new(:name => 'foo', :minute => ['0','1','60'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :minute => ['0','120','59'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :minute => ['-1','1','59'] ) }.should raise_error(Puppet::Error) # two invalid proc { @class.new(:name => 'foo', :minute => ['0','61','62'] ) }.should raise_error(Puppet::Error) # all invalid proc { @class.new(:name => 'foo', :minute => ['-1','61','62'] ) }.should raise_error(Puppet::Error) end it "should support valid step syntax" do proc { @class.new(:name => 'foo', :minute => '*/2' ) }.should_not raise_error proc { @class.new(:name => 'foo', :minute => '10-16/2' ) }.should_not raise_error end it "should not support invalid steps" do proc { @class.new(:name => 'foo', :minute => '*/A' ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :minute => '*/2A' ) }.should raise_error(Puppet::Error) # As it turns out cron does not complaining about steps that exceed the valid range # proc { @class.new(:name => 'foo', :minute => '*/120' ) }.should raise_error(Puppet::Error) end end describe "hour" do it "should support absent" do proc { @class.new(:name => 'foo', :hour => 'absent') }.should_not raise_error end it "should support *" do proc { @class.new(:name => 'foo', :hour => '*') }.should_not raise_error end it "should translate absent to :absent" do @class.new(:name => 'foo', :hour => 'absent')[:hour].should == :absent end it "should translate * to :absent" do @class.new(:name => 'foo', :hour => '*')[:hour].should == :absent end it "should support valid single values" do proc { @class.new(:name => 'foo', :hour => '0') }.should_not raise_error proc { @class.new(:name => 'foo', :hour => '11') }.should_not raise_error proc { @class.new(:name => 'foo', :hour => '12') }.should_not raise_error proc { @class.new(:name => 'foo', :hour => '13') }.should_not raise_error proc { @class.new(:name => 'foo', :hour => '23') }.should_not raise_error end it "should not support non numeric characters" do proc { @class.new(:name => 'foo', :hour => 'z15') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :hour => '1z5') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :hour => '15z') }.should raise_error(Puppet::Error) end it "should not support single values out of range" do proc { @class.new(:name => 'foo', :hour => '-1') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :hour => '24') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :hour => '120') }.should raise_error(Puppet::Error) end it "should support valid multiple values" do proc { @class.new(:name => 'foo', :hour => ['0','1','23'] ) }.should_not raise_error proc { @class.new(:name => 'foo', :hour => ['5','16','14'] ) }.should_not raise_error proc { @class.new(:name => 'foo', :hour => ['16','13','9'] ) }.should_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid proc { @class.new(:name => 'foo', :hour => ['0','1','24'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :hour => ['0','-1','5'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :hour => ['-1','1','23'] ) }.should raise_error(Puppet::Error) # two invalid proc { @class.new(:name => 'foo', :hour => ['0','25','26'] ) }.should raise_error(Puppet::Error) # all invalid proc { @class.new(:name => 'foo', :hour => ['-1','24','120'] ) }.should raise_error(Puppet::Error) end it "should support valid step syntax" do proc { @class.new(:name => 'foo', :hour => '*/2' ) }.should_not raise_error proc { @class.new(:name => 'foo', :hour => '10-18/4' ) }.should_not raise_error end it "should not support invalid steps" do proc { @class.new(:name => 'foo', :hour => '*/A' ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :hour => '*/2A' ) }.should raise_error(Puppet::Error) # As it turns out cron does not complaining about steps that exceed the valid range # proc { @class.new(:name => 'foo', :hour => '*/26' ) }.should raise_error(Puppet::Error) end end describe "weekday" do it "should support absent" do proc { @class.new(:name => 'foo', :weekday => 'absent') }.should_not raise_error end it "should support *" do proc { @class.new(:name => 'foo', :weekday => '*') }.should_not raise_error end it "should translate absent to :absent" do @class.new(:name => 'foo', :weekday => 'absent')[:weekday].should == :absent end it "should translate * to :absent" do @class.new(:name => 'foo', :weekday => '*')[:weekday].should == :absent end it "should support valid numeric weekdays" do proc { @class.new(:name => 'foo', :weekday => '0') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => '1') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => '6') }.should_not raise_error # According to http://www.manpagez.com/man/5/crontab 7 is also valid (Sunday) proc { @class.new(:name => 'foo', :weekday => '7') }.should_not raise_error end it "should support valid weekdays as words (3 character version)" do proc { @class.new(:name => 'foo', :weekday => 'Monday') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Tuesday') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Wednesday') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Thursday') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Friday') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Saturday') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Sunday') }.should_not raise_error end it "should support valid weekdays as words (3 character version)" do proc { @class.new(:name => 'foo', :weekday => 'Mon') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Tue') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Wed') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Thu') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Fri') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Sat') }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => 'Sun') }.should_not raise_error end it "should not support numeric values out of range" do proc { @class.new(:name => 'foo', :weekday => '-1') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :weekday => '8') }.should raise_error(Puppet::Error) end it "should not support invalid weekday names" do proc { @class.new(:name => 'foo', :weekday => 'Sar') }.should raise_error(Puppet::Error) end it "should support valid multiple values" do proc { @class.new(:name => 'foo', :weekday => ['0','1','6'] ) }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => ['Mon','Wed','Friday'] ) }.should_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid proc { @class.new(:name => 'foo', :weekday => ['0','1','8'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :weekday => ['Mon','Fii','Sat'] ) }.should raise_error(Puppet::Error) # two invalid proc { @class.new(:name => 'foo', :weekday => ['Mos','Fii','Sat'] ) }.should raise_error(Puppet::Error) # all invalid proc { @class.new(:name => 'foo', :weekday => ['Mos','Fii','Saa'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :weekday => ['-1','8','11'] ) }.should raise_error(Puppet::Error) end it "should support valid step syntax" do proc { @class.new(:name => 'foo', :weekday => '*/2' ) }.should_not raise_error proc { @class.new(:name => 'foo', :weekday => '0-4/2' ) }.should_not raise_error end it "should not support invalid steps" do proc { @class.new(:name => 'foo', :weekday => '*/A' ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :weekday => '*/2A' ) }.should raise_error(Puppet::Error) # As it turns out cron does not complaining about steps that exceed the valid range # proc { @class.new(:name => 'foo', :weekday => '*/9' ) }.should raise_error(Puppet::Error) end end describe "month" do it "should support absent" do proc { @class.new(:name => 'foo', :month => 'absent') }.should_not raise_error end it "should support *" do proc { @class.new(:name => 'foo', :month => '*') }.should_not raise_error end it "should translate absent to :absent" do @class.new(:name => 'foo', :month => 'absent')[:month].should == :absent end it "should translate * to :absent" do @class.new(:name => 'foo', :month => '*')[:month].should == :absent end it "should support valid numeric values" do proc { @class.new(:name => 'foo', :month => '1') }.should_not raise_error proc { @class.new(:name => 'foo', :month => '12') }.should_not raise_error end it "should support valid months as words" do proc { @class.new(:name => 'foo', :month => 'January') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'February') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'March') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'April') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'May') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'June') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'July') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'August') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'September') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'October') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'November') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'December') }.should_not raise_error end it "should support valid months as words (3 character short version)" do proc { @class.new(:name => 'foo', :month => 'Jan') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Feb') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Mar') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Apr') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'May') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Jun') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Jul') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Aug') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Sep') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Oct') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Nov') }.should_not raise_error proc { @class.new(:name => 'foo', :month => 'Dec') }.should_not raise_error end it "should not support numeric values out of range" do proc { @class.new(:name => 'foo', :month => '-1') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :month => '0') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :month => '13') }.should raise_error(Puppet::Error) end it "should not support words that are not valid months" do proc { @class.new(:name => 'foo', :month => 'Jal') }.should raise_error(Puppet::Error) end it "should not support single values out of range" do proc { @class.new(:name => 'foo', :month => '-1') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :month => '60') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :month => '61') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :month => '120') }.should raise_error(Puppet::Error) end it "should support valid multiple values" do proc { @class.new(:name => 'foo', :month => ['1','9','12'] ) }.should_not raise_error proc { @class.new(:name => 'foo', :month => ['Jan','March','Jul'] ) }.should_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid proc { @class.new(:name => 'foo', :month => ['0','1','12'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :month => ['1','13','10'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :month => ['Jan','Feb','Jxx'] ) }.should raise_error(Puppet::Error) # two invalid proc { @class.new(:name => 'foo', :month => ['Jan','Fex','Jux'] ) }.should raise_error(Puppet::Error) # all invalid proc { @class.new(:name => 'foo', :month => ['-1','0','13'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :month => ['Jax','Fex','Aux'] ) }.should raise_error(Puppet::Error) end it "should support valid step syntax" do proc { @class.new(:name => 'foo', :month => '*/2' ) }.should_not raise_error proc { @class.new(:name => 'foo', :month => '1-12/3' ) }.should_not raise_error end it "should not support invalid steps" do proc { @class.new(:name => 'foo', :month => '*/A' ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :month => '*/2A' ) }.should raise_error(Puppet::Error) # As it turns out cron does not complaining about steps that exceed the valid range # proc { @class.new(:name => 'foo', :month => '*/13' ) }.should raise_error(Puppet::Error) end end describe "monthday" do it "should support absent" do proc { @class.new(:name => 'foo', :monthday => 'absent') }.should_not raise_error end it "should support *" do proc { @class.new(:name => 'foo', :monthday => '*') }.should_not raise_error end it "should translate absent to :absent" do @class.new(:name => 'foo', :monthday => 'absent')[:monthday].should == :absent end it "should translate * to :absent" do @class.new(:name => 'foo', :monthday => '*')[:monthday].should == :absent end it "should support valid single values" do proc { @class.new(:name => 'foo', :monthday => '1') }.should_not raise_error proc { @class.new(:name => 'foo', :monthday => '30') }.should_not raise_error proc { @class.new(:name => 'foo', :monthday => '31') }.should_not raise_error end it "should not support non numeric characters" do proc { @class.new(:name => 'foo', :monthday => 'z23') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :monthday => '2z3') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :monthday => '23z') }.should raise_error(Puppet::Error) end it "should not support single values out of range" do proc { @class.new(:name => 'foo', :monthday => '-1') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :monthday => '0') }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :monthday => '32') }.should raise_error(Puppet::Error) end it "should support valid multiple values" do proc { @class.new(:name => 'foo', :monthday => ['1','23','31'] ) }.should_not raise_error proc { @class.new(:name => 'foo', :monthday => ['31','23','1'] ) }.should_not raise_error proc { @class.new(:name => 'foo', :monthday => ['1','31','23'] ) }.should_not raise_error end it "should not support multiple values if at least one is invalid" do # one invalid proc { @class.new(:name => 'foo', :monthday => ['1','23','32'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :monthday => ['-1','12','23'] ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :monthday => ['13','32','30'] ) }.should raise_error(Puppet::Error) # two invalid proc { @class.new(:name => 'foo', :monthday => ['-1','0','23'] ) }.should raise_error(Puppet::Error) # all invalid proc { @class.new(:name => 'foo', :monthday => ['-1','0','32'] ) }.should raise_error(Puppet::Error) end it "should support valid step syntax" do proc { @class.new(:name => 'foo', :monthday => '*/2' ) }.should_not raise_error proc { @class.new(:name => 'foo', :monthday => '10-16/2' ) }.should_not raise_error end it "should not support invalid steps" do proc { @class.new(:name => 'foo', :monthday => '*/A' ) }.should raise_error(Puppet::Error) proc { @class.new(:name => 'foo', :monthday => '*/2A' ) }.should raise_error(Puppet::Error) # As it turns out cron does not complaining about steps that exceed the valid range # proc { @class.new(:name => 'foo', :monthday => '*/32' ) }.should raise_error(Puppet::Error) end end describe "environment" do it "it should accept an :environment that looks like a path" do lambda do @cron[:environment] = 'PATH=/bin:/usr/bin:/usr/sbin' end.should_not raise_error end it "should not accept environment variables that do not contain '='" do lambda do @cron[:environment] = "INVALID" end.should raise_error(Puppet::Error) end it "should accept empty environment variables that do not contain '='" do lambda do @cron[:environment] = "MAILTO=" end.should_not raise_error(Puppet::Error) end it "should accept 'absent'" do lambda do @cron[:environment] = 'absent' end.should_not raise_error(Puppet::Error) end end end it "should require a command when adding an entry" do entry = @class.new(:name => "test_entry", :ensure => :present) expect { entry.value(:command) }.should raise_error(/No command/) end it "should not require a command when removing an entry" do entry = @class.new(:name => "test_entry", :ensure => :absent) entry.value(:command).should == nil end end diff --git a/spec/unit/util/rdoc/parser_spec.rb b/spec/unit/util/rdoc/parser_spec.rb index 4c2c79e88..29e3298f0 100755 --- a/spec/unit/util/rdoc/parser_spec.rb +++ b/spec/unit/util/rdoc/parser_spec.rb @@ -1,564 +1,568 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/resource/type_collection' require 'puppet/util/rdoc/parser' require 'puppet/util/rdoc/code_objects' require 'rdoc/options' require 'rdoc/rdoc' describe RDoc::Parser, :'fails_on_ruby_1.9.2' => true do include PuppetSpec::Files before :each do File.stubs(:stat).with("init.pp") @top_level = stub_everything 'toplevel', :file_relative_name => "init.pp" @parser = RDoc::Parser.new(@top_level, "module/manifests/init.pp", nil, Options.instance, RDoc::Stats.new) end describe "when scanning files" do it "should parse puppet files with the puppet parser" do @parser.stubs(:scan_top_level) parser = stub 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once parser.expects(:file=).with("module/manifests/init.pp") parser.expects(:file=).with(make_absolute("/dev/null/manifests/site.pp")) @parser.scan end it "should scan the ast for Puppet files" do parser = stub_everything 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once @parser.expects(:scan_top_level) @parser.scan end it "should return a PuppetTopLevel to RDoc" do parser = stub_everything 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) parser.expects(:parse).returns(Puppet::Parser::AST::Hostclass.new('')).at_least_once @parser.expects(:scan_top_level) @parser.scan.should be_a(RDoc::PuppetTopLevel) end it "should scan the top level even if the file has already parsed" do known_type = stub 'known_types' env = stub 'env' Puppet::Node::Environment.stubs(:new).returns(env) env.stubs(:known_resource_types).returns(known_type) known_type.expects(:watching_file?).with("module/manifests/init.pp").returns(true) @parser.expects(:scan_top_level) @parser.scan end end describe "when scanning top level entities" do before :each do @resource_type_collection = resource_type_collection = stub_everything('resource_type_collection') @parser.instance_eval { @known_resource_types = resource_type_collection } @parser.stubs(:split_module).returns("module") @topcontainer = stub_everything 'topcontainer' @container = stub_everything 'container' @module = stub_everything 'module' @container.stubs(:add_module).returns(@module) @parser.stubs(:get_class_or_module).returns([@container, "module"]) end it "should read any present README as module documentation" do FileTest.stubs(:readable?).returns(true) File.stubs(:open).returns("readme") @parser.stubs(:parse_elements) @module.expects(:comment=).with("readme") @parser.scan_top_level(@topcontainer) end it "should tell the container its module name" do @parser.stubs(:parse_elements) @topcontainer.expects(:module_name=).with("module") @parser.scan_top_level(@topcontainer) end it "should not document our toplevel if it isn't a valid module" do @parser.stubs(:split_module).returns(nil) @topcontainer.expects(:document_self=).with(false) @parser.expects(:parse_elements).never @parser.scan_top_level(@topcontainer) end it "should set the module as global if we parse the global manifests (ie __site__ module)" do @parser.stubs(:split_module).returns(RDoc::Parser::SITE) @parser.stubs(:parse_elements) @topcontainer.expects(:global=).with(true) @parser.scan_top_level(@topcontainer) end it "should attach this module container to the toplevel container" do @parser.stubs(:parse_elements) @container.expects(:add_module).with(RDoc::PuppetModule, "module").returns(@module) @parser.scan_top_level(@topcontainer) end it "should defer ast parsing to parse_elements for this module" do @parser.expects(:parse_elements).with(@module) @parser.scan_top_level(@topcontainer) end it "should defer plugins parsing to parse_plugins for this module" do @parser.input_file_name = "module/lib/puppet/parser/function.rb" @parser.expects(:parse_plugins).with(@module) @parser.scan_top_level(@topcontainer) end end describe "when finding modules from filepath" do before :each do Puppet::Module.stubs(:modulepath).returns("/path/to/modules") end it "should return the module name for modulized puppet manifests" do File.stubs(:expand_path).returns("/path/to/module/manifests/init.pp") File.stubs(:identical?).with("/path/to", "/path/to/modules").returns(true) @parser.split_module("/path/to/modules/mymodule/manifests/init.pp").should == "module" end it "should return for manifests not under module path" do File.stubs(:expand_path).returns("/path/to/manifests/init.pp") File.stubs(:identical?).returns(false) @parser.split_module("/path/to/manifests/init.pp").should == RDoc::Parser::SITE end + + it "should handle windows paths with drive letters", :if => Puppet.features.microsoft_windows? do + @parser.split_module("C:/temp/init.pp").should == RDoc::Parser::SITE + end end describe "when parsing AST elements" do before :each do @klass = stub_everything 'klass', :file => "module/manifests/init.pp", :name => "myclass", :type => :hostclass @definition = stub_everything 'definition', :file => "module/manifests/init.pp", :type => :definition, :name => "mydef" @node = stub_everything 'node', :file => "module/manifests/init.pp", :type => :node, :name => "mynode" @resource_type_collection = resource_type_collection = Puppet::Resource::TypeCollection.new("env") @parser.instance_eval { @known_resource_types = resource_type_collection } @container = stub_everything 'container' end it "should document classes in the parsed file" do @resource_type_collection.add_hostclass(@klass) @parser.expects(:document_class).with("myclass", @klass, @container) @parser.parse_elements(@container) end it "should not document class parsed in an other file" do @klass.stubs(:file).returns("/not/same/path/file.pp") @resource_type_collection.add_hostclass(@klass) @parser.expects(:document_class).with("myclass", @klass, @container).never @parser.parse_elements(@container) end it "should document vardefs for the main class" do @klass.stubs(:name).returns :main @resource_type_collection.add_hostclass(@klass) code = stub 'code', :is_a? => false @klass.stubs(:name).returns("") @klass.stubs(:code).returns(code) @parser.expects(:scan_for_vardef).with(@container, code) @parser.parse_elements(@container) end it "should document definitions in the parsed file" do @resource_type_collection.add_definition(@definition) @parser.expects(:document_define).with("mydef", @definition, @container) @parser.parse_elements(@container) end it "should not document definitions parsed in an other file" do @definition.stubs(:file).returns("/not/same/path/file.pp") @resource_type_collection.add_definition(@definition) @parser.expects(:document_define).with("mydef", @definition, @container).never @parser.parse_elements(@container) end it "should document nodes in the parsed file" do @resource_type_collection.add_node(@node) @parser.expects(:document_node).with("mynode", @node, @container) @parser.parse_elements(@container) end it "should not document node parsed in an other file" do @node.stubs(:file).returns("/not/same/path/file.pp") @resource_type_collection.add_node(@node) @parser.expects(:document_node).with("mynode", @node, @container).never @parser.parse_elements(@container) end end describe "when documenting definition" do before(:each) do @define = stub_everything 'define', :arguments => [], :doc => "mydoc", :file => "file", :line => 42 @class = stub_everything 'class' @parser.stubs(:get_class_or_module).returns([@class, "mydef"]) end it "should register a RDoc method to the current container" do @class.expects(:add_method).with { |m| m.name == "mydef"} @parser.document_define("mydef", @define, @class) end it "should attach the documentation to this method" do @class.expects(:add_method).with { |m| m.comment = "mydoc" } @parser.document_define("mydef", @define, @class) end it "should produce a better error message on unhandled exception" do @class.expects(:add_method).raises(ArgumentError) lambda { @parser.document_define("mydef", @define, @class) }.should raise_error(Puppet::ParseError, /in file at line 42/) end it "should convert all definition parameter to string" do arg = stub 'arg' val = stub 'val' @define.stubs(:arguments).returns({arg => val}) arg.expects(:to_s).returns("arg") val.expects(:to_s).returns("val") @parser.document_define("mydef", @define, @class) end end describe "when documenting nodes" do before :each do @code = stub_everything 'code' @node = stub_everything 'node', :doc => "mydoc", :parent => "parent", :code => @code, :file => "file", :line => 42 @rdoc_node = stub_everything 'rdocnode' @class = stub_everything 'class' @class.stubs(:add_node).returns(@rdoc_node) end it "should add a node to the current container" do @class.expects(:add_node).with("mynode", "parent").returns(@rdoc_node) @parser.document_node("mynode", @node, @class) end it "should associate the node documentation to the rdoc node" do @rdoc_node.expects(:comment=).with("mydoc") @parser.document_node("mynode", @node, @class) end it "should scan for include and require" do @parser.expects(:scan_for_include_or_require).with(@rdoc_node, @code) @parser.document_node("mynode", @node, @class) end it "should scan for variable definition" do @parser.expects(:scan_for_vardef).with(@rdoc_node, @code) @parser.document_node("mynode", @node, @class) end it "should scan for resources if needed" do Puppet.settings.stubs(:[]).with(:document_all).returns(true) @parser.expects(:scan_for_resource).with(@rdoc_node, @code) @parser.document_node("mynode", @node, @class) end it "should produce a better error message on unhandled exception" do @class.stubs(:add_node).raises(ArgumentError) lambda { @parser.document_node("mynode", @node, @class) }.should raise_error(Puppet::ParseError, /in file at line 42/) end end describe "when documenting classes" do before :each do @code = stub_everything 'code' @class = stub_everything 'class', :doc => "mydoc", :parent => "parent", :code => @code, :file => "file", :line => 42 @rdoc_class = stub_everything 'rdoc-class' @module = stub_everything 'class' @module.stubs(:add_class).returns(@rdoc_class) @parser.stubs(:get_class_or_module).returns([@module, "myclass"]) end it "should add a class to the current container" do @module.expects(:add_class).with(RDoc::PuppetClass, "myclass", "parent").returns(@rdoc_class) @parser.document_class("mynode", @class, @module) end it "should set the superclass" do @rdoc_class.expects(:superclass=).with("parent") @parser.document_class("mynode", @class, @module) end it "should associate the node documentation to the rdoc class" do @rdoc_class.expects(:comment=).with("mydoc") @parser.document_class("mynode", @class, @module) end it "should scan for include and require" do @parser.expects(:scan_for_include_or_require).with(@rdoc_class, @code) @parser.document_class("mynode", @class, @module) end it "should scan for resources if needed" do Puppet.settings.stubs(:[]).with(:document_all).returns(true) @parser.expects(:scan_for_resource).with(@rdoc_class, @code) @parser.document_class("mynode", @class, @module) end it "should produce a better error message on unhandled exception" do @module.stubs(:add_class).raises(ArgumentError) lambda { @parser.document_class("mynode", @class, @module) }.should raise_error(Puppet::ParseError, /in file at line 42/) end end describe "when scanning for includes and requires" do def create_stmt(name) stmt_value = stub "#{name}_value", :to_s => "myclass" Puppet::Parser::AST::Function.new( :name => name, :arguments => [stmt_value], :doc => 'mydoc' ) end before(:each) do @class = stub_everything 'class' @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) end it "should also scan mono-instruction code" do @class.expects(:add_include).with { |i| i.is_a?(RDoc::Include) and i.name == "myclass" and i.comment == "mydoc" } @parser.scan_for_include_or_require(@class, create_stmt("include")) end it "should register recursively includes to the current container" do @code.stubs(:children).returns([ create_stmt("include") ]) @class.expects(:add_include)#.with { |i| i.is_a?(RDoc::Include) and i.name == "myclass" and i.comment == "mydoc" } @parser.scan_for_include_or_require(@class, [@code]) end it "should register requires to the current container" do @code.stubs(:children).returns([ create_stmt("require") ]) @class.expects(:add_require).with { |i| i.is_a?(RDoc::Include) and i.name == "myclass" and i.comment == "mydoc" } @parser.scan_for_include_or_require(@class, [@code]) end end describe "when scanning for realized virtual resources" do def create_stmt stmt_value = stub "resource_ref", :to_s => "File[\"/tmp/a\"]" Puppet::Parser::AST::Function.new( :name => 'realize', :arguments => [stmt_value], :doc => 'mydoc' ) end before(:each) do @class = stub_everything 'class' @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) end it "should also scan mono-instruction code" do @class.expects(:add_realize).with { |i| i.is_a?(RDoc::Include) and i.name == "File[\"/tmp/a\"]" and i.comment == "mydoc" } @parser.scan_for_realize(@class,create_stmt) end it "should register recursively includes to the current container" do @code.stubs(:children).returns([ create_stmt ]) @class.expects(:add_realize).with { |i| i.is_a?(RDoc::Include) and i.name == "File[\"/tmp/a\"]" and i.comment == "mydoc" } @parser.scan_for_realize(@class, [@code]) end end describe "when scanning for variable definition" do before :each do @class = stub_everything 'class' @stmt = stub_everything 'stmt', :name => "myvar", :value => "myvalue", :doc => "mydoc" @stmt.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(false) @stmt.stubs(:is_a?).with(Puppet::Parser::AST::VarDef).returns(true) @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) end it "should recursively register variables to the current container" do @code.stubs(:children).returns([ @stmt ]) @class.expects(:add_constant).with { |i| i.is_a?(RDoc::Constant) and i.name == "myvar" and i.comment == "mydoc" } @parser.scan_for_vardef(@class, [ @code ]) end it "should also scan mono-instruction code" do @class.expects(:add_constant).with { |i| i.is_a?(RDoc::Constant) and i.name == "myvar" and i.comment == "mydoc" } @parser.scan_for_vardef(@class, @stmt) end end describe "when scanning for resources" do before :each do @class = stub_everything 'class' @stmt = Puppet::Parser::AST::Resource.new( :type => "File", :instances => Puppet::Parser::AST::ASTArray.new(:children => [ Puppet::Parser::AST::ResourceInstance.new( :title => Puppet::Parser::AST::Name.new(:value => "myfile"), :parameters => Puppet::Parser::AST::ASTArray.new(:children => []) ) ]), :doc => 'mydoc' ) @code = stub_everything 'code' @code.stubs(:is_a?).with(Puppet::Parser::AST::ASTArray).returns(true) end it "should register a PuppetResource to the current container" do @code.stubs(:children).returns([ @stmt ]) @class.expects(:add_resource).with { |i| i.is_a?(RDoc::PuppetResource) and i.title == "myfile" and i.comment == "mydoc" } @parser.scan_for_resource(@class, [ @code ]) end it "should also scan mono-instruction code" do @class.expects(:add_resource).with { |i| i.is_a?(RDoc::PuppetResource) and i.title == "myfile" and i.comment == "mydoc" } @parser.scan_for_resource(@class, @stmt) end end describe "when parsing plugins" do before :each do @container = stub 'container' end it "should delegate parsing custom facts to parse_facts" do @parser = RDoc::Parser.new(@top_level, "module/manifests/lib/puppet/facter/test.rb", nil, Options.instance, RDoc::Stats.new) @parser.expects(:parse_fact).with(@container) @parser.parse_plugins(@container) end it "should delegate parsing plugins to parse_plugins" do @parser = RDoc::Parser.new(@top_level, "module/manifests/lib/puppet/functions/test.rb", nil, Options.instance, RDoc::Stats.new) @parser.expects(:parse_puppet_plugin).with(@container) @parser.parse_plugins(@container) end end describe "when parsing plugins" do before :each do @container = stub_everything 'container' end it "should add custom functions to the container" do File.stubs(:open).yields("# documentation module Puppet::Parser::Functions newfunction(:myfunc, :type => :rvalue) do |args| File.dirname(args[0]) end end".split("\n")) @container.expects(:add_plugin).with do |plugin| plugin.comment == "documentation\n" #and plugin.name == "myfunc" end @parser.parse_puppet_plugin(@container) end it "should add custom types to the container" do File.stubs(:open).yields("# documentation Puppet::Type.newtype(:mytype) do end".split("\n")) @container.expects(:add_plugin).with do |plugin| plugin.comment == "documentation\n" #and plugin.name == "mytype" end @parser.parse_puppet_plugin(@container) end end describe "when parsing facts" do before :each do @container = stub_everything 'container' File.stubs(:open).yields(["# documentation", "Facter.add('myfact') do", "confine :kernel => :linux", "end"]) end it "should add facts to the container" do @container.expects(:add_fact).with do |fact| fact.comment == "documentation\n" and fact.name == "myfact" end @parser.parse_fact(@container) end it "should add confine to the parsed facts" do ourfact = nil @container.expects(:add_fact).with do |fact| ourfact = fact true end @parser.parse_fact(@container) ourfact.confine.should == { :type => "kernel", :value => ":linux" } end end end