diff --git a/lib/puppet/face/node/clean.rb b/lib/puppet/face/node/clean.rb index d2852de04..a4df1bfaf 100644 --- a/lib/puppet/face/node/clean.rb +++ b/lib/puppet/face/node/clean.rb @@ -1,154 +1,151 @@ Puppet::Face.define(:node, '0.0.1') do action(:clean) do option "--[no-]unexport" do summary "Unexport exported resources" end summary "Clean up everything a puppetmaster knows about a node" arguments " [ ...]" description <<-EOT This includes * 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. This will unexport exported resources of a host, so that consumers of these resources can remove the exported resources and we will safely remove the node from our infrastructure. EOT when_invoked do |*args| nodes = args[0..-2] options = args.last raise "At least one node should be passed" if nodes.empty? || nodes == options # TODO: this is a hack and should be removed if faces provide the proper # infrastructure to set the run mode. require 'puppet/util/run_mode' $puppet_application_mode = Puppet::Util::RunMode[:master] if Puppet::SSL::CertificateAuthority.ca? Puppet::SSL::Host.ca_location = :local else Puppet::SSL::Host.ca_location = :none end Puppet::Node::Facts.indirection.terminus_class = :yaml Puppet::Node::Facts.indirection.cache_class = :yaml Puppet::Node.indirection.terminus_class = :yaml Puppet::Node.indirection.cache_class = :yaml nodes.each { |node| cleanup(node.downcase, options[:unexport]) } end end def cleanup(node, unexport) clean_cert(node) clean_cached_facts(node) clean_cached_node(node) clean_reports(node) - - # This is roughly functional, but seems to introduce order-dependent test - # failures; this can be re-added when those issues are resolved. - # clean_storeconfigs(node, unexport) + clean_storeconfigs(node, unexport) end # clean signed cert for +host+ def clean_cert(node) if Puppet::SSL::CertificateAuthority.ca? Puppet::Face[:ca, :current].revoke(node) Puppet::Face[:ca, :current].destroy(node) Puppet.info "#{node} certificates removed from ca" else Puppet.info "Not managing #{node} certs as this host is not a CA" end end # clean facts for +host+ def clean_cached_facts(node) Puppet::Node::Facts.indirection.destroy(node) Puppet.info "#{node}'s facts removed" end # clean cached node +host+ def clean_cached_node(node) Puppet::Node.indirection.destroy(node) Puppet.info "#{node}'s cached node removed" end # clean node reports for +host+ def clean_reports(node) Puppet::Transaction::Report.indirection.destroy(node) Puppet.info "#{node}'s reports removed" end # clean storeconfig for +node+ def clean_storeconfigs(node, do_unexport=false) return unless Puppet[:storeconfigs] && Puppet.features.rails? require 'puppet/rails' Puppet::Rails.connect unless rails_node = Puppet::Rails::Host.find_by_name(node) Puppet.notice "No entries found for #{node} in storedconfigs." return end if do_unexport unexport(rails_node) Puppet.notice "Force #{node}'s exported resources to absent" Puppet.warning "Please wait until all other hosts have checked out their configuration before finishing the cleanup with:" Puppet.warning "$ puppet node clean #{node}" else rails_node.destroy Puppet.notice "#{node} storeconfigs removed" end end def unexport(node) # fetch all exported resource query = {:include => {:param_values => :param_name}} query[:conditions] = [ "exported=? AND host_id=?", true, node.id ] Puppet::Rails::Resource.find(:all, query).each do |resource| if type_is_ensurable(resource) 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("#{resource.name} has been marked as \"absent\"") end end end def environment @environment ||= Puppet::Node::Environment.new end def type_is_ensurable(resource) if (type = Puppet::Type.type(resource.restype)) && type.validattr?(:ensure) return true else type = environment.known_resource_types.find_definition('', resource.restype) return true if type && type.arguments.keys.include?('ensure') end return false end end diff --git a/spec/unit/face/node_spec.rb b/spec/unit/face/node_spec.rb index cb36e58a4..b126af6bb 100755 --- a/spec/unit/face/node_spec.rb +++ b/spec/unit/face/node_spec.rb @@ -1,271 +1,271 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/face' describe Puppet::Face[:node, '0.0.1'] do after :all do Puppet::SSL::Host.ca_location = :none end describe '#cleanup' do it "should clean everything" do { "cert" => ['hostname'], "cached_facts" => ['hostname'], "cached_node" => ['hostname'], "reports" => ['hostname'], - - # Support for cleaning storeconfigs has been temporarily suspended. - # "storeconfigs" => ['hostname', :unexport] + "storeconfigs" => ['hostname', :unexport] }.each { |k, v| subject.expects("clean_#{k}".to_sym).with(*v) } subject.cleanup('hostname', :unexport) end end describe 'when running #clean' do before :each do Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) end it 'should invoke #cleanup' do subject.expects(:cleanup).with('hostname', nil) subject.clean('hostname') end end describe "clean action" do before :each do Puppet::Node::Facts.indirection.stubs(:terminus_class=) Puppet::Node::Facts.indirection.stubs(:cache_class=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) subject.stubs(:cleanup) end it "should have a clean action" do subject.should be_action :clean end it "should not accept a call with no arguments" do expect { subject.clean() }.should raise_error end it "should accept a node name" do expect { subject.clean('hostname') }.should_not raise_error end it "should accept more than one node name" do expect do subject.clean('hostname', 'hostname2', {}) end.should_not raise_error expect do subject.clean('hostname', 'hostname2', 'hostname3', { :unexport => true }) end.should_not raise_error end it "should accept the option --unexport" do expect { subject.help('hostname', :unexport => true) }. should_not raise_error ArgumentError end context "clean action" do subject { Puppet::Face[:node, :current] } before :each do Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) end describe "during setup" do it "should set facts terminus and cache class to yaml" do Puppet::Node::Facts.indirection.expects(:terminus_class=).with(:yaml) Puppet::Node::Facts.indirection.expects(:cache_class=).with(:yaml) subject.clean('hostname') end it "should run in master mode" do subject.clean('hostname') $puppet_application_mode.name.should == :master end it "should set node cache as yaml" do Puppet::Node.indirection.expects(:terminus_class=).with(:yaml) Puppet::Node.indirection.expects(:cache_class=).with(:yaml) subject.clean('hostname') end it "should manage the certs if the host is a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:local) subject.clean('hostname') end it "should not manage the certs if the host is not a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) Puppet::SSL::Host.expects(:ca_location=).with(:none) subject.clean('hostname') end end describe "when cleaning certificate" do before :each do Puppet::SSL::Host.stubs(:destroy) @ca = mock() Puppet::SSL::CertificateAuthority.stubs(:instance).returns(@ca) end it "should send the :destroy order to the ca if we are a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) @ca.expects(:revoke).with(@host) @ca.expects(:destroy).with(@host) subject.clean_cert(@host) end it "should not destroy the certs if we are not a CA" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(false) @ca.expects(:revoke).never @ca.expects(:destroy).never subject.clean_cert(@host) end end describe "when cleaning cached facts" do it "should destroy facts" do @host = 'node' Puppet::Node::Facts.indirection.expects(:destroy).with(@host) subject.clean_cached_facts(@host) end end describe "when cleaning cached node" do it "should destroy the cached node" do Puppet::Node.indirection.expects(:destroy).with(@host) subject.clean_cached_node(@host) end end describe "when cleaning archived reports" do it "should tell the reports to remove themselves" do Puppet::Transaction::Report.indirection.stubs(:destroy).with(@host) subject.clean_reports(@host) end end - # describe "when cleaning storeconfigs entries for host", :if => Puppet.features.rails? do - # before :each do - # # Stub this so we don't need access to the DB - # require 'puppet/rails/host' - # - # Puppet.stubs(:[]).with(:storeconfigs).returns(true) - # - # Puppet::Rails.stubs(:connect) - # @rails_node = stub_everything 'rails_node' - # Puppet::Rails::Host.stubs(:find_by_name).returns(@rails_node) - # end - # - # it "should connect to the database" do - # Puppet::Rails.expects(:connect) - # subject.clean_storeconfigs(@host, false) - # end - # - # it "should find the right host entry" do - # Puppet::Rails::Host.expects(:find_by_name).with(@host).returns(@rails_node) - # subject.clean_storeconfigs(@host, false) - # end - # - # describe "without unexport" do - # it "should remove the host and it's content" do - # @rails_node.expects(:destroy) - # subject.clean_storeconfigs(@host, false) - # end - # end - # - # describe "with unexport" do - # before :each do - # @rails_node.stubs(:id).returns(1234) - # - # @type = stub_everything '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([]) - # - # subject.clean_storeconfigs(@host, true) - # end - # - # describe "with an exported native type" do - # before :each do - # Puppet::Type.stubs(:type).returns(@type) - # @type.expects(:validattr?).with(:ensure).returns(true) - # end - # - # it "should test a native type for ensure as an attribute" do - # subject.clean_storeconfigs(@host, true) - # 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); - # subject.clean_storeconfigs(@host, true) - # end - # - # it "should add an ensure => absent parameter" do - # @param_values.expects(:create).with(:value => "absent", - # :line => 0, - # :param_name => @ensure_name) - # subject.clean_storeconfigs(@host, true) - # end - # end - # - # describe "with an exported definition" do - # it "should try to lookup a definition and test it for the ensure argument" do - # Puppet::Type.stubs(:type).returns(nil) - # definition = stub_everything 'definition', :arguments => { 'ensure' => 'present' } - # Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(definition) - # subject.clean_storeconfigs(@host, true) - # end - # end - # - # it "should not unexport the resource of an unknown type" do - # Puppet::Type.stubs(:type).returns(nil) - # Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(nil) - # Puppet::Rails::ParamName.expects(:find_or_create_by_name).never - # subject.clean_storeconfigs(@host) - # end - # - # it "should not unexport the resource of a not ensurable native type" do - # Puppet::Type.stubs(:type).returns(@type) - # @type.expects(:validattr?).with(:ensure).returns(false) - # Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(nil) - # Puppet::Rails::ParamName.expects(:find_or_create_by_name).never - # subject.clean_storeconfigs(@host, true) - # end - # - # it "should not unexport the resource of a not ensurable definition" do - # Puppet::Type.stubs(:type).returns(nil) - # definition = stub_everything 'definition', :arguments => { 'foobar' => 'someValue' } - # Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(definition) - # Puppet::Rails::ParamName.expects(:find_or_create_by_name).never - # subject.clean_storeconfigs(@host, true) - # end - # end - # end + describe "when cleaning storeconfigs entries for host", :if => Puppet.features.rails? do + before :each do + # Stub this so we don't need access to the DB + require 'puppet/rails/host' + + Puppet.stubs(:[]).with(:storeconfigs).returns(true) + + Puppet::Rails.stubs(:connect) + @rails_node = stub_everything 'rails_node' + Puppet::Rails::Host.stubs(:find_by_name).returns(@rails_node) + end + + it "should connect to the database" do + Puppet::Rails.expects(:connect) + subject.clean_storeconfigs(@host, false) + end + + it "should find the right host entry" do + Puppet::Rails::Host.expects(:find_by_name).with(@host).returns(@rails_node) + + subject.clean_storeconfigs(@host, false) + end + + describe "without unexport" do + it "should remove the host and it's content" do + @rails_node.expects(:destroy) + + subject.clean_storeconfigs(@host, false) + end + end + + describe "with unexport" do + before :each do + @rails_node.stubs(:id).returns(1234) + + @type = stub_everything '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([]) + + subject.clean_storeconfigs(@host, true) + end + + describe "with an exported native type" do + before :each do + Puppet::Type.stubs(:type).returns(@type) + @type.expects(:validattr?).with(:ensure).returns(true) + end + + it "should test a native type for ensure as an attribute" do + subject.clean_storeconfigs(@host, true) + 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); + subject.clean_storeconfigs(@host, true) + end + + it "should add an ensure => absent parameter" do + @param_values.expects(:create).with(:value => "absent", + :line => 0, + :param_name => @ensure_name) + subject.clean_storeconfigs(@host, true) + end + end + + describe "with an exported definition" do + it "should try to lookup a definition and test it for the ensure argument" do + Puppet::Type.stubs(:type).returns(nil) + definition = stub_everything 'definition', :arguments => { 'ensure' => 'present' } + Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(definition) + subject.clean_storeconfigs(@host, true) + end + end + + it "should not unexport the resource of an unkown type" do + Puppet::Type.stubs(:type).returns(nil) + Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(nil) + Puppet::Rails::ParamName.expects(:find_or_create_by_name).never + subject.clean_storeconfigs(@host, true) + end + + it "should not unexport the resource of a not ensurable native type" do + Puppet::Type.stubs(:type).returns(@type) + @type.expects(:validattr?).with(:ensure).returns(false) + Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(nil) + Puppet::Rails::ParamName.expects(:find_or_create_by_name).never + subject.clean_storeconfigs(@host, true) + end + + it "should not unexport the resource of a not ensurable definition" do + Puppet::Type.stubs(:type).returns(nil) + definition = stub_everything 'definition', :arguments => { 'foobar' => 'someValue' } + Puppet::Resource::TypeCollection.any_instance.expects(:find_definition).with('', "File").returns(definition) + Puppet::Rails::ParamName.expects(:find_or_create_by_name).never + subject.clean_storeconfigs(@host, true) + end + end + end end end end