From: Brice Figureau <[email protected]>

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 <[email protected]>
Signed-off-by: Peter Meier <[email protected]>
---
 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.7.2.3

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

Reply via email to