Hi,

Here is the second iteration for the puppetclean application.
This version takes into account review comments, and adds the
possibility to "unexport" exported resources (by unexport, I mean
that it puts ensure => absent if it is possible on those exported
resources instead of removing all storedconfig content).

I also moved the report destroy stuff to the various report processors (even
  though only the store report implements it for the moment), but I'm not sure
that was the right way of doing this. I couldn't see how to move the reports 
knowledge
directly into the node. I'm open for better alternatives.

Thanks,
Brice

Original commit msg:
Here is a changeset that adds a new puppet application to
the puppet application portfolio: puppetcleaner.

This application removes all traces of a node on the puppetmaster
(including certs, cached facts and nodes, reports, and storedconfig
entries).

Usage:
puppetclean <host>

Signed-off-by: Brice Figureau <brice-pup...@daysofwonder.com>
---
 bin/puppetclean                       |   65 +++++++++
 lib/puppet/application/puppetclean.rb |  118 +++++++++++++++++
 spec/unit/application/puppetclean.rb  |  235 +++++++++++++++++++++++++++++++++
 3 files changed, 418 insertions(+), 0 deletions(-)
 create mode 100644 bin/puppetclean
 create mode 100644 lib/puppet/application/puppetclean.rb
 create mode 100644 spec/unit/application/puppetclean.rb

diff --git a/bin/puppetclean b/bin/puppetclean
new file mode 100644
index 0000000..2e9243b
--- /dev/null
+++ b/bin/puppetclean
@@ -0,0 +1,65 @@
+#!/usr/bin/env ruby
+# vim: softtabstop=4 shiftwidth=4 expandtab
+#
+# = Synopsis
+#
+# Fully clean a puppetmaster view of a host
+#
+# = Usage
+#
+#   puppetclean [-h|--help] [-d|--debug] [-v|--verbose] [-u|--unexport] host
+#
+# = Description
+#
+# This command cleans everything a puppetmaster knows about a node, including
+# 
+# * Signed certificates ($vardir/ssl/ca/signed/node.domain.pem)
+# * Cached facts ($vardir/yaml/facts/node.domain.yaml)
+# * Cached node stuff ($vardir/yaml/node/node.domain.yaml)
+# * Reports ($vardir/reports/node.domain)
+# * Stored configs: it can either remove all data from an host in your 
storedconfig
+# database, or with --unexport turn every exported resource supporting ensure 
to absent
+# so that any other host checking out their config can remove those exported 
configurations.
+#
+# = Options
+#
+# Note that any configuration parameter that's valid in the configuration file
+# is also a valid long argument.  For example, 'ssldir' is a valid 
configuration
+# parameter, so you can specify '--ssldir <directory>' as an argument.
+#
+# See the configuration file documentation at
+# http://reductivelabs.com/projects/puppet/reference/configref.html for
+# the full list of acceptable parameters. A commented list of all
+# configuration options can also be generated by running puppet with
+# '--genconfig'.
+#
+# debug::
+#   Enable full debugging.
+#
+# help:
+#   Print this help message.
+#
+# verbose::
+#   Print extra information.
+#
+# unexport::
+#   Instead of removing all the storedconfig data, it changes all exported 
resource's ensure parameter
+#   of this host to "absent". Then it is necessary to wait that all other host 
have checked out their
+#   configuration to run again puppetclean whithout this option to finish the 
clean-up.
+#   
+#
+# = Example
+#
+# puppeclean some-random-host.reductivelabs.com
+#
+# = Author
+#
+# Brice Figureau
+#
+# = Copyright
+#
+# Copyright (c) 2005-2007 Reductive Labs, LLC
+# Licensed under the GNU Public License
+
+require 'puppet/application/puppetclean'
+Puppet::Application[:puppetclean].run
diff --git a/lib/puppet/application/puppetclean.rb 
b/lib/puppet/application/puppetclean.rb
new file mode 100644
index 0000000..71af71e
--- /dev/null
+++ b/lib/puppet/application/puppetclean.rb
@@ -0,0 +1,118 @@
+require 'puppet'
+require 'puppet/application'
+
+Puppet::Application.new(:puppetclean) do
+
+    should_not_parse_config
+
+    attr_accessor :host
+
+    option("--debug","-d")
+    option("--verbose","-v")
+    option("--unexport","-u")
+
+    command(:main) do
+        if ARGV.length > 0
+            host = ARGV.shift
+        else
+            raise "You must specify the host to clean"
+        end
+
+        [ :clean_cert, :clean_cached_facts, :clean_cached_node, 
:clean_reports, :clean_storeconfigs ].each do |m|
+            begin
+                send(m, host)
+            rescue => detail
+                puts detail.backtrace if Puppet[:trace]
+                puts detail.to_s
+            end
+        end
+    end
+
+    # clean signed cert for +host+
+    def clean_cert(host)
+        Puppet::SSL::Host.destroy(host)
+        Puppet.info "%s certificates removed from ca" % host
+    end
+
+    # clean facts for +host+
+    def clean_cached_facts(host)
+        Puppet::Node::Facts.destroy(host)
+        Puppet.info "%s's facts removed" % host
+    end
+
+    # clean cached node +host+
+    def clean_cached_node(host)
+        
Puppet::Node.indirection.cache.destroy(Puppet::Node.indirection.request(:destroy,
 host))
