[Pkg-puppet-devel] [SCM] Puppet packaging for Debian branch, experimental, updated. debian/2.6.8-1-844-g7ec39d5
Daniel Pittman
daniel at puppetlabs.com
Tue May 10 08:11:37 UTC 2011
The following commit has been merged in the experimental branch:
commit 0f24db1c3fd0aa6601ba032aedbe25be36010954
Merge: af792351a62399f8bbeba0d2425f2b88eabb3dff 79f4774182046d2fdf392c1eb27ee78505659199
Author: Daniel Pittman <daniel at puppetlabs.com>
Date: Thu Apr 7 17:58:07 2011 -0700
Merge puppet-interfaces into puppet.
This joins the two repositories, including full history, into a single run, as
well as landing the interfaces work on the next branch ready for release.
diff --combined README.strings
index 0000000,28289ee..28289ee
mode 000000,100644..100644
--- a/README.strings
+++ b/README.strings
@@@ -1,0 -1,115 +1,115 @@@
+ Puppet Strings
+ =================
+ A set of executables that provide complete CLI access to Puppet's
+ core data types. They also provide String classes for
+ each of the core data types, which are extensible via plugins.
+
+ For instance, you can create a new action for catalogs at
+ lib/puppet/string/catalog/$action.rb.
+
+ This is a Puppet module and should work fine if you install it
+ in Puppet's module path.
+
+ **Note that this only works with Puppet 2.6.next (and thus will work
+ with 2.6.5), because there is otherwise a bug in finding Puppet applications.
+ You also have to either install the lib files into your Puppet libdir, or
+ you need to add this lib directory to your RUBYLIB.**
+
+ This is meant to be tested and iterated upon, with the plan that it will be
+ merged into Puppet core once we're satisfied with it.
+
+ Usage
+ -----
+ The general usage is:
+
+ $ puppet <string> <verb> <name>
+
+ So, e.g.:
+
+ $ puppet facts find myhost.domain.com
+ $ puppet node destroy myhost
+
+ You can use it to list all known data types and the available terminus classes:
+
+ $ puppet string list
+ catalog : active_record, compiler, queue, rest, yaml
+ certificate : ca, file, rest
+ certificate_request : ca, file, rest
+ certificate_revocation_list : ca, file, rest
+ file_bucket_file : file, rest
+ inventory : yaml
+ key : ca, file
+ node : active_record, exec, ldap, memory, plain, rest, yaml
+ report : processor, rest, yaml
+ resource : ral, rest
+ resource_type : parser, rest
+ status : local, rest
+
+ But most interestingly, you can use it for two main purposes:
+
+ * As a client for any Puppet REST server, such as catalogs, facts, reports, etc.
+ * As a local CLI for any local Puppet data
+
+ A simple case is looking at the local facts:
+
+ $ puppet facts find localhost
+
+ If you're on the server, you can look in that server's fact collection:
+
+ $ puppet facts --mode master --vardir /tmp/foo --terminus yaml find localhost
+
+ Note that we're setting both the vardir and the 'mode', which switches from the default 'agent' mode to server mode (requires a patch in my branch).
+
+ If you'd prefer the data be outputted in json instead of yaml, well, you can do that, too:
+
+ $ puppet find --mode master facts --vardir /tmp/foo --terminus yaml --format pson localhost
+
+ To test using it as an endpoint for compiling and retrieving catalogs from a remote server, (from my commit), try this:
+
+ # Terminal 1
+ $ sbin/puppetmasterd --trace --confdir /tmp/foo --vardir /tmp/foo --debug --manifest ~/bin/test.pp --certname localhost --no-daemonize
+
+ # Terminal 2
+ $ sbin/puppetd --trace --debug --confdir /tmp/foo --vardir /tmp/foo --certname localhost --server localhost --test --report
+
+ # Terminal 3, actual testing
+ $ puppet catalog find localhost --certname localhost --server localhost --mode master --confdir /tmp/foo --vardir /tmp/foo --trace --terminus rest
+
+ This compiles a test catalog (assuming that ~/bin/test.pp exists) and returns it. With the right auth setup, you can also get facts:
+
+ $ puppet facts find localhost --certname localhost --server localhost --mode master --confdir /tmp/foo --vardir /tmp/foo --trace --terminus rest
+
+ Or use IRB to do the same thing:
+
+ $ irb
+ >> require 'puppet/string'
+ => true
+ >> string = Puppet::String[:facts, '1.0.0']
+ => #<Puppet::String::Facts:0x1024a1390 @format=:yaml>
+ >> facts = string.find("myhost")
+
+ Like I said, a prototype, but I'd love it if people would play it with some and make some recommendations.
+
+ Extending
+ ---------
+ Like most parts of Puppet, these are easy to extend. Just drop a new action into a given string's directory. E.g.:
+
+ $ cat lib/puppet/string/catalog/select.rb
+ # Select and show a list of resources of a given type.
+ Puppet::String.define(:catalog, '1.0.0') do
+ action :select do
+ invoke do |host,type|
+ catalog = Puppet::Resource::Catalog.indirection.find(host)
+
+ catalog.resources.reject { |res| res.type != type }.each { |res| puts res }
+ end
+ end
+ end
+ $ puppet catalog select localhost Class
+ Class[main]
+ Class[Settings]
+ $
+
+ Notice that this gets loaded automatically when you try to use it. So, if you have a simple command you've written, such as for cleaning up nodes or diffing catalogs, you an port it to this framework and it should fit cleanly.
+
+ Also note that strings are versioned. These version numbers are interpreted according to Semantic Versioning (http://semver.org).
diff --combined lib/puppet/application/faces_base.rb
index 0000000,6d66ee8..288b500
mode 000000,100644..100644
--- a/lib/puppet/application/faces_base.rb
+++ b/lib/puppet/application/faces_base.rb
@@@ -1,0 -1,150 +1,150 @@@
+ require 'puppet/application'
+ require 'puppet/faces'
+
+ class Puppet::Application::FacesBase < Puppet::Application
+ should_parse_config
+ run_mode :agent
+
+ option("--debug", "-d") do |arg|
+ Puppet::Util::Log.level = :debug
+ end
+
+ option("--verbose", "-v") do
+ Puppet::Util::Log.level = :info
+ end
+
+ option("--format FORMAT") do |arg|
+ @format = arg.to_sym
+ end
+
+ option("--mode RUNMODE", "-r") do |arg|
+ raise "Invalid run mode #{arg}; supported modes are user, agent, master" unless %w{user agent master}.include?(arg)
+ self.class.run_mode(arg.to_sym)
+ set_run_mode self.class.run_mode
+ end
+
+
+ attr_accessor :face, :action, :type, :arguments, :format
+ attr_writer :exit_code
+
+ # This allows you to set the exit code if you don't want to just exit
+ # immediately but you need to indicate a failure.
+ def exit_code
+ @exit_code || 0
+ end
+
+ # Override this if you need custom rendering.
+ def render(result)
+ render_method = Puppet::Network::FormatHandler.format(format).render_method
+ if render_method == "to_pson"
+ jj result
+ exit(0)
+ else
+ result.send(render_method)
+ end
+ end
+
+ def preinit
+ super
- trap(:INT) do
++ Signal.trap(:INT) do
+ $stderr.puts "Cancelling Face"
+ exit(0)
+ end
+
+ # We need to parse enough of the command line out early, to identify what
+ # the action is, so that we can obtain the full set of options to parse.
+
+ # TODO: These should be configurable versions, through a global
+ # '--version' option, but we don't implement that yet... --daniel 2011-03-29
+ @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym
+ @face = Puppet::Faces[@type, :current]
+ @format = @face.default_format
+
+ # Now, walk the command line and identify the action. We skip over
+ # arguments based on introspecting the action and all, and find the first
+ # non-option word to use as the action.
+ action = nil
+ index = -1
+ until @action or (index += 1) >= command_line.args.length do
+ item = command_line.args[index]
+ if item =~ /^-/ then
+ option = @face.options.find do |name|
+ item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/
+ end
+ if option then
+ option = @face.get_option(option)
+ # If we have an inline argument, just carry on. We don't need to
+ # care about optional vs mandatory in that case because we do a real
+ # parse later, and that will totally take care of raising the error
+ # when we get there. --daniel 2011-04-04
+ if option.takes_argument? and !item.index('=') then
+ index += 1 unless
+ (option.optional_argument? and command_line.args[index + 1] =~ /^-/)
+ end
+ elsif option = find_global_settings_argument(item) then
+ unless Puppet.settings.boolean? option.name then
+ # As far as I can tell, we treat non-bool options as always having
+ # a mandatory argument. --daniel 2011-04-05
+ index += 1 # ...so skip the argument.
+ end
+ else
+ raise ArgumentError, "Unknown option #{item.sub(/=.*$/, '').inspect}"
+ end
+ else
+ action = @face.get_action(item.to_sym)
+ if action.nil? then
+ raise ArgumentError, "#{@face} does not have an #{item.inspect} action!"
+ end
+ @action = action
+ end
+ end
+
+ @action or raise ArgumentError, "No action given on the command line!"
+
+ # Finally, we can interact with the default option code to build behaviour
+ # around the full set of options we now know we support.
+ @action.options.each do |option|
+ option = @action.get_option(option) # make it the object.
+ self.class.option(*option.optparse) # ...and make the CLI parse it.
+ end
+ end
+
+ def find_global_settings_argument(item)
+ Puppet.settings.each do |name, object|
+ object.optparse_args.each do |arg|
+ next unless arg =~ /^-/
+ # sadly, we have to emulate some of optparse here...
+ pattern = /^#{arg.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/
+ pattern.match item and return object
+ end
+ end
+ return nil # nothing found.
+ end
+
+ def setup
+ Puppet::Util::Log.newdestination :console
+
+ @arguments = command_line.args
+
+ # Note: because of our definition of where the action is set, we end up
+ # with it *always* being the first word of the remaining set of command
+ # line arguments. So, strip that off when we construct the arguments to
+ # pass down to the face action. --daniel 2011-04-04
+ @arguments.delete_at(0)
+
+ # We copy all of the app options to the end of the call; This allows each
+ # action to read in the options. This replaces the older model where we
+ # would invoke the action with options set as global state in the
+ # interface object. --daniel 2011-03-28
+ @arguments << options
+ end
+
+
+ def main
+ # Call the method associated with the provided action (e.g., 'find').
+ if result = @face.send(@action.name, *arguments)
+ puts render(result)
+ end
+ exit(exit_code)
+ end
+ end
diff --combined lib/puppet/faces/catalog.rb
index 0000000,2e2168a..3353d5d
mode 000000,100644..100644
--- a/lib/puppet/faces/catalog.rb
+++ b/lib/puppet/faces/catalog.rb
@@@ -1,0 -1,40 +1,40 @@@
+ require 'puppet/faces/indirector'
+
+ Puppet::Faces::Indirector.define(:catalog, '0.0.1') do
+ action(:apply) do
+ when_invoked do |catalog, options|
+ report = Puppet::Transaction::Report.new("apply")
+ report.configuration_version = catalog.version
+
+ Puppet::Util::Log.newdestination(report)
+
+ begin
+ benchmark(:notice, "Finished catalog run") do
+ catalog.apply(:report => report)
+ end
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Failed to apply catalog: #{detail}"
+ end
+
+ report.finalize_report
+ report
+ end
+ end
+
+ action(:download) do
+ when_invoked do |certname, facts, options|
- Puppet::Resource::Catalog.terminus_class = :rest
++ Puppet::Resource::Catalog.indirection.terminus_class = :rest
+ facts_to_upload = {:facts_format => :b64_zlib_yaml, :facts => CGI.escape(facts.render(:b64_zlib_yaml))}
+ catalog = nil
+ retrieval_duration = thinmark do
+ catalog = Puppet::Faces[:catalog, '0.0.1'].find(certname, facts_to_upload)
+ end
+ catalog = catalog.to_ral
+ catalog.finalize
+ catalog.retrieval_duration = retrieval_duration
+ catalog.write_class_file
+ catalog
+ end
+ end
+ end
diff --combined spec/lib/puppet/faces/huzzah.rb
index 0000000,0000000..7350044
new file mode 100644
--- /dev/null
+++ b/spec/lib/puppet/faces/huzzah.rb
@@@ -1,0 -1,0 +1,4 @@@
++require 'puppet/faces'
++Puppet::Faces.define(:huzzah, '2.0.1') do
++ action :bar do "is where beer comes from" end
++end
diff --combined spec/spec_helper.rb
index 1347042,615c9d3..fc63c6d
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@@ -1,46 -1,21 +1,48 @@@
-require 'pathname'
-dir = Pathname.new(__FILE__).parent
-$LOAD_PATH.unshift(dir, dir + 'lib', dir + '../lib')
+unless defined?(SPEC_HELPER_IS_LOADED)
+SPEC_HELPER_IS_LOADED = 1
+
+dir = File.expand_path(File.dirname(__FILE__))
+
+$LOAD_PATH.unshift("#{dir}/")
+$LOAD_PATH.unshift("#{dir}/lib") # a spec-specific test lib dir
+$LOAD_PATH.unshift("#{dir}/../lib")
+
+# Don't want puppet getting the command line arguments for rake or autotest
+ARGV.clear
-require 'mocha'
require 'puppet'
-require 'puppet/faces'
-require 'rspec'
+require 'mocha'
+gem 'rspec', '>=2.0.0'
+
+# So everyone else doesn't have to include this base constant.
+module PuppetSpec
+ FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") unless defined?(FIXTURE_DIR)
+end
+
+require 'pathname'
+ require 'tmpdir'
+
+require 'lib/puppet_spec/verbose'
+require 'lib/puppet_spec/files'
+require 'lib/puppet_spec/fixtures'
+require 'monkey_patches/alias_should_to_must'
+require 'monkey_patches/publicize_methods'
+
Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour|
- require behaviour.relative_path_from(dir)
+ require behaviour.relative_path_from(Pathname.new(dir))
end
RSpec.configure do |config|
+ include PuppetSpec::Fixtures
+
config.mock_with :mocha
config.before :each do
+ # these globals are set by Application
+ $puppet_application_mode = nil
+ $puppet_application_name = nil
+ Signal.stubs(:trap)
+
# Set the confdir and vardir to gibberish so that tests
# have to be correctly mocked.
Puppet[:confdir] = "/dev/null"
@@@ -50,20 -25,35 +52,21 @@@
Puppet.settings[:bindaddress] = "127.0.0.1"
@logs = []
- Puppet::Util::Log.newdestination(@logs)
-
- @load_path_scratch_dir = Dir.mktmpdir
- $LOAD_PATH.push @load_path_scratch_dir
- FileUtils.mkdir_p(File.join @load_path_scratch_dir, 'puppet', 'faces')
+ Puppet::Util::Log.newdestination(Puppet::Test::LogCollector.new(@logs))
end
config.after :each do
Puppet.settings.clear
+ Puppet::Node::Environment.clear
+ Puppet::Util::Storage.clear
+ Puppet::Util::ExecutionStub.reset
+
+ PuppetSpec::Files.cleanup
@logs.clear
Puppet::Util::Log.close_all
end
end
-# We need this because the RAL uses 'should' as a method. This
-# allows us the same behaviour but with a different method name.
-class Object
- alias :must :should
++# close of the "don't evaluate twice" mess.
end
diff --combined spec/unit/faces/catalog_spec.rb
index 0000000,7197219..e0a771d
mode 000000,100755..100755
--- a/spec/unit/faces/catalog_spec.rb
+++ b/spec/unit/faces/catalog_spec.rb
@@@ -1,0 -1,3 +1,4 @@@
++require 'puppet/faces'
+ describe Puppet::Faces[:catalog, '0.0.1'] do
+ it "should actually have some testing..."
+ end
diff --combined spec/unit/interface/face_collection_spec.rb
index 0000000,de6d29c..bf3801e
mode 000000,100755..100755
--- a/spec/unit/interface/face_collection_spec.rb
+++ b/spec/unit/interface/face_collection_spec.rb
@@@ -1,0 -1,184 +1,175 @@@
+ #!/usr/bin/env ruby
-
+ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
++
+ require 'tmpdir'
++require 'puppet/interface/face_collection'
+
+ describe Puppet::Interface::FaceCollection do
+ # To avoid cross-pollution we have to save and restore both the hash
+ # containing all the interface data, and the array used by require. Restoring
+ # both means that we don't leak side-effects across the code. --daniel 2011-04-06
+ before :each do
+ @original_faces = subject.instance_variable_get("@faces").dup
+ @original_required = $".dup
+ subject.instance_variable_get("@faces").clear
+ end
+
+ after :each do
+ subject.instance_variable_set("@faces", @original_faces)
+ $".clear ; @original_required.each do |item| $" << item end
+ end
+
+ describe "::faces" do
+ it "REVISIT: should have some tests here, if we describe it"
+ end
+
+ describe "::validate_version" do
+ it 'should permit three number versions' do
+ subject.validate_version('10.10.10').should == true
+ end
+
+ it 'should permit versions with appended descriptions' do
+ subject.validate_version('10.10.10beta').should == true
+ end
+
+ it 'should not permit versions with more than three numbers' do
+ subject.validate_version('1.2.3.4').should == false
+ end
+
+ it 'should not permit versions with only two numbers' do
+ subject.validate_version('10.10').should == false
+ end
+
+ it 'should not permit versions with only one number' do
+ subject.validate_version('123').should == false
+ end
+
+ it 'should not permit versions with text in any position but at the end' do
+ subject.validate_version('v1.1.1').should == false
+ end
+ end
+
+ describe "::[]" do
+ before :each do
+ subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10
+ end
+
+ before :each do
+ @dir = Dir.mktmpdir
+ @lib = FileUtils.mkdir_p(File.join @dir, 'puppet', 'faces')
+ $LOAD_PATH.push(@dir)
+ end
+
+ after :each do
+ FileUtils.remove_entry_secure @dir
+ $LOAD_PATH.pop
+ end
+
+ it "should return the faces with the given name" do
+ subject["foo", '0.0.1'].should == 10
+ end
+
+ it "should attempt to load the faces if it isn't found" do
+ subject.expects(:require).with('puppet/faces/bar')
+ subject["bar", '0.0.1']
+ end
+
+ it "should attempt to load the default faces for the specified version :current" do
+ subject.expects(:require).never # except...
+ subject.expects(:require).with('puppet/faces/fozzie')
+ subject['fozzie', :current]
+ end
+ end
+
+ describe "::face?" do
+ before :each do
+ subject.instance_variable_get("@faces")[:foo]['0.0.1'] = 10
+ end
+
+ it "should return true if the faces specified is registered" do
+ subject.face?("foo", '0.0.1').should == true
+ end
+
+ it "should attempt to require the faces if it is not registered" do
+ subject.expects(:require).with do |file|
+ subject.instance_variable_get("@faces")[:bar]['0.0.1'] = true
+ file == 'puppet/faces/bar'
+ end
+ subject.face?("bar", '0.0.1').should == true
+ end
+
+ it "should return true if requiring the faces registered it" do
+ subject.stubs(:require).with do
+ subject.instance_variable_get("@faces")[:bar]['0.0.1'] = 20
+ end
+ end
+
+ it "should return false if the faces is not registered" do
+ subject.stubs(:require).returns(true)
+ subject.face?("bar", '0.0.1').should be_false
+ end
+
+ it "should return false if the faces file itself is missing" do
+ subject.stubs(:require).
+ raises(LoadError, 'no such file to load -- puppet/faces/bar')
+ subject.face?("bar", '0.0.1').should be_false
+ end
+
+ it "should register the version loaded by `:current` as `:current`" do
+ subject.expects(:require).with do |file|
+ subject.instance_variable_get("@faces")[:huzzah]['2.0.1'] = :huzzah_faces
+ file == 'puppet/faces/huzzah'
+ end
+ subject.face?("huzzah", :current)
+ subject.instance_variable_get("@faces")[:huzzah][:current].should == :huzzah_faces
+ end
+
+ context "with something on disk" do
- before :each do
- write_scratch_faces :huzzah do |fh|
- fh.puts <<EOF
-Puppet::Faces.define(:huzzah, '2.0.1') do
- action :bar do "is where beer comes from" end
-end
-EOF
- end
- end
-
+ it "should register the version loaded from `puppet/faces/{name}` as `:current`" do
+ subject.should be_face "huzzah", '2.0.1'
+ subject.should be_face "huzzah", :current
+ Puppet::Faces[:huzzah, '2.0.1'].should == Puppet::Faces[:huzzah, :current]
+ end
+
+ it "should index :current when the code was pre-required" do
+ subject.instance_variable_get("@faces")[:huzzah].should_not be_key :current
+ require 'puppet/faces/huzzah'
+ subject.face?(:huzzah, :current).should be_true
+ end
+ end
+ end
+
+ describe "::register" do
+ it "should store the faces by name" do
+ faces = Puppet::Faces.new(:my_faces, '0.0.1')
+ subject.register(faces)
+ subject.instance_variable_get("@faces").should == {:my_faces => {'0.0.1' => faces}}
+ end
+ end
+
+ describe "::underscorize" do
+ faulty = [1, "#foo", "$bar", "sturm und drang", :"sturm und drang"]
+ valid = {
+ "Foo" => :foo,
+ :Foo => :foo,
+ "foo_bar" => :foo_bar,
+ :foo_bar => :foo_bar,
+ "foo-bar" => :foo_bar,
+ :"foo-bar" => :foo_bar,
+ }
+
+ valid.each do |input, expect|
+ it "should map #{input.inspect} to #{expect.inspect}" do
+ result = subject.underscorize(input)
+ result.should == expect
+ end
+ end
+
+ faulty.each do |input|
+ it "should fail when presented with #{input.inspect} (#{input.class})" do
+ expect { subject.underscorize(input) }.
+ should raise_error ArgumentError, /not a valid face name/
+ end
+ end
+ end
+ end
--
Puppet packaging for Debian
More information about the Pkg-puppet-devel
mailing list