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.