+        Puppet.info "%s's cached node removed" % host
+    end
+
+    # clean node reports for +host+
+    def clean_reports(host)
+        Puppet::Transaction::Report.destroy(host)
+        Puppet.info "%s's reports removed" % host
+    end
+
+    # clean store config for +host+
+    def clean_storeconfigs(host)
+        return unless Puppet.features.rails?
+
+        require 'puppet/rails'
+        Puppet::Rails.connect
+
+        return unless rail_host = Puppet::Rails::Host.find_by_name(host)
+
+        if options[:unexport]
+            unexport(rail_host)
+            Puppet.notice "Force %s's exported resources to absent" % host
+            Puppet.warning "Please wait other host have checked-out their 
configuration before finishing clean-up wih:"
+            Puppet.warning "$ puppetclean #{host}"
+        else
+            rail_host.destroy
+            Puppet.notice "%s storeconfigs removed" % host
+        end
+    end
+
+    def unexport(host)
+        # fetch all exported resource
+        query = {:include => {:param_values => :param_name}}
+        values = [true, host.id]
+        query[:conditions] = ["exported=? AND host_id=?", *values]
+
+        Puppet::Rails::Resource.find(:all, query).each do |resource|
+            if 
Puppet::Type.type(resource.restype.downcase.to_sym).validattr?(:ensure)
+                line = 0
+                param_name = 
Puppet::Rails::ParamName.find_or_create_by_name("ensure")
+
+                if ensure_param = resource.param_values.find(:first, 
:conditions => [ 'param_name_id = ?', param_name.id])
+                    line = ensure_param.line.to_i
+                    Puppet::Rails::ParamValue.delete(ensure_param.id);
+                end
+
+                # force ensure parameter to "absent"
+                resource.param_values.create(:value => "absent",
+                                               :line => line,
+                                               :param_name => param_name)
+                Puppet.info("%s has been marked as \"absent\"" % resource.name)
+            end
+        end
+    end
+
+    setup do
+        Puppet::Util::Log.newdestination(:console)
+
+        Puppet.parse_config
+
+        # let's pretend we are puppetmasterd to access the
+        # right configuration settings from puppet.conf
+        Puppet[:name] = "puppetmasterd"
+
+        if options[:debug]
+            Puppet::Util::Log.level = :debug
+        elsif options[:verbose]
+            Puppet::Util::Log.level = :info
+        end
+
+        Puppet::Node::Facts.terminus_class = :yaml
+        Puppet::Node.cache_class = :yaml
+    end
+end
diff --git a/spec/unit/application/puppetclean.rb 
b/spec/unit/application/puppetclean.rb
new file mode 100644
index 0000000..8bd6ec7
--- /dev/null
+++ b/spec/unit/application/puppetclean.rb
@@ -0,0 +1,235 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'puppet/application/puppetclean'
+
+describe "puppetclean" do
+    before :each do
+        @puppetclean = Puppet::Application[:puppetclean]
+    end
+
+    it "should not ask Puppet::Application to parse Puppet configuration file" 
do
+        @puppetclean.should_not be_should_parse_config
+    end
+
+    it "should declare a main command" do
+        @puppetclean.should respond_to(:main)
+    end
+
+    describe "when handling options" do
+        [:debug, :verbose, :unexport].each do |option|
+            it "should declare handle_#{option} method" do
+                @puppetclean.should respond_to("handle_#{option}".to_sym)
+            end
+
+            it "should store argument value when calling handle_#{option}" do
+                @puppetclean.options.expects(:[]=).with(option, 'arg')
+                @puppetclean.send("handle_#{option}".to_sym, 'arg')
+            end
+        end
+    end
+
+    describe "during setup" do
+        before :each do
+            Puppet::Log.stubs(:newdestination)
+            Puppet::Log.stubs(:level=)
+            Puppet.stubs(:parse_config)
+            Puppet.stubs(:[]=).with(:name, "puppetmasterd")
+            Puppet::Node::Facts.stubs(:terminus_class=)
+            Puppet::Node.stubs(:cache_class=)
+        end
+
+        it "should set console as the log destination" do
+            Puppet::Log.expects(:newdestination).with(:console)
+
+            @puppetclean.run_setup
+        end
+
+        it "should parse puppet configuration" do
+            Puppet.expects(:parse_config)
+
+            @puppetclean.run_setup
+        end
+
+        it "should change application name to get puppetmasterd options" do
+            Puppet.expects(:[]=).with(:name, "puppetmasterd")
+
+            @puppetclean.run_setup
+        end
+
+        it "should set log level to debug if --debug was passed" do
+            @puppetclean.options.stubs(:[]).with(:debug).returns(true)
+
+            Puppet::Log.expects(:level=).with(:debug)
+
+            @puppetclean.run_setup
+        end
+
+        it "should set log level to info if --verbose was passed" do
+            @puppetclean.options.stubs(:[]).with(:debug).returns(false)
+            @puppetclean.options.stubs(:[]).with(:verbose).returns(true)
+
+            Puppet::Log.expects(:level=).with(:info)
+
+            @puppetclean.run_setup
+        end
+
+        it "should set facts terminus to yaml" do
+            Puppet::Node::Facts.expects(:terminus_class=).with(:yaml)
+
+            @puppetclean.run_setup
+        end
+
+        it "should set node cache as yaml" do
+            Puppet::Node.expects(:cache_class=).with(:yaml)
+
+            @puppetclean.run_setup
+        end
+    end
+
+    describe "when running" do
+
+        before :each do
+           # @puppetclean.stubs(:puts)
+            @host = 'node'
+            ARGV.stubs(:shift).returns(@host)
+            ARGV.stubs(:length).returns(1)
+            Puppet.stubs(:info)
+            [ "cert", "cached_facts", "cached_node", "reports", "storeconfigs" 
].each do |m|
+                @puppetclean.stubs("clean_#{m}".to_sym).with(@host)
+            end
+        end
+
+        it "should raise an error if no type is given" do
+            ARGV.stubs(:length).returns(0)
+
+            lambda { @puppetclean.main }.should raise_error
+        end
+
+
+        [ "cert", "cached_facts", "cached_node", "reports", "storeconfigs" 
].each do |m|
+            it "should clean #{m.sub('_',' ')}" do
+                @puppetclean.expects("clean_#{m}".to_sym).with(@host)
+
+                @puppetclean.main
+            end
+        end
+    end
+
+    describe "when cleaning certificate" do
+        before :each do
+            Puppet::SSL::Host.stubs(:destroy)
+        end
+
+        it "should send the :destroy order to the SSL host infrastructure" do
+            Puppet::SSL::Host.stubs(:destroy).with(@host)
+
+            @puppetclean.clean_cert(@host)
+        end
+    end
+
+    describe "when cleaning cached facts" do
+        it "should destroy facts" do
+            Puppet::Node::Facts.expects(:destroy).with(@host)
+
+            @puppetclean.clean_cached_facts(@host)
+        end
+    end
+
+    describe "when cleaning cached node" do
+        it "should destroy the cached node" do
+            cache = stub_everything 'cache'
+            request = stub_everything 'request'
+
+            Puppet::Node.indirection.stubs(:request).with(:destroy, 
@host).returns(request)
+            Puppet::Node.indirection.stubs(:cache).returns(cache)
+
+            Puppet::Node.indirection.cache.expects(:destroy).with(request)
+
+            @puppetclean.clean_cached_node(@host)
+        end
+    end
+
+    describe "when cleaning archived reports" do
+        it "should tell the reports to remove themselves" do
+            Puppet::Transaction::Report.stubs(:destroy).with(@host)
+
+            @puppetclean.clean_reports(@host)
+        end
+    end
+
+    describe "when cleaning storeconfigs entries for host" do
+        before :each do
+            @puppetclean.options.stubs(:[]).with(:unexport).returns(false)
+            Puppet.features.stubs(:rails?).returns(true)
+            Puppet::Rails.stubs(:connect)
+            @rails_node = stub_everything 'rails_node'
+            Puppet::Rails::Host.stubs(:find_by_name).returns(@rails_node)
+            @rail_node.stubs(:destroy)
+        end
+
+        it "should connect to the database" do
+            Puppet::Rails.expects(:connect)
+
+            @puppetclean.clean_storeconfigs(@host)
+        end
+
+        it "should find the right host entry" do
+            
Puppet::Rails::Host.expects(:find_by_name).with(@host).returns(@rails_node)
+
+            @puppetclean.clean_storeconfigs(@host)
+        end
+
+        describe "without unexport" do
+            it "should remove the host and it's content" do
+                @rails_node.expects(:destroy)
+
+                @puppetclean.clean_storeconfigs(@host)
+            end
+        end
+
+        describe "with unexport" do
+            before :each do
+                @puppetclean.options.stubs(:[]).with(:unexport).returns(true)
+                @rails_node.stubs(:id).returns(1234)
+
+                @type = stub_everything 'type'
+                Puppet::Type.stubs(:type).returns(@type)
+                @type.stubs(:validattr?).with(:ensure).returns(true)
+
+                @ensure_name = stub_everything 'ensure_name', :id => 23453
+                
Puppet::Rails::ParamName.stubs(:find_or_create_by_name).returns(@ensure_name)
+
+                @param_values = stub_everything 'param_values'
+                @resource = stub_everything 'resource', :param_values => 
@param_values, :restype => "File"
+                Puppet::Rails::Resource.stubs(:find).returns([...@resource])
+            end
+
+            it "should find all resources" do
+                Puppet::Rails::Resource.expects(:find).with(:all, {:include => 
{:param_values => :param_name}, :conditions => ["exported=? AND host_id=?", 
true, 1234]}).returns([])
+
+                @puppetclean.clean_storeconfigs(@host)
+            end
+
+            it "should delete the old ensure parameter" do
+                ensure_param = stub 'ensure_param', :id => 12345, :line => 12
+                @param_values.stubs(:find).returns(ensure_param)
+
+                Puppet::Rails::ParamValue.expects(:delete).with(12345);
+
+                @puppetclean.clean_storeconfigs(@host)
+            end
+
+            it "should add an ensure => absent parameter" do
+                @param_values.expects(:create).with(:value => "absent",
+                                               :line => 0,
+                                               :param_name => @ensure_name)
+
+
+                @puppetclean.clean_storeconfigs(@host)
+            end
+
+        end
+    end
+end
-- 
1.6.0.2


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Puppet Developers" group.
To post to this group, send email to puppet-dev@googlegroups.com
To unsubscribe from this group, send email to 
puppet-dev+unsubscr...@googlegroups.com
For more options, visit this group at 
http://groups.google.com/group/puppet-dev?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to